Source code for amulet.utils.world_utils

from __future__ import annotations

import math
import sys
import gzip
from io import StringIO
from typing import Tuple
import numpy
from numpy import ndarray, zeros, uint8

Coordinates = Tuple[int, int]
DimensionCoordinates = Tuple[int, int, int]

SECTOR_BYTES = 4096
SECTOR_INTS = SECTOR_BYTES / 4
CHUNK_HEADER_SIZE = 5
VERSION_GZIP = 1
VERSION_DEFLATE = 2


[docs]def block_coords_to_chunk_coords( x: int, z: int, chunk_x_size: int = 16, chunk_z_size: int = 16 ) -> Coordinates: """ Converts the supplied block coordinates into chunk coordinates :param x: The x coordinate of the block :param z: The z coordinate of the block :param chunk_x_size: The dimension of the chunk in the x direction (Optional. Default 16) :param chunk_z_size: The dimension of the chunk in the z direction (Optional. Default 16) :return: The resulting chunk coordinates in (x, z) order """ return int(math.floor(x / chunk_x_size)), int(math.floor(z / chunk_z_size))
[docs]def chunk_coords_to_block_coords( x: int, z: int, chunk_x_size: int = 16, chunk_z_size: int = 16 ) -> Coordinates: """ Converts the supplied chunk coordinates into block coordinates :param x: The x coordinate of the chunk :param z: The z coordinate of the chunk :param chunk_x_size: The dimension of the chunk in the x direction (Optional. Default 16) :param chunk_z_size: The dimension of the chunk in the z direction (Optional. Default 16) :return: The resulting block coordinates in (x, z) order """ return x * chunk_x_size, z * chunk_z_size
[docs]def chunk_coords_to_region_coords(cx: int, cz: int) -> Coordinates: """ Converts the supplied chunk coordinates into region coordinates :param cx: The x coordinate of the chunk :param cz: The z coordinate of the chunk :return: The resulting region coordinates in (x, z) order """ return cx >> 5, cz >> 5
[docs]def region_coords_to_chunk_coords(rx: int, rz: int) -> Coordinates: """ Converts the supplied region coordinates into the minimum chunk coordinates of that region :param rx: The x coordinate of the region :param rz: The y coordinate of the region :return: The resulting minimum chunk coordinates of that region in (x, z) order """ return rx << 5, rz << 5
[docs]def blocks_slice_to_chunk_slice( blocks_slice: slice, chunk_shape: int, chunk_coord: int ) -> slice: """ Converts the supplied blocks slice into chunk slice :param blocks_slice: The slice of the blocks :param chunk_shape: The shape of the chunk in this direction :param chunk_coord: The coordinate of the chunk in this direction :return: The resulting chunk slice """ return slice( min(max(0, blocks_slice.start - chunk_coord * chunk_shape), chunk_shape), min(max(0, blocks_slice.stop - chunk_coord * chunk_shape), chunk_shape), )
[docs]def gunzip(data): """ Decompresses data that is in Gzip format """ return gzip.GzipFile(fileobj=StringIO(data)).read()
[docs]def from_nibble_array(arr: ndarray) -> ndarray: """ Unpacks a flat nibble array into a full size numpy array :param arr: The nibble array :return: The resulting array """ shape = arr.size new_arr = zeros((shape * 2), dtype=uint8) new_arr[::2] = arr & 0xF new_arr[1::2] = arr >> 4 return new_arr
[docs]def to_nibble_array(arr: ndarray) -> ndarray: """ packs a full size numpy array into a nibble array. :param arr: Full numpy array :return: The nibble array """ arr = arr.ravel() return (arr[::2] + (arr[1::2] << 4)).astype("uint8")
[docs]def decode_long_array(long_array: numpy.ndarray, size: int) -> numpy.ndarray: """ Decode an long array (from BlockStates or Heightmaps) :param long_array: Encoded long array :param size: int: The expected size of the returned array :return: Decoded array as numpy array """ long_array = long_array.astype(">q") bits_per_entry = (len(long_array) * 64) // size return numpy.packbits( numpy.pad( numpy.unpackbits(long_array[::-1].astype(">i8").view("uint8")).reshape( -1, bits_per_entry ), [(0, 0), (64 - bits_per_entry, 0)], "constant", ) ).view(dtype=">q")[::-1]
[docs]def encode_long_array(array: numpy.ndarray) -> numpy.ndarray: """ Encode an long array (from BlockStates or Heightmaps) :param array: A numpy array of the data to be encoded. :return: Encoded array as numpy array """ array = array.astype(">q") bits_per_entry = max(int(numpy.amax(array)).bit_length(), 2) return numpy.packbits( numpy.unpackbits(numpy.ascontiguousarray(array[::-1]).view("uint8")).reshape( -1, 64 )[:, -bits_per_entry:] ).view(dtype=">q")[::-1]
[docs]def get_size(obj, seen=None): """Recursively finds size of objects""" size = sys.getsizeof(obj) if seen is None: seen = set() obj_id = id(obj) if obj_id in seen: return 0 # Important mark as seen *before* entering recursion to gracefully handle # self-referential objects seen.add(obj_id) if isinstance(obj, dict): size += sum([get_size(v, seen) for v in obj.values()]) size += sum([get_size(k, seen) for k in obj.keys()]) elif hasattr(obj, "__dict__"): size += get_size(obj.__dict__, seen) elif hasattr(obj, "__iter__") and not isinstance(obj, (str, bytes, bytearray)): size += sum([get_size(i, seen) for i in obj]) return size
[docs]def get_smallest_dtype(arr: ndarray, uint: bool = True) -> int: """ Returns the smallest dtype (number) that the array can afford :param arr: The array to check on :param uint: Should the array fit in uint or not (default: True) :return: The number of bits all the elements can be represented with """ possible_dtypes = (2 ** x for x in range(3, 8)) max_number = numpy.amax(arr) if not uint: max_number = max_number * 2 if max_number == 0: max_number = 1 return next(dtype for dtype in possible_dtypes if dtype > math.log(max_number, 2))
def entity_position_to_chunk_coordinates( entity_coordinates: Tuple[float, float, float] ): return ( int(math.floor(entity_coordinates[0])) >> 4, int(math.floor(entity_coordinates[2])) >> 4, ) def fast_unique(array: numpy.ndarray) -> Tuple[numpy.ndarray, numpy.ndarray]: uni = numpy.unique(array) lut = numpy.zeros(numpy.amax(uni) + 1, dtype=numpy.uint) lut[uni] = numpy.arange(uni.size) inv = lut[array] return uni, inv