Registry: Block IDs after failing to find a mapping for them

This commit is contained in:
Player 2014-03-24 21:16:36 +01:00
parent ff6083e77b
commit e9ca678ab3
4 changed files with 166 additions and 42 deletions

View file

@ -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<String,Integer> 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<String> failedElements = GameData.injectWorldIDMap(dataList, true, true);
// read blocked ids
int[] blockedIds = tag.func_74759_k("BlockedIds");
List<String> 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);

View file

@ -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<String> fireMissingMappingEvent(ArrayListMultimap<String,String> 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<String> fireMissingMappingEvent(LinkedHashMap<String, Integer> 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<String,MissingMapping> missingMappings = ArrayListMultimap.create();
List<MissingMapping> remaps = Lists.newArrayList();
for (Map.Entry<String, String> mapping : missing.entries())
return ImmutableList.of();
}
FMLLog.fine("There are %d mappings missing - attempting a mod remap", missing.size());
ArrayListMultimap<String, MissingMapping> missingMappings = ArrayListMultimap.create();
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);
}
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<String, MissingMapping> 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<String> missedMapping = new ArrayList<String>();
for (MissingMapping mapping : missingMappings.values())
{
List<String> missedMapping = Lists.newArrayList();
for (java.util.Map.Entry<String, MissingMapping> 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<String, Integer[]> remaps)
{
if (remaps.isEmpty())

View file

@ -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<MissingMapping> remaps;
public MissingMapping(String name, List<MissingMapping> 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()

View file

@ -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<String> injectWorldIDMap(Map<String, Integer> dataList, boolean injectFrozenData, boolean isLocalWorld)
{
return injectWorldIDMap(dataList, new int[0], injectFrozenData, isLocalWorld);
}
public static List<String> injectWorldIDMap(Map<String, Integer> 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<String, Integer[]> remaps = Maps.newHashMap();
ArrayListMultimap<String,String> missingMappings = ArrayListMultimap.create();
LinkedHashMap<String, Integer> missingMappings = new LinkedHashMap<String, Integer>();
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<String> missedMappings = Loader.instance().fireMissingMappingEvent(missingMappings, isLocalWorld);
List<String> 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<String> processIdRematches(List<MissingMapping> remaps, boolean isLocalWorld)
public static List<String> processIdRematches(Iterable<MissingMapping> remaps, boolean isLocalWorld, GameData gameData)
{
List<String> failed = Lists.newArrayList();
List<String> 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<Item> 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<Integer> blockedIds;
private GameData()
{
iBlockRegistry = new FMLControlledNamespacedRegistry<Block>("air", 4095, 0, Block.class,'\u0001');
iItemRegistry = new FMLControlledNamespacedRegistry<Item>(null, 32000, 4096, Item.class,'\u0002');
availabilityMap = new BitSet(32000);
blockedIds = new HashSet<Integer>();
}
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<Object> 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<Object> 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)
{