    001package cpw.mods.fml.common.registry;
    002
    003import java.io.File;
    004import java.io.FileInputStream;
    005import java.io.FileNotFoundException;
    006import java.io.IOException;
    007import java.util.Map;
    008import java.util.Properties;
    009import java.util.Set;
    010import java.util.concurrent.CountDownLatch;
    011import java.util.logging.Level;
    012
    013import net.minecraft.item.Item;
    014import net.minecraft.nbt.NBTTagCompound;
    015import net.minecraft.nbt.NBTTagList;
    016
    017import com.google.common.base.Function;
    018import com.google.common.base.Throwables;
    019import com.google.common.collect.ImmutableMap;
    020import com.google.common.collect.MapDifference;
    021import com.google.common.collect.MapDifference.ValueDifference;
    022import com.google.common.collect.Maps;
    023import com.google.common.collect.Sets;
    024
    025import cpw.mods.fml.common.FMLLog;
    026import cpw.mods.fml.common.Loader;
    027import cpw.mods.fml.common.LoaderState;
    028import cpw.mods.fml.common.ModContainer;
    029
    030public class GameData {
    031    private static Map<Integer, ItemData> idMap = Maps.newHashMap();
    032    private static CountDownLatch serverValidationLatch;
    033    private static CountDownLatch clientValidationLatch;
    034    private static MapDifference<Integer, ItemData> difference;
    035    private static boolean shouldContinue = true;
    036    private static boolean isSaveValid = true;
    037    private static Map<String,String> ignoredMods;
    038
    039    private static boolean isModIgnoredForIdValidation(String modId)
    040    {
    041        if (ignoredMods == null)
    042        {
    043            File f = new File(Loader.instance().getConfigDir(),"fmlIDChecking.properties");
    044            if (f.exists())
    045            {
    046                Properties p = new Properties();
    047                try
    048                {
    049                    p.load(new FileInputStream(f));
    050                    ignoredMods = Maps.fromProperties(p);
    051                    if (ignoredMods.size()>0)
    052                    {
    053                        FMLLog.log("fml.ItemTracker", Level.WARNING, "Using non-empty ignored mods configuration file %s", ignoredMods.keySet());
    054                    }
    055                }
    056                catch (Exception e)
    057                {
    058                    Throwables.propagateIfPossible(e);
    059                    FMLLog.log("fml.ItemTracker", Level.SEVERE, e, "Failed to read ignored ID checker mods properties file");
    060                    ignoredMods = ImmutableMap.<String, String>of();
    061                }
    062            }
    063            else
    064            {
    065                ignoredMods = ImmutableMap.<String, String>of();
    066            }
    067        }
    068        return ignoredMods.containsKey(modId);
    069    }
    070
    071    public static void newItemAdded(Item item)
    072    {
    073        ModContainer mc = Loader.instance().activeModContainer();
    074        if (mc == null)
    075        {
    076            mc = Loader.instance().getMinecraftModContainer();
    077            if (Loader.instance().hasReachedState(LoaderState.AVAILABLE))
    078            {
    079                FMLLog.severe("It appears something has tried to allocate an Item outside of the initialization phase of Minecraft, this could be very bad for your network connectivity.");
    080            }
    081        }
    082        String itemType = item.getClass().getName();
    083        ItemData itemData = new ItemData(item, mc);
    084        if (idMap.containsKey(item.itemID))
    085        {
    086            ItemData id = idMap.get(item.itemID);
    087            FMLLog.log("fml.ItemTracker", Level.INFO, "The mod %s is overwriting existing item at %d (%s from %s) with %s", mc.getModId(), id.getItemId(), id.getItemType(), id.getModId(), itemType);
    088        }
    089        idMap.put(item.itemID, itemData);
    090        if (!"Minecraft".equals(mc.getModId()))
    091        {
    092            FMLLog.log("fml.ItemTracker",Level.FINE, "Adding item %s(%d) owned by %s", item.getClass().getName(), item.itemID, mc.getModId());
    093        }
    094    }
    095
    096    public static void validateWorldSave(Set<ItemData> worldSaveItems)
    097    {
    098        isSaveValid = true;
    099        shouldContinue = true;
    100        // allow ourselves to continue if there's no saved data
    101        if (worldSaveItems == null)
    102        {
    103            serverValidationLatch.countDown();
    104            try
    105            {
    106                clientValidationLatch.await();
    107            }
    108            catch (InterruptedException e)
    109            {
    110            }
    111            return;
    112        }
    113
    114        Function<? super ItemData, Integer> idMapFunction = new Function<ItemData, Integer>() {
    115            public Integer apply(ItemData input) {
    116                return input.getItemId();
    117            };
    118        };
    119
    120        Map<Integer,ItemData> worldMap = Maps.uniqueIndex(worldSaveItems,idMapFunction);
    121        difference = Maps.difference(worldMap, idMap);
    122        FMLLog.log("fml.ItemTracker", Level.FINE, "The difference set is %s", difference);
    123        if (!difference.entriesDiffering().isEmpty() || !difference.entriesOnlyOnLeft().isEmpty())
    124        {
    125            FMLLog.log("fml.ItemTracker", Level.SEVERE, "FML has detected item discrepancies");
    126            FMLLog.log("fml.ItemTracker", Level.SEVERE, "Missing items : %s", difference.entriesOnlyOnLeft());
    127            FMLLog.log("fml.ItemTracker", Level.SEVERE, "Mismatched items : %s", difference.entriesDiffering());
    128            boolean foundNonIgnored = false;
    129            for (ItemData diff : difference.entriesOnlyOnLeft().values())
    130            {
    131                if (!isModIgnoredForIdValidation(diff.getModId()))
    132                {
    133                    foundNonIgnored = true;
    134                }
    135            }
    136            for (ValueDifference<ItemData> diff : difference.entriesDiffering().values())
    137            {
    138                if (! ( isModIgnoredForIdValidation(diff.leftValue().getModId()) || isModIgnoredForIdValidation(diff.rightValue().getModId()) ) )
    139                {
    140                    foundNonIgnored = true;
    141                }
    142            }
    143            if (!foundNonIgnored)
    144            {
    145                FMLLog.log("fml.ItemTracker", Level.SEVERE, "FML is ignoring these ID discrepancies because of configuration. YOUR GAME WILL NOW PROBABLY CRASH. HOPEFULLY YOU WON'T HAVE CORRUPTED YOUR WORLD. BLAME %s", ignoredMods.keySet());
    146            }
    147            isSaveValid = !foundNonIgnored;
    148            serverValidationLatch.countDown();
    149        }
    150        else
    151        {
    152            isSaveValid = true;
    153            serverValidationLatch.countDown();
    154        }
    155        try
    156        {
    157            clientValidationLatch.await();
    158            if (!shouldContinue)
    159            {
    160                throw new RuntimeException("This server instance is going to stop abnormally because of a fatal ID mismatch");
    161            }
    162        }
    163        catch (InterruptedException e)
    164        {
    165        }
    166    }
    167
    168    public static void writeItemData(NBTTagList itemList)
    169    {
    170        for (ItemData dat : idMap.values())
    171        {
    172            itemList.appendTag(dat.toNBT());
    173        }
    174    }
    175
    176    /**
    177     * Initialize the server gate
    178     * @param gateCount the countdown amount. If it's 2 we're on the client and the client and server
    179     * will wait at the latch. 1 is a server and the server will proceed
    180     */
    181    public static void initializeServerGate(int gateCount)
    182    {
    183        serverValidationLatch = new CountDownLatch(gateCount - 1);
    184        clientValidationLatch = new CountDownLatch(gateCount - 1);
    185    }
    186
    187    public static MapDifference<Integer, ItemData> gateWorldLoadingForValidation()
    188    {
    189        try
    190        {
    191            serverValidationLatch.await();
    192            if (!isSaveValid)
    193            {
    194                return difference;
    195            }
    196        }
    197        catch (InterruptedException e)
    198        {
    199        }
    200        difference = null;
    201        return null;
    202    }
    203
    204
    205    public static void releaseGate(boolean carryOn)
    206    {
    207        shouldContinue = carryOn;
    208        clientValidationLatch.countDown();
    209    }
    210
    211    public static Set<ItemData> buildWorldItemData(NBTTagList modList)
    212    {
    213        Set<ItemData> worldSaveItems = Sets.newHashSet();
    214        for (int i = 0; i < modList.tagCount(); i++)
    215        {
    216            NBTTagCompound mod = (NBTTagCompound) modList.tagAt(i);
    217            ItemData dat = new ItemData(mod);
    218            worldSaveItems.add(dat);
    219        }
    220        return worldSaveItems;
    221    }
    222
    223    static void setName(Item item, String name, String modId)
    224    {
    225        int id = item.itemID;
    226        ItemData itemData = idMap.get(id);
    227        itemData.setName(name,modId);
    228    }
    229}