0

Merge branch 'master' into rendermode-options

Conflicts:
	overviewer.py
This commit is contained in:
Aaron Griffith
2011-08-18 11:40:26 -04:00
21 changed files with 632 additions and 139 deletions

View File

@@ -73,18 +73,30 @@ def get_lvldata(world, filename, x, y, retries=2):
def get_blockarray(level):
"""Takes the level struct as returned from get_lvldata, and returns the
Block array, which just contains all the block ids"""
return numpy.frombuffer(level['Blocks'], dtype=numpy.uint8).reshape((16,16,128))
return level['Blocks']
def get_blockarray_fromfile(filename):
def get_blockarray_fromfile(filename, north_direction='lower-left'):
"""Same as get_blockarray except takes a filename. This is a shortcut"""
d = nbt.load_from_region(filename, x, y)
d = nbt.load_from_region(filename, x, y, north_direction)
level = d[1]['Level']
return get_blockarray(level)
chunk_data = level
rots = 0
if self.north_direction == 'upper-left':
rots = 1
elif self.north_direction == 'upper-right':
rots = 2
elif self.north_direction == 'lower-right':
rots = 3
chunk_data['Blocks'] = numpy.rot90(numpy.frombuffer(
level['Blocks'], dtype=numpy.uint8).reshape((16,16,128)),
rots)
return get_blockarray(chunk_data)
def get_skylight_array(level):
"""Returns the skylight array. This is 4 bits per block, but it is
expanded for you so you may index it normally."""
skylight = numpy.frombuffer(level['SkyLight'], dtype=numpy.uint8).reshape((16,16,64))
skylight = level['SkyLight']
# this array is 2 blocks per byte, so expand it
skylight_expanded = numpy.empty((16,16,128), dtype=numpy.uint8)
# Even elements get the lower 4 bits
@@ -97,7 +109,7 @@ def get_blocklight_array(level):
"""Returns the blocklight array. This is 4 bits per block, but it
is expanded for you so you may index it normally."""
# expand just like get_skylight_array()
blocklight = numpy.frombuffer(level['BlockLight'], dtype=numpy.uint8).reshape((16,16,64))
blocklight = level['BlockLight']
blocklight_expanded = numpy.empty((16,16,128), dtype=numpy.uint8)
blocklight_expanded[:,:,::2] = blocklight & 0x0F
blocklight_expanded[:,:,1::2] = (blocklight & 0xF0) >> 4
@@ -106,7 +118,7 @@ def get_blocklight_array(level):
def get_blockdata_array(level):
"""Returns the ancillary data from the 'Data' byte array. Data is packed
in a similar manner to skylight data"""
return numpy.frombuffer(level['Data'], dtype=numpy.uint8).reshape((16,16,64))
return level['Data']
def get_tileentity_data(level):
"""Returns the TileEntities TAG_List from chunk dat file"""
@@ -128,8 +140,8 @@ solid_blocks = set([1, 2, 3, 4, 5, 7, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22
fluid_blocks = set([8,9,10,11])
# This set holds block ids that are not candidates for spawning mobs on
# (glass, half blocks)
nospawn_blocks = set([20,44])
# (glass, half blocks, fluids)
nospawn_blocks = set([20,44]).union(fluid_blocks)
class ChunkCorrupt(Exception):
pass

View File

@@ -1,4 +1,4 @@
from optparse import OptionParser
import optparse
import sys
import os.path
import logging
@@ -9,22 +9,23 @@ class OptionsResults(object):
class ConfigOptionParser(object):
def __init__(self, **kwargs):
self.cmdParser = OptionParser(usage=kwargs.get("usage",""))
self.cmdParser = optparse.OptionParser(usage=kwargs.get("usage",""))
self.configFile = kwargs.get("config","settings.py")
self.configVars = []
self.advancedHelp = []
# these are arguments not understood by OptionParser, so they must be removed
# in add_option before being passed to the OptionParser
# note that default is a valid OptionParser argument, but we remove it
# because we want to do our default value handling
self.customArgs = ["required", "commandLineOnly", "default", "listify", "listdelim", "choices"]
self.customArgs = ["required", "commandLineOnly", "default", "listify", "listdelim", "choices", "helptext", "advanced"]
self.requiredArgs = []
# add the *very* special config-file path option
self.add_option("--settings", dest="config_file", help="Specifies a settings file to load, by name. This file's format is discussed in the README.", metavar="PATH", type="string", commandLineOnly=True)
# add the *very* special advanced help and config-file path options
self.add_option("--advanced-help", dest="advanced_help", action="store_true", helptext="Display help - including advanced options", commandLineOnly=True)
self.add_option("--settings", dest="config_file", helptext="Specifies a settings file to load, by name. This file's format is discussed in the README.", metavar="PATH", type="string", commandLineOnly=True)
def display_config(self):
logging.info("Using the following settings:")
@@ -35,22 +36,38 @@ class ConfigOptionParser(object):
def add_option(self, *args, **kwargs):
if kwargs.get("configFileOnly", False) and kwargs.get("commandLineOnly", False):
raise Exception(args, "configFileOnly and commandLineOnly are mututally exclusive")
self.configVars.append(kwargs.copy())
if not kwargs.get("configFileOnly", False):
for arg in self.customArgs:
if arg in kwargs.keys(): del kwargs[arg]
if kwargs.get("advanced"):
kwargs['help'] = optparse.SUPPRESS_HELP
self.advancedHelp.append((args, kwargs.copy()))
else:
kwargs["help"]=kwargs["helptext"]
for arg in self.customArgs:
if arg in kwargs.keys(): del kwargs[arg]
if kwargs.get("type", None):
kwargs['type'] = 'string' # we'll do our own converting later
self.cmdParser.add_option(*args, **kwargs)
if kwargs.get("type", None):
kwargs['type'] = 'string' # we'll do our own converting later
self.cmdParser.add_option(*args, **kwargs)
def print_help(self):
self.cmdParser.print_help()
def advanced_help(self):
self.cmdParser.set_conflict_handler('resolve') # Allows us to overwrite the previous definitions
for opt in self.advancedHelp:
opt[1]['help']="[!]" + opt[1]['helptext']
for arg in self.customArgs:
if arg in opt[1].keys():
del opt[1][arg]
if opt[1].get("type", None):
opt[1]['type'] = 'string' # we'll do our own converting later
self.cmdParser.add_option(*opt[0], **opt[1])
self.cmdParser.epilog = "Advanced options indicated by [!]. These options should not normally be required, and may have caveats regarding their use. See README file for more details"
self.print_help()
def parse_args(self):
# first, load the results from the command line:
@@ -61,7 +78,6 @@ class ConfigOptionParser(object):
g = dict()
for a in self.configVars:
n = a['dest']
if a.get('configFileOnly', False): continue
if a.get('commandLineOnly', False): continue
v = getattr(options, n)
if v != None:
@@ -116,7 +132,6 @@ class ConfigOptionParser(object):
# third, merge options into configReslts (with options overwriting anything in configResults)
for a in self.configVars:
n = a['dest']
if a.get('configFileOnly', False): continue
if getattr(options, n) != None:
configResults.__dict__[n] = getattr(options, n)

View File

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@@ -114,6 +114,14 @@ body {
background-colour: #fff;
}
#searchControl>input.inactive {
color: #ccc;
}
#searchControl>input.active {
color: #000;
}
div#searchDropDown {
border: 1px solid #000;
width: 17em;

View File

@@ -536,6 +536,19 @@ var overviewer = {
var perPixel = 1.0 / (overviewerConfig.CONST.tileSize *
Math.pow(2, overviewerConfig.map.zoomLevels));
if(overviewerConfig.map.north_direction == 'upper-left'){
temp = x;
x = -y-1;
y = temp;
} else if(overviewerConfig.map.north_direction == 'upper-right'){
x = -x-1;
y = -y-1;
} else if(overviewerConfig.map.north_direction == 'lower-right'){
temp = x;
x = y;
y = -temp-1;
}
// This information about where the center column is may change with
// a different drawing implementation -- check it again after any
// drawing overhauls!
@@ -607,6 +620,19 @@ var overviewer = {
point.x += 64;
point.z -= 64;
if(overviewerConfig.map.north_direction == 'upper-left'){
temp = point.z;
point.z = -point.x;
point.x = temp;
} else if(overviewerConfig.map.north_direction == 'upper-right'){
point.x = -point.x;
point.z = -point.z;
} else if(overviewerConfig.map.north_direction == 'lower-right'){
temp = point.z;
point.z = point.x;
point.x = -temp;
}
return point;
},
/**
@@ -817,6 +843,25 @@ var overviewer = {
var searchInput = document.createElement("input");
searchInput.type = "text";
searchInput.value = "Sign Search";
searchInput.title = "Sign Search";
$(searchInput).addClass("inactive");
/* Hey dawg, I heard you like functions.
* So we defined a function inside your function.
*/
searchInput.onfocus = function() {
if (searchInput.value == "Sign Search") {
searchInput.value = "";
$(searchInput).removeClass("inactive").addClass("active");
}
};
searchInput.onblur = function() {
if (searchInput.value == "") {
searchInput.value = "Sign Search";
$(searchInput).removeClass("active").addClass("inactive");
}
};
searchControl.appendChild(searchInput);

View File

@@ -14,7 +14,7 @@ var overviewerConfig = {
'image': {
'defaultMarker': 'signpost.png',
'signMarker': 'signpost_icon.png',
'compass': 'compass.png',
'compass': 'compass_{north_direction}.png',
'spawnMarker': 'http://google-maps-icons.googlecode.com/files/home.png',
'queryMarker': 'http://google-maps-icons.googlecode.com/files/regroup.png'
},
@@ -94,7 +94,11 @@ var overviewerConfig = {
* Set to true to turn on debug mode, which adds a grid to the map along
* with co-ordinates and a bunch of console output.
*/
'debug': false
'debug': false,
/**
* Set which way north points.
*/
'north_direction': '{north_direction}'
},
/**
* Group definitions for objects that are partially selectable (signs and

View File

@@ -77,6 +77,7 @@ class MapGen(object):
self.web_assets_hook = configInfo.get('web_assets_hook', None)
self.web_assets_path = configInfo.get('web_assets_path', None)
self.bg_color = configInfo.get('bg_color')
self.north_direction = configInfo.get('north_direction', 'lower-left')
if not len(quadtrees) > 0:
raise ValueError("there must be at least one quadtree to work on")
@@ -121,6 +122,8 @@ class MapGen(object):
"{maxzoom}", str(zoomlevel))
config = config.replace(
"{zoomlevels}", str(zoomlevel))
config = config.replace(
"{north_direction}", self.north_direction)
config = config.replace("{spawn_coords}",
json.dumps(list(self.world.spawn)))
@@ -157,9 +160,6 @@ class MapGen(object):
def finalize(self):
if self.skipjs:
return
# since we will only discover PointsOfInterest in chunks that need to be
# [re]rendered, POIs like signs in unchanged chunks will not be listed
# in self.world.POI. To make sure we don't remove these from markers.js
@@ -172,6 +172,17 @@ class MapGen(object):
else:
markers = self.world.POI
# save persistent data
self.world.persistentData['POI'] = self.world.POI
self.world.persistentData['north_direction'] = self.world.north_direction
with open(self.world.pickleFile,"wb") as f:
cPickle.dump(self.world.persistentData,f)
# the rest of the function is javascript stuff
if self.skipjs:
return
# write out the default marker table
with open(os.path.join(self.destdir, "markers.js"), 'w') as output:
output.write("overviewer.collections.markerDatas.push([\n")
@@ -182,11 +193,6 @@ class MapGen(object):
output.write("\n")
output.write("]);\n")
# save persistent data
self.world.persistentData['POI'] = self.world.POI
with open(self.world.pickleFile,"wb") as f:
cPickle.dump(self.world.persistentData,f)
# write out the default (empty, but documented) region table
with open(os.path.join(self.destdir, "regions.js"), 'w') as output:
output.write('overviewer.collections.regionDatas.push([\n')

View File

@@ -17,6 +17,7 @@ import gzip, zlib
import struct
import StringIO
import os
import numpy
# decorator to handle filename or object as first parameter
def _file_loader(func):
@@ -34,15 +35,15 @@ def _file_loader(func):
def load(fileobj):
return NBTFileReader(fileobj).read_all()
def load_from_region(filename, x, y):
nbt = load_region(filename).load_chunk(x, y)
def load_from_region(filename, x, y, north_direction):
nbt = load_region(filename, north_direction).load_chunk(x, y)
if nbt is None:
return None ## return none. I think this is who we should indicate missing chunks
#raise IOError("No such chunk in region: (%i, %i)" % (x, y))
return nbt.read_all()
def load_region(filename):
return MCRFileReader(filename)
def load_region(filename, north_direction):
return MCRFileReader(filename, north_direction)
# compile the unpacker's into a classes
@@ -199,13 +200,24 @@ class MCRFileReader(object):
chunks (as instances of NBTFileReader), getting chunk timestamps,
and for listing chunks contained in the file."""
def __init__(self, filename):
def __init__(self, filename, north_direction):
self._file = None
self._filename = filename
self.north_direction = north_direction
# cache used when the entire header tables are read in get_chunks()
self._locations = None
self._timestamps = None
self._chunks = None
def get_north_rotations(self):
if self.north_direction == 'upper-left':
return 3
elif self.north_direction == 'upper-right':
return 2
elif self.north_direction == 'lower-right':
return 1
elif self.north_direction == 'lower-left':
return 0
def _read_24bit_int(self):
"""Read in a 24-bit, big-endian int, used in the chunk
@@ -318,21 +330,24 @@ class MCRFileReader(object):
self.openfile()
self._chunks = None
self._locations = []
self._locations = [0]*32*32
self._timestamps = []
# go to the beginning of the file
self._file.seek(0)
# read chunk location table
locations_append = self._locations.append
for _ in xrange(32*32):
locations_append(self._read_chunk_location())
locations_index = numpy.reshape(numpy.rot90(numpy.reshape(range(32*32),
(32, 32)), -self.get_north_rotations()), -1)
for i in locations_index:
self._locations[i] = self._read_chunk_location()
# read chunk timestamp table
timestamp_append = self._timestamps.append
for _ in xrange(32*32):
timestamp_append(self._read_chunk_timestamp())
self._timestamps = numpy.reshape(numpy.rot90(numpy.reshape(
self._timestamps, (32,32)),self.get_north_rotations()), -1)
if closeFile:
self.closefile()

View File

@@ -302,7 +302,7 @@ class QuadtreeGen(object):
tile_mtime = None
#check mtimes on each part of the quad, this also checks if they exist
needs_rerender = tile_mtime is None
needs_rerender = (tile_mtime is None) or self.forcerender
quadPath_filtered = []
for path in quadPath:
try:
@@ -318,7 +318,7 @@ class QuadtreeGen(object):
if tile_mtime is not None:
os.unlink(imgpath)
return
# quit now if we don't need rerender
# quit now if we don't need rerender
if not needs_rerender:
return
#logging.debug("writing out innertile {0}".format(imgpath))

View File

@@ -65,7 +65,8 @@ def pool_initializer(rendernode):
# make sure textures are generated for this process
# and initialize c_overviewer
textures.generate(path=rendernode.options.get('textures_path', None))
textures.generate(path=rendernode.options.get('textures_path', None),
north_direction=rendernode.options.get('north_direction', None))
c_overviewer.init_chunk_render()
# setup c_overviewer rendermode customs / options

View File

@@ -513,6 +513,9 @@ def generate_texture_tuple(img, blockid):
def generate_special_texture(blockID, data):
"""Generates a special texture, such as a correctly facing minecraft track"""
data = convert_data(blockID, data)
# blocks need to be handled here (and in chunk.py)
if blockID == 2: # grass
@@ -1679,6 +1682,237 @@ def generate_special_texture(blockID, data):
return None
def convert_data(blockID, data):
if blockID == 26: # bed
#Masked to not clobber block head/foot info
if _north == 'upper-left':
if (data & 0b0011) == 0: data = data & 0b1100 | 1
elif (data & 0b0011) == 1: data = data & 0b1100 | 2
elif (data & 0b0011) == 2: data = data & 0b1100 | 3
elif (data & 0b0011) == 3: data = data & 0b1100 | 0
elif _north == 'upper-right':
if (data & 0b0011) == 0: data = data & 0b1100 | 2
elif (data & 0b0011) == 1: data = data & 0b1100 | 3
elif (data & 0b0011) == 2: data = data & 0b1100 | 0
elif (data & 0b0011) == 3: data = data & 0b1100 | 1
elif _north == 'lower-right':
if (data & 0b0011) == 0: data = data & 0b1100 | 3
elif (data & 0b0011) == 1: data = data & 0b1100 | 0
elif (data & 0b0011) == 2: data = data & 0b1100 | 1
elif (data & 0b0011) == 3: data = data & 0b1100 | 2
if blockID in (29, 33, 34): # sticky piston, piston, piston extension
#Masked to not clobber block head/foot info
if _north == 'upper-left':
if (data & 0b0111) == 2: data = data & 0b1000 | 5
elif (data & 0b0111) == 3: data = data & 0b1000 | 4
elif (data & 0b0111) == 4: data = data & 0b1000 | 2
elif (data & 0b0111) == 5: data = data & 0b1000 | 3
elif _north == 'upper-right':
if (data & 0b0111) == 2: data = data & 0b1000 | 3
elif (data & 0b0111) == 3: data = data & 0b1000 | 2
elif (data & 0b0111) == 4: data = data & 0b1000 | 5
elif (data & 0b0111) == 5: data = data & 0b1000 | 4
elif _north == 'lower-right':
if (data & 0b0111) == 2: data = data & 0b1000 | 4
elif (data & 0b0111) == 3: data = data & 0b1000 | 5
elif (data & 0b0111) == 4: data = data & 0b1000 | 3
elif (data & 0b0111) == 5: data = data & 0b1000 | 2
if blockID in (27, 28, 66): # minetrack:
#Masked to not clobber powered rail on/off info
#Ascending and flat straight
if _north == 'upper-left':
if (data & 0b0111) == 0: data = data & 0b1000 | 1
elif (data & 0b0111) == 1: data = data & 0b1000 | 0
elif (data & 0b0111) == 2: data = data & 0b1000 | 5
elif (data & 0b0111) == 3: data = data & 0b1000 | 4
elif (data & 0b0111) == 4: data = data & 0b1000 | 2
elif (data & 0b0111) == 5: data = data & 0b1000 | 3
elif _north == 'upper-right':
if (data & 0b0111) == 2: data = data & 0b1000 | 3
elif (data & 0b0111) == 3: data = data & 0b1000 | 2
elif (data & 0b0111) == 4: data = data & 0b1000 | 5
elif (data & 0b0111) == 5: data = data & 0b1000 | 4
elif _north == 'lower-right':
if (data & 0b0111) == 0: data = data & 0b1000 | 1
elif (data & 0b0111) == 1: data = data & 0b1000 | 0
elif (data & 0b0111) == 2: data = data & 0b1000 | 4
elif (data & 0b0111) == 3: data = data & 0b1000 | 5
elif (data & 0b0111) == 4: data = data & 0b1000 | 3
elif (data & 0b0111) == 5: data = data & 0b1000 | 2
if blockID == 66: # normal minetrack only
#Corners
if _north == 'upper-left':
if data == 6: data = 7
elif data == 7: data = 8
elif data == 8: data = 6
elif data == 9: data = 9
elif _north == 'upper-right':
if data == 6: data = 8
elif data == 7: data = 9
elif data == 8: data = 6
elif data == 9: data = 7
elif _north == 'lower-right':
if data == 6: data = 9
elif data == 7: data = 6
elif data == 8: data = 8
elif data == 9: data = 7
if blockID in (50, 75, 76): # torch, off/on redstone torch
if _north == 'upper-left':
if data == 1: data = 3
elif data == 2: data = 4
elif data == 3: data = 2
elif data == 4: data = 1
elif _north == 'upper-right':
if data == 1: data = 2
elif data == 2: data = 1
elif data == 3: data = 4
elif data == 4: data = 3
elif _north == 'lower-right':
if data == 1: data = 4
elif data == 2: data = 3
elif data == 3: data = 1
elif data == 4: data = 2
if blockID in (53,67): # wooden and cobblestone stairs.
if _north == 'upper-left':
if data == 0: data = 2
elif data == 1: data = 3
elif data == 2: data = 1
elif data == 3: data = 0
elif _north == 'upper-right':
if data == 0: data = 1
elif data == 1: data = 0
elif data == 2: data = 3
elif data == 3: data = 2
elif _north == 'lower-right':
if data == 0: data = 3
elif data == 1: data = 2
elif data == 2: data = 0
elif data == 3: data = 1
if blockID in (61, 62, 23): # furnace and burning furnace
if _north == 'upper-left':
if data == 2: data = 5
elif data == 3: data = 4
elif data == 4: data = 2
elif data == 5: data = 3
elif _north == 'upper-right':
if data == 2: data = 3
elif data == 3: data = 2
elif data == 4: data = 5
elif data == 5: data = 4
elif _north == 'lower-right':
if data == 2: data = 4
elif data == 3: data = 5
elif data == 4: data = 3
elif data == 5: data = 2
if blockID == 63: # signposts
if _north == 'upper-left':
data = (data + 4) % 16
elif _north == 'upper-right':
data = (data + 8) % 16
elif _north == 'lower-right':
data = (data + 12) % 16
if blockID in (64,71): # wooden/iron door
#Masked to not clobber block top/bottom & swung info
if _north == 'upper-left':
if (data & 0b0011) == 0: data = data & 0b1100 | 1
elif (data & 0b0011) == 1: data = data & 0b1100 | 2
elif (data & 0b0011) == 2: data = data & 0b1100 | 3
elif (data & 0b0011) == 3: data = data & 0b1100 | 0
elif _north == 'upper-right':
if (data & 0b0011) == 0: data = data & 0b1100 | 2
elif (data & 0b0011) == 1: data = data & 0b1100 | 3
elif (data & 0b0011) == 2: data = data & 0b1100 | 0
elif (data & 0b0011) == 3: data = data & 0b1100 | 1
elif _north == 'lower-right':
if (data & 0b0011) == 0: data = data & 0b1100 | 3
elif (data & 0b0011) == 1: data = data & 0b1100 | 0
elif (data & 0b0011) == 2: data = data & 0b1100 | 1
elif (data & 0b0011) == 3: data = data & 0b1100 | 2
if blockID == 65: # ladder
if _north == 'upper-left':
if data == 2: data = 5
elif data == 3: data = 4
elif data == 4: data = 2
elif data == 5: data = 3
elif _north == 'upper-right':
if data == 2: data = 3
elif data == 3: data = 2
elif data == 4: data = 5
elif data == 5: data = 4
elif _north == 'lower-right':
if data == 2: data = 4
elif data == 3: data = 5
elif data == 4: data = 3
elif data == 5: data = 2
if blockID == 68: # wall sign
if _north == 'upper-left':
if data == 2: data = 5
elif data == 3: data = 4
elif data == 4: data = 2
elif data == 5: data = 3
elif _north == 'upper-right':
if data == 2: data = 3
elif data == 3: data = 2
elif data == 4: data = 5
elif data == 5: data = 4
elif _north == 'lower-right':
if data == 2: data = 4
elif data == 3: data = 5
elif data == 4: data = 3
elif data == 5: data = 2
if blockID in (86,91): # pumpkins, jack-o-lantern
if _north == 'upper-left':
if data == 0: data = 1
elif data == 1: data = 2
elif data == 2: data = 3
elif data == 3: data = 0
elif _north == 'upper-right':
if data == 0: data = 2
elif data == 1: data = 3
elif data == 2: data = 0
elif data == 3: data = 1
elif _north == 'lower-right':
if data == 0: data = 3
elif data == 1: data = 0
elif data == 2: data = 1
elif data == 3: data = 2
if blockID in (93, 94): # redstone repeaters, ON and OFF
#Masked to not clobber delay info
if _north == 'upper-left':
if (data & 0b0011) == 0: data = data & 0b1100 | 1
elif (data & 0b0011) == 1: data = data & 0b1100 | 2
elif (data & 0b0011) == 2: data = data & 0b1100 | 3
elif (data & 0b0011) == 3: data = data & 0b1100 | 0
elif _north == 'upper-right':
if (data & 0b0011) == 0: data = data & 0b1100 | 2
elif (data & 0b0011) == 1: data = data & 0b1100 | 3
elif (data & 0b0011) == 2: data = data & 0b1100 | 0
elif (data & 0b0011) == 3: data = data & 0b1100 | 1
elif _north == 'lower-right':
if (data & 0b0011) == 0: data = data & 0b1100 | 3
elif (data & 0b0011) == 1: data = data & 0b1100 | 0
elif (data & 0b0011) == 2: data = data & 0b1100 | 1
elif (data & 0b0011) == 3: data = data & 0b1100 | 2
if blockID == 96: # trapdoor
#Masked to not clobber opened/closed info
if _north == 'upper-left':
if (data & 0b0011) == 0: data = data & 0b1100 | 3
elif (data & 0b0011) == 1: data = data & 0b1100 | 2
elif (data & 0b0011) == 2: data = data & 0b1100 | 0
elif (data & 0b0011) == 3: data = data & 0b1100 | 1
elif _north == 'upper-right':
if (data & 0b0011) == 0: data = data & 0b1100 | 1
elif (data & 0b0011) == 1: data = data & 0b1100 | 0
elif (data & 0b0011) == 2: data = data & 0b1100 | 3
elif (data & 0b0011) == 3: data = data & 0b1100 | 2
elif _north == 'lower-right':
if (data & 0b0011) == 0: data = data & 0b1100 | 2
elif (data & 0b0011) == 1: data = data & 0b1100 | 3
elif (data & 0b0011) == 2: data = data & 0b1100 | 1
elif (data & 0b0011) == 3: data = data & 0b1100 | 0
return data
def tintTexture(im, c):
# apparently converting to grayscale drops the alpha channel?
i = ImageOps.colorize(ImageOps.grayscale(im), (0,0,0), c)
@@ -1722,8 +1956,25 @@ def getBiomeData(worlddir, chunkX, chunkY):
'''
global currentBiomeFile, currentBiomeData
biomeX = chunkX // 32
biomeY = chunkY // 32
rots = 0
if _north == 'upper-left':
temp = biomeX
biomeX = biomeY
biomeY = -temp-1
rots = 3
elif _north == 'upper-right':
biomeX = -biomeX-1
biomeY = -biomeY-1
rots = 2
elif _north == 'lower-right':
temp = biomeX
biomeX = -biomeY-1
biomeY = temp
rots = 1
biomeFile = "b.%d.%d.biome" % (chunkX // 32, chunkY // 32)
biomeFile = "b.%d.%d.biome" % (biomeX, biomeY)
if biomeFile == currentBiomeFile:
return currentBiomeData
@@ -1733,7 +1984,9 @@ def getBiomeData(worlddir, chunkX, chunkY):
# make sure the file size is correct
if not len(rawdata) == 512 * 512 * 2:
raise Exception("Biome file %s is not valid." % (biomeFile,))
data = numpy.frombuffer(rawdata, dtype=numpy.dtype(">u2"))
data = numpy.reshape(numpy.rot90(numpy.reshape(
numpy.frombuffer(rawdata, dtype=numpy.dtype(">u2")),
(512,512)),rots), -1)
except IOError:
data = None
pass # no biome data
@@ -1822,7 +2075,10 @@ biome_tall_fern_texture = None
biome_leaf_texture = None
specialblockmap = None
def generate(path=None,texture_size=24,bgc = (26,26,26,0)):
def generate(path=None,texture_size=24,bgc = (26,26,26,0),north_direction='lower-left'):
global _north
_north = north_direction
global _find_file_local_path
global bgcolor
bgcolor = bgc
global _find_file_local_path, texture_dimensions

View File

@@ -69,31 +69,11 @@ class World(object):
mincol = maxcol = minrow = maxrow = 0
def __init__(self, worlddir, useBiomeData=False,regionlist=None):
def __init__(self, worlddir, outputdir, useBiomeData=False, regionlist=None, north_direction="auto"):
self.worlddir = worlddir
self.outputdir = outputdir
self.useBiomeData = useBiomeData
#find region files, or load the region list
#this also caches all the region file header info
logging.info("Scanning regions")
regionfiles = {}
self.regions = {}
if regionlist:
self.regionlist = map(os.path.abspath, regionlist) # a list of paths
else:
self.regionlist = None
for x, y, regionfile in self._iterate_regionfiles():
mcr = self.reload_region(regionfile)
mcr.get_chunk_info()
regionfiles[(x,y)] = (x,y,regionfile,mcr)
self.regionfiles = regionfiles
# set the number of region file handles we will permit open at any time before we start closing them
# self.regionlimit = 1000
# the max number of chunks we will keep before removing them (includes emptry chunks)
self.chunklimit = 1024
self.chunkcount = 0
self.empty_chunk = [None,None]
logging.debug("Done scanning regions")
self.north_direction = north_direction
# figure out chunk format is in use
# if not mcregion, error out early
@@ -111,14 +91,69 @@ class World(object):
# info self.persistentData. This dictionary can hold any information
# that may be needed between runs.
# Currently only holds into about POIs (more more details, see quadtree)
# TODO maybe store this with the tiles, not with the world?
self.pickleFile = os.path.join(self.worlddir, "overviewer.dat")
self.oldPickleFile = os.path.join(self.worlddir, "overviewer.dat")
self.pickleFile = os.path.join(self.outputdir, "overviewer.dat")
if os.path.exists(self.oldPickleFile):
logging.warning("overviewer.dat detected in WorldDir - this is no longer the correct location")
if os.path.exists(self.pickleFile):
# new file exists, so make a note of it
logging.warning("you should delete the `overviewer.dat' file in your world directory")
else:
# new file does not exist, so move the old one
logging.warning("Moving overviewer.dat to OutputDir")
import shutil
try:
# make sure destination dir actually exists
try:
os.mkdir(self.outputdir)
except OSError: # already exists, or failed
pass
shutil.move(self.oldPickleFile, self.pickleFile)
logging.info("overviewer.dat moved")
except BaseException as ex:
logging.error("Unable to move overviewer.dat")
logging.debug(ex.str())
if os.path.exists(self.pickleFile):
self.persistentDataIsNew = False;
with open(self.pickleFile,"rb") as p:
self.persistentData = cPickle.load(p)
if not self.persistentData.get('north_direction', False):
# this is a pre-configurable-north map, so add the north_direction key
self.persistentData['north_direction'] = 'lower-left'
else:
# some defaults
self.persistentData = dict(POI=[])
# some defaults, presumably a new map
self.persistentData = dict(POI=[], north_direction='lower-left')
self.persistentDataIsNew = True # indicates that the values in persistentData are new defaults, and it's OK to override them
# handle 'auto' north
if self.north_direction == 'auto':
self.north_direction = self.persistentData['north_direction']
north_direction = self.north_direction
#find region files, or load the region list
#this also caches all the region file header info
logging.info("Scanning regions")
regionfiles = {}
self.regions = {}
if regionlist:
self.regionlist = map(os.path.abspath, regionlist) # a list of paths
else:
self.regionlist = None
for x, y, regionfile in self._iterate_regionfiles(regionlist):
mcr = self.reload_region(regionfile)
mcr.get_chunk_info()
regionfiles[(x,y)] = (x,y,regionfile,mcr)
self.regionfiles = regionfiles
# set the number of region file handles we will permit open at any time before we start closing them
# self.regionlimit = 1000
# the max number of chunks we will keep before removing them (includes emptry chunks)
self.chunklimit = 1024
self.chunkcount = 0
self.empty_chunk = [None,None]
logging.debug("Done scanning regions")
def get_region_path(self, chunkX, chunkY):
@@ -151,6 +186,18 @@ class World(object):
data = nbt.read_all()
level = data[1]['Level']
chunk_data = level
chunk_data['Blocks'] = numpy.array(numpy.rot90(numpy.frombuffer(
level['Blocks'], dtype=numpy.uint8).reshape((16,16,128)),
self._get_north_rotations()))
chunk_data['Data'] = numpy.array(numpy.rot90(numpy.frombuffer(
level['Data'], dtype=numpy.uint8).reshape((16,16,64)),
self._get_north_rotations()))
chunk_data['SkyLight'] = numpy.array(numpy.rot90(numpy.frombuffer(
level['SkyLight'], dtype=numpy.uint8).reshape((16,16,64)),
self._get_north_rotations()))
chunk_data['BlockLight'] = numpy.array(numpy.rot90(numpy.frombuffer(
level['BlockLight'], dtype=numpy.uint8).reshape((16,16,64)),
self._get_north_rotations()))
#chunk_data = {}
#chunk_data['skylight'] = chunk.get_skylight_array(level)
#chunk_data['blocklight'] = chunk.get_blocklight_array(level)
@@ -167,7 +214,7 @@ class World(object):
if self.regions.get(filename) is not None:
self.regions[filename][0].closefile()
chunkcache = {}
mcr = nbt.MCRFileReader(filename)
mcr = nbt.MCRFileReader(filename, self.north_direction)
self.regions[filename] = (mcr,os.path.getmtime(filename),chunkcache)
return mcr
@@ -183,7 +230,7 @@ class World(object):
in the image each one should be. Returns (col, row)."""
# columns are determined by the sum of the chunk coords, rows are the
# difference (TODO: be able to change direction of north)
# difference
# change this function, and you MUST change unconvert_coords
return (chunkx + chunky, chunky - chunkx)
@@ -201,9 +248,20 @@ class World(object):
## read spawn info from level.dat
data = nbt.load(os.path.join(self.worlddir, "level.dat"))[1]
spawnX = data['Data']['SpawnX']
disp_spawnX = spawnX = data['Data']['SpawnX']
spawnY = data['Data']['SpawnY']
spawnZ = data['Data']['SpawnZ']
disp_spawnZ = spawnZ = data['Data']['SpawnZ']
if self.north_direction == 'upper-left':
temp = spawnX
spawnX = -spawnZ
spawnZ = temp
elif self.north_direction == 'upper-right':
spawnX = -spawnX
spawnZ = -spawnZ
elif self.north_direction == 'lower-right':
temp = spawnX
spawnX = spawnZ
spawnZ = -temp
## The chunk that holds the spawn location
chunkX = spawnX/16
@@ -213,7 +271,7 @@ class World(object):
## The filename of this chunk
chunkFile = self.get_region_path(chunkX, chunkY)
if chunkFile is not None:
data = nbt.load_from_region(chunkFile, chunkX, chunkY)[1]
data = nbt.load_from_region(chunkFile, chunkX, chunkY, self.north_direction)[1]
if data is not None:
level = data['Level']
blockArray = numpy.frombuffer(level['Blocks'], dtype=numpy.uint8).reshape((16,16,128))
@@ -230,9 +288,9 @@ class World(object):
except ChunkCorrupt:
#ignore corrupt spawn, and continue
pass
self.POI.append( dict(x=spawnX, y=spawnY, z=spawnZ,
self.POI.append( dict(x=disp_spawnX, y=spawnY, z=disp_spawnZ,
msg="Spawn", type="spawn", chunk=(chunkX, chunkY)))
self.spawn = (spawnX, spawnY, spawnZ)
self.spawn = (disp_spawnX, spawnY, disp_spawnZ)
def go(self, procs):
"""Scan the world directory, to fill in
@@ -278,6 +336,16 @@ class World(object):
self.findTrueSpawn()
def _get_north_rotations(self):
if self.north_direction == 'upper-left':
return 1
elif self.north_direction == 'upper-right':
return 2
elif self.north_direction == 'lower-right':
return 3
elif self.north_direction == 'lower-left':
return 0
def _iterate_regionfiles(self,regionlist=None):
"""Returns an iterator of all of the region files, along with their
coordinates
@@ -294,7 +362,20 @@ class World(object):
if f.startswith("r.") and f.endswith(".mcr"):
p = f.split(".")
logging.debug("Using path %s from regionlist", f)
yield (int(p[1]), int(p[2]), join(self.worlddir, 'region', f))
x = int(p[1])
y = int(p[2])
if self.north_direction == 'upper-left':
temp = x
x = -y-1
y = temp
elif self.north_direction == 'upper-right':
x = -x-1
y = -y-1
elif self.north_direction == 'lower-right':
temp = x
x = y
y = -temp-1
yield (x, y, join(self.worlddir, 'region', f))
else:
logging.warning("Ignore path '%s' in regionlist", f)
@@ -302,7 +383,20 @@ class World(object):
for path in glob(os.path.join(self.worlddir, 'region') + "/r.*.*.mcr"):
dirpath, f = os.path.split(path)
p = f.split(".")
yield (int(p[1]), int(p[2]), join(dirpath, f))
x = int(p[1])
y = int(p[2])
if self.north_direction == 'upper-left':
temp = x
x = -y-1
y = temp
elif self.north_direction == 'upper-right':
x = -x-1
y = -y-1
elif self.north_direction == 'lower-right':
temp = x
x = y
y = -temp-1
yield (x, y, join(dirpath, f))
def get_save_dir():
"""Returns the path to the local saves directory