/* * Minecraft Forge * Copyright (c) 2016-2018. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation version 2.1 * of the License. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package net.minecraftforge.registries; import com.google.common.collect.HashBiMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import net.minecraft.block.Block; import net.minecraft.block.BlockAir; import net.minecraft.block.material.Material; import net.minecraft.block.state.IBlockState; import net.minecraft.enchantment.Enchantment; import net.minecraft.entity.EntityType; import net.minecraft.item.Item; import net.minecraft.item.ItemBlock; import net.minecraft.potion.Potion; import net.minecraft.potion.PotionType; import net.minecraft.tileentity.TileEntityType; import net.minecraft.util.ObjectIntIdentityMap; import net.minecraft.util.ResourceLocation; import net.minecraft.util.SoundEvent; import net.minecraft.util.registry.RegistryNamespaced; import net.minecraft.util.registry.RegistryNamespacedDefaultedByKey; import net.minecraft.world.biome.Biome; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.RegistryEvent; import net.minecraftforge.event.RegistryEvent.MissingMappings; import net.minecraftforge.fml.common.EnhancedRuntimeException; import net.minecraftforge.fml.common.registry.VillagerRegistry.VillagerProfession; import net.minecraftforge.fml.ModThreadContext; import net.minecraftforge.fml.StartupQuery; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.BiMap; import java.lang.reflect.Field; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.function.BiConsumer; import java.util.function.Predicate; import java.util.stream.Collectors; import javax.annotation.Nullable; import net.minecraftforge.fml.common.thread.EffectiveSide; import org.apache.commons.lang3.Validate; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; import net.minecraftforge.fml.common.EnhancedRuntimeException.WrappedPrintStream; /** * INTERNAL ONLY * MODDERS SHOULD HAVE NO REASON TO USE THIS CLASS */ public class GameData { private static final Logger LOGGER = LogManager.getLogger(); private static final Marker GD = MarkerManager.getMarker("GAMEDATA"); public static final ResourceLocation BLOCKS = new ResourceLocation("minecraft:blocks"); public static final ResourceLocation ITEMS = new ResourceLocation("minecraft:items"); public static final ResourceLocation POTIONS = new ResourceLocation("minecraft:potions"); public static final ResourceLocation BIOMES = new ResourceLocation("minecraft:biomes"); public static final ResourceLocation SOUNDEVENTS = new ResourceLocation("minecraft:soundevents"); public static final ResourceLocation POTIONTYPES = new ResourceLocation("minecraft:potiontypes"); public static final ResourceLocation ENCHANTMENTS = new ResourceLocation("minecraft:enchantments"); public static final ResourceLocation ENTITIES = new ResourceLocation("minecraft:entities"); public static final ResourceLocation TILEENTITIES = new ResourceLocation("minecraft:tileentities"); public static final ResourceLocation PROFESSIONS = new ResourceLocation("minecraft:villagerprofessions"); private static final int MAX_BLOCK_ID = 4095; private static final int MIN_ITEM_ID = MAX_BLOCK_ID + 1; private static final int MAX_ITEM_ID = 31999; private static final int MAX_POTION_ID = 255; // SPacketEntityEffect sends bytes, we can only use 255 private static final int MAX_BIOME_ID = 255; // Maximum number in a byte in the chunk private static final int MAX_SOUND_ID = Integer.MAX_VALUE >> 5; // Varint (SPacketSoundEffect) private static final int MAX_POTIONTYPE_ID = Integer.MAX_VALUE >> 5; // Int (SPacketEffect) private static final int MAX_ENCHANTMENT_ID = Short.MAX_VALUE - 1; // Short - serialized as a short in ItemStack NBTs. private static final int MAX_ENTITY_ID = Integer.MAX_VALUE >> 5; // Varint (SPacketSpawnMob) private static final int MAX_TILE_ENTITY_ID = Integer.MAX_VALUE; //Doesnt seem to be serialized anywhere, so no max. private static final int MAX_PROFESSION_ID = 1024; //TODO: Is this serialized anywhere anymore? private static final ResourceLocation BLOCK_TO_ITEM = new ResourceLocation("minecraft:blocktoitemmap"); private static final ResourceLocation BLOCKSTATE_TO_ID = new ResourceLocation("minecraft:blockstatetoid"); private static boolean hasInit = false; private static final boolean DISABLE_VANILLA_REGISTRIES = Boolean.parseBoolean(System.getProperty("forge.disableVanillaGameData", "false")); // Use for unit tests/debugging private static final BiConsumer> LOCK_VANILLA = (name, reg) -> reg.slaves.values().stream().filter(o -> o instanceof ILockableRegistry).forEach(o -> ((ILockableRegistry)o).lock()); static { init(); } @SuppressWarnings("unchecked") public static void init() { if ( DISABLE_VANILLA_REGISTRIES) { LOGGER.warn(GD, "DISABLING VANILLA REGISTRY CREATION AS PER SYSTEM VARIABLE SETTING! forge.disableVanillaGameData"); return; } if (hasInit) return; hasInit = true; makeRegistry(BLOCKS, Block.class, MAX_BLOCK_ID, new ResourceLocation("air")).addCallback(BlockCallbacks.INSTANCE).create(); makeRegistry(ITEMS, Item.class, MIN_ITEM_ID, MAX_ITEM_ID).addCallback(ItemCallbacks.INSTANCE).create(); makeRegistry(POTIONS, Potion.class, MAX_POTION_ID).create(); makeRegistry(BIOMES, Biome.class, MAX_BIOME_ID).create(); makeRegistry(SOUNDEVENTS, SoundEvent.class, MAX_SOUND_ID).create(); makeRegistry(POTIONTYPES, PotionType.class, MAX_POTIONTYPE_ID, new ResourceLocation("empty")).create(); makeRegistry(ENCHANTMENTS, Enchantment.class, MAX_ENCHANTMENT_ID).create(); makeRegistry(PROFESSIONS, VillagerProfession.class, MAX_PROFESSION_ID).create(); // TODO do we need the callback and the static field anymore? makeRegistry(ENTITIES, EntityType.class, MAX_ENTITY_ID).create(); makeRegistry(TILEENTITIES, TileEntityType.class, MAX_TILE_ENTITY_ID).disableSaving().create(); } private static > RegistryBuilder makeRegistry(ResourceLocation name, Class type, int min, int max) { return new RegistryBuilder().setName(name).setType(type).setIDRange(min, max).addCallback(new NamespacedWrapper.Factory()); } private static > RegistryBuilder makeRegistry(ResourceLocation name, Class type, int max) { return new RegistryBuilder().setName(name).setType(type).setMaxID(max).addCallback(new NamespacedWrapper.Factory()); } private static > RegistryBuilder makeRegistry(ResourceLocation name, Class type, int max, ResourceLocation _default) { return new RegistryBuilder().setName(name).setType(type).setMaxID(max).addCallback(new NamespacedDefaultedWrapper.Factory()).setDefaultKey(_default); } public static > RegistryNamespacedDefaultedByKey getWrapperDefaulted(Class cls) { IForgeRegistry reg = RegistryManager.ACTIVE.getRegistry(cls); Validate.notNull(reg, "Attempted to get vanilla wrapper for unknown registry: " + cls.toString()); @SuppressWarnings("unchecked") RegistryNamespacedDefaultedByKey ret = reg.getSlaveMap(NamespacedDefaultedWrapper.Factory.ID, NamespacedDefaultedWrapper.class); Validate.notNull(ret, "Attempted to get vanilla wrapper for registry created incorrectly: " + cls.toString()); return ret; } public static > RegistryNamespaced getWrapper(Class cls) { IForgeRegistry reg = RegistryManager.ACTIVE.getRegistry(cls); Validate.notNull(reg, "Attempted to get vanilla wrapper for unknown registry: " + cls.toString()); @SuppressWarnings("unchecked") RegistryNamespaced ret = reg.getSlaveMap(NamespacedWrapper.Factory.ID, NamespacedWrapper.class); Validate.notNull(ret, "Attempted to get vanilla wrapper for registry created incorrectly: " + cls.toString()); return ret; } @SuppressWarnings("unchecked") public static Map getBlockItemMap() { return RegistryManager.ACTIVE.getRegistry(Item.class).getSlaveMap(BLOCK_TO_ITEM, Map.class); } @SuppressWarnings("unchecked") public static ObjectIntIdentityMap getBlockStateIDMap() { return RegistryManager.ACTIVE.getRegistry(Block.class).getSlaveMap(BLOCKSTATE_TO_ID, ObjectIntIdentityMap.class); } public static > K register_impl(K value) { Validate.notNull(value, "Attempted to register a null object"); Validate.notNull(value.getRegistryName(), String.format("Attempt to register object without having set a registry name %s (type %s)", value, value.getClass().getName())); final IForgeRegistry registry = RegistryManager.ACTIVE.getRegistry(value.getRegistryType()); Validate.notNull(registry, "Attempted to registry object without creating registry first: " + value.getRegistryType().getName()); registry.register(value); return value; } @SuppressWarnings({ "unchecked", "rawtypes" }) public static void vanillaSnapshot() { LOGGER.debug("Creating vanilla freeze snapshot"); for (Map.Entry>> r : RegistryManager.ACTIVE.registries.entrySet()) { final Class clazz = RegistryManager.ACTIVE.getSuperType(r.getKey()); loadRegistry(r.getKey(), RegistryManager.ACTIVE, RegistryManager.VANILLA, clazz, true); } RegistryManager.VANILLA.registries.forEach((name, reg) -> { reg.validateContent(name); reg.freeze(); }); RegistryManager.VANILLA.registries.forEach(LOCK_VANILLA); RegistryManager.ACTIVE.registries.forEach(LOCK_VANILLA); LOGGER.debug("Vanilla freeze snapshot created"); } @SuppressWarnings({ "rawtypes", "unchecked" }) public static void freezeData() { LOGGER.debug(GD, "Freezing registries"); for (Map.Entry>> r : RegistryManager.ACTIVE.registries.entrySet()) { final Class clazz = RegistryManager.ACTIVE.getSuperType(r.getKey()); loadRegistry(r.getKey(), RegistryManager.ACTIVE, RegistryManager.FROZEN, clazz, true); } RegistryManager.FROZEN.registries.forEach((name, reg) -> { reg.validateContent(name); reg.freeze(); }); RegistryManager.ACTIVE.registries.forEach((name, reg) -> reg.freeze()); // the id mapping is finalized, no ids actually changed but this is a good place to tell everyone to 'bake' their stuff. //Loader.instance().fireRemapEvent(ImmutableMap.of(), true); LOGGER.debug(GD, "All registries frozen"); } @SuppressWarnings({ "unchecked", "rawtypes" }) public static void revertToFrozen() { if (RegistryManager.FROZEN.registries.isEmpty()) { LOGGER.warn(GD, "Can't revert to frozen GameData state without freezing first."); return; } RegistryManager.ACTIVE.registries.forEach((name, reg) -> reg.resetDelegates()); LOGGER.debug(GD, "Reverting to frozen data state."); for (Map.Entry>> r : RegistryManager.ACTIVE.registries.entrySet()) { final Class clazz = RegistryManager.ACTIVE.getSuperType(r.getKey()); loadRegistry(r.getKey(), RegistryManager.FROZEN, RegistryManager.ACTIVE, clazz, true); } // the id mapping has reverted, fire remap events for those that care about id changes //Loader.instance().fireRemapEvent(ImmutableMap.of(), true); // the id mapping has reverted, ensure we sync up the object holders LOGGER.debug("Frozen state restored."); } @SuppressWarnings({ "rawtypes", "unchecked" }) public static void revert(RegistryManager state, ResourceLocation registry, boolean lock) { LOGGER.debug(GD, "Reverting {} to {}", registry, state.getName()); final Class clazz = RegistryManager.ACTIVE.getSuperType(registry); loadRegistry(registry, state, RegistryManager.ACTIVE, clazz, lock); LOGGER.debug(GD, "Reverting complete"); } //Lets us clear the map so we can rebuild it. static class ClearableObjectIntIdentityMap extends ObjectIntIdentityMap { void clear() { this.identityMap.clear(); this.objectList.clear(); } } private static class BlockCallbacks implements IForgeRegistry.AddCallback, IForgeRegistry.ClearCallback, IForgeRegistry.CreateCallback, IForgeRegistry.DummyFactory { static final BlockCallbacks INSTANCE = new BlockCallbacks(); @Override public void onAdd(IForgeRegistryInternal owner, RegistryManager stage, int id, Block block, @Nullable Block oldBlock) { @SuppressWarnings("unchecked") ClearableObjectIntIdentityMap blockstateMap = owner.getSlaveMap(BLOCKSTATE_TO_ID, ClearableObjectIntIdentityMap.class); int offset = 0; for (IBlockState state : block.getStateContainer().getValidStates()) blockstateMap.put(state, id + offset++); /* if ("minecraft:tripwire".equals(block.getRegistryName().toString())) //Tripwire is crap so we have to special case whee! { for (int meta = 0; meta < 15; meta++) blockstateMap.put(block.getStateFromMeta(meta), id << 4 | meta); } //So, due to blocks having more in-world states then metadata allows, we have to turn the map into a semi-milti-bimap. //We can do this however because the implementation of the map is last set wins. So we can add all states, then fix the meta bimap. //Multiple states -> meta. But meta to CORRECT state. final boolean[] usedMeta = new boolean[16]; //Hold a list of known meta from all states. for (IBlockState state : block.getBlockState().getValidStates()) { final int meta = block.getMetaFromState(state); blockstateMap.put(state, id << 4 | meta); //Add ALL the things! usedMeta[meta] = true; } for (int meta = 0; meta < 16; meta++) { if (block.getClass() == BlockObserver.class) continue; //Observers are bad and have non-cyclical states. So we HAVE to use the vanilla logic above. if (usedMeta[meta]) blockstateMap.put(block.getStateFromMeta(meta), id << 4 | meta); // Put the CORRECT thing! } if (oldBlock != null) { @SuppressWarnings("unchecked") BiMap blockToItem = owner.getSlaveMap(BLOCK_TO_ITEM, BiMap.class); Item item = blockToItem.get(oldBlock); if (item != null) blockToItem.forcePut(block, item); } */ } @Override public void onClear(IForgeRegistryInternal owner, RegistryManager stage) { owner.getSlaveMap(BLOCKSTATE_TO_ID, ClearableObjectIntIdentityMap.class).clear(); } @Override public void onCreate(IForgeRegistryInternal owner, RegistryManager stage) { final ClearableObjectIntIdentityMap idMap = new ClearableObjectIntIdentityMap() { @Override public int get(IBlockState key) { Integer integer = (Integer)this.identityMap.get(key); // There are some cases where this map is queried to serialize a state that is valid, //but somehow not in this list, so attempt to get real metadata. Doing this hear saves us 7 patches //if (integer == null && key != null) // integer = this.identityMap.get(key.getBlock().getStateFromMeta(key.getBlock().getMetaFromState(key))); return integer == null ? -1 : integer.intValue(); } }; owner.setSlaveMap(BLOCKSTATE_TO_ID, idMap); owner.setSlaveMap(BLOCK_TO_ITEM, Maps.newHashMap()); } @Override public Block createDummy(ResourceLocation key) { Block ret = new BlockDummyAir(Block.Builder.create(Material.AIR)); GameData.forceRegistryName(ret, key); return ret; } private static class BlockDummyAir extends BlockAir //A named class so DummyBlockReplacementTest can detect if its a dummy { private BlockDummyAir(Block.Builder builder) { super(builder); } @Override public String getTranslationKey() { return "block.minecraft.air"; } } } private static class ItemCallbacks implements IForgeRegistry.AddCallback, IForgeRegistry.ClearCallback, IForgeRegistry.CreateCallback { static final ItemCallbacks INSTANCE = new ItemCallbacks(); @Override public void onAdd(IForgeRegistryInternal owner, RegistryManager stage, int id, Item item, @Nullable Item oldItem) { if (item instanceof ItemBlock) { @SuppressWarnings("unchecked") Map blockToItem = owner.getSlaveMap(BLOCK_TO_ITEM, Map.class); ((ItemBlock)item).addToBlockToItemMap(blockToItem, item); } } @Override public void onClear(IForgeRegistryInternal owner, RegistryManager stage) { owner.getSlaveMap(BLOCK_TO_ITEM, Map.class).clear(); } @Override public void onCreate(IForgeRegistryInternal owner, RegistryManager stage) { // We share the blockItem map between items and blocks registries Map map = stage.getRegistry(BLOCKS).getSlaveMap(BLOCK_TO_ITEM, Map.class); owner.setSlaveMap(BLOCK_TO_ITEM, map); } } /* private static class RecipeCallbacks implements IForgeRegistry.ValidateCallback, IForgeRegistry.MissingFactory { static final RecipeCallbacks INSTANCE = new RecipeCallbacks(); @Override public void onValidate(IForgeRegistryInternal owner, RegistryManager stage, int id, ResourceLocation key, IRecipe obj) { if (stage != RegistryManager.ACTIVE) return; // verify the recipe output yields a registered item Item item = obj.getRecipeOutput().getItem(); if (!stage.getRegistry(Item.class).containsValue(item)) { throw new IllegalStateException(String.format("Recipe %s (%s) produces unregistered item %s (%s)", key, obj, item.getRegistryName(), item)); } } @Override public IRecipe createMissing(ResourceLocation key, boolean isNetwork) { return isNetwork ? new DummyRecipe().setRegistryName(key) : null; } private static class DummyRecipe implements IRecipe { private static ItemStack result = new ItemStack(Items.DIAMOND, 64); private ResourceLocation name; @Override public IRecipe setRegistryName(ResourceLocation name) { this.name = name; return this; } @Override public ResourceLocation getRegistryName() { return name; } @Override public Class getRegistryType() { return IRecipe.class; } @Override public boolean matches(InventoryCrafting inv, World worldIn) { return false; } //dirt? @Override public ItemStack getCraftingResult(InventoryCrafting inv) { return result; } @Override public boolean canFit(int width, int height) { return false; } @Override public ItemStack getRecipeOutput() { return result; } @Override public boolean isDynamic() { return true; } } } private static ForgeRegistry entityRegistry; public static ForgeRegistry getEntityRegistry() { return entityRegistry; } public static void registerEntity(int id, ResourceLocation key, Class clazz, String oldName) { RegistryNamespaced reg = getWrapper(EntityEntry.class); reg.register(id, key, new EntityEntry(clazz, oldName)); } private static class EntityCallbacks implements IForgeRegistry.AddCallback { static final EntityCallbacks INSTANCE = new EntityCallbacks(); @Override public void onAdd(IForgeRegistryInternal owner, RegistryManager stage, int id, EntityEntry entry, @Nullable EntityEntry oldEntry) { if (entry instanceof EntityEntryBuilder.BuiltEntityEntry) { ((EntityEntryBuilder.BuiltEntityEntry) entry).addedToRegistry(); } if (entry.getEgg() != null) EntityList.ENTITY_EGGS.put(entry.getRegistryName(), entry.getEgg()); } } */ private static > void loadRegistry(final ResourceLocation registryName, final RegistryManager from, final RegistryManager to, final Class regType, boolean freeze) { ForgeRegistry fromRegistry = from.getRegistry(registryName); if (fromRegistry == null) { ForgeRegistry toRegistry = to.getRegistry(registryName); if (toRegistry == null) { throw new EnhancedRuntimeException("Could not find registry to load: " + registryName){ private static final long serialVersionUID = 1L; @Override protected void printStackTrace(WrappedPrintStream stream) { stream.println("Looking For: " + registryName); stream.println("Found From:"); for (ResourceLocation name : from.registries.keySet()) stream.println(" " + name); stream.println("Found To:"); for (ResourceLocation name : to.registries.keySet()) 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 { ForgeRegistry toRegistry = to.getRegistry(registryName, from); toRegistry.sync(registryName, fromRegistry); if (freeze) toRegistry.isFrozen = true; } } @SuppressWarnings({ "unchecked", "rawtypes" }) public static Multimap injectSnapshot(Map snapshot, boolean injectFrozenData, boolean isLocalWorld) { LOGGER.info(GD, "Injecting existing registry data into this {} instance", EffectiveSide.get()); RegistryManager.ACTIVE.registries.forEach((name, reg) -> reg.validateContent(name)); RegistryManager.ACTIVE.registries.forEach((name, reg) -> reg.dump(name)); RegistryManager.ACTIVE.registries.forEach((name, reg) -> reg.resetDelegates()); List missingRegs = snapshot.keySet().stream().filter(name -> !RegistryManager.ACTIVE.registries.containsKey(name)).collect(Collectors.toList()); if (missingRegs.size() > 0) { String text = "Forge Mod Loader detected missing/unknown registrie(s).\n\n" + "There are " + missingRegs.size() + " 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" + "Missing Registries:\n"; for (ResourceLocation s : missingRegs) text += s.toString() + "\n"; if (!StartupQuery.confirm(text)) StartupQuery.abort(); } RegistryManager STAGING = new RegistryManager("STAGING"); final Map> remaps = Maps.newHashMap(); final LinkedHashMap> missing = Maps.newLinkedHashMap(); // Load the snapshot into the "STAGING" registry snapshot.forEach((key, value) -> { final Class clazz = RegistryManager.ACTIVE.getSuperType(key); remaps.put(key, Maps.newLinkedHashMap()); missing.put(key, Maps.newHashMap()); loadPersistentDataToStagingRegistry(RegistryManager.ACTIVE, STAGING, remaps.get(key), missing.get(key), key, value, clazz); }); snapshot.forEach((key, value) -> { value.dummied.forEach(dummy -> { Map m = missing.get(key); ForgeRegistry reg = STAGING.getRegistry(key); // Currently missing locally, we just inject and carry on if (m.containsKey(dummy)) { if (reg.markDummy(dummy, m.get(dummy))) m.remove(dummy); } else if (isLocalWorld) { LOGGER.debug("Registry {}: Resuscitating dummy entry {}", key, dummy); } else { // The server believes this is a dummy block identity, but we seem to have one locally. This is likely a conflict // in mod setup - Mark this entry as a dummy int id = reg.getID(dummy); LOGGER.warn(GD, "Registry {}: The ID {} @ {} is currently locally mapped - it will be replaced with a dummy for this session", dummy, key, id); reg.markDummy(dummy, id); } }); }); int count = missing.values().stream().mapToInt(Map::size).sum(); if (count > 0) { LOGGER.debug(GD,"There are {} mappings missing - attempting a mod remap", count); Multimap defaulted = ArrayListMultimap.create(); Multimap failed = ArrayListMultimap.create(); missing.entrySet().stream().filter(e -> e.getValue().size() > 0).forEach(m -> { ResourceLocation name = m.getKey(); ForgeRegistry reg = STAGING.getRegistry(name); RegistryEvent.MissingMappings event = reg.getMissingEvent(name, m.getValue()); MinecraftForge.EVENT_BUS.post(event); List> lst = event.getAllMappings().stream().filter(e -> e.getAction() == MissingMappings.Action.DEFAULT).sorted((a, b) -> a.toString().compareTo(b.toString())).collect(Collectors.toList()); if (!lst.isEmpty()) { LOGGER.error(GD,"Unidentified mapping from registry {}", name); lst.forEach(map -> LOGGER.error(" {}: {}", map.key, map.id)); } event.getAllMappings().stream().filter(e -> e.getAction() == MissingMappings.Action.FAIL).forEach(fail -> failed.put(name, fail.key)); final Class clazz = RegistryManager.ACTIVE.getSuperType(name); processMissing(clazz, name, STAGING, event, m.getValue(), remaps.get(name), defaulted.get(name), failed.get(name), !isLocalWorld); }); if (!defaulted.isEmpty() && !isLocalWorld) return defaulted; if (!defaulted.isEmpty()) { StringBuilder buf = new StringBuilder(); buf.append("Forge Mod Loader detected missing registry entries.\n\n") .append("There are ").append(defaulted.size()).append(" missing entries in this save.\n") .append("If you continue the missing entries will get removed.\n") .append("A world backup will be automatically created in your saves directory.\n\n"); defaulted.asMap().forEach((name, entries) -> { buf.append("Missing ").append(name).append(":\n"); entries.forEach(rl -> buf.append(" ").append(rl).append("\n")); }); boolean confirmed = StartupQuery.confirm(buf.toString()); if (!confirmed) StartupQuery.abort(); /* try { String skip = System.getProperty("fml.doNotBackup"); if (skip == null || !"true".equals(skip)) { ZipperUtil.backupWorld(); } else { for (int x = 0; x < 10; x++) LOGGER.error(GD, "!!!!!!!!!! UPDATING WORLD WITHOUT DOING BACKUP !!!!!!!!!!!!!!!!"); } } catch (IOException e) { StartupQuery.notify("The world backup couldn't be created.\n\n" + e); StartupQuery.abort(); } */ } if (!defaulted.isEmpty()) { if (isLocalWorld) LOGGER.error(GD, "There are unidentified mappings in this world - we are going to attempt to process anyway"); } } if (injectFrozenData) { // If we're loading from disk, we can actually substitute air in the block map for anything that is otherwise "missing". This keeps the reference in the map, in case // the block comes back later missing.forEach((name, m) -> { ForgeRegistry reg = STAGING.getRegistry(name); m.forEach((rl, id) -> reg.markDummy(rl, id)); }); // 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 RegistryManager.ACTIVE.registries.forEach((name, reg) -> { final Class clazz = RegistryManager.ACTIVE.getSuperType(name); loadFrozenDataToStagingRegistry(STAGING, name, remaps.get(name), clazz); }); } // Validate that all the STAGING data is good STAGING.registries.forEach((name, reg) -> reg.validateContent(name)); // Load the STAGING registry into the ACTIVE registry //for (Map.Entry>> r : RegistryManager.ACTIVE.registries.entrySet()) RegistryManager.ACTIVE.registries.forEach((key, value) -> { final Class registrySuperType = RegistryManager.ACTIVE.getSuperType(key); loadRegistry(key, STAGING, RegistryManager.ACTIVE, registrySuperType, true); }); // Dump the active registry RegistryManager.ACTIVE.registries.forEach((name, reg) -> reg.dump(name)); // Tell mods that the ids have changed //Loader.instance().fireRemapEvent(remaps, false); // The id map changed, ensure we apply object holders ObjectHolderRegistry.applyObjectHolders(); // Return an empty list, because we're good return ArrayListMultimap.create(); } //Has to be split because of generics, Yay! private static > void loadPersistentDataToStagingRegistry(RegistryManager pool, RegistryManager to, Map remaps, Map missing, ResourceLocation name, ForgeRegistry.Snapshot snap, Class regType) { ForgeRegistry 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 _new = to.getRegistry(name, RegistryManager.ACTIVE); snap.aliases.forEach(_new::addAlias); snap.blocked.forEach(_new::block); // Load current dummies BEFORE the snapshot is loaded so that add() will remove from the list. snap.dummied.forEach(_new::addDummy); _new.loadIds(snap.ids, snap.overrides, missing, remaps, active, name); } //Another bouncer for generic reasons @SuppressWarnings("unchecked") private static > void processMissing(Class clazz, ResourceLocation name, RegistryManager STAGING, MissingMappings e, Map missing, Map remaps, Collection defaulted, Collection failed, boolean injectNetworkDummies) { List> mappings = ((MissingMappings)e).getAllMappings(); ForgeRegistry active = RegistryManager.ACTIVE.getRegistry(name); ForgeRegistry staging = STAGING.getRegistry(name); staging.processMissingEvent(name, active, mappings, missing, remaps, defaulted, failed, injectNetworkDummies); } private static > void loadFrozenDataToStagingRegistry(RegistryManager STAGING, ResourceLocation name, Map remaps, Class clazz) { ForgeRegistry frozen = RegistryManager.FROZEN.getRegistry(name); ForgeRegistry newRegistry = STAGING.getRegistry(name, RegistryManager.FROZEN); Map _new = Maps.newHashMap(); frozen.getKeys().stream().filter(key -> !newRegistry.containsKey(key)).forEach(key -> _new.put(key, frozen.getID(key))); newRegistry.loadIds(_new, frozen.getOverrideOwners(), Maps.newLinkedHashMap(), remaps, frozen, name); } public static void fireCreateRegistryEvents() { MinecraftForge.EVENT_BUS.post(new RegistryEvent.NewRegistry()); } public static void fireRegistryEvents() { fireRegistryEvents(rl -> true); } public static void fireRegistryEvents(Predicate filter) { List keys = Lists.newArrayList(RegistryManager.ACTIVE.registries.keySet()); Collections.sort(keys, (o1, o2) -> o1.toString().compareToIgnoreCase(o2.toString())); /* RegistryManager.ACTIVE.registries.forEach((name, reg) -> { if (filter.test(name)) ((ForgeRegistry)reg).unfreeze(); }); */ if (filter.test(BLOCKS)) { MinecraftForge.EVENT_BUS.post(RegistryManager.ACTIVE.getRegistry(BLOCKS).getRegisterEvent(BLOCKS)); } if (filter.test(ITEMS)) { MinecraftForge.EVENT_BUS.post(RegistryManager.ACTIVE.getRegistry(ITEMS).getRegisterEvent(ITEMS)); } for (ResourceLocation rl : keys) { if (!filter.test(rl)) continue; if (rl == BLOCKS || rl == ITEMS) continue; MinecraftForge.EVENT_BUS.post(RegistryManager.ACTIVE.getRegistry(rl).getRegisterEvent(rl)); } /* RegistryManager.ACTIVE.registries.forEach((name, reg) -> { if (filter.test(name)) ((ForgeRegistry)reg).freeze(); }); */ } public static ResourceLocation checkPrefix(String name) { int index = name.lastIndexOf(':'); String oldPrefix = index == -1 ? "" : name.substring(0, index).toLowerCase(Locale.ROOT); name = index == -1 ? name : name.substring(index + 1); String prefix = ModThreadContext.get().getActiveContainer().getPrefix(); if (!oldPrefix.equals(prefix) && oldPrefix.length() > 0) { LogManager.getLogger().info("Potentially Dangerous alternative prefix `{}` for name `{}`, expected `{}`. This could be a intended override, but in most cases indicates a broken mod.", oldPrefix, name, prefix); prefix = oldPrefix; } return new ResourceLocation(prefix, name); } private static Field regName; private static void forceRegistryName(IForgeRegistryEntry entry, ResourceLocation name) { if (regName == null) { try { regName = ForgeRegistryEntry.class.getDeclaredField("registryName"); regName.setAccessible(true); } catch (NoSuchFieldException | SecurityException e) { LOGGER.error(GD, "Could not get `registryName` field from IForgeRegistryEntry.Impl", e); throw new RuntimeException(e); } } try { regName.set(entry, name); } catch (IllegalArgumentException | IllegalAccessException e) { LOGGER.error(GD,"Could not set `registryName` field in IForgeRegistryEntry.Impl to `{}`", name.toString(), e); throw new RuntimeException(e); } } }