implemented 1.12.2 format, sorta

still broken but it's progress!
This commit is contained in:
əlemi 2022-01-15 01:57:40 +01:00
parent b0b0e2dcfa
commit 0ef56df704

View file

@ -1,17 +1,19 @@
import io import io
import math
import logging
from typing import Dict, Tuple, Any from typing import Dict, Tuple, Any
import numpy as np import numpy as np
from aiocraft.mc.types import VarInt, Short, UnsignedByte, Type from aiocraft.mc.types import VarInt, Short, UnsignedByte, Type, Context
class BitStream: class BitStream:
data : bytes data : bytes
cursor : int cursor : int
size : int size : int
def __init__(self, data:bytes, size:int=-1): def __init__(self, data:bytes, size:int):
self.data = data self.data = data
self.cursor = 0 self.cursor = 0
self.size = size if size > 0 else len(self.data) * 8 self.size = size if size > 0 else len(self.data) * 8
@ -21,95 +23,134 @@ class BitStream:
def read(self, size:int) -> int: def read(self, size:int) -> int:
if len(self) < size: if len(self) < size:
raise ValueError("Not enough bits") raise ValueError(f"Not enough bits ({len(self)} left, {size} requested)")
aligned_size = (size//8)+1 start_byte = (self.cursor//8)
end_byte = math.ceil((self.cursor + size) / 8) + 1
buf = int.from_bytes( buf = int.from_bytes(
self.data[self.cursor:aligned_size], self.data[start_byte:end_byte],
byteorder='little', signed=False byteorder='little', signed=False
) )
cut_right = 8 - ((self.cursor + size) % 8)
self.cursor += size self.cursor += size
delta = aligned_size - size return ( buf >> cut_right ) & ( 0xFF >> (8 - (size%8)))
return ( buf << delta ) >> delta
class PalettedContainer(Type): class PalettedContainer(Type):
pytype : type pytype : type
threshold : int threshold : int
maxsize : int size : int
def __init__(self, threshold:int, maxsize:int): def __init__(self, threshold:int, size:int):
self.threshold = threshold self.threshold = threshold
self.maxsize = maxsize self.size = size
def write(self, data, buffer:io.BytesIO, ctx:object=None): def write(self, data, buffer:io.BytesIO, ctx:Context):
raise NotImplementedError raise NotImplementedError
def read(self, buffer:io.BytesIO, ctx:object=None): def read(self, buffer:io.BytesIO, ctx:Context):
bits = UnsignedByte.read(buffer, ctx=ctx) bits = max(UnsignedByte.read(buffer, ctx=ctx), 4)
if bits > 13:
raise ValueError("Bits Per Bit too high : %d", bits)
palette = np.empty((0,), dtype='int32') palette = np.empty((0,), dtype='int32')
palette_len = VarInt.read(buffer, ctx=ctx)
if bits == 0: if bits == 0:
value = VarInt.read(buffer, ctx=ctx) value = palette_len
elif bits < self.threshold: elif bits < self.threshold:
palette_len = VarInt.read(buffer, ctx=ctx)
palette = np.zeros((palette_len,), dtype='int32') palette = np.zeros((palette_len,), dtype='int32')
for i in range(palette_len): for i in range(palette_len):
palette[i] = VarInt.read(buffer) palette[i] = VarInt.read(buffer, ctx=ctx)
size = VarInt.read(buffer, ctx=ctx) size = VarInt.read(buffer, ctx=ctx)
stream = BitStream(buffer.read(size * 8)) stream = BitStream(buffer.read(size * 8), size*8*8) # a Long is 64 bits long
section = np.zeros((self.maxsize,), dtype='int32') section = np.zeros((self.size, self.size, self.size), dtype='int32')
index = 0 index = 0
while index < self.maxsize and len(stream) > 0: for y in range(self.size):
val = stream.read(bits) if bits > 0 else value for z in range(self.size):
section[index] = palette[val] if bits < self.threshold and bits > 0 else val for x in range(self.size):
index+=1 val = stream.read(bits) if bits > 0 else value
if bits < self.threshold and bits > 0 and val > len(palette):
logging.warning("Expected index %d in palette of size %d", val, len(palette))
section[x][y][z] = palette[val] if bits < self.threshold and bits > 0 and val < len(palette) else val
return section return section
BiomeContainer = PalettedContainer(4, 64) BiomeContainer = PalettedContainer(4, 4)
BlockStateContainer = PalettedContainer(9, 4096) BlockStateContainer = PalettedContainer(9, 16)
class ChunkSectionType(Type): class NewChunkSectionType(Type):
pytype : type pytype : type
def write(self, data, buffer:io.BytesIO, ctx:object=None): def write(self, data, buffer:io.BytesIO, ctx:Context):
raise NotImplementedError raise NotImplementedError
def read(self, buffer:io.BytesIO, ctx:object=None): def read(self, buffer:io.BytesIO, ctx:Context):
block_count = Short.read(buffer) block_count = Short.read(buffer, ctx=ctx)
block_states = BlockStateContainer.read(buffer) block_states = BlockStateContainer.read(buffer, ctx=ctx)
biomes = BiomeContainer.read(buffer) biomes = BiomeContainer.read(buffer, ctx=ctx)
return ( return (
block_count, block_count,
block_states.reshape((16, 16, 16)), block_states,
biomes.reshape((4, 4, 4)) biomes
) )
ChunkSection = ChunkSectionType() class OldChunkSectionType(Type):
pytype : type
def write(self, data, buffer:io.BytesIO, ctx:Context):
raise NotImplementedError
def read(self, buffer:io.BytesIO, ctx:Context):
section = BlockStateContainer.read(buffer, ctx=ctx)
block_light = np.empty((16, 16, 16), dtype='int32')
block_light_buffer = BitStream(buffer.read(2048), 2048*8)
for y in range(16):
for z in range(16):
for x in range(16):
block_light[x][y][z] = block_light_buffer.read(4)
sky_light = np.empty((16, 16, 16), dtype='int32')
if ctx.overworld:
sky_light_buffer = BitStream(buffer.read(2048), 2048*8)
for y in range(16):
for z in range(16):
for x in range(16):
sky_light[x][y][z] = sky_light_buffer.read(4)
return (
section,
block_light,
sky_light
)
ChunkSection = OldChunkSectionType()
class Chunk(Type): class Chunk(Type):
x : int x : int
z : int z : int
bitmask : int bitmask : int
ground_up_continuous : bool
blocks : np.ndarray blocks : np.ndarray
biomes : np.ndarray block_light : np.ndarray
block_count : int sky_light : np.ndarray
biomes: bytes
def __init__(self, x:int, z:int, bitmask:int): def __init__(self, x:int, z:int, bitmask:int, ground_up_continuous:bool):
self.x = x self.x = x
self.z = z self.z = z
self.bitmask = bitmask self.bitmask = bitmask
self.blocks = np.zeros((16, 256, 16)) self.blocks = np.zeros((16, 256, 16))
self.biomes = np.zeros((4, 64, 4)) self.block_light = np.zeros((16, 256, 16))
self.block_count = 0 self.sky_light = np.zeros((16, 256, 16))
self.ground_up_continuous = ground_up_continuous
def __getitem__(self, item:Any): def __getitem__(self, item:Any):
return self.blocks[item] return self.blocks[item]
def read(self, buffer:io.BytesIO, ctx:object=None): def read(self, buffer:io.BytesIO, ctx:Context):
for i in range(16): for i in range(16):
if (self.bitmask >> i) & 1: if (self.bitmask >> i) & 1:
block_count, block_states, biomes = ChunkSection.read(buffer) section, block_light, sky_light = ChunkSection.read(buffer, ctx=ctx)
self.block_count += block_count self.blocks[:, i*16 : (i+1)*16, :] = section
self.blocks[:, i*16 : (i+1)*16, :] = block_states self.block_light[:, i*16 : (i+1)*16, :] = block_light
self.biomes[:, i*4 : (i+1)*4, :] = biomes self.sky_light[:, i*16 : (i+1)*16, :] = sky_light
if self.ground_up_continuous:
self.biomes = buffer.read(256)
return self return self
class World: class World:
@ -121,11 +162,11 @@ class World:
def __getitem__(self, item:Tuple[int, int, int]): def __getitem__(self, item:Tuple[int, int, int]):
return self.get(*item) return self.get(*item)
def get(self, x:int, y:int, z:int): def get(self, x:int, y:int, z:int) -> int:
coord = (x//16, z//16) coord = (x//16, z//16)
if coord not in self.chunks: if coord not in self.chunks:
raise KeyError(f"Chunk {coord} not loaded") raise KeyError(f"Chunk {coord} not loaded")
return self.chunks[coord][x%16, y, z%16] return self.chunks[coord][int(x%16), int(y), int(z%16)]
def put(self, chunk:Chunk, x:int, z:int): def put(self, chunk:Chunk, x:int, z:int):
self.chunks[(x,z)] = chunk self.chunks[(x,z)] = chunk