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}