diff --git a/common/forge_at.cfg b/common/forge_at.cfg index cca5888e7..4e901758a 100644 --- a/common/forge_at.cfg +++ b/common/forge_at.cfg @@ -73,4 +73,8 @@ public aae.m # ComponentScatteredFeatureJunglePyramid.junglePyramidsDispenserCon public aan.a # ComponentStrongholdChestCorridor.strongholdChestContents public aar.b # ComponentStrongholdLibrary.strongholdLibraryChestContents public aaw.c # ComponentStrongholdRoomCrossing.field_75014_c -public abu.a # ComponentVillageHouse2.villageBlacksmithChestContents \ No newline at end of file +public abu.a # ComponentVillageHouse2.villageBlacksmithChestContents +# AnvilChunkLoader.chunkSaveLocation +default wy.d +# ChunkProviderServer.currentChunkLoader +default gq.e diff --git a/common/net/minecraftforge/common/Configuration.java b/common/net/minecraftforge/common/Configuration.java index 03799929e..7da8b7a4a 100644 --- a/common/net/minecraftforge/common/Configuration.java +++ b/common/net/minecraftforge/common/Configuration.java @@ -20,6 +20,9 @@ import java.util.Locale; import java.util.Map; import java.util.TreeMap; +import com.google.common.base.Splitter; +import com.google.common.collect.Maps; + import net.minecraft.src.Block; /** @@ -36,12 +39,15 @@ public class Configuration public static final String CATEGORY_ITEM = "item"; File file; - + public Map> categories = new TreeMap>(); - + public TreeMap blockProperties = new TreeMap(); public TreeMap itemProperties = new TreeMap(); public TreeMap generalProperties = new TreeMap(); + + private Map customCategoryComments = Maps.newHashMap(); + private boolean caseSensitiveCustomCategories; public static final String ALLOWED_CHARS = "._-"; /** @@ -55,6 +61,11 @@ public class Configuration categories.put(CATEGORY_ITEM, itemProperties); } + public Configuration(File file, boolean caseSensitiveCustomCategories) + { + this(file); + this.caseSensitiveCustomCategories = caseSensitiveCustomCategories; + } /** * Gets or create a block id property. If the block id property key is * already in the configuration, then it will be used. Otherwise, @@ -72,7 +83,7 @@ public class Configuration configBlocks[i] = false; } } - + Map properties = categories.get(CATEGORY_BLOCK); if (properties.containsKey(key)) { @@ -108,7 +119,7 @@ public class Configuration } } } - + public Property getOrCreateIntProperty(String key, String category, int defaultValue) { Property prop = getOrCreateProperty(key, category, Integer.toString(defaultValue)); @@ -123,7 +134,7 @@ public class Configuration return prop; } } - + public Property getOrCreateBooleanProperty(String key, String category, boolean defaultValue) { Property prop = getOrCreateProperty(key, category, Boolean.toString(defaultValue)); @@ -137,10 +148,13 @@ public class Configuration return prop; } } - + public Property getOrCreateProperty(String key, String category, String defaultValue) { - category = category.toLowerCase(Locale.ENGLISH); + if (!caseSensitiveCustomCategories) + { + category = category.toLowerCase(Locale.ENGLISH); + } Map source = categories.get(category); if(source == null) @@ -276,7 +290,7 @@ public class Configuration { if (buffer != null) { - try + try { buffer.close(); } catch (IOException e){} @@ -311,6 +325,17 @@ public class Configuration { buffer.write("####################\r\n"); buffer.write("# " + category.getKey() + " \r\n"); + if (customCategoryComments.containsKey(category.getKey())) + { + buffer.write("#===================\r\n"); + String comment = customCategoryComments.get(category.getKey()); + Splitter splitter = Splitter.onPattern("\r?\n"); + for (String commentLine : splitter.split(comment)) + { + buffer.write("# "); + buffer.write(commentLine+"\r\n"); + } + } buffer.write("####################\r\n\r\n"); buffer.write(category.getKey() + " {\r\n"); @@ -328,13 +353,24 @@ public class Configuration } } + public void addCustomCategoryComment(String category, String comment) + { + if (!caseSensitiveCustomCategories) + category = category.toLowerCase(Locale.ENGLISH); + customCategoryComments.put(category, comment); + } + private void writeProperties(BufferedWriter buffer, Collection props) throws IOException { for (Property property : props) { if (property.comment != null) { - buffer.write(" # " + property.comment + "\r\n"); + Splitter splitter = Splitter.onPattern("\r?\n"); + for (String commentLine : splitter.split(property.comment)) + { + buffer.write(" # " + commentLine + "\r\n"); + } } buffer.write(" " + property.getName() + "=" + property.value); diff --git a/common/net/minecraftforge/common/DimensionManager.java b/common/net/minecraftforge/common/DimensionManager.java index c458c2e7e..504139352 100644 --- a/common/net/minecraftforge/common/DimensionManager.java +++ b/common/net/minecraftforge/common/DimensionManager.java @@ -2,9 +2,15 @@ package net.minecraftforge.common; import java.util.ArrayList; import java.util.Hashtable; +import java.util.Map; import java.util.Map.Entry; import java.util.logging.Level; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableListMultimap; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Maps; + import cpw.mods.fml.common.FMLCommonHandler; import net.minecraft.server.MinecraftServer; @@ -17,6 +23,7 @@ public class DimensionManager private static Hashtable worlds = new Hashtable(); private static boolean hasInit = false; private static Hashtable dimensions = new Hashtable(); + private static Map> persistentChunkStore = Maps.newHashMap(); public static boolean registerProviderType(int id, Class provider, boolean keepLoaded) { @@ -133,12 +140,11 @@ public class DimensionManager { return null; } - } + } catch (Exception e) { FMLCommonHandler.instance().getFMLLogger().log(Level.SEVERE,String.format("An error occured trying to create an instance of WorldProvider %d (%s)", - dim, - providers.get(getProviderType(dim)).getSimpleName()),e); + dim, providers.get(getProviderType(dim)).getSimpleName()),e); throw new RuntimeException(e); } } diff --git a/common/net/minecraftforge/common/ForgeChunkManager.java b/common/net/minecraftforge/common/ForgeChunkManager.java new file mode 100644 index 000000000..518290661 --- /dev/null +++ b/common/net/minecraftforge/common/ForgeChunkManager.java @@ -0,0 +1,557 @@ +package net.minecraftforge.common; + +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ImmutableSetMultimap; +import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.ListMultimap; +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Multimap; +import com.google.common.collect.Multiset; +import com.google.common.collect.SetMultimap; +import com.google.common.collect.Sets; +import com.google.common.collect.TreeMultiset; + +import cpw.mods.fml.common.FMLLog; +import cpw.mods.fml.common.Loader; +import cpw.mods.fml.common.ModContainer; + +import net.minecraft.src.ChunkCoordIntPair; +import net.minecraft.src.CompressedStreamTools; +import net.minecraft.src.Entity; +import net.minecraft.src.MathHelper; +import net.minecraft.src.NBTBase; +import net.minecraft.src.NBTTagCompound; +import net.minecraft.src.NBTTagList; +import net.minecraft.src.World; +import net.minecraft.src.WorldServer; +import net.minecraftforge.common.ForgeChunkManager.Ticket; + +/** + * Manages chunkloading for mods. + * + * The basic principle is a ticket based system. + * 1. Mods register a callback {@link #setForcedChunkLoadingCallback(Object, LoadingCallback)} + * 2. Mods ask for a ticket {@link #requestTicket(Object, World, Type)} and then hold on to that ticket. + * 3. Mods request chunks to stay loaded {@link #forceChunk(Ticket, ChunkCoordIntPair)} or remove chunks from force loading {@link #unforceChunk(Ticket, ChunkCoordIntPair)}. + * 4. When a world unloads, the tickets associated with that world are saved by the chunk manager. + * 5. When a world loads, saved tickets are offered to the mods associated with the tickets. The {@link Ticket#getModData()} that is set by the mod should be used to re-register + * chunks to stay loaded (and maybe take other actions). + * + * The chunkloading is configurable at runtime. The file "config/forgeChunkLoading.cfg" contains both default configuration for chunkloading, and a sample individual mod + * specific override section. + * + * @author cpw + * + */ +public class ForgeChunkManager +{ + private static int defaultMaxCount; + private static int defaultMaxChunks; + private static boolean overridesEnabled; + + private static Map> tickets = Maps.newHashMap(); + private static Map ticketConstraints = Maps.newHashMap(); + private static Map chunkConstraints = Maps.newHashMap(); + + private static Map callbacks = Maps.newHashMap(); + + private static Map> forcedChunks = Maps.newHashMap(); + + /** + * All mods requiring chunkloading need to implement this to handle the + * re-registration of chunk tickets at world loading time + * + * @author cpw + * + */ + public interface LoadingCallback + { + /** + * Called back when tickets are loaded from the world to allow the + * mod to re-register the chunks associated with those tickets + * + * @param tickets + * @param world + */ + public void ticketsLoaded(List tickets, World world); + } + public enum Type + { + + /** + * For non-entity registrations + */ + NORMAL, + /** + * For entity registrations + */ + ENTITY + } + public static class Ticket + { + private String modId; + private Type ticketType; + private LinkedHashSet requestedChunks; + private NBTTagCompound modData; + private World world; + private int maxDepth; + private String entityClazz; + private int entityX; + private int entityY; + private int entityZ; + private Entity entity; + + Ticket(String modId, Type type, World world) + { + this.modId = modId; + this.ticketType = type; + this.world = world; + this.maxDepth = getMaxChunkDepthFor(modId); + this.requestedChunks = Sets.newLinkedHashSet(); + } + + void bindEntityData(int x, int y, int z, String clazz) + { + this.entityX = x; + this.entityY = y; + this.entityZ = z; + this.entityClazz = clazz; + } + + /** + * The chunk list depth can be manipulated up to the maximal grant allowed for the mod. This value is configurable. Once the maximum is reached, + * the least recently forced chunk, by original registration time, is removed from the forced chunk list. + * + * @param depth The new depth to set + */ + public void setChunkListDepth(int depth) + { + if (depth > getMaxChunkDepthFor(modId)) + { + FMLLog.warning("The mod %s tried to modify the chunk ticket depth to: %d, greater than it's maximum: %d", modId, depth, getMaxChunkDepthFor(modId)); + } + else + { + this.maxDepth = depth; + } + } + /** + * Get the maximum chunk depth size + * + * @return The maximum chunk depth size + */ + public int getMaxChunkListDepth() + { + return getMaxChunkDepthFor(modId); + } + + /** + * Bind the entity to the ticket for {@link Type#ENTITY} type tickets. Other types will throw a runtime exception. + * + * @param entity The entity to bind + */ + public void bindEntity(Entity entity) + { + if (ticketType!=Type.ENTITY) + { + throw new RuntimeException("Cannot bind an entity to a non-entity ticket"); + } + this.entity = entity; + } + + /** + * Retrieve the {@link NBTTagCompound} that stores mod specific data for the chunk ticket. + * Example data to store would be a TileEntity or Block location. This is persisted with the ticket and + * provided to the {@link LoadingCallback} for the mod. It is recommended to use this to recover + * useful state information for the forced chunks. + * + * @return The custom compound tag for mods to store additional chunkloading data + */ + public NBTTagCompound getModData() + { + if (this.modData == null) + { + this.modData = new NBTTagCompound(); + } + return modData; + } + + /** + * Get the entity class associated with this ticket. Only valid for callbacks. + * @return + */ + public String getEntityClass() + { + return this.entityClazz; + } + + /** + * Get the last known entity X coordinate for this ticket. Only valid for callbacks. + * @return + */ + public int getEntityX() + { + return entityX; + } + /** + * Get the last known entity Y coordinate for this ticket. Only valid for callbacks. + * @return + */ + public int getEntityY() + { + return entityY; + } + /** + * Get the last known entity Z coordinate for this ticket. Only valid for callbacks. + * @return + */ + public int getEntityZ() + { + return entityZ; + } + } + + static void loadWorld(World world) + { + ArrayListMultimap loadedTickets = ArrayListMultimap.create(); + tickets.put(world, loadedTickets); + + SetMultimap forcedChunkMap = LinkedHashMultimap.create(); + forcedChunks.put(world, forcedChunkMap); + + if (!(world instanceof WorldServer)) + { + return; + } + + WorldServer worldServer = (WorldServer) world; + File chunkDir = worldServer.getChunkSaveLocation(); + File chunkLoaderData = new File(chunkDir, "forcedchunks.dat"); + + if (chunkLoaderData.exists() && chunkLoaderData.isFile()) + { + NBTTagCompound forcedChunkData; + try + { + forcedChunkData = CompressedStreamTools.read(chunkLoaderData); + } + catch (IOException e) + { + FMLLog.log(Level.WARNING, e, "Unable to read forced chunk data at %s - it will be ignored", chunkLoaderData.getAbsolutePath()); + return; + } + NBTTagList ticketList = forcedChunkData.getTagList("TicketList"); + for (int i = 0; i < ticketList.tagCount(); i++) + { + NBTTagCompound ticketHolder = (NBTTagCompound) ticketList.tagAt(i); + String modId = ticketHolder.getString("Owner"); + + if (!Loader.isModLoaded(modId)) + { + FMLLog.warning("Found chunkloading data for mod %s which is currently not available or active - it will be removed from the world save", modId); + continue; + } + + if (!callbacks.containsKey(modId)) + { + FMLLog.warning("The mod %s has registered persistent chunkloading data but doesn't seem to want to be called back with it - it will be removed from the world save", modId); + continue; + } + + int maxTicketLength = getMaxTicketLengthFor(modId); + + NBTTagList tickets = ticketHolder.getTagList("Tickets"); + if (tickets.tagCount() > maxTicketLength) + { + FMLLog.warning("The mod %s has more tickets in to load than it is allowed. Only the first %d will be loaded - the rest will be removed", modId, maxTicketLength); + } + for (int j = 0; j < Math.min(tickets.tagCount(), maxTicketLength); j++) + { + NBTTagCompound ticket = (NBTTagCompound) tickets.tagAt(j); + Type type = Type.values()[ticket.getByte("Type")]; + byte ticketChunkDepth = ticket.getByte("ChunkListDepth"); + NBTTagCompound modData = ticket.getCompoundTag("ModData"); + Ticket tick = new Ticket(modId, type, world); + tick.modData = modData; + if (type == Type.ENTITY) + { + int entX = ticket.getInteger("entityX"); + int entY = ticket.getInteger("entityY"); + int entZ = ticket.getInteger("entityZ"); + String entClass = ticket.getString("entityClass"); + tick.bindEntityData(entX, entY, entZ, entClass); + } + loadedTickets.put(modId, tick); + } + } + + // send callbacks + for (String modId : loadedTickets.keySet()) + { + callbacks.get(modId).ticketsLoaded(loadedTickets.get(modId), world); + } + } + } + + /** + * Set a chunkloading callback for the supplied mod object + * + * @param mod The mod instance registering the callback + * @param callback The code to call back when forced chunks are loaded + */ + public static void setForcedChunkLoadingCallback(Object mod, LoadingCallback callback) + { + ModContainer container = getContainer(mod); + if (container == null) + { + FMLLog.warning("Unable to register a callback for an unknown mod %s (%s : %x)", mod, mod.getClass().getName(), System.identityHashCode(mod)); + return; + } + + callbacks.put(container.getModId(), callback); + } + + /** + * Discover the available tickets for the mod in the world + * + * @param mod The mod that will own the tickets + * @param world The world + * @return The count of tickets left for the mod in the supplied world + */ + public static int ticketCountAvailableFor(Object mod, World world) + { + ModContainer container = getContainer(mod); + if (container!=null) + { + String modId = container.getModId(); + int allowedCount = getMaxTicketLengthFor(modId); + return allowedCount - tickets.get(world).get(modId).size(); + } + else + { + return 0; + } + } + + private static ModContainer getContainer(Object mod) + { + ModContainer container = Loader.instance().getModObjectList().inverse().get(mod); + return container; + } + + private static int getMaxTicketLengthFor(String modId) + { + int allowedCount = ticketConstraints.containsKey(modId) && overridesEnabled ? ticketConstraints.get(modId) : defaultMaxCount; + return allowedCount; + } + + private static int getMaxChunkDepthFor(String modId) + { + int allowedCount = chunkConstraints.containsKey(modId) && overridesEnabled ? chunkConstraints.get(modId) : defaultMaxChunks; + return allowedCount; + } + /** + * Request a chunkloading ticket of the appropriate type for the supplied mod + * + * @param mod The mod requesting a ticket + * @param world The world in which it is requesting the ticket + * @param type The type of ticket + * @return A ticket with which to register chunks for loading, or null if no further tickets are available + */ + public static Ticket requestTicket(Object mod, World world, Type type) + { + ModContainer container = getContainer(mod); + if (container == null) + { + FMLLog.log(Level.SEVERE, "Failed to locate the container for mod instance %s (%s : %x)", mod, mod.getClass().getName(), System.identityHashCode(mod)); + return null; + } + String modId = container.getModId(); + if (!callbacks.containsKey(modId)) + { + FMLLog.severe("The mod %s has attempted to request a ticket without a listener in place", modId); + throw new RuntimeException("Invalid ticket request"); + } + + int allowedCount = ticketConstraints.containsKey(modId) ? ticketConstraints.get(modId) : defaultMaxCount; + + if (tickets.get(world).get(modId).size() >= allowedCount) + { + FMLLog.info("The mod %s has attempted to allocate a chunkloading ticket beyond it's currently allocated maximum : %d", modId, allowedCount); + return null; + } + Ticket ticket = new Ticket(modId, type, world); + tickets.get(world).put(modId, ticket); + + return ticket; + } + + /** + * Release the ticket back to the system. This will also unforce any chunks held by the ticket so that they can be unloaded and/or stop ticking. + * + * @param ticket The ticket to release + */ + public static void releaseTicket(Ticket ticket) + { + if (ticket == null) + { + return; + } + for (ChunkCoordIntPair chunk : ticket.requestedChunks) + { + unforceChunk(ticket, chunk); + } + tickets.get(ticket.world).remove(ticket.modId, ticket); + } + + /** + * Force the supplied chunk coordinate to be loaded by the supplied ticket. If the ticket's {@link Ticket#maxDepth} is exceeded, the least + * recently registered chunk is unforced and may be unloaded. + * It is safe to force the chunk several times for a ticket, it will not generate duplication or change the ordering. + * + * @param ticket The ticket registering the chunk + * @param chunk The chunk to force + */ + public static void forceChunk(Ticket ticket, ChunkCoordIntPair chunk) + { + if (ticket == null || chunk == null) + { + return; + } + if (ticket.ticketType == Type.ENTITY && ticket.entity == null) + { + throw new RuntimeException("Attempted to use an entity ticket to force a chunk, without an entity"); + } + ticket.requestedChunks.add(chunk); + forcedChunks.get(ticket.world).put(chunk, ticket); + if (ticket.maxDepth > 0 && ticket.requestedChunks.size() > ticket.maxDepth) + { + ChunkCoordIntPair removed = ticket.requestedChunks.iterator().next(); + unforceChunk(ticket,removed); + } + } + + /** + * Unforce the supplied chunk, allowing it to be unloaded and stop ticking. + * + * @param ticket The ticket holding the chunk + * @param chunk The chunk to unforce + */ + public static void unforceChunk(Ticket ticket, ChunkCoordIntPair chunk) + { + if (ticket == null || chunk == null) + { + return; + } + ticket.requestedChunks.remove(chunk); + forcedChunks.get(ticket.world).remove(chunk, ticket); + } + + static void loadConfiguration(File configDir) + { + Configuration config = new Configuration(new File(configDir,"forgeChunkLoading.cfg"), true); + try + { + config.categories.clear(); + config.load(); + config.addCustomCategoryComment("defaults", "Default configuration for forge chunk loading control"); + Property maxTicketCount = config.getOrCreateIntProperty("maximumTicketCount", "defaults", 200); + maxTicketCount.comment = "The default maximum ticket count for a mod which does not have an override\n" + + "in this file. This is the number of chunk loading requests a mod is allowed to make."; + defaultMaxCount = maxTicketCount.getInt(200); + + Property maxChunks = config.getOrCreateIntProperty("maximumChunksPerTicket", "defaults", 25); + maxChunks.comment = "The default maximum number of chunks a mod can force, per ticket, \n" + + "for a mod without an override. This is the maximum number of chunks a single ticket can force."; + defaultMaxChunks = maxChunks.getInt(25); + + Property modOverridesEnabled = config.getOrCreateBooleanProperty("enabled", "defaults", true); + modOverridesEnabled.comment = "Are mod overrides enabled?"; + overridesEnabled = modOverridesEnabled.getBoolean(true); + + config.addCustomCategoryComment("Forge", "Sample mod specific control section.\n" + + "Copy this section and rename the with the modid for the mod you wish to override.\n" + + "A value of zero in either entry effectively disables any chunkloading capabilities\n" + + "for that mod"); + Property sampleTC = config.getOrCreateIntProperty("maximumTicketCount", "Forge", 200); + sampleTC.comment = "Maximum ticket count for the mod. Zero disables chunkloading capabilities."; + sampleTC = config.getOrCreateIntProperty("maximumChunksPerTicket", "Forge", 25); + sampleTC.comment = "Maximum chunks per ticket for the mod."; + } + finally + { + config.save(); + } + } + + /** + * The list of persistent chunks in the world. This set is immutable. + * @param world + * @return + */ + public static SetMultimap getPersistentChunksFor(World world) + { + return ImmutableSetMultimap.copyOf(forcedChunks.get(world)); + } + + static void saveWorld(World world) + { + // only persist persistent worlds + if (!(world instanceof WorldServer)) { return; } + WorldServer worldServer = (WorldServer) world; + File chunkDir = worldServer.getChunkSaveLocation(); + File chunkLoaderData = new File(chunkDir, "forcedchunks.dat"); + + NBTTagCompound forcedChunkData = new NBTTagCompound(); + NBTTagList ticketList = new NBTTagList(); + forcedChunkData.setTag("TicketList", ticketList); + + Multimap ticketSet = tickets.get(worldServer); + for (String modId : ticketSet.keySet()) + { + NBTTagCompound ticketHolder = new NBTTagCompound(); + ticketList.appendTag(ticketHolder); + + ticketHolder.setString("Owner", modId); + NBTTagList tickets = new NBTTagList(); + ticketHolder.setTag("Tickets", tickets); + + for (Ticket tick : ticketSet.get(modId)) + { + NBTTagCompound ticket = new NBTTagCompound(); + tickets.appendTag(ticket); + ticket.setByte("Type", (byte) tick.ticketType.ordinal()); + ticket.setByte("ChunkListDepth", (byte) tick.maxDepth); + ticket.setCompoundTag("ModData", tick.modData); + if (tick.ticketType == Type.ENTITY) + { + ticket.setInteger("entityX", MathHelper.floor_double(tick.entity.posX)); + ticket.setInteger("entityY", MathHelper.floor_double(tick.entity.posY)); + ticket.setInteger("entityZ", MathHelper.floor_double(tick.entity.posZ)); + ticket.setString("entityClass", tick.entity.getClass().getName()); + } + } + } + try + { + CompressedStreamTools.write(forcedChunkData, chunkLoaderData); + } + catch (IOException e) + { + FMLLog.log(Level.WARNING, e, "Unable to write forced chunk data to %s - chunkloading won't work", chunkLoaderData.getAbsolutePath()); + return; + } + } +} diff --git a/common/net/minecraftforge/common/ForgeDummyContainer.java b/common/net/minecraftforge/common/ForgeDummyContainer.java index 22be4b0d9..12e8746b9 100644 --- a/common/net/minecraftforge/common/ForgeDummyContainer.java +++ b/common/net/minecraftforge/common/ForgeDummyContainer.java @@ -3,11 +3,13 @@ package net.minecraftforge.common; import java.util.Arrays; import com.google.common.eventbus.EventBus; +import com.google.common.eventbus.Subscribe; import cpw.mods.fml.common.DummyModContainer; import cpw.mods.fml.common.LoadController; import cpw.mods.fml.common.Loader; import cpw.mods.fml.common.ModMetadata; +import cpw.mods.fml.common.event.FMLPreInitializationEvent; import static net.minecraftforge.common.ForgeVersion.*; @@ -34,6 +36,13 @@ public class ForgeDummyContainer extends DummyModContainer @Override public boolean registerBus(EventBus bus, LoadController controller) { + bus.register(this); return true; } + + @Subscribe + public void preInit(FMLPreInitializationEvent evt) + { + ForgeChunkManager.loadConfiguration(evt.getModConfigurationDirectory()); + } } diff --git a/common/net/minecraftforge/common/ForgeHooks.java b/common/net/minecraftforge/common/ForgeHooks.java index 49ba9c76e..a929b0383 100644 --- a/common/net/minecraftforge/common/ForgeHooks.java +++ b/common/net/minecraftforge/common/ForgeHooks.java @@ -359,55 +359,4 @@ public class ForgeHooks player.joinEntityItemWithWorld(event.entityItem); return event.entityItem; } - public static void dumpPersistentChunks(WorldInfo worldInfo, ListMultimap persistentChunks) - { - worldInfo.forgeWorldData = new NBTTagCompound(); - - NBTTagList chunkDataList = new NBTTagList(); - for (Entry> entry : persistentChunks.asMap().entrySet()) - { - NBTTagCompound dataList = new NBTTagCompound(); - dataList.setIntArray("Chunk", new int[] { entry.getKey().chunkXPos, entry.getKey().chunkZPos }); - NBTTagList modList = new NBTTagList(); - for (String modId : entry.getValue()) - { - modList.appendTag(new NBTTagString(modId, "")); - } - dataList.setTag("Mods", modList); - chunkDataList.appendTag(dataList); - } - worldInfo.forgeWorldData.setTag("PersistentChunks", chunkDataList); - } - - public static ListMultimap loadPersistentChunkData(WorldInfo worldInfo) - { - ArrayListMultimap chunkList = ArrayListMultimap.create(); - - if (worldInfo == null || worldInfo.forgeWorldData == null) - { - return chunkList; - } - - NBTTagList chunkDataList = worldInfo.forgeWorldData.getTagList("PersistentChunks"); - for (int i = 0; i < chunkDataList.tagCount(); i++) - { - NBTTagCompound dataList = (NBTTagCompound) chunkDataList.tagAt(i); - int[] ccpairint = dataList.getIntArray("Chunk"); - ChunkCoordIntPair ccpair = new ChunkCoordIntPair(ccpairint[0], ccpairint[1]); - NBTTagList modList = dataList.getTagList("Mods"); - for (int j = 0; j < modList.tagCount(); j++) - { - String modId = modList.tagAt(j).getName(); - if (Loader.isModLoaded(modId)) - { - chunkList.put(ccpair, modId); - } - else - { - FMLLog.warning("The mod %s is not present - it's chunks will not be persisted any longer", modId); - } - } - } - return chunkList; - } } diff --git a/common/net/minecraftforge/common/ForgeInternalHandler.java b/common/net/minecraftforge/common/ForgeInternalHandler.java index 95fc2e82f..d9264386d 100644 --- a/common/net/minecraftforge/common/ForgeInternalHandler.java +++ b/common/net/minecraftforge/common/ForgeInternalHandler.java @@ -3,6 +3,7 @@ package net.minecraftforge.common; import net.minecraft.src.*; import net.minecraftforge.event.*; import net.minecraftforge.event.entity.*; +import net.minecraftforge.event.world.WorldEvent; public class ForgeInternalHandler { @@ -25,4 +26,16 @@ public class ForgeInternalHandler } } } + + @ForgeSubscribe(priority = EventPriority.HIGHEST) + public void onDimensionLoad(WorldEvent.Load event) + { + ForgeChunkManager.loadWorld(event.world); + } + + @ForgeSubscribe(priority = EventPriority.HIGHEST) + public void onDimensionSave(WorldEvent.Save event) + { + ForgeChunkManager.saveWorld(event.world); + } } diff --git a/common/net/minecraftforge/common/MinecraftForge.java b/common/net/minecraftforge/common/MinecraftForge.java index 46c138803..f58e6456d 100644 --- a/common/net/minecraftforge/common/MinecraftForge.java +++ b/common/net/minecraftforge/common/MinecraftForge.java @@ -18,15 +18,15 @@ import net.minecraftforge.event.entity.EntityEvent; public class MinecraftForge { /** - * The core Forge EventBus, all events for Forge will be fired on this, + * The core Forge EventBus, all events for Forge will be fired on this, * you should use this to register all your listeners. * This replaces every register*Handler() function in the old version of Forge. */ public static final EventBus EVENT_BUS = new EventBus(); public static boolean SPAWNER_ALLOW_ON_INVERTED = false; private static final ForgeInternalHandler INTERNAL_HANDLER = new ForgeInternalHandler(); - - + + /** Register a new plant to be planted when bonemeal is used on grass. * @param block The block to place. * @param metadata The metadata to set for the block when being placed. @@ -38,25 +38,25 @@ public class MinecraftForge ForgeHooks.grassList.add(new GrassEntry(block, metadata, weight)); } - /** + /** * Register a new seed to be dropped when breaking tall grass. - * + * * @param seed The item to drop as a seed. - * @param weight The relative probability of the seeds, + * @param weight The relative probability of the seeds, * where wheat seeds are 10. */ public static void addGrassSeed(ItemStack seed, int weight) { ForgeHooks.seedList.add(new SeedEntry(seed, weight)); } - - /** - * + + /** + * * Register a tool as a tool class with a given harvest level. * * @param tool The custom tool to register. * @param toolClass The tool class to register as. The predefined tool - * clases are "pickaxe", "shovel", "axe". You can add + * clases are "pickaxe", "shovel", "axe". You can add * others for custom tools. * @param harvestLevel The harvest level of the tool. */ @@ -65,7 +65,7 @@ public class MinecraftForge ForgeHooks.toolClasses.put(tool, Arrays.asList(toolClass, harvestLevel)); } - /** + /** * Register a block to be harvested by a tool class. This is the metadata * sensitive version, use it if your blocks are using metadata variants. * By default, this sets the block class as effective against that type. @@ -86,13 +86,13 @@ public class MinecraftForge ForgeHooks.toolEffectiveness.add(key); } - /** + /** * Remove a block effectiveness mapping. Since setBlockHarvestLevel * makes the tool class effective against the block by default, this can be * used to remove that mapping. This will force a block to be harvested at * the same speed regardless of tool quality, while still requiring a given * harvesting level. - * + * * @param block The block to remove effectiveness from. * @param metadata The metadata for the block subtype. * @param toolClass The tool class to remove the effectiveness mapping from. @@ -104,7 +104,7 @@ public class MinecraftForge ForgeHooks.toolEffectiveness.remove(key); } - /** + /** * Register a block to be harvested by a tool class. * By default, this sets the block class as effective against that type. * @@ -125,8 +125,8 @@ public class MinecraftForge ForgeHooks.toolEffectiveness.add(key); } } - - /** + + /** * Returns the block harvest level for a particular tool class. * * @param block The block to check. @@ -147,13 +147,13 @@ public class MinecraftForge return harvestLevel; } - /** + /** * Remove a block effectiveness mapping. Since setBlockHarvestLevel * makes the tool class effective against the block by default, this can be * used to remove that mapping. This will force a block to be harvested at * the same speed regardless of tool quality, while still requiring a given * harvesting level. - * + * * @param block The block to remove effectiveness from. * @param toolClass The tool class to remove the effectiveness mapping from. * @see MinecraftForge#setToolClass for details on tool classes. @@ -166,7 +166,7 @@ public class MinecraftForge ForgeHooks.toolEffectiveness.remove(key); } } - + /** * Method invoked by FML before any other mods are loaded. */ @@ -197,47 +197,9 @@ public class MinecraftForge EVENT_BUS.register(INTERNAL_HANDLER); } - + public static String getBrandingVersion() { return "Minecraft Forge "+ ForgeVersion.getVersion(); } - - /** - * Force a chunk to remain loaded by the supplied mod - * - * @param chunk the chunk to keep loaded - * @param world the world holding the chunk - * @param mod the mod (either {@link Mod} or {@link BaseMod}) that wishes to force the load - */ - public static void forceChunkLoaded(ChunkCoordIntPair chunk, World world, Object mod) - { - ModContainer mc = FMLCommonHandler.instance().findContainerFor(mod); - if (mc == null) - { - FMLLog.warning("Attempt to force chunk load for a non-existent mod %s", mod.getClass().getName()); - return; - } - String modId = mc.getModId(); - world.getPersistentChunks().put(chunk, modId); - } - - /** - * Stop forcing the chunk to remain loaded by the supplied mod (other mods may still - * keep the chunk loaded, however) - * @param chunk the chunk to keep loaded - * @param world the world holding the chunk - * @param mod the mod (either {@link Mod} or {@link BaseMod}) that wishes to unforce the load - */ - public static void unforceChunkLoaded(ChunkCoordIntPair chunk, World world, Object mod) - { - ModContainer mc = FMLCommonHandler.instance().findContainerFor(mod); - if (mc == null) - { - FMLLog.warning("Attempt to unforce chunk load for a non-existent mod %s", mod.getClass().getName()); - return; - } - String modId = mc.getModId(); - world.getPersistentChunks().remove(chunk, modId); - } } diff --git a/common/net/minecraftforge/common/test.jpage b/common/net/minecraftforge/common/test.jpage new file mode 100644 index 000000000..fa9b32790 --- /dev/null +++ b/common/net/minecraftforge/common/test.jpage @@ -0,0 +1 @@ +net.minecraftforge.common.ForgeChunkManager.loadConfiguration(new java.io.File("/tmp")); \ No newline at end of file diff --git a/patches/common/net/minecraft/src/ChunkProviderServer.java.patch b/patches/common/net/minecraft/src/ChunkProviderServer.java.patch index dd1190aa3..0a07013b1 100644 --- a/patches/common/net/minecraft/src/ChunkProviderServer.java.patch +++ b/patches/common/net/minecraft/src/ChunkProviderServer.java.patch @@ -4,7 +4,7 @@ { if (!this.currentServer.canNotSave) { -+ for (ChunkCoordIntPair forced : currentServer.persistentChunks.keySet()) ++ for (ChunkCoordIntPair forced : currentServer.getPersistentChunks().keySet()) + { + this.chunksToUnload.remove(ChunkCoordIntPair.chunkXZ2Int(forced.chunkXPos, forced.chunkZPos)); + } diff --git a/patches/common/net/minecraft/src/CompressedStreamTools.java.patch b/patches/common/net/minecraft/src/CompressedStreamTools.java.patch new file mode 100644 index 000000000..d0fa02dc7 --- /dev/null +++ b/patches/common/net/minecraft/src/CompressedStreamTools.java.patch @@ -0,0 +1,10 @@ +--- ../src_base/common/net/minecraft/src/CompressedStreamTools.java ++++ ../src_work/common/net/minecraft/src/CompressedStreamTools.java +@@ -155,7 +155,6 @@ + } + } + +- @SideOnly(Side.CLIENT) + public static NBTTagCompound read(File par0File) throws IOException + { + if (!par0File.exists()) diff --git a/patches/common/net/minecraft/src/World.java.patch b/patches/common/net/minecraft/src/World.java.patch index 1281acb8a..3926d2313 100644 --- a/patches/common/net/minecraft/src/World.java.patch +++ b/patches/common/net/minecraft/src/World.java.patch @@ -1,12 +1,13 @@ --- ../src_base/common/net/minecraft/src/World.java +++ ../src_work/common/net/minecraft/src/World.java -@@ -10,8 +10,22 @@ +@@ -10,8 +10,27 @@ import java.util.Random; import java.util.Set; -+import com.google.common.collect.ArrayListMultimap; -+import com.google.common.collect.ListMultimap; ++import com.google.common.collect.SetMultimap; + ++import net.minecraftforge.common.ForgeChunkManager; ++import net.minecraftforge.common.ForgeChunkManager.Ticket; +import net.minecraftforge.common.ForgeHooks; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.common.ForgeDirection; @@ -19,7 +20,7 @@ { + /** + * Used in the getEntitiesWithinAABB functions to expand the search area for entities. -+ * Modders should change this variable to a higher value if it is less then the radius ++ * Modders should change this variable to a higher value if it is less then the radius + * of one of there entities. + */ + public static double MAX_ENTITY_RADIUS = 2.0D; @@ -27,13 +28,7 @@ /** * boolean; if true updates scheduled by scheduleBlockUpdate happen immediately */ -@@ -128,6 +145,7 @@ - */ - public boolean isRemote; - -+ protected ListMultimap persistentChunks; - /** -@@ -132,6 +146,11 @@ +@@ -132,6 +151,11 @@ * Gets the biome for a given set of x/z coordinates */ public BiomeGenBase getBiomeGenForCoords(int par1, int par2) @@ -45,7 +40,7 @@ { if (this.blockExists(par1, 0, par2)) { -@@ -167,6 +186,7 @@ +@@ -167,6 +191,7 @@ this.chunkProvider = this.createChunkProvider(); this.calculateInitialSkylight(); this.calculateInitialWeather(); @@ -53,18 +48,15 @@ } public World(ISaveHandler par1ISaveHandler, String par2Str, WorldSettings par3WorldSettings, WorldProvider par4WorldProvider, Profiler par5Profiler) -@@ -213,6 +233,7 @@ +@@ -213,6 +238,7 @@ this.calculateInitialSkylight(); this.calculateInitialWeather(); -+ -+ persistentChunks = ForgeHooks.loadPersistentChunkData(this.worldInfo); -+ + MinecraftForge.EVENT_BUS.post(new WorldEvent.Load(this)); } /** -@@ -269,7 +290,8 @@ +@@ -269,7 +295,8 @@ */ public boolean isAirBlock(int par1, int par2, int par3) { @@ -74,7 +66,7 @@ } /** -@@ -278,7 +300,8 @@ +@@ -278,7 +305,8 @@ public boolean blockHasTileEntity(int par1, int par2, int par3) { int var4 = this.getBlockId(par1, par2, par3); @@ -84,7 +76,7 @@ } /** -@@ -980,7 +1003,7 @@ +@@ -980,7 +1008,7 @@ */ public boolean isDaytime() { @@ -93,7 +85,7 @@ } /** -@@ -1012,7 +1035,7 @@ +@@ -1012,7 +1040,7 @@ int var12 = this.getBlockMetadata(var8, var9, var10); Block var13 = Block.blocksList[var11]; @@ -102,7 +94,7 @@ { MovingObjectPosition var14 = var13.collisionRayTrace(this, var8, var9, var10, par1Vec3, par2Vec3); -@@ -1212,6 +1235,12 @@ +@@ -1212,6 +1240,12 @@ */ public void playSoundAtEntity(Entity par1Entity, String par2Str, float par3, float par4) { @@ -115,7 +107,7 @@ if (par1Entity != null && par2Str != null) { Iterator var5 = this.worldAccesses.iterator(); -@@ -1312,6 +1341,11 @@ +@@ -1312,6 +1346,11 @@ EntityPlayer var5 = (EntityPlayer)par1Entity; this.playerEntities.add(var5); this.updateAllPlayersSleepingFlag(); @@ -127,7 +119,7 @@ } this.getChunkFromChunkCoords(var2, var3).addEntity(par1Entity); -@@ -1563,6 +1597,12 @@ +@@ -1563,6 +1602,12 @@ * Calculates the color for the skybox */ public Vec3 getSkyColor(Entity par1Entity, float par2) @@ -140,7 +132,7 @@ { float var3 = this.getCelestialAngle(par2); float var4 = MathHelper.cos(var3 * (float)Math.PI * 2.0F) * 2.0F + 0.5F; -@@ -1658,6 +1698,12 @@ +@@ -1658,6 +1703,12 @@ @SideOnly(Side.CLIENT) public Vec3 drawClouds(float par1) { @@ -153,7 +145,7 @@ float var2 = this.getCelestialAngle(par1); float var3 = MathHelper.cos(var2 * (float)Math.PI * 2.0F) * 2.0F + 0.5F; -@@ -1736,7 +1782,7 @@ +@@ -1736,7 +1787,7 @@ { int var5 = var3.getBlockID(par1, var4, par2); @@ -162,7 +154,7 @@ { return var4 + 1; } -@@ -1751,6 +1797,12 @@ +@@ -1751,6 +1802,12 @@ * How bright are stars in the sky */ public float getStarBrightness(float par1) @@ -175,7 +167,7 @@ { float var2 = this.getCelestialAngle(par1); float var3 = 1.0F - (MathHelper.cos(var2 * (float)Math.PI * 2.0F) * 2.0F + 0.25F); -@@ -1893,7 +1945,7 @@ +@@ -1893,7 +1950,7 @@ if (var8 != null) { @@ -184,18 +176,18 @@ } } } -@@ -1903,6 +1955,10 @@ +@@ -1903,6 +1960,10 @@ if (!this.entityRemoval.isEmpty()) { + for (Object tile : entityRemoval) + { -+ ((TileEntity)tile).onChunkUnload(); ++ ((TileEntity)tile).onChunkUnload(); + } this.loadedTileEntityList.removeAll(this.entityRemoval); this.entityRemoval.clear(); } -@@ -1923,7 +1979,9 @@ +@@ -1923,7 +1984,9 @@ { this.loadedTileEntityList.add(var9); } @@ -206,7 +198,7 @@ if (this.chunkExists(var9.xCoord >> 4, var9.zCoord >> 4)) { Chunk var10 = this.getChunkFromChunkCoords(var9.xCoord >> 4, var9.zCoord >> 4); -@@ -1933,8 +1991,6 @@ +@@ -1933,8 +1996,6 @@ var10.setChunkBlockTileEntity(var9.xCoord & 15, var9.yCoord, var9.zCoord & 15, var9); } } @@ -215,7 +207,7 @@ } } -@@ -1947,13 +2003,13 @@ +@@ -1947,13 +2008,13 @@ public void addTileEntity(Collection par1Collection) { @@ -236,13 +228,15 @@ } } -@@ -1974,8 +2030,14 @@ +@@ -1973,9 +2034,17 @@ + { int var3 = MathHelper.floor_double(par1Entity.posX); int var4 = MathHelper.floor_double(par1Entity.posZ); - byte var5 = 32; - - if (!par2 || this.checkChunksExist(var3 - var5, 0, var4 - var5, var3 + var5, 0, var4 + var5)) -+ boolean isForced = persistentChunks.containsKey(new ChunkCoordIntPair(var3 >> 4, var4 >> 4)); ++ ++ boolean isForced = getPersistentChunks().containsKey(new ChunkCoordIntPair(var3 >> 4, var4 >> 4)); + byte var5 = isForced ? (byte)0 : 32; + boolean canUpdate = !par2 || this.checkChunksExist(var3 - var5, 0, var4 - var5, var3 + var5, 0, var4 + var5); + if (!canUpdate) @@ -255,7 +249,7 @@ { par1Entity.lastTickPosX = par1Entity.posX; par1Entity.lastTickPosY = par1Entity.posY; -@@ -2210,6 +2272,14 @@ +@@ -2210,6 +2279,14 @@ { return true; } @@ -270,7 +264,7 @@ } } } -@@ -2516,25 +2586,21 @@ +@@ -2516,25 +2593,21 @@ */ public void setBlockTileEntity(int par1, int par2, int par3, TileEntity par4TileEntity) { @@ -311,7 +305,7 @@ } } -@@ -2543,27 +2609,10 @@ +@@ -2543,27 +2616,10 @@ */ public void removeBlockTileEntity(int par1, int par2, int par3) { @@ -343,7 +337,7 @@ } } -@@ -2589,7 +2638,8 @@ +@@ -2589,7 +2645,8 @@ */ public boolean isBlockNormalCube(int par1, int par2, int par3) { @@ -353,7 +347,7 @@ } /** -@@ -2597,8 +2647,7 @@ +@@ -2597,8 +2654,7 @@ */ public boolean doesBlockHaveSolidTopSurface(int par1, int par2, int par3) { @@ -363,7 +357,7 @@ } /** -@@ -2614,7 +2663,7 @@ +@@ -2614,7 +2670,7 @@ if (var5 != null && !var5.isEmpty()) { Block var6 = Block.blocksList[this.getBlockId(par1, par2, par3)]; @@ -372,7 +366,7 @@ } else { -@@ -2645,8 +2694,7 @@ +@@ -2645,8 +2701,7 @@ */ public void setAllowedSpawnTypes(boolean par1, boolean par2) { @@ -382,7 +376,7 @@ } /** -@@ -2662,6 +2710,11 @@ +@@ -2662,6 +2717,11 @@ */ private void calculateInitialWeather() { @@ -394,7 +388,7 @@ if (this.worldInfo.isRaining()) { this.rainingStrength = 1.0F; -@@ -2677,6 +2730,11 @@ +@@ -2677,6 +2737,11 @@ * Updates all weather states. */ protected void updateWeather() @@ -406,7 +400,7 @@ { if (!this.provider.hasNoSky) { -@@ -2779,7 +2837,7 @@ +@@ -2779,12 +2844,14 @@ public void toggleRain() { @@ -415,7 +409,14 @@ } protected void setActivePlayerChunksAndCheckLight() -@@ -2891,6 +2949,11 @@ + { + this.activeChunkSet.clear(); ++ this.activeChunkSet.addAll(getPersistentChunks().keySet()); ++ + this.theProfiler.startSection("buildList"); + int var1; + EntityPlayer var2; +@@ -2891,6 +2958,11 @@ */ public boolean canBlockFreeze(int par1, int par2, int par3, boolean par4) { @@ -427,7 +428,7 @@ BiomeGenBase var5 = this.getBiomeGenForCoords(par1, par3); float var6 = var5.getFloatTemperature(); -@@ -2948,6 +3011,11 @@ +@@ -2948,6 +3020,11 @@ * Tests whether or not snow can be placed at a given location */ public boolean canSnowAt(int par1, int par2, int par3) @@ -439,7 +440,7 @@ { BiomeGenBase var4 = this.getBiomeGenForCoords(par1, par3); float var5 = var4.getFloatTemperature(); -@@ -3041,7 +3109,7 @@ +@@ -3041,7 +3118,7 @@ private int computeBlockLightValue(int par1, int par2, int par3, int par4, int par5, int par6) { @@ -448,7 +449,7 @@ int var8 = this.getSavedLightValue(EnumSkyBlock.Block, par2 - 1, par3, par4) - par6; int var9 = this.getSavedLightValue(EnumSkyBlock.Block, par2 + 1, par3, par4) - par6; int var10 = this.getSavedLightValue(EnumSkyBlock.Block, par2, par3 - 1, par4) - par6; -@@ -3309,10 +3377,10 @@ +@@ -3309,10 +3386,10 @@ public List getEntitiesWithinAABBExcludingEntity(Entity par1Entity, AxisAlignedBB par2AxisAlignedBB) { this.entitiesWithinAABBExcludingEntity.clear(); @@ -463,7 +464,7 @@ for (int var7 = var3; var7 <= var4; ++var7) { -@@ -3333,10 +3401,10 @@ +@@ -3333,10 +3410,10 @@ */ public List getEntitiesWithinAABB(Class par1Class, AxisAlignedBB par2AxisAlignedBB) { @@ -478,7 +479,7 @@ ArrayList var7 = new ArrayList(); for (int var8 = var3; var8 <= var4; ++var8) -@@ -3425,11 +3493,14 @@ +@@ -3425,11 +3502,14 @@ */ public void addLoadedEntities(List par1List) { @@ -496,7 +497,7 @@ } } -@@ -3466,7 +3537,10 @@ +@@ -3466,7 +3546,10 @@ { var9 = null; } @@ -508,7 +509,7 @@ return par1 > 0 && var9 == null && var10.canPlaceBlockOnSide(this, par2, par3, par4, par6); } } -@@ -3656,7 +3730,7 @@ +@@ -3656,7 +3739,7 @@ */ public void setWorldTime(long par1) { @@ -517,7 +518,7 @@ } /** -@@ -3664,12 +3738,12 @@ +@@ -3664,12 +3747,12 @@ */ public long getSeed() { @@ -532,7 +533,7 @@ } /** -@@ -3677,13 +3751,13 @@ +@@ -3677,13 +3760,13 @@ */ public ChunkCoordinates getSpawnPoint() { @@ -548,7 +549,7 @@ } @SideOnly(Side.CLIENT) -@@ -3707,7 +3781,10 @@ +@@ -3707,7 +3790,10 @@ if (!this.loadedEntityList.contains(par1Entity)) { @@ -560,7 +561,7 @@ } } -@@ -3715,6 +3792,11 @@ +@@ -3715,6 +3801,11 @@ * Called when checking if a certain block can be mined or not. The 'spawn safe zone' check is located here. */ public boolean canMineBlock(EntityPlayer par1EntityPlayer, int par2, int par3, int par4) @@ -572,7 +573,7 @@ { return true; } -@@ -3827,8 +3909,7 @@ +@@ -3827,8 +3918,7 @@ */ public boolean isBlockHighHumidity(int par1, int par2, int par3) { @@ -582,7 +583,7 @@ } /** -@@ -3882,7 +3963,7 @@ +@@ -3882,7 +3972,7 @@ */ public int getHeight() { @@ -591,7 +592,7 @@ } /** -@@ -3890,7 +3971,7 @@ +@@ -3890,7 +3980,7 @@ */ public int getActualHeight() { @@ -600,7 +601,7 @@ } /** -@@ -3936,7 +4017,7 @@ +@@ -3936,7 +4026,7 @@ */ public double getHorizon() { @@ -609,7 +610,7 @@ } /** -@@ -3964,4 +4045,65 @@ +@@ -3964,4 +4054,75 @@ var7.destroyBlockPartially(par1, par2, par3, par4, par5); } } @@ -618,7 +619,7 @@ + * Adds a single TileEntity to the world. + * @param entity The TileEntity to be added. + */ -+ public void addTileEntity(TileEntity entity) ++ public void addTileEntity(TileEntity entity) + { + List dest = scanningTileEntities ? addedTileEntityList : loadedTileEntityList; + if(entity.canUpdate()) @@ -630,7 +631,7 @@ + /** + * Determine if the given block is considered solid on the + * specified side. Used by placement logic. -+ * ++ * + * @param X Block X Position + * @param Y Block Y Position + * @param Z Block Z Position @@ -645,7 +646,7 @@ + /** + * Determine if the given block is considered solid on the + * specified side. Used by placement logic. -+ * ++ * + * @param X Block X Position + * @param Y Block Y Position + * @param Z Block Z Position @@ -675,8 +676,13 @@ + return block.isBlockSolidOnSide(this, X, Y, Z, side); + } + -+ public ListMultimap getPersistentChunks() ++ /** ++ * Get the persistent chunks for this world ++ * ++ * @return ++ */ ++ SetMultimap getPersistentChunks() + { -+ return persistentChunks; ++ return ForgeChunkManager.getPersistentChunksFor(this); + } } diff --git a/patches/common/net/minecraft/src/WorldInfo.java.patch b/patches/common/net/minecraft/src/WorldInfo.java.patch index 2edcb48e5..5fb3b265b 100644 --- a/patches/common/net/minecraft/src/WorldInfo.java.patch +++ b/patches/common/net/minecraft/src/WorldInfo.java.patch @@ -7,32 +7,3 @@ import cpw.mods.fml.common.Side; import cpw.mods.fml.common.asm.SideOnly; -@@ -59,6 +60,8 @@ - private boolean hardcore; - private boolean allowCommands; - private boolean initialized; -+ -+ public NBTTagCompound forgeWorldData; - - protected WorldInfo() - { -@@ -139,6 +142,10 @@ - { - this.playerTag = par1NBTTagCompound.getCompoundTag("Player"); - this.dimension = this.playerTag.getInteger("Dimension"); -+ } -+ if (par1NBTTagCompound.hasKey("ForgeData")) -+ { -+ this.forgeWorldData = par1NBTTagCompound.getCompoundTag("ForgeData"); - } - } - -@@ -228,6 +235,8 @@ - { - par1NBTTagCompound.setCompoundTag("Player", par2NBTTagCompound); - } -+ -+ par1NBTTagCompound.setCompoundTag("ForgeData", forgeWorldData); - } - - /** diff --git a/patches/common/net/minecraft/src/WorldServer.java.patch b/patches/common/net/minecraft/src/WorldServer.java.patch index 7a8e0e768..63f63bfec 100644 --- a/patches/common/net/minecraft/src/WorldServer.java.patch +++ b/patches/common/net/minecraft/src/WorldServer.java.patch @@ -1,6 +1,15 @@ --- ../src_base/common/net/minecraft/src/WorldServer.java +++ ../src_work/common/net/minecraft/src/WorldServer.java -@@ -10,6 +10,11 @@ +@@ -2,6 +2,8 @@ + + import cpw.mods.fml.common.Side; + import cpw.mods.fml.common.asm.SideOnly; ++ ++import java.io.File; + import java.util.ArrayList; + import java.util.HashSet; + import java.util.Iterator; +@@ -10,6 +12,11 @@ import java.util.Set; import java.util.TreeSet; import net.minecraft.server.MinecraftServer; @@ -12,7 +21,7 @@ public class WorldServer extends World { -@@ -71,6 +76,7 @@ +@@ -71,6 +78,7 @@ { this.pendingTickListEntries = new TreeSet(); } @@ -20,7 +29,7 @@ } /** -@@ -179,10 +185,7 @@ +@@ -179,10 +187,7 @@ private void resetRainAndThunder() { @@ -32,7 +41,7 @@ } public boolean areAllPlayersAsleep() -@@ -270,7 +273,7 @@ +@@ -270,7 +275,7 @@ int var10; int var11; @@ -41,7 +50,7 @@ { this.updateLCG = this.updateLCG * 3 + 1013904223; var8 = this.updateLCG >> 2; -@@ -288,7 +291,7 @@ +@@ -288,7 +293,7 @@ this.theProfiler.endStartSection("iceandsnow"); int var13; @@ -55,22 +64,31 @@ { NextTickListEntry var6 = new NextTickListEntry(par1, par2, par3, par4); - byte var7 = 8; -+ boolean isForced = persistentChunks.containsKey(new ChunkCoordIntPair(var6.xCoord >> 4, var6.zCoord >> 4)); ++ boolean isForced = getPersistentChunks().containsKey(new ChunkCoordIntPair(var6.xCoord >> 4, var6.zCoord >> 4)); + byte var7 = isForced ? (byte)0 : 8; if (this.scheduledUpdatesAreImmediate) { +@@ -418,7 +424,7 @@ + */ + public void updateEntities() + { +- if (this.playerEntities.isEmpty()) ++ if (this.playerEntities.isEmpty() && getPersistentChunks().isEmpty()) + { + if (this.updateEntityTick++ >= 60) + { @@ -462,7 +468,8 @@ this.pendingTickListEntries.remove(var4); this.field_73064_N.remove(var4); - byte var5 = 8; -+ boolean isForced = persistentChunks.containsKey(new ChunkCoordIntPair(var4.xCoord >> 4, var4.zCoord >> 4)); ++ boolean isForced = getPersistentChunks().containsKey(new ChunkCoordIntPair(var4.xCoord >> 4, var4.zCoord >> 4)); + byte var5 = isForced ? (byte)0 : 8; if (this.checkChunksExist(var4.xCoord - var5, var4.yCoord - var5, var4.zCoord - var5, var4.xCoord + var5, var4.yCoord + var5, var4.zCoord + var5)) { -@@ -559,15 +562,27 @@ +@@ -559,15 +566,27 @@ public List getAllTileEntityInBox(int par1, int par2, int par3, int par4, int par5, int par6) { ArrayList var7 = new ArrayList(); @@ -96,7 +114,7 @@ + TileEntity entity = (TileEntity)obj; + if (!entity.isInvalid()) + { -+ if (entity.xCoord >= par1 && entity.yCoord >= par2 && entity.zCoord >= par3 && ++ if (entity.xCoord >= par1 && entity.yCoord >= par2 && entity.zCoord >= par3 && + entity.xCoord <= par4 && entity.yCoord <= par5 && entity.zCoord <= par6) + { + var7.add(entity); @@ -107,7 +125,7 @@ } } -@@ -578,6 +593,11 @@ +@@ -578,6 +597,11 @@ * Called when checking if a certain block can be mined or not. The 'spawn safe zone' check is located here. */ public boolean canMineBlock(EntityPlayer par1EntityPlayer, int par2, int par3, int par4) @@ -119,7 +137,7 @@ { int var5 = MathHelper.abs_int(par2 - this.worldInfo.getSpawnX()); int var6 = MathHelper.abs_int(par4 - this.worldInfo.getSpawnZ()); -@@ -587,7 +607,7 @@ +@@ -587,7 +611,7 @@ var6 = var5; } @@ -128,7 +146,7 @@ } protected void initialize(WorldSettings par1WorldSettings) -@@ -670,7 +690,7 @@ +@@ -670,7 +694,7 @@ */ protected void createBonusChest() { @@ -137,16 +155,7 @@ for (int var2 = 0; var2 < 10; ++var2) { -@@ -702,6 +721,8 @@ - par2IProgressUpdate.displayProgressMessage("Saving level"); - } - -+ ForgeHooks.dumpPersistentChunks(worldInfo, persistentChunks); -+ - this.saveLevel(); - - if (par2IProgressUpdate != null) -@@ -713,6 +733,7 @@ +@@ -713,6 +737,7 @@ } this.chunkProvider.saveChunks(par1, par2IProgressUpdate); @@ -154,3 +163,13 @@ } } +@@ -971,4 +996,9 @@ + { + return this.thePlayerManager; + } ++ ++ public File getChunkSaveLocation() ++ { ++ return ((AnvilChunkLoader)theChunkProviderServer.currentChunkLoader).chunkSaveLocation; ++ } + }