more work on server-based generation: use hashes instead of version numbers, clear the world cache via listdir, put readmes into the two folders, fix compatibility with older servers
This commit is contained in:
parent
675f814b78
commit
6f035c4aaa
116
infiniteworld.py
116
infiniteworld.py
@ -80,9 +80,31 @@ class ServerJarCache(object):
|
||||
self.cacheDir = cacheDir
|
||||
if not os.path.exists(self.cacheDir):
|
||||
os.makedirs(self.cacheDir)
|
||||
readme = os.path.join(self.cacheDir, "README.TXT")
|
||||
if not os.path.exists(readme):
|
||||
with file(readme, "w") as f:
|
||||
f.write("""
|
||||
About this folder:
|
||||
|
||||
This folder is used by MCEdit and pymclevel to store different versions of the
|
||||
Minecraft Server to use for terrain generation. It should have one or more
|
||||
subfolders, one for each version of the server. Each subfolder must contain at
|
||||
least one file named minecraft_server.jar, and the subfolder's name should
|
||||
contain the server's version and the names of any installed mods.
|
||||
|
||||
There may already be a subfolder here (for example, "Beta 1.7.3") if you have
|
||||
used the Chunk Create feature in MCEdit to create chunks using the server.
|
||||
|
||||
Version numbers can be automatically detected. If you place one or more
|
||||
minecraft_server.jar files in this folder, they will be placed automatically
|
||||
into well-named subfolders the next time you run MCEdit. If a file's name
|
||||
begins with "minecraft_server" and ends with ".jar", it will be detected in
|
||||
this way.
|
||||
""")
|
||||
|
||||
|
||||
cacheDirList = os.listdir(self.cacheDir)
|
||||
self.versions = [v for v in cacheDirList if os.path.exists(self.jarfileForVersion(v))]
|
||||
self.versions = list(reversed(sorted([v for v in cacheDirList if os.path.exists(self.jarfileForVersion(v))], key=alphanum_key)))
|
||||
|
||||
for f in cacheDirList:
|
||||
p = os.path.join(self.cacheDir, f)
|
||||
@ -126,6 +148,11 @@ class ServerJarCache(object):
|
||||
|
||||
def jarfileForVersion(self, v):
|
||||
return os.path.join(self.cacheDir, v, "minecraft_server.jar")
|
||||
def checksumForVersion(self, v):
|
||||
jf = self.jarfileForVersion(v)
|
||||
with file(jf, "rb") as f:
|
||||
import md5
|
||||
return (md5.md5(f.read()).hexdigest())
|
||||
|
||||
@property
|
||||
def latestVersion(self):
|
||||
@ -174,11 +201,13 @@ class MCServerChunkGenerator(object):
|
||||
else:
|
||||
javaExe = which("java")
|
||||
|
||||
jarcache = None
|
||||
jarcache = ServerJarCache()
|
||||
tempWorldCache = {}
|
||||
@classmethod
|
||||
def reloadJarCache(cls):
|
||||
cls.jarcache = ServerJarCache()
|
||||
|
||||
def __init__(self, version=None, jarfile=None):
|
||||
if self.__class__.jarcache is None:
|
||||
self.__class__.jarcache = ServerJarCache()
|
||||
if self.javaExe is None:
|
||||
raise JavaNotFound, "Could not find java. Please check that java is installed correctly. (Could not find java in your PATH environment variable.)"
|
||||
if jarfile is None:
|
||||
@ -187,14 +216,13 @@ class MCServerChunkGenerator(object):
|
||||
raise VersionNotFound, "Could not find minecraft_server.jar for version {0}. Please make sure that a minecraft_server.jar is placed under {1} in a subfolder named after the server's version number.".format(version or "(latest)", self.jarcache.cacheDir)
|
||||
self.serverJarFile = jarfile
|
||||
self.serverVersion = self._serverVersion()
|
||||
self.tempWorldCache = {}
|
||||
|
||||
def clearWorldCache(self):
|
||||
tempDirs = [tempDir for tempDir, _tempWorld in self.tempWorldCache.itervalues()]
|
||||
#self.tempWorldCache = None
|
||||
@classmethod
|
||||
def clearWorldCache(cls):
|
||||
cls.tempWorldCache = {}
|
||||
|
||||
for tempDir in tempDirs:
|
||||
shutil.rmtree(tempDir)
|
||||
for tempDir in os.listdir(cls.worldCacheDir):
|
||||
shutil.rmtree(os.path.join(cls.worldCacheDir, tempDir))
|
||||
|
||||
def waitForServer(self, proc):
|
||||
""" wait for the server to finish starting up, then stop it. """
|
||||
@ -209,17 +237,32 @@ class MCServerChunkGenerator(object):
|
||||
proc.wait()
|
||||
raise RuntimeError, "Server Died!"
|
||||
|
||||
worldCacheDir = os.path.join(tempfile.gettempdir(), "pymclevel_MCServerChunkGenerator")
|
||||
|
||||
def tempWorldForLevel(self, level):
|
||||
if level.RandomSeed in self.tempWorldCache:
|
||||
return self.tempWorldCache[level.RandomSeed]
|
||||
|
||||
#tempDir = tempfile.mkdtemp("mclevel_servergen")
|
||||
tempDir = os.path.join(tempfile.gettempdir(), "pymclevel_MCServerChunkGenerator", self.serverVersion, str(level.RandomSeed))
|
||||
tempDir = os.path.join(self.worldCacheDir, self.jarcache.checksumForVersion(self.serverVersion), str(level.RandomSeed))
|
||||
readme = os.path.join(self.worldCacheDir, "README.TXT")
|
||||
|
||||
if not os.path.exists(readme):
|
||||
with file(readme, "w") as f:
|
||||
f.write("""
|
||||
About this folder:
|
||||
|
||||
This folder is used by MCEdit and pymclevel to cache levels during terrain
|
||||
generation. Feel free to delete it for any reason.
|
||||
""")
|
||||
|
||||
if not os.path.exists(tempDir):
|
||||
os.makedirs(tempDir)
|
||||
|
||||
tempWorldDir = os.path.join(tempDir, "world")
|
||||
tempWorld = 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[level.RandomSeed] = (tempWorld, tempDir)
|
||||
return (tempWorld, tempDir)
|
||||
|
||||
@ -233,12 +276,17 @@ class MCServerChunkGenerator(object):
|
||||
tempWorld.setPlayerSpawnPosition((cx * 16, 64, cz * 16))
|
||||
tempWorld.saveInPlace()
|
||||
tempWorld.unloadRegions()
|
||||
|
||||
proc = self.runServer(tempDir)
|
||||
self.waitForServer(proc)
|
||||
|
||||
tempWorld.loadLevelDat() #reload version number
|
||||
|
||||
def copyChunkAtPosition(self, tempWorld, level, cx, cz):
|
||||
tempChunk = tempWorld.getChunk(cx, cz)
|
||||
try:
|
||||
tempChunk = tempWorld.getChunk(cx, cz)
|
||||
except ChunkNotPresent, e:
|
||||
raise ChunkNotPresent, "While generating a world in {0} using server {1} ({2!r})".format(tempWorld, self.serverJarFile, e), sys.exc_traceback
|
||||
|
||||
tempChunk.decompress()
|
||||
tempChunk.unpackChunkData()
|
||||
root_tag = tempChunk.root_tag
|
||||
@ -257,23 +305,40 @@ class MCServerChunkGenerator(object):
|
||||
|
||||
|
||||
def generateChunksInLevel(self, level, chunks):
|
||||
for i in self.generateChunksInLevelIter(level, chunks):
|
||||
yield i
|
||||
|
||||
def generateChunksInLevelIter(self, level, chunks):
|
||||
assert isinstance(level, MCInfdevOldLevel)
|
||||
tempWorld, tempDir = self.tempWorldForLevel(level)
|
||||
|
||||
startRegionRadius = 7 #more recent servers use 12
|
||||
def inBox(cPos):
|
||||
x, z = cPos
|
||||
return x > centercx - 12 and x < centercx + 12 and z > centercz - 12 and z < centercz + 12
|
||||
return (x > centercx - startRegionRadius
|
||||
and x < centercx + startRegionRadius
|
||||
and z > centercz - startRegionRadius
|
||||
and z < centercz + startRegionRadius)
|
||||
|
||||
startLength = len(chunks)
|
||||
while len(chunks):
|
||||
centercx, centercz = chunks[0]
|
||||
|
||||
boxedChunks = [cPos for cPos in chunks if inBox(cPos)]
|
||||
print "Generating {0} chunks out of {1} starting from {2}".format(len(boxedChunks), len(chunks), (centercx, centercz))
|
||||
yield startLength - len(chunks), startLength
|
||||
|
||||
chunks = [c for c in chunks if not inBox(c)]
|
||||
|
||||
self.generateAtPosition(tempWorld, tempDir, centercx, centercz)
|
||||
|
||||
for cx, cz in boxedChunks:
|
||||
self.copyChunkAtPosition(tempWorld, level, cx, cz)
|
||||
try:
|
||||
self.copyChunkAtPosition(tempWorld, level, cx, cz)
|
||||
except ChunkNotPresent:
|
||||
print "Failed to copy chunk", (cx, cz), "at a delta of ", (centercx - cx, centercz - cz), "from the last generation center."
|
||||
raise
|
||||
|
||||
|
||||
level.saveInPlace()
|
||||
|
||||
@ -305,7 +370,7 @@ class MCServerChunkGenerator(object):
|
||||
#out, err = proc.communicate()
|
||||
#for line in err.split("\n"):
|
||||
|
||||
while True:
|
||||
while proc.poll() is None:
|
||||
line = proc.stderr.readline()
|
||||
if "Preparing start region" in line: break
|
||||
if "Starting minecraft server version" in line:
|
||||
@ -1337,7 +1402,7 @@ class MCInfdevOldLevel(EntityLevel):
|
||||
self.preloadDimensions();
|
||||
#self.preloadChunkPositions();
|
||||
|
||||
def loadLevelDat(self, create, random_seed, last_played):
|
||||
def loadLevelDat(self, create=False, random_seed=None, last_played=None):
|
||||
|
||||
if create:
|
||||
self.create(self.filename, random_seed, last_played);
|
||||
@ -1528,6 +1593,17 @@ class MCInfdevOldLevel(EntityLevel):
|
||||
else:
|
||||
return None
|
||||
|
||||
@version.setter
|
||||
@decompress_first
|
||||
def version(self, val):
|
||||
if 'version' in self.root_tag['Data']:
|
||||
self.root_tag['Data']['version'].value = val
|
||||
|
||||
|
||||
@version.deleter
|
||||
@decompress_first
|
||||
def version(self):
|
||||
self.root_tag['Data'].pop('version')
|
||||
|
||||
def _loadChunk(self, chunk):
|
||||
""" load the chunk data from disk, and set the chunk's compressedTag
|
||||
@ -1792,7 +1868,7 @@ class MCInfdevOldLevel(EntityLevel):
|
||||
the chunk is done later, accesses to chunk attributes may
|
||||
raise ChunkMalformed"""
|
||||
|
||||
if not self.containsChunk(cx, cz):
|
||||
if not self.containsChunk(cx, cz) :
|
||||
raise ChunkNotPresent, (cx, cz);
|
||||
|
||||
if not (cx, cz) in self._loadedChunks:
|
||||
@ -2626,7 +2702,7 @@ class MCInfdevOldLevel(EntityLevel):
|
||||
return array(yp);
|
||||
|
||||
class MCAlphaDimension (MCInfdevOldLevel):
|
||||
def loadLevelDat(self, create, random_seed, last_played):
|
||||
def loadLevelDat(self, create=False, random_seed=None, last_played=None):
|
||||
pass;
|
||||
def preloadDimensions(self):
|
||||
pass
|
||||
@ -2738,7 +2814,7 @@ class ZipSchematic (MCInfdevOldLevel):
|
||||
def preloadDimensions(self):
|
||||
pass
|
||||
|
||||
def loadLevelDat(self, create, random_seed, last_played):
|
||||
def loadLevelDat(self, create=False, random_seed=None, last_played=None):
|
||||
if create:
|
||||
raise NotImplementedError, "Cannot save zipfiles yet!"
|
||||
|
||||
|
Reference in New Issue
Block a user