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}