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}