Files
MinecraftForge/src/main/java/net/minecraftforge/registries/GameData.java
T

750 lines
37 KiB
Java
Raw Normal View History

2017-06-19 15:02:18 -07:00
/*
* Copyright (c) Forge Development LLC and contributors
* SPDX-License-Identifier: LGPL-2.1-only
2017-06-19 15:02:18 -07:00
*/
package net.minecraftforge.registries;
2022-06-07 15:31:26 -05:00
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;
2024-04-30 10:56:00 -07:00
import com.mojang.serialization.MapCodec;
2022-11-22 13:32:47 -06:00
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
2022-11-22 13:32:47 -06:00
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
2024-04-30 10:56:00 -07:00
import java.util.function.BiFunction;
2022-11-22 13:32:47 -06:00
import java.util.stream.Collectors;
import it.unimi.dsi.fastutil.objects.Object2IntLinkedOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
2024-04-30 10:56:00 -07:00
import net.minecraft.core.DefaultedRegistry;
import net.minecraft.core.Holder;
2022-06-07 15:31:26 -05:00
import net.minecraft.core.IdMapper;
import net.minecraft.core.MappedRegistry;
import net.minecraft.core.Registry;
2024-04-30 10:56:00 -07:00
import net.minecraft.core.WritableRegistry;
2022-11-22 13:32:47 -06:00
import net.minecraft.core.registries.BuiltInRegistries;
2024-10-07 17:26:02 -07:00
import net.minecraft.core.registries.Registries;
2022-06-07 15:31:26 -05:00
import net.minecraft.network.syncher.EntityDataSerializer;
import net.minecraft.resources.ResourceKey;
2025-12-09 18:18:19 -08:00
import net.minecraft.resources.Identifier;
2021-07-17 00:08:49 -05:00
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.SpawnPlacements;
2021-07-17 00:08:49 -05:00
import net.minecraft.world.entity.ai.attributes.Attribute;
import net.minecraft.world.entity.ai.attributes.DefaultAttributes;
2022-06-07 15:31:26 -05:00
import net.minecraft.world.entity.ai.village.poi.PoiType;
2021-07-17 00:08:49 -05:00
import net.minecraft.world.item.BlockItem;
import net.minecraft.world.item.Item;
2023-02-24 08:58:27 -06:00
import net.minecraft.world.item.ItemDisplayContext;
2022-06-07 15:31:26 -05:00
import net.minecraft.world.level.block.Block;
2023-09-07 20:23:27 -07:00
import net.minecraft.world.level.block.entity.BlockEntityType;
2022-06-07 15:31:26 -05:00
import net.minecraft.world.level.block.state.BlockState;
2021-07-17 00:08:49 -05:00
import net.minecraft.world.level.block.state.StateDefinition;
import net.minecraft.world.level.levelgen.DebugLevelSource;
2022-06-07 15:31:26 -05:00
import net.minecraft.world.level.material.Fluid;
2022-11-22 13:32:47 -06:00
import net.minecraftforge.common.CreativeModeTabRegistry;
2022-06-07 15:31:26 -05:00
import net.minecraftforge.common.ForgeHooks;
2023-09-20 15:24:46 -07:00
import net.minecraftforge.common.loot.IGlobalLootModifier;
2021-07-17 00:08:49 -05:00
import net.minecraftforge.common.util.LogMessageAdapter;
2022-06-14 18:07:51 -04:00
import net.minecraftforge.fluids.FluidType;
import net.minecraftforge.fml.ModLoader;
import net.minecraftforge.fml.ModLoadingContext;
2021-07-17 00:08:49 -05:00
import net.minecraftforge.fml.StartupMessageManager;
import net.minecraftforge.fml.util.EnhancedRuntimeException;
import net.minecraftforge.fml.util.thread.EffectiveSide;
import net.minecraftforge.registries.ForgeRegistries.Keys;
2024-04-30 10:56:00 -07:00
import net.minecraftforge.registries.IForgeRegistry.AddCallback;
import net.minecraftforge.registries.IForgeRegistry.BakeCallback;
import net.minecraftforge.registries.IForgeRegistry.ClearCallback;
import net.minecraftforge.registries.IForgeRegistry.CreateCallback;
import net.minecraftforge.registries.IForgeRegistry.SlaveKey;
import net.minecraftforge.registries.IForgeRegistry.ValidateCallback;
2019-01-11 23:52:30 +01:00
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.jetbrains.annotations.ApiStatus;
2022-06-07 15:31:26 -05:00
import org.jetbrains.annotations.Nullable;
2017-06-19 15:02:18 -07:00
/**
* INTERNAL ONLY
* MODDERS SHOULD HAVE NO REASON TO USE THIS CLASS
* <p>Use the public {@link IForgeRegistry} and {@link ForgeRegistries} APIs to get the data</p>
2017-06-19 15:02:18 -07:00
*/
@ApiStatus.Internal
2024-04-30 10:56:00 -07:00
public class GameData {
2018-08-27 13:10:07 -04:00
private static final Logger LOGGER = LogManager.getLogger();
private static final Marker REGISTRIES = ForgeRegistry.REGISTRIES;
2019-05-23 16:02:15 -07:00
private static final int MAX_VARINT = Integer.MAX_VALUE - 1; //We were told it is their intention to have everything in a reg be unlimited, so assume that until we find cases where it isnt.
2017-06-19 15:02:18 -07:00
private static boolean hasInit = false;
private static final boolean DISABLE_VANILLA_REGISTRIES = Boolean.parseBoolean(System.getProperty("forge.disableVanillaGameData", "false")); // Use for unit tests/debugging
2026-06-16 11:18:58 -07:00
private static final BiConsumer<Identifier, ForgeRegistry<?>> LOCK_VANILLA = (_, reg) -> reg.slaves.values().stream().filter(o -> o instanceof ILockableRegistry).forEach(o -> ((ILockableRegistry)o).lock());
2025-12-09 18:18:19 -08:00
private static Set<Identifier> vanillaRegistryOrder = null;
2017-06-19 15:02:18 -07:00
static {
init();
}
2023-09-20 15:24:46 -07:00
@SuppressWarnings("deprecation")
2024-04-30 10:56:00 -07:00
public static void init() {
if (DISABLE_VANILLA_REGISTRIES) {
LOGGER.warn(REGISTRIES, "DISABLING VANILLA REGISTRY CREATION AS PER SYSTEM VARIABLE SETTING! forge.disableVanillaGameData");
2017-06-19 15:02:18 -07:00
return;
}
2024-04-30 10:56:00 -07:00
2017-06-19 15:02:18 -07:00
if (hasInit)
return;
2024-04-30 10:56:00 -07:00
2017-06-19 15:02:18 -07:00
hasInit = true;
2020-06-19 10:58:45 -04:00
// Game objects
makeRegistry(Keys.BLOCKS, "air").addCallback(BlockCallbacks.INSTANCE).legacyName("blocks").intrusiveHolderCallback(Block::builtInRegistryHolder).create();
makeRegistry(Keys.FLUIDS, "empty").intrusiveHolderCallback(Fluid::builtInRegistryHolder).create();
makeRegistry(Keys.ITEMS, "air").addCallback(ItemCallbacks.INSTANCE).legacyName("items").intrusiveHolderCallback(Item::builtInRegistryHolder).create();
2024-04-30 10:56:00 -07:00
makeRegistry(Keys.MOB_EFFECTS).create();
makeRegistry(Keys.SOUND_EVENTS).create();
makeRegistry(Keys.POTIONS).create();
makeRegistry(Keys.ENTITY_TYPES, "pig").legacyName("entities").intrusiveHolderCallback(EntityType::builtInRegistryHolder).create();
2023-09-07 20:23:27 -07:00
makeRegistry(Keys.BLOCK_ENTITY_TYPES).disableSaving().legacyName("blockentities").intrusiveHolderCallback(BlockEntityType::builtInRegistryHolder).create();
makeRegistry(Keys.PARTICLE_TYPES).disableSaving().create();
makeRegistry(Keys.MENU_TYPES).disableSaving().create();
makeRegistry(Keys.PAINTING_VARIANTS, "kebab").create();
makeRegistry(Keys.RECIPE_TYPES).disableSaving().disableSync().create();
makeRegistry(Keys.RECIPE_SERIALIZERS).disableSaving().create();
makeRegistry(Keys.ATTRIBUTES).onValidate(AttributeCallbacks.INSTANCE).disableSaving().disableSync().create();
makeRegistry(Keys.STAT_TYPES).create();
makeRegistry(Keys.COMMAND_ARGUMENT_TYPES).disableSaving().create();
2024-10-07 17:26:02 -07:00
makeRegistry(Registries.DATA_COMPONENT_TYPE).disableSaving().create();
2020-06-19 10:58:45 -04:00
// Villagers
makeRegistry(Keys.VILLAGER_PROFESSIONS, "none").create();
2024-04-30 10:56:00 -07:00
makeRegistry(Keys.POI_TYPES).addCallback(PoiTypeCallbacks.INSTANCE).disableSync().create();
makeRegistry(Keys.MEMORY_MODULE_TYPES, "dummy").disableSync().create();
makeRegistry(Keys.SENSOR_TYPES, "dummy").disableSaving().disableSync().create();
makeRegistry(Keys.ACTIVITIES).disableSaving().disableSync().create();
// Worldgen
makeRegistry(Keys.CHUNK_STATUS, "empty").disableSaving().disableSync().create();
makeRegistry(Keys.FOLIAGE_PLACER_TYPES).disableSaving().disableSync().create();
makeRegistry(Keys.TREE_DECORATOR_TYPES).disableSaving().disableSync().create();
// Dynamic Worldgen
makeRegistry(Keys.BIOMES).disableSync().create();
}
2024-04-30 10:56:00 -07:00
static RegistryBuilder<EntityDataSerializer<?>> getDataSerializersRegistryBuilder() {
return makeRegistry(Keys.ENTITY_DATA_SERIALIZERS, 256 /*vanilla space*/, MAX_VARINT).disableSaving().disableOverrides();
}
2024-04-30 10:56:00 -07:00
static RegistryBuilder<MapCodec<? extends IGlobalLootModifier>> getGLMSerializersRegistryBuilder() {
return makeRegistry(Keys.GLOBAL_LOOT_MODIFIER_SERIALIZERS).disableSaving().disableSync();
2019-05-23 16:02:15 -07:00
}
2024-04-30 10:56:00 -07:00
static RegistryBuilder<FluidType> getFluidTypeRegistryBuilder() {
return makeRegistry(Keys.FLUID_TYPES).disableSaving();
2022-06-14 18:07:51 -04:00
}
2023-10-06 20:54:08 -07:00
static <T> RegistryBuilder<T> makeUnsavedAndUnsynced() {
return RegistryBuilder.<T>of().disableSaving().disableSync();
2023-09-20 15:24:46 -07:00
}
2024-04-30 10:56:00 -07:00
static RegistryBuilder<ItemDisplayContext> getItemDisplayContextRegistryBuilder() {
2023-02-24 08:58:27 -06:00
return new RegistryBuilder<ItemDisplayContext>()
.setMaxID(128 * 2) /* 0 -> 127 gets positive ID, 128 -> 256 gets negative ID */.disableOverrides().disableSaving()
2025-12-09 18:18:19 -08:00
.setDefaultKey(Identifier.withDefaultNamespace("none"))
2023-02-24 08:58:27 -06:00
.onAdd(ItemDisplayContext.ADD_CALLBACK);
}
2024-04-30 10:56:00 -07:00
private static <T> RegistryBuilder<T> makeRegistry(ResourceKey<? extends Registry<T>> key) {
2025-12-09 18:18:19 -08:00
return new RegistryBuilder<T>().setName(key.identifier()).setMaxID(MAX_VARINT).hasWrapper();
2017-06-19 15:02:18 -07:00
}
2024-04-30 10:56:00 -07:00
private static <T> RegistryBuilder<T> makeRegistry(ResourceKey<? extends Registry<T>> key, int min, int max) {
2025-12-09 18:18:19 -08:00
return new RegistryBuilder<T>().setName(key.identifier()).setIDRange(min, max).hasWrapper();
}
2024-04-30 10:56:00 -07:00
private static <T> RegistryBuilder<T> makeRegistry(ResourceKey<? extends Registry<T>> key, String _default) {
2025-12-09 18:18:19 -08:00
return new RegistryBuilder<T>().setName(key.identifier()).setMaxID(MAX_VARINT).hasWrapper().setDefaultKey(Identifier.parse(_default));
2017-06-19 15:02:18 -07:00
}
2024-04-30 10:56:00 -07:00
@SuppressWarnings("unchecked")
public static <T, R extends WritableRegistry<T>> R getWrapper(ResourceKey<? extends Registry<T>> key, R vanilla) {
2025-12-09 18:18:19 -08:00
var reg = RegistryManager.ACTIVE.getRegistry(key.identifier());
2024-04-30 10:56:00 -07:00
if (reg == null)
return vanilla;
2017-06-19 15:02:18 -07:00
2024-04-30 10:56:00 -07:00
var wrapper = reg.getSlaveMap(WrapperFactory.WRAPPER);
if (wrapper == null)
return vanilla;
2017-06-19 15:02:18 -07:00
2024-04-30 10:56:00 -07:00
var vanillaD = vanilla instanceof DefaultedRegistry tmp ? tmp : null;
var wrapperD = wrapper instanceof DefaultedRegistry tmp ? tmp : null;
if (vanillaD == null && wrapperD != null)
2025-12-09 18:18:19 -08:00
throw new IllegalStateException("Invalid wrapper " + key.identifier() + " was defaulted when not expected");
2024-04-30 10:56:00 -07:00
if (vanillaD != null && wrapperD == null)
2025-12-09 18:18:19 -08:00
throw new IllegalStateException("Invalid wrapper " + key.identifier() + " was normal when should be expected");
2024-04-30 10:56:00 -07:00
if (vanillaD != null && !vanillaD.getDefaultKey().equals(wrapperD.getDefaultKey()))
2025-12-09 18:18:19 -08:00
throw new IllegalStateException("Invalid wrapper " + key.identifier() + " mismatched default key " + vanillaD.getDefaultKey() + " != " + wrapperD.getDefaultKey());
2024-04-30 10:56:00 -07:00
return (R)wrapper;
2017-06-19 15:02:18 -07:00
}
2024-04-30 10:56:00 -07:00
static <V> WrapperFactory<V> createWrapperFactory(boolean defaulted) {
BiFunction<ForgeRegistry<V>, RegistryManager, WritableRegistry<V>> factory = defaulted
? (reg, stage) -> new NamespacedDefaultedWrapper<>(reg, reg.getBuilder().getIntrusiveHolderCallback(), stage)
: (reg, stage) -> new NamespacedWrapper<V>(reg, reg.getBuilder().getIntrusiveHolderCallback(), stage);
return new WrapperFactory<>(factory);
2017-06-19 15:02:18 -07:00
}
2024-04-30 10:56:00 -07:00
static record WrapperFactory<V>(BiFunction<ForgeRegistry<V>, RegistryManager, WritableRegistry<V>> factory) implements CreateCallback<V>, AddCallback<V> {
static SlaveKey<WritableRegistry<?>> WRAPPER = SlaveKey.create("forge:vanilla_wrapper");
@Override
public void onCreate(IForgeRegistryInternal<V> owner, RegistryManager stage) {
owner.setSlaveMap(WRAPPER, factory.apply((ForgeRegistry<V>)owner, stage));
}
@Override
public void onAdd(IForgeRegistryInternal<V> owner, RegistryManager stage, int id, ResourceKey<V> key, V value, V oldValue) {
@SuppressWarnings("unchecked")
var wrapper = (NamespacedWrapper<V>)owner.getSlaveMap(WRAPPER);
wrapper.onAdded(stage, id, key, value, oldValue);
}
}
2017-06-19 15:02:18 -07:00
public static void vanillaSnapshot() {
LOGGER.debug(REGISTRIES, "Creating vanilla freeze snapshot");
for (var r : RegistryManager.ACTIVE.registries.entrySet())
2022-06-07 15:31:26 -05:00
loadRegistry(r.getKey(), RegistryManager.ACTIVE, RegistryManager.VANILLA, true);
RegistryManager.VANILLA.registries.forEach((name, reg) -> {
2017-06-20 18:39:55 -07:00
reg.validateContent(name);
reg.freeze();
});
2017-06-20 18:39:55 -07:00
RegistryManager.VANILLA.registries.forEach(LOCK_VANILLA);
RegistryManager.ACTIVE.registries.forEach(LOCK_VANILLA);
// Capture the vanilla registry order.
vanillaRegistryOrder = new LinkedHashSet<>(MappedRegistry.getKnownRegistries());
LOGGER.debug(REGISTRIES, "Vanilla registry order:");
for (var key : vanillaRegistryOrder)
LOGGER.info(REGISTRIES, "\t" + key);
LOGGER.debug(REGISTRIES, "Vanilla freeze snapshot created");
2017-06-19 15:02:18 -07:00
}
2023-09-20 15:24:46 -07:00
@SuppressWarnings("deprecation")
public static void unfreezeData() {
2022-03-01 14:59:27 -06:00
LOGGER.debug(REGISTRIES, "Unfreezing vanilla registries");
for (var reg : BuiltInRegistries.REGISTRY) {
if (reg instanceof MappedRegistry mapped)
mapped.unfreeze();
}
2022-03-01 14:59:27 -06:00
}
public static void freezeData() {
LOGGER.debug(REGISTRIES, "Freezing registries");
for (var reg : BuiltInRegistries.REGISTRY) {
if (reg instanceof NamespacedWrapper<?> named) {
2025-03-26 16:32:44 -07:00
// This is called after we fire our register events, but before the game loads any tags.
// So lets skip the validation for this pass and just make every unbound tag empty
// Tags will be bound later when tag data is reloaded/synced
2025-03-26 16:32:44 -07:00
if (named.isFrozen())
named.unfreeze();
named.freeze();
} else if (reg instanceof MappedRegistry maped)
maped.freeze();
}
for (var r : RegistryManager.ACTIVE.registries.entrySet())
2022-06-07 15:31:26 -05:00
loadRegistry(r.getKey(), RegistryManager.ACTIVE, RegistryManager.FROZEN, true);
RegistryManager.FROZEN.registries.forEach((name, reg) -> {
2017-06-20 18:39:55 -07:00
reg.validateContent(name);
reg.freeze();
});
2019-01-11 23:52:30 +01:00
RegistryManager.ACTIVE.registries.forEach((name, reg) -> {
reg.freeze();
reg.bake();
reg.dump(name);
2019-01-11 23:52:30 +01:00
});
2018-08-29 21:30:15 -04:00
// the id mapping is finalized, no ids actually changed but this is a good place to tell everyone to 'bake' their stuff.
fireRemapEvent(ImmutableMap.of(), true);
LOGGER.debug(REGISTRIES, "All registries frozen");
2017-06-19 15:02:18 -07:00
}
public static void revertToFrozen() {
revertTo(RegistryManager.FROZEN, true);
}
2023-09-20 15:24:46 -07:00
public static void revertTo(final RegistryManager target, boolean fireEvents) {
if (target.registries.isEmpty()) {
LOGGER.warn(REGISTRIES, "Can't revert to {} GameData state without a valid snapshot.", target.getName());
2017-06-19 15:02:18 -07:00
return;
}
2026-06-16 11:18:58 -07:00
RegistryManager.ACTIVE.registries.forEach((_, reg) -> reg.resetDelegates());
2017-06-19 15:02:18 -07:00
LOGGER.debug(REGISTRIES, "Reverting to {} data state.", target.getName());
for (var r : RegistryManager.ACTIVE.registries.entrySet())
2022-06-07 15:31:26 -05:00
loadRegistry(r.getKey(), target, RegistryManager.ACTIVE, true);
2026-06-16 11:18:58 -07:00
RegistryManager.ACTIVE.registries.forEach((_, reg) -> reg.bake());
2017-06-19 15:02:18 -07:00
// the id mapping has reverted, fire remap events for those that care about id changes
if (fireEvents) {
2022-06-07 15:31:26 -05:00
fireRemapEvent(ImmutableMap.of(), true);
ObjectHolderRegistry.applyObjectHolders();
}
2017-06-19 15:02:18 -07:00
// the id mapping has reverted, ensure we sync up the object holders
LOGGER.debug(REGISTRIES, "{} state restored.", target.getName());
2017-06-19 15:02:18 -07:00
}
2025-12-09 18:18:19 -08:00
public static void revert(RegistryManager state, Identifier registry, boolean lock) {
LOGGER.debug(REGISTRIES, "Reverting {} to {}", registry, state.getName());
2022-06-07 15:31:26 -05:00
loadRegistry(registry, state, RegistryManager.ACTIVE, lock);
LOGGER.debug(REGISTRIES, "Reverting complete");
2017-06-19 15:02:18 -07:00
}
2022-06-07 15:31:26 -05:00
@SuppressWarnings("deprecation")
2024-10-25 17:52:16 -07:00
public static void postRegisterEvents() {
2025-12-09 18:18:19 -08:00
Set<Identifier> keySet = new HashSet<>(RegistryManager.ACTIVE.registries.keySet());
2022-06-07 15:31:26 -05:00
keySet.addAll(RegistryManager.getVanillaRegistryKeys());
2025-12-09 18:18:19 -08:00
Set<Identifier> ordered = new LinkedHashSet<>(vanillaRegistryOrder);
ordered.retainAll(keySet);
2025-12-09 18:18:19 -08:00
ordered.addAll(keySet.stream().sorted(Identifier::compareNamespaced).toList());
2022-06-07 15:31:26 -05:00
RuntimeException aggregate = new RuntimeException();
2025-12-09 18:18:19 -08:00
for (Identifier rootRegistryName : ordered) {
2024-10-25 17:52:16 -07:00
try {
2022-06-07 15:31:26 -05:00
ResourceKey<? extends Registry<?>> registryKey = ResourceKey.createRegistryKey(rootRegistryName);
ForgeRegistry<?> forgeRegistry = RegistryManager.ACTIVE.getRegistry(rootRegistryName);
2024-10-25 17:52:16 -07:00
Registry<?> vanillaRegistry = BuiltInRegistries.REGISTRY.getValue(rootRegistryName);
2022-06-07 15:31:26 -05:00
RegisterEvent registerEvent = new RegisterEvent(registryKey, forgeRegistry, vanillaRegistry);
2025-12-09 18:18:19 -08:00
StartupMessageManager.modLoaderConsumer().ifPresent(s -> s.accept("REGISTERING " + registryKey.identifier()));
2022-06-07 15:31:26 -05:00
if (forgeRegistry != null)
forgeRegistry.unfreeze();
2025-10-01 11:12:41 -07:00
ModLoader.postEventWrapContainerInModOrder(registerEvent);
2022-06-07 15:31:26 -05:00
if (forgeRegistry != null)
forgeRegistry.freeze();
2025-12-09 18:18:19 -08:00
LOGGER.debug(REGISTRIES, "Applying holder lookups: {}", registryKey.identifier());
ObjectHolderRegistry.applyObjectHolders(registryKey.identifier());
2025-12-09 18:18:19 -08:00
LOGGER.debug(REGISTRIES, "Holder lookups applied: {}", registryKey.identifier());
2024-10-25 17:52:16 -07:00
} catch (Throwable t) {
2022-06-07 15:31:26 -05:00
aggregate.addSuppressed(t);
}
2022-06-07 15:31:26 -05:00
}
2024-10-25 17:52:16 -07:00
if (aggregate.getSuppressed().length > 0) {
2022-06-07 15:31:26 -05:00
LOGGER.fatal("Failed to register some entries, see suppressed exceptions for details", aggregate);
LOGGER.fatal("Detected errors during registry event dispatch, rolling back to VANILLA state");
revertTo(RegistryManager.VANILLA, false);
LOGGER.fatal("Detected errors during registry event dispatch, roll back to VANILLA complete");
throw aggregate;
2024-10-25 17:52:16 -07:00
} else {
2022-06-07 15:31:26 -05:00
ForgeHooks.modifyAttributes();
SpawnPlacements.fireSpawnPlacementEvent();
2023-04-01 20:45:29 -05:00
CreativeModeTabRegistry.sortTabs();
2022-06-07 15:31:26 -05:00
}
}
2017-06-19 15:02:18 -07:00
//Lets us clear the map so we can rebuild it.
private static class ClearableObjectIntIdentityMap<I> extends IdMapper<I> {
void clear() {
this.tToId.clear();
this.idToT.clear();
2019-01-11 23:52:30 +01:00
this.nextId = 0;
2017-06-19 15:02:18 -07:00
}
2023-09-20 15:24:46 -07:00
@SuppressWarnings("unused")
void remove(I key) {
boolean hadId = this.tToId.containsKey(key);
int prev = this.tToId.removeInt(key);
if (hadId) {
this.idToT.set(prev, null);
}
}
2017-06-19 15:02:18 -07:00
}
2024-04-30 10:56:00 -07:00
public static class BlockCallbacks implements AddCallback<Block>, ClearCallback<Block>, BakeCallback<Block>, CreateCallback<Block> {
2017-06-19 15:02:18 -07:00
static final BlockCallbacks INSTANCE = new BlockCallbacks();
2024-04-30 10:56:00 -07:00
private static final SlaveKey<ClearableObjectIntIdentityMap<BlockState>> STATE_TO_ID = SlaveKey.create("state_to_id");
public static IdMapper<BlockState> getBlockStateIDMap() {
return RegistryManager.ACTIVE.getRegistry(Keys.BLOCKS).getSlaveMap(STATE_TO_ID);
}
2017-06-19 15:02:18 -07:00
@Override
2024-04-30 10:56:00 -07:00
public void onAdd(IForgeRegistryInternal<Block> owner, RegistryManager stage, int id, ResourceKey<Block> key, Block block, @Nullable Block oldBlock) {
if (oldBlock != null) {
2021-07-17 00:08:49 -05:00
StateDefinition<Block, BlockState> oldContainer = oldBlock.getStateDefinition();
StateDefinition<Block, BlockState> newContainer = block.getStateDefinition();
2019-01-11 23:52:30 +01:00
// Test vanilla blockstates, if the number matches, make sure they also match in their string representations
2025-12-09 18:18:19 -08:00
if (key.identifier().getNamespace().equals("minecraft") && !oldContainer.getProperties().equals(newContainer.getProperties())) {
2019-01-11 23:52:30 +01:00
String oldSequence = oldContainer.getProperties().stream()
.map(s -> String.format(Locale.ENGLISH, "%s={%s}", s.getName(),
s.getPossibleValues().stream().map(Object::toString).collect(Collectors.joining( "," ))))
2019-01-11 23:52:30 +01:00
.collect(Collectors.joining(";"));
String newSequence = newContainer.getProperties().stream()
.map(s -> String.format(Locale.ENGLISH, "%s={%s}", s.getName(),
s.getPossibleValues().stream().map(Object::toString).collect(Collectors.joining( "," ))))
2019-01-11 23:52:30 +01:00
.collect(Collectors.joining(";"));
2021-07-17 00:08:49 -05:00
LOGGER.error(REGISTRIES,()-> LogMessageAdapter.adapt(sb-> {
2025-12-09 18:18:19 -08:00
sb.append("Registry replacements for vanilla block '").append(key.identifier())
2022-06-07 15:31:26 -05:00
.append("' must not change the number or order of blockstates.\n");
sb.append("\tOld: ").append(oldSequence).append('\n');
sb.append("\tNew: ").append(newSequence);
}));
2019-01-11 23:52:30 +01:00
throw new RuntimeException("Invalid vanilla replacement. See log for details.");
}
}
2017-06-19 15:02:18 -07:00
}
@Override
2024-04-30 10:56:00 -07:00
public void onClear(IForgeRegistryInternal<Block> owner, RegistryManager stage) {
owner.getSlaveMap(STATE_TO_ID).clear();
2017-06-19 15:02:18 -07:00
}
@Override
2024-04-30 10:56:00 -07:00
public void onCreate(IForgeRegistryInternal<Block> owner, RegistryManager stage) {
var idMap = new ClearableObjectIntIdentityMap<BlockState>() {
2017-06-19 15:02:18 -07:00
@Override
2024-04-30 10:56:00 -07:00
public int getId(BlockState key) {
return this.tToId.containsKey(key) ? this.tToId.getInt(key) : -1;
2017-06-19 15:02:18 -07:00
}
};
2024-04-30 10:56:00 -07:00
owner.setSlaveMap(STATE_TO_ID, idMap);
2017-06-19 15:02:18 -07:00
}
2019-01-11 23:52:30 +01:00
@Override
2024-04-30 10:56:00 -07:00
public void onBake(IForgeRegistryInternal<Block> owner, RegistryManager stage) {
var blockstateMap = owner.getSlaveMap(STATE_TO_ID);
2019-01-11 23:52:30 +01:00
2024-04-30 10:56:00 -07:00
for (Block block : owner) {
for (BlockState state : block.getStateDefinition().getPossibleStates()) {
2019-01-11 23:52:30 +01:00
blockstateMap.add(state);
state.initCache();
2019-01-11 23:52:30 +01:00
}
2019-05-23 16:02:15 -07:00
2019-06-08 10:45:28 -04:00
block.getLootTable();
2019-01-11 23:52:30 +01:00
}
2021-07-17 00:08:49 -05:00
DebugLevelSource.initValidStates();
2019-01-11 23:52:30 +01:00
}
2017-06-19 15:02:18 -07:00
}
2024-04-30 10:56:00 -07:00
public static class ItemCallbacks implements AddCallback<Item>, ClearCallback<Item>, CreateCallback<Item> {
2017-06-19 15:02:18 -07:00
static final ItemCallbacks INSTANCE = new ItemCallbacks();
2024-04-30 10:56:00 -07:00
private static final SlaveKey<Map<Block, Item>> BLOCK_TO_ITEM = SlaveKey.create("block_to_item");
public static Map<Block,Item> getBlockItemMap() {
return RegistryManager.ACTIVE.getRegistry(Keys.ITEMS).getSlaveMap(BLOCK_TO_ITEM);
}
2017-06-19 15:02:18 -07:00
@Override
2024-04-30 10:56:00 -07:00
public void onAdd(IForgeRegistryInternal<Item> owner, RegistryManager stage, int id, ResourceKey<Item> key, Item item, @Nullable Item oldItem) {
if (oldItem instanceof BlockItem block)
2024-04-30 10:56:00 -07:00
block.removeFromBlockToItemMap(owner.getSlaveMap(BLOCK_TO_ITEM), item);
if (item instanceof BlockItem block)
2024-04-30 10:56:00 -07:00
block.registerBlocks(owner.getSlaveMap(BLOCK_TO_ITEM), item);
2017-06-19 15:02:18 -07:00
}
@Override
2024-04-30 10:56:00 -07:00
public void onClear(IForgeRegistryInternal<Item> owner, RegistryManager stage) {
owner.getSlaveMap(BLOCK_TO_ITEM).clear();
2017-06-19 15:02:18 -07:00
}
@Override
2024-04-30 10:56:00 -07:00
public void onCreate(IForgeRegistryInternal<Item> owner, RegistryManager stage) {
owner.setSlaveMap(BLOCK_TO_ITEM, new HashMap<>());
2017-06-19 15:02:18 -07:00
}
}
2024-04-30 10:56:00 -07:00
private static class AttributeCallbacks implements ValidateCallback<Attribute> {
2020-06-23 20:56:24 -04:00
static final AttributeCallbacks INSTANCE = new AttributeCallbacks();
2017-06-19 15:02:18 -07:00
@Override
2025-12-09 18:18:19 -08:00
public void onValidate(IForgeRegistryInternal<Attribute> owner, RegistryManager stage, int id, Identifier key, Attribute obj) {
// some stuff hard patched in can cause this to derp if it's JUST vanilla, so skip
if (stage != RegistryManager.VANILLA)
DefaultAttributes.validate();
2017-06-19 15:02:18 -07:00
}
}
2024-04-30 10:56:00 -07:00
public static class PoiTypeCallbacks implements AddCallback<PoiType>, ClearCallback<PoiType>, CreateCallback<PoiType> {
static final PoiTypeCallbacks INSTANCE = new PoiTypeCallbacks();
private static final SlaveKey<Map<BlockState, Holder<PoiType>>> STATE_TO_POI = SlaveKey.create("state_to_poi");
public static Map<BlockState, Holder<PoiType>> getStateToPoi() {
return RegistryManager.ACTIVE.getRegistry(Keys.POI_TYPES).getSlaveMap(STATE_TO_POI);
}
@Override
2024-04-30 10:56:00 -07:00
public void onAdd(IForgeRegistryInternal<PoiType> owner, RegistryManager stage, int id, ResourceKey<PoiType> key, PoiType obj, @Nullable PoiType oldObj) {
var map = owner.getSlaveMap(STATE_TO_POI);
if (oldObj != null)
2022-06-07 15:31:26 -05:00
oldObj.matchingStates().forEach(map::remove);
2024-04-30 10:56:00 -07:00
var holder = owner.getHolder(obj).orElse(null);
if (holder == null)
2024-04-30 10:56:00 -07:00
throw new IllegalStateException("Could not get holder for " + key + " " + obj);
for (BlockState state : obj.matchingStates()) {
2024-04-30 10:56:00 -07:00
var oldType = map.put(state, holder);
if (oldType != null)
throw new IllegalStateException(String.format(Locale.ENGLISH, "Point of interest types %s and %s both list %s in their blockstates, this is not allowed. Blockstates can only have one point of interest type each.", oldType, obj, state));
}
}
@Override
2024-04-30 10:56:00 -07:00
public void onClear(IForgeRegistryInternal<PoiType> owner, RegistryManager stage) {
owner.getSlaveMap(STATE_TO_POI).clear();
}
@Override
2024-04-30 10:56:00 -07:00
public void onCreate(IForgeRegistryInternal<PoiType> owner, RegistryManager stage) {
owner.setSlaveMap(STATE_TO_POI, new HashMap<>());
}
}
2025-12-09 18:18:19 -08:00
private static <T> void loadRegistry(final Identifier registryName, final RegistryManager from, final RegistryManager to, boolean freeze) {
2017-06-19 15:02:18 -07:00
ForgeRegistry<T> fromRegistry = from.getRegistry(registryName);
if (fromRegistry == null) {
2017-06-19 15:02:18 -07:00
ForgeRegistry<T> toRegistry = to.getRegistry(registryName);
if (toRegistry == null) {
2017-06-19 15:02:18 -07:00
throw new EnhancedRuntimeException("Could not find registry to load: " + registryName){
private static final long serialVersionUID = 1L;
@Override
protected void printStackTrace(WrappedPrintStream stream) {
2017-06-19 15:02:18 -07:00
stream.println("Looking For: " + registryName);
stream.println("Found From:");
2025-12-09 18:18:19 -08:00
for (Identifier name : from.registries.keySet())
2017-06-19 15:02:18 -07:00
stream.println(" " + name);
stream.println("Found To:");
2025-12-09 18:18:19 -08:00
for (Identifier name : to.registries.keySet())
2017-06-19 15:02:18 -07:00
stream.println(" " + name);
}
};
}
// We found it in to, so lets trust to's state...
// This happens when connecting to a server that doesn't have this registry.
// Such as a 1.8.0 Forge server with 1.8.8+ Forge.
// We must however, re-fire the callbacks as some internal data may be corrupted {potions}
//TODO: With my rework of how registries add callbacks are done.. I don't think this is necessary.
//fire addCallback for each entry
} else {
2017-06-19 15:02:18 -07:00
ForgeRegistry<T> toRegistry = to.getRegistry(registryName, from);
toRegistry.sync(registryName, fromRegistry);
if (freeze)
toRegistry.isFrozen = true;
}
}
2025-12-09 18:18:19 -08:00
public static Multimap<Identifier, Identifier> injectSnapshot(Map<Identifier, ForgeRegistry.Snapshot> snapshot, boolean injectFrozenData, boolean isLocalWorld) {
LOGGER.info(REGISTRIES, "Injecting existing registry data into this {} instance", EffectiveSide.get());
2017-06-19 15:02:18 -07:00
RegistryManager.ACTIVE.registries.forEach((name, reg) -> reg.validateContent(name));
RegistryManager.ACTIVE.registries.forEach((name, reg) -> reg.dump(name));
2026-06-16 11:18:58 -07:00
RegistryManager.ACTIVE.registries.forEach((_, reg) -> reg.resetDelegates());
2020-06-19 10:58:45 -04:00
// Update legacy names
snapshot = snapshot.entrySet().stream()
.sorted(Map.Entry.comparingByKey()) // FIXME Registries need dependency ordering, this makes sure blocks are done before items (for ItemCallbacks) but it's lazy as hell
2026-06-16 11:18:58 -07:00
.collect(Collectors.toMap(e -> RegistryManager.ACTIVE.updateLegacyName(e.getKey()), Map.Entry::getValue, (k1, _) -> k1, LinkedHashMap::new));
2017-06-19 15:02:18 -07:00
if (isLocalWorld) {
2025-12-09 18:18:19 -08:00
Identifier[] missingRegs = snapshot.keySet().stream().filter(name -> !RegistryManager.ACTIVE.registries.containsKey(name)).toArray(Identifier[]::new);
if (missingRegs.length > 0)
{
String header = "Forge Mod Loader detected missing/unknown registrie(s).\n\n" +
"There are " + missingRegs.length + " missing registries in this save.\n" +
"If you continue the missing registries will get removed.\n" +
"This may cause issues, it is advised that you create a world backup before continuing.\n\n";
StringBuilder text = new StringBuilder("Missing Registries:\n");
2017-06-19 15:02:18 -07:00
2025-12-09 18:18:19 -08:00
for (Identifier s : missingRegs)
text.append(s).append("\n");
LOGGER.warn(REGISTRIES, header);
LOGGER.warn(REGISTRIES, text.toString());
}
2017-06-19 15:02:18 -07:00
}
2022-04-13 23:04:04 -07:00
RegistryManager STAGING = new RegistryManager();
2017-06-19 15:02:18 -07:00
2025-12-09 18:18:19 -08:00
final Map<Identifier, Map<Identifier, IdMappingEvent.IdRemapping>> remaps = new HashMap<>();
final LinkedHashMap<Identifier, Object2IntMap<Identifier>> missing = new LinkedHashMap<>();
2017-06-19 15:02:18 -07:00
// Load the snapshot into the "STAGING" registry
snapshot.forEach((key, value) -> {
remaps.put(key, new LinkedHashMap<>());
missing.put(key, new Object2IntLinkedOpenHashMap<>());
2022-06-07 15:31:26 -05:00
loadPersistentDataToStagingRegistry(RegistryManager.ACTIVE, STAGING, remaps.get(key), missing.get(key), key, value);
2017-06-19 15:02:18 -07:00
});
2017-06-28 00:14:10 -07:00
int count = missing.values().stream().mapToInt(Map::size).sum();
if (count > 0) {
LOGGER.debug(REGISTRIES,"There are {} mappings missing - attempting a mod remap", count);
2025-12-09 18:18:19 -08:00
Multimap<Identifier, Identifier> defaulted = ArrayListMultimap.create();
Multimap<Identifier, Identifier> failed = ArrayListMultimap.create();
2017-06-19 15:02:18 -07:00
missing.entrySet().stream().filter(e -> !e.getValue().isEmpty()).forEach(m -> {
2025-12-09 18:18:19 -08:00
Identifier name = m.getKey();
2017-06-19 15:02:18 -07:00
ForgeRegistry<?> reg = STAGING.getRegistry(name);
2025-12-09 18:18:19 -08:00
Object2IntMap<Identifier> missingIds = m.getValue();
MissingMappingsEvent event = reg.getMissingEvent(name, missingIds);
2025-06-18 02:34:13 -07:00
MissingMappingsEvent.BUS.post(event);
2017-06-19 15:02:18 -07:00
2022-06-07 15:31:26 -05:00
List<MissingMappingsEvent.Mapping<?>> lst = event.getAllMappings(reg.getRegistryKey()).stream()
.filter(e -> e.action == MissingMappingsEvent.Action.DEFAULT)
.sorted(Comparator.comparing(Object::toString))
.collect(Collectors.toList());
if (!lst.isEmpty()) {
2022-06-07 15:31:26 -05:00
LOGGER.error(REGISTRIES, () -> LogMessageAdapter.adapt(sb -> {
sb.append("Unidentified mapping from registry ").append(name).append('\n');
2022-06-07 15:31:26 -05:00
lst.stream().sorted().forEach(map -> sb.append('\t').append(map.key).append(": ").append(map.id).append('\n'));
}));
2017-06-19 15:02:18 -07:00
}
2022-06-07 15:31:26 -05:00
event.getAllMappings(reg.getRegistryKey()).stream()
.filter(e -> e.action == MissingMappingsEvent.Action.FAIL)
.forEach(fail -> failed.put(name, fail.key));
2017-06-19 15:02:18 -07:00
processMissing(name, STAGING, event, missingIds, remaps.get(name), defaulted.get(name), failed.get(name), !isLocalWorld);
2017-06-19 15:02:18 -07:00
});
if (!defaulted.isEmpty() && !isLocalWorld)
return defaulted;
if (!defaulted.isEmpty()) {
String header = "Forge Mod Loader detected missing registry entries.\n\n" +
"There are " + defaulted.size() + " missing entries in this save.\n" +
"If you continue the missing entries will get removed.\n" +
"A world backup will be automatically created in your saves directory.\n\n";
2017-06-19 15:02:18 -07:00
StringBuilder buf = new StringBuilder();
defaulted.asMap().forEach((name, entries) -> {
2017-06-19 15:02:18 -07:00
buf.append("Missing ").append(name).append(":\n");
2025-12-09 18:18:19 -08:00
entries.stream().sorted(Identifier::compareNamespaced).forEach(rl -> buf.append(" ").append(rl).append("\n"));
buf.append("\n");
2017-06-19 15:02:18 -07:00
});
LOGGER.warn(REGISTRIES, header);
LOGGER.warn(REGISTRIES, buf.toString());
2017-06-19 15:02:18 -07:00
}
if (!defaulted.isEmpty()) {
2017-06-19 15:02:18 -07:00
if (isLocalWorld)
LOGGER.error(REGISTRIES, "There are unidentified mappings in this world - we are going to attempt to process anyway");
2017-06-19 15:02:18 -07:00
}
}
if (injectFrozenData) {
2017-06-19 15:02:18 -07:00
// If we're loading up the world from disk, we want to add in the new data that might have been provisioned by mods
// So we load it from the frozen persistent registry
2026-06-16 11:18:58 -07:00
RegistryManager.ACTIVE.registries.forEach((name, _) -> {
2022-06-07 15:31:26 -05:00
loadFrozenDataToStagingRegistry(STAGING, name, remaps.get(name));
2017-06-19 15:02:18 -07:00
});
}
// Validate that all the STAGING data is good
2017-06-20 18:39:55 -07:00
STAGING.registries.forEach((name, reg) -> reg.validateContent(name));
2017-06-19 15:02:18 -07:00
// Load the STAGING registry into the ACTIVE registry
2025-12-09 18:18:19 -08:00
//for (Map.Entry<Identifier, IForgeRegistry<?>>> r : RegistryManager.ACTIVE.registries.entrySet())
2026-06-16 11:18:58 -07:00
RegistryManager.ACTIVE.registries.forEach((key, _) -> {
2022-06-07 15:31:26 -05:00
loadRegistry(key, STAGING, RegistryManager.ACTIVE, true);
});
2017-06-19 15:02:18 -07:00
2019-01-11 23:52:30 +01:00
RegistryManager.ACTIVE.registries.forEach((name, reg) -> {
reg.bake();
// Dump the active registry
reg.dump(name);
});
2017-06-19 15:02:18 -07:00
// Tell mods that the ids have changed
fireRemapEvent(remaps, false);
2017-06-19 15:02:18 -07:00
// The id map changed, ensure we apply object holders
ObjectHolderRegistry.applyObjectHolders();
2017-06-19 15:02:18 -07:00
// Return an empty list, because we're good
return ArrayListMultimap.create();
}
2025-12-09 18:18:19 -08:00
private static void fireRemapEvent(final Map<Identifier, Map<Identifier, IdMappingEvent.IdRemapping>> remaps, final boolean isFreezing) {
2025-06-18 02:34:13 -07:00
IdMappingEvent.BUS.post(new IdMappingEvent(remaps, isFreezing));
}
2017-06-19 15:02:18 -07:00
//Has to be split because of generics, Yay!
2025-12-09 18:18:19 -08:00
private static <T> void loadPersistentDataToStagingRegistry(RegistryManager pool, RegistryManager to, Map<Identifier, IdMappingEvent.IdRemapping> remaps, Object2IntMap<Identifier> missing, Identifier name, ForgeRegistry.Snapshot snap) {
2017-06-19 15:02:18 -07:00
ForgeRegistry<T> active = pool.getRegistry(name);
if (active == null)
return; // We've already asked the user if they wish to continue. So if the reg isnt found just assume the user knows and accepted it.
ForgeRegistry<T> _new = to.getRegistry(name, RegistryManager.ACTIVE);
2017-06-28 00:14:10 -07:00
snap.aliases.forEach(_new::addAlias);
snap.blocked.forEach(_new::block);
2017-06-23 00:58:00 -07:00
_new.loadIds(snap.ids, snap.overrides, missing, remaps, active, name);
2017-06-19 15:02:18 -07:00
}
//Another bouncer for generic reasons
2025-12-09 18:18:19 -08:00
private static <T> void processMissing(Identifier name, RegistryManager STAGING, MissingMappingsEvent e, Object2IntMap<Identifier> missing, Map<Identifier, IdMappingEvent.IdRemapping> remaps, Collection<Identifier> defaulted, Collection<Identifier> failed, boolean injectNetworkDummies) {
2022-06-07 15:31:26 -05:00
List<MissingMappingsEvent.Mapping<T>> mappings = e.getAllMappings(ResourceKey.createRegistryKey(name));
2017-06-19 15:02:18 -07:00
ForgeRegistry<T> active = RegistryManager.ACTIVE.getRegistry(name);
ForgeRegistry<T> staging = STAGING.getRegistry(name);
staging.processMissingEvent(name, active, mappings, missing, remaps, defaulted, failed, injectNetworkDummies);
2017-06-19 15:02:18 -07:00
}
2025-12-09 18:18:19 -08:00
private static <T> void loadFrozenDataToStagingRegistry(RegistryManager STAGING, Identifier name, Map<Identifier, IdMappingEvent.IdRemapping> remaps) {
2017-06-19 15:02:18 -07:00
ForgeRegistry<T> frozen = RegistryManager.FROZEN.getRegistry(name);
ForgeRegistry<T> newRegistry = STAGING.getRegistry(name, RegistryManager.FROZEN);
2025-12-09 18:18:19 -08:00
Object2IntMap<Identifier> _new = new Object2IntLinkedOpenHashMap<>();
2017-06-19 15:02:18 -07:00
frozen.getKeys().stream().filter(key -> !newRegistry.containsKey(key)).forEach(key -> _new.put(key, frozen.getID(key)));
newRegistry.loadIds(_new, frozen.getOverrideOwners(), new Object2IntLinkedOpenHashMap<>(), remaps, frozen, name);
2017-06-19 15:02:18 -07:00
}
/**
* Check a name for a domain prefix, and if not present infer it from the
* current active mod container.
2019-05-23 16:02:15 -07:00
*
* @param name The name or resource location
* @param warnOverrides If true, logs a warning if domain differs from that of
* the currently currently active mod container
2019-05-23 16:02:15 -07:00
*
2025-12-09 18:18:19 -08:00
* @return The {@link Identifier} with given or inferred domain
*/
2025-12-09 18:18:19 -08:00
public static Identifier checkPrefix(String name, boolean warnOverrides) {
int index = name.lastIndexOf(':');
String oldPrefix = index == -1 ? "" : name.substring(0, index).toLowerCase(Locale.ROOT);
name = index == -1 ? name : name.substring(index + 1);
@SuppressWarnings("removal")
String prefix = ModLoadingContext.get().getActiveNamespace();
if (warnOverrides && !oldPrefix.equals(prefix) && !oldPrefix.isEmpty()) {
2022-06-07 15:31:26 -05:00
LogManager.getLogger().debug("Mod `{}` attempting to register `{}` to the namespace `{}`. This could be intended, but likely means an EventBusSubscriber without a modid.", prefix, name, oldPrefix);
prefix = oldPrefix;
}
2025-12-09 18:18:19 -08:00
return Identifier.fromNamespaceAndPath(prefix, name);
}
2017-06-19 15:02:18 -07:00
}