diff --git a/src/main/java/cpw/mods/fml/common/FMLContainer.java b/src/main/java/cpw/mods/fml/common/FMLContainer.java index a5804c9499..061c74bfa5 100644 --- a/src/main/java/cpw/mods/fml/common/FMLContainer.java +++ b/src/main/java/cpw/mods/fml/common/FMLContainer.java @@ -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 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 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 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 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 blockAliases = new HashMap(); + 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 itemAliases = new HashMap(); + 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 failedElements = GameData.injectWorldIDMap(dataList, blockedIds, true, true); + List 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); diff --git a/src/main/java/cpw/mods/fml/common/Loader.java b/src/main/java/cpw/mods/fml/common/Loader.java index 41f2eb2144..8320dbfdae 100644 --- a/src/main/java/cpw/mods/fml/common/Loader.java +++ b/src/main/java/cpw/mods/fml/common/Loader.java @@ -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 fireMissingMappingEvent(LinkedHashMap missing, boolean isLocalWorld, GameData gameData) + public List fireMissingMappingEvent(LinkedHashMap missing, boolean isLocalWorld, GameData gameData, Map remaps) { if (missing.isEmpty()) // nothing to do { @@ -868,10 +868,9 @@ public class Loader for (Map.Entry 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 remaps) diff --git a/src/main/java/cpw/mods/fml/common/event/FMLMissingMappingsEvent.java b/src/main/java/cpw/mods/fml/common/event/FMLMissingMappingsEvent.java index 21fe1cc20b..b99edd0cc9 100644 --- a/src/main/java/cpw/mods/fml/common/event/FMLMissingMappingsEvent.java +++ b/src/main/java/cpw/mods/fml/common/event/FMLMissingMappingsEvent.java @@ -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 missing; private ModContainer activeContainer; diff --git a/src/main/java/cpw/mods/fml/common/registry/FMLControlledNamespacedRegistry.java b/src/main/java/cpw/mods/fml/common/registry/FMLControlledNamespacedRegistry.java index 1ae9d56b7f..9cb236d6b9 100644 --- a/src/main/java/cpw/mods/fml/common/registry/FMLControlledNamespacedRegistry.java +++ b/src/main/java/cpw/mods/fml/common/registry/FMLControlledNamespacedRegistry.java @@ -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 extends RegistryNamespaced { private final Class 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 aliases = new HashMap(); FMLControlledNamespacedRegistry(String optionalDefault, int maxIdValue, int minIdValue, Class type, char discriminator) { @@ -47,6 +49,7 @@ public class FMLControlledNamespacedRegistry 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 extends RegistryNamespaced { } } + // public api + /** * Add an object to the registry, trying to use the specified id. * @@ -70,6 +75,137 @@ public class FMLControlledNamespacedRegistry 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 idMapping) + { + for (Iterator it = iterator(); it.hasNext(); ) + { + I thing = (I) it.next(); + idMapping.put(discriminator+func_148750_c(thing), getId(thing)); + } + } + + public Map 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 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 nameMap() - { - return (BiMap) field_82596_a; + aliases.put(from, to); } Map getEntriesNotIn(FMLControlledNamespacedRegistry registry) @@ -137,53 +252,6 @@ public class FMLControlledNamespacedRegistry 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 idMapping) - { - for (Iterator 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 ids = new ArrayList(); diff --git a/src/main/java/cpw/mods/fml/common/registry/GameData.java b/src/main/java/cpw/mods/fml/common/registry/GameData.java index f2a71cbd03..05c0bc1bc9 100644 --- a/src/main/java/cpw/mods/fml/common/registry/GameData.java +++ b/src/main/java/cpw/mods/fml/common/registry/GameData.java @@ -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 injectWorldIDMap(Map dataList, boolean injectFrozenData, boolean isLocalWorld) { - return injectWorldIDMap(dataList, new int[0], injectFrozenData, isLocalWorld); + return injectWorldIDMap(dataList, new int[0], new HashMap(), new HashMap(), injectFrozenData, isLocalWorld); } - public static List injectWorldIDMap(Map dataList, int[] blockedIds, boolean injectFrozenData, boolean isLocalWorld) + public static List injectWorldIDMap(Map dataList, int[] blockedIds, Map blockAliases, Map itemAliases, boolean injectFrozenData, boolean isLocalWorld) { FMLLog.info("Injecting existing block and item data into this %s instance", FMLCommonHandler.instance().getEffectiveSide().isServer() ? "server" : "client"); Map remaps = Maps.newHashMap(); @@ -224,6 +226,16 @@ public class GameData { newData.block(id); } + for (Map.Entry entry : blockAliases.entrySet()) + { + newData.iBlockRegistry.addAlias(entry.getKey(), entry.getValue()); + } + + for (Map.Entry 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 missedMappings = Loader.instance().fireMissingMappingEvent(missingMappings, isLocalWorld, newData); + List 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 processIdRematches(Iterable remaps, boolean isLocalWorld, GameData gameData) + public static List processIdRematches(Iterable missedMappings, boolean isLocalWorld, GameData gameData, Map remaps) { List failed = Lists.newArrayList(); List ignored = Lists.newArrayList(); List 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()) {