2016-06-23 03:49:47 +00:00
/ *
* Minecraft Forge
2018-07-01 21:17:28 +00:00
* Copyright ( c ) 2016 - 2018 .
2016-06-23 03:49:47 +00:00
*
* This library is free software ; you can redistribute it and / or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2 . 1
* of the License .
*
* This library is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* Lesser General Public License for more details .
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA
* /
2012-09-22 05:43:54 +00:00
package net.minecraftforge.common ;
import java.io.File ;
import java.io.IOException ;
2014-06-04 21:39:47 +00:00
import java.util.ArrayList ;
Initial update to 1.8, Super beta. Most rendering related hooks are out due to major changes in 1.8.
Some notes:
Almost all int x, int y, int z parameters have been changed to BlockPos class
ForgeDirection has been removed, replaced by net.minecraft.util.EnumFacing.
All FML classes have moved from packet cpw.mods.fml to net.minecraftforge.fml
Fluid Rendering has been disabled for the time being, to be re-evaulated and a test mod created for it.
Minecraft now uses a Model based system for rendering blocks and Items. The intention is to expand the model format to better suit modder's needed once it is evaulated.
As such, The model loaders from Forge have been removed, to be replaced by expanding vanilla's model format.
Metadata has been extracted out in Minecraft to IBlockState, which holds a list of properties instead of magic number metadata. DO NOT listen to the fearmongering, you can do EVERYTHING with block states you could previously with metadata.
Stencil Bits are disabled entirely by for the main Display, Modders must enable and recreate the FrameBuffer if they wish to use Stencil Bits.
2014-11-26 03:56:35 +00:00
import java.util.Iterator ;
2012-09-22 05:43:54 +00:00
import java.util.LinkedHashSet ;
import java.util.List ;
import java.util.Map ;
2017-06-25 01:08:20 +00:00
import java.util.Objects ;
2012-09-22 16:27:14 +00:00
import java.util.Set ;
import java.util.UUID ;
2016-03-09 05:48:32 +00:00
2016-03-23 14:34:48 +00:00
import javax.annotation.Nullable ;
import net.minecraft.entity.Entity ;
import net.minecraft.nbt.CompressedStreamTools ;
import net.minecraft.nbt.NBTTagCompound ;
import net.minecraft.nbt.NBTTagList ;
2017-10-02 22:17:37 +00:00
import net.minecraft.tileentity.TileEntity ;
2016-03-23 14:34:48 +00:00
import net.minecraft.util.ClassInheritanceMultiMap ;
2016-05-18 10:29:10 +00:00
import net.minecraft.util.math.ChunkPos ;
2016-03-23 14:34:48 +00:00
import net.minecraft.util.math.MathHelper ;
import net.minecraft.world.World ;
import net.minecraft.world.WorldServer ;
import net.minecraft.world.chunk.Chunk ;
2017-10-02 22:17:37 +00:00
import net.minecraft.world.chunk.storage.AnvilChunkLoader ;
2018-02-27 03:49:56 +00:00
import net.minecraft.world.storage.ThreadedFileIOBase ;
2016-03-23 14:34:48 +00:00
import net.minecraftforge.common.config.ConfigCategory ;
import net.minecraftforge.common.config.Configuration ;
import net.minecraftforge.common.config.Property ;
import net.minecraftforge.common.util.Constants ;
2018-08-27 17:10:07 +00:00
import net.minecraftforge.fml.server.ServerLifecycleHooks ;
2016-03-23 14:34:48 +00:00
import net.minecraftforge.fml.common.FMLCommonHandler ;
import net.minecraftforge.fml.common.FMLLog ;
import net.minecraftforge.fml.common.Loader ;
2018-06-15 19:03:35 +00:00
import net.minecraftforge.fml.ModContainer ;
2016-03-23 14:34:48 +00:00
2012-09-23 02:37:21 +00:00
import com.google.common.cache.Cache ;
import com.google.common.cache.CacheBuilder ;
2012-09-22 05:43:54 +00:00
import com.google.common.collect.ArrayListMultimap ;
2012-09-22 16:27:14 +00:00
import com.google.common.collect.BiMap ;
import com.google.common.collect.HashBiMap ;
2012-09-28 03:32:21 +00:00
import com.google.common.collect.HashMultimap ;
2012-09-25 02:07:39 +00:00
import com.google.common.collect.ImmutableList ;
2012-12-02 05:38:32 +00:00
import com.google.common.collect.ImmutableListMultimap ;
2012-09-22 16:27:14 +00:00
import com.google.common.collect.ImmutableSet ;
2012-09-22 05:43:54 +00:00
import com.google.common.collect.ImmutableSetMultimap ;
import com.google.common.collect.LinkedHashMultimap ;
import com.google.common.collect.ListMultimap ;
2012-10-07 01:32:41 +00:00
import com.google.common.collect.MapMaker ;
2012-09-22 05:43:54 +00:00
import com.google.common.collect.Maps ;
import com.google.common.collect.Multimap ;
import com.google.common.collect.SetMultimap ;
import com.google.common.collect.Sets ;
2016-03-03 10:57:38 +00:00
2012-09-22 05:43:54 +00:00
/ * *
* 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 .
2016-12-21 23:52:30 +00:00
* 3 . Mods request chunks to stay loaded { @link # forceChunk ( Ticket , ChunkPos ) } or remove chunks from force loading { @link # unforceChunk ( Ticket , ChunkPos ) } .
2012-09-22 05:43:54 +00:00
* 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 ;
2012-10-07 01:32:41 +00:00
private static Map < World , Multimap < String , Ticket > > tickets = new MapMaker ( ) . weakKeys ( ) . makeMap ( ) ;
2012-09-22 05:43:54 +00:00
private static Map < String , Integer > ticketConstraints = Maps . newHashMap ( ) ;
private static Map < String , Integer > chunkConstraints = Maps . newHashMap ( ) ;
2012-09-28 03:32:21 +00:00
private static SetMultimap < String , Ticket > playerTickets = HashMultimap . create ( ) ;
2012-09-22 05:43:54 +00:00
private static Map < String , LoadingCallback > callbacks = Maps . newHashMap ( ) ;
2016-05-18 10:29:10 +00:00
private static Map < World , ImmutableSetMultimap < ChunkPos , Ticket > > forcedChunks = new MapMaker ( ) . weakKeys ( ) . makeMap ( ) ;
2012-09-22 16:27:14 +00:00
private static BiMap < UUID , Ticket > pendingEntities = HashBiMap . create ( ) ;
2012-09-22 05:43:54 +00:00
2017-10-02 22:17:37 +00:00
private static Map < World , Cache < Long , ChunkEntry > > dormantChunkCache = new MapMaker ( ) . weakKeys ( ) . makeMap ( ) ;
2012-09-28 21:03:49 +00:00
2012-09-28 03:32:21 +00:00
private static File cfgFile ;
private static Configuration config ;
private static int playerTicketLength ;
2012-09-28 21:03:49 +00:00
private static int dormantChunkCacheSize ;
2014-07-17 01:36:12 +00:00
2018-01-18 20:44:14 +00:00
public static boolean asyncChunkLoading ;
2014-06-04 21:39:47 +00:00
public static final List < String > MOD_PROP_ORDER = new ArrayList < String > ( 2 ) ;
2012-12-06 21:14:15 +00:00
private static Set < String > warnedMods = Sets . newHashSet ( ) ;
2014-07-17 01:36:12 +00:00
2014-06-04 21:39:47 +00:00
static
{
MOD_PROP_ORDER . add ( " maximumTicketCount " ) ;
MOD_PROP_ORDER . add ( " maximumChunksPerTicket " ) ;
}
2014-07-17 01:36:12 +00:00
2017-10-02 22:17:37 +00:00
private static class ChunkEntry
{
public final Chunk chunk ;
public final NBTTagCompound nbt ;
public ChunkEntry ( Chunk chunk )
{
this . chunk = chunk ;
this . nbt = new NBTTagCompound ( ) ;
}
}
2016-03-09 05:48:32 +00:00
public static Iterator < Chunk > getPersistentChunksIterableFor ( final World world , Iterator < Chunk > chunkIterator )
{
2016-05-18 10:29:10 +00:00
final ImmutableSetMultimap < ChunkPos , Ticket > persistentChunksFor = getPersistentChunksFor ( world ) ;
2016-03-09 05:48:32 +00:00
final ImmutableSet . Builder < Chunk > builder = ImmutableSet . builder ( ) ;
2017-06-11 00:59:01 +00:00
world . profiler . startSection ( " forcedChunkLoading " ) ;
2017-06-25 01:08:20 +00:00
builder . addAll ( persistentChunksFor . keys ( ) . stream ( ) . filter ( Objects : : nonNull ) . map ( input - > world . getChunkFromChunkCoords ( input . x , input . z ) ) . iterator ( ) ) ;
2017-06-11 00:59:01 +00:00
world . profiler . endStartSection ( " regularChunkLoading " ) ;
2016-03-09 05:48:32 +00:00
builder . addAll ( chunkIterator ) ;
2017-06-11 00:59:01 +00:00
world . profiler . endSection ( ) ;
2016-03-09 05:48:32 +00:00
return builder . build ( ) . iterator ( ) ;
}
2012-09-22 05:43:54 +00:00
/ * *
* 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
2012-09-24 21:31:03 +00:00
* mod to re - register the chunks associated with those tickets . The list supplied
* here is truncated to length prior to use . Tickets unwanted by the
2012-09-25 02:07:39 +00:00
* mod must be disposed of manually unless the mod is an OrderedLoadingCallback instance
* in which case , they will have been disposed of by the earlier callback .
2012-09-22 05:43:54 +00:00
*
2012-09-25 02:07:39 +00:00
* @param tickets The tickets to re - register . The list is immutable and cannot be manipulated directly . Copy it first .
* @param world the world
2012-09-22 05:43:54 +00:00
* /
2018-01-13 07:54:29 +00:00
void ticketsLoaded ( List < Ticket > tickets , World world ) ;
2012-09-22 05:43:54 +00:00
}
2012-09-24 21:31:03 +00:00
/ * *
* This is a special LoadingCallback that can be implemented as well as the
* LoadingCallback to provide access to additional behaviour .
* Specifically , this callback will fire prior to Forge dropping excess
* tickets . Tickets in the returned list are presumed ordered and excess will
* be truncated from the returned list .
* This allows the mod to control not only if they actually < em > want < / em > a ticket but
* also their preferred ticket ordering .
*
* @author cpw
*
* /
public interface OrderedLoadingCallback extends LoadingCallback
{
/ * *
* Called back when tickets are loaded from the world to allow the
2012-09-25 02:07:39 +00:00
* mod to decide if it wants the ticket still , and prioritise overflow
* based on the ticket count .
* WARNING : You cannot force chunks in this callback , it is strictly for allowing the mod
* to be more selective in which tickets it wishes to preserve in an overflow situation
2012-09-24 21:31:03 +00:00
*
2012-09-25 02:07:39 +00:00
* @param tickets The tickets that you will want to select from . The list is immutable and cannot be manipulated directly . Copy it first .
2012-09-24 21:31:03 +00:00
* @param world The world
* @param maxTicketCount The maximum number of tickets that will be allowed .
* @return A list of the tickets this mod wishes to continue using . This list will be truncated
2012-09-25 02:07:39 +00:00
* to " maxTicketCount " size after the call returns and then offered to the other callback
* method
2012-09-24 21:31:03 +00:00
* /
2018-01-13 07:54:29 +00:00
List < Ticket > ticketsLoaded ( List < Ticket > tickets , World world , int maxTicketCount ) ;
2012-09-24 21:31:03 +00:00
}
2012-12-02 05:38:32 +00:00
public interface PlayerOrderedLoadingCallback extends LoadingCallback
{
/ * *
* Called back when tickets are loaded from the world to allow the
* mod to decide if it wants the ticket still .
* This is for player bound tickets rather than mod bound tickets . It is here so mods can
* decide they want to dump all player tickets
*
* WARNING : You cannot force chunks in this callback , it is strictly for allowing the mod
* to be more selective in which tickets it wishes to preserve
*
* @param tickets The tickets that you will want to select from . The list is immutable and cannot be manipulated directly . Copy it first .
* @param world The world
* @return A list of the tickets this mod wishes to use . This list will subsequently be offered
* to the main callback for action
* /
2018-01-13 07:54:29 +00:00
ListMultimap < String , Ticket > playerTicketsLoaded ( ListMultimap < String , Ticket > tickets , World world ) ;
2012-12-02 05:38:32 +00:00
}
2012-09-22 05:43:54 +00:00
public enum Type
{
/ * *
* For non - entity registrations
* /
NORMAL ,
/ * *
* For entity registrations
* /
ENTITY
}
public static class Ticket
{
private String modId ;
private Type ticketType ;
2016-05-18 10:29:10 +00:00
private LinkedHashSet < ChunkPos > requestedChunks ;
2012-09-22 05:43:54 +00:00
private NBTTagCompound modData ;
2012-11-27 23:28:59 +00:00
public final World world ;
2012-09-22 05:43:54 +00:00
private int maxDepth ;
2014-01-18 05:55:48 +00:00
//private String entityClazz;
2012-09-22 16:27:14 +00:00
private int entityChunkX ;
private int entityChunkZ ;
2012-09-22 05:43:54 +00:00
private Entity entity ;
2012-09-28 03:32:21 +00:00
private String player ;
2012-09-22 05:43:54 +00:00
Ticket ( String modId , Type type , World world )
{
this . modId = modId ;
this . ticketType = type ;
this . world = world ;
this . maxDepth = getMaxChunkDepthFor ( modId ) ;
this . requestedChunks = Sets . newLinkedHashSet ( ) ;
}
2012-11-27 23:28:59 +00:00
Ticket ( String modId , Type type , World world , String player )
2012-09-28 03:32:21 +00:00
{
this ( modId , type , world ) ;
if ( player ! = null )
{
2012-11-27 23:28:59 +00:00
this . player = player ;
2012-09-28 03:32:21 +00:00
}
else
{
2017-06-23 05:33:11 +00:00
FMLLog . log . error ( " Attempt to create a player ticket without a valid player " ) ;
2012-09-28 03:32:21 +00:00
throw new RuntimeException ( ) ;
}
}
2012-09-22 05:43:54 +00:00
/ * *
* 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 )
{
2012-09-24 21:31:03 +00:00
if ( depth > getMaxChunkDepthFor ( modId ) | | ( depth < = 0 & & getMaxChunkDepthFor ( modId ) > 0 ) )
2012-09-22 05:43:54 +00:00
{
2017-06-23 05:33:11 +00:00
FMLLog . log . warn ( " The mod {} tried to modify the chunk ticket depth to: {}, its allowed maximum is: {} " , modId , depth , getMaxChunkDepthFor ( modId ) ) ;
2012-09-22 05:43:54 +00:00
}
else
{
this . maxDepth = depth ;
}
}
2012-10-12 01:27:51 +00:00
/ * *
2012-10-14 16:30:33 +00:00
* Gets the current max depth for this ticket .
* Should be the same as getMaxChunkListDepth ( )
2012-10-12 01:27:51 +00:00
* unless setChunkListDepth has been called .
2012-10-14 16:30:33 +00:00
*
2012-10-12 01:27:51 +00:00
* @return Current max depth
* /
public int getChunkListDepth ( )
{
return maxDepth ;
}
2012-10-14 16:30:33 +00:00
2012-09-22 05:43:54 +00:00
/ * *
* 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 ;
}
/ * *
2012-09-22 16:27:14 +00:00
* Get the entity associated with this { @link Type # ENTITY } type ticket
2013-01-22 02:56:04 +00:00
* @return the entity
2012-09-22 05:43:54 +00:00
* /
2012-09-22 16:27:14 +00:00
public Entity getEntity ( )
2012-09-22 05:43:54 +00:00
{
2012-09-22 16:27:14 +00:00
return entity ;
2012-09-22 05:43:54 +00:00
}
2012-09-28 03:32:21 +00:00
/ * *
* Is this a player associated ticket rather than a mod associated ticket ?
* /
public boolean isPlayerTicket ( )
{
return player ! = null ;
}
/ * *
* Get the player associated with this ticket
* /
public String getPlayerName ( )
{
return player ;
}
2012-10-12 01:27:51 +00:00
/ * *
* Get the associated mod id
* /
public String getModId ( )
{
return modId ;
}
/ * *
* Gets the ticket type
* /
public Type getType ( )
{
return ticketType ;
}
/ * *
* Gets a list of requested chunks for this ticket .
* /
2016-05-18 10:29:10 +00:00
public ImmutableSet < ChunkPos > getChunkList ( )
2012-10-12 01:27:51 +00:00
{
return ImmutableSet . copyOf ( requestedChunks ) ;
}
2012-09-22 05:43:54 +00:00
}
2018-06-11 01:12:46 +00:00
public static class ForceChunkEvent extends net . minecraftforge . eventbus . api . Event
{
2016-03-24 08:44:52 +00:00
private final Ticket ticket ;
2016-05-18 10:29:10 +00:00
private final ChunkPos location ;
2012-11-19 00:28:02 +00:00
2016-05-18 10:29:10 +00:00
public ForceChunkEvent ( Ticket ticket , ChunkPos location )
2012-11-19 00:33:15 +00:00
{
this . ticket = ticket ;
this . location = location ;
}
2016-03-24 08:44:52 +00:00
public Ticket getTicket ( )
{
return ticket ;
}
2016-05-18 10:29:10 +00:00
public ChunkPos getLocation ( )
2016-03-24 08:44:52 +00:00
{
return location ;
}
2012-11-19 00:28:02 +00:00
}
2018-06-11 01:12:46 +00:00
public static class UnforceChunkEvent extends net . minecraftforge . eventbus . api . Event
{
2016-03-24 08:44:52 +00:00
private final Ticket ticket ;
2016-05-18 10:29:10 +00:00
private final ChunkPos location ;
2012-11-19 00:33:15 +00:00
2016-05-18 10:29:10 +00:00
public UnforceChunkEvent ( Ticket ticket , ChunkPos location )
2012-11-19 00:33:15 +00:00
{
this . ticket = ticket ;
this . location = location ;
}
2016-03-24 08:44:52 +00:00
public Ticket getTicket ( )
{
return ticket ;
}
2016-05-18 10:29:10 +00:00
public ChunkPos getLocation ( )
2016-03-24 08:44:52 +00:00
{
return location ;
}
2012-11-19 00:28:02 +00:00
}
2012-11-19 00:33:15 +00:00
2012-12-02 05:38:32 +00:00
/ * *
* Allows dynamically loading world mods to test if there are chunk tickets in the world
* Mods that add dynamically generated worlds ( like Mystcraft ) should call this method
* to determine if the world should be loaded during server starting .
*
* @param chunkDir The chunk directory to test : should be equivalent to { @link WorldServer # getChunkSaveLocation ( ) }
* @return if there are tickets outstanding for this world or not
* /
public static boolean savedWorldHasForcedChunkTickets ( File chunkDir )
{
File chunkLoaderData = new File ( chunkDir , " forcedchunks.dat " ) ;
if ( chunkLoaderData . exists ( ) & & chunkLoaderData . isFile ( ) )
{
;
try
{
NBTTagCompound forcedChunkData = CompressedStreamTools . read ( chunkLoaderData ) ;
2014-02-11 00:16:03 +00:00
return forcedChunkData . getTagList ( " TicketList " , Constants . NBT . TAG_COMPOUND ) . tagCount ( ) > 0 ;
2012-12-02 05:38:32 +00:00
}
catch ( IOException e )
{
}
}
return false ;
}
2012-09-22 05:43:54 +00:00
static void loadWorld ( World world )
{
2016-03-23 14:34:48 +00:00
ArrayListMultimap < String , Ticket > newTickets = ArrayListMultimap . create ( ) ;
2012-09-25 23:12:10 +00:00
tickets . put ( world , newTickets ) ;
2012-09-22 05:43:54 +00:00
2017-10-02 22:17:37 +00:00
forcedChunks . put ( world , ImmutableSetMultimap . of ( ) ) ;
2012-09-22 05:43:54 +00:00
if ( ! ( world instanceof WorldServer ) )
{
return ;
}
2017-01-14 19:01:27 +00:00
if ( dormantChunkCacheSize ! = 0 )
{ // only put into cache if we're using dormant chunk caching
2017-10-02 22:17:37 +00:00
dormantChunkCache . put ( world , CacheBuilder . newBuilder ( ) . maximumSize ( dormantChunkCacheSize ) . build ( ) ) ;
2017-01-14 19:01:27 +00:00
}
2012-09-22 05:43:54 +00:00
WorldServer worldServer = ( WorldServer ) world ;
File chunkDir = worldServer . getChunkSaveLocation ( ) ;
File chunkLoaderData = new File ( chunkDir , " forcedchunks.dat " ) ;
if ( chunkLoaderData . exists ( ) & & chunkLoaderData . isFile ( ) )
{
2016-03-23 14:34:48 +00:00
ArrayListMultimap < String , Ticket > loadedTickets = ArrayListMultimap . create ( ) ;
2012-12-02 05:38:32 +00:00
Map < String , ListMultimap < String , Ticket > > playerLoadedTickets = Maps . newHashMap ( ) ;
2012-09-22 05:43:54 +00:00
NBTTagCompound forcedChunkData ;
try
{
forcedChunkData = CompressedStreamTools . read ( chunkLoaderData ) ;
}
catch ( IOException e )
{
2017-06-23 05:33:11 +00:00
FMLLog . log . warn ( " Unable to read forced chunk data at {} - it will be ignored " , chunkLoaderData . getAbsolutePath ( ) , e ) ;
2012-09-22 05:43:54 +00:00
return ;
}
2014-02-11 00:16:03 +00:00
NBTTagList ticketList = forcedChunkData . getTagList ( " TicketList " , Constants . NBT . TAG_COMPOUND ) ;
2012-09-22 05:43:54 +00:00
for ( int i = 0 ; i < ticketList . tagCount ( ) ; i + + )
{
2016-03-23 14:34:48 +00:00
NBTTagCompound ticketHolder = ticketList . getCompoundTagAt ( i ) ;
2012-09-22 05:43:54 +00:00
String modId = ticketHolder . getString ( " Owner " ) ;
2016-12-17 21:23:39 +00:00
boolean isPlayer = ForgeVersion . MOD_ID . equals ( modId ) ;
2012-09-22 05:43:54 +00:00
2012-09-28 03:32:21 +00:00
if ( ! isPlayer & & ! Loader . isModLoaded ( modId ) )
2012-09-22 05:43:54 +00:00
{
2017-06-23 05:33:11 +00:00
FMLLog . log . warn ( " Found chunkloading data for mod {} which is currently not available or active - it will be removed from the world save " , modId ) ;
2012-09-22 05:43:54 +00:00
continue ;
}
2012-09-28 03:32:21 +00:00
if ( ! isPlayer & & ! callbacks . containsKey ( modId ) )
2012-09-22 05:43:54 +00:00
{
2017-06-23 05:33:11 +00:00
FMLLog . log . warn ( " The mod {} 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 ) ;
2012-09-22 05:43:54 +00:00
continue ;
}
2014-02-11 00:16:03 +00:00
NBTTagList tickets = ticketHolder . getTagList ( " Tickets " , Constants . NBT . TAG_COMPOUND ) ;
2012-09-24 21:31:03 +00:00
for ( int j = 0 ; j < tickets . tagCount ( ) ; j + + )
2012-09-22 05:43:54 +00:00
{
2016-03-23 14:34:48 +00:00
NBTTagCompound ticket = tickets . getCompoundTagAt ( j ) ;
2012-09-28 03:32:21 +00:00
modId = ticket . hasKey ( " ModId " ) ? ticket . getString ( " ModId " ) : modId ;
2012-09-22 05:43:54 +00:00
Type type = Type . values ( ) [ ticket . getByte ( " Type " ) ] ;
2014-01-18 05:55:48 +00:00
//byte ticketChunkDepth = ticket.getByte("ChunkListDepth");
2012-09-22 05:43:54 +00:00
Ticket tick = new Ticket ( modId , type , world ) ;
2012-09-24 21:31:03 +00:00
if ( ticket . hasKey ( " ModData " ) )
{
tick . modData = ticket . getCompoundTag ( " ModData " ) ;
}
2012-09-28 03:32:21 +00:00
if ( ticket . hasKey ( " Player " ) )
{
tick . player = ticket . getString ( " Player " ) ;
2012-12-02 05:38:32 +00:00
if ( ! playerLoadedTickets . containsKey ( tick . modId ) )
{
2017-10-02 22:17:37 +00:00
playerLoadedTickets . put ( modId , ArrayListMultimap . create ( ) ) ;
2012-12-02 05:38:32 +00:00
}
playerLoadedTickets . get ( tick . modId ) . put ( tick . player , tick ) ;
2012-09-28 03:32:21 +00:00
}
else
{
loadedTickets . put ( modId , tick ) ;
}
2012-09-22 05:43:54 +00:00
if ( type = = Type . ENTITY )
{
2012-09-22 16:27:14 +00:00
tick . entityChunkX = ticket . getInteger ( " chunkX " ) ;
tick . entityChunkZ = ticket . getInteger ( " chunkZ " ) ;
UUID uuid = new UUID ( ticket . getLong ( " PersistentIDMSB " ) , ticket . getLong ( " PersistentIDLSB " ) ) ;
// add the ticket to the "pending entity" list
pendingEntities . put ( uuid , tick ) ;
2012-09-22 05:43:54 +00:00
}
}
}
2012-09-22 16:27:14 +00:00
for ( Ticket tick : ImmutableSet . copyOf ( pendingEntities . values ( ) ) )
{
if ( tick . ticketType = = Type . ENTITY & & tick . entity = = null )
{
// force the world to load the entity's chunk
// the load will come back through the loadEntity method and attach the entity
// to the ticket
world . getChunkFromChunkCoords ( tick . entityChunkX , tick . entityChunkZ ) ;
}
}
for ( Ticket tick : ImmutableSet . copyOf ( pendingEntities . values ( ) ) )
{
if ( tick . ticketType = = Type . ENTITY & & tick . entity = = null )
{
2017-06-23 05:33:11 +00:00
FMLLog . log . warn ( " Failed to load persistent chunkloading entity {} from store. " , pendingEntities . inverse ( ) . get ( tick ) ) ;
2012-09-22 16:27:14 +00:00
loadedTickets . remove ( tick . modId , tick ) ;
}
}
pendingEntities . clear ( ) ;
2012-09-22 05:43:54 +00:00
// send callbacks
for ( String modId : loadedTickets . keySet ( ) )
{
2012-09-24 21:31:03 +00:00
LoadingCallback loadingCallback = callbacks . get ( modId ) ;
2013-03-28 12:19:24 +00:00
if ( loadingCallback = = null )
{
continue ;
}
2012-09-24 21:31:03 +00:00
int maxTicketLength = getMaxTicketLengthFor ( modId ) ;
List < Ticket > tickets = loadedTickets . get ( modId ) ;
if ( loadingCallback instanceof OrderedLoadingCallback )
{
OrderedLoadingCallback orderedLoadingCallback = ( OrderedLoadingCallback ) loadingCallback ;
2012-09-25 02:07:39 +00:00
tickets = orderedLoadingCallback . ticketsLoaded ( ImmutableList . copyOf ( tickets ) , world , maxTicketLength ) ;
2012-09-24 21:31:03 +00:00
}
2012-09-25 02:07:39 +00:00
if ( tickets . size ( ) > maxTicketLength )
2012-09-24 21:31:03 +00:00
{
2017-06-23 05:33:11 +00:00
FMLLog . log . warn ( " The mod {} has too many open chunkloading tickets {}. Excess will be dropped " , modId , tickets . size ( ) ) ;
2012-09-25 02:07:39 +00:00
tickets . subList ( maxTicketLength , tickets . size ( ) ) . clear ( ) ;
2012-09-24 21:31:03 +00:00
}
ForgeChunkManager . tickets . get ( world ) . putAll ( modId , tickets ) ;
2012-09-25 02:07:39 +00:00
loadingCallback . ticketsLoaded ( ImmutableList . copyOf ( tickets ) , world ) ;
2012-09-22 05:43:54 +00:00
}
2012-09-28 03:32:21 +00:00
for ( String modId : playerLoadedTickets . keySet ( ) )
{
LoadingCallback loadingCallback = callbacks . get ( modId ) ;
2013-03-28 12:19:24 +00:00
if ( loadingCallback = = null )
{
continue ;
}
2012-12-02 05:38:32 +00:00
ListMultimap < String , Ticket > tickets = playerLoadedTickets . get ( modId ) ;
if ( loadingCallback instanceof PlayerOrderedLoadingCallback )
{
PlayerOrderedLoadingCallback orderedLoadingCallback = ( PlayerOrderedLoadingCallback ) loadingCallback ;
tickets = orderedLoadingCallback . playerTicketsLoaded ( ImmutableListMultimap . copyOf ( tickets ) , world ) ;
playerTickets . putAll ( tickets ) ;
}
2016-12-17 21:23:39 +00:00
ForgeChunkManager . tickets . get ( world ) . putAll ( ForgeVersion . MOD_ID , tickets . values ( ) ) ;
2012-12-02 05:38:32 +00:00
loadingCallback . ticketsLoaded ( ImmutableList . copyOf ( tickets . values ( ) ) , world ) ;
2012-09-28 03:32:21 +00:00
}
2012-09-22 05:43:54 +00:00
}
}
2012-12-02 05:38:32 +00:00
static void unloadWorld ( World world )
{
// World save fires before this event so the chunk loading info will be done
if ( ! ( world instanceof WorldServer ) )
{
return ;
}
forcedChunks . remove ( world ) ;
2017-01-14 19:01:27 +00:00
if ( dormantChunkCacheSize ! = 0 ) // only if in use
{
dormantChunkCache . remove ( world ) ;
}
2016-03-03 10:57:38 +00:00
// integrated server is shutting down
2018-06-21 19:37:32 +00:00
if ( ! ServerLifecycleHooks . getCurrentServer ( ) . isServerRunning ( ) )
2012-12-02 05:38:32 +00:00
{
playerTickets . clear ( ) ;
tickets . clear ( ) ;
}
}
2012-09-22 05:43:54 +00:00
/ * *
* 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 )
{
2017-06-23 05:33:11 +00:00
FMLLog . log . warn ( " Unable to register a callback for an unknown mod {} ({} : {}) " , mod , mod . getClass ( ) . getName ( ) , Integer . toHexString ( System . identityHashCode ( mod ) ) ) ;
2012-09-22 05:43:54 +00:00
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 ;
}
2012-11-27 23:28:59 +00:00
public static int getMaxTicketLengthFor ( String modId )
2012-09-22 05:43:54 +00:00
{
int allowedCount = ticketConstraints . containsKey ( modId ) & & overridesEnabled ? ticketConstraints . get ( modId ) : defaultMaxCount ;
return allowedCount ;
}
2012-11-27 23:28:59 +00:00
public static int getMaxChunkDepthFor ( String modId )
2012-09-22 05:43:54 +00:00
{
int allowedCount = chunkConstraints . containsKey ( modId ) & & overridesEnabled ? chunkConstraints . get ( modId ) : defaultMaxChunks ;
return allowedCount ;
}
2012-09-28 03:32:21 +00:00
2012-12-12 10:16:23 +00:00
public static int ticketCountAvailableFor ( String username )
2012-11-27 23:28:59 +00:00
{
2012-11-29 12:04:03 +00:00
return playerTicketLength - playerTickets . get ( username ) . size ( ) ;
2012-11-27 23:28:59 +00:00
}
2017-01-11 23:17:56 +00:00
@Nullable
2012-11-27 23:28:59 +00:00
public static Ticket requestPlayerTicket ( Object mod , String player , World world , Type type )
2012-09-28 03:32:21 +00:00
{
ModContainer mc = getContainer ( mod ) ;
if ( mc = = null )
{
2017-06-23 05:33:11 +00:00
FMLLog . log . error ( " Failed to locate the container for mod instance {} ({} : {}) " , mod , mod . getClass ( ) . getName ( ) , Integer . toHexString ( System . identityHashCode ( mod ) ) ) ;
2012-09-28 03:32:21 +00:00
return null ;
}
2012-11-27 23:28:59 +00:00
if ( playerTickets . get ( player ) . size ( ) > playerTicketLength )
2012-09-28 03:32:21 +00:00
{
2017-06-23 05:33:11 +00:00
FMLLog . log . warn ( " Unable to assign further chunkloading tickets to player {} (on behalf of mod {}) " , player , mc . getModId ( ) ) ;
2012-09-28 03:32:21 +00:00
return null ;
}
Ticket ticket = new Ticket ( mc . getModId ( ) , type , world , player ) ;
2012-11-27 23:28:59 +00:00
playerTickets . put ( player , ticket ) ;
2016-12-17 21:23:39 +00:00
tickets . get ( world ) . put ( ForgeVersion . MOD_ID , ticket ) ;
2012-09-28 03:32:21 +00:00
return ticket ;
}
2012-09-22 05:43:54 +00:00
/ * *
* 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
* /
2017-01-11 23:17:56 +00:00
@Nullable
2012-09-22 05:43:54 +00:00
public static Ticket requestTicket ( Object mod , World world , Type type )
{
ModContainer container = getContainer ( mod ) ;
if ( container = = null )
{
2017-06-23 05:33:11 +00:00
FMLLog . log . error ( " Failed to locate the container for mod instance {} ({} : {}) " , mod , mod . getClass ( ) . getName ( ) , Integer . toHexString ( System . identityHashCode ( mod ) ) ) ;
2012-09-22 05:43:54 +00:00
return null ;
}
String modId = container . getModId ( ) ;
if ( ! callbacks . containsKey ( modId ) )
{
2017-06-23 05:33:11 +00:00
FMLLog . log . fatal ( " The mod {} has attempted to request a ticket without a listener in place " , modId ) ;
2012-09-22 05:43:54 +00:00
throw new RuntimeException ( " Invalid ticket request " ) ;
}
2015-04-19 15:57:39 +00:00
int allowedCount = getMaxTicketLengthFor ( modId ) ;
2012-09-22 05:43:54 +00:00
2013-01-29 00:36:03 +00:00
if ( tickets . get ( world ) . get ( modId ) . size ( ) > = allowedCount )
2012-09-22 05:43:54 +00:00
{
2013-01-29 00:36:03 +00:00
if ( ! warnedMods . contains ( modId ) )
{
2017-06-23 05:33:11 +00:00
FMLLog . log . info ( " The mod {} has attempted to allocate a chunkloading ticket beyond it's currently allocated maximum: {} " , modId , allowedCount ) ;
2013-01-29 00:36:03 +00:00
warnedMods . add ( modId ) ;
}
2012-09-22 05:43:54 +00:00
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 ;
}
2012-09-28 03:32:21 +00:00
if ( ticket . isPlayerTicket ( ) ? ! playerTickets . containsValue ( ticket ) : ! tickets . get ( ticket . world ) . containsEntry ( ticket . modId , ticket ) )
2012-09-25 02:07:39 +00:00
{
return ;
}
2012-09-22 18:01:09 +00:00
if ( ticket . requestedChunks ! = null )
2012-09-22 05:43:54 +00:00
{
2016-05-18 10:29:10 +00:00
for ( ChunkPos chunk : ImmutableSet . copyOf ( ticket . requestedChunks ) )
2012-09-22 18:01:09 +00:00
{
unforceChunk ( ticket , chunk ) ;
}
2012-09-22 05:43:54 +00:00
}
2012-09-28 03:32:21 +00:00
if ( ticket . isPlayerTicket ( ) )
{
playerTickets . remove ( ticket . player , ticket ) ;
2016-12-17 21:23:39 +00:00
tickets . get ( ticket . world ) . remove ( ForgeVersion . MOD_ID , ticket ) ;
2012-09-28 03:32:21 +00:00
}
else
{
tickets . get ( ticket . world ) . remove ( ticket . modId , ticket ) ;
}
2012-09-22 05:43:54 +00:00
}
/ * *
* 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
* /
2016-05-18 10:29:10 +00:00
public static void forceChunk ( Ticket ticket , ChunkPos chunk )
2012-09-22 05:43:54 +00:00
{
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 " ) ;
}
2012-09-28 03:32:21 +00:00
if ( ticket . isPlayerTicket ( ) ? ! playerTickets . containsValue ( ticket ) : ! tickets . get ( ticket . world ) . containsEntry ( ticket . modId , ticket ) )
2012-09-25 02:07:39 +00:00
{
2017-06-23 05:33:11 +00:00
FMLLog . log . fatal ( " The mod {} attempted to force load a chunk with an invalid ticket. This is not permitted. " , ticket . modId ) ;
2012-09-25 02:07:39 +00:00
return ;
}
2012-09-22 05:43:54 +00:00
ticket . requestedChunks . add ( chunk ) ;
2012-11-19 00:28:02 +00:00
MinecraftForge . EVENT_BUS . post ( new ForceChunkEvent ( ticket , chunk ) ) ;
2016-05-18 10:29:10 +00:00
ImmutableSetMultimap < ChunkPos , Ticket > newMap = ImmutableSetMultimap . < ChunkPos , Ticket > builder ( ) . putAll ( forcedChunks . get ( ticket . world ) ) . put ( chunk , ticket ) . build ( ) ;
2012-10-14 16:30:33 +00:00
forcedChunks . put ( ticket . world , newMap ) ;
2012-09-22 05:43:54 +00:00
if ( ticket . maxDepth > 0 & & ticket . requestedChunks . size ( ) > ticket . maxDepth )
{
2016-05-18 10:29:10 +00:00
ChunkPos removed = ticket . requestedChunks . iterator ( ) . next ( ) ;
2012-09-22 05:43:54 +00:00
unforceChunk ( ticket , removed ) ;
}
}
2012-09-24 21:31:03 +00:00
/ * *
* Reorganize the internal chunk list so that the chunk supplied is at the * end * of the list
* This helps if you wish to guarantee a certain " automatic unload ordering " for the chunks
* in the ticket list
*
* @param ticket The ticket holding the chunk list
* @param chunk The chunk you wish to push to the end ( so that it would be unloaded last )
* /
2016-05-18 10:29:10 +00:00
public static void reorderChunk ( Ticket ticket , ChunkPos chunk )
2012-09-24 21:31:03 +00:00
{
if ( ticket = = null | | chunk = = null | | ! ticket . requestedChunks . contains ( chunk ) )
{
return ;
}
ticket . requestedChunks . remove ( chunk ) ;
ticket . requestedChunks . add ( chunk ) ;
}
2012-09-22 05:43:54 +00:00
/ * *
* 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
* /
2016-05-18 10:29:10 +00:00
public static void unforceChunk ( Ticket ticket , ChunkPos chunk )
2012-09-22 05:43:54 +00:00
{
if ( ticket = = null | | chunk = = null )
{
return ;
}
ticket . requestedChunks . remove ( chunk ) ;
2012-11-19 00:28:02 +00:00
MinecraftForge . EVENT_BUS . post ( new UnforceChunkEvent ( ticket , chunk ) ) ;
2016-05-18 10:29:10 +00:00
LinkedHashMultimap < ChunkPos , Ticket > copy = LinkedHashMultimap . create ( forcedChunks . get ( ticket . world ) ) ;
2012-10-14 16:30:33 +00:00
copy . remove ( chunk , ticket ) ;
2016-05-18 10:29:10 +00:00
ImmutableSetMultimap < ChunkPos , Ticket > newMap = ImmutableSetMultimap . copyOf ( copy ) ;
2012-10-14 16:30:33 +00:00
forcedChunks . put ( ticket . world , newMap ) ;
2012-09-22 05:43:54 +00:00
}
2012-09-28 03:32:21 +00:00
static void loadConfiguration ( )
2012-09-22 05:43:54 +00:00
{
2014-06-04 21:39:47 +00:00
ticketConstraints . clear ( ) ;
chunkConstraints . clear ( ) ;
2013-03-05 00:33:52 +00:00
for ( String mod : config . getCategoryNames ( ) )
2012-09-22 05:43:54 +00:00
{
2016-12-17 21:23:39 +00:00
if ( mod . equals ( ForgeVersion . MOD_ID ) | | mod . equals ( " defaults " ) )
2012-09-23 02:37:21 +00:00
{
2012-09-28 03:32:21 +00:00
continue ;
2012-09-23 02:37:21 +00:00
}
2012-09-28 03:32:21 +00:00
Property modTC = config . get ( mod , " maximumTicketCount " , 200 ) ;
Property modCPT = config . get ( mod , " maximumChunksPerTicket " , 25 ) ;
ticketConstraints . put ( mod , modTC . getInt ( 200 ) ) ;
chunkConstraints . put ( mod , modCPT . getInt ( 25 ) ) ;
2012-09-22 05:43:54 +00:00
}
2013-03-05 00:33:52 +00:00
if ( config . hasChanged ( ) )
{
config . save ( ) ;
}
2012-09-22 05:43:54 +00:00
}
/ * *
* The list of persistent chunks in the world . This set is immutable .
* @param world
2013-01-22 02:56:04 +00:00
* @return the list of persistent chunks in the world
2012-09-22 05:43:54 +00:00
* /
2016-05-18 10:29:10 +00:00
public static ImmutableSetMultimap < ChunkPos , Ticket > getPersistentChunksFor ( World world )
2012-09-22 05:43:54 +00:00
{
2017-10-02 22:17:37 +00:00
return forcedChunks . containsKey ( world ) ? forcedChunks . get ( world ) : ImmutableSetMultimap . of ( ) ;
2012-09-22 05:43:54 +00:00
}
static void saveWorld ( World world )
{
// only persist persistent worlds
2013-09-05 17:16:01 +00:00
if ( ! ( world instanceof WorldServer ) )
{
return ;
}
2012-09-22 05:43:54 +00:00
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 ) ;
2014-07-17 01:36:12 +00:00
if ( ticketSet = = null ) return ;
2012-09-22 05:43:54 +00:00
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 ( ) ;
ticket . setByte ( " Type " , ( byte ) tick . ticketType . ordinal ( ) ) ;
ticket . setByte ( " ChunkListDepth " , ( byte ) tick . maxDepth ) ;
2012-09-28 03:32:21 +00:00
if ( tick . isPlayerTicket ( ) )
{
ticket . setString ( " ModId " , tick . modId ) ;
ticket . setString ( " Player " , tick . player ) ;
}
2012-09-24 21:31:03 +00:00
if ( tick . modData ! = null )
{
2013-12-13 07:32:36 +00:00
ticket . setTag ( " ModData " , tick . modData ) ;
2012-09-24 21:31:03 +00:00
}
2013-09-03 19:52:58 +00:00
if ( tick . ticketType = = Type . ENTITY & & tick . entity ! = null & & tick . entity . writeToNBTOptional ( new NBTTagCompound ( ) ) )
2012-09-22 05:43:54 +00:00
{
2016-12-21 23:52:30 +00:00
ticket . setInteger ( " chunkX " , MathHelper . floor ( tick . entity . chunkCoordX ) ) ;
ticket . setInteger ( " chunkZ " , MathHelper . floor ( tick . entity . chunkCoordZ ) ) ;
2012-09-24 21:31:03 +00:00
ticket . setLong ( " PersistentIDMSB " , tick . entity . getPersistentID ( ) . getMostSignificantBits ( ) ) ;
ticket . setLong ( " PersistentIDLSB " , tick . entity . getPersistentID ( ) . getLeastSignificantBits ( ) ) ;
2012-09-25 23:12:10 +00:00
tickets . appendTag ( ticket ) ;
}
else if ( tick . ticketType ! = Type . ENTITY )
{
tickets . appendTag ( ticket ) ;
2012-09-22 05:43:54 +00:00
}
}
}
2018-02-27 03:49:56 +00:00
// Write the actual file on the IO thread rather than blocking the server thread
ThreadedFileIOBase . getThreadedIOInstance ( ) . queueIO ( ( ) - > {
try
{
CompressedStreamTools . write ( forcedChunkData , chunkLoaderData ) ;
}
catch ( IOException e )
{
FMLLog . log . warn ( " Unable to write forced chunk data to {} - chunkloading won't work " , chunkLoaderData . getAbsolutePath ( ) , e ) ;
}
return false ;
} ) ;
2012-09-22 05:43:54 +00:00
}
2012-09-22 16:27:14 +00:00
static void loadEntity ( Entity entity )
{
UUID id = entity . getPersistentID ( ) ;
Ticket tick = pendingEntities . get ( id ) ;
if ( tick ! = null )
{
tick . bindEntity ( entity ) ;
pendingEntities . remove ( id ) ;
}
}
2012-09-23 02:37:21 +00:00
public static void putDormantChunk ( long coords , Chunk chunk )
{
2017-01-14 19:01:27 +00:00
if ( dormantChunkCacheSize = = 0 ) return ; // Skip if we're not dormant caching chunks
2017-10-02 22:17:37 +00:00
Cache < Long , ChunkEntry > cache = dormantChunkCache . get ( chunk . getWorld ( ) ) ;
2012-09-28 21:03:49 +00:00
if ( cache ! = null )
{
2017-10-02 22:17:37 +00:00
cache . put ( coords , new ChunkEntry ( chunk ) ) ;
}
}
public static void storeChunkNBT ( Chunk chunk , NBTTagCompound nbt )
{
if ( dormantChunkCacheSize = = 0 ) return ;
Cache < Long , ChunkEntry > cache = dormantChunkCache . get ( chunk . getWorld ( ) ) ;
if ( cache = = null ) return ;
ChunkEntry entry = cache . getIfPresent ( ChunkPos . asLong ( chunk . x , chunk . z ) ) ;
if ( entry ! = null )
{
entry . nbt . setTag ( " Entities " , nbt . getTagList ( " Entities " , Constants . NBT . TAG_COMPOUND ) ) ;
entry . nbt . setTag ( " TileEntities " , nbt . getTagList ( " TileEntities " , Constants . NBT . TAG_COMPOUND ) ) ;
ClassInheritanceMultiMap < Entity > [ ] entityLists = chunk . getEntityLists ( ) ;
for ( int i = 0 ; i < entityLists . length ; + + i )
{
entityLists [ i ] = new ClassInheritanceMultiMap < > ( Entity . class ) ;
}
chunk . getTileEntityMap ( ) . clear ( ) ;
2012-09-28 21:03:49 +00:00
}
2012-09-23 02:37:21 +00:00
}
2017-01-11 23:17:56 +00:00
@Nullable
2012-09-28 21:03:49 +00:00
public static Chunk fetchDormantChunk ( long coords , World world )
2012-09-23 02:37:21 +00:00
{
2017-01-14 19:01:27 +00:00
if ( dormantChunkCacheSize = = 0 ) return null ; // Don't bother with maps at all if its never gonna get a response
2017-10-02 22:17:37 +00:00
Cache < Long , ChunkEntry > cache = dormantChunkCache . get ( world ) ;
if ( cache = = null ) return null ;
ChunkEntry entry = cache . getIfPresent ( coords ) ;
if ( entry = = null ) return null ;
loadChunkEntities ( entry . chunk , entry . nbt , world ) ;
2018-01-09 18:48:56 +00:00
cache . invalidate ( coords ) ;
2017-10-02 22:17:37 +00:00
return entry . chunk ;
}
private static void loadChunkEntities ( Chunk chunk , NBTTagCompound nbt , World world )
{
NBTTagList entities = nbt . getTagList ( " Entities " , Constants . NBT . TAG_COMPOUND ) ;
for ( int i = 0 ; i < entities . tagCount ( ) ; + + i )
2013-01-26 15:24:48 +00:00
{
2017-10-02 22:17:37 +00:00
AnvilChunkLoader . readChunkEntity ( entities . getCompoundTagAt ( i ) , world , chunk ) ;
chunk . setHasEntities ( true ) ;
2013-01-26 15:24:48 +00:00
}
2017-10-02 22:17:37 +00:00
NBTTagList tileEntities = nbt . getTagList ( " TileEntities " , Constants . NBT . TAG_COMPOUND ) ;
for ( int i = 0 ; i < tileEntities . tagCount ( ) ; + + i )
2013-01-26 02:21:20 +00:00
{
2017-10-02 22:17:37 +00:00
TileEntity tileEntity = TileEntity . create ( world , tileEntities . getCompoundTagAt ( i ) ) ;
if ( tileEntity ! = null ) chunk . addTileEntity ( tileEntity ) ;
2013-01-26 02:21:20 +00:00
}
2012-09-23 02:37:21 +00:00
}
2012-09-28 03:32:21 +00:00
static void captureConfig ( File configDir )
{
cfgFile = new File ( configDir , " forgeChunkLoading.cfg " ) ;
config = new Configuration ( cfgFile , true ) ;
try
{
config . load ( ) ;
}
catch ( Exception e )
{
File dest = new File ( cfgFile . getParentFile ( ) , " forgeChunkLoading.cfg.bak " ) ;
if ( dest . exists ( ) )
{
dest . delete ( ) ;
}
cfgFile . renameTo ( dest ) ;
2017-06-23 05:33:11 +00:00
FMLLog . log . error ( " A critical error occurred reading the forgeChunkLoading.cfg file, defaults will be used - the invalid file is backed up at forgeChunkLoading.cfg.bak " , e ) ;
2012-09-28 03:32:21 +00:00
}
2014-06-04 21:39:47 +00:00
syncConfigDefaults ( ) ;
}
2014-07-17 01:36:12 +00:00
2014-06-04 21:39:47 +00:00
/ * *
* Synchronizes the local fields with the values in the Configuration object .
* /
public static void syncConfigDefaults ( )
{
// By adding a property order list we are defining the order that the properties will appear both in the config file and on the GUIs.
// Property order lists are defined per-ConfigCategory.
List < String > propOrder = new ArrayList < String > ( ) ;
config . setCategoryComment ( " defaults " , " Default configuration for forge chunk loading control " )
. setCategoryRequiresWorldRestart ( " defaults " , true ) ;
2014-07-17 01:36:12 +00:00
2014-06-04 21:39:47 +00:00
Property temp = config . get ( " defaults " , " enabled " , true ) ;
2016-03-24 10:14:39 +00:00
temp . setComment ( " Are mod overrides enabled? " ) ;
2014-06-04 21:39:47 +00:00
temp . setLanguageKey ( " forge.configgui.enableModOverrides " ) ;
overridesEnabled = temp . getBoolean ( true ) ;
propOrder . add ( " enabled " ) ;
temp = config . get ( " defaults " , " maximumChunksPerTicket " , 25 ) ;
2016-03-24 10:14:39 +00:00
temp . setComment ( " 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. " ) ;
2014-06-04 21:39:47 +00:00
temp . setLanguageKey ( " forge.configgui.maximumChunksPerTicket " ) ;
temp . setMinValue ( 0 ) ;
defaultMaxChunks = temp . getInt ( 25 ) ;
propOrder . add ( " maximumChunksPerTicket " ) ;
2014-07-17 01:36:12 +00:00
2014-06-04 21:39:47 +00:00
temp = config . get ( " defaults " , " maximumTicketCount " , 200 ) ;
2016-03-24 10:14:39 +00:00
temp . setComment ( " 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. " ) ;
2014-06-04 21:39:47 +00:00
temp . setLanguageKey ( " forge.configgui.maximumTicketCount " ) ;
temp . setMinValue ( 0 ) ;
defaultMaxCount = temp . getInt ( 200 ) ;
propOrder . add ( " maximumTicketCount " ) ;
temp = config . get ( " defaults " , " playerTicketCount " , 500 ) ;
2016-03-24 10:14:39 +00:00
temp . setComment ( " The number of tickets a player can be assigned instead of a mod. This is shared across all mods and it is up to the mods to use it. " ) ;
2014-06-04 21:39:47 +00:00
temp . setLanguageKey ( " forge.configgui.playerTicketCount " ) ;
temp . setMinValue ( 0 ) ;
playerTicketLength = temp . getInt ( 500 ) ;
propOrder . add ( " playerTicketCount " ) ;
temp = config . get ( " defaults " , " dormantChunkCacheSize " , 0 ) ;
2016-03-24 10:14:39 +00:00
temp . setComment ( " Unloaded chunks can first be kept in a dormant cache for quicker \ n " +
" loading times. Specify the size (in chunks) of that cache here " ) ;
2014-06-04 21:39:47 +00:00
temp . setLanguageKey ( " forge.configgui.dormantChunkCacheSize " ) ;
temp . setMinValue ( 0 ) ;
dormantChunkCacheSize = temp . getInt ( 0 ) ;
propOrder . add ( " dormantChunkCacheSize " ) ;
2017-06-23 05:33:11 +00:00
FMLLog . log . info ( " Configured a dormant chunk cache size of {} " , temp . getInt ( 0 ) ) ;
2014-07-17 01:36:12 +00:00
2018-01-18 20:44:14 +00:00
temp = config . get ( " defaults " , " asyncChunkLoading " , true ) ;
temp . setComment ( " Load chunks asynchronously for players, reducing load on the server thread. \ n " +
" Can be disabled to help troubleshoot chunk loading issues. " ) ;
temp . setLanguageKey ( " forge.configgui.asyncChunkLoading " ) ;
asyncChunkLoading = temp . getBoolean ( true ) ;
propOrder . add ( " asyncChunkLoading " ) ;
2014-06-04 21:39:47 +00:00
config . setCategoryPropertyOrder ( " defaults " , propOrder ) ;
2012-09-28 03:32:21 +00:00
2016-12-17 21:23:39 +00:00
config . addCustomCategoryComment ( ForgeVersion . MOD_ID , " Sample mod specific control section. \ n " +
2012-09-28 03:32:21 +00:00
" 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 " ) ;
2016-12-17 21:23:39 +00:00
temp = config . get ( ForgeVersion . MOD_ID , " maximumTicketCount " , 200 ) ;
2016-03-24 10:14:39 +00:00
temp . setComment ( " Maximum ticket count for the mod. Zero disables chunkloading capabilities. " ) ;
2016-12-17 21:23:39 +00:00
temp = config . get ( ForgeVersion . MOD_ID , " maximumChunksPerTicket " , 25 ) ;
2016-03-24 10:14:39 +00:00
temp . setComment ( " Maximum chunks per ticket for the mod. " ) ;
2013-03-05 00:33:52 +00:00
for ( String mod : config . getCategoryNames ( ) )
2012-09-28 03:32:21 +00:00
{
2016-12-17 21:23:39 +00:00
if ( mod . equals ( ForgeVersion . MOD_ID ) | | mod . equals ( " defaults " ) )
2012-09-28 03:32:21 +00:00
{
continue ;
}
2014-06-04 21:39:47 +00:00
config . get ( mod , " maximumTicketCount " , 200 ) . setLanguageKey ( " forge.configgui.maximumTicketCount " ) . setMinValue ( 0 ) ;
config . get ( mod , " maximumChunksPerTicket " , 25 ) . setLanguageKey ( " forge.configgui.maximumChunksPerTicket " ) . setMinValue ( 0 ) ;
}
2014-07-17 01:36:12 +00:00
2014-06-04 21:39:47 +00:00
if ( config . hasChanged ( ) )
{
config . save ( ) ;
2012-09-28 03:32:21 +00:00
}
}
2014-07-17 01:36:12 +00:00
2014-06-04 21:39:47 +00:00
public static Configuration getConfig ( )
{
return config ;
}
2014-07-17 01:36:12 +00:00
2014-06-04 21:39:47 +00:00
public static ConfigCategory getDefaultsCategory ( )
{
return config . getCategory ( " defaults " ) ;
}
2014-07-17 01:36:12 +00:00
2014-06-04 21:39:47 +00:00
public static List < ConfigCategory > getModCategories ( )
{
List < ConfigCategory > list = new ArrayList < ConfigCategory > ( ) ;
for ( String mod : config . getCategoryNames ( ) )
{
2016-12-17 21:23:39 +00:00
if ( mod . equals ( ForgeVersion . MOD_ID ) | | mod . equals ( " defaults " ) )
2014-06-04 21:39:47 +00:00
{
continue ;
}
list . add ( config . getCategory ( mod ) ) ;
}
return list ;
}
2012-09-28 03:32:21 +00:00
2017-01-11 23:17:56 +00:00
@Nullable
2013-03-05 00:33:52 +00:00
public static ConfigCategory getConfigFor ( Object mod )
2012-09-28 03:32:21 +00:00
{
ModContainer container = getContainer ( mod ) ;
if ( container ! = null )
{
2013-03-05 00:33:52 +00:00
return config . getCategory ( container . getModId ( ) ) ;
2012-09-28 03:32:21 +00:00
}
return null ;
}
public static void addConfigProperty ( Object mod , String propertyName , String value , Property . Type type )
{
ModContainer container = getContainer ( mod ) ;
if ( container ! = null )
{
2013-03-05 00:33:52 +00:00
ConfigCategory cat = config . getCategory ( container . getModId ( ) ) ;
2014-06-04 21:39:47 +00:00
Property prop = new Property ( propertyName , value , type ) . setLanguageKey ( " forge.configgui. " + propertyName ) ;
if ( type = = Property . Type . INTEGER )
{
prop . setMinValue ( 0 ) ;
}
cat . put ( propertyName , prop ) ;
2012-09-28 03:32:21 +00:00
}
}
2012-09-22 05:43:54 +00:00
}