Fix [Tile]Entities not getting exported to schematics

Because nobody was really sure where they were being stored.
This commit is contained in:
David Vierra 2015-03-19 17:03:18 -10:00
parent 31dc9d49bb
commit 34285d3dc9
3 changed files with 141 additions and 95 deletions

View File

@ -129,7 +129,7 @@ def copyBlocksIter(destLevel, sourceLevel, sourceSelection, destinationPoint, bl
continue
# Recompute destSectionBox and intersect using the shape of destSection.Blocks
# after destChunk is loaded to work with odd shaped FakeChunks XXXXXXXXXXXX
# after destChunk is loaded to work with odd shaped FakeChunkDatas XXXXXXXXXXXX
destSectionBox = SectionBox(destCpos[0], destCy, destCpos[1], destSection)
intersect = destSectionBox.intersect(destBox)
if intersect.volume == 0:

View File

@ -23,6 +23,96 @@ log = getLogger(__name__)
GetBlocksResult = namedtuple("GetBlocksResult", ["Blocks", "Data", "BlockLight", "SkyLight", "Biomes"])
class FakeSection(object):
pass
class FakeChunkData(object):
dirty = False
dimension = NotImplemented #: The parent dimension that this chunk belongs to
cx = cz = NotImplemented #: This chunk's position as a tuple (cx, cz)
Width = Length = 16
dimName = ""
_heightMap = None
HeightMap = None
#
# @property
# def HeightMap(self):
# """
# Compute, cache, and return an artificial HeightMap for levels that don't provide one.
# :return: Array of height info.
# :rtype: ndarray(shape=(16, 16))
# """
# if self._heightMap is not None:
# return self._heightMap
# from mceditlib.heightmaps import computeChunkHeightMap
#
# self._heightMap = computeChunkHeightMap(self)
# return self._heightMap
#
# pass
def sectionPositions(self):
return self.dimension.bounds.sectionPositions(self.cx, self.cz)
@property
def Height(self):
return self.dimension.Height
@property
def bounds(self):
return BoundingBox((self.cx << 4, 0, self.cz << 4), self.size)
@property
def size(self):
return self.Width, self.Height, self.Length
def sectionPositions(self):
return range(0, (self.Height + 15) >> 4)
def chunkChanged(self, needsLighting=True):
"""
You are required to call this function after directly modifying any of a chunk's
arrays or its rootTag. Alternately, just set `chunk.dirty = True`
needsLighting is deprecated; Use the updateLights= argument
of setBlocks and other editing functions, or call updateLights(level, x, y, z) to
explicitly update lights yourself.
"""
self.dirty = True
@property
def blocktypes(self):
return self.dimension.blocktypes
def getSection(self, cy, create=False):
y = cy << 4
if y < self.bounds.miny or y >= self.bounds.maxy:
return None
section = FakeSection()
section.chunk = self
slices = numpy.s_[:, :, cy << 4:(cy + 1 << 4)]
if hasattr(self, 'Blocks'):
section.Blocks = self.Blocks[slices].swapaxes(0, 2)
else:
raise NotImplementedError("ChunkBase.getSection is only implemented for chunks providing a Blocks array")
if hasattr(self, 'Data'):
section.Data = self.Data[slices].swapaxes(0, 2)
if hasattr(self, 'BlockLight'):
section.BlockLight = self.BlockLight[slices].swapaxes(0, 2)
if hasattr(self, 'SkyLight'):
section.SkyLight = self.SkyLight[slices].swapaxes(0, 2)
section.Y = cy
return section
class FakeChunkedLevelAdapter(object):
""" FakeChunkedLevelAdapter is an abstract class for implementing fixed size, non-chunked storage formats.
Classic, Indev, and Schematic formats inherit from this class. FakeChunkedLevelAdapter has dummy functions for
@ -58,6 +148,8 @@ class FakeChunkedLevelAdapter(object):
hasLights = False
ChunkDataClass = FakeChunkData
@property
def size(self):
return self.Width, self.Height, self.Length
@ -68,7 +160,7 @@ class FakeChunkedLevelAdapter(object):
def readChunk(self, cx, cz, dimName, create=False):
"""
Creates a FakeChunk object representing the chunk at the given
Creates a FakeChunkData object representing the chunk at the given
position. Subclasses may choose to override
fakeBlocksForChunk and fakeDataForChunk to provide block and blockdata arrays.
They may instead override getChunk and return a ChunkBase subclass.
@ -78,7 +170,7 @@ class FakeChunkedLevelAdapter(object):
"""
if not self.bounds.containsChunk(cx, cz):
raise ChunkNotPresent((cx, cz))
chunk = FakeChunk()
chunk = self.ChunkDataClass()
chunk.dimension = self
chunk.cx = cx
chunk.cz = cz
@ -360,95 +452,6 @@ class GenericEntityRef(object):
# """
class FakeChunk(object):
dirty = False
dimension = NotImplemented #: The parent dimension that this chunk belongs to
cx = cz = NotImplemented #: This chunk's position as a tuple (cx, cz)
Width = Length = 16
dimName = ""
_heightMap = None
HeightMap = None
#
# @property
# def HeightMap(self):
# """
# Compute, cache, and return an artificial HeightMap for levels that don't provide one.
# :return: Array of height info.
# :rtype: ndarray(shape=(16, 16))
# """
# if self._heightMap is not None:
# return self._heightMap
# from mceditlib.heightmaps import computeChunkHeightMap
#
# self._heightMap = computeChunkHeightMap(self)
# return self._heightMap
#
# pass
def sectionPositions(self):
return self.dimension.bounds.sectionPositions(self.cx, self.cz)
@property
def Height(self):
return self.dimension.Height
@property
def bounds(self):
return BoundingBox((self.cx << 4, 0, self.cz << 4), self.size)
@property
def size(self):
return self.Width, self.Height, self.Length
def sectionPositions(self):
return range(0, (self.Height + 15) >> 4)
def chunkChanged(self, needsLighting=True):
"""
You are required to call this function after directly modifying any of a chunk's
arrays or its rootTag. Alternately, just set `chunk.dirty = True`
needsLighting is deprecated; Use the updateLights= argument
of setBlocks and other editing functions, or call updateLights(level, x, y, z) to
explicitly update lights yourself.
"""
self.dirty = True
@property
def blocktypes(self):
return self.dimension.blocktypes
def getSection(self, cy, create=False):
y = cy << 4
if y < self.bounds.miny or y >= self.bounds.maxy:
return None
section = FakeSection()
section.chunk = self
slices = numpy.s_[:, :, cy << 4:(cy + 1 << 4)]
if hasattr(self, 'Blocks'):
section.Blocks = self.Blocks[slices].swapaxes(0, 2)
else:
raise NotImplementedError("ChunkBase.getSection is only implemented for chunks providing a Blocks array")
if hasattr(self, 'Data'):
section.Data = self.Data[slices].swapaxes(0, 2)
if hasattr(self, 'BlockLight'):
section.BlockLight = self.BlockLight[slices].swapaxes(0, 2)
if hasattr(self, 'SkyLight'):
section.SkyLight = self.SkyLight[slices].swapaxes(0, 2)
section.Y = cy
return section
class FakeSection(object):
pass
class LightedChunk(object):
"""

View File

@ -5,6 +5,7 @@ Created on Jul 22, 2011
'''
from __future__ import absolute_import
import atexit
from collections import defaultdict
from contextlib import closing
import os
import shutil
@ -20,7 +21,7 @@ from mceditlib.anvil.entities import PCEntityRef
from mceditlib.anvil.entities import PCTileEntityRef
from mceditlib.exceptions import PlayerNotFound
from mceditlib.selection import BoundingBox
from mceditlib.levelbase import FakeChunkedLevelAdapter
from mceditlib.levelbase import FakeChunkedLevelAdapter, FakeChunkData
from mceditlib.blocktypes import pc_blocktypes, BlockTypeSet, blocktypes_named
from mceditlib import nbt
@ -33,6 +34,14 @@ def createSchematic(shape, blocktypes='Alpha'):
editor = WorldEditor(adapter=adapter)
return editor
class SchematicChunkData(FakeChunkData):
def addEntity(self, entity):
self.dimension.addEntity(entity)
def addTileEntity(self, tileEntity):
self.dimension.addTileEntity(tileEntity)
class SchematicFileAdapter(FakeChunkedLevelAdapter):
"""
@ -43,6 +52,8 @@ class SchematicFileAdapter(FakeChunkedLevelAdapter):
EntityRef = PCEntityRef
TileEntityRef = PCTileEntityRef
ChunkDataClass = SchematicChunkData
def __init__(self, shape=None, filename=None, blocktypes='Alpha', readonly=False, resume=False):
"""
Creates an object which stores a section of a Minecraft world as an
@ -156,8 +167,22 @@ class SchematicFileAdapter(FakeChunkedLevelAdapter):
self.rootTag["Data"].value &= 0xF # discard high bits
self.Entities = [self.EntityRef(tag, None) for tag in self.rootTag["Entities"]]
self.TileEntities = [self.EntityRef(tag, None) for tag in self.rootTag["TileEntities"]]
self.entitiesByChunk = defaultdict(list)
for tag in self.rootTag["Entities"]:
ref = self.EntityRef(tag)
pos = ref.Position
cx, cy, cz = pos.chunkPos()
self.entitiesByChunk[cx, cz].append(tag)
self.tileEntitiesByChunk = defaultdict(list)
for tag in self.rootTag["TileEntities"]:
ref = self.TileEntityRef(tag)
pos = ref.Position
cx, cy, cz = pos.chunkPos()
self.tileEntitiesByChunk[cx, cz].append(tag)
def fakeEntitiesForChunk(self, cx, cz):
return self.entitiesByChunk[cx, cz], self.tileEntitiesByChunk[cx, cz]
def syncToDisk(self):
"""
@ -196,6 +221,24 @@ class SchematicFileAdapter(FakeChunkedLevelAdapter):
packed_add = packed_add[0::2]
self.rootTag["AddBlocks"] = nbt.TAG_Byte_Array(packed_add)
entities = []
for e in self.entitiesByChunk.values():
entities.extend(e)
tileEntities = []
for te in self.tileEntitiesByChunk.values():
tileEntities.extend(te)
self.rootTag["Entities"] = nbt.TAG_List(entities)
self.rootTag["TileEntities"] = nbt.TAG_List(tileEntities)
log.info("Saving schematic %s with %d blocks, %d Entities and %d TileEntities",
os.path.basename(filename),
self.rootTag["Blocks"].value.size,
len(self.rootTag["Entities"]),
len(self.rootTag["TileEntities"]),
)
with open(filename, 'wb') as chunkfh:
self.rootTag.save(chunkfh)