    001/*
    002 * Forge Mod Loader
    003 * Copyright (c) 2012-2013 cpw.
    004 * All rights reserved. This program and the accompanying materials
    005 * are made available under the terms of the GNU Lesser Public License v2.1
    006 * which accompanies this distribution, and is available at
    007 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
    008 *
    009 * Contributors:
    010 *     cpw - implementation
    011 */
    012
    013package cpw.mods.fml.common.registry;
    014
    015import java.io.File;
    016import java.io.FileInputStream;
    017import java.io.FileNotFoundException;
    018import java.io.IOException;
    019import java.util.Map;
    020import java.util.Properties;
    021import java.util.Set;
    022import java.util.concurrent.CountDownLatch;
    023import java.util.logging.Level;
    024
    025import net.minecraft.block.Block;
    026import net.minecraft.block.BlockSand;
    027import net.minecraft.item.Item;
    028import net.minecraft.nbt.NBTTagCompound;
    029import net.minecraft.nbt.NBTTagList;
    030
    031import com.google.common.base.Function;
    032import com.google.common.base.Throwables;
    033import com.google.common.collect.ImmutableMap;
    034import com.google.common.collect.ImmutableTable;
    035import com.google.common.collect.ImmutableTable.Builder;
    036import com.google.common.collect.MapDifference;
    037import com.google.common.collect.Tables;
    038import com.google.common.collect.MapDifference.ValueDifference;
    039import com.google.common.collect.Maps;
    040import com.google.common.collect.Sets;
    041import com.google.common.collect.Table;
    042import com.google.common.collect.Table.Cell;
    043
    044import cpw.mods.fml.common.FMLLog;
    045import cpw.mods.fml.common.Loader;
    046import cpw.mods.fml.common.LoaderState;
    047import cpw.mods.fml.common.ModContainer;
    048
    049public class GameData {
    050    private static Map<Integer, ItemData> idMap = Maps.newHashMap();
    051    private static CountDownLatch serverValidationLatch;
    052    private static CountDownLatch clientValidationLatch;
    053    private static MapDifference<Integer, ItemData> difference;
    054    private static boolean shouldContinue = true;
    055    private static boolean isSaveValid = true;
    056    private static ImmutableTable<String, String, Integer> modObjectTable;
    057    private static Map<String,String> ignoredMods;
    058
    059    private static boolean isModIgnoredForIdValidation(String modId)
    060    {
    061        if (ignoredMods == null)
    062        {
    063            File f = new File(Loader.instance().getConfigDir(),"fmlIDChecking.properties");
    064            if (f.exists())
    065            {
    066                Properties p = new Properties();
    067                try
    068                {
    069                    p.load(new FileInputStream(f));
    070                    ignoredMods = Maps.fromProperties(p);
    071                    if (ignoredMods.size()>0)
    072                    {
    073                        FMLLog.log("fml.ItemTracker", Level.WARNING, "Using non-empty ignored mods configuration file %s", ignoredMods.keySet());
    074                    }
    075                }
    076                catch (Exception e)
    077                {
    078                    Throwables.propagateIfPossible(e);
    079                    FMLLog.log("fml.ItemTracker", Level.SEVERE, e, "Failed to read ignored ID checker mods properties file");
    080                    ignoredMods = ImmutableMap.<String, String>of();
    081                }
    082            }
    083            else
    084            {
    085                ignoredMods = ImmutableMap.<String, String>of();
    086            }
    087        }
    088        return ignoredMods.containsKey(modId);
    089    }
    090
    091    public static void newItemAdded(Item item)
    092    {
    093        ModContainer mc = Loader.instance().activeModContainer();
    094        if (mc == null)
    095        {
    096            mc = Loader.instance().getMinecraftModContainer();
    097            if (Loader.instance().hasReachedState(LoaderState.AVAILABLE))
    098            {
    099                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.");
    100            }
    101        }
    102        String itemType = item.getClass().getName();
    103        ItemData itemData = new ItemData(item, mc);
    104        if (idMap.containsKey(item.itemID))
    105        {
    106            ItemData id = idMap.get(item.itemID);
    107            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);
    108        }
    109        idMap.put(item.itemID, itemData);
    110        if (!"Minecraft".equals(mc.getModId()))
    111        {
    112            FMLLog.log("fml.ItemTracker",Level.FINE, "Adding item %s(%d) owned by %s", item.getClass().getName(), item.itemID, mc.getModId());
    113        }
    114    }
    115
    116    public static void validateWorldSave(Set<ItemData> worldSaveItems)
    117    {
    118        isSaveValid = true;
    119        shouldContinue = true;
    120        // allow ourselves to continue if there's no saved data
    121        if (worldSaveItems == null)
    122        {
    123            serverValidationLatch.countDown();
    124            try
    125            {
    126                clientValidationLatch.await();
    127            }
    128            catch (InterruptedException e)
    129            {
    130            }
    131            return;
    132        }
    133
    134        Function<? super ItemData, Integer> idMapFunction = new Function<ItemData, Integer>() {
    135            public Integer apply(ItemData input) {
    136                return input.getItemId();
    137            };
    138        };
    139
    140        Map<Integer,ItemData> worldMap = Maps.uniqueIndex(worldSaveItems,idMapFunction);
    141        difference = Maps.difference(worldMap, idMap);
    142        FMLLog.log("fml.ItemTracker", Level.FINE, "The difference set is %s", difference);
    143        if (!difference.entriesDiffering().isEmpty() || !difference.entriesOnlyOnLeft().isEmpty())
    144        {
    145            FMLLog.log("fml.ItemTracker", Level.SEVERE, "FML has detected item discrepancies");
    146            FMLLog.log("fml.ItemTracker", Level.SEVERE, "Missing items : %s", difference.entriesOnlyOnLeft());
    147            FMLLog.log("fml.ItemTracker", Level.SEVERE, "Mismatched items : %s", difference.entriesDiffering());
    148            boolean foundNonIgnored = false;
    149            for (ItemData diff : difference.entriesOnlyOnLeft().values())
    150            {
    151                if (!isModIgnoredForIdValidation(diff.getModId()))
    152                {
    153                    foundNonIgnored = true;
    154                }
    155            }
    156            for (ValueDifference<ItemData> diff : difference.entriesDiffering().values())
    157            {
    158                if (! ( isModIgnoredForIdValidation(diff.leftValue().getModId()) || isModIgnoredForIdValidation(diff.rightValue().getModId()) ) )
    159                {
    160                    foundNonIgnored = true;
    161                }
    162            }
    163            if (!foundNonIgnored)
    164            {
    165                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());
    166            }
    167            isSaveValid = !foundNonIgnored;
    168            serverValidationLatch.countDown();
    169        }
    170        else
    171        {
    172            isSaveValid = true;
    173            serverValidationLatch.countDown();
    174        }
    175        try
    176        {
    177            clientValidationLatch.await();
    178            if (!shouldContinue)
    179            {
    180                throw new RuntimeException("This server instance is going to stop abnormally because of a fatal ID mismatch");
    181            }
    182        }
    183        catch (InterruptedException e)
    184        {
    185        }
    186    }
    187
    188    public static void writeItemData(NBTTagList itemList)
    189    {
    190        for (ItemData dat : idMap.values())
    191        {
    192            itemList.appendTag(dat.toNBT());
    193        }
    194    }
    195
    196    /**
    197     * Initialize the server gate
    198     * @param gateCount the countdown amount. If it's 2 we're on the client and the client and server
    199     * will wait at the latch. 1 is a server and the server will proceed
    200     */
    201    public static void initializeServerGate(int gateCount)
    202    {
    203        serverValidationLatch = new CountDownLatch(gateCount - 1);
    204        clientValidationLatch = new CountDownLatch(gateCount - 1);
    205    }
    206
    207    public static MapDifference<Integer, ItemData> gateWorldLoadingForValidation()
    208    {
    209        try
    210        {
    211            serverValidationLatch.await();
    212            if (!isSaveValid)
    213            {
    214                return difference;
    215            }
    216        }
    217        catch (InterruptedException e)
    218        {
    219        }
    220        difference = null;
    221        return null;
    222    }
    223
    224
    225    public static void releaseGate(boolean carryOn)
    226    {
    227        shouldContinue = carryOn;
    228        clientValidationLatch.countDown();
    229    }
    230
    231    public static Set<ItemData> buildWorldItemData(NBTTagList modList)
    232    {
    233        Set<ItemData> worldSaveItems = Sets.newHashSet();
    234        for (int i = 0; i < modList.tagCount(); i++)
    235        {
    236            NBTTagCompound mod = (NBTTagCompound) modList.tagAt(i);
    237            ItemData dat = new ItemData(mod);
    238            worldSaveItems.add(dat);
    239        }
    240        return worldSaveItems;
    241    }
    242
    243    static void setName(Item item, String name, String modId)
    244    {
    245        int id = item.itemID;
    246        ItemData itemData = idMap.get(id);
    247        itemData.setName(name,modId);
    248    }
    249
    250    public static void buildModObjectTable()
    251    {
    252        if (modObjectTable != null)
    253        {
    254            throw new IllegalStateException("Illegal call to buildModObjectTable!");
    255        }
    256
    257        Map<Integer, Cell<String, String, Integer>> map = Maps.transformValues(idMap, new Function<ItemData,Cell<String,String,Integer>>() {
    258            public Cell<String,String,Integer> apply(ItemData data)
    259            {
    260                if ("Minecraft".equals(data.getModId()) || !data.isOveridden())
    261                {
    262                    return null;
    263                }
    264                return Tables.immutableCell(data.getModId(), data.getItemType(), data.getItemId());
    265            }
    266        });
    267
    268        Builder<String, String, Integer> tBuilder = ImmutableTable.builder();
    269        for (Cell<String, String, Integer> c : map.values())
    270        {
    271            if (c!=null)
    272            {
    273                tBuilder.put(c);
    274            }
    275        }
    276        modObjectTable = tBuilder.build();
    277    }
    278    static Item findItem(String modId, String name)
    279    {
    280        if (modObjectTable == null)
    281        {
    282            return null;
    283        }
    284
    285        return Item.itemsList[modObjectTable.get(modId, name)];
    286    }
    287
    288    static Block findBlock(String modId, String name)
    289    {
    290        if (modObjectTable == null)
    291        {
    292            return null;
    293        }
    294
    295        Integer blockId = modObjectTable.get(modId, name);
    296        if (blockId >= Block.blocksList.length)
    297        {
    298            return null;
    299        }
    300        return Block.blocksList[blockId];
    301    }
    302}