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}