diff --git a/fml/src/main/java/cpw/mods/fml/common/FMLContainer.java b/fml/src/main/java/cpw/mods/fml/common/FMLContainer.java index da426d94ca..a5804c9499 100644 --- a/fml/src/main/java/cpw/mods/fml/common/FMLContainer.java +++ b/fml/src/main/java/cpw/mods/fml/common/FMLContainer.java @@ -15,19 +15,25 @@ package cpw.mods.fml.common; import java.io.File; import java.security.cert.Certificate; import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; + import net.minecraft.item.Item; import net.minecraft.nbt.NBTBase; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.world.storage.SaveHandler; import net.minecraft.world.storage.WorldInfo; + import org.apache.logging.log4j.Level; + import com.google.common.collect.Maps; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; + import cpw.mods.fml.client.FMLFileResourcePack; import cpw.mods.fml.client.FMLFolderResourcePack; import cpw.mods.fml.common.asm.FMLSanityChecker; @@ -106,6 +112,7 @@ 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()); return fmlData; } @@ -180,6 +187,7 @@ 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); Map dataList = Maps.newLinkedHashMap(); for (int i = 0; i < list.func_74745_c(); i++) @@ -187,7 +195,10 @@ public class FMLContainer extends DummyModContainer implements WorldAccessContai NBTTagCompound dataTag = list.func_150305_b(i); dataList.put(dataTag.func_74779_i("K"), dataTag.func_74762_e("V")); } - List failedElements = GameData.injectWorldIDMap(dataList, true, true); + // read blocked ids + int[] blockedIds = tag.func_74759_k("BlockedIds"); + + List failedElements = GameData.injectWorldIDMap(dataList, blockedIds, 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/fml/src/main/java/cpw/mods/fml/common/Loader.java b/fml/src/main/java/cpw/mods/fml/common/Loader.java index b45ecf075b..41f2eb2144 100644 --- a/fml/src/main/java/cpw/mods/fml/common/Loader.java +++ b/fml/src/main/java/cpw/mods/fml/common/Loader.java @@ -17,13 +17,17 @@ import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.net.MalformedURLException; +import java.util.ArrayList; import java.util.Comparator; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; + import org.apache.logging.log4j.Level; + import com.google.common.base.CharMatcher; import com.google.common.base.Function; import com.google.common.base.Joiner; @@ -45,6 +49,7 @@ import com.google.common.collect.Ordering; import com.google.common.collect.SetMultimap; import com.google.common.collect.Sets; import com.google.common.collect.TreeMultimap; + import cpw.mods.fml.common.LoaderState.ModState; import cpw.mods.fml.common.ModContainer.Disableable; import cpw.mods.fml.common.discovery.ModDiscoverer; @@ -56,6 +61,7 @@ import cpw.mods.fml.common.event.FMLModIdMappingEvent; import cpw.mods.fml.common.functions.ArtifactVersionNameFunction; import cpw.mods.fml.common.functions.ModIdFunction; import cpw.mods.fml.common.registry.GameData; +import cpw.mods.fml.common.registry.GameRegistry.Type; import cpw.mods.fml.common.toposort.ModSorter; import cpw.mods.fml.common.toposort.ModSortingException; import cpw.mods.fml.common.toposort.ModSortingException.SortingExceptionData; @@ -841,41 +847,75 @@ public class Loader return true; } - public List fireMissingMappingEvent(ArrayListMultimap missing, boolean isLocalWorld) + /** + * Fire a FMLMissingMappingsEvent to let mods determine how blocks/items defined in the world + * save, but missing from the runtime, are to be handled. + * + * @param missing Map containing missing names with their associated id, blocks need to come before items for remapping. + * @param isLocalWorld Whether this is executing for a world load (local/server) or a client. + * @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) { - if (!missing.isEmpty()) + if (missing.isEmpty()) // nothing to do { - FMLLog.fine("There are %d mappings missing - attempting a mod remap", missing.size()); - ArrayListMultimap missingMappings = ArrayListMultimap.create(); - List remaps = Lists.newArrayList(); - for (Map.Entry mapping : missing.entries()) + return ImmutableList.of(); + } + + FMLLog.fine("There are %d mappings missing - attempting a mod remap", missing.size()); + ArrayListMultimap missingMappings = ArrayListMultimap.create(); + + 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); + } + + FMLMissingMappingsEvent missingEvent = new FMLMissingMappingsEvent(missingMappings); + modController.propogateStateMessage(missingEvent); + + if (isLocalWorld) // local world, warn about entries still being set to the default action + { + boolean didWarn = false; + + for (MissingMapping mapping : missingMappings.values()) { - MissingMapping m = new MissingMapping(mapping.getValue(), remaps); - missingMappings.put(mapping.getKey(), m); - } - FMLMissingMappingsEvent missingEvent = new FMLMissingMappingsEvent(missingMappings); - modController.propogateStateMessage(missingEvent); - if (!missingMappings.isEmpty() && isLocalWorld) - { - FMLLog.severe("There are unidentified mappings in this world - we are going to attempt to process anyway"); - for (java.util.Map.Entry missed : missingMappings.entries()) + if (mapping.getAction() == FMLMissingMappingsEvent.Action.DEFAULT) { - remaps.add(missed.getValue()); + if (!didWarn) + { + FMLLog.severe("There are unidentified mappings in this world - we are going to attempt to process anyway"); + didWarn = true; + } + + FMLLog.severe("Unidentified %s: %s, id %d", mapping.type == Type.BLOCK ? "block" : "item", mapping.name, mapping.id); } } - else if (!missingMappings.isEmpty() && !isLocalWorld) + } + else // remote world, fail on entries with the default action + { + List missedMapping = new ArrayList(); + + for (MissingMapping mapping : missingMappings.values()) { - List missedMapping = Lists.newArrayList(); - for (java.util.Map.Entry missed : missingMappings.entries()) + if (mapping.getAction() == FMLMissingMappingsEvent.Action.DEFAULT) { - missedMapping.add(missed.getKey()+ ":" + missed.getValue().name); + missedMapping.add(mapping.name); } + } + + if (!missedMapping.isEmpty()) + { return ImmutableList.copyOf(missedMapping); } - return GameData.processIdRematches(remaps, isLocalWorld); } - return ImmutableList.of(); + + return GameData.processIdRematches(missingMappings.values(), isLocalWorld, gameData); } + public void fireRemapEvent(Map remaps) { if (remaps.isEmpty()) diff --git a/fml/src/main/java/cpw/mods/fml/common/event/FMLMissingMappingsEvent.java b/fml/src/main/java/cpw/mods/fml/common/event/FMLMissingMappingsEvent.java index 3045c38d15..767f9a0092 100644 --- a/fml/src/main/java/cpw/mods/fml/common/event/FMLMissingMappingsEvent.java +++ b/fml/src/main/java/cpw/mods/fml/common/event/FMLMissingMappingsEvent.java @@ -28,23 +28,43 @@ public class FMLMissingMappingsEvent extends FMLEvent { * @author cpw * */ - public static enum Action { IGNORE, WARN, FAIL } + public static enum Action { DEFAULT, IGNORE, WARN, FAIL } public static class MissingMapping { public final GameRegistry.Type type; public final String name; - private Action action; - private List remaps; - public MissingMapping(String name, List remaps) + public final int id; + private Action action = Action.DEFAULT; + + public MissingMapping(String name, int id) { this.type = name.charAt(0) == '\u0001' ? GameRegistry.Type.BLOCK : GameRegistry.Type.ITEM; this.name = name; - this.remaps = remaps; - this.action = FMLCommonHandler.instance().getDefaultMissingAction(); + this.id = id; } + /** + * @deprecated use ignore(), warn() or fail() instead + */ + @Deprecated public void setAction(Action target) { + if (target == Action.DEFAULT) throw new IllegalArgumentException(); + this.action = target; - remaps.add(this); + } + + public void ignore() + { + this.action = Action.IGNORE; + } + + public void warn() + { + this.action = Action.WARN; + } + + public void fail() + { + this.action = Action.FAIL; } public Action getAction() diff --git a/fml/src/main/java/cpw/mods/fml/common/registry/GameData.java b/fml/src/main/java/cpw/mods/fml/common/registry/GameData.java index e3d549f22d..f2a71cbd03 100644 --- a/fml/src/main/java/cpw/mods/fml/common/registry/GameData.java +++ b/fml/src/main/java/cpw/mods/fml/common/registry/GameData.java @@ -15,23 +15,24 @@ package cpw.mods.fml.common.registry; import java.io.File; import java.io.IOException; import java.util.BitSet; +import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; - -import org.apache.logging.log4j.Level; +import java.util.Set; import net.minecraft.block.Block; import net.minecraft.item.Item; import net.minecraft.item.ItemBlock; import net.minecraft.item.ItemStack; -import net.minecraft.util.RegistryNamespaced; + +import org.apache.logging.log4j.Level; import com.google.common.base.Charsets; import com.google.common.base.Joiner; import com.google.common.base.Joiner.MapJoiner; -import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.HashBasedTable; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableListMultimap; @@ -96,6 +97,20 @@ public class GameData { return idMapping; } + public static int[] getBlockedIds() + { + int[] ret = new int[getMain().blockedIds.size()]; + int index = 0; + + for (int id : getMain().blockedIds) + { + ret[index] = id; + index++; + } + + return ret; + } + public static void dumpRegistry(File minecraftDir) { if (customItemStacks == null) @@ -189,16 +204,26 @@ public class GameData { } public static List injectWorldIDMap(Map dataList, boolean injectFrozenData, boolean isLocalWorld) + { + return injectWorldIDMap(dataList, new int[0], injectFrozenData, isLocalWorld); + } + + public static List injectWorldIDMap(Map dataList, int[] blockedIds, 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(); - ArrayListMultimap missingMappings = ArrayListMultimap.create(); + LinkedHashMap missingMappings = new LinkedHashMap(); getMain().testConsistency(); getMain().iBlockRegistry.dump(); getMain().iItemRegistry.dump(); GameData newData = new GameData(); + for (int id : blockedIds) + { + newData.block(id); + } + // 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++) @@ -219,7 +244,7 @@ public class GameData { if (currId == -1) { FMLLog.info("Found a missing id from the world %s", itemName); - missingMappings.put(itemName.substring(0, itemName.indexOf(':')), itemName); + missingMappings.put(itemName, newId); continue; // no block/item -> nothing to add } else if (currId != newId) @@ -254,7 +279,7 @@ public class GameData { } } - List missedMappings = Loader.instance().fireMissingMappingEvent(missingMappings, isLocalWorld); + List missedMappings = Loader.instance().fireMissingMappingEvent(missingMappings, isLocalWorld, newData); if (!missedMappings.isEmpty()) return missedMappings; if (injectFrozenData) // add blocks + items missing from the map @@ -302,7 +327,7 @@ public class GameData { return ImmutableList.of(); } - public static List processIdRematches(List remaps, boolean isLocalWorld) + public static List processIdRematches(Iterable remaps, boolean isLocalWorld, GameData gameData) { List failed = Lists.newArrayList(); List ignored = Lists.newArrayList(); @@ -311,6 +336,11 @@ public class GameData { for (MissingMapping remap : remaps) { FMLMissingMappingsEvent.Action action = remap.getAction(); + if (action == FMLMissingMappingsEvent.Action.DEFAULT) + { + action = FMLCommonHandler.instance().getDefaultMissingAction(); + } + if (action == FMLMissingMappingsEvent.Action.IGNORE) { ignored.add(remap.name); @@ -319,10 +349,12 @@ public class GameData { { failed.add(remap.name); } - else + else if (action == FMLMissingMappingsEvent.Action.WARN) { warned.add(remap.name); } + + gameData.block(remap.id); // prevent the id from being reused later } if (!failed.isEmpty()) { @@ -373,12 +405,15 @@ public class GameData { private final FMLControlledNamespacedRegistry iItemRegistry; // bit set marking ids as occupied private final BitSet availabilityMap; + // IDs previously allocated in a world, but now unmapped/dangling; prevents the IDs from being reused + private final Set blockedIds; private GameData() { iBlockRegistry = new FMLControlledNamespacedRegistry("air", 4095, 0, Block.class,'\u0001'); iItemRegistry = new FMLControlledNamespacedRegistry(null, 32000, 4096, Item.class,'\u0002'); availabilityMap = new BitSet(32000); + blockedIds = new HashSet(); } private GameData(GameData data) @@ -393,6 +428,7 @@ public class GameData { iItemRegistry.set(data.iItemRegistry); availabilityMap.clear(); availabilityMap.or(data.availabilityMap); + blockedIds.addAll(data.blockedIds); } void register(Object obj, String name, int idHint) @@ -485,6 +521,15 @@ public class GameData { return blockId; } + /** + * Block the specified id from being reused. + */ + private void block(int id) + { + blockedIds.add(id); + useSlot(id); + } + private boolean useSlot(int id) { boolean oldValue = availabilityMap.get(id); @@ -503,13 +548,13 @@ public class GameData { // test if there's an entry for every set bit in availabilityMap for (int i = availabilityMap.nextSetBit(0); i >= 0; i = availabilityMap.nextSetBit(i+1)) { - if (iBlockRegistry.getRaw(i) == null && iItemRegistry.getRaw(i) == null) + if (iBlockRegistry.getRaw(i) == null && iItemRegistry.getRaw(i) == null && !blockedIds.contains(i)) { throw new IllegalStateException(String.format("availabilityMap references empty entries for id %d.", i)); } } - // test if there's a bit in availabilityMap set for every entry in the block registry + // test if there's a bit in availabilityMap set for every entry in the block registry, make sure it's not a blocked id for (Iterator it = iBlockRegistry.iterator(); it.hasNext(); ) { Block block = (Block) it.next(); @@ -519,9 +564,13 @@ public class GameData { { throw new IllegalStateException(String.format("Registry entry for block %s, id %d, marked as empty.", block, id)); } + if (blockedIds.contains(id)) + { + throw new IllegalStateException(String.format("Registry entry for block %s, id %d, marked as dangling.", block, id)); + } } - // test if there's a bit in availabilityMap set for every entry in the item registry, + // test if there's a bit in availabilityMap set for every entry in the item registry, make sure it's not a blocked id, // check if ItemBlocks have blocks with matching ids in the block registry for (Iterator it = iItemRegistry.iterator(); it.hasNext(); ) { @@ -532,6 +581,10 @@ public class GameData { { throw new IllegalStateException(String.format("Registry entry for item %s, id %d, marked as empty.", item, id)); } + if (blockedIds.contains(id)) + { + throw new IllegalStateException(String.format("Registry entry for item %s, id %d, marked as dangling.", item, id)); + } if (item instanceof ItemBlock) {