/*
* Minecraft Forge
* Copyright (c) 2016.
*
* 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.fml.common.registry;
import java.lang.reflect.Field;
import java.util.Map;
import com.google.common.base.Throwables;
import com.google.common.collect.HashBiMap;
import net.minecraft.block.Block;
import net.minecraft.block.state.IBlockState;
import net.minecraft.enchantment.Enchantment;
import net.minecraft.entity.EntityList;
import net.minecraft.item.Item;
import net.minecraft.item.ItemBlock;
import net.minecraft.potion.Potion;
import net.minecraft.potion.PotionType;
import net.minecraft.tileentity.TileEntity;
import net.minecraft.util.ObjectIntIdentityMap;
import net.minecraft.util.ResourceLocation;
import net.minecraft.util.SoundEvent;
import net.minecraft.world.biome.Biome;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.ModContainer;
import com.google.common.collect.BiMap;
import net.minecraftforge.fml.relauncher.ReflectionHelper;
import org.apache.logging.log4j.Level;
public class GameData
{
private static final int MIN_BLOCK_ID = 0;
private static final int MAX_BLOCK_ID = 4095;
private static final int MIN_ITEM_ID = 4096;
private static final int MAX_ITEM_ID = 31999;
private static final int MIN_POTION_ID = 0; // 0-~31 are vanilla, forge start at 32
private static final int MAX_POTION_ID = 255; // SPacketEntityEffect sends bytes, we can only use 255
private static final int MIN_BIOME_ID = 0;
private static final int MAX_BIOME_ID = 255; // Maximum number in a byte in the chunk
private static final int MIN_SOUND_ID = 0; // Varint
private static final int MAX_SOUND_ID = Integer.MAX_VALUE >> 5; // Varint (SPacketSoundEffect)
private static final int MIN_POTIONTYPE_ID = 0; // Int
private static final int MAX_POTIONTYPE_ID = Integer.MAX_VALUE >> 5; // Int (SPacketEffect)
private static final int MIN_ENCHANTMENT_ID = 0; // Int
private static final int MAX_ENCHANTMENT_ID = Short.MAX_VALUE - 1; // Short - serialized as a short in ItemStack NBTs.
private static final int MIN_ENTITY_ID = 0;
private static final int MAX_ENTITY_ID = Integer.MAX_VALUE >> 5; // Varint (SPacketSpawnMob)
private static final ResourceLocation BLOCK_TO_ITEM = new ResourceLocation("minecraft:blocktoitemmap");
private static final ResourceLocation BLOCKSTATE_TO_ID = new ResourceLocation("minecraft:blockstatetoid");
private static final GameData mainData = new GameData();
private static Field blockField;
public GameData()
{
iBlockRegistry = PersistentRegistryManager.createRegistry(PersistentRegistryManager.BLOCKS, Block.class, new ResourceLocation("minecraft:air"), MIN_BLOCK_ID, MAX_BLOCK_ID, true, BlockCallbacks.INSTANCE, BlockCallbacks.INSTANCE, BlockCallbacks.INSTANCE, BlockCallbacks.INSTANCE);
iItemRegistry = PersistentRegistryManager.createRegistry(PersistentRegistryManager.ITEMS, Item.class, null, MIN_ITEM_ID, MAX_ITEM_ID, true, ItemCallbacks.INSTANCE, ItemCallbacks.INSTANCE, ItemCallbacks.INSTANCE, ItemCallbacks.INSTANCE);
iPotionRegistry = PersistentRegistryManager.createRegistry(PersistentRegistryManager.POTIONS, Potion.class, null, MIN_POTION_ID, MAX_POTION_ID, false, PotionCallbacks.INSTANCE, PotionCallbacks.INSTANCE, PotionCallbacks.INSTANCE, null);
iBiomeRegistry = PersistentRegistryManager.createRegistry(PersistentRegistryManager.BIOMES, Biome.class, null, MIN_BIOME_ID, MAX_BIOME_ID, false, BiomeCallbacks.INSTANCE, BiomeCallbacks.INSTANCE, BiomeCallbacks.INSTANCE, null);
iSoundEventRegistry = PersistentRegistryManager.createRegistry(PersistentRegistryManager.SOUNDEVENTS, SoundEvent.class, null, MIN_SOUND_ID, MAX_SOUND_ID, false, null, null, null, null);
ResourceLocation WATER = new ResourceLocation("water");
iPotionTypeRegistry = PersistentRegistryManager.createRegistry(PersistentRegistryManager.POTIONTYPES, PotionType.class, WATER, MIN_POTIONTYPE_ID, MAX_POTIONTYPE_ID, false, null, null, null, null);
iEnchantmentRegistry = PersistentRegistryManager.createRegistry(PersistentRegistryManager.ENCHANTMENTS, Enchantment.class, null, MIN_ENCHANTMENT_ID, MAX_ENCHANTMENT_ID, false, null, null, null, null);
iEntityRegistry = (FMLControlledNamespacedRegistry<EntityEntry>)new RegistryBuilder<EntityEntry>().setName(PersistentRegistryManager.ENTITIES).setType(EntityEntry.class).setIDRange(MIN_ENTITY_ID, MAX_ENTITY_ID).addCallback(EntityCallbacks.INSTANCE).create();
try
{
blockField = FinalFieldHelper.makeWritable(ReflectionHelper.findField(ItemBlock.class, "block", "field_150939" + "_a"));
}
catch (Exception e)
{
FMLLog.log(Level.FATAL, e, "Cannot access the 'block' field from ItemBlock, this is fatal!");
throw Throwables.propagate(e);
}
iTileEntityRegistry = new LegacyNamespacedRegistry<Class<? extends TileEntity>>();
}
// internal registry objects
private final FMLControlledNamespacedRegistry<Block> iBlockRegistry;
private final FMLControlledNamespacedRegistry<Item> iItemRegistry;
private final FMLControlledNamespacedRegistry<Potion> iPotionRegistry;
private final FMLControlledNamespacedRegistry<Biome> iBiomeRegistry;
private final FMLControlledNamespacedRegistry<SoundEvent> iSoundEventRegistry;
private final FMLControlledNamespacedRegistry<PotionType> iPotionTypeRegistry;
private final FMLControlledNamespacedRegistry<Enchantment> iEnchantmentRegistry;
private final FMLControlledNamespacedRegistry<EntityEntry> iEntityRegistry;
//TODO: ? These are never used by ID, so they don't need to be full registries/persisted.
//Need cpw to decide how we want to go about this as they are generic registries that
//don't follow the same patterns as the other ones.
private final LegacyNamespacedRegistry<Class<? extends TileEntity>> iTileEntityRegistry;
/** INTERNAL ONLY */
@Deprecated
public static FMLControlledNamespacedRegistry<Block> getBlockRegistry()
{
return getMain().iBlockRegistry;
}
/** INTERNAL ONLY */
@Deprecated
public static FMLControlledNamespacedRegistry<Item> getItemRegistry()
{
return getMain().iItemRegistry;
}
/** INTERNAL ONLY */
@Deprecated
public static FMLControlledNamespacedRegistry<Potion> getPotionRegistry() {
return getMain().iPotionRegistry;
}
/** INTERNAL ONLY */
@Deprecated
public static FMLControlledNamespacedRegistry<Biome> getBiomeRegistry() { return getMain().iBiomeRegistry; }
/** INTERNAL ONLY */
@Deprecated
public static FMLControlledNamespacedRegistry<SoundEvent> getSoundEventRegistry() { return getMain().iSoundEventRegistry; }
/** INTERNAL ONLY */
@Deprecated
public static FMLControlledNamespacedRegistry<PotionType> getPotionTypesRegistry() { return getMain().iPotionTypeRegistry; }
/** INTERNAL ONLY */
@Deprecated
public static FMLControlledNamespacedRegistry<Enchantment> getEnchantmentRegistry() { return getMain().iEnchantmentRegistry; }
/** INTERNAL ONLY */
@Deprecated
public static LegacyNamespacedRegistry<Class<? extends TileEntity>> getTileEntityRegistry() { return getMain().iTileEntityRegistry; }
/** INTERNAL ONLY */
@Deprecated
public static FMLControlledNamespacedRegistry<EntityEntry> getEntityRegistry() { return getMain().iEntityRegistry; }
@Deprecated
static Item findItem(String modId, String name)
{
return getMain().iItemRegistry.getObject(new ResourceLocation(modId, name));
}
@Deprecated
static Block findBlock(String modId, String name)
{
return getMain().iBlockRegistry.getObject(new ResourceLocation(modId, name));
}
protected static GameData getMain()
{
return mainData;
}
void registerSubstitutionAlias(String name, GameRegistry.Type type, Object toReplace) throws ExistingSubstitutionException
{
ResourceLocation nameToSubstitute = new ResourceLocation(name);
final BiMap<Block, Item> blockItemMap = getBlockItemMap();
if (type == GameRegistry.Type.BLOCK)
{
iBlockRegistry.addSubstitutionAlias(Loader.instance().activeModContainer().getModId(), nameToSubstitute, (Block)toReplace);
iBlockRegistry.activateSubstitution(nameToSubstitute);
}
else if (type == GameRegistry.Type.ITEM)
{
iItemRegistry.addSubstitutionAlias(Loader.instance().activeModContainer().getModId(), nameToSubstitute, (Item)toReplace);
iItemRegistry.activateSubstitution(nameToSubstitute);
}
}
@SuppressWarnings("unchecked")
public static BiMap<Block,Item> getBlockItemMap()
{
return (BiMap<Block,Item>)getMain().iItemRegistry.getSlaveMap(BLOCK_TO_ITEM, BiMap.class);
}
@SuppressWarnings("unchecked")
static <K extends IForgeRegistryEntry<K>> K register_impl(Object object, ResourceLocation location)
{
if (object == null)
{
FMLLog.getLogger().log(Level.ERROR, "Attempt to register a null object");
throw new NullPointerException("Attempt to register a null object");
}
((K)object).setRegistryName(location);
// explicit type is needed for some reason
return GameData.<K>register_impl(object);
}
// argument is Object due to incorrect inferrence of the type if source level = 6, probably related to https://bugs.openjdk.java.net/browse/JDK-8026527
@SuppressWarnings("unchecked")
static <K extends IForgeRegistryEntry<K>> K register_impl(Object object)
{
K castedObj = (K)object;
if (object == null)
{
FMLLog.getLogger().log(Level.ERROR, "Attempt to register a null object");
throw new NullPointerException("Attempt to register a null object");
}
if (castedObj.getRegistryName() == null)
{
FMLLog.getLogger().log(Level.ERROR, "Attempt to register object without having set a registry name {} (type {})", object, object.getClass().getName());
throw new IllegalArgumentException(String.format("No registry name set for object %s (%s)", object, object.getClass().getName()));
}
final IForgeRegistry<K> registry = PersistentRegistryManager.findRegistry(castedObj);
registry.register(castedObj);
return castedObj;
}
@SuppressWarnings("unchecked")
public static ObjectIntIdentityMap<IBlockState> getBlockStateIDMap()
{
return (ObjectIntIdentityMap<IBlockState>)getMain().iBlockRegistry.getSlaveMap(BLOCKSTATE_TO_ID, ObjectIntIdentityMap.class);
}
public static void vanillaSnapshot()
{
PersistentRegistryManager.freezeVanilla();
}
//Lets us clear the map so we can rebuild it.
static class ClearableObjectIntIdentityMap<I> extends ObjectIntIdentityMap<I>
{
void clear()
{
this.identityMap.clear();
this.objectList.clear();
}
}
public <T extends IForgeRegistryEntry<T>> RegistryDelegate<T> makeDelegate(T obj, Class<T> rootClass)
{
return PersistentRegistryManager.makeDelegate(obj, rootClass);
}
private static class BlockCallbacks implements IForgeRegistry.AddCallback<Block>,IForgeRegistry.ClearCallback<Block>,IForgeRegistry.CreateCallback<Block>, IForgeRegistry.SubstitutionCallback<Block>
{
static final BlockCallbacks INSTANCE = new BlockCallbacks();
@SuppressWarnings("unchecked")
@Override
public void onAdd(Block block, int blockId, Map<ResourceLocation,?> slaves)
{
ClearableObjectIntIdentityMap<IBlockState> blockstateMap = (ClearableObjectIntIdentityMap<IBlockState>)slaves.get(BLOCKSTATE_TO_ID);
//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, blockId << 4 | meta); //Add ALL the things!
usedMeta[meta] = true;
}
for (int meta = 0; meta < 16; meta++)
{
if (usedMeta[meta])
blockstateMap.put(block.getStateFromMeta(meta), blockId << 4 | meta); // Put the CORRECT thing!
}
}
@SuppressWarnings("unchecked")
@Override
public void onClear(IForgeRegistry<Block> registry, Map<ResourceLocation, ?> slaveset)
{
ClearableObjectIntIdentityMap<IBlockState> blockstateMap = (ClearableObjectIntIdentityMap<IBlockState>)slaveset.get(BLOCKSTATE_TO_ID);
blockstateMap.clear();
final Map<ResourceLocation, Block> originals = (Map<ResourceLocation, Block>)slaveset.get(PersistentRegistryManager.SUBSTITUTION_ORIGINALS);
final BiMap<Block, Item> blockItemMap = (BiMap<Block, Item>)slaveset.get(BLOCK_TO_ITEM);
for (Item it : blockItemMap.values())
{
if (it instanceof ItemBlock) {
ItemBlock itemBlock = (ItemBlock)it;
final ResourceLocation registryKey = registry.getKey(itemBlock.block);
if (!originals.containsKey(registryKey)) continue;
try
{
FinalFieldHelper.setField(blockField, itemBlock, originals.get(registryKey));
}
catch (Exception e)
{
throw Throwables.propagate(e);
}
}
}
}
@SuppressWarnings("unchecked")
@Override
public void onCreate(Map<ResourceLocation, ?> slaveset, BiMap<ResourceLocation, ? extends IForgeRegistry<?>> registries)
{
final ClearableObjectIntIdentityMap<IBlockState> idMap = new ClearableObjectIntIdentityMap<IBlockState>()
{
@SuppressWarnings("deprecation")
@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();
}
};
((Map<ResourceLocation,Object>)slaveset).put(BLOCKSTATE_TO_ID, idMap);
final HashBiMap<Block, Item> map = HashBiMap.create();
((Map<ResourceLocation,Object>)slaveset).put(BLOCK_TO_ITEM, map);
}
@Override
public void onSubstituteActivated(Map<ResourceLocation, ?> slaveset, Block original, Block replacement, ResourceLocation name)
{
final BiMap<Block, Item> blockItemMap = (BiMap<Block, Item>)slaveset.get(BLOCK_TO_ITEM);
if (blockItemMap.containsKey(original))
{
Item i = blockItemMap.get(original);
if (i instanceof ItemBlock)
{
try
{
FinalFieldHelper.setField(blockField, i, replacement);
}
catch (Exception e)
{
throw Throwables.propagate(e);
}
}
blockItemMap.forcePut(replacement,i);
}
}
}
private static class ItemCallbacks implements IForgeRegistry.AddCallback<Item>,IForgeRegistry.ClearCallback<Item>,IForgeRegistry.CreateCallback<Item>, IForgeRegistry.SubstitutionCallback<Item>
{
static final ItemCallbacks INSTANCE = new ItemCallbacks();
@Override
public void onAdd(Item item, int blockId, Map<ResourceLocation, ?> slaves)
{
if (item instanceof ItemBlock)
{
ItemBlock itemBlock = (ItemBlock)item;
@SuppressWarnings("unchecked") BiMap<Block, Item> blockToItem = (BiMap<Block, Item>)slaves.get(BLOCK_TO_ITEM);
final Block block = itemBlock.getBlock().delegate.get();
blockToItem.forcePut(block, item);
}
}
@SuppressWarnings("unchecked")
@Override
public void onClear(IForgeRegistry<Item> registry, Map<ResourceLocation, ?> slaveset)
{
Map<Block,Item> map = (Map<Block, Item>)slaveset.get(BLOCK_TO_ITEM);
map.clear();
}
@SuppressWarnings("unchecked")
@Override
public void onCreate(Map<ResourceLocation, ?> slaveset, BiMap<ResourceLocation, ? extends IForgeRegistry<?>> registries)
{
// We share the blockItem map between items and blocks registries
final BiMap blockItemMap = (BiMap<Block, Item>)registries.get(PersistentRegistryManager.BLOCKS).getSlaveMap(BLOCK_TO_ITEM, BiMap.class);
((Map<ResourceLocation,Object>)slaveset).put(BLOCK_TO_ITEM, blockItemMap);
}
@Override
public void onSubstituteActivated(Map<ResourceLocation, ?> slaveset, Item original, Item replacement, ResourceLocation name)
{
final BiMap<Block, Item> blockItemMap = (BiMap<Block, Item>)slaveset.get(BLOCK_TO_ITEM);
}
}
private static class PotionCallbacks implements IForgeRegistry.AddCallback<Potion>,IForgeRegistry.ClearCallback<Potion>,IForgeRegistry.CreateCallback<Potion>
{
static final PotionCallbacks INSTANCE = new PotionCallbacks();
@Override
public void onAdd(Potion potion, int id, Map<ResourceLocation, ?> slaves) {
// no op for the minute?
}
@Override
public void onClear(IForgeRegistry<Potion> registry, Map<ResourceLocation, ?> slaveset)
{
// no op for the minute?
}
@Override
public void onCreate(Map<ResourceLocation, ?> slaveset, BiMap<ResourceLocation, ? extends IForgeRegistry<?>> registries)
{
// no op for the minute?
}
}
private static class BiomeCallbacks implements IForgeRegistry.AddCallback<Biome>,IForgeRegistry.ClearCallback<Biome>,IForgeRegistry.CreateCallback<Biome>
{
static final BiomeCallbacks INSTANCE = new BiomeCallbacks();
@Override
public void onAdd(Biome biome, int id, Map<ResourceLocation, ?> slaves) {
// no op for the minute?
}
@Override
public void onClear(IForgeRegistry<Biome> registry, Map<ResourceLocation, ?> slaveset)
{
// no op for the minute?
}
@Override
public void onCreate(Map<ResourceLocation, ?> slaveset, BiMap<ResourceLocation, ? extends IForgeRegistry<?>> registries)
{
// no op for the minute?
}
}
private static class EntityCallbacks implements IForgeRegistry.AddCallback<EntityEntry>,IForgeRegistry.ClearCallback<EntityEntry>,IForgeRegistry.CreateCallback<EntityEntry>
{
static final EntityCallbacks INSTANCE = new EntityCallbacks();
@Override
public void onAdd(EntityEntry entry, int id, Map<ResourceLocation, ?> slaves)
{
if (entry.getEgg() != null)
EntityList.ENTITY_EGGS.put(entry.getRegistryName(), entry.getEgg());
}
@Override
public void onClear(IForgeRegistry<EntityEntry> registry, Map<ResourceLocation, ?> slaveset)
{
// no op for the minute?
}
@Override
public void onCreate(Map<ResourceLocation, ?> slaveset, BiMap<ResourceLocation, ? extends IForgeRegistry<?>> registries)
{
// no op for the minute?
}
}
}