refactor base entity code into an EntityLevel subclass and derive Indev/Infdev levels and MCSchematic from it. remove the hasEntities flag.

This commit is contained in:
David Vierra 2011-08-16 13:38:30 -10:00
parent 9127ee0ff5
commit 2a4e0435a0
6 changed files with 188 additions and 198 deletions

View File

@ -85,11 +85,10 @@ Spawn = "Spawn"
__all__ = ["MCIndevLevel"] __all__ = ["MCIndevLevel"]
class MCIndevLevel(MCLevel): class MCIndevLevel(EntityLevel):
""" IMPORTANT: self.Blocks and self.Data are indexed with [x,z,y] via axis """ IMPORTANT: self.Blocks and self.Data are indexed with [x,z,y] via axis
swapping to be consistent with infinite levels.""" swapping to be consistent with infinite levels."""
hasEntities = True
def setPlayerSpawnPosition(self, pos, player=None): def setPlayerSpawnPosition(self, pos, player=None):
assert len(pos) == 3 assert len(pos) == 3

View File

@ -46,7 +46,7 @@ class ZeroChunk(object):
self.SkyLight = whiteLight self.SkyLight = whiteLight
self.Data = zeroChunk self.Data = zeroChunk
class InfdevChunk(MCLevel): class InfdevChunk(EntityLevel):
""" This is a 16x16xH chunk in an (infinite) world. """ This is a 16x16xH chunk in an (infinite) world.
The properties Blocks, Data, SkyLight, BlockLight, and Heightmap The properties Blocks, Data, SkyLight, BlockLight, and Heightmap
are ndarrays containing the respective blocks in the chunk file. are ndarrays containing the respective blocks in the chunk file.
@ -54,8 +54,6 @@ class InfdevChunk(MCLevel):
arrays are automatically unpacked from nibble arrays into byte arrays arrays are automatically unpacked from nibble arrays into byte arrays
for better handling. for better handling.
""" """
hasEntities = True
@property @property
def filename(self): def filename(self):
if self.world.version: if self.world.version:
@ -396,18 +394,18 @@ class InfdevChunk(MCLevel):
chunkTag[Level][SkyLight].value.shape = (chunkSize, chunkSize, self.world.ChunkHeight / 2) chunkTag[Level][SkyLight].value.shape = (chunkSize, chunkSize, self.world.ChunkHeight / 2)
chunkTag[Level][BlockLight].value.shape = (chunkSize, chunkSize, self.world.ChunkHeight / 2) chunkTag[Level][BlockLight].value.shape = (chunkSize, chunkSize, self.world.ChunkHeight / 2)
chunkTag[Level]["Data"].value.shape = (chunkSize, chunkSize, self.world.ChunkHeight / 2) chunkTag[Level]["Data"].value.shape = (chunkSize, chunkSize, self.world.ChunkHeight / 2)
if not TileEntities in chunkTag[Level]: if TileEntities not in chunkTag[Level]:
chunkTag[Level][TileEntities] = TAG_List(); chunkTag[Level][TileEntities] = TAG_List();
if not Entities in chunkTag[Level]: if Entities not in chunkTag[Level]:
chunkTag[Level][Entities] = TAG_List(); chunkTag[Level][Entities] = TAG_List();
def removeEntitiesInBox(self, box): def removeEntitiesInBox(self, box):
self.dirty = True; self.dirty = True;
return MCLevel.removeEntitiesInBox(self, box) return super(InfdevChunk, self).removeEntitiesInBox(box)
def removeTileEntitiesInBox(self, box): def removeTileEntitiesInBox(self, box):
self.dirty = True; self.dirty = True;
return MCLevel.removeTileEntitiesInBox(self, box) return super(InfdevChunk, self).removeTileEntitiesInBox(box)
@property @property
@ -847,10 +845,9 @@ def deflate(data):
def inflate(data): def inflate(data):
return zlib.decompress(data) return zlib.decompress(data)
class MCInfdevOldLevel(MCLevel): class MCInfdevOldLevel(EntityLevel):
materials = alphaMaterials; materials = alphaMaterials;
isInfinite = True isInfinite = True
hasEntities = True;
parentWorld = None; parentWorld = None;
dimNo = 0; dimNo = 0;
ChunkHeight = 128 ChunkHeight = 128
@ -1904,24 +1901,12 @@ class MCInfdevOldLevel(MCLevel):
except (ChunkNotPresent, ChunkMalformed), e: except (ChunkNotPresent, ChunkMalformed), e:
return None return None
# raise Error, can't find a chunk? # raise Error, can't find a chunk?
chunk.Entities.append(entityTag); chunk.addEntity(entityTag);
chunk.dirty = True chunk.dirty = True
def tileEntityAt(self, x, y, z): def tileEntityAt(self, x, y, z):
chunk = self.getChunk(x >> 4, z >> 4) chunk = self.getChunk(x >> 4, z >> 4)
entities = []; return chunk.tileEntityAt(x, y, z)
if chunk.TileEntities is None: return None;
for entity in chunk.TileEntities:
pos = [entity[a].value for a in 'xyz']
if pos == [x, y, z]:
entities.append(entity);
if len(entities) > 1:
info("Multiple tile entities found: {0}".format(entities))
if len(entities) == 0:
return None
return entities[0];
def addTileEntity(self, tileEntityTag): def addTileEntity(self, tileEntityTag):
@ -1942,6 +1927,13 @@ class MCInfdevOldLevel(MCLevel):
chunk.TileEntities.append(tileEntityTag); chunk.TileEntities.append(tileEntityTag);
chunk.dirty = True chunk.dirty = True
def getEntitiesInBox(self, box):
entities = []
for chunk, slices, point in self.getChunkSlices(box):
entities += chunk.getEntitiesInBox(box)
return entities
def removeEntitiesInBox(self, box): def removeEntitiesInBox(self, box):
count = 0; count = 0;
for chunk, slices, point in self.getChunkSlices(box): for chunk, slices, point in self.getChunkSlices(box):
@ -2061,11 +2053,11 @@ class MCInfdevOldLevel(MCLevel):
destBox = BoundingBox(destinationPoint, sourceBox.size) destBox = BoundingBox(destinationPoint, sourceBox.size)
destChunks = self.getChunkSlices(destBox)
i = 0; i = 0;
chunkCount = float(destBox.chunkCount) chunkCount = float(destBox.chunkCount)
for (chunk, slices, point) in destChunks: for (chunk, slices, point) in self.getChunkSlices(destBox):
i += 1; i += 1;
yield (i, chunkCount) yield (i, chunkCount)

310
level.py
View File

@ -7,8 +7,7 @@ Created on Jul 22, 2011
from mclevelbase import * from mclevelbase import *
import tempfile import tempfile
from collections import defaultdict
#decorator for the primitive methods of MCLevel.
class MCLevel(object): class MCLevel(object):
""" MCLevel is an abstract class providing many routines to the different level types, """ MCLevel is an abstract class providing many routines to the different level types,
@ -30,7 +29,6 @@ class MCLevel(object):
materials = classicMaterials; materials = classicMaterials;
isInfinite = False isInfinite = False
hasEntities = False;
compressedTag = None compressedTag = None
root_tag = None root_tag = None
@ -96,20 +94,20 @@ class MCLevel(object):
Blocks, Data, Light, SkyLight, HeightMap attributes if present """ Blocks, Data, Light, SkyLight, HeightMap attributes if present """
pass pass
def close(self): def close(self): pass
pass
def compress(self):
pass
def decompress(self):
pass
def compress(self): pass
def decompress(self):pass
def compressChunk(self, cx, cz): pass def compressChunk(self, cx, cz): pass
def tileEntityAt(self, x, y, z): def addEntity(self, entityTag): pass
return None def tileEntityAt(self, x, y, z): return None
def addEntity(self, *args): pass def addTileEntity(self, entityTag): pass
def addTileEntity(self, *args): pass def getEntitiesInBox(self, box): return []
def getTileEntitiesInBox(self, box): return []
def copyEntitiesFromIter(self, *args, **kw): yield;
@property @property
def loadedChunks(self): def loadedChunks(self):
@ -130,6 +128,12 @@ class MCLevel(object):
being a chunked level format.""" being a chunked level format."""
return self.loadedChunks return self.loadedChunks
def _getFakeChunkEntities(self, cx, cz):
"""Returns Entities, TileEntities"""
return ([], [])
def getChunk(self, cx, cz): def getChunk(self, cx, cz):
"""Synthesize a FakeChunk object representing the chunk at the given """Synthesize a FakeChunk object representing the chunk at the given
position. Subclasses override fakeBlocksForChunk and fakeDataForChunk position. Subclasses override fakeBlocksForChunk and fakeDataForChunk
@ -157,8 +161,9 @@ class MCLevel(object):
f.BlockLight = whiteLight f.BlockLight = whiteLight
f.SkyLight = whiteLight f.SkyLight = whiteLight
f.Entities = []
f.TileEntities = [] f.Entities, f.TileEntities = self._getFakeChunkEntities(cx, cz)
f.root_tag = TAG_Compound(); f.root_tag = TAG_Compound();
@ -444,14 +449,11 @@ class MCLevel(object):
def copyBlocksFromInfinite(self, sourceLevel, sourceBox, destinationPoint, blocksToCopy): def copyBlocksFromInfinite(self, sourceLevel, sourceBox, destinationPoint, blocksToCopy):
chunkIterator = sourceLevel.getChunkSlices(sourceBox)
if blocksToCopy is not None: if blocksToCopy is not None:
typemask = zeros((256) , dtype='bool') typemask = zeros((256) , dtype='bool')
typemask[blocksToCopy] = True; typemask[blocksToCopy] = True;
for (chunk, slices, point) in chunkIterator: for (chunk, slices, point) in sourceLevel.getChunkSlices(sourceBox):
point = map(lambda a, b:a + b, point, destinationPoint) point = map(lambda a, b:a + b, point, destinationPoint)
point = point[0], point[2], point[1] point = point[0], point[2], point[1]
mask = slice(None, None) mask = slice(None, None)
@ -475,8 +477,6 @@ class MCLevel(object):
#self.Data[ destSlices ][mask] = chunk.Data[slices][mask] #self.Data[ destSlices ][mask] = chunk.Data[slices][mask]
chunk.compress();
def adjustCopyParameters(self, sourceLevel, sourceBox, destinationPoint): def adjustCopyParameters(self, sourceLevel, sourceBox, destinationPoint):
@ -578,113 +578,6 @@ class MCLevel(object):
return (-45., 0.) return (-45., 0.)
def copyEntitiesFromInfiniteIter(self, sourceLevel, sourceBox, destinationPoint):
chunkIterator = sourceLevel.getChunkSlices(sourceBox);
chunkCount = sourceBox.chunkCount
i = 0
for (chunk, slices, point) in chunkIterator:
#remember, slices are ordered x,z,y so you can subscript them like so: chunk.Blocks[slices]
cx, cz = chunk.chunkPosition
#wx, wz = cx << 4, cz << 4
copyOffset = map(lambda x, y:x - y, destinationPoint, sourceBox.origin)
for entityTag in chunk.Entities:
x, y, z = Entity.pos(entityTag)
if (x, y, z) not in sourceBox: continue
eTag = Entity.copyWithOffset(entityTag, copyOffset)
self.addEntity(eTag);
for tileEntityTag in chunk.TileEntities:
if not 'x' in tileEntityTag: continue
x, y, z = TileEntity.pos(tileEntityTag)
if (x, y, z) not in sourceBox: continue
eTag = TileEntity.copyWithOffset(tileEntityTag, copyOffset)
self.addTileEntity(eTag)
chunk.compress();
yield (i, chunkCount)
i += 1
def copyEntitiesFrom(self, sourceLevel, sourceBox, destinationPoint, entities=True):
for i in self.copyEntitiesFromIter(sourceLevel, sourceBox, destinationPoint, entities):
pass
def copyEntitiesFromIter(self, sourceLevel, sourceBox, destinationPoint, entities=True):
#assume coords have already been adjusted by copyBlocks
if not self.hasEntities or not sourceLevel.hasEntities: return;
sourcePoint0 = sourceBox.origin;
sourcePoint1 = sourceBox.maximum;
if sourceLevel.isInfinite:
for i in self.copyEntitiesFromInfiniteIter(sourceLevel, sourceBox, destinationPoint):
yield i
else:
entsCopied = 0;
tileEntsCopied = 0;
copyOffset = map(lambda x, y:x - y, destinationPoint, sourcePoint0)
if entities:
for entity in getEntitiesInRange(sourceBox, sourceLevel.Entities):
eTag = Entity.copyWithOffset(entity, copyOffset)
self.addEntity(eTag)
entsCopied += 1;
i = 0
for entity in getTileEntitiesInRange(sourceBox, sourceLevel.TileEntities):
i += 1
if i % 100 == 0:
yield
if not 'x' in entity: continue
eTag = TileEntity.copyWithOffset(entity, copyOffset)
try:
self.addTileEntity(eTag)
tileEntsCopied += 1;
except ChunkNotPresent:
pass
yield
debug(u"Copied {0} entities, {1} tile entities".format(entsCopied, tileEntsCopied))
def removeEntitiesInBox(self, box):
if not self.hasEntities: return 0;
newEnts = [];
for ent in self.Entities:
if map(lambda x:x.value, ent["Pos"]) in box:
continue;
newEnts.append(ent);
entsRemoved = len(self.Entities) - len(newEnts);
debug("Removed {0} entities".format(entsRemoved))
self.Entities.value[:] = newEnts
return entsRemoved
def removeTileEntitiesInBox(self, box):
if not hasattr(self, "TileEntities"): return;
newEnts = [];
for ent in self.TileEntities:
if not "x" in ent: continue
if map(lambda x:x.value, (ent[a] for a in "xyz")) in box:
continue;
newEnts.append(ent);
entsRemoved = len(self.TileEntities) - len(newEnts);
debug("Removed {0} tile entities".format(entsRemoved))
self.TileEntities.value[:] = newEnts
return entsRemoved
def generateLights(self, dirtyChunks=None): def generateLights(self, dirtyChunks=None):
pass; pass;
@ -738,23 +631,150 @@ class MCLevel(object):
return box, (destX, destY, destZ) return box, (destX, destY, destZ)
def getEntitiesInRange(sourceBox, entities):
entsInRange = [];
for entity in entities:
dir()
x, y, z = Entity.pos(entity)
if not (x, y, z) in sourceBox: continue
entsInRange.append(entity)
return entsInRange class EntityLevel(MCLevel):
"""Abstract subclass of MCLevel that adds default entity behavior"""
def copyEntitiesFromInfiniteIter(self, sourceLevel, sourceBox, destinationPoint):
chunkCount = sourceBox.chunkCount
i = 0
copyOffset = map(lambda x, y:x - y, destinationPoint, sourceBox.origin)
for (chunk, slices, point) in sourceLevel.getChunkSlices(sourceBox):
self.copyEntitiesFromInfiniteChunk(chunk, slices, point, sourceBox, copyOffset)
yield (i, chunkCount)
i += 1
def getTileEntitiesInRange(sourceBox, tileEntities): def copyEntitiesFromInfiniteChunk(self, chunk, slices, point, sourceBox, copyOffset):
entsInRange = [];
for tileEntity in tileEntities:
if not 'x' in tileEntity: continue
x, y, z = TileEntity.pos(tileEntity) for entityTag in chunk.Entities:
if not (x, y, z) in sourceBox: continue x, y, z = Entity.pos(entityTag)
entsInRange.append(tileEntity) if (x, y, z) not in sourceBox: continue
eTag = Entity.copyWithOffset(entityTag, copyOffset)
self.addEntity(eTag);
for tileEntityTag in chunk.TileEntities:
x, y, z = TileEntity.pos(tileEntityTag)
if (x, y, z) not in sourceBox: continue
eTag = TileEntity.copyWithOffset(tileEntityTag, copyOffset)
self.addTileEntity(eTag)
def copyEntitiesFromIter(self, sourceLevel, sourceBox, destinationPoint, entities=True):
#assume coords have already been adjusted by copyBlocks
#if not self.hasEntities or not sourceLevel.hasEntities: return;
sourcePoint0 = sourceBox.origin;
sourcePoint1 = sourceBox.maximum;
if sourceLevel.isInfinite:
for i in self.copyEntitiesFromInfiniteIter(sourceLevel, sourceBox, destinationPoint):
yield i
else:
entsCopied = 0;
tileEntsCopied = 0;
copyOffset = map(lambda x, y:x - y, destinationPoint, sourcePoint0)
if entities:
for entity in sourceLevel.getEntitiesInBox(sourceBox):
eTag = Entity.copyWithOffset(entity, copyOffset)
self.addEntity(eTag)
entsCopied += 1;
i = 0
for entity in sourceLevel.getTileEntitiesInBox(sourceBox):
i += 1
if i % 100 == 0:
yield
if not 'x' in entity: continue
eTag = TileEntity.copyWithOffset(entity, copyOffset)
try:
self.addTileEntity(eTag)
tileEntsCopied += 1;
except ChunkNotPresent:
pass
yield
debug(u"Copied {0} entities, {1} tile entities".format(entsCopied, tileEntsCopied))
def getEntitiesInBox(self, box):
"""Returns a list of references to entities in this chunk, whose positions are within box"""
return [ent for ent in self.Entities if Entity.pos(ent) in box]
def getTileEntitiesInBox(self, box):
"""Returns a list of references to tile entities in this chunk, whose positions are within box"""
return [ent for ent in self.TileEntities if TileEntity.pos(ent) in box]
def removeEntitiesInBox(self, box):
newEnts = [];
for ent in self.Entities:
if Entity.pos(ent) in box:
continue;
newEnts.append(ent);
entsRemoved = len(self.Entities) - len(newEnts);
debug("Removed {0} entities".format(entsRemoved))
self.Entities.value[:] = newEnts
return entsRemoved
def removeTileEntitiesInBox(self, box):
if not hasattr(self, "TileEntities"): return;
newEnts = [];
for ent in self.TileEntities:
if TileEntity.pos(ent) in box:
continue;
newEnts.append(ent);
entsRemoved = len(self.TileEntities) - len(newEnts);
debug("Removed {0} tile entities".format(entsRemoved))
self.TileEntities.value[:] = newEnts
return entsRemoved
def addEntity(self, entityTag):
assert isinstance(entityTag, TAG_Compound)
self.Entities.append(entityTag);
def tileEntityAt(self, x, y, z):
entities = [];
for entityTag in self.TileEntities:
if TileEntity.pos(entityTag) == [x, y, z]:
entities.append(entityTag);
if len(entities) > 1:
info("Multiple tile entities found: {0}".format(entities))
if len(entities) == 0:
return None
return entities[0];
def addTileEntity(self, entityTag):
assert isinstance(entityTag, TAG_Compound)
self.TileEntities.append(entityTag);
_fakeEntities = None
def _getFakeChunkEntities(self, cx, cz):
"""distribute entities into sublists based on fake chunk position
_fakeEntities keys are (cx,cz) and values are (Entities, TileEntities)"""
if self._fakeEntities is None:
self._fakeEntities = defaultdict(lambda: ([], []))
for i, e in enumerate((self.Entities, self.TileEntities)):
for ent in e:
x, y, z = Entity.pos(ent)
ecx, ecz = map(lambda x:(int(floor(x)) >> 4), (x, z))
self._fakeEntities[ecx, ecz][i].append(ent)
return self._fakeEntities[cx, cz]
return entsInRange

View File

@ -63,4 +63,4 @@ class ChunkNotPresent(Exception): pass
class RegionMalformed(Exception): pass class RegionMalformed(Exception): pass
class ChunkMalformed(ChunkNotPresent): pass class ChunkMalformed(ChunkNotPresent): pass
from level import MCLevel from level import MCLevel, EntityLevel

View File

@ -8,10 +8,8 @@ from mclevelbase import *
Materials = 'Materials' Materials = 'Materials'
__all__ = ['MCSchematic', 'INVEditChest'] __all__ = ['MCSchematic', 'INVEditChest']
class MCSchematic (MCLevel): class MCSchematic (EntityLevel):
materials = alphaMaterials materials = alphaMaterials
hasEntities = True;
def __str__(self): def __str__(self):
return u"MCSchematic(shape={0}, filename=\"{1}\")".format(self.size, self.filename or u"") return u"MCSchematic(shape={0}, filename=\"{1}\")".format(self.size, self.filename or u"")
@ -359,28 +357,6 @@ class MCSchematic (MCLevel):
return self.Data[x, z, y]; return self.Data[x, z, y];
def addEntity(self, entityTag):
assert isinstance(entityTag, TAG_Compound)
self.Entities.append(entityTag);
def tileEntityAt(self, x, y, z):
entities = [];
for entityTag in self.TileEntities:
pos = [entityTag[a].value for a in 'xyz']
if pos == [x, y, z]:
entities.append(entityTag);
if len(entities) > 1:
info("Multiple tile entities found: {0}".format(entities))
if len(entities) == 0:
return None
return entities[0];
def addTileEntity(self, entityTag):
assert isinstance(entityTag, TAG_Compound)
self.TileEntities.append(entityTag);
@classmethod @classmethod
def chestWithItemID(self, itemID, count=64, damage=0): def chestWithItemID(self, itemID, count=64, damage=0):
""" Creates a chest with a stack of 'itemID' in each slot. """ Creates a chest with a stack of 'itemID' in each slot.

View File

@ -113,6 +113,9 @@ class TestAlphaLevel(unittest.TestCase):
level.LastPlayed level.LastPlayed
level.LastPlayed = time.time() * 1000 - 1000000 level.LastPlayed = time.time() * 1000 - 1000000
def testGetEntities(self):
level = self.alphalevel.level
print len(level.getEntitiesInBox(level.bounds))
def testCreateChunks(self): def testCreateChunks(self):
indevlevel = self.indevlevel.level indevlevel = self.indevlevel.level