Working cross dimensional implementation of chunkloading for Forge.

This commit is contained in:
Christian 2012-09-22 01:43:54 -04:00
parent d21e3ae218
commit c684360f51
14 changed files with 783 additions and 240 deletions

View file

@ -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
public abu.a # ComponentVillageHouse2.villageBlacksmithChestContents
# AnvilChunkLoader.chunkSaveLocation
default wy.d
# ChunkProviderServer.currentChunkLoader
default gq.e

View file

@ -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<String, Map<String, Property>> categories = new TreeMap<String, Map<String, Property>>();
public TreeMap<String, Property> blockProperties = new TreeMap<String, Property>();
public TreeMap<String, Property> itemProperties = new TreeMap<String, Property>();
public TreeMap<String, Property> generalProperties = new TreeMap<String, Property>();
private Map<String,String> 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<String, Property> 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<String, Property> 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<Property> 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);

View file

@ -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<Integer, WorldServer> worlds = new Hashtable<Integer, WorldServer>();
private static boolean hasInit = false;
private static Hashtable<Integer, Integer> dimensions = new Hashtable<Integer, Integer>();
private static Map<World, ListMultimap<ChunkCoordIntPair, String>> persistentChunkStore = Maps.newHashMap();
public static boolean registerProviderType(int id, Class<? extends WorldProvider> 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);
}
}

View file

@ -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<World, Multimap<String, Ticket>> tickets = Maps.newHashMap();
private static Map<String, Integer> ticketConstraints = Maps.newHashMap();
private static Map<String, Integer> chunkConstraints = Maps.newHashMap();
private static Map<String, LoadingCallback> callbacks = Maps.newHashMap();
private static Map<World, SetMultimap<ChunkCoordIntPair,Ticket>> 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<Ticket> 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<ChunkCoordIntPair> 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<String, Ticket> loadedTickets = ArrayListMultimap.<String, Ticket>create();
tickets.put(world, loadedTickets);
SetMultimap<ChunkCoordIntPair,Ticket> 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<ChunkCoordIntPair, Ticket> 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<String, Ticket> 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;
}
}
}

View file

@ -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());
}
}

View file

@ -359,55 +359,4 @@ public class ForgeHooks
player.joinEntityItemWithWorld(event.entityItem);
return event.entityItem;
}
public static void dumpPersistentChunks(WorldInfo worldInfo, ListMultimap<ChunkCoordIntPair, String> persistentChunks)
{
worldInfo.forgeWorldData = new NBTTagCompound();
NBTTagList chunkDataList = new NBTTagList();
for (Entry<ChunkCoordIntPair, Collection<String>> 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<ChunkCoordIntPair, String> loadPersistentChunkData(WorldInfo worldInfo)
{
ArrayListMultimap<ChunkCoordIntPair, String> chunkList = ArrayListMultimap.<ChunkCoordIntPair, String>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;
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -0,0 +1 @@
net.minecraftforge.common.ForgeChunkManager.loadConfiguration(new java.io.File("/tmp"));

View file

@ -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));
+ }

View file

@ -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())

View file

@ -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<ChunkCoordIntPair, String> 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<ChunkCoordIntPair, String> getPersistentChunks()
+ /**
+ * Get the persistent chunks for this world
+ *
+ * @return
+ */
+ SetMultimap<ChunkCoordIntPair, Ticket> getPersistentChunks()
+ {
+ return persistentChunks;
+ return ForgeChunkManager.getPersistentChunksFor(this);
+ }
}

View file

@ -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);
}
/**

View file

@ -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;
+ }
}