(I like to store backups of my worlds in the same dir as World1.tar and it was trying to open up world number 'r')
156 lines
5.3 KiB
Python
156 lines
5.3 KiB
Python
import functools
|
|
import os
|
|
import os.path
|
|
import multiprocessing
|
|
|
|
from PIL import Image
|
|
|
|
import chunk
|
|
import nbt
|
|
|
|
"""
|
|
This module has routines related to generating all the chunks for a world
|
|
and for extracting information about available worlds
|
|
|
|
"""
|
|
|
|
base36decode = functools.partial(int, base=36)
|
|
|
|
|
|
def _convert_coords(chunks):
|
|
"""Takes the list of (chunkx, chunky, chunkfile) where chunkx and chunky
|
|
are in the chunk coordinate system, and figures out the row and column in
|
|
the image each one should be.
|
|
|
|
returns mincol, maxcol, minrow, maxrow, chunks_translated
|
|
chunks_translated is a list of (col, row, filename)
|
|
"""
|
|
chunks_translated = []
|
|
# columns are determined by the sum of the chunk coords, rows are the
|
|
# difference
|
|
item = chunks[0]
|
|
mincol = maxcol = item[0] + item[1]
|
|
minrow = maxrow = item[1] - item[0]
|
|
for c in chunks:
|
|
col = c[0] + c[1]
|
|
mincol = min(mincol, col)
|
|
maxcol = max(maxcol, col)
|
|
row = c[1] - c[0]
|
|
minrow = min(minrow, row)
|
|
maxrow = max(maxrow, row)
|
|
chunks_translated.append((col, row, c[2]))
|
|
|
|
return mincol, maxcol, minrow, maxrow, chunks_translated
|
|
|
|
class WorldRenderer(object):
|
|
"""Renders a world's worth of chunks"""
|
|
def __init__(self, worlddir):
|
|
self.worlddir = worlddir
|
|
self.caves = False
|
|
|
|
def go(self, procs):
|
|
"""Starts the render. This returns when it is finished"""
|
|
|
|
print "Scanning chunks"
|
|
raw_chunks = self._find_chunkfiles()
|
|
|
|
# Translate chunks to our diagonal coordinate system
|
|
mincol, maxcol, minrow, maxrow, chunks = _convert_coords(raw_chunks)
|
|
|
|
self.chunkmap = self._render_chunks_async(chunks, procs)
|
|
|
|
self.mincol = mincol
|
|
self.maxcol = maxcol
|
|
self.minrow = minrow
|
|
self.maxrow = maxrow
|
|
|
|
def _find_chunkfiles(self):
|
|
"""Returns a list of all the chunk file locations, and the file they
|
|
correspond to.
|
|
|
|
Returns a list of (chunkx, chunky, filename) where chunkx and chunky are
|
|
given in chunk coordinates. Use convert_coords() to turn the resulting list
|
|
into an oblique coordinate system"""
|
|
all_chunks = []
|
|
for dirpath, dirnames, filenames in os.walk(self.worlddir):
|
|
if not dirnames and filenames:
|
|
for f in filenames:
|
|
if f.startswith("c.") and f.endswith(".dat"):
|
|
p = f.split(".")
|
|
all_chunks.append((base36decode(p[1]), base36decode(p[2]),
|
|
os.path.join(dirpath, f)))
|
|
return all_chunks
|
|
|
|
def _render_chunks_async(self, chunks, processes):
|
|
"""Starts up a process pool and renders all the chunks asynchronously.
|
|
|
|
chunks is a list of (col, row, chunkfile)
|
|
|
|
Returns a dictionary mapping (col, row) to the file where that
|
|
chunk is rendered as an image
|
|
"""
|
|
results = {}
|
|
if processes == 1:
|
|
# Skip the multiprocessing stuff
|
|
print "Rendering chunks synchronously since you requested 1 process"
|
|
for i, (col, row, chunkfile) in enumerate(chunks):
|
|
result = chunk.render_and_save(chunkfile, cave=self.caves)
|
|
results[(col, row)] = result
|
|
if i > 0:
|
|
if 1000 % i == 0 or i % 1000 == 0:
|
|
print "{0}/{1} chunks rendered".format(i, len(chunks))
|
|
else:
|
|
print "Rendering chunks in {0} processes".format(processes)
|
|
pool = multiprocessing.Pool(processes=processes)
|
|
asyncresults = []
|
|
for col, row, chunkfile in chunks:
|
|
result = pool.apply_async(chunk.render_and_save, args=(chunkfile,),
|
|
kwds=dict(cave=self.caves))
|
|
asyncresults.append((col, row, result))
|
|
|
|
pool.close()
|
|
|
|
for i, (col, row, result) in enumerate(asyncresults):
|
|
results[(col, row)] = result.get()
|
|
if i > 0:
|
|
if 1000 % i == 0 or i % 1000 == 0:
|
|
print "{0}/{1} chunks rendered".format(i, len(chunks))
|
|
|
|
pool.join()
|
|
print "Done!"
|
|
|
|
return results
|
|
|
|
def get_save_dir():
|
|
"""Returns the path to the local saves directory
|
|
* On Windows, at %APPDATA%/.minecraft/saves/
|
|
* On Darwin, at $HOME/Library/Application Support/minecraft/saves/
|
|
* at $HOME/.minecraft/saves/
|
|
|
|
"""
|
|
|
|
savepaths = []
|
|
if "APPDATA" in os.environ:
|
|
savepaths += [os.path.join(os.environ['APPDATA'], ".minecraft", "saves")]
|
|
if "HOME" in os.environ:
|
|
savepaths += [os.path.join(os.environ['HOME'], "Library",
|
|
"Application Support", "minecraft", "saves")]
|
|
savepaths += [os.path.join(os.environ['HOME'], ".minecraft", "saves")]
|
|
|
|
for path in savepaths:
|
|
if os.path.exists(path):
|
|
return path
|
|
|
|
def get_worlds():
|
|
"Returns {world # : level.dat information}"
|
|
ret = {}
|
|
save_dir = get_save_dir()
|
|
for dir in os.listdir(save_dir):
|
|
if dir.startswith("World") and len(dir) == 6:
|
|
world_n = int(dir[-1])
|
|
info = nbt.load(os.path.join(save_dir, dir, "level.dat"))[1]
|
|
info['Data']['path'] = os.path.join(save_dir, dir)
|
|
ret[world_n] = info['Data']
|
|
|
|
return ret
|