2021-06-20 20:12:07 +02:00
package net.minestom.server.instance ;
2022-08-05 21:05:23 +02:00
import it.unimi.dsi.fastutil.ints.Int2ObjectArrayMap ;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap ;
import it.unimi.dsi.fastutil.ints.IntIntImmutablePair ;
2021-06-20 20:12:07 +02:00
import net.minestom.server.MinecraftServer ;
import net.minestom.server.instance.block.Block ;
2021-06-22 18:02:50 +02:00
import net.minestom.server.instance.block.BlockHandler ;
2021-12-13 16:41:30 +01:00
import net.minestom.server.utils.NamespaceID ;
2021-07-11 03:35:17 +02:00
import net.minestom.server.utils.async.AsyncUtils ;
2021-06-20 20:12:07 +02:00
import net.minestom.server.world.biomes.Biome ;
2021-06-20 20:28:43 +02:00
import org.jetbrains.annotations.NotNull ;
2021-06-22 23:26:12 +02:00
import org.jetbrains.annotations.Nullable ;
2021-06-20 20:12:07 +02:00
import org.jglrxavpok.hephaistos.mca.* ;
2022-08-05 21:05:23 +02:00
import org.jglrxavpok.hephaistos.mca.readers.ChunkReader ;
import org.jglrxavpok.hephaistos.mca.readers.ChunkSectionReader ;
import org.jglrxavpok.hephaistos.mca.readers.SectionBiomeInformation ;
import org.jglrxavpok.hephaistos.mca.writer.ChunkSectionWriter ;
import org.jglrxavpok.hephaistos.mca.writer.ChunkWriter ;
2021-08-09 01:16:51 +02:00
import org.jglrxavpok.hephaistos.nbt.* ;
2021-12-13 16:41:30 +01:00
import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound ;
2021-06-20 20:12:07 +02:00
import org.slf4j.Logger ;
import org.slf4j.LoggerFactory ;
2021-06-20 20:28:43 +02:00
import java.io.File ;
2021-06-20 20:12:07 +02:00
import java.io.IOException ;
import java.io.RandomAccessFile ;
import java.nio.file.Files ;
import java.nio.file.Path ;
2021-08-09 01:16:51 +02:00
import java.nio.file.StandardCopyOption ;
2021-12-13 16:41:30 +01:00
import java.util.* ;
2021-07-11 02:54:02 +02:00
import java.util.concurrent.CompletableFuture ;
2021-06-20 20:12:07 +02:00
import java.util.concurrent.ConcurrentHashMap ;
public class AnvilLoader implements IChunkLoader {
private final static Logger LOGGER = LoggerFactory . getLogger ( AnvilLoader . class ) ;
2024-02-12 21:25:46 +01:00
private final static Biome PLAINS = MinecraftServer . getBiomeManager ( ) . getByName ( NamespaceID . from ( " minecraft:plains " ) ) ;
2021-06-20 20:12:07 +02:00
private final Map < String , RegionFile > alreadyLoaded = new ConcurrentHashMap < > ( ) ;
private final Path path ;
2021-08-09 01:16:51 +02:00
private final Path levelPath ;
2021-06-22 23:26:12 +02:00
private final Path regionPath ;
2021-06-20 20:12:07 +02:00
2022-10-27 15:25:51 +02:00
private static class RegionCache extends ConcurrentHashMap < IntIntImmutablePair , Set < IntIntImmutablePair > > {
}
2022-08-05 21:05:23 +02:00
/ * *
* Represents the chunks currently loaded per region . Used to determine when a region file can be unloaded .
* /
private final RegionCache perRegionLoadedChunks = new RegionCache ( ) ;
// thread local to avoid contention issues with locks
2022-10-27 15:52:59 +02:00
private final ThreadLocal < Int2ObjectMap < BlockState > > blockStateId2ObjectCacheTLS = ThreadLocal . withInitial ( Int2ObjectArrayMap : : new ) ;
2022-08-05 21:05:23 +02:00
2021-06-20 20:28:43 +02:00
public AnvilLoader ( @NotNull Path path ) {
2021-06-20 20:12:07 +02:00
this . path = path ;
2021-08-09 01:16:51 +02:00
this . levelPath = path . resolve ( " level.dat " ) ;
2021-06-22 23:26:12 +02:00
this . regionPath = path . resolve ( " region " ) ;
2021-06-20 20:12:07 +02:00
}
2021-06-20 20:28:43 +02:00
public AnvilLoader ( @NotNull String path ) {
2021-06-20 20:12:07 +02:00
this ( Path . of ( path ) ) ;
}
2021-08-09 01:16:51 +02:00
@Override
public void loadInstance ( @NotNull Instance instance ) {
if ( ! Files . exists ( levelPath ) ) {
return ;
}
try ( var reader = new NBTReader ( Files . newInputStream ( levelPath ) ) ) {
final NBTCompound tag = ( NBTCompound ) reader . read ( ) ;
Files . copy ( levelPath , path . resolve ( " level.dat_old " ) , StandardCopyOption . REPLACE_EXISTING ) ;
2022-03-20 01:47:57 +01:00
instance . tagHandler ( ) . updateContent ( tag ) ;
2021-08-09 01:16:51 +02:00
} catch ( IOException | NBTException e ) {
MinecraftServer . getExceptionManager ( ) . handleException ( e ) ;
}
}
2021-06-20 20:12:07 +02:00
@Override
2021-07-11 02:54:02 +02:00
public @NotNull CompletableFuture < @Nullable Chunk > loadChunk ( @NotNull Instance instance , int chunkX , int chunkZ ) {
2021-06-20 21:23:18 +02:00
if ( ! Files . exists ( path ) ) {
// No world folder
2021-07-11 02:54:02 +02:00
return CompletableFuture . completedFuture ( null ) ;
2021-06-20 21:23:18 +02:00
}
2021-06-20 20:12:07 +02:00
try {
2021-07-11 02:54:02 +02:00
return loadMCA ( instance , chunkX , chunkZ ) ;
2021-12-13 23:30:16 +01:00
} catch ( Exception e ) {
2022-01-19 21:41:25 +01:00
MinecraftServer . getExceptionManager ( ) . handleException ( e ) ;
2021-06-20 20:12:07 +02:00
}
2021-07-11 02:54:02 +02:00
return CompletableFuture . completedFuture ( null ) ;
2021-06-20 20:12:07 +02:00
}
2021-07-11 02:54:02 +02:00
private @NotNull CompletableFuture < @Nullable Chunk > loadMCA ( Instance instance , int chunkX , int chunkZ ) throws IOException , AnvilException {
2022-01-07 22:48:07 +01:00
final RegionFile mcaFile = getMCAFile ( instance , chunkX , chunkZ ) ;
2021-06-21 16:32:46 +02:00
if ( mcaFile = = null )
2021-07-11 02:54:02 +02:00
return CompletableFuture . completedFuture ( null ) ;
2022-08-05 21:05:23 +02:00
final NBTCompound chunkData = mcaFile . getChunkData ( chunkX , chunkZ ) ;
if ( chunkData = = null )
2021-07-11 02:54:02 +02:00
return CompletableFuture . completedFuture ( null ) ;
2021-06-21 16:32:46 +02:00
2022-08-05 21:05:23 +02:00
final ChunkReader chunkReader = new ChunkReader ( chunkData ) ;
2022-01-09 15:50:43 +01:00
2023-05-28 01:41:14 +02:00
Chunk chunk = instance . getChunkSupplier ( ) . createChunk ( instance , chunkX , chunkZ ) ;
2022-08-05 21:05:23 +02:00
synchronized ( chunk ) {
var yRange = chunkReader . getYRange ( ) ;
2022-10-27 15:25:51 +02:00
if ( yRange . getStart ( ) < instance . getDimensionType ( ) . getMinY ( ) ) {
2022-08-05 21:05:23 +02:00
throw new AnvilException (
String . format ( " Trying to load chunk with minY = %d, but instance dimension type (%s) has a minY of %d " ,
yRange . getStart ( ) ,
instance . getDimensionType ( ) . getName ( ) . asString ( ) ,
instance . getDimensionType ( ) . getMinY ( )
) ) ;
}
2022-10-27 15:25:51 +02:00
if ( yRange . getEndInclusive ( ) > instance . getDimensionType ( ) . getMaxY ( ) ) {
2022-08-05 21:05:23 +02:00
throw new AnvilException (
String . format ( " Trying to load chunk with maxY = %d, but instance dimension type (%s) has a maxY of %d " ,
yRange . getEndInclusive ( ) ,
instance . getDimensionType ( ) . getName ( ) . asString ( ) ,
instance . getDimensionType ( ) . getMaxY ( )
) ) ;
}
2021-12-13 16:41:30 +01:00
2022-08-05 21:05:23 +02:00
// TODO: Parallelize block, block entities and biome loading
// Blocks + Biomes
loadSections ( chunk , chunkReader ) ;
2021-12-13 16:41:30 +01:00
2022-08-05 21:05:23 +02:00
// Block entities
loadBlockEntities ( chunk , chunkReader ) ;
2021-06-21 16:32:46 +02:00
}
2022-08-05 21:05:23 +02:00
synchronized ( perRegionLoadedChunks ) {
int regionX = CoordinatesKt . chunkToRegion ( chunkX ) ;
int regionZ = CoordinatesKt . chunkToRegion ( chunkZ ) ;
var chunks = perRegionLoadedChunks . computeIfAbsent ( new IntIntImmutablePair ( regionX , regionZ ) , r - > new HashSet < > ( ) ) ; // region cache may have been removed on another thread due to unloadChunk
chunks . add ( new IntIntImmutablePair ( chunkX , chunkZ ) ) ;
2022-10-27 15:25:51 +02:00
}
2021-07-11 02:54:02 +02:00
return CompletableFuture . completedFuture ( chunk ) ;
2021-06-20 20:12:07 +02:00
}
2022-01-07 22:48:07 +01:00
private @Nullable RegionFile getMCAFile ( Instance instance , int chunkX , int chunkZ ) {
2021-06-20 20:28:43 +02:00
final int regionX = CoordinatesKt . chunkToRegion ( chunkX ) ;
final int regionZ = CoordinatesKt . chunkToRegion ( chunkZ ) ;
2021-06-20 20:12:07 +02:00
return alreadyLoaded . computeIfAbsent ( RegionFile . Companion . createFileName ( regionX , regionZ ) , n - > {
try {
2021-06-22 23:26:12 +02:00
final Path regionPath = this . regionPath . resolve ( n ) ;
2021-06-20 20:12:07 +02:00
if ( ! Files . exists ( regionPath ) ) {
return null ;
}
2022-08-05 21:05:23 +02:00
synchronized ( perRegionLoadedChunks ) {
Set < IntIntImmutablePair > previousVersion = perRegionLoadedChunks . put ( new IntIntImmutablePair ( regionX , regionZ ) , new HashSet < > ( ) ) ;
assert previousVersion = = null : " The AnvilLoader cache should not already have data for this region. " ;
2022-10-27 15:25:51 +02:00
}
return new RegionFile ( new RandomAccessFile ( regionPath . toFile ( ) , " rw " ) , regionX , regionZ , instance . getDimensionType ( ) . getMinY ( ) , instance . getDimensionType ( ) . getMaxY ( ) - 1 ) ;
2021-06-20 20:12:07 +02:00
} catch ( IOException | AnvilException e ) {
2022-01-19 21:41:25 +01:00
MinecraftServer . getExceptionManager ( ) . handleException ( e ) ;
2021-06-20 20:12:07 +02:00
return null ;
}
} ) ;
}
2022-08-05 21:05:23 +02:00
private void loadSections ( Chunk chunk , ChunkReader chunkReader ) {
final HashMap < String , Biome > biomeCache = new HashMap < > ( ) ;
2022-10-27 15:52:59 +02:00
for ( NBTCompound sectionNBT : chunkReader . getSections ( ) ) {
2022-08-05 21:05:23 +02:00
ChunkSectionReader sectionReader = new ChunkSectionReader ( chunkReader . getMinecraftVersion ( ) , sectionNBT ) ;
2022-09-30 11:28:47 +02:00
if ( sectionReader . isSectionEmpty ( ) ) continue ;
final int sectionY = sectionReader . getY ( ) ;
final int yOffset = Chunk . CHUNK_SECTION_SIZE * sectionY ;
Section section = chunk . getSection ( sectionY ) ;
2022-08-05 21:05:23 +02:00
2022-10-27 15:25:51 +02:00
if ( sectionReader . getSkyLight ( ) ! = null ) {
2022-08-05 21:05:23 +02:00
section . setSkyLight ( sectionReader . getSkyLight ( ) . copyArray ( ) ) ;
}
2022-10-27 15:25:51 +02:00
if ( sectionReader . getBlockLight ( ) ! = null ) {
2022-08-05 21:05:23 +02:00
section . setBlockLight ( sectionReader . getBlockLight ( ) . copyArray ( ) ) ;
}
// Biomes
2022-10-27 15:25:51 +02:00
if ( chunkReader . getGenerationStatus ( ) . compareTo ( ChunkColumn . GenerationStatus . Biomes ) > 0 ) {
2022-08-05 21:05:23 +02:00
SectionBiomeInformation sectionBiomeInformation = chunkReader . readSectionBiomes ( sectionReader ) ;
2022-10-27 15:25:51 +02:00
if ( sectionBiomeInformation ! = null & & sectionBiomeInformation . hasBiomeInformation ( ) ) {
if ( sectionBiomeInformation . isFilledWithSingleBiome ( ) ) {
2022-08-05 21:05:23 +02:00
for ( int y = 0 ; y < Chunk . CHUNK_SECTION_SIZE ; y + + ) {
for ( int z = 0 ; z < Chunk . CHUNK_SIZE_Z ; z + + ) {
for ( int x = 0 ; x < Chunk . CHUNK_SIZE_X ; x + + ) {
int finalX = chunk . chunkX * Chunk . CHUNK_SIZE_X + x ;
int finalZ = chunk . chunkZ * Chunk . CHUNK_SIZE_Z + z ;
int finalY = sectionY * Chunk . CHUNK_SECTION_SIZE + y ;
String biomeName = sectionBiomeInformation . getBaseBiome ( ) ;
Biome biome = biomeCache . computeIfAbsent ( biomeName , n - >
2024-02-12 21:25:46 +01:00
Objects . requireNonNullElse ( MinecraftServer . getBiomeManager ( ) . getByName ( NamespaceID . from ( n ) ) , PLAINS ) ) ;
2022-08-05 21:05:23 +02:00
chunk . setBiome ( finalX , finalY , finalZ , biome ) ;
}
}
}
} else {
for ( int y = 0 ; y < Chunk . CHUNK_SECTION_SIZE ; y + + ) {
for ( int z = 0 ; z < Chunk . CHUNK_SIZE_Z ; z + + ) {
for ( int x = 0 ; x < Chunk . CHUNK_SIZE_X ; x + + ) {
int finalX = chunk . chunkX * Chunk . CHUNK_SIZE_X + x ;
int finalZ = chunk . chunkZ * Chunk . CHUNK_SIZE_Z + z ;
int finalY = sectionY * Chunk . CHUNK_SECTION_SIZE + y ;
2022-10-27 15:25:51 +02:00
int index = x / 4 + ( z / 4 ) * 4 + ( y / 4 ) * 16 ;
2022-08-05 21:05:23 +02:00
String biomeName = sectionBiomeInformation . getBiomes ( ) [ index ] ;
Biome biome = biomeCache . computeIfAbsent ( biomeName , n - >
2024-02-12 21:25:46 +01:00
Objects . requireNonNullElse ( MinecraftServer . getBiomeManager ( ) . getByName ( NamespaceID . from ( n ) ) , PLAINS ) ) ;
2022-08-05 21:05:23 +02:00
chunk . setBiome ( finalX , finalY , finalZ , biome ) ;
}
}
}
}
}
}
// Blocks
final NBTList < NBTCompound > blockPalette = sectionReader . getBlockPalette ( ) ;
2022-10-27 15:25:51 +02:00
if ( blockPalette ! = null ) {
2022-10-27 15:52:59 +02:00
final int [ ] blockStateIndices = sectionReader . getUncompressedBlockStateIDs ( ) ;
2022-08-05 21:05:23 +02:00
Block [ ] convertedPalette = new Block [ blockPalette . getSize ( ) ] ;
for ( int i = 0 ; i < convertedPalette . length ; i + + ) {
final NBTCompound paletteEntry = blockPalette . get ( i ) ;
2024-01-16 15:07:45 +01:00
String blockName = Objects . requireNonNull ( paletteEntry . getString ( " Name " ) ) ;
2022-08-05 21:05:23 +02:00
if ( blockName . equals ( " minecraft:air " ) ) {
convertedPalette [ i ] = Block . AIR ;
} else {
2024-01-16 15:07:45 +01:00
if ( blockName . equals ( " minecraft:grass " ) ) {
blockName = " minecraft:short_grass " ;
}
2022-08-05 21:05:23 +02:00
Block block = Objects . requireNonNull ( Block . fromNamespaceId ( blockName ) ) ;
// Properties
final Map < String , String > properties = new HashMap < > ( ) ;
NBTCompound propertiesNBT = paletteEntry . getCompound ( " Properties " ) ;
if ( propertiesNBT ! = null ) {
for ( var property : propertiesNBT ) {
if ( property . getValue ( ) . getID ( ) ! = NBTType . TAG_String ) {
LOGGER . warn ( " Fail to parse block state properties {}, expected a TAG_String for {}, but contents were {} " ,
propertiesNBT ,
property . getKey ( ) ,
property . getValue ( ) . toSNBT ( ) ) ;
} else {
properties . put ( property . getKey ( ) , ( ( NBTString ) property . getValue ( ) ) . getValue ( ) ) ;
}
}
}
if ( ! properties . isEmpty ( ) ) block = block . withProperties ( properties ) ;
// Handler
final BlockHandler handler = MinecraftServer . getBlockManager ( ) . getHandler ( block . name ( ) ) ;
if ( handler ! = null ) block = block . withHandler ( handler ) ;
convertedPalette [ i ] = block ;
}
}
for ( int y = 0 ; y < Chunk . CHUNK_SECTION_SIZE ; y + + ) {
for ( int z = 0 ; z < Chunk . CHUNK_SECTION_SIZE ; z + + ) {
for ( int x = 0 ; x < Chunk . CHUNK_SECTION_SIZE ; x + + ) {
try {
2022-10-27 15:52:59 +02:00
final int blockIndex = y * Chunk . CHUNK_SECTION_SIZE * Chunk . CHUNK_SECTION_SIZE + z * Chunk . CHUNK_SECTION_SIZE + x ;
final int paletteIndex = blockStateIndices [ blockIndex ] ;
final Block block = convertedPalette [ paletteIndex ] ;
2022-08-05 21:05:23 +02:00
chunk . setBlock ( x , y + yOffset , z , block ) ;
} catch ( Exception e ) {
MinecraftServer . getExceptionManager ( ) . handleException ( e ) ;
}
2021-06-20 20:12:07 +02:00
}
}
}
}
}
}
2022-08-05 21:05:23 +02:00
private void loadBlockEntities ( Chunk loadedChunk , ChunkReader chunkReader ) {
for ( NBTCompound te : chunkReader . getBlockEntities ( ) ) {
2021-06-22 23:14:26 +02:00
final var x = te . getInt ( " x " ) ;
final var y = te . getInt ( " y " ) ;
final var z = te . getInt ( " z " ) ;
2021-06-22 22:59:25 +02:00
if ( x = = null | | y = = null | | z = = null ) {
2021-06-24 16:06:11 +02:00
LOGGER . warn ( " Tile entity has failed to load due to invalid coordinate " ) ;
2021-06-22 22:59:25 +02:00
continue ;
}
2021-06-24 16:06:11 +02:00
Block block = loadedChunk . getBlock ( x , y , z ) ;
final String tileEntityID = te . getString ( " id " ) ;
if ( tileEntityID ! = null ) {
2022-01-19 21:41:25 +01:00
final BlockHandler handler = MinecraftServer . getBlockManager ( ) . getHandlerOrDummy ( tileEntityID ) ;
2021-06-24 16:06:11 +02:00
block = block . withHandler ( handler ) ;
2021-06-22 22:59:25 +02:00
}
// Remove anvil tags
2021-12-13 16:41:30 +01:00
MutableNBTCompound mutableCopy = te . toMutableCompound ( ) ;
mutableCopy . remove ( " id " ) ;
mutableCopy . remove ( " x " ) ;
mutableCopy . remove ( " y " ) ;
mutableCopy . remove ( " z " ) ;
mutableCopy . remove ( " keepPacked " ) ;
2021-06-22 22:59:25 +02:00
// Place block
2021-12-13 16:41:30 +01:00
final var finalBlock = mutableCopy . getSize ( ) > 0 ?
block . withNbt ( mutableCopy . toCompound ( ) ) : block ;
2021-06-24 16:13:00 +02:00
loadedChunk . setBlock ( x , y , z , finalBlock ) ;
2021-06-22 22:59:25 +02:00
}
}
2021-08-09 01:16:51 +02:00
@Override
public @NotNull CompletableFuture < Void > saveInstance ( @NotNull Instance instance ) {
2022-10-27 15:52:59 +02:00
final NBTCompound nbt = instance . tagHandler ( ) . asCompound ( ) ;
2022-03-20 01:47:57 +01:00
if ( nbt . isEmpty ( ) ) {
2021-08-09 01:16:51 +02:00
// Instance has no data
return AsyncUtils . VOID_FUTURE ;
}
try ( NBTWriter writer = new NBTWriter ( Files . newOutputStream ( levelPath ) ) ) {
writer . writeNamed ( " " , nbt ) ;
} catch ( IOException e ) {
e . printStackTrace ( ) ;
}
return AsyncUtils . VOID_FUTURE ;
}
2021-07-11 02:54:02 +02:00
2021-06-20 20:12:07 +02:00
@Override
2021-07-11 02:54:02 +02:00
public @NotNull CompletableFuture < Void > saveChunk ( @NotNull Chunk chunk ) {
2021-06-20 20:28:43 +02:00
final int chunkX = chunk . getChunkX ( ) ;
final int chunkZ = chunk . getChunkZ ( ) ;
RegionFile mcaFile ;
synchronized ( alreadyLoaded ) {
2022-01-07 22:48:07 +01:00
mcaFile = getMCAFile ( chunk . instance , chunkX , chunkZ ) ;
2021-06-20 20:28:43 +02:00
if ( mcaFile = = null ) {
final int regionX = CoordinatesKt . chunkToRegion ( chunkX ) ;
final int regionZ = CoordinatesKt . chunkToRegion ( chunkZ ) ;
final String n = RegionFile . Companion . createFileName ( regionX , regionZ ) ;
2021-06-22 23:26:12 +02:00
File regionFile = new File ( regionPath . toFile ( ) , n ) ;
2021-06-20 20:28:43 +02:00
try {
if ( ! regionFile . exists ( ) ) {
if ( ! regionFile . getParentFile ( ) . exists ( ) ) {
regionFile . getParentFile ( ) . mkdirs ( ) ;
}
regionFile . createNewFile ( ) ;
}
mcaFile = new RegionFile ( new RandomAccessFile ( regionFile , " rw " ) , regionX , regionZ ) ;
alreadyLoaded . put ( n , mcaFile ) ;
} catch ( AnvilException | IOException e ) {
LOGGER . error ( " Failed to save chunk " + chunkX + " , " + chunkZ , e ) ;
2022-01-19 21:41:25 +01:00
MinecraftServer . getExceptionManager ( ) . handleException ( e ) ;
2021-07-28 17:28:36 +02:00
return AsyncUtils . VOID_FUTURE ;
2021-06-20 20:28:43 +02:00
}
}
}
2022-08-05 21:05:23 +02:00
ChunkWriter writer = new ChunkWriter ( SupportedVersion . Companion . getLatest ( ) ) ;
save ( chunk , writer ) ;
2021-06-20 20:28:43 +02:00
try {
LOGGER . debug ( " Attempt saving at {} {} " , chunk . getChunkX ( ) , chunk . getChunkZ ( ) ) ;
2022-08-05 21:05:23 +02:00
mcaFile . writeColumnData ( writer . toNBT ( ) , chunk . getChunkX ( ) , chunk . getChunkZ ( ) ) ;
2021-06-20 20:28:43 +02:00
} catch ( IOException e ) {
LOGGER . error ( " Failed to save chunk " + chunkX + " , " + chunkZ , e ) ;
2022-01-19 21:41:25 +01:00
MinecraftServer . getExceptionManager ( ) . handleException ( e ) ;
2021-07-28 17:28:36 +02:00
return AsyncUtils . VOID_FUTURE ;
2021-06-20 20:28:43 +02:00
}
2021-07-28 17:28:36 +02:00
return AsyncUtils . VOID_FUTURE ;
2021-06-20 20:28:43 +02:00
}
2022-08-05 21:05:23 +02:00
private BlockState getBlockState ( final Block block ) {
return blockStateId2ObjectCacheTLS . get ( ) . computeIfAbsent ( block . stateId ( ) , _unused - > new BlockState ( block . name ( ) , block . properties ( ) ) ) ;
}
private void save ( Chunk chunk , ChunkWriter chunkWriter ) {
2022-10-27 15:25:51 +02:00
final int minY = chunk . getMinSection ( ) * Chunk . CHUNK_SECTION_SIZE ;
final int maxY = chunk . getMaxSection ( ) * Chunk . CHUNK_SECTION_SIZE - 1 ;
2022-08-05 21:05:23 +02:00
chunkWriter . setYPos ( minY ) ;
List < NBTCompound > blockEntities = new ArrayList < > ( ) ;
chunkWriter . setStatus ( ChunkColumn . GenerationStatus . Full ) ;
List < NBTCompound > sectionData = new ArrayList < > ( ( maxY - minY + 1 ) / Chunk . CHUNK_SECTION_SIZE ) ;
int [ ] palettedBiomes = new int [ ChunkSection . Companion . getBiomeArraySize ( ) ] ;
int [ ] palettedBlockStates = new int [ Chunk . CHUNK_SIZE_X * Chunk . CHUNK_SECTION_SIZE * Chunk . CHUNK_SIZE_Z ] ;
for ( int sectionY = chunk . getMinSection ( ) ; sectionY < chunk . getMaxSection ( ) ; sectionY + + ) {
2022-10-27 15:25:51 +02:00
ChunkSectionWriter sectionWriter = new ChunkSectionWriter ( SupportedVersion . Companion . getLatest ( ) , ( byte ) sectionY ) ;
2022-08-05 21:05:23 +02:00
Section section = chunk . getSection ( sectionY ) ;
2023-05-28 01:41:14 +02:00
sectionWriter . setSkyLights ( section . skyLight ( ) . array ( ) ) ;
sectionWriter . setBlockLights ( section . blockLight ( ) . array ( ) ) ;
2022-08-05 21:05:23 +02:00
BiomePalette biomePalette = new BiomePalette ( ) ;
BlockPalette blockPalette = new BlockPalette ( ) ;
for ( int sectionLocalY = 0 ; sectionLocalY < Chunk . CHUNK_SECTION_SIZE ; sectionLocalY + + ) {
for ( int z = 0 ; z < Chunk . CHUNK_SIZE_Z ; z + + ) {
for ( int x = 0 ; x < Chunk . CHUNK_SIZE_X ; x + + ) {
final int y = sectionLocalY + sectionY * Chunk . CHUNK_SECTION_SIZE ;
2022-10-27 15:52:59 +02:00
final int blockIndex = x + sectionLocalY * 16 * 16 + z * 16 ;
2022-08-05 21:05:23 +02:00
final Block block = chunk . getBlock ( x , y , z ) ;
final BlockState hephaistosBlockState = getBlockState ( block ) ;
blockPalette . increaseReference ( hephaistosBlockState ) ;
palettedBlockStates [ blockIndex ] = blockPalette . getPaletteIndex ( hephaistosBlockState ) ;
// biome are stored for 4x4x4 volumes, avoid unnecessary work
2022-10-27 15:25:51 +02:00
if ( x % 4 = = 0 & & sectionLocalY % 4 = = 0 & & z % 4 = = 0 ) {
int biomeIndex = ( x / 4 ) + ( sectionLocalY / 4 ) * 4 * 4 + ( z / 4 ) * 4 ;
2022-08-05 21:05:23 +02:00
final Biome biome = chunk . getBiome ( x , y , z ) ;
2024-02-12 21:25:46 +01:00
final String biomeName = biome . name ( ) ;
2022-08-05 21:05:23 +02:00
biomePalette . increaseReference ( biomeName ) ;
palettedBiomes [ biomeIndex ] = biomePalette . getPaletteIndex ( biomeName ) ;
}
// Block entities
final BlockHandler handler = block . handler ( ) ;
2022-10-27 15:52:59 +02:00
final NBTCompound originalNBT = block . nbt ( ) ;
2022-08-05 21:05:23 +02:00
if ( originalNBT ! = null | | handler ! = null ) {
MutableNBTCompound nbt = originalNBT ! = null ?
originalNBT . toMutableCompound ( ) : new MutableNBTCompound ( ) ;
if ( handler ! = null ) {
nbt . setString ( " id " , handler . getNamespaceId ( ) . asString ( ) ) ;
}
nbt . setInt ( " x " , x + Chunk . CHUNK_SIZE_X * chunk . getChunkX ( ) ) ;
nbt . setInt ( " y " , y ) ;
nbt . setInt ( " z " , z + Chunk . CHUNK_SIZE_Z * chunk . getChunkZ ( ) ) ;
nbt . setByte ( " keepPacked " , ( byte ) 0 ) ;
blockEntities . add ( nbt . toCompound ( ) ) ;
2021-06-24 16:16:41 +02:00
}
2021-06-22 18:02:50 +02:00
}
2021-06-20 20:28:43 +02:00
}
}
2022-08-05 21:05:23 +02:00
sectionWriter . setPalettedBiomes ( biomePalette , palettedBiomes ) ;
sectionWriter . setPalettedBlockStates ( blockPalette , palettedBlockStates ) ;
sectionData . add ( sectionWriter . toNBT ( ) ) ;
2021-06-20 20:28:43 +02:00
}
2022-08-05 21:05:23 +02:00
chunkWriter . setSectionsData ( NBT . List ( NBTType . TAG_Compound , sectionData ) ) ;
chunkWriter . setBlockEntityData ( NBT . List ( NBTType . TAG_Compound , blockEntities ) ) ;
}
/ * *
* Unload a given chunk . Also unloads a region when no chunk from that region is loaded .
2022-10-27 15:25:51 +02:00
*
2022-08-05 21:05:23 +02:00
* @param chunk the chunk to unload
* /
@Override
public void unloadChunk ( Chunk chunk ) {
final int regionX = CoordinatesKt . chunkToRegion ( chunk . chunkX ) ;
final int regionZ = CoordinatesKt . chunkToRegion ( chunk . chunkZ ) ;
final IntIntImmutablePair regionKey = new IntIntImmutablePair ( regionX , regionZ ) ;
synchronized ( perRegionLoadedChunks ) {
Set < IntIntImmutablePair > chunks = perRegionLoadedChunks . get ( regionKey ) ;
2022-10-27 15:25:51 +02:00
if ( chunks ! = null ) { // if null, trying to unload a chunk from a region that was not created by the AnvilLoader
2022-08-05 21:05:23 +02:00
// don't check return value, trying to unload a chunk not created by the AnvilLoader is valid
chunks . remove ( new IntIntImmutablePair ( chunk . chunkX , chunk . chunkZ ) ) ;
2022-10-27 15:25:51 +02:00
if ( chunks . isEmpty ( ) ) {
2022-08-05 21:05:23 +02:00
perRegionLoadedChunks . remove ( regionKey ) ;
RegionFile regionFile = alreadyLoaded . remove ( RegionFile . Companion . createFileName ( regionX , regionZ ) ) ;
2022-10-27 15:25:51 +02:00
if ( regionFile ! = null ) {
2022-08-05 21:05:23 +02:00
try {
regionFile . close ( ) ;
} catch ( IOException e ) {
MinecraftServer . getExceptionManager ( ) . handleException ( e ) ;
}
}
}
}
2022-10-27 15:25:51 +02:00
}
2021-06-20 20:12:07 +02:00
}
2021-07-11 03:14:17 +02:00
@Override
public boolean supportsParallelLoading ( ) {
return true ;
}
@Override
public boolean supportsParallelSaving ( ) {
return true ;
}
2021-06-20 20:12:07 +02:00
}