Increase block limit to 4096. Change Schematic format slightly to store extended blockIDs.

InfdevOldWorld now allocates Blocks as a 16-bit array and reads Add into the top 8 bits if present. MCSchematic also allocates Blocks as uint16 and uses WorldEdit's extension to the schematic format to store the Add arrays.

Conflicts:
	test/extended_id_test.py
This commit is contained in:
David Vierra 2013-01-08 11:27:59 -10:00
parent 3095eacaa3
commit b824cfbfaf
8 changed files with 128 additions and 68 deletions

View File

@ -14,7 +14,7 @@ def convertBlocks(destLevel, sourceLevel, blocks, blockData):
def sourceMaskFunc(blocksToCopy): def sourceMaskFunc(blocksToCopy):
if blocksToCopy is not None: if blocksToCopy is not None:
typemask = numpy.zeros(256, dtype='bool') typemask = numpy.zeros(materials.id_limit, dtype='bool')
typemask[blocksToCopy] = 1 typemask[blocksToCopy] = 1
def maskedSourceMask(sourceBlocks): def maskedSourceMask(sourceBlocks):

View File

@ -1,4 +1,6 @@
import logging import logging
from pymclevel import materials
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
import numpy import numpy
@ -8,7 +10,7 @@ import blockrotation
from entity import TileEntity from entity import TileEntity
def blockReplaceTable(blocksToReplace): def blockReplaceTable(blocksToReplace):
blocktable = numpy.zeros((256, 16), dtype='bool') blocktable = numpy.zeros((materials.id_limit, 16), dtype='bool')
for b in blocksToReplace: for b in blocksToReplace:
if b.hasVariants: if b.hasVariants:
blocktable[b.ID, b.blockData] = True blocktable[b.ID, b.blockData] = True

View File

@ -1,3 +1,4 @@
import materials
from materials import alphaMaterials from materials import alphaMaterials
from numpy import arange, zeros from numpy import arange, zeros
@ -474,9 +475,9 @@ rotationClasses.append(Vines)
def masterRotationTable(attrname): def masterRotationTable(attrname):
# compute a 256x16 table mapping each possible blocktype/data combination to # compute a materials.id_limitx16 table mapping each possible blocktype/data combination to
# the resulting data when the block is rotated # the resulting data when the block is rotated
table = zeros((256, 16), dtype='uint8') table = zeros((materials.id_limit, 16), dtype='uint8')
table[:] = arange(16, dtype='uint8') table[:] = arange(16, dtype='uint8')
for cls in rotationClasses: for cls in rotationClasses:
if hasattr(cls, attrname): if hasattr(cls, attrname):

View File

@ -113,7 +113,7 @@ class AnvilChunkData(object):
self.root_tag = root_tag self.root_tag = root_tag
self.dirty = False self.dirty = False
self.Blocks = zeros((16, 16, world.Height), 'uint8') # xxx uint16? self.Blocks = zeros((16, 16, world.Height), 'uint16')
self.Data = zeros((16, 16, world.Height), 'uint8') self.Data = zeros((16, 16, world.Height), 'uint8')
self.BlockLight = zeros((16, 16, world.Height), 'uint8') self.BlockLight = zeros((16, 16, world.Height), 'uint8')
self.SkyLight = zeros((16, 16, world.Height), 'uint8') self.SkyLight = zeros((16, 16, world.Height), 'uint8')
@ -152,6 +152,7 @@ class AnvilChunkData(object):
for sec in self.root_tag["Level"].pop("Sections", []): for sec in self.root_tag["Level"].pop("Sections", []):
y = sec["Y"].value * 16 y = sec["Y"].value * 16
for name in "Blocks", "Data", "SkyLight", "BlockLight": for name in "Blocks", "Data", "SkyLight", "BlockLight":
arr = getattr(self, name) arr = getattr(self, name)
secarray = sec[name].value secarray = sec[name].value
@ -163,6 +164,11 @@ class AnvilChunkData(object):
arr[..., y:y + 16] = secarray.swapaxes(0, 2) arr[..., y:y + 16] = secarray.swapaxes(0, 2)
tag = sec.get("Add")
if tag is not None:
tag.value.shape = (16, 16, 8)
add = unpackNibbleArray(tag.value)
self.Blocks[...,y:y + 16] |= (array(add, 'uint16') << 8).swapaxes(0, 2)
def savedTagData(self): def savedTagData(self):
""" does not recalculate any data or light """ """ does not recalculate any data or light """
@ -188,7 +194,11 @@ class AnvilChunkData(object):
BlockLight = packNibbleArray(BlockLight) BlockLight = packNibbleArray(BlockLight)
SkyLight = packNibbleArray(SkyLight) SkyLight = packNibbleArray(SkyLight)
section['Blocks'] = nbt.TAG_Byte_Array(array(Blocks)) add = Blocks >> 8
if add.any():
section["Add"] = nbt.TAG_Byte_Array(packNibbleArray(add).astype('uint8'))
section['Blocks'] = nbt.TAG_Byte_Array(array(Blocks, 'uint8'))
section['Data'] = nbt.TAG_Byte_Array(array(Data)) section['Data'] = nbt.TAG_Byte_Array(array(Data))
section['BlockLight'] = nbt.TAG_Byte_Array(array(BlockLight)) section['BlockLight'] = nbt.TAG_Byte_Array(array(BlockLight))
section['SkyLight'] = nbt.TAG_Byte_Array(array(SkyLight)) section['SkyLight'] = nbt.TAG_Byte_Array(array(SkyLight))

View File

@ -53,7 +53,7 @@ class Block(object):
r = r[self.blockData] r = r[self.blockData]
return r return r
id_limit = 4096
class MCMaterials(object): class MCMaterials(object):
defaultColor = (0xc9, 0x77, 0xf0, 0xff) defaultColor = (0xc9, 0x77, 0xf0, 0xff)
defaultBrightness = 0 defaultBrightness = 0
@ -67,21 +67,22 @@ class MCMaterials(object):
self.defaultName = defaultName self.defaultName = defaultName
self.blockTextures = zeros((256, 16, 6, 2), dtype='uint8')
self.blockTextures[:] = self.defaultTexture
self.names = [[defaultName] * 16 for i in range(256)]
self.aka = [[""] * 16 for i in range(256)]
self.type = [["NORMAL"] * 16] * 256 self.blockTextures = zeros((id_limit, 16, 6, 2), dtype='uint8')
self.blockTextures[:] = self.defaultTexture
self.names = [[defaultName] * 16 for i in range(id_limit)]
self.aka = [[""] * 16 for i in range(id_limit)]
self.type = [["NORMAL"] * 16] * id_limit
self.blocksByType = defaultdict(list) self.blocksByType = defaultdict(list)
self.allBlocks = [] self.allBlocks = []
self.blocksByID = {} self.blocksByID = {}
self.lightEmission = zeros(256, dtype='uint8') self.lightEmission = zeros(id_limit, dtype='uint8')
self.lightEmission[:] = self.defaultBrightness self.lightEmission[:] = self.defaultBrightness
self.lightAbsorption = zeros(256, dtype='uint8') self.lightAbsorption = zeros(id_limit, dtype='uint8')
self.lightAbsorption[:] = self.defaultOpacity self.lightAbsorption[:] = self.defaultOpacity
self.flatColors = zeros((256, 16, 4), dtype='uint8') self.flatColors = zeros((id_limit, 16, 4), dtype='uint8')
self.flatColors[:] = self.defaultColor self.flatColors[:] = self.defaultColor
self.idStr = {} self.idStr = {}
@ -153,7 +154,8 @@ class MCMaterials(object):
import pkg_resources import pkg_resources
f = pkg_resources.resource_stream(__name__, filename) f = pkg_resources.resource_stream(__name__, filename)
except (ImportError, IOError): except (ImportError, IOError), e:
print "Cannot get resource_stream for ", filename, e
root = os.environ.get("PYMCLEVEL_YAML_ROOT", "pymclevel") # fall back to cwd as last resort root = os.environ.get("PYMCLEVEL_YAML_ROOT", "pymclevel") # fall back to cwd as last resort
path = join(root, filename) path = join(root, filename)
@ -752,12 +754,12 @@ pocketMaterials.NetherReactorUsed = pocketMaterials[247, 1]
# b.ID, b.blockData) # b.ID, b.blockData)
# for b in sorted(mats.pocketMaterials.allBlocks)]) # for b in sorted(mats.pocketMaterials.allBlocks)])
_indices = rollaxis(indices((256, 16)), 0, 3) _indices = rollaxis(indices((id_limit, 16)), 0, 3)
def _filterTable(filters, unavailable, default=(0, 0)): def _filterTable(filters, unavailable, default=(0, 0)):
# a filter table is a 256x16 table of (ID, data) pairs. # a filter table is a id_limit table of (ID, data) pairs.
table = zeros((256, 16, 2), dtype='uint8') table = zeros((id_limit, 16, 2), dtype='uint8')
table[:] = _indices table[:] = _indices
for u in unavailable: for u in unavailable:
try: try:

5
mce.py
View File

@ -1,6 +1,7 @@
#!/usr/bin/env python #!/usr/bin/env python
import mclevelbase import mclevelbase
import mclevel import mclevel
import materials
import infiniteworld import infiniteworld
import sys import sys
import os import os
@ -269,7 +270,7 @@ class mce(object):
return blockInfo return blockInfo
def readBlocksToCopy(self, command): def readBlocksToCopy(self, command):
blocksToCopy = range(256) blocksToCopy = range(materials.id_limit)
while len(command): while len(command):
word = command.pop() word = command.pop()
if word == "noair": if word == "noair":
@ -460,7 +461,7 @@ class mce(object):
if i % 100 == 0: if i % 100 == 0:
logging.info("Chunk {0}...".format(i)) logging.info("Chunk {0}...".format(i))
for blockID in range(256): for blockID in range(materials.id_limit):
block = self.level.materials.blockWithID(blockID, 0) block = self.level.materials.blockWithID(blockID, 0)
if block.hasVariants: if block.hasVariants:
for data in range(16): for data in range(16):

View File

@ -17,7 +17,7 @@ from level import MCLevel, EntityLevel
from materials import alphaMaterials, MCMaterials, namedMaterials from materials import alphaMaterials, MCMaterials, namedMaterials
from mclevelbase import exhaust from mclevelbase import exhaust
import nbt import nbt
from numpy import array, swapaxes, uint8, zeros from numpy import array, swapaxes, uint8, zeros, resize
log = getLogger(__name__) log = getLogger(__name__)
@ -46,9 +46,6 @@ class MCSchematic (EntityLevel):
I'm not sure what happens when I try to re-save a rotated schematic. I'm not sure what happens when I try to re-save a rotated schematic.
""" """
# if(shape != None):
# self.setShape(shape)
if filename: if filename:
self.filename = filename self.filename = filename
if None is root_tag and os.path.exists(filename): if None is root_tag and os.path.exists(filename):
@ -68,7 +65,39 @@ class MCSchematic (EntityLevel):
self.materials = namedMaterials[self.Materials] self.materials = namedMaterials[self.Materials]
else: else:
root_tag["Materials"] = nbt.TAG_String(self.materials.name) root_tag["Materials"] = nbt.TAG_String(self.materials.name)
self.shapeChunkData()
w = self.root_tag["Width"].value
l = self.root_tag["Length"].value
h = self.root_tag["Height"].value
self._Blocks = self.root_tag["Blocks"].value.astype('uint16').reshape(h, l, w)
del self.root_tag["Blocks"]
if "AddBlocks" in self.root_tag:
# Use WorldEdit's "AddBlocks" array to load and store the 4 high bits of a block ID.
# Unlike Minecraft's NibbleArrays, this array stores the first block's bits in the
# 4 high bits of the first byte.
size = (h * l * w)
# If odd, add one to the size to make sure the adjacent slices line up.
add = zeros(size + (size & 1), 'uint16')
# Fill the even bytes with data
add[::2] = self.root_tag["AddBlocks"].value
# Copy the low 4 bits to the odd bytes
add[1::2] = add[::2] & 0xf
# Shift the even bytes down
add[::2] >>= 4
# Shift every byte up before merging it with Blocks
add <<= 8
self._Blocks |= add[:size].reshape(h, l, w)
del self.root_tag["AddBlocks"]
self.root_tag["Data"].value = self.root_tag["Data"].value.reshape(h, l, w)
else: else:
assert shape is not None assert shape is not None
@ -81,12 +110,11 @@ class MCSchematic (EntityLevel):
root_tag["TileEntities"] = nbt.TAG_List() root_tag["TileEntities"] = nbt.TAG_List()
root_tag["Materials"] = nbt.TAG_String(self.materials.name) root_tag["Materials"] = nbt.TAG_String(self.materials.name)
root_tag["Blocks"] = nbt.TAG_Byte_Array(zeros((shape[1], shape[2], shape[0]), uint8)) self._Blocks = zeros((shape[1], shape[2], shape[0]), 'uint16')
root_tag["Data"] = nbt.TAG_Byte_Array(zeros((shape[1], shape[2], shape[0]), uint8)) root_tag["Data"] = nbt.TAG_Byte_Array(zeros((shape[1], shape[2], shape[0]), uint8))
self.root_tag = root_tag self.root_tag = root_tag
self.packUnpack()
self.root_tag["Data"].value &= 0xF # discard high bits self.root_tag["Data"].value &= 0xF # discard high bits
@ -99,11 +127,33 @@ class MCSchematic (EntityLevel):
self.Materials = self.materials.name self.Materials = self.materials.name
self.packUnpack() self.root_tag["Blocks"] = nbt.TAG_Byte_Array(self._Blocks.astype('uint8'))
add = self._Blocks >> 8
if add.any():
# WorldEdit AddBlocks compatibility.
# The first 4-bit value is stored in the high bits of the first byte.
# Increase odd size by one to align slices.
packed_add = zeros(add.size + (add.size & 1), 'uint8')
packed_add[:-(add.size & 1)] = add.ravel()
# Shift even bytes to the left
packed_add[::2] <<= 4
# Merge odd bytes into even bytes
packed_add[::2] |= packed_add[1::2]
# Save only the even bytes, now that they contain the odd bytes in their lower bits.
packed_add = packed_add[0::2]
self.root_tag["AddBlocks"] = nbt.TAG_Byte_Array(packed_add)
with open(filename, 'wb') as chunkfh: with open(filename, 'wb') as chunkfh:
self.root_tag.save(chunkfh) self.root_tag.save(chunkfh)
self.packUnpack() del self.root_tag["Blocks"]
self.root_tag.pop("AddBlocks", None)
def __str__(self): def __str__(self):
return u"MCSchematic(shape={0}, materials={2}, filename=\"{1}\")".format(self.size, self.filename or u"", self.Materials) return u"MCSchematic(shape={0}, materials={2}, filename=\"{1}\")".format(self.size, self.filename or u"", self.Materials)
@ -124,11 +174,11 @@ class MCSchematic (EntityLevel):
@property @property
def Blocks(self): def Blocks(self):
return self.root_tag["Blocks"].value return swapaxes(self._Blocks, 0, 2)
@property @property
def Data(self): def Data(self):
return self.root_tag["Data"].value return swapaxes(self.root_tag["Data"].value, 0, 2)
@property @property
def Entities(self): def Entities(self):
@ -152,19 +202,6 @@ class MCSchematic (EntityLevel):
def _isTagLevel(cls, root_tag): def _isTagLevel(cls, root_tag):
return "Schematic" == root_tag.name return "Schematic" == root_tag.name
def shapeChunkData(self):
w = self.root_tag["Width"].value
l = self.root_tag["Length"].value
h = self.root_tag["Height"].value
self.root_tag["Blocks"].value.shape = (h, l, w)
self.root_tag["Data"].value.shape = (h, l, w)
def packUnpack(self):
self.root_tag["Blocks"].value = swapaxes(self.root_tag["Blocks"].value, 0, 2) # yzx to xzy
self.root_tag["Data"].value = swapaxes(self.root_tag["Data"].value, 0, 2) # yzx to xzy
def _update_shape(self): def _update_shape(self):
root_tag = self.root_tag root_tag = self.root_tag
shape = self.Blocks.shape shape = self.Blocks.shape
@ -174,8 +211,8 @@ class MCSchematic (EntityLevel):
def rotateLeft(self): def rotateLeft(self):
self.root_tag["Blocks"].value = swapaxes(self.Blocks, 1, 0)[:, ::-1, :] # x=z; z=-x self._Blocks = swapaxes(self._Blocks, 1, 0)[:, ::-1, :] # x=z; z=-x
self.root_tag["Data"].value = swapaxes(self.Data, 1, 0)[:, ::-1, :] # x=z; z=-x self.root_tag["Data"].value = swapaxes(self.root_tag["Data"].value, 1, 0)[:, ::-1, :] # x=z; z=-x
self._update_shape() self._update_shape()
blockrotation.RotateLeft(self.Blocks, self.Data) blockrotation.RotateLeft(self.Blocks, self.Data)
@ -213,20 +250,20 @@ class MCSchematic (EntityLevel):
def roll(self): def roll(self):
" xxx rotate stuff " " xxx rotate stuff "
self.root_tag["Blocks"].value = swapaxes(self.Blocks, 2, 0)[:, :, ::-1] # x=z; z=-x self._Blocks = swapaxes(self.Blocks, 2, 0)[:, :, ::-1] # x=z; z=-x
self.root_tag["Data"].value = swapaxes(self.Data, 2, 0)[:, :, ::-1] self.root_tag["Data"].value = swapaxes(self.root_tag["Data"].value, 2, 0)[:, :, ::-1]
self._update_shape() self._update_shape()
def flipVertical(self): def flipVertical(self):
" xxx delete stuff " " xxx delete stuff "
blockrotation.FlipVertical(self.Blocks, self.Data) blockrotation.FlipVertical(self.Blocks, self.Data)
self.root_tag["Blocks"].value = self.Blocks[:, :, ::-1] # y=-y self._Blocks = self.Blocks[:, :, ::-1] # y=-y
self.root_tag["Data"].value = self.Data[:, :, ::-1] self.root_tag["Data"].value = self.root_tag["Data"].value[:, :, ::-1]
def flipNorthSouth(self): def flipNorthSouth(self):
blockrotation.FlipNorthSouth(self.Blocks, self.Data) blockrotation.FlipNorthSouth(self.Blocks, self.Data)
self.root_tag["Blocks"].value = self.Blocks[::-1, :, :] # x=-x self._Blocks = self.Blocks[::-1, :, :] # x=-x
self.root_tag["Data"].value = self.Data[::-1, :, :] self.root_tag["Data"].value = self.root_tag["Data"].value[::-1, :, :]
northSouthPaintingMap = [0, 3, 2, 1] northSouthPaintingMap = [0, 3, 2, 1]
@ -249,10 +286,9 @@ class MCSchematic (EntityLevel):
tileEntity["x"].value = self.Width - tileEntity["x"].value - 1 tileEntity["x"].value = self.Width - tileEntity["x"].value - 1
def flipEastWest(self): def flipEastWest(self):
" xxx flip entities "
blockrotation.FlipEastWest(self.Blocks, self.Data) blockrotation.FlipEastWest(self.Blocks, self.Data)
self.root_tag["Blocks"].value = self.Blocks[:, ::-1, :] # z=-z self._Blocks = self._Blocks[:, ::-1, :] # z=-z
self.root_tag["Data"].value = self.Data[:, ::-1, :] self.root_tag["Data"].value = self.root_tag["Data"].value[:, ::-1, :]
eastWestPaintingMap = [2, 1, 0, 3] eastWestPaintingMap = [2, 1, 0, 3]
@ -271,17 +307,6 @@ class MCSchematic (EntityLevel):
for tileEntity in self.TileEntities: for tileEntity in self.TileEntities:
tileEntity["z"].value = self.Length - tileEntity["z"].value - 1 tileEntity["z"].value = self.Length - tileEntity["z"].value - 1
def setShape(self, shape):
"""shape is a tuple of (width, height, length). sets the
schematic's properties and clears the block and data arrays"""
x, y, z = shape
shape = (x, z, y)
self.root_tag["Blocks"].value = zeros(dtype='uint8', shape=shape)
self.root_tag["Data"].value = zeros(dtype='uint8', shape=shape)
self.shapeChunkData()
def setBlockDataAt(self, x, y, z, newdata): def setBlockDataAt(self, x, y, z, newdata):
if x < 0 or y < 0 or z < 0: if x < 0 or y < 0 or z < 0:

View File

@ -1,8 +1,27 @@
from pymclevel.schematic import MCSchematic from pymclevel.schematic import MCSchematic
from pymclevel import MCInfdevOldLevel
from templevel import TempLevel
__author__ = 'Rio' __author__ = 'Rio'
def test_schematic_extended_ids(): def test_schematic_extended_ids():
s = MCSchematic(shape=(3, 2, 2)) s = MCSchematic(shape=(1, 1, 5))
s.Blocks[0,0,0] = 2048 s.Blocks[0,0,0] = 2048
temp = TempLevel("schematic", createFunc=s.saveToFile)
s = temp.level
assert s.Blocks[0,0,0] == 2048 assert s.Blocks[0,0,0] == 2048
def test_alpha_extended_ids():
temp = TempLevel("alpha", createFunc=lambda f: MCInfdevOldLevel(f, create=True))
level = temp.level
level.createChunk(0, 0)
level.setBlockAt(0,2,5, 2048)
level.saveInPlace()
level.close()
level = MCInfdevOldLevel(filename=level.filename)
assert level.blockAt(0,2,5) == 2048