    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.item.ItemStack;
    029import net.minecraft.nbt.NBTTagCompound;
    030import net.minecraft.nbt.NBTTagList;
    031
    032import com.google.common.base.Charsets;
    033import com.google.common.base.Function;
    034import com.google.common.base.Joiner;
    035import com.google.common.base.Throwables;
    036import com.google.common.base.Joiner.MapJoiner;
    037import com.google.common.collect.HashBasedTable;
    038import com.google.common.collect.ImmutableListMultimap;
    039import com.google.common.collect.ImmutableMap;
    040import com.google.common.collect.ImmutableTable;
    041import com.google.common.collect.ImmutableTable.Builder;
    042import com.google.common.collect.MapDifference;
    043import com.google.common.collect.Tables;
    044import com.google.common.collect.MapDifference.ValueDifference;
    045import com.google.common.collect.Maps;
    046import com.google.common.collect.Sets;
    047import com.google.common.collect.Table;
    048import com.google.common.collect.Table.Cell;
    049import com.google.common.io.Files;
    050
    051import cpw.mods.fml.common.FMLLog;
    052import cpw.mods.fml.common.Loader;
    053import cpw.mods.fml.common.LoaderState;
    054import cpw.mods.fml.common.ModContainer;
    055
    056public class GameData {
    057    private static Map<Integer, ItemData> idMap = Maps.newHashMap();
    058    private static CountDownLatch serverValidationLatch;
    059    private static CountDownLatch clientValidationLatch;
    060    private static MapDifference<Integer, ItemData> difference;
    061    private static boolean shouldContinue = true;
    062    private static boolean isSaveValid = true;
    063    private static ImmutableTable<String, String, Integer> modObjectTable;
    064    private static Table<String, String, ItemStack> customItemStacks = HashBasedTable.create();
    065    private static Map<String,String> ignoredMods;
    066
    067    private static boolean isModIgnoredForIdValidation(String modId)
    068    {
    069        if (ignoredMods == null)
    070        {
    071            File f = new File(Loader.instance().getConfigDir(),"fmlIDChecking.properties");
    072            if (f.exists())
    073            {
    074                Properties p = new Properties();
    075                try
    076                {
    077                    p.load(new FileInputStream(f));
    078                    ignoredMods = Maps.fromProperties(p);
    079                    if (ignoredMods.size()>0)
    080                    {
    081                        FMLLog.log("fml.ItemTracker", Level.WARNING, "Using non-empty ignored mods configuration file %s", ignoredMods.keySet());
    082                    }
    083                }
    084                catch (Exception e)
    085                {
    086                    Throwables.propagateIfPossible(e);
    087                    FMLLog.log("fml.ItemTracker", Level.SEVERE, e, "Failed to read ignored ID checker mods properties file");
    088                    ignoredMods = ImmutableMap.<String, String>of();
    089                }
    090            }
    091            else
    092            {
    093                ignoredMods = ImmutableMap.<String, String>of();
    094            }
    095        }
    096        return ignoredMods.containsKey(modId);
    097    }
    098
    099    public static void newItemAdded(Item item)
    100    {
    101        ModContainer mc = Loader.instance().activeModContainer();
    102        if (mc == null)
    103        {
    104            mc = Loader.instance().getMinecraftModContainer();
    105            if (Loader.instance().hasReachedState(LoaderState.AVAILABLE))
    106            {
    107                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.");
    108            }
    109        }
    110        String itemType = item.getClass().getName();
    111        ItemData itemData = new ItemData(item, mc);
    112        if (idMap.containsKey(item.itemID))
    113        {
    114            ItemData id = idMap.get(item.itemID);
    115            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);
    116        }
    117        idMap.put(item.itemID, itemData);
    118        if (!"Minecraft".equals(mc.getModId()))
    119        {
    120            FMLLog.log("fml.ItemTracker",Level.FINE, "Adding item %s(%d) owned by %s", item.getClass().getName(), item.itemID, mc.getModId());
    121        }
    122    }
    123
    124    public static void validateWorldSave(Set<ItemData> worldSaveItems)
    125    {
    126        isSaveValid = true;
    127        shouldContinue = true;
    128        // allow ourselves to continue if there's no saved data
    129        if (worldSaveItems == null)
    130        {
    131            serverValidationLatch.countDown();
    132            try
    133            {
    134                clientValidationLatch.await();
    135            }
    136            catch (InterruptedException e)
    137            {
    138            }
    139            return;
    140        }
    141
    142        Function<? super ItemData, Integer> idMapFunction = new Function<ItemData, Integer>() {
    143            public Integer apply(ItemData input) {
    144                return input.getItemId();
    145            };
    146        };
    147
    148        Map<Integer,ItemData> worldMap = Maps.uniqueIndex(worldSaveItems,idMapFunction);
    149        difference = Maps.difference(worldMap, idMap);
    150        FMLLog.log("fml.ItemTracker", Level.FINE, "The difference set is %s", difference);
    151        if (!difference.entriesDiffering().isEmpty() || !difference.entriesOnlyOnLeft().isEmpty())
    152        {
    153            FMLLog.log("fml.ItemTracker", Level.SEVERE, "FML has detected item discrepancies");
    154            FMLLog.log("fml.ItemTracker", Level.SEVERE, "Missing items : %s", difference.entriesOnlyOnLeft());
    155            FMLLog.log("fml.ItemTracker", Level.SEVERE, "Mismatched items : %s", difference.entriesDiffering());
    156            boolean foundNonIgnored = false;
    157            for (ItemData diff : difference.entriesOnlyOnLeft().values())
    158            {
    159                if (!isModIgnoredForIdValidation(diff.getModId()))
    160                {
    161                    foundNonIgnored = true;
    162                }
    163            }
    164            for (ValueDifference<ItemData> diff : difference.entriesDiffering().values())
    165            {
    166                if (! ( isModIgnoredForIdValidation(diff.leftValue().getModId()) || isModIgnoredForIdValidation(diff.rightValue().getModId()) ) )
    167                {
    168                    foundNonIgnored = true;
    169                }
    170            }
    171            if (!foundNonIgnored)
    172            {
    173                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());
    174            }
    175            isSaveValid = !foundNonIgnored;
    176            serverValidationLatch.countDown();
    177        }
    178        else
    179        {
    180            isSaveValid = true;
    181            serverValidationLatch.countDown();
    182        }
    183        try
    184        {
    185            clientValidationLatch.await();
    186            if (!shouldContinue)
    187            {
    188                throw new RuntimeException("This server instance is going to stop abnormally because of a fatal ID mismatch");
    189            }
    190        }
    191        catch (InterruptedException e)
    192        {
    193        }
    194    }
    195
    196    public static void writeItemData(NBTTagList itemList)
    197    {
    198        for (ItemData dat : idMap.values())
    199        {
    200            itemList.appendTag(dat.toNBT());
    201        }
    202    }
    203
    204    /**
    205     * Initialize the server gate
    206     * @param gateCount the countdown amount. If it's 2 we're on the client and the client and server
    207     * will wait at the latch. 1 is a server and the server will proceed
    208     */
    209    public static void initializeServerGate(int gateCount)
    210    {
    211        serverValidationLatch = new CountDownLatch(gateCount - 1);
    212        clientValidationLatch = new CountDownLatch(gateCount - 1);
    213    }
    214
    215    public static MapDifference<Integer, ItemData> gateWorldLoadingForValidation()
    216    {
    217        try
    218        {
    219            serverValidationLatch.await();
    220            if (!isSaveValid)
    221            {
    222                return difference;
    223            }
    224        }
    225        catch (InterruptedException e)
    226        {
    227        }
    228        difference = null;
    229        return null;
    230    }
    231
    232
    233    public static void releaseGate(boolean carryOn)
    234    {
    235        shouldContinue = carryOn;
    236        clientValidationLatch.countDown();
    237    }
    238
    239    public static Set<ItemData> buildWorldItemData(NBTTagList modList)
    240    {
    241        Set<ItemData> worldSaveItems = Sets.newHashSet();
    242        for (int i = 0; i < modList.tagCount(); i++)
    243        {
    244            NBTTagCompound mod = (NBTTagCompound) modList.tagAt(i);
    245            ItemData dat = new ItemData(mod);
    246            worldSaveItems.add(dat);
    247        }
    248        return worldSaveItems;
    249    }
    250
    251    static void setName(Item item, String name, String modId)
    252    {
    253        int id = item.itemID;
    254        ItemData itemData = idMap.get(id);
    255        itemData.setName(name,modId);
    256    }
    257
    258    public static void buildModObjectTable()
    259    {
    260        if (modObjectTable != null)
    261        {
    262            throw new IllegalStateException("Illegal call to buildModObjectTable!");
    263        }
    264
    265        Map<Integer, Cell<String, String, Integer>> map = Maps.transformValues(idMap, new Function<ItemData,Cell<String,String,Integer>>() {
    266            public Cell<String,String,Integer> apply(ItemData data)
    267            {
    268                if ("Minecraft".equals(data.getModId()) || !data.isOveridden())
    269                {
    270                    return null;
    271                }
    272                return Tables.immutableCell(data.getModId(), data.getItemType(), data.getItemId());
    273            }
    274        });
    275
    276        Builder<String, String, Integer> tBuilder = ImmutableTable.builder();
    277        for (Cell<String, String, Integer> c : map.values())
    278        {
    279            if (c!=null)
    280            {
    281                tBuilder.put(c);
    282            }
    283        }
    284        modObjectTable = tBuilder.build();
    285    }
    286    static Item findItem(String modId, String name)
    287    {
    288        if (modObjectTable == null || !modObjectTable.contains(modId, name))
    289        {
    290            return null;
    291        }
    292
    293        return Item.itemsList[modObjectTable.get(modId, name)];
    294    }
    295
    296    static Block findBlock(String modId, String name)
    297    {
    298        if (modObjectTable == null)
    299        {
    300            return null;
    301        }
    302
    303        Integer blockId = modObjectTable.get(modId, name);
    304        if (blockId == null || blockId >= Block.blocksList.length)
    305        {
    306            return null;
    307        }
    308        return Block.blocksList[blockId];
    309    }
    310
    311    static ItemStack findItemStack(String modId, String name)
    312    {
    313        ItemStack is = customItemStacks.get(modId, name);
    314        if (is == null)
    315        {
    316            Item i = findItem(modId, name);
    317            if (i != null)
    318            {
    319                is = new ItemStack(i, 0 ,0);
    320            }
    321        }
    322        if (is == null)
    323        {
    324            Block b = findBlock(modId, name);
    325            if (b != null)
    326            {
    327                is = new ItemStack(b, 0, Short.MAX_VALUE);
    328            }
    329        }
    330        return is;
    331    }
    332
    333    static void registerCustomItemStack(String name, ItemStack itemStack)
    334    {
    335        customItemStacks.put(Loader.instance().activeModContainer().getModId(), name, itemStack);
    336    }
    337
    338    public static void dumpRegistry(File minecraftDir)
    339    {
    340        if (customItemStacks == null)
    341        {
    342            return;
    343        }
    344        if (Boolean.valueOf(System.getProperty("fml.dumpRegistry", "false")).booleanValue())
    345        {
    346            ImmutableListMultimap.Builder<String, String> builder = ImmutableListMultimap.builder();
    347            for (String modId : customItemStacks.rowKeySet())
    348            {
    349                builder.putAll(modId, customItemStacks.row(modId).keySet());
    350            }
    351
    352            File f = new File(minecraftDir, "itemStackRegistry.csv");
    353            MapJoiner mapJoiner = Joiner.on("\n").withKeyValueSeparator(",");
    354            try
    355            {
    356                Files.write(mapJoiner.join(builder.build().entries()), f, Charsets.UTF_8);
    357                FMLLog.log(Level.INFO, "Dumped item registry data to %s", f.getAbsolutePath());
    358            }
    359            catch (IOException e)
    360            {
    361                FMLLog.log(Level.SEVERE, e, "Failed to write registry data to %s", f.getAbsolutePath());
    362            }
    363        }
    364    }
    365}