Registry: Implement support for remapping blocks/items to a new name.

This commit is contained in:
Player 2014-03-25 00:36:37 +01:00
parent 180c605570
commit ac44af863b
5 changed files with 330 additions and 113 deletions

View file

@ -15,6 +15,7 @@ package cpw.mods.fml.common;
import java.io.File;
import java.security.cert.Certificate;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@ -101,6 +102,7 @@ public class FMLContainer extends DummyModContainer implements WorldAccessContai
list.func_74742_a(mod);
}
fmlData.func_74782_a("ModList", list);
// name <-> id mappings
NBTTagList dataList = new NBTTagList();
FMLLog.fine("Gathering id map for writing to world save %s", info.func_76065_j());
Map<String,Integer> itemList = GameData.buildItemDataList();
@ -112,7 +114,29 @@ public class FMLContainer extends DummyModContainer implements WorldAccessContai
dataList.func_74742_a(tag);
}
fmlData.func_74782_a("ItemData", dataList);
fmlData.func_74783_a("BlockedIds", GameData.getBlockedIds());
// blocked ids
fmlData.func_74783_a("BlockedItemIds", GameData.getBlockedIds());
// block aliases
NBTTagList blockAliasList = new NBTTagList();
for (Entry<String, String> entry : GameData.getBlockRegistry().getAliases().entrySet())
{
NBTTagCompound tag = new NBTTagCompound();
tag.func_74778_a("K", entry.getKey());
tag.func_74778_a("V", entry.getValue());
blockAliasList.func_74742_a(tag);
}
fmlData.func_74782_a("BlockAliases", blockAliasList);
// item aliases
NBTTagList itemAliasList = new NBTTagList();
for (Entry<String, String> entry : GameData.getItemRegistry().getAliases().entrySet())
{
NBTTagCompound tag = new NBTTagCompound();
tag.func_74778_a("K", entry.getKey());
tag.func_74778_a("V", entry.getValue());
itemAliasList.func_74742_a(tag);
}
fmlData.func_74782_a("ItemAliases", itemAliasList);
return fmlData;
}
@ -187,18 +211,34 @@ public class FMLContainer extends DummyModContainer implements WorldAccessContai
}
else if (tag.func_74764_b("ItemData"))
{
// read name <-> id mappings
NBTTagList list = tag.func_150295_c("ItemData", (byte)10);
// name <-> id mappings
NBTTagList list = tag.func_150295_c("ItemData", 10);
Map<String,Integer> dataList = Maps.newLinkedHashMap();
for (int i = 0; i < list.func_74745_c(); i++)
{
NBTTagCompound dataTag = list.func_150305_b(i);
dataList.put(dataTag.func_74779_i("K"), dataTag.func_74762_e("V"));
}
// read blocked ids
int[] blockedIds = tag.func_74759_k("BlockedIds");
// blocked ids
int[] blockedIds = tag.func_74759_k("BlockedItemIds");
// block aliases
Map<String, String> blockAliases = new HashMap<String, String>();
list = tag.func_150295_c("BlockAliases", 10);
for (int i = 0; i < list.func_74745_c(); i++)
{
NBTTagCompound dataTag = list.func_150305_b(i);
blockAliases.put(dataTag.func_74779_i("K"), dataTag.func_74779_i("V"));
}
// item aliases
Map<String, String> itemAliases = new HashMap<String, String>();
list = tag.func_150295_c("ItemAliases", 10);
for (int i = 0; i < list.func_74745_c(); i++)
{
NBTTagCompound dataTag = list.func_150305_b(i);
itemAliases.put(dataTag.func_74779_i("K"), dataTag.func_74779_i("V"));
}
List<String> failedElements = GameData.injectWorldIDMap(dataList, blockedIds, true, true);
List<String> failedElements = GameData.injectWorldIDMap(dataList, blockedIds, blockAliases, itemAliases, true, true);
if (!failedElements.isEmpty())
{
throw new GameRegistryException("Failed to load the world - there are fatal block and item id issues", failedElements);

View file

@ -856,7 +856,7 @@ public class Loader
* @param gameData GameData instance where the new map's config is to be loaded into.
* @return List with the mapping results.
*/
public List<String> fireMissingMappingEvent(LinkedHashMap<String, Integer> missing, boolean isLocalWorld, GameData gameData)
public List<String> fireMissingMappingEvent(LinkedHashMap<String, Integer> missing, boolean isLocalWorld, GameData gameData, Map<String, Integer[]> remaps)
{
if (missing.isEmpty()) // nothing to do
{
@ -868,10 +868,9 @@ public class Loader
for (Map.Entry<String, Integer> mapping : missing.entrySet())
{
String itemName = mapping.getKey();
int id = mapping.getValue();
MissingMapping m = new MissingMapping(itemName, id);
missingMappings.put(itemName.substring(0, itemName.indexOf(':')), m);
MissingMapping m = new MissingMapping(mapping.getKey(), id);
missingMappings.put(m.name.substring(0, m.name.indexOf(':')), m);
}
FMLMissingMappingsEvent missingEvent = new FMLMissingMappingsEvent(missingMappings);
@ -913,7 +912,7 @@ public class Loader
}
}
return GameData.processIdRematches(missingMappings.values(), isLocalWorld, gameData);
return GameData.processIdRematches(missingMappings.values(), isLocalWorld, gameData, remaps);
}
public void fireRemapEvent(Map<String, Integer[]> remaps)

View file

@ -2,11 +2,14 @@ package cpw.mods.fml.common.event;
import java.util.List;
import net.minecraft.block.Block;
import net.minecraft.item.Item;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ListMultimap;
import cpw.mods.fml.common.FMLCommonHandler;
import cpw.mods.fml.common.ModContainer;
import cpw.mods.fml.common.registry.GameData;
import cpw.mods.fml.common.registry.GameRegistry;
/**
@ -28,49 +31,102 @@ public class FMLMissingMappingsEvent extends FMLEvent {
* @author cpw
*
*/
public static enum Action { DEFAULT, IGNORE, WARN, FAIL }
public static enum Action { DEFAULT, IGNORE, WARN, FAIL, REMAP }
public static class MissingMapping {
public final GameRegistry.Type type;
public final String name;
public final int id;
private Action action = Action.DEFAULT;
private Object target;
public MissingMapping(String name, int id)
{
this.type = name.charAt(0) == '\u0001' ? GameRegistry.Type.BLOCK : GameRegistry.Type.ITEM;
this.name = name;
this.name = name.substring(1);
this.id = id;
}
/**
* @deprecated use ignore(), warn() or fail() instead
* @deprecated use ignore(), warn(), fail() or remap() instead
*/
@Deprecated
public void setAction(Action target)
{
if (target == Action.DEFAULT) throw new IllegalArgumentException();
if (target == Action.DEFAULT || target == Action.REMAP) throw new IllegalArgumentException();
this.action = target;
}
/**
* Ignore the missing item.
*/
public void ignore()
{
this.action = Action.IGNORE;
action = Action.IGNORE;
}
/**
* Warn the user about the missing item.
*/
public void warn()
{
this.action = Action.WARN;
action = Action.WARN;
}
/**
* Prevent the world from loading due to the missing item.
*/
public void fail()
{
this.action = Action.FAIL;
action = Action.FAIL;
}
/**
* Remap the missing item to the specified Block.
*
* Use this if you have renamed a Block, don't forget to handle the ItemBlock.
* Existing references using the old name will point to the new one.
*
* @param target Block to remap to.
*/
public void remap(Block target)
{
if (type != GameRegistry.Type.BLOCK) throw new IllegalArgumentException("Can't remap an item to a block.");
if (target == null) throw new NullPointerException("remap target is null");
if (GameData.getBlockRegistry().getId(target) < 0) throw new IllegalArgumentException(String.format("The specified block %s hasn't been registered at startup.", target));
action = Action.REMAP;
this.target = target;
}
/**
* Remap the missing item to the specified Item.
*
* Use this if you have renamed an Item.
* Existing references using the old name will point to the new one.
*
* @param target Item to remap to.
*/
public void remap(Item target)
{
if (type != GameRegistry.Type.ITEM) throw new IllegalArgumentException("Can't remap a block to an item.");
if (target == null) throw new NullPointerException("remap target is null");
if (GameData.getItemRegistry().getId(target) < 0) throw new IllegalArgumentException(String.format("The specified item %s hasn't been registered at startup.", target));
action = Action.REMAP;
this.target = target;
}
// internal
public Action getAction()
{
return this.action;
}
public Object getTarget()
{
return target;
}
}
private ListMultimap<String,MissingMapping> missing;
private ModContainer activeContainer;

View file

@ -15,6 +15,7 @@ import net.minecraft.util.RegistryNamespaced;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import cpw.mods.fml.common.FMLLog;
@ -25,10 +26,11 @@ public class FMLControlledNamespacedRegistry<I> extends RegistryNamespaced {
private final Class<I> superType;
private String optionalDefaultName;
private I optionalDefaultObject;
int maxId;
private int maxId;
private int minId;
private char discriminator;
// aliases redirecting legacy names to the actual name, may need recursive application to find the final name
private final Map<String, String> aliases = new HashMap<String, String>();
FMLControlledNamespacedRegistry(String optionalDefault, int maxIdValue, int minIdValue, Class<I> type, char discriminator)
{
@ -47,6 +49,7 @@ public class FMLControlledNamespacedRegistry<I> extends RegistryNamespaced {
this.optionalDefaultName = registry.optionalDefaultName;
this.maxId = registry.maxId;
this.minId = registry.minId;
this.aliases.putAll(registry.aliases);
field_148759_a = new ObjectIntIdentityMap();
field_82596_a.clear();
@ -58,6 +61,8 @@ public class FMLControlledNamespacedRegistry<I> extends RegistryNamespaced {
}
}
// public api
/**
* Add an object to the registry, trying to use the specified id.
*
@ -70,6 +75,137 @@ public class FMLControlledNamespacedRegistry<I> extends RegistryNamespaced {
GameData.getMain().register(thing, name, id);
}
@Override
public I func_82594_a(String name)
{
I object = getRaw(name);
return object == null ? this.optionalDefaultObject : object;
}
@Override
public I func_148754_a(int id)
{
I object = getRaw(id);
return object == null ? this.optionalDefaultObject : object;
}
/**
* Get the object identified by the specified id.
*
* The default object is the air block for the block registry or null for the item registry.
*
* @param id Block/Item id.
* @return Block/Item object or the default object if it wasn't found.
*/
public I get(int id)
{
return func_148754_a(id);
}
/**
* Get the object identified by the specified name.
*
* The default object is the air block for the block registry or null for the item registry.
*
* @param name Block/Item name.
* @return Block/Item object or the default object if it wasn't found.
*/
public I get(String name)
{
return func_82594_a(name);
}
/**
* Get the id for the specified object.
*
* Don't hold onto the id across the world, it's being dynamically re-mapped as needed.
*
* Usually the name should be used instead of the id, if using the Block/Item object itself is
* not suitable for the task.
*
* @param think Block/Item object.
* @return Block/Item id or -1 if it wasn't found.
*/
public int getId(I thing)
{
return func_148757_b(thing);
}
/**
* Get the object identified by the specified id.
*
* @param id Block/Item id.
* @return Block/Item object or null if it wasn't found.
*/
public I getRaw(int id)
{
return superType.cast(super.func_148754_a(id));
}
/**
* Get the object identified by the specified name.
*
* @param name Block/Item name.
* @return Block/Item object or null if it wasn't found.
*/
public I getRaw(String name)
{
I ret = superType.cast(super.func_82594_a(name));
if (ret == null) // no match, try aliases recursively
{
name = aliases.get(name);
if (name != null) return getRaw(name);
}
return ret;
}
@Override
public boolean func_148741_d(String name)
{
boolean ret = super.func_148741_d(name);
if (!ret) // no match, try aliases recursively
{
name = aliases.get(name);
if (name != null) return func_148741_d(name);
}
return ret;
}
public int getId(String itemName)
{
I obj = getRaw(itemName);
if (obj == null) return -1;
return getId(obj);
}
public boolean contains(String itemName)
{
return func_148741_d(itemName);
}
// internal
public void serializeInto(Map<String, Integer> idMapping)
{
for (Iterator<Object> it = iterator(); it.hasNext(); )
{
I thing = (I) it.next();
idMapping.put(discriminator+func_148750_c(thing), getId(thing));
}
}
public Map<String, String> getAliases()
{
return ImmutableMap.copyOf(aliases);
}
int add(int id, String name, I thing, BitSet availabilityMap)
{
if (name.equals(optionalDefaultName))
@ -98,30 +234,9 @@ public class FMLControlledNamespacedRegistry<I> extends RegistryNamespaced {
return idToUse;
}
@Override
public I func_82594_a(String name)
void addAlias(String from, String to)
{
I object = superType.cast(super.func_82594_a(name));
return object == null ? this.optionalDefaultObject : object;
}
@Override
public I func_148754_a(int id)
{
I object = superType.cast(super.func_148754_a(id));
return object == null ? this.optionalDefaultObject : object;
}
private ObjectIntIdentityMap idMap()
{
return field_148759_a;
}
@SuppressWarnings("unchecked")
private BiMap<String,I> nameMap()
{
return (BiMap<String,I>) field_82596_a;
aliases.put(from, to);
}
Map<String,Integer> getEntriesNotIn(FMLControlledNamespacedRegistry<I> registry)
@ -137,53 +252,6 @@ public class FMLControlledNamespacedRegistry<I> extends RegistryNamespaced {
return ret;
}
public I get(int id)
{
return func_148754_a(id);
}
public I get(String name)
{
return func_82594_a(name);
}
public int getId(I thing)
{
return func_148757_b(thing);
}
public I getRaw(int id)
{
return superType.cast(super.func_148754_a(id));
}
public I getRaw(String name)
{
return superType.cast(super.func_82594_a(name));
}
public void serializeInto(Map<String, Integer> idMapping)
{
for (Iterator<Object> it = iterator(); it.hasNext(); )
{
I thing = (I) it.next();
idMapping.put(discriminator+func_148750_c(thing), getId(thing));
}
}
public int getId(String itemName)
{
I obj = getRaw(itemName);
if (obj == null) return -1;
return getId(obj);
}
public boolean contains(String itemName)
{
return field_82596_a.containsKey(itemName);
}
void dump()
{
List<Integer> ids = new ArrayList<Integer>();

View file

@ -15,6 +15,7 @@ package cpw.mods.fml.common.registry;
import java.io.File;
import java.io.IOException;
import java.util.BitSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
@ -47,6 +48,7 @@ import cpw.mods.fml.common.Loader;
import cpw.mods.fml.common.ModContainer;
import cpw.mods.fml.common.event.FMLMissingMappingsEvent;
import cpw.mods.fml.common.event.FMLMissingMappingsEvent.MissingMapping;
import cpw.mods.fml.common.registry.GameRegistry.Type;
import cpw.mods.fml.common.registry.GameRegistry.UniqueIdentifier;
public class GameData {
@ -205,10 +207,10 @@ public class GameData {
public static List<String> injectWorldIDMap(Map<String, Integer> dataList, boolean injectFrozenData, boolean isLocalWorld)
{
return injectWorldIDMap(dataList, new int[0], injectFrozenData, isLocalWorld);
return injectWorldIDMap(dataList, new int[0], new HashMap<String, String>(), new HashMap<String, String>(), injectFrozenData, isLocalWorld);
}
public static List<String> injectWorldIDMap(Map<String, Integer> dataList, int[] blockedIds, boolean injectFrozenData, boolean isLocalWorld)
public static List<String> injectWorldIDMap(Map<String, Integer> dataList, int[] blockedIds, Map<String, String> blockAliases, Map<String, String> itemAliases, boolean injectFrozenData, boolean isLocalWorld)
{
FMLLog.info("Injecting existing block and item data into this %s instance", FMLCommonHandler.instance().getEffectiveSide().isServer() ? "server" : "client");
Map<String, Integer[]> remaps = Maps.newHashMap();
@ -224,6 +226,16 @@ public class GameData {
newData.block(id);
}
for (Map.Entry<String, String> entry : blockAliases.entrySet())
{
newData.iBlockRegistry.addAlias(entry.getKey(), entry.getValue());
}
for (Map.Entry<String, String> entry : itemAliases.entrySet())
{
newData.iItemRegistry.addAlias(entry.getKey(), entry.getValue());
}
// process blocks and items in the world, blocks in the first pass, items in the second
// blocks need to be added first for proper ItemBlock handling
for (int pass = 0; pass < 2; pass++)
@ -244,7 +256,7 @@ public class GameData {
if (currId == -1)
{
FMLLog.info("Found a missing id from the world %s", itemName);
missingMappings.put(itemName, newId);
missingMappings.put(entry.getKey(), newId);
continue; // no block/item -> nothing to add
}
else if (currId != newId)
@ -279,7 +291,7 @@ public class GameData {
}
}
List<String> missedMappings = Loader.instance().fireMissingMappingEvent(missingMappings, isLocalWorld, newData);
List<String> missedMappings = Loader.instance().fireMissingMappingEvent(missingMappings, isLocalWorld, newData, remaps);
if (!missedMappings.isEmpty()) return missedMappings;
if (injectFrozenData) // add blocks + items missing from the map
@ -327,34 +339,76 @@ public class GameData {
return ImmutableList.of();
}
public static List<String> processIdRematches(Iterable<MissingMapping> remaps, boolean isLocalWorld, GameData gameData)
public static List<String> processIdRematches(Iterable<MissingMapping> missedMappings, boolean isLocalWorld, GameData gameData, Map<String, Integer[]> remaps)
{
List<String> failed = Lists.newArrayList();
List<String> ignored = Lists.newArrayList();
List<String> warned = Lists.newArrayList();
for (MissingMapping remap : remaps)
for (MissingMapping remap : missedMappings)
{
FMLMissingMappingsEvent.Action action = remap.getAction();
if (action == FMLMissingMappingsEvent.Action.DEFAULT)
{
action = FMLCommonHandler.instance().getDefaultMissingAction();
}
if (action == FMLMissingMappingsEvent.Action.IGNORE)
if (action == FMLMissingMappingsEvent.Action.REMAP)
{
ignored.add(remap.name);
}
else if (action == FMLMissingMappingsEvent.Action.FAIL)
{
failed.add(remap.name);
}
else if (action == FMLMissingMappingsEvent.Action.WARN)
{
warned.add(remap.name);
}
// block/item re-mapped, finish the registration with the new name/object, but the old id
int currId, newId;
String newName;
gameData.block(remap.id); // prevent the id from being reused later
if (remap.type == Type.BLOCK)
{
currId = getMain().iBlockRegistry.getId((Block) remap.getTarget());
newName = getMain().iBlockRegistry.func_148750_c(remap.getTarget());
FMLLog.fine("The Block %s is being remapped to %s.", remap.name, newName);
newId = gameData.registerBlock((Block) remap.getTarget(), newName, null, remap.id);
gameData.iBlockRegistry.addAlias(remap.name, newName);
}
else
{
currId = getMain().iItemRegistry.getId((Item) remap.getTarget());
newName = getMain().iItemRegistry.func_148750_c(remap.getTarget());
FMLLog.fine("The Item %s is being remapped to %s.", remap.name, newName);
newId = gameData.registerItem((Item) remap.getTarget(), newName, null, remap.id);
gameData.iItemRegistry.addAlias(remap.name, newName);
}
if (newId != remap.id) throw new IllegalStateException();
if (currId != newId)
{
FMLLog.info("Found %s id mismatch %s : %d (was %d)", remap.type == Type.BLOCK ? "block" : "item", newName, currId, newId);
remaps.put(newName, new Integer[] { currId, newId });
}
}
else
{
// block item missing, warn as requested and block the id
if (action == FMLMissingMappingsEvent.Action.DEFAULT)
{
action = FMLCommonHandler.instance().getDefaultMissingAction();
}
if (action == FMLMissingMappingsEvent.Action.IGNORE)
{
ignored.add(remap.name);
}
else if (action == FMLMissingMappingsEvent.Action.FAIL)
{
failed.add(remap.name);
}
else if (action == FMLMissingMappingsEvent.Action.WARN)
{
warned.add(remap.name);
}
else
{
throw new RuntimeException(String.format("Invalid default missing id action specified: %s", action.name()));
}
gameData.block(remap.id); // prevent the id from being reused later
}
}
if (!failed.isEmpty())
{