module xmcl import { deserialize } from "@xmcl/nbt" ; import { FileSystem , openFileSystem } from "@xmcl/system" ; import Long from "long" ; /**
 * Compute the bit length from new region section
 */ function computeBitLen ( palette , blockStates ) { let computedBitLen = log2DeBruijn ( palette . length ) ; let avgBitLen = blockStates . length * 64 / 4096 ; return computedBitLen >= 9 ? computedBitLen : avgBitLen ; } /**
 * Create bit vector from a long array
 */ function createBitVector ( arr , bitLen ) { let maxEntryValue = Long . fromNumber ( 1 ) . shiftLeft ( bitLen ) . sub ( 1 ) ; let result = new Array < number > ( 4096 ) ; for ( let i = 0 ; i < 4096 ; ++ i ) { result [ i ] = seek ( arr , bitLen , i , maxEntryValue ) ; } return result ; } param data param bitLen param index param maxEntryValue function seek ( data , bitLen , index , maxEntryValue = Long . fromNumber ( 1 ) . shiftLeft ( bitLen ) . sub ( 1 ) ) { let offset = index * bitLen ; let j = offset >> 6 ; let k = ( ( index + 1 ) * bitLen - 1 ) >>> 6 ; let l = offset ^ j << 6 ; if ( j == k ) { return data [ j ] . shiftRightUnsigned ( l ) . and ( maxEntryValue ) . toInt ( ) ; } else { let shiftLeft = 64 - l ; const v = data [ j ] . shiftRightUnsigned ( l ) . or ( data [ k ] . shiftLeft ( shiftLeft ) ) ; return v . and ( maxEntryValue ) . toInt ( ) ; } } /**
 * Legacy algorithm to seek block state from chunk
 */ function seekLegacy ( blocks , data , add , i ) { function getFromNibbleArray ( arr , index ) { const nibbled = index >>> 1 ; if ( ( index & 1 ) === 0 ) { return arr [ nibbled ] & 15 ; } else { return arr [ nibbled ] >>> 4 & 15 ; } } let additional = ! add ? 0 : getFromNibbleArray ( add , i ) ; return ( additional << 12 ) | ( ( blocks [ i ] & 255 ) << 4 ) | getFromNibbleArray ( data , i ) ; } const MULTIPLY_DE_BRUIJN_BIT_POSITION = [ 0 , 1 , 28 , 2 , 29 , 14 , 24 , 3 , 30 , 22 , 20 , 15 , 25 , 17 , 4 , 8 , 31 , 27 , 13 , 23 , 21 , 19 , 16 , 7 , 26 , 12 , 18 , 6 , 11 , 5 , 10 , 9 ] ; function log2DeBruijn ( value ) { function isPowerOfTwo ( v ) { return v !== 0 && ( v & v - 1 ) === 0 ; } function smallestEncompassingPowerOfTwo ( value ) { let i = value - 1 ; i = i | i >> 1 ; i = i | i >> 2 ; i = i | i >> 4 ; i = i | i >> 8 ; i = i | i >> 16 ; return i + 1 ; } value = isPowerOfTwo ( value ) ? value : smallestEncompassingPowerOfTwo ( value ) ; return MULTIPLY_DE_BRUIJN_BIT_POSITION [ Long . fromInt ( value ) . multiply ( 125613361 ) . shiftRight ( 27 ) . and ( 31 ) . low ] ; } function getChunkOffset ( buffer , x , z ) { x &= 31 ; z &= 31 ; let offsetBytesLocation = ( x + z * 32 ) * 4 ; let offsetBytes = buffer . slice ( offsetBytesLocation , offsetBytesLocation + 4 ) ; let offset = offsetBytes [ 0 ] << 16 | offsetBytes [ 1 ] << 8 | offsetBytes [ 2 ] ; let sectors = offsetBytes [ 3 ] ; if ( offset === 0 ) { return 0 ; } else { return offset * 4096 ; } } export class WorldReader { static async create ( path ) { return new WorldReader ( await openFileSystem ( path ) ) ; } constructor ( private fs ) { } param chunkX param chunkZ public async getRegionData ( chunkX , chunkZ ) { let path = this . fs . join ( "region" , `r.${ chunkX >> 5 }.${ chunkZ >> 5 }.mca` ) ; let buffer = await this . fs . readFile ( path ) ; let off = getChunkOffset ( buffer , chunkX , chunkZ ) ; let lengthBuf = buffer . slice ( off , off + 4 ) ; let length = lengthBuf [ 0 ] << 24 | lengthBuf [ 1 ] << 16 | lengthBuf [ 2 ] << 8 | lengthBuf [ 3 ] ; let format = buffer [ off + 4 ] ; if ( format !== 1 && format !== 2 ) { throw new Error ( `Illegal Chunk format ${ format } on (${ chunkX }, ${ chunkZ })!` ) ; } let compressed = format === 1 ? "gzip" as const : "deflate" as const ; let chunkData = buffer . slice ( off + 5 , off + 5 + length ) ; let data = await deserialize ( chunkData , { compressed } ) ; return data ; } /**
     * Read the level data
     */ public async getLevelData ( ) { return this . fs . readFile ( "level.dat" ) . then ( ( b ) => deserialize ( b , { compressed : "gzip" } ) ) . then ( ( d ) => d . Data ) ; } public async getPlayerData ( ) { const files = await this . fs . listFiles ( "playerdata" ) ; return Promise . all ( files . map ( ( f ) => this . fs . readFile ( this . fs . join ( "playerdata" , f ) ) . then ( ( b ) => deserialize < PlayerDataFrame > ( b ) ) ) ) ; } public async getAdvancementsData ( ) { const files = await this . fs . listFiles ( "advancements" ) ; return Promise . all ( files . map ( ( f ) => this . fs . readFile ( this . fs . join ( "advancements" , f ) ) . then ( ( b ) => deserialize < AdvancementDataFrame > ( b ) ) ) ) ; } } /**
 * The chunk index is a number in range [0, 4096), which is mapped position from (0,0,0) to (16,16,16) inside the chunk.
 */ export type ChunkIndex = number ; param x param y param z export function getIndexInChunk ( x , y , z ) { return ( y & 15 ) << 8 | ( z & 15 ) << 4 | ( x & 15 ) ; } param index export function getCoordFromIndex ( index ) { let x = index & 15 ; let y = ( index >>> 8 ) & 15 ; let z = ( index >>> 4 ) & 15 ; return { x , y , z } ; } export namespace RegionReader { param region param chunkY export function getSection ( region , chunkY ) { return region . Level . Sections [ 0 ] . Y === 0 ? region . Level . Sections [ chunkY ] : region . Level . Sections [ chunkY + 1 ] ; } param section param reader export function walkBlockStateId ( section , reader ) { let seekFunc ; if ( "Blocks" in section ) { let add = section . Add ; let data = section . Data ; let blocks = section . Blocks ; seekFunc = ( i ) => seekLegacy ( blocks , data , add , i ) ; } else { let blockStates = section . BlockStates ; let vector = createBitVector ( blockStates , computeBitLen ( section . Palette , blockStates ) ) ; seekFunc = ( i ) => vector [ i ] ; } for ( let i = 0 ; i < 4096 ; ++ i ) { let x = i & 15 ; let y = ( i >>> 8 ) & 15 ; let z = ( i >>> 4 ) & 15 ; let id = seekFunc ( i ) ; reader ( x , y , z , id ) ; } } param section param index export function seekBlockStateId ( section , index ) { if ( "BlockStates" in section ) { const blockStates = section . BlockStates ; const bitLen = computeBitLen ( section . Palette , blockStates ) ; return seek ( blockStates , bitLen , index ) ; } return seekLegacy ( section . Blocks , section . Data , section . Add , index ) ; } param section param index export function seekBlockState ( section , index ) { const blockStates = section . BlockStates ; const bitLen = computeBitLen ( section . Palette , blockStates ) ; return section . Palette [ seek ( section . BlockStates , bitLen , index ) ] ; } } /**
 * The Minecraft provided block state info. Only presented in the version >= 1.13 chunk data.
 */ export interface BlockStateData { Name : string ; Properties : { [ key ] : string ; } ; } export enum GameType { NON = - 1 , SURVIVAL = 0 , CREATIVE = 1 , ADVENTURE = 2 , SPECTATOR = 3 } export interface PlayerDataFrame { UUIDLeast : Long ; UUIDMost : Long ; DataVersion : number ; Pos : [ number , number , number ] ; Rotation : [ number , number , number ] ; Motion : [ number , number , number ] ; Dimension : number ; SpawnX : number ; SpawnY : number ; SpawnZ : number ; playerGameType : number ; Attributes : Array < { Base : number ; Name : string ; } > ; HurtTime : number ; DeathTime : number ; HurtByTimestamp : number ; SleepTimer : number ; SpawnForced : number ; FallDistance : number ; SelectedItemSlot : number ; seenCredits : number ; Air : number ; AbsorptionAmount : number ; Invulnerable : number ; FallFlying : number ; PortalCooldown : number ; Health : number ; OnGround : number ; XpLevel : number ; Score : number ; Sleeping : number ; Fire : number ; XpP : number ; XpSeed : number ; XpTotal : number ; foodLevel : number ; foodExhaustionLevel : number ; foodTickTimer : number ; foodSaturationLevel : number ; recipeBook : { isFilteringCraftable : number ; isGuiOpen : number ; } ; abilities : { invulnerable : number ; mayfly : number ; instabuild : number ; walkSpeed : number ; mayBuild : number ; flying : number ; flySpeed : number ; } ; } type StringBoolean = "true" | "false" ; export interface LevelDataFrame { BorderCenterX : number ; BorderCenterZ : number ; BorderDamagePerBlock : number ; BorderSafeZone : number ; BorderSize : number ; BorderSizeLerpTarget : number ; BorderSizeLerpTime : Long ; BorderWarningBlocks : number ; BorderWarningTime : number ; DataVersion : number ; DayTime : Long ; Difficulty : number ; DifficultyLocked : number ; DimensionData : { [ dimension ] : { DragonFight : { Gateways : number [ ] ; DragonKilled : number ; PreviouslyKilled : number ; ExitPortalLocation ? : [ number , number , number ] ; } ; } ; } ; GameRules : { doTileDrops : StringBoolean ; doFireTick : StringBoolean ; gameLoopFunction : string ; maxCommandChainLength : string ; reducedDebugInfo : string ; naturalRegeneration : string ; disableElytraMovementCheck : string ; doMobLoot : StringBoolean ; announceAdvancements : string ; keepInventory : StringBoolean ; doEntityDrops : StringBoolean ; doLimitedCrafting : StringBoolean ; mobGriefing : StringBoolean ; randomTickSpeed : string ; commandBlockOutput : string ; spawnRadius : string ; doMobSpawning : StringBoolean ; maxEntityCramming : string ; logAdminCommands : string ; spectatorsGenerateChunks : string ; doWeatherCycle : StringBoolean ; sendCommandFeedback : string ; doDaylightCycle : StringBoolean ; showDeathMessages : StringBoolean ; } ; GameType : GameType ; LastPlayed : Long ; LevelName : string ; MapFeatures : number ; Player : PlayerDataFrame ; RandomSeed : Long ; readonly SizeOnDisk : Long ; SpawnX : number ; SpawnY : number ; SpawnZ : number ; Time : Long ; Version : { Snapshot : number ; Id : number ; Name : string ; } ; allowCommands : number ; clearWeatherTime : number ; generatorName : "default" | "flat" | "largeBiomes" | "amplified" | "buffet" | "debug_all_block_states" | string ; generatorOptions : string ; generatorVersion : number ; hardcore : number ; initialized : number ; rainTime : number ; raining : number ; thunderTime : number ; thundering : number ; version : number ; } export interface AdvancementDataFrame { display ? : { background ? : string ; description : object | string ; show_toast : boolean ; announce_to_chat : boolean ; hidden : boolean ; } ; parent ? : string ; criteria : { [ name ] : { trigger : string ; conditions : { } ; } ; } ; requirements : string [ ] ; rewards : { recipes : string [ ] ; loot : string [ ] ; experience : number ; function : string ; } ; } export interface ItemStackDataFrame { Slot : number ; id : string ; Count : number ; Damage : number ; tag ? : { Unbreakable : number ; CanDestroy : string [ ] ; CanPlaceOn : string [ ] ; BlockEntityTag : { } ; ench : Array < { id : number ; lvl : number ; } > ; StoredEnchantments : Array < { id : number ; lvl : number ; } > ; RepairCost : number ; AttributeModifiers : Array < { AttributeName : string ; Name : string ; Slot : string ; Operation : number ; Amount : number ; UUIDMost : Long ; UUIDLeast : Long ; } > ; CustomPotionEffects : Array < { Id : number ; Amplifier : number ; Duration : number ; Ambient : number ; ShowParticles : number ; } > ; Potion : string ; CustomPotionColor : number ; display : Array < { color : number ; Name : string ; LocName : string ; Lore : string [ ] ; } > ; HideFlags : number ; resolved : number ; /**
         * The copy tier of the book. 0 = original, number = copy of original, number = copy of copy, number = tattered.
         * If the value is greater than number, the book cannot be copied. Does not exist for original books.
         * If this tag is missing, it is assumed the book is an original. 'Tattered' is unused in normal gameplay, and functions identically to the 'copy of copy' tier.
         */ generation : number ; author : string ; title : string ; /**
         * A single page in the book. If generated by writing in a book and quill in-game, each page is a string in double quotes and uses the escape sequences \" for a double quote,
         * for a line break and \\ for a backslash. If created by commands or external tools, a page can be a serialized JSON object or an array of strings and/or objects (see Commands#Raw JSON text) or an unescaped string.
         */ pages : string [ ] ; } ; } export interface TileEntityDataFrame { x : number ; y : number ; z : number ; Items : ItemStackDataFrame [ ] ; id : string ; [ key ] : any ; } export type LegacyRegionSectionDataFrame = { Blocks : Array < number > ; Data : Array < number > ; Add : Array < number > ; BlockLight : number [ ] ; SkyLight : number [ ] ; Y : number ; } ; export type NewRegionSectionDataFrame = { BlockStates : Long [ ] ; Palette : Array < BlockStateData > ; Data : number [ ] ; BlockLight : number [ ] ; SkyLight : number [ ] ; Y : number ; } ; export type RegionSectionDataFrame = LegacyRegionSectionDataFrame | NewRegionSectionDataFrame ; export interface RegionDataFrame { Level : { xPos : number ; zPos : number ; LightPopulated : number ; LastUpdate : Long ; InhabitedTime : Long ; HeightMap : number [ ] ; Biomes : number [ ] ; Entities : object [ ] ; TileEntities : TileEntityDataFrame [ ] ; Sections : RegionSectionDataFrame [ ] ; } ; DataVersion : number ; ForgeDataVersion ? : number ; }