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:
parent
9127ee0ff5
commit
2a4e0435a0
3
indev.py
3
indev.py
@ -85,11 +85,10 @@ Spawn = "Spawn"
|
||||
__all__ = ["MCIndevLevel"]
|
||||
|
||||
|
||||
class MCIndevLevel(MCLevel):
|
||||
class MCIndevLevel(EntityLevel):
|
||||
|
||||
""" IMPORTANT: self.Blocks and self.Data are indexed with [x,z,y] via axis
|
||||
swapping to be consistent with infinite levels."""
|
||||
hasEntities = True
|
||||
|
||||
def setPlayerSpawnPosition(self, pos, player=None):
|
||||
assert len(pos) == 3
|
||||
|
@ -46,7 +46,7 @@ class ZeroChunk(object):
|
||||
self.SkyLight = whiteLight
|
||||
self.Data = zeroChunk
|
||||
|
||||
class InfdevChunk(MCLevel):
|
||||
class InfdevChunk(EntityLevel):
|
||||
""" This is a 16x16xH chunk in an (infinite) world.
|
||||
The properties Blocks, Data, SkyLight, BlockLight, and Heightmap
|
||||
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
|
||||
for better handling.
|
||||
"""
|
||||
hasEntities = True
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
if self.world.version:
|
||||
@ -396,18 +394,18 @@ class InfdevChunk(MCLevel):
|
||||
chunkTag[Level][SkyLight].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)
|
||||
if not TileEntities in chunkTag[Level]:
|
||||
if TileEntities not in chunkTag[Level]:
|
||||
chunkTag[Level][TileEntities] = TAG_List();
|
||||
if not Entities in chunkTag[Level]:
|
||||
if Entities not in chunkTag[Level]:
|
||||
chunkTag[Level][Entities] = TAG_List();
|
||||
|
||||
def removeEntitiesInBox(self, box):
|
||||
self.dirty = True;
|
||||
return MCLevel.removeEntitiesInBox(self, box)
|
||||
return super(InfdevChunk, self).removeEntitiesInBox(box)
|
||||
|
||||
def removeTileEntitiesInBox(self, box):
|
||||
self.dirty = True;
|
||||
return MCLevel.removeTileEntitiesInBox(self, box)
|
||||
return super(InfdevChunk, self).removeTileEntitiesInBox(box)
|
||||
|
||||
|
||||
@property
|
||||
@ -847,10 +845,9 @@ def deflate(data):
|
||||
def inflate(data):
|
||||
return zlib.decompress(data)
|
||||
|
||||
class MCInfdevOldLevel(MCLevel):
|
||||
class MCInfdevOldLevel(EntityLevel):
|
||||
materials = alphaMaterials;
|
||||
isInfinite = True
|
||||
hasEntities = True;
|
||||
parentWorld = None;
|
||||
dimNo = 0;
|
||||
ChunkHeight = 128
|
||||
@ -1904,24 +1901,12 @@ class MCInfdevOldLevel(MCLevel):
|
||||
except (ChunkNotPresent, ChunkMalformed), e:
|
||||
return None
|
||||
# raise Error, can't find a chunk?
|
||||
chunk.Entities.append(entityTag);
|
||||
chunk.addEntity(entityTag);
|
||||
chunk.dirty = True
|
||||
|
||||
def tileEntityAt(self, x, y, z):
|
||||
chunk = self.getChunk(x >> 4, z >> 4)
|
||||
entities = [];
|
||||
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];
|
||||
return chunk.tileEntityAt(x, y, z)
|
||||
|
||||
|
||||
def addTileEntity(self, tileEntityTag):
|
||||
@ -1942,6 +1927,13 @@ class MCInfdevOldLevel(MCLevel):
|
||||
chunk.TileEntities.append(tileEntityTag);
|
||||
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):
|
||||
count = 0;
|
||||
for chunk, slices, point in self.getChunkSlices(box):
|
||||
@ -2061,11 +2053,11 @@ class MCInfdevOldLevel(MCLevel):
|
||||
|
||||
|
||||
destBox = BoundingBox(destinationPoint, sourceBox.size)
|
||||
destChunks = self.getChunkSlices(destBox)
|
||||
|
||||
i = 0;
|
||||
chunkCount = float(destBox.chunkCount)
|
||||
|
||||
for (chunk, slices, point) in destChunks:
|
||||
for (chunk, slices, point) in self.getChunkSlices(destBox):
|
||||
i += 1;
|
||||
yield (i, chunkCount)
|
||||
|
||||
|
310
level.py
310
level.py
@ -7,8 +7,7 @@ Created on Jul 22, 2011
|
||||
|
||||
from mclevelbase import *
|
||||
import tempfile
|
||||
|
||||
#decorator for the primitive methods of MCLevel.
|
||||
from collections import defaultdict
|
||||
|
||||
class MCLevel(object):
|
||||
""" MCLevel is an abstract class providing many routines to the different level types,
|
||||
@ -30,7 +29,6 @@ class MCLevel(object):
|
||||
materials = classicMaterials;
|
||||
isInfinite = False
|
||||
|
||||
hasEntities = False;
|
||||
compressedTag = None
|
||||
root_tag = None
|
||||
|
||||
@ -96,20 +94,20 @@ class MCLevel(object):
|
||||
Blocks, Data, Light, SkyLight, HeightMap attributes if present """
|
||||
pass
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
def compress(self):
|
||||
pass
|
||||
def decompress(self):
|
||||
pass
|
||||
def close(self): pass
|
||||
|
||||
def compress(self): pass
|
||||
def decompress(self):pass
|
||||
|
||||
def compressChunk(self, cx, cz): pass
|
||||
def tileEntityAt(self, x, y, z):
|
||||
return None
|
||||
def addEntity(self, *args): pass
|
||||
def addTileEntity(self, *args): pass
|
||||
def addEntity(self, entityTag): pass
|
||||
def tileEntityAt(self, x, y, z): return None
|
||||
def addTileEntity(self, entityTag): pass
|
||||
def getEntitiesInBox(self, box): return []
|
||||
def getTileEntitiesInBox(self, box): return []
|
||||
|
||||
def copyEntitiesFromIter(self, *args, **kw): yield;
|
||||
|
||||
|
||||
@property
|
||||
def loadedChunks(self):
|
||||
@ -130,6 +128,12 @@ class MCLevel(object):
|
||||
being a chunked level format."""
|
||||
return self.loadedChunks
|
||||
|
||||
|
||||
|
||||
def _getFakeChunkEntities(self, cx, cz):
|
||||
"""Returns Entities, TileEntities"""
|
||||
return ([], [])
|
||||
|
||||
def getChunk(self, cx, cz):
|
||||
"""Synthesize a FakeChunk object representing the chunk at the given
|
||||
position. Subclasses override fakeBlocksForChunk and fakeDataForChunk
|
||||
@ -157,8 +161,9 @@ class MCLevel(object):
|
||||
|
||||
f.BlockLight = whiteLight
|
||||
f.SkyLight = whiteLight
|
||||
f.Entities = []
|
||||
f.TileEntities = []
|
||||
|
||||
f.Entities, f.TileEntities = self._getFakeChunkEntities(cx, cz)
|
||||
|
||||
|
||||
|
||||
f.root_tag = TAG_Compound();
|
||||
@ -444,14 +449,11 @@ class MCLevel(object):
|
||||
|
||||
def copyBlocksFromInfinite(self, sourceLevel, sourceBox, destinationPoint, blocksToCopy):
|
||||
|
||||
chunkIterator = sourceLevel.getChunkSlices(sourceBox)
|
||||
|
||||
|
||||
if blocksToCopy is not None:
|
||||
typemask = zeros((256) , dtype='bool')
|
||||
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 = point[0], point[2], point[1]
|
||||
mask = slice(None, None)
|
||||
@ -475,8 +477,6 @@ class MCLevel(object):
|
||||
#self.Data[ destSlices ][mask] = chunk.Data[slices][mask]
|
||||
|
||||
|
||||
chunk.compress();
|
||||
|
||||
|
||||
def adjustCopyParameters(self, sourceLevel, sourceBox, destinationPoint):
|
||||
|
||||
@ -578,113 +578,6 @@ class MCLevel(object):
|
||||
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):
|
||||
pass;
|
||||
@ -738,23 +631,150 @@ class MCLevel(object):
|
||||
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):
|
||||
entsInRange = [];
|
||||
for tileEntity in tileEntities:
|
||||
if not 'x' in tileEntity: continue
|
||||
def copyEntitiesFromInfiniteChunk(self, chunk, slices, point, sourceBox, copyOffset):
|
||||
|
||||
x, y, z = TileEntity.pos(tileEntity)
|
||||
if not (x, y, z) in sourceBox: continue
|
||||
entsInRange.append(tileEntity)
|
||||
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:
|
||||
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
|
||||
|
@ -63,4 +63,4 @@ class ChunkNotPresent(Exception): pass
|
||||
class RegionMalformed(Exception): pass
|
||||
class ChunkMalformed(ChunkNotPresent): pass
|
||||
|
||||
from level import MCLevel
|
||||
from level import MCLevel, EntityLevel
|
||||
|
26
schematic.py
26
schematic.py
@ -8,10 +8,8 @@ from mclevelbase import *
|
||||
Materials = 'Materials'
|
||||
|
||||
__all__ = ['MCSchematic', 'INVEditChest']
|
||||
class MCSchematic (MCLevel):
|
||||
class MCSchematic (EntityLevel):
|
||||
materials = alphaMaterials
|
||||
hasEntities = True;
|
||||
|
||||
|
||||
def __str__(self):
|
||||
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];
|
||||
|
||||
|
||||
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
|
||||
def chestWithItemID(self, itemID, count=64, damage=0):
|
||||
""" Creates a chest with a stack of 'itemID' in each slot.
|
||||
|
3
tests.py
3
tests.py
@ -113,6 +113,9 @@ class TestAlphaLevel(unittest.TestCase):
|
||||
level.LastPlayed
|
||||
level.LastPlayed = time.time() * 1000 - 1000000
|
||||
|
||||
def testGetEntities(self):
|
||||
level = self.alphalevel.level
|
||||
print len(level.getEntitiesInBox(level.bounds))
|
||||
|
||||
def testCreateChunks(self):
|
||||
indevlevel = self.indevlevel.level
|
||||
|
Reference in New Issue
Block a user