Refactor: Reorder members and methods of MCInfdevOldLevel to place related methods closer together.

Change version to use TagProperty. Remove some backward compatibility from MCServerChunkGenerator
Add # --- Subheaders --- to mark related method groups
Fix whitespace according to pep8
This commit is contained in:
David Vierra 2012-10-26 17:24:21 -10:00
parent f4b40912a3
commit 38cd052bb0
2 changed files with 220 additions and 228 deletions

View File

@ -10,6 +10,7 @@ import itertools
from logging import getLogger
from math import floor
import os
import re
import random
import time
import traceback
@ -39,6 +40,7 @@ DIM_END = 1
__all__ = ["ZeroChunk", "AnvilChunk", "ChunkedLevelMixin", "MCInfdevOldLevel", "MCAlphaDimension", "ZipSchematic"]
_zeros = {}
def ZeroChunk(height=512):
z = _zeros.get(height)
if z is None:
@ -46,7 +48,6 @@ def ZeroChunk(height=512):
return z
class _ZeroChunk(ChunkBase):
" a placebo for neighboring-chunk routines "
@ -86,7 +87,6 @@ class AnvilChunk(LightedChunk):
for better handling.
"""
@property
def filename(self):
cx, cz = self.chunkPosition
rx, rz = cx >> 5, cz >> 5
@ -99,7 +99,6 @@ class AnvilChunk(LightedChunk):
index=4 * ((cx & 0x1f) + ((cz & 0x1f) * 32))
)
root_tag = None
def __init__(self, world, chunkPosition, create=False):
@ -114,13 +113,11 @@ class AnvilChunk(LightedChunk):
self.SkyLight = zeros((16, 16, self.Height), 'uint8')
self.SkyLight[:] = 15
if create:
self._create()
else:
self._load()
def _create(self):
(cx, cz) = self.chunkPosition
chunkTag = nbt.TAG_Compound()
@ -162,7 +159,6 @@ class AnvilChunk(LightedChunk):
arr[..., y:y + 16] = secarray.swapaxes(0, 2)
def save(self):
""" does not recalculate any data or light """
@ -213,7 +209,6 @@ class AnvilChunk(LightedChunk):
self.Blocks[:, :, 1:][badsnow] = self.materials.Air.ID
def __str__(self):
return u"AnvilChunk, coords:{0}, world: {1}, D:{2}, L:{3}".format(self.chunkPosition, self.world.displayName, self.dirty, self.needsLighting)
@ -234,7 +229,6 @@ class AnvilChunk(LightedChunk):
else:
computeChunkHeightMap(self.materials, self.Blocks, self.HeightMap)
def addEntity(self, entityTag):
def doubleize(name):
@ -256,7 +250,6 @@ class AnvilChunk(LightedChunk):
self.dirty = True
return super(AnvilChunk, self).removeTileEntitiesInBox(box)
@property
def HeightMap(self):
return self.root_tag["Level"]["HeightMap"].value.reshape((16, 16))
@ -315,6 +308,7 @@ def deflate(data):
# return zdata
return zlib.compress(data)
def inflate(data):
return zlib.decompress(data)
@ -532,7 +526,6 @@ class ChunkedLevelMixin(MCLevel):
if i:
info("Finished {2} chunks in {0} ({1} per chunk)".format(d, d / i, i))
def copyBlocksFromInfiniteIter(self, sourceLevel, sourceBox, destinationPoint, blocksToCopy, create=False):
""" copy blocks between two infinite levels by looping through the
destination's chunks. make a sub-box of the source level for each chunk
@ -846,7 +839,6 @@ class ChunkedLevelMixin(MCLevel):
progressInfo = u"{0} Pass {1}: {2} chunks".format(light, i, len(newDirtyChunks))
info(progressInfo)
# propagate light!
# for each of the six cardinal directions, figure a new light value for
# adjoining blocks by reducing this chunk's light by light absorption and fall off.
@ -872,9 +864,9 @@ class ChunkedLevelMixin(MCLevel):
neighboringChunks = {}
for dir, dx, dz in ((FaceXDecreasing, -1, 0),
(FaceXIncreasing, 1, 0),
(FaceZDecreasing, 0, -1),
(FaceZIncreasing, 0, 1)):
(FaceXIncreasing, 1, 0),
(FaceZDecreasing, 0, -1),
(FaceZIncreasing, 0, 1)):
try:
neighboringChunks[dir] = self.getChunk(cx + dx, cz + dz)
except (ChunkNotPresent, ChunkMalformed):
@ -1027,6 +1019,7 @@ class ChunkedLevelMixin(MCLevel):
for ch in startingDirtyChunks:
ch.needsLighting = False
def TagProperty(tagName, tagType, defaultValueFunc=lambda self: None):
def getter(self):
if tagName not in self.root_tag["Data"]:
@ -1038,152 +1031,8 @@ def TagProperty(tagName, tagType, defaultValueFunc=lambda self: None):
return property(getter, setter)
class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
materials = alphaMaterials
isInfinite = True
parentWorld = None
dimNo = 0
Height = 256
@property
def displayName(self):
# shortname = os.path.basename(self.filename)
# if shortname == "level.dat":
shortname = os.path.basename(os.path.dirname(self.filename))
return shortname
@classmethod
def _isLevel(cls, filename):
join = os.path.join
exists = os.path.exists
if exists(join(filename, "chunks.dat")):
return False # exclude Pocket Edition folders
if not os.path.isdir(filename):
f = os.path.basename(filename)
if f not in ("level.dat", "level.dat_old"):
return False
filename = os.path.dirname(filename)
files = os.listdir(filename)
if "level.dat" in files or "level.dat_old" in files:
return True
return False
def getWorldBounds(self):
if self.chunkCount == 0:
return BoundingBox((0, 0, 0), (0, 0, 0))
allChunks = array(list(self.allChunks))
mincx = (allChunks[:, 0]).min()
maxcx = (allChunks[:, 0]).max()
mincz = (allChunks[:, 1]).min()
maxcz = (allChunks[:, 1]).max()
origin = (mincx << 4, 0, mincz << 4)
size = ((maxcx - mincx + 1) << 4, self.Height, (maxcz - mincz + 1) << 4)
return BoundingBox(origin, size)
def __str__(self):
return "MCInfdevOldLevel(\"" + os.path.split(self.worldDir)[1] + "\")"
SizeOnDisk = TagProperty('SizeOnDisk', nbt.TAG_Long)
RandomSeed = TagProperty('RandomSeed', nbt.TAG_Long)
Time = TagProperty('Time', nbt.TAG_Long) # Age of the world in ticks. 20 ticks per second; 24000 ticks per day.
LastPlayed = TagProperty('LastPlayed', nbt.TAG_Long, lambda self: long(time.time() * 1000))
LevelName = TagProperty('LevelName', nbt.TAG_String, lambda self: self.displayName)
MapFeatures = TagProperty('MapFeatures', nbt.TAG_Byte, lambda self: 1)
GameType = TagProperty('GameType', nbt.TAG_Int, lambda self: 0) # 0 for survival, 1 for creative
GAMETYPE_SURVIVAL = 0
GAMETYPE_CREATIVE = 1
VERSION_MCR = 19132
VERSION_ANVIL = 19133
_bounds = None
@property
def bounds(self):
if self._bounds is None:
self._bounds = self.getWorldBounds()
return self._bounds
@property
def size(self):
return self.bounds.size
def close(self):
for rf in (self.regionFiles or {}).values():
rf.close()
self.regionFiles = {}
self._allChunks = None
self._loadedChunks = {}
def _create(self, filename, random_seed, last_played):
# create a new level
root_tag = nbt.TAG_Compound()
root_tag["Data"] = nbt.TAG_Compound()
root_tag["Data"]["SpawnX"] = nbt.TAG_Int(0)
root_tag["Data"]["SpawnY"] = nbt.TAG_Int(2)
root_tag["Data"]["SpawnZ"] = nbt.TAG_Int(0)
if last_played is None:
last_played = long(time.time() * 1000)
if random_seed is None:
random_seed = long(random.random() * 0xffffffffffffffffL) - 0x8000000000000000L
self.root_tag = root_tag
root_tag["Data"]['version'] = nbt.TAG_Int(self.VERSION_ANVIL)
self.LastPlayed = long(last_played)
self.RandomSeed = long(random_seed)
self.SizeOnDisk = 0
self.Time = 1
self.LevelName = os.path.basename(self.worldDir)
### if singleplayer:
self.createPlayer("Player")
if not os.path.exists(self.worldDir):
os.mkdir(self.worldDir)
def createPlayer(self, playerName):
if playerName == "Player":
playerTag = self.root_tag["Data"].setdefault(playerName, nbt.TAG_Compound())
else:
playerTag = nbt.TAG_Compound()
playerTag['Air'] = nbt.TAG_Short(300)
playerTag['AttackTime'] = nbt.TAG_Short(0)
playerTag['DeathTime'] = nbt.TAG_Short(0)
playerTag['Fire'] = nbt.TAG_Short(-20)
playerTag['Health'] = nbt.TAG_Short(20)
playerTag['HurtTime'] = nbt.TAG_Short(0)
playerTag['Score'] = nbt.TAG_Int(0)
playerTag['FallDistance'] = nbt.TAG_Float(0)
playerTag['OnGround'] = nbt.TAG_Byte(0)
playerTag["Inventory"] = nbt.TAG_List()
playerTag['Motion'] = nbt.TAG_List([nbt.TAG_Double(0) for i in range(3)])
playerTag['Pos'] = nbt.TAG_List([nbt.TAG_Double([0.5, 2.8, 0.5][i]) for i in range(3)])
playerTag['Rotation'] = nbt.TAG_List([nbt.TAG_Float(0), nbt.TAG_Float(0)])
if playerName != "Player":
playerTag.save(self.getPlayerPath(playerName))
def __init__(self, filename=None, create=False, random_seed=None, last_played=None):
"""
@ -1250,7 +1099,38 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
self.players.append("Player")
self.preloadDimensions()
# self.preloadChunkPositions()
# --- Load, save, create ---
def _create(self, filename, random_seed, last_played):
# create a new level
root_tag = nbt.TAG_Compound()
root_tag["Data"] = nbt.TAG_Compound()
root_tag["Data"]["SpawnX"] = nbt.TAG_Int(0)
root_tag["Data"]["SpawnY"] = nbt.TAG_Int(2)
root_tag["Data"]["SpawnZ"] = nbt.TAG_Int(0)
if last_played is None:
last_played = long(time.time() * 1000)
if random_seed is None:
random_seed = long(random.random() * 0xffffffffffffffffL) - 0x8000000000000000L
self.root_tag = root_tag
root_tag["Data"]['version'] = nbt.TAG_Int(self.VERSION_ANVIL)
self.LastPlayed = long(last_played)
self.RandomSeed = long(random_seed)
self.SizeOnDisk = 0
self.Time = 1
self.LevelName = os.path.basename(self.worldDir)
### if singleplayer:
self.createPlayer("Player")
if not os.path.exists(self.worldDir):
os.mkdir(self.worldDir)
def loadLevelDat(self, create=False, random_seed=None, last_played=None):
@ -1273,6 +1153,128 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
info("Error loading level.dat_old. Initializing with defaults.")
self._create(self.filename, random_seed, last_played)
def saveInPlace(self):
for level in self.dimensions.itervalues():
level.saveInPlace(True)
dirtyChunkCount = 0
if self._loadedChunks:
for chunk in self._loadedChunks.itervalues():
if chunk.dirty:
dirtyChunkCount += 1
chunk.save()
for path, tag in self.playerTagCache.iteritems():
tag.save(path)
self.playerTagCache = {}
self.root_tag.save(self.filename)
info(u"Saved {0} chunks".format(dirtyChunkCount))
def close(self):
for rf in (self.regionFiles or {}).values():
rf.close()
self.regionFiles = {}
self._allChunks = None
self._loadedChunks = {}
# --- Constants ---
GAMETYPE_SURVIVAL = 0
GAMETYPE_CREATIVE = 1
VERSION_MCR = 19132
VERSION_ANVIL = 19133
# --- Instance variables ---
materials = alphaMaterials
isInfinite = True
parentWorld = None
dimNo = 0
Height = 256
_bounds = None
# --- NBT Tag variables ---
SizeOnDisk = TagProperty('SizeOnDisk', nbt.TAG_Long)
RandomSeed = TagProperty('RandomSeed', nbt.TAG_Long)
Time = TagProperty('Time', nbt.TAG_Long) # Age of the world in ticks. 20 ticks per second; 24000 ticks per day.
LastPlayed = TagProperty('LastPlayed', nbt.TAG_Long, lambda self: long(time.time() * 1000))
LevelName = TagProperty('LevelName', nbt.TAG_String, lambda self: self.displayName)
MapFeatures = TagProperty('MapFeatures', nbt.TAG_Byte, lambda self: 1)
GameType = TagProperty('GameType', nbt.TAG_Int, lambda self: 0) # 0 for survival, 1 for creative
version = TagProperty('version', nbt.TAG_Int, lambda s: MCInfdevOldLevel.VERSION_ANVIL)
# --- World info ---
def __str__(self):
return "MCInfdevOldLevel(\"" + os.path.split(self.worldDir)[1] + "\")"
@property
def displayName(self):
# shortname = os.path.basename(self.filename)
# if shortname == "level.dat":
shortname = os.path.basename(os.path.dirname(self.filename))
return shortname
@property
def bounds(self):
if self._bounds is None:
self._bounds = self.getWorldBounds()
return self._bounds
def getWorldBounds(self):
if self.chunkCount == 0:
return BoundingBox((0, 0, 0), (0, 0, 0))
allChunks = array(list(self.allChunks))
mincx = (allChunks[:, 0]).min()
maxcx = (allChunks[:, 0]).max()
mincz = (allChunks[:, 1]).min()
maxcz = (allChunks[:, 1]).max()
origin = (mincx << 4, 0, mincz << 4)
size = ((maxcx - mincx + 1) << 4, self.Height, (maxcz - mincz + 1) << 4)
return BoundingBox(origin, size)
@property
def size(self):
return self.bounds.size
# --- Format detection ---
@classmethod
def _isLevel(cls, filename):
join = os.path.join
exists = os.path.exists
if exists(join(filename, "chunks.dat")):
return False # exclude Pocket Edition folders
if not os.path.isdir(filename):
f = os.path.basename(filename)
if f not in ("level.dat", "level.dat_old"):
return False
filename = os.path.dirname(filename)
files = os.listdir(filename)
if "level.dat" in files or "level.dat_old" in files:
return True
return False
# --- Dimensions ---
def preloadDimensions(self):
worldDirs = os.listdir(self.worldDir)
@ -1280,10 +1282,10 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
if dirname.startswith("DIM"):
try:
intInDirname = re.findall("\\d+", dirname)
if len(intInDirname)>0:
if len(intInDirname) > 0:
dimNo = int(intInDirname[-1])
else:
dimNo = 999 #identical dimNo should not matter
dimNo = 999 # identical dimNo should not matter
info("Found dimension {0}".format(dirname))
dim = MCAlphaDimension(self, dimNo, dirname)
self.dimensions[dirname] = dim
@ -1292,7 +1294,7 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
def getDimension(self, dimNo, dirname=None):
if dirname is None:
dirname="DIM" + str(int(dimNo))
dirname = "DIM" + str(int(dimNo))
if self.dimNo != 0:
return self.parentWorld.getDimension(dimNo, dirname)
@ -1307,15 +1309,11 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
self.dimensions[dirname] = dim
return dim
def getRegionForChunk(self, cx, cz):
rx = cx >> 5
rz = cz >> 5
return self.getRegionFile(rx, rz)
# --- Region I/O ---
def preloadChunkPositions(self):
self.preloadRegions()
def findRegionFiles(self):
regionDir = os.path.join(self.worldDir, "region")
if not os.path.exists(regionDir):
@ -1353,6 +1351,11 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
self.regionFiles[rx, rz] = regionFile
return regionFile
def getRegionForChunk(self, cx, cz):
rx = cx >> 5
rz = cz >> 5
return self.getRegionFile(rx, rz)
def unloadRegions(self):
self.close()
@ -1383,21 +1386,7 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
regionFile.close()
os.unlink(regionFile.path)
@property
def version(self):
if 'version' in self.root_tag['Data']:
return self.root_tag['Data']['version'].value
else:
return None
@version.setter
def version(self, val):
if 'version' in self.root_tag['Data']:
self.root_tag['Data']['version'].value = val
@version.deleter
def version(self):
self.root_tag['Data'].pop('version')
# --- Chunk I/O ---
def _loadChunk(self, cx, cz):
""" load the chunk data from disk, and return its root tag as an NBT_Compound"""
@ -1437,14 +1426,9 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
def chunkFilename(self, cx, cz):
s = os.path.join(self.worldDir, self.dirhash(cx), self.dirhash(cz),
"c.%s.%s.dat" % (base36(cx), base36(cz)))
"c.%s.%s.dat" % (base36(cx), base36(cz)))
return s
def chunkFilenameAt(self, x, y, z):
cx = x >> 4
cz = z >> 4
return self._loadedChunks.get((cx, cz)).filename
def extractChunksInBox(self, box, parentFolder):
for cx, cz in box.chunkPositions:
if self.containsChunk(cx, cz):
@ -1462,19 +1446,6 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
with file(outputFile, "wb") as f:
chunk.root_tag.save(buf=f)
def heightMapAt(self, x, z):
zc = z >> 4
xc = x >> 4
xInChunk = x & 0xf
zInChunk = z & 0xf
ch = self.getChunk(xc, zc)
heightMap = ch.HeightMap
return heightMap[zInChunk, xInChunk]
# the heightmap is ordered differently because in minecraft it is a flat array
@property
def chunkCount(self):
"""Returns the number of chunks in the level. May initiate a costly
@ -1491,13 +1462,12 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
self.preloadChunkPositions()
return self._allChunks.__iter__()
def getChunk(self, cx, cz):
""" read the chunk from disk, load it, and return it."""
chunk = self._loadedChunks.get((cx, cz))
if chunk is None:
chunk = self.chunkClass(self, (cx, cz))
chunk = self.chunkClass(self, (cx, cz))
self._loadedChunks[cx, cz] = chunk
return chunk
@ -1511,24 +1481,21 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
for cx, cz in box.chunkPositions:
self.markDirtyChunk(cx, cz)
def saveInPlace(self):
for level in self.dimensions.itervalues():
level.saveInPlace(True)
# --- HeightMaps ---
dirtyChunkCount = 0
if self._loadedChunks:
for chunk in self._loadedChunks.itervalues():
if chunk.dirty:
dirtyChunkCount += 1
chunk.save()
def heightMapAt(self, x, z):
zc = z >> 4
xc = x >> 4
xInChunk = x & 0xf
zInChunk = z & 0xf
for path, tag in self.playerTagCache.iteritems():
tag.save(path)
ch = self.getChunk(xc, zc)
self.playerTagCache = {}
heightMap = ch.HeightMap
self.root_tag.save(self.filename)
info(u"Saved {0} chunks".format(dirtyChunkCount))
return heightMap[zInChunk, xInChunk] # HeightMap indices are backwards
# --- Entities and TileEntities ---
def addEntity(self, entityTag):
assert isinstance(entityTag, nbt.TAG_Compound)
@ -1583,10 +1550,7 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
info("Removed {0} tile entities".format(count))
return count
def containsPoint(self, x, y, z):
if y < 0 or y > 127:
return False
return self.containsChunk(x >> 4, z >> 4)
# --- Chunk manipulation ---
def containsChunk(self, cx, cz):
if self._allChunks is not None:
@ -1601,6 +1565,11 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
return self.getRegionFile(rx, rz).containsChunk(cx, cz)
def containsPoint(self, x, y, z):
if y < 0 or y > 127:
return False
return self.containsChunk(x >> 4, z >> 4)
def createChunk(self, cx, cz):
if self.containsChunk(cx, cz):
raise ValueError("{0}:Chunk {1} already present!".format(self, (cx, cz)))
@ -1670,7 +1639,7 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
return ret
spawnxyz = ["SpawnX", "SpawnY", "SpawnZ"]
# --- Player and spawn manipulation ---
def playerSpawnPosition(self, player=None):
"""
@ -1683,7 +1652,7 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
else:
playerSpawnTag = self.getPlayerTag(player)
return [playerSpawnTag.get(i, dataTag[i]).value for i in self.spawnxyz]
return [playerSpawnTag.get(i, dataTag[i]).value for i in ("SpawnX", "SpawnY", "SpawnZ")]
def setPlayerSpawnPosition(self, pos, player=None):
""" xxx if player is None then it sets the default spawn position for the world """
@ -1691,7 +1660,7 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
playerSpawnTag = self.root_tag["Data"]
else:
playerSpawnTag = self.getPlayerTag(player)
for name, val in zip(self.spawnxyz, pos):
for name, val in zip(("SpawnX", "SpawnY", "SpawnZ"), pos):
playerSpawnTag[name] = nbt.TAG_Int(val)
def getPlayerPath(self, player):
@ -1795,6 +1764,31 @@ class MCInfdevOldLevel(ChunkedLevelMixin, EntityLevel):
playerTag = self.getPlayerTag(player)
return playerTag["playerGameType"].value
def createPlayer(self, playerName):
if playerName == "Player":
playerTag = self.root_tag["Data"].setdefault(playerName, nbt.TAG_Compound())
else:
playerTag = nbt.TAG_Compound()
playerTag['Air'] = nbt.TAG_Short(300)
playerTag['AttackTime'] = nbt.TAG_Short(0)
playerTag['DeathTime'] = nbt.TAG_Short(0)
playerTag['Fire'] = nbt.TAG_Short(-20)
playerTag['Health'] = nbt.TAG_Short(20)
playerTag['HurtTime'] = nbt.TAG_Short(0)
playerTag['Score'] = nbt.TAG_Int(0)
playerTag['FallDistance'] = nbt.TAG_Float(0)
playerTag['OnGround'] = nbt.TAG_Byte(0)
playerTag["Inventory"] = nbt.TAG_List()
playerTag['Motion'] = nbt.TAG_List([nbt.TAG_Double(0) for i in range(3)])
playerTag['Pos'] = nbt.TAG_List([nbt.TAG_Double([0.5, 2.8, 0.5][i]) for i in range(3)])
playerTag['Rotation'] = nbt.TAG_List([nbt.TAG_Float(0), nbt.TAG_Float(0)])
if playerName != "Player":
playerTag.save(self.getPlayerPath(playerName))
class MCAlphaDimension (MCInfdevOldLevel):
def __init__(self, parentWorld, dimNo, dirname, create=False):
@ -1874,7 +1868,6 @@ class ZipSchematic (MCInfdevOldLevel):
self.Height = 128
self.Length = 0
def close(self):
MCInfdevOldLevel.close(self)
self.zipfile.close()
@ -1920,5 +1913,5 @@ class ZipSchematic (MCInfdevOldLevel):
def chunkFilename(self, x, z):
s = "/".join((self.dirhash(x), self.dirhash(z),
"c.%s.%s.dat" % (base36(x), base36(z))))
"c.%s.%s.dat" % (base36(x), base36(z))))
return s

View File

@ -315,7 +315,6 @@ class MCServerChunkGenerator(object):
tempWorldDir = os.path.join(tempDir, worldName)
tempWorld = infiniteworld.MCInfdevOldLevel(tempWorldDir, create=True, random_seed=level.RandomSeed)
del tempWorld.version # for compatibility with older servers. newer ones will set it again without issue.
self.tempWorldCache[self.serverVersion, level.RandomSeed] = tempWorld