2020-04-24 03:25:58 +02:00
package net.minestom.server.instance ;
2019-08-24 20:34:01 +02:00
2022-04-08 07:19:52 +02:00
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps ;
2022-01-05 09:01:21 +01:00
import net.minestom.server.MinecraftServer ;
2021-07-08 18:56:40 +02:00
import net.minestom.server.coordinate.Point ;
import net.minestom.server.coordinate.Vec ;
2021-11-01 18:04:00 +01:00
import net.minestom.server.entity.Entity ;
2020-04-24 03:25:58 +02:00
import net.minestom.server.entity.Player ;
2021-06-04 03:48:51 +02:00
import net.minestom.server.event.EventDispatcher ;
2020-07-01 15:51:00 +02:00
import net.minestom.server.event.instance.InstanceChunkLoadEvent ;
import net.minestom.server.event.instance.InstanceChunkUnloadEvent ;
2020-05-07 15:46:21 +02:00
import net.minestom.server.event.player.PlayerBlockBreakEvent ;
2020-05-04 21:06:13 +02:00
import net.minestom.server.instance.block.Block ;
2023-01-17 00:57:18 +01:00
import net.minestom.server.instance.block.BlockFace ;
2021-05-29 01:05:12 +02:00
import net.minestom.server.instance.block.BlockHandler ;
2020-04-24 03:25:58 +02:00
import net.minestom.server.instance.block.rule.BlockPlacementRule ;
2022-04-08 07:19:52 +02:00
import net.minestom.server.instance.generator.Generator ;
import net.minestom.server.instance.palette.Palette ;
2020-06-30 12:59:37 +02:00
import net.minestom.server.network.packet.server.play.BlockChangePacket ;
2022-02-10 16:48:59 +01:00
import net.minestom.server.network.packet.server.play.BlockEntityDataPacket ;
2020-12-14 01:19:35 +01:00
import net.minestom.server.network.packet.server.play.EffectPacket ;
2020-06-30 12:59:37 +02:00
import net.minestom.server.network.packet.server.play.UnloadChunkPacket ;
2024-03-21 20:29:03 +01:00
import net.minestom.server.thread.TickSchedulerThread ;
2023-07-04 23:44:30 +02:00
import net.minestom.server.utils.NamespaceID ;
2020-12-14 06:06:28 +01:00
import net.minestom.server.utils.PacketUtils ;
2021-08-15 05:58:53 +02:00
import net.minestom.server.utils.async.AsyncUtils ;
2022-02-10 16:48:59 +01:00
import net.minestom.server.utils.block.BlockUtils ;
2022-03-17 00:14:12 +01:00
import net.minestom.server.utils.chunk.ChunkCache ;
2020-10-10 05:50:49 +02:00
import net.minestom.server.utils.chunk.ChunkSupplier ;
2022-04-08 07:19:52 +02:00
import net.minestom.server.utils.chunk.ChunkUtils ;
2020-05-27 20:30:13 +02:00
import net.minestom.server.utils.validate.Check ;
2020-07-13 14:12:21 +02:00
import net.minestom.server.world.DimensionType ;
2021-08-28 13:06:09 +02:00
import org.jetbrains.annotations.ApiStatus ;
2020-10-24 10:46:23 +02:00
import org.jetbrains.annotations.NotNull ;
import org.jetbrains.annotations.Nullable ;
2022-02-10 16:48:59 +01:00
import org.jglrxavpok.hephaistos.nbt.NBTCompound ;
2023-08-05 19:10:37 +02:00
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
2021-12-16 05:29:11 +01:00
import space.vectrix.flare.fastutil.Long2ObjectSyncMap ;
2019-08-24 20:34:01 +02:00
import java.util.* ;
2021-07-11 02:54:02 +02:00
import java.util.concurrent.CompletableFuture ;
2022-04-08 07:19:52 +02:00
import java.util.concurrent.ConcurrentHashMap ;
2019-08-24 20:34:01 +02:00
import java.util.concurrent.CopyOnWriteArrayList ;
2022-04-08 07:19:52 +02:00
import java.util.concurrent.ForkJoinPool ;
2020-05-04 21:06:13 +02:00
import java.util.concurrent.locks.Lock ;
2021-08-15 05:58:53 +02:00
import java.util.concurrent.locks.ReentrantLock ;
2024-03-21 20:29:03 +01:00
import java.util.function.Consumer ;
2021-08-15 05:58:53 +02:00
import java.util.function.Supplier ;
2019-08-24 20:34:01 +02:00
2022-03-01 00:35:55 +01:00
import static net.minestom.server.utils.chunk.ChunkUtils.* ;
2019-08-24 20:34:01 +02:00
/ * *
* InstanceContainer is an instance that contains chunks in contrary to SharedInstance .
* /
2019-08-24 21:41:43 +02:00
public class InstanceContainer extends Instance {
2023-08-05 19:10:37 +02:00
private static final Logger LOGGER = LoggerFactory . getLogger ( InstanceContainer . class ) ;
2022-10-27 15:52:59 +02:00
private static final AnvilLoader DEFAULT_LOADER = new AnvilLoader ( " world " ) ;
2019-08-24 20:34:01 +02:00
2023-07-16 14:33:09 +02:00
private static final BlockFace [ ] BLOCK_UPDATE_FACES = new BlockFace [ ] {
BlockFace . WEST , BlockFace . EAST , BlockFace . NORTH , BlockFace . SOUTH , BlockFace . BOTTOM , BlockFace . TOP
} ;
2020-10-10 13:46:41 +02:00
// the shared instances assigned to this instance
2020-10-15 08:48:13 +02:00
private final List < SharedInstance > sharedInstances = new CopyOnWriteArrayList < > ( ) ;
2019-08-24 20:34:01 +02:00
2020-10-10 13:46:41 +02:00
// the chunk generator used, can be null
2022-04-08 07:19:52 +02:00
private volatile Generator generator ;
2020-10-10 13:46:41 +02:00
// (chunk index -> chunk) map, contains all the chunks in the instance
2021-07-24 11:44:50 +02:00
// used as a monitor when access is required
2021-12-16 05:29:11 +01:00
private final Long2ObjectSyncMap < Chunk > chunks = Long2ObjectSyncMap . hashmap ( ) ;
2022-04-11 22:38:37 +02:00
private final Map < Long , CompletableFuture < Chunk > > loadingChunks = new ConcurrentHashMap < > ( ) ;
2019-08-24 20:34:01 +02:00
2021-08-15 05:58:53 +02:00
private final Lock changingBlockLock = new ReentrantLock ( ) ;
2021-07-08 18:56:40 +02:00
private final Map < Point , Block > currentlyChangingBlocks = new HashMap < > ( ) ;
2020-10-10 13:46:41 +02:00
// the chunk loader, used when trying to load/save a chunk from another source
2020-06-30 18:07:47 +02:00
private IChunkLoader chunkLoader ;
2020-05-04 21:06:13 +02:00
2020-10-10 13:46:41 +02:00
// used to automatically enable the chunk loading or not
2021-03-07 16:09:28 +01:00
private boolean autoChunkLoad = true ;
2019-08-25 20:03:43 +02:00
2020-10-10 13:46:41 +02:00
// used to supply a new chunk object at a position when requested
2020-10-10 05:50:49 +02:00
private ChunkSupplier chunkSupplier ;
2020-08-15 16:01:47 +02:00
2020-10-31 19:22:23 +01:00
// Fields for instance copy
protected InstanceContainer srcInstance ; // only present if this instance has been created using a copy
private long lastBlockChangeTime ; // Time at which the last block change happened (#setBlock)
2023-07-04 23:44:30 +02:00
public InstanceContainer ( @NotNull UUID uniqueId , @NotNull DimensionType dimensionType ) {
this ( uniqueId , dimensionType , null , dimensionType . getName ( ) ) ;
}
public InstanceContainer ( @NotNull UUID uniqueId , @NotNull DimensionType dimensionType , @NotNull NamespaceID dimensionName ) {
this ( uniqueId , dimensionType , null , dimensionName ) ;
}
2021-08-28 13:06:09 +02:00
@ApiStatus.Experimental
public InstanceContainer ( @NotNull UUID uniqueId , @NotNull DimensionType dimensionType , @Nullable IChunkLoader loader ) {
2023-07-04 23:44:30 +02:00
this ( uniqueId , dimensionType , loader , dimensionType . getName ( ) ) ;
}
@ApiStatus.Experimental
public InstanceContainer ( @NotNull UUID uniqueId , @NotNull DimensionType dimensionType , @Nullable IChunkLoader loader , @NotNull NamespaceID dimensionName ) {
super ( uniqueId , dimensionType , dimensionName ) ;
2020-10-15 08:48:13 +02:00
setChunkSupplier ( DynamicChunk : : new ) ;
2022-10-27 15:52:59 +02:00
setChunkLoader ( Objects . requireNonNullElse ( loader , DEFAULT_LOADER ) ) ;
2021-08-09 01:16:51 +02:00
this . chunkLoader . loadInstance ( this ) ;
2019-08-24 20:34:01 +02:00
}
@Override
2023-07-16 13:48:05 +02:00
public void setBlock ( int x , int y , int z , @NotNull Block block , boolean doBlockUpdates ) {
2022-03-01 00:35:55 +01:00
Chunk chunk = getChunkAt ( x , z ) ;
if ( chunk = = null ) {
2020-10-31 04:38:53 +01:00
Check . stateCondition ( ! hasEnabledAutoChunkLoad ( ) ,
" Tried to set a block to an unloaded chunk with auto chunk load disabled " ) ;
2022-03-01 00:35:55 +01:00
chunk = loadChunk ( getChunkCoordinate ( x ) , getChunkCoordinate ( z ) ) . join ( ) ;
2020-10-01 19:57:19 +02:00
}
2023-07-16 14:33:09 +02:00
if ( isLoaded ( chunk ) ) UNSAFE_setBlock ( chunk , x , y , z , block , null , null , doBlockUpdates , 0 ) ;
2020-10-01 19:57:19 +02:00
}
/ * *
2021-01-28 16:19:06 +01:00
* Sets a block at the specified position .
2020-10-01 19:57:19 +02:00
* < p >
2021-01-28 16:19:06 +01:00
* Unsafe because the method is not synchronized and it does not verify if the chunk is loaded or not .
2020-10-01 19:57:19 +02:00
*
2021-05-23 00:28:31 +02:00
* @param chunk the { @link Chunk } which should be loaded
* @param x the block X
* @param y the block Y
* @param z the block Z
* @param block the block to place
2020-10-01 19:57:19 +02:00
* /
2021-08-15 17:50:38 +02:00
private synchronized void UNSAFE_setBlock ( @NotNull Chunk chunk , int x , int y , int z , @NotNull Block block ,
2023-07-16 13:48:05 +02:00
@Nullable BlockHandler . Placement placement , @Nullable BlockHandler . Destroy destroy ,
2023-07-16 14:33:09 +02:00
boolean doBlockUpdates , int updateDistance ) {
2021-08-15 05:58:53 +02:00
if ( chunk . isReadOnly ( ) ) return ;
2023-08-05 19:10:37 +02:00
if ( y > = getDimensionType ( ) . getMaxY ( ) | | y < getDimensionType ( ) . getMinY ( ) ) {
LOGGER . warn ( " tried to set a block outside the world bounds, should be within [{}, {}): {} " , getDimensionType ( ) . getMinY ( ) , getDimensionType ( ) . getMaxY ( ) , y ) ;
return ;
}
2019-08-24 20:34:01 +02:00
synchronized ( chunk ) {
2020-10-31 19:22:23 +01:00
// Refresh the last block change time
this . lastBlockChangeTime = System . currentTimeMillis ( ) ;
2021-07-08 18:56:40 +02:00
final Vec blockPosition = new Vec ( x , y , z ) ;
2021-05-24 22:00:18 +02:00
if ( isAlreadyChanged ( blockPosition , block ) ) { // do NOT change the block again.
2020-05-04 21:06:13 +02:00
// Avoids StackOverflowExceptions when onDestroy tries to destroy the block itself
// This can happen with nether portals which break the entire frame when a portal block is broken
return ;
}
2021-08-15 05:58:53 +02:00
this . currentlyChangingBlocks . put ( blockPosition , block ) ;
2020-08-20 02:42:27 +02:00
2020-04-26 20:41:58 +02:00
// Change id based on neighbors
2022-01-19 21:41:25 +01:00
final BlockPlacementRule blockPlacementRule = MinecraftServer . getBlockManager ( ) . getBlockPlacementRule ( block ) ;
2023-07-16 14:33:09 +02:00
if ( placement ! = null & & blockPlacementRule ! = null & & doBlockUpdates ) {
BlockPlacementRule . PlacementState rulePlacement ;
if ( placement instanceof BlockHandler . PlayerPlacement pp ) {
rulePlacement = new BlockPlacementRule . PlacementState (
this , block , pp . getBlockFace ( ) , blockPosition ,
new Vec ( pp . getCursorX ( ) , pp . getCursorY ( ) , pp . getCursorZ ( ) ) ,
pp . getPlayer ( ) . getPosition ( ) ,
pp . getPlayer ( ) . getItemInHand ( pp . getHand ( ) ) . meta ( ) ,
pp . getPlayer ( ) . isSneaking ( )
) ;
} else {
rulePlacement = new BlockPlacementRule . PlacementState (
this , block , null , blockPosition ,
null , null , null ,
false
) ;
}
block = blockPlacementRule . blockPlace ( rulePlacement ) ;
if ( block = = null ) block = Block . AIR ;
2021-08-15 05:58:53 +02:00
}
2020-04-12 10:24:25 +02:00
2020-09-12 12:45:37 +02:00
// Set the block
2023-07-02 21:34:54 +02:00
chunk . setBlock ( x , y , z , block , placement , destroy ) ;
2020-09-12 12:45:37 +02:00
2020-04-26 20:41:58 +02:00
// Refresh neighbors since a new block has been placed
2023-07-16 13:48:05 +02:00
if ( doBlockUpdates ) {
2023-07-16 14:33:09 +02:00
executeNeighboursBlockPlacementRule ( blockPosition , updateDistance ) ;
2023-07-16 13:48:05 +02:00
}
2019-09-21 20:42:27 +02:00
2020-04-28 23:38:44 +02:00
// Refresh player chunk block
2022-02-10 16:48:59 +01:00
{
chunk . sendPacketToViewers ( new BlockChangePacket ( blockPosition , block . stateId ( ) ) ) ;
var registry = block . registry ( ) ;
if ( registry . isBlockEntity ( ) ) {
final NBTCompound data = BlockUtils . extractClientNbt ( block ) ;
chunk . sendPacketToViewers ( new BlockEntityDataPacket ( blockPosition , registry . blockEntityId ( ) , data ) ) ;
}
}
2019-08-24 20:34:01 +02:00
}
}
2021-06-22 23:51:01 +02:00
@Override
2023-07-16 13:48:05 +02:00
public boolean placeBlock ( @NotNull BlockHandler . Placement placement , boolean doBlockUpdates ) {
2021-08-30 15:52:07 +02:00
final Point blockPosition = placement . getBlockPosition ( ) ;
2021-06-22 23:51:01 +02:00
final Chunk chunk = getChunkAt ( blockPosition ) ;
2022-03-01 00:35:55 +01:00
if ( ! isLoaded ( chunk ) ) return false ;
2021-08-30 15:52:07 +02:00
UNSAFE_setBlock ( chunk , blockPosition . blockX ( ) , blockPosition . blockY ( ) , blockPosition . blockZ ( ) ,
2023-07-16 14:33:09 +02:00
placement . getBlock ( ) , placement , null , doBlockUpdates , 0 ) ;
2021-06-22 23:51:01 +02:00
return true ;
2020-04-12 10:24:25 +02:00
}
2019-08-24 20:34:01 +02:00
@Override
2023-07-16 13:48:05 +02:00
public boolean breakBlock ( @NotNull Player player , @NotNull Point blockPosition , @NotNull BlockFace blockFace , boolean doBlockUpdates ) {
2020-07-23 07:36:49 +02:00
final Chunk chunk = getChunkAt ( blockPosition ) ;
2020-10-26 01:30:32 +01:00
Check . notNull ( chunk , " You cannot break blocks in a null chunk! " ) ;
2021-08-15 05:58:53 +02:00
if ( chunk . isReadOnly ( ) ) return false ;
2022-03-01 00:35:55 +01:00
if ( ! isLoaded ( chunk ) ) return false ;
2020-06-02 14:43:31 +02:00
2021-08-15 05:58:53 +02:00
final Block block = getBlock ( blockPosition ) ;
2021-07-05 11:38:33 +02:00
final int x = blockPosition . blockX ( ) ;
final int y = blockPosition . blockY ( ) ;
final int z = blockPosition . blockZ ( ) ;
2021-05-24 22:00:18 +02:00
if ( block . isAir ( ) ) {
2021-08-15 05:58:53 +02:00
// The player probably have a wrong version of this chunk section, send it
2021-06-23 15:25:05 +02:00
chunk . sendChunk ( player ) ;
2020-05-27 20:30:13 +02:00
return false ;
2019-08-25 20:03:43 +02:00
}
2023-01-17 00:57:18 +01:00
PlayerBlockBreakEvent blockBreakEvent = new PlayerBlockBreakEvent ( player , block , Block . AIR , blockPosition , blockFace ) ;
2021-06-14 15:26:16 +02:00
EventDispatcher . call ( blockBreakEvent ) ;
2020-09-25 21:50:50 +02:00
final boolean allowed = ! blockBreakEvent . isCancelled ( ) ;
if ( allowed ) {
2020-04-15 13:20:28 +02:00
// Break or change the broken block based on event result
2021-05-24 22:00:18 +02:00
final Block resultBlock = blockBreakEvent . getResultBlock ( ) ;
2021-06-23 20:18:34 +02:00
UNSAFE_setBlock ( chunk , x , y , z , resultBlock , null ,
2023-07-16 14:33:09 +02:00
new BlockHandler . PlayerDestroy ( block , this , blockPosition , player ) , doBlockUpdates , 0 ) ;
2020-12-14 01:19:35 +01:00
// Send the block break effect packet
2021-07-15 18:26:02 +02:00
PacketUtils . sendGroupedPacket ( chunk . getViewers ( ) ,
2022-04-27 16:14:54 +02:00
new EffectPacket ( 2001 /*Block break + block break sound*/ , blockPosition , block . stateId ( ) , false ) ,
2021-07-15 18:26:02 +02:00
// Prevent the block breaker to play the particles and sound two times
( viewer ) - > ! viewer . equals ( player ) ) ;
2019-08-24 20:34:01 +02:00
}
2020-09-25 21:50:50 +02:00
return allowed ;
2019-08-24 20:34:01 +02:00
}
@Override
2021-07-11 03:26:08 +02:00
public @NotNull CompletableFuture < Chunk > loadChunk ( int chunkX , int chunkZ ) {
2021-08-15 05:58:53 +02:00
return loadOrRetrieve ( chunkX , chunkZ , ( ) - > retrieveChunk ( chunkX , chunkZ ) ) ;
2019-08-24 20:34:01 +02:00
}
2019-08-25 20:03:43 +02:00
@Override
2021-07-11 02:54:02 +02:00
public @NotNull CompletableFuture < Chunk > loadOptionalChunk ( int chunkX , int chunkZ ) {
2021-08-15 05:58:53 +02:00
return loadOrRetrieve ( chunkX , chunkZ , ( ) - > hasEnabledAutoChunkLoad ( ) ? retrieveChunk ( chunkX , chunkZ ) : AsyncUtils . empty ( ) ) ;
2019-08-25 20:03:43 +02:00
}
2020-04-20 23:43:09 +02:00
@Override
2021-08-14 16:37:19 +02:00
public synchronized void unloadChunk ( @NotNull Chunk chunk ) {
2022-03-01 00:35:55 +01:00
if ( ! isLoaded ( chunk ) ) return ;
2021-08-14 16:37:19 +02:00
final int chunkX = chunk . getChunkX ( ) ;
final int chunkZ = chunk . getChunkZ ( ) ;
chunk . sendPacketToViewers ( new UnloadChunkPacket ( chunkX , chunkZ ) ) ;
2021-09-07 18:24:24 +02:00
EventDispatcher . call ( new InstanceChunkUnloadEvent ( this , chunk ) ) ;
2021-08-14 16:37:19 +02:00
// Remove all entities in chunk
2021-12-23 23:44:22 +01:00
getEntityTracker ( ) . chunkEntities ( chunkX , chunkZ , EntityTracker . Target . ENTITIES ) . forEach ( Entity : : remove ) ;
2021-08-14 16:37:19 +02:00
// Clear cache
2022-03-01 00:35:55 +01:00
this . chunks . remove ( getChunkIndex ( chunkX , chunkZ ) ) ;
2021-08-14 16:37:19 +02:00
chunk . unload ( ) ;
2022-10-27 15:52:59 +02:00
if ( chunkLoader ! = null ) {
2022-08-05 21:05:23 +02:00
chunkLoader . unloadChunk ( chunk ) ;
}
2022-01-05 09:01:21 +01:00
var dispatcher = MinecraftServer . process ( ) . dispatcher ( ) ;
dispatcher . deletePartition ( chunk ) ;
2020-04-20 23:43:09 +02:00
}
2019-08-24 20:34:01 +02:00
@Override
public Chunk getChunk ( int chunkX , int chunkZ ) {
2022-03-01 00:35:55 +01:00
return chunks . get ( getChunkIndex ( chunkX , chunkZ ) ) ;
2019-08-24 20:34:01 +02:00
}
2021-08-09 01:16:51 +02:00
@Override
public @NotNull CompletableFuture < Void > saveInstance ( ) {
return chunkLoader . saveInstance ( this ) ;
}
2019-08-24 20:34:01 +02:00
@Override
2021-07-11 03:26:08 +02:00
public @NotNull CompletableFuture < Void > saveChunkToStorage ( @NotNull Chunk chunk ) {
2021-07-11 02:54:02 +02:00
return chunkLoader . saveChunk ( chunk ) ;
2020-02-09 15:34:09 +01:00
}
@Override
2021-07-11 03:26:08 +02:00
public @NotNull CompletableFuture < Void > saveChunksToStorage ( ) {
2021-07-24 11:44:50 +02:00
return chunkLoader . saveChunks ( getChunks ( ) ) ;
2019-08-24 20:34:01 +02:00
}
2021-07-11 03:26:08 +02:00
protected @NotNull CompletableFuture < @NotNull Chunk > retrieveChunk ( int chunkX , int chunkZ ) {
2021-07-11 02:54:02 +02:00
CompletableFuture < Chunk > completableFuture = new CompletableFuture < > ( ) ;
2022-03-01 00:35:55 +01:00
final long index = getChunkIndex ( chunkX , chunkZ ) ;
final CompletableFuture < Chunk > prev = loadingChunks . putIfAbsent ( index , completableFuture ) ;
if ( prev ! = null ) return prev ;
2021-08-15 05:58:53 +02:00
final IChunkLoader loader = chunkLoader ;
2024-03-21 20:29:03 +01:00
final Consumer < Chunk > postLoadCallback = chunk - > {
cacheChunk ( chunk ) ;
chunk . onLoad ( ) ;
EventDispatcher . call ( new InstanceChunkLoadEvent ( this , chunk ) ) ;
final CompletableFuture < Chunk > future = this . loadingChunks . remove ( index ) ;
assert future = = completableFuture : " Invalid future: " + future ;
completableFuture . complete ( chunk ) ;
} ;
2021-08-15 05:58:53 +02:00
final Runnable retriever = ( ) - > loader . loadChunk ( this , chunkX , chunkZ )
2022-03-01 00:35:55 +01:00
. thenCompose ( chunk - > {
if ( chunk ! = null ) {
// Chunk has been loaded from storage
return CompletableFuture . completedFuture ( chunk ) ;
} else {
// Loader couldn't load the chunk, generate it
return createChunk ( chunkX , chunkZ ) ;
}
} )
2021-08-22 16:44:24 +02:00
// cache the retrieved chunk
2022-03-01 00:35:55 +01:00
. thenAccept ( chunk - > {
2024-03-21 20:29:03 +01:00
if ( Thread . currentThread ( ) instanceof TickSchedulerThread ) {
postLoadCallback . accept ( chunk ) ; // Already running in instance tick thread
} else {
scheduleNextTick ( ignored - > postLoadCallback . accept ( chunk ) ) ;
}
2022-04-08 07:19:52 +02:00
} )
. exceptionally ( throwable - > {
MinecraftServer . getExceptionManager ( ) . handleException ( throwable ) ;
return null ;
2021-08-22 16:44:24 +02:00
} ) ;
2021-08-15 05:58:53 +02:00
if ( loader . supportsParallelLoading ( ) ) {
CompletableFuture . runAsync ( retriever ) ;
2021-07-11 03:14:17 +02:00
} else {
2021-08-15 05:58:53 +02:00
retriever . run ( ) ;
2021-07-11 03:14:17 +02:00
}
2021-07-11 02:54:02 +02:00
return completableFuture ;
2019-08-24 20:34:01 +02:00
}
2022-04-08 07:19:52 +02:00
Map < Long , List < GeneratorImpl . SectionModifierImpl > > generationForks = new ConcurrentHashMap < > ( ) ;
2021-07-11 03:26:08 +02:00
protected @NotNull CompletableFuture < @NotNull Chunk > createChunk ( int chunkX , int chunkZ ) {
2021-11-06 06:24:45 +01:00
final Chunk chunk = chunkSupplier . createChunk ( this , chunkX , chunkZ ) ;
2020-10-10 05:54:07 +02:00
Check . notNull ( chunk , " Chunks supplied by a ChunkSupplier cannot be null. " ) ;
2022-04-08 07:19:52 +02:00
Generator generator = generator ( ) ;
2021-08-15 05:58:53 +02:00
if ( generator ! = null & & chunk . shouldGenerate ( ) ) {
2022-04-08 07:19:52 +02:00
CompletableFuture < Chunk > resultFuture = new CompletableFuture < > ( ) ;
// TODO: virtual thread once Loom is available
ForkJoinPool . commonPool ( ) . submit ( ( ) - > {
var chunkUnit = GeneratorImpl . chunk ( chunk ) ;
try {
// Generate block/biome palette
generator . generate ( chunkUnit ) ;
// Apply nbt/handler
if ( chunkUnit . modifier ( ) instanceof GeneratorImpl . AreaModifierImpl chunkModifier ) {
for ( var section : chunkModifier . sections ( ) ) {
if ( section . modifier ( ) instanceof GeneratorImpl . SectionModifierImpl sectionModifier ) {
applyGenerationData ( chunk , sectionModifier ) ;
}
}
}
// Register forks or apply locally
for ( var fork : chunkUnit . forks ( ) ) {
var sections = ( ( GeneratorImpl . AreaModifierImpl ) fork . modifier ( ) ) . sections ( ) ;
for ( var section : sections ) {
if ( section . modifier ( ) instanceof GeneratorImpl . SectionModifierImpl sectionModifier ) {
if ( sectionModifier . blockPalette ( ) . count ( ) = = 0 )
continue ;
final Point start = section . absoluteStart ( ) ;
final Chunk forkChunk = start . chunkX ( ) = = chunkX & & start . chunkZ ( ) = = chunkZ ? chunk : getChunkAt ( start ) ;
if ( forkChunk ! = null ) {
applyFork ( forkChunk , sectionModifier ) ;
2022-06-30 23:12:42 +02:00
// Update players
2024-02-11 00:02:02 +01:00
forkChunk . invalidate ( ) ;
2022-06-30 23:12:42 +02:00
forkChunk . sendChunk ( ) ;
2022-04-08 07:19:52 +02:00
} else {
final long index = ChunkUtils . getChunkIndex ( start ) ;
this . generationForks . compute ( index , ( i , sectionModifiers ) - > {
if ( sectionModifiers = = null ) sectionModifiers = new ArrayList < > ( ) ;
sectionModifiers . add ( sectionModifier ) ;
return sectionModifiers ;
} ) ;
}
}
}
}
// Apply awaiting forks
processFork ( chunk ) ;
} catch ( Throwable e ) {
MinecraftServer . getExceptionManager ( ) . handleException ( e ) ;
} finally {
// End generation
refreshLastBlockChangeTime ( ) ;
resultFuture . complete ( chunk ) ;
}
} ) ;
return resultFuture ;
2020-08-03 11:26:10 +02:00
} else {
// No chunk generator, execute the callback with the empty chunk
2022-04-08 07:19:52 +02:00
processFork ( chunk ) ;
2021-07-11 03:26:08 +02:00
return CompletableFuture . completedFuture ( chunk ) ;
2019-08-24 20:34:01 +02:00
}
}
2022-04-08 07:19:52 +02:00
private void processFork ( Chunk chunk ) {
this . generationForks . compute ( ChunkUtils . getChunkIndex ( chunk ) , ( aLong , sectionModifiers ) - > {
if ( sectionModifiers ! = null ) {
for ( var sectionModifier : sectionModifiers ) {
applyFork ( chunk , sectionModifier ) ;
}
}
return null ;
} ) ;
}
private void applyFork ( Chunk chunk , GeneratorImpl . SectionModifierImpl sectionModifier ) {
synchronized ( chunk ) {
Section section = chunk . getSectionAt ( sectionModifier . start ( ) . blockY ( ) ) ;
Palette currentBlocks = section . blockPalette ( ) ;
// -1 is necessary because forked units handle explicit changes by changing AIR 0 to 1
sectionModifier . blockPalette ( ) . getAllPresent ( ( x , y , z , value ) - > currentBlocks . set ( x , y , z , value - 1 ) ) ;
applyGenerationData ( chunk , sectionModifier ) ;
}
}
private void applyGenerationData ( Chunk chunk , GeneratorImpl . SectionModifierImpl section ) {
var cache = section . cache ( ) ;
if ( cache . isEmpty ( ) ) return ;
final int height = section . start ( ) . blockY ( ) ;
synchronized ( chunk ) {
Int2ObjectMaps . fastForEach ( cache , blockEntry - > {
final int index = blockEntry . getIntKey ( ) ;
final Block block = blockEntry . getValue ( ) ;
final int x = ChunkUtils . blockIndexToChunkPositionX ( index ) ;
final int y = ChunkUtils . blockIndexToChunkPositionY ( index ) + height ;
final int z = ChunkUtils . blockIndexToChunkPositionZ ( index ) ;
chunk . setBlock ( x , y , z , block ) ;
} ) ;
}
}
2019-08-25 20:03:43 +02:00
@Override
public void enableAutoChunkLoad ( boolean enable ) {
2020-04-24 17:43:35 +02:00
this . autoChunkLoad = enable ;
2019-08-25 20:03:43 +02:00
}
@Override
public boolean hasEnabledAutoChunkLoad ( ) {
2020-04-24 17:43:35 +02:00
return autoChunkLoad ;
2019-08-25 20:03:43 +02:00
}
2020-04-27 20:33:08 +02:00
@Override
2021-07-06 20:44:24 +02:00
public boolean isInVoid ( @NotNull Point point ) {
2022-01-12 09:39:59 +01:00
// TODO: more customizable
return point . y ( ) < getDimensionType ( ) . getMinY ( ) - 64 ;
2020-04-27 20:33:08 +02:00
}
2020-10-10 05:50:49 +02:00
/ * *
2020-10-15 21:16:31 +02:00
* Changes which type of { @link Chunk } implementation to use once one needs to be loaded .
2020-10-10 05:50:49 +02:00
* < p >
* Uses { @link DynamicChunk } by default .
2020-10-10 08:45:26 +02:00
* < p >
* WARNING : if you need to save this instance ' s chunks later ,
2021-07-11 02:54:02 +02:00
* the code needs to be predictable for { @link IChunkLoader # loadChunk ( Instance , int , int ) }
2020-10-24 09:34:19 +02:00
* to create the correct type of { @link Chunk } . tl ; dr : Need chunk save = no random type .
2020-10-10 05:50:49 +02:00
*
2020-10-10 06:07:28 +02:00
* @param chunkSupplier the new { @link ChunkSupplier } of this instance , chunks need to be non - null
2020-10-10 05:50:49 +02:00
* @throws NullPointerException if { @code chunkSupplier } is null
* /
2023-05-28 01:41:14 +02:00
@Override
2020-10-24 10:46:23 +02:00
public void setChunkSupplier ( @NotNull ChunkSupplier chunkSupplier ) {
2020-10-10 05:50:49 +02:00
this . chunkSupplier = chunkSupplier ;
}
2020-10-10 08:45:26 +02:00
/ * *
2020-10-15 21:16:31 +02:00
* Gets the current { @link ChunkSupplier } .
2020-10-10 08:45:26 +02:00
* < p >
* You shouldn ' t use it to generate a new chunk , but as a way to view which one is currently in use .
*
* @return the current { @link ChunkSupplier }
* /
public ChunkSupplier getChunkSupplier ( ) {
return chunkSupplier ;
}
2020-09-01 20:20:46 +02:00
/ * *
2020-10-15 21:16:31 +02:00
* Gets all the { @link SharedInstance } linked to this container .
2020-09-01 20:20:46 +02:00
*
* @return an unmodifiable { @link List } containing all the { @link SharedInstance } linked to this container
* /
public List < SharedInstance > getSharedInstances ( ) {
return Collections . unmodifiableList ( sharedInstances ) ;
}
2020-11-14 09:02:29 +01:00
/ * *
* Gets if this instance has { @link SharedInstance } linked to it .
*
* @return true if { @link # getSharedInstances ( ) } is not empty
* /
public boolean hasSharedInstances ( ) {
return ! sharedInstances . isEmpty ( ) ;
}
2020-08-15 13:32:36 +02:00
/ * *
2020-10-15 21:16:31 +02:00
* Assigns a { @link SharedInstance } to this container .
2020-08-15 13:32:36 +02:00
* < p >
2020-10-10 06:07:28 +02:00
* Only used by { @link InstanceManager } , mostly unsafe .
2020-08-15 13:32:36 +02:00
*
* @param sharedInstance the shared instance to assign to this container
* /
2019-08-24 21:41:43 +02:00
protected void addSharedInstance ( SharedInstance sharedInstance ) {
this . sharedInstances . add ( sharedInstance ) ;
2019-08-24 20:34:01 +02:00
}
2020-10-31 19:22:23 +01:00
/ * *
* Copies all the chunks of this instance and create a new instance container with all of them .
* < p >
2021-03-11 20:54:30 +01:00
* Chunks are copied with { @link Chunk # copy ( Instance , int , int ) } ,
2022-02-09 23:07:18 +01:00
* { @link UUID } is randomized and { @link DimensionType } is passed over .
2020-10-31 19:22:23 +01:00
*
* @return an { @link InstanceContainer } with the exact same chunks as ' this '
2020-11-01 00:07:15 +01:00
* @see # getSrcInstance ( ) to retrieve the " creation source " of the copied instance
2020-10-31 19:22:23 +01:00
* /
public synchronized InstanceContainer copy ( ) {
2021-07-15 05:23:33 +02:00
InstanceContainer copiedInstance = new InstanceContainer ( UUID . randomUUID ( ) , getDimensionType ( ) ) ;
2020-10-31 19:22:23 +01:00
copiedInstance . srcInstance = this ;
2024-01-16 15:18:11 +01:00
copiedInstance . tagHandler = this . tagHandler . copy ( ) ;
copiedInstance . lastBlockChangeTime = this . lastBlockChangeTime ;
2021-12-16 05:29:11 +01:00
for ( Chunk chunk : chunks . values ( ) ) {
final int chunkX = chunk . getChunkX ( ) ;
final int chunkZ = chunk . getChunkZ ( ) ;
final Chunk copiedChunk = chunk . copy ( copiedInstance , chunkX , chunkZ ) ;
copiedInstance . cacheChunk ( copiedChunk ) ;
2020-10-31 19:22:23 +01:00
}
return copiedInstance ;
}
/ * *
* Gets the instance from which this one has been copied .
* < p >
* Only present if this instance has been created with { @link InstanceContainer # copy ( ) } .
*
* @return the instance source , null if not created by a copy
2020-11-01 00:07:15 +01:00
* @see # copy ( ) to create a copy of this instance with ' this ' as the source
2020-10-31 19:22:23 +01:00
* /
2021-08-15 05:58:53 +02:00
public @Nullable InstanceContainer getSrcInstance ( ) {
2020-10-31 19:22:23 +01:00
return srcInstance ;
}
/ * *
* Gets the last time at which a block changed .
*
* @return the time at which the last block changed in milliseconds , 0 if never
* /
public long getLastBlockChangeTime ( ) {
return lastBlockChangeTime ;
}
/ * *
* Signals the instance that a block changed .
* < p >
* Useful if you change blocks values directly using a { @link Chunk } object .
* /
public void refreshLastBlockChangeTime ( ) {
this . lastBlockChangeTime = System . currentTimeMillis ( ) ;
}
2020-05-27 20:30:13 +02:00
@Override
2022-04-08 07:19:52 +02:00
public @Nullable Generator generator ( ) {
return generator ;
2020-05-27 20:30:13 +02:00
}
@Override
2022-04-08 07:19:52 +02:00
public void setGenerator ( @Nullable Generator generator ) {
this . generator = generator ;
2019-08-24 20:34:01 +02:00
}
2020-08-17 16:50:23 +02:00
/ * *
2020-10-15 21:16:31 +02:00
* Gets all the instance chunks .
2020-08-17 16:50:23 +02:00
*
* @return the chunks of this instance
* /
2020-05-04 18:15:29 +02:00
@Override
2021-07-15 05:23:33 +02:00
public @NotNull Collection < @NotNull Chunk > getChunks ( ) {
2021-12-16 05:29:11 +01:00
return chunks . values ( ) ;
2019-08-24 20:34:01 +02:00
}
2020-09-11 00:14:17 +02:00
/ * *
2020-10-15 21:16:31 +02:00
* Gets the { @link IChunkLoader } of this instance .
2020-09-11 00:14:17 +02:00
*
* @return the { @link IChunkLoader } of this instance
* /
2020-06-30 18:07:47 +02:00
public IChunkLoader getChunkLoader ( ) {
return chunkLoader ;
}
2020-09-11 00:14:17 +02:00
/ * *
2020-10-15 21:16:31 +02:00
* Changes the { @link IChunkLoader } of this instance ( to change how chunks are retrieved when not already loaded ) .
2020-09-11 00:14:17 +02:00
*
* @param chunkLoader the new { @link IChunkLoader }
* /
2020-06-30 18:07:47 +02:00
public void setChunkLoader ( IChunkLoader chunkLoader ) {
this . chunkLoader = chunkLoader ;
}
2020-05-04 21:06:13 +02:00
@Override
public void tick ( long time ) {
2020-09-19 18:35:00 +02:00
// Time/world border
2020-05-04 21:06:13 +02:00
super . tick ( time ) ;
2021-08-15 05:58:53 +02:00
// Clear block change map
Lock wrlock = this . changingBlockLock ;
2020-05-04 21:06:13 +02:00
wrlock . lock ( ) ;
2021-08-15 05:58:53 +02:00
this . currentlyChangingBlocks . clear ( ) ;
2020-05-04 21:06:13 +02:00
wrlock . unlock ( ) ;
}
2020-07-01 15:51:00 +02:00
2021-06-22 23:51:01 +02:00
/ * *
* Has this block already changed since last update ?
* Prevents StackOverflow with blocks trying to modify their position in onDestroy or onPlace .
*
* @param blockPosition the block position
* @param block the block
* @return true if the block changed since the last update
* /
2021-07-08 18:56:40 +02:00
private boolean isAlreadyChanged ( @NotNull Point blockPosition , @NotNull Block block ) {
2021-06-22 23:51:01 +02:00
final Block changedBlock = currentlyChangingBlocks . get ( blockPosition ) ;
2022-05-23 23:40:57 +02:00
return Objects . equals ( changedBlock , block ) ;
2021-06-22 23:51:01 +02:00
}
/ * *
* Executed when a block is modified , this is used to modify the states of neighbours blocks .
* < p >
* For example , this can be used for redstone wires which need an understanding of its neighborhoods to take the right shape .
*
* @param blockPosition the position of the modified block
* /
2023-07-16 14:33:09 +02:00
private void executeNeighboursBlockPlacementRule ( @NotNull Point blockPosition , int updateDistance ) {
2022-03-17 00:14:12 +01:00
ChunkCache cache = new ChunkCache ( this , null , null ) ;
2023-07-16 14:33:09 +02:00
for ( var updateFace : BLOCK_UPDATE_FACES ) {
var direction = updateFace . toDirection ( ) ;
final int neighborX = blockPosition . blockX ( ) + direction . normalX ( ) ;
final int neighborY = blockPosition . blockY ( ) + direction . normalY ( ) ;
final int neighborZ = blockPosition . blockZ ( ) + direction . normalZ ( ) ;
if ( neighborY < getDimensionType ( ) . getMinY ( ) | | neighborY > getDimensionType ( ) . getTotalHeight ( ) )
continue ;
final Block neighborBlock = cache . getBlock ( neighborX , neighborY , neighborZ , Condition . TYPE ) ;
if ( neighborBlock = = null )
continue ;
final BlockPlacementRule neighborBlockPlacementRule = MinecraftServer . getBlockManager ( ) . getBlockPlacementRule ( neighborBlock ) ;
if ( neighborBlockPlacementRule = = null | | updateDistance > = neighborBlockPlacementRule . maxUpdateDistance ( ) ) continue ;
final Vec neighborPosition = new Vec ( neighborX , neighborY , neighborZ ) ;
final Block newNeighborBlock = neighborBlockPlacementRule . blockUpdate ( new BlockPlacementRule . UpdateState (
this ,
neighborPosition ,
neighborBlock ,
updateFace . getOppositeFace ( )
) ) ;
if ( neighborBlock ! = newNeighborBlock ) {
final Chunk chunk = getChunkAt ( neighborPosition ) ;
if ( ! isLoaded ( chunk ) ) continue ;
UNSAFE_setBlock ( chunk , neighborPosition . blockX ( ) , neighborPosition . blockY ( ) , neighborPosition . blockZ ( ) , newNeighborBlock ,
null , null , true , updateDistance + 1 ) ;
2021-06-22 23:51:01 +02:00
}
}
}
2021-08-15 05:58:53 +02:00
private CompletableFuture < Chunk > loadOrRetrieve ( int chunkX , int chunkZ , Supplier < CompletableFuture < Chunk > > supplier ) {
final Chunk chunk = getChunk ( chunkX , chunkZ ) ;
if ( chunk ! = null ) {
// Chunk already loaded
return CompletableFuture . completedFuture ( chunk ) ;
}
return supplier . get ( ) ;
2020-07-01 15:51:00 +02:00
}
2021-08-15 05:58:53 +02:00
private void cacheChunk ( @NotNull Chunk chunk ) {
2022-03-01 00:35:55 +01:00
this . chunks . put ( getChunkIndex ( chunk ) , chunk ) ;
2022-01-05 09:01:21 +01:00
var dispatcher = MinecraftServer . process ( ) . dispatcher ( ) ;
dispatcher . createPartition ( chunk ) ;
2020-07-01 15:51:00 +02:00
}
2022-04-27 16:14:54 +02:00
}