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
|
self.cacheDir = cacheDir
|
||||||
if not os.path.exists(self.cacheDir):
|
if not os.path.exists(self.cacheDir):
|
||||||
os.makedirs(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)
|
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:
|
for f in cacheDirList:
|
||||||
p = os.path.join(self.cacheDir, f)
|
p = os.path.join(self.cacheDir, f)
|
||||||
@ -126,6 +148,11 @@ class ServerJarCache(object):
|
|||||||
|
|
||||||
def jarfileForVersion(self, v):
|
def jarfileForVersion(self, v):
|
||||||
return os.path.join(self.cacheDir, v, "minecraft_server.jar")
|
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
|
@property
|
||||||
def latestVersion(self):
|
def latestVersion(self):
|
||||||
@ -174,11 +201,13 @@ class MCServerChunkGenerator(object):
|
|||||||
else:
|
else:
|
||||||
javaExe = which("java")
|
javaExe = which("java")
|
||||||
|
|
||||||
jarcache = None
|
jarcache = ServerJarCache()
|
||||||
|
tempWorldCache = {}
|
||||||
|
@classmethod
|
||||||
|
def reloadJarCache(cls):
|
||||||
|
cls.jarcache = ServerJarCache()
|
||||||
|
|
||||||
def __init__(self, version=None, jarfile=None):
|
def __init__(self, version=None, jarfile=None):
|
||||||
if self.__class__.jarcache is None:
|
|
||||||
self.__class__.jarcache = ServerJarCache()
|
|
||||||
if self.javaExe is None:
|
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.)"
|
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:
|
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)
|
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.serverJarFile = jarfile
|
||||||
self.serverVersion = self._serverVersion()
|
self.serverVersion = self._serverVersion()
|
||||||
self.tempWorldCache = {}
|
|
||||||
|
|
||||||
def clearWorldCache(self):
|
@classmethod
|
||||||
tempDirs = [tempDir for tempDir, _tempWorld in self.tempWorldCache.itervalues()]
|
def clearWorldCache(cls):
|
||||||
#self.tempWorldCache = None
|
cls.tempWorldCache = {}
|
||||||
|
|
||||||
for tempDir in tempDirs:
|
for tempDir in os.listdir(cls.worldCacheDir):
|
||||||
shutil.rmtree(tempDir)
|
shutil.rmtree(os.path.join(cls.worldCacheDir, tempDir))
|
||||||
|
|
||||||
def waitForServer(self, proc):
|
def waitForServer(self, proc):
|
||||||
""" wait for the server to finish starting up, then stop it. """
|
""" wait for the server to finish starting up, then stop it. """
|
||||||
@ -209,17 +237,32 @@ class MCServerChunkGenerator(object):
|
|||||||
proc.wait()
|
proc.wait()
|
||||||
raise RuntimeError, "Server Died!"
|
raise RuntimeError, "Server Died!"
|
||||||
|
|
||||||
|
worldCacheDir = os.path.join(tempfile.gettempdir(), "pymclevel_MCServerChunkGenerator")
|
||||||
|
|
||||||
def tempWorldForLevel(self, level):
|
def tempWorldForLevel(self, level):
|
||||||
if level.RandomSeed in self.tempWorldCache:
|
if level.RandomSeed in self.tempWorldCache:
|
||||||
return self.tempWorldCache[level.RandomSeed]
|
return self.tempWorldCache[level.RandomSeed]
|
||||||
|
|
||||||
#tempDir = tempfile.mkdtemp("mclevel_servergen")
|
#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):
|
if not os.path.exists(tempDir):
|
||||||
os.makedirs(tempDir)
|
os.makedirs(tempDir)
|
||||||
|
|
||||||
tempWorldDir = os.path.join(tempDir, "world")
|
tempWorldDir = os.path.join(tempDir, "world")
|
||||||
tempWorld = MCInfdevOldLevel(tempWorldDir, create=True, random_seed=level.RandomSeed)
|
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)
|
self.tempWorldCache[level.RandomSeed] = (tempWorld, tempDir)
|
||||||
return (tempWorld, tempDir)
|
return (tempWorld, tempDir)
|
||||||
|
|
||||||
@ -233,12 +276,17 @@ class MCServerChunkGenerator(object):
|
|||||||
tempWorld.setPlayerSpawnPosition((cx * 16, 64, cz * 16))
|
tempWorld.setPlayerSpawnPosition((cx * 16, 64, cz * 16))
|
||||||
tempWorld.saveInPlace()
|
tempWorld.saveInPlace()
|
||||||
tempWorld.unloadRegions()
|
tempWorld.unloadRegions()
|
||||||
|
|
||||||
proc = self.runServer(tempDir)
|
proc = self.runServer(tempDir)
|
||||||
self.waitForServer(proc)
|
self.waitForServer(proc)
|
||||||
|
|
||||||
|
tempWorld.loadLevelDat() #reload version number
|
||||||
|
|
||||||
def copyChunkAtPosition(self, tempWorld, level, cx, cz):
|
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.decompress()
|
||||||
tempChunk.unpackChunkData()
|
tempChunk.unpackChunkData()
|
||||||
root_tag = tempChunk.root_tag
|
root_tag = tempChunk.root_tag
|
||||||
@ -257,23 +305,40 @@ class MCServerChunkGenerator(object):
|
|||||||
|
|
||||||
|
|
||||||
def generateChunksInLevel(self, level, chunks):
|
def generateChunksInLevel(self, level, chunks):
|
||||||
|
for i in self.generateChunksInLevelIter(level, chunks):
|
||||||
|
yield i
|
||||||
|
|
||||||
|
def generateChunksInLevelIter(self, level, chunks):
|
||||||
assert isinstance(level, MCInfdevOldLevel)
|
assert isinstance(level, MCInfdevOldLevel)
|
||||||
tempWorld, tempDir = self.tempWorldForLevel(level)
|
tempWorld, tempDir = self.tempWorldForLevel(level)
|
||||||
|
|
||||||
|
startRegionRadius = 7 #more recent servers use 12
|
||||||
def inBox(cPos):
|
def inBox(cPos):
|
||||||
x, z = 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):
|
while len(chunks):
|
||||||
centercx, centercz = chunks[0]
|
centercx, centercz = chunks[0]
|
||||||
|
|
||||||
boxedChunks = [cPos for cPos in chunks if inBox(cPos)]
|
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))
|
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)]
|
chunks = [c for c in chunks if not inBox(c)]
|
||||||
|
|
||||||
self.generateAtPosition(tempWorld, tempDir, centercx, centercz)
|
self.generateAtPosition(tempWorld, tempDir, centercx, centercz)
|
||||||
|
|
||||||
for cx, cz in boxedChunks:
|
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()
|
level.saveInPlace()
|
||||||
|
|
||||||
@ -305,7 +370,7 @@ class MCServerChunkGenerator(object):
|
|||||||
#out, err = proc.communicate()
|
#out, err = proc.communicate()
|
||||||
#for line in err.split("\n"):
|
#for line in err.split("\n"):
|
||||||
|
|
||||||
while True:
|
while proc.poll() is None:
|
||||||
line = proc.stderr.readline()
|
line = proc.stderr.readline()
|
||||||
if "Preparing start region" in line: break
|
if "Preparing start region" in line: break
|
||||||
if "Starting minecraft server version" in line:
|
if "Starting minecraft server version" in line:
|
||||||
@ -1337,7 +1402,7 @@ class MCInfdevOldLevel(EntityLevel):
|
|||||||
self.preloadDimensions();
|
self.preloadDimensions();
|
||||||
#self.preloadChunkPositions();
|
#self.preloadChunkPositions();
|
||||||
|
|
||||||
def loadLevelDat(self, create, random_seed, last_played):
|
def loadLevelDat(self, create=False, random_seed=None, last_played=None):
|
||||||
|
|
||||||
if create:
|
if create:
|
||||||
self.create(self.filename, random_seed, last_played);
|
self.create(self.filename, random_seed, last_played);
|
||||||
@ -1528,6 +1593,17 @@ class MCInfdevOldLevel(EntityLevel):
|
|||||||
else:
|
else:
|
||||||
return None
|
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):
|
def _loadChunk(self, chunk):
|
||||||
""" load the chunk data from disk, and set the chunk's compressedTag
|
""" 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
|
the chunk is done later, accesses to chunk attributes may
|
||||||
raise ChunkMalformed"""
|
raise ChunkMalformed"""
|
||||||
|
|
||||||
if not self.containsChunk(cx, cz):
|
if not self.containsChunk(cx, cz) :
|
||||||
raise ChunkNotPresent, (cx, cz);
|
raise ChunkNotPresent, (cx, cz);
|
||||||
|
|
||||||
if not (cx, cz) in self._loadedChunks:
|
if not (cx, cz) in self._loadedChunks:
|
||||||
@ -2626,7 +2702,7 @@ class MCInfdevOldLevel(EntityLevel):
|
|||||||
return array(yp);
|
return array(yp);
|
||||||
|
|
||||||
class MCAlphaDimension (MCInfdevOldLevel):
|
class MCAlphaDimension (MCInfdevOldLevel):
|
||||||
def loadLevelDat(self, create, random_seed, last_played):
|
def loadLevelDat(self, create=False, random_seed=None, last_played=None):
|
||||||
pass;
|
pass;
|
||||||
def preloadDimensions(self):
|
def preloadDimensions(self):
|
||||||
pass
|
pass
|
||||||
@ -2738,7 +2814,7 @@ class ZipSchematic (MCInfdevOldLevel):
|
|||||||
def preloadDimensions(self):
|
def preloadDimensions(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def loadLevelDat(self, create, random_seed, last_played):
|
def loadLevelDat(self, create=False, random_seed=None, last_played=None):
|
||||||
if create:
|
if create:
|
||||||
raise NotImplementedError, "Cannot save zipfiles yet!"
|
raise NotImplementedError, "Cannot save zipfiles yet!"
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user