diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst index 30c5ffe..174ebe7 100644 --- a/CONTRIBUTORS.rst +++ b/CONTRIBUTORS.rst @@ -41,6 +41,7 @@ feature. * arrai * Kyle Brantley + * but2002 * Eric Carr * cbarber * Alex Cline diff --git a/MANIFEST.in b/MANIFEST.in index fe84bcb..ecc747a 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,6 +4,6 @@ include CONTRIBUTORS.rst include overviewer.py include sample.settings.py recursive-include contrib/ *.py -recursive-include overviewer_core/*.py +recursive-include overviewer_core/ *.py recursive-include overviewer_core/src/ *.c *.h -recursive-include overviewer_core/data/ *.png *.js index.html style.css +recursive-include overviewer_core/data/ * diff --git a/overviewer.py b/overviewer.py index 022e838..638a82c 100755 --- a/overviewer.py +++ b/overviewer.py @@ -188,6 +188,11 @@ def main(): parser.print_help() logging.error("Invalid world number") sys.exit(1) + + # final sanity check for worlddir + if not os.path.exists(os.path.join(worlddir, 'level.dat')): + logging.error("Invalid world path -- does not contain level.dat") + sys.exit(1) if len(args) < 2: if options.delete: diff --git a/overviewer_core/chunk.py b/overviewer_core/chunk.py index 1a1ad14..3dc25de 100644 --- a/overviewer_core/chunk.py +++ b/overviewer_core/chunk.py @@ -114,10 +114,10 @@ def get_tileentity_data(level): return data # This set holds blocks ids that can be seen through, for occlusion calculations -transparent_blocks = set([ 0, 6, 8, 9, 18, 20, 26, 27, 28, 30, 31, 32, 37, 38, - 39, 40, 44, 50, 51, 52, 53, 55, 59, 63, 64, 65, 66, 67, - 68, 69, 70, 71, 72, 74, 75, 76, 77, 78, 79, 81, 83, 85, - 90, 92, 93, 94, 96]) +transparent_blocks = set([ 0, 6, 8, 9, 18, 20, 26, 27, 28, 29, 30, 31, 32, 33, + 34, 37, 38, 39, 40, 44, 50, 51, 52, 53, 55, 59, 63, 64, + 65, 66, 67, 68, 69, 70, 71, 72, 74, 75, 76, 77, 78, 79, + 81, 83, 85, 90, 92, 93, 94, 96]) # This set holds block ids that are solid blocks solid_blocks = set([1, 2, 3, 4, 5, 7, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, @@ -335,67 +335,6 @@ class ChunkRenderer(object): return self._up_left_blocklight up_left_blocklight = property(_load_up_left_blocklight) - def generate_pseudo_ancildata(self,x,y,z,blockid, north_position = 0 ): - """ Generates a pseudo ancillary data for blocks that depend of - what are surrounded and don't have ancillary data - - This uses a binary number of 4 digits to encode the info. - The encode is: - - Bit: 1 2 3 4 - Side: x y -x -y - Values: bit = 0 -> The corresponding side block has different blockid - bit = 1 -> The corresponding side block has same blockid - Example: if the bit1 is 1 that means that there is a block with - blockid in the side of the +x direction. - - You can rotate the pseudo data multiplying by 2 and - if it is > 15 subtracting 15 and adding 1. (moving bits - in the left direction is like rotate 90 degree in anticlockwise - direction). In this way can be used for maps with other - north orientation. - - North position can have the values 0, 1, 2, 3, corresponding to - north in bottom-left, bottom-right, top-right and top-left of - the screen. - - The rotation feature is not used anywhere yet. - """ - - blocks = self.blocks - up_left_blocks = self.up_left_blocks - up_right_blocks = self.up_right_blocks - left_blocks = self.left_blocks - right_blocks = self.right_blocks - - pseudo_data = 0 - - # first check if we are in the border of a chunk, next check for chunks adjacent to this - # and finally check for a block with same blockid. I we aren't in the border of a chunk, - # check for the block having the sme blockid. - - if (up_right_blocks is not None and up_right_blocks[0,y,z] == blockid) if x == 15 else blocks[x+1,y,z] == blockid: - pseudo_data = pseudo_data | 0b1000 - - if (right_blocks is not None and right_blocks[x,0,z] == blockid) if y == 15 else blocks[x,y + 1,z] == blockid: - pseudo_data = pseudo_data | 0b0100 - - if (left_blocks is not None and left_blocks[15,y,z] == blockid) if x == 0 else blocks[x - 1,y,z] == blockid: - pseudo_data = pseudo_data | 0b0010 - - if (up_left_blocks is not None and up_left_blocks[x,15,z] == blockid) if y == 0 else blocks[x,y - 1,z] == blockid: - pseudo_data = pseudo_data | 0b0001 - - # rotate the bits for other north orientations - while north_position > 0: - pseudo_data *= 2 - if pseudo_data > 15: - pseudo_data -= 16 - pseudo_data +=1 - north_position -= 1 - - return pseudo_data - def chunk_render(self, img=None, xoff=0, yoff=0, cave=False): """Renders a chunk with the given parameters, and returns the image. If img is given, the chunk is rendered to that image object. Otherwise, diff --git a/overviewer_core/data/web_assets/overviewer.js b/overviewer_core/data/web_assets/overviewer.js index a97341c..c5083fc 100644 --- a/overviewer_core/data/web_assets/overviewer.js +++ b/overviewer_core/data/web_assets/overviewer.js @@ -58,7 +58,6 @@ var overviewer = { overviewer.util.initializeMarkers(); overviewer.util.initializeRegions(); overviewer.util.createMapControls(); - overviewer.util.createSearchBox(); }, /** * This adds some methods to these classes because Javascript is stupid @@ -166,6 +165,9 @@ var overviewer = { var zoom = overviewerConfig.map.defaultZoom; var mapcenter; var queryParams = overviewer.util.parseQueryString(); + if (queryParams.debug) { + overviewerConfig.map.debug=true; + } if (queryParams.lat) { lat = parseFloat(queryParams.lat); } @@ -311,6 +313,9 @@ var overviewer = { 'title': jQuery.trim(item.msg), 'icon': overviewerConfig.CONST.image.queryMarker }); + google.maps.event.addListener(marker, 'click', function(){ marker.setVisible(false); }); + + continue; } @@ -392,59 +397,76 @@ var overviewer = { point.x, point.y, point.z)); } + + if (region.label) { + var name = region.label; + } else { + var name = "rawr"; + } + + if(region.opacity) { + var strokeOpacity = region.opacity; + var fillOpacity = region.opacity * 0.25; + } else { + var strokeOpacity = region.strokeOpacity; + var fillOpacity = region.fillOpacity; + } + + var shapeOptions = { + 'name': name, + 'geodesic': false, + 'map': null, + 'strokeColor': region.color, + 'strokeOpacity': strokeOpacity, + 'strokeWeight': overviewerConfig.CONST.regionStrokeWeight, + 'zIndex': j + }; + if (region.closed) { + shapeOptions["fillColor"] = region.color; + shapeOptions["fillOpacity"] = fillOpacity; + shapeOptions["paths"] = converted; + } else { + shapeOptions["path"] = converted; + } + + var matched = false; + for (k in overviewerConfig.objectGroups.regions) { var regionGroup = overviewerConfig.objectGroups.regions[k]; var clickable = regionGroup.clickable; var label = regionGroup.label; - - if(region.label) { - var name = region.label - } else { - var name = 'rawr'; + + if (!regionGroup.match(region)) + continue; + matched = true; + + if (!region.label) { clickable = false; // if it doesn't have a name, we dont have to show it. } - if(region.opacity) { - var strokeOpacity = region.opacity; - var fillOpacity = region.opacity * 0.25; + if (region.closed) { + var shape = new google.maps.Polygon(shapeOptions); } else { - var strokeOpacity = region.strokeOpacity; - var fillOpacity = region.fillOpacity; + var shape = new google.maps.Polyline(shapeOptions); } - if (region.closed) { - var shape = new google.maps.Polygon({ - 'name': name, - 'clickable': clickable, - 'geodesic': false, - 'map': null, - 'strokeColor': region.color, - 'strokeOpacity': strokeOpacity, - 'strokeWeight': overviewerConfig.CONST.regionStrokeWeight, - 'fillColor': region.color, - 'fillOpacity': fillOpacity, - 'zIndex': j, - 'paths': converted - }); - } else { - var shape = new google.maps.Polyline({ - 'name': name, - 'clickable': clickable, - 'geodesic': false, - 'map': null, - 'strokeColor': region.color, - 'strokeOpacity': strokeOpacity, - 'strokeWeight': overviewerConfig.CONST.regionStrokeWeight, - 'zIndex': j, - 'path': converted - }); - } overviewer.collections.regions[label].push(shape); if (clickable) { overviewer.util.createRegionInfoWindow(shape); } } + + // if we haven't matched anything, go ahead and add it + if (!matched) { + if (region.closed) { + var shape = new google.maps.Polygon(shapeOptions); + } else { + var shape = new google.maps.Polyline(shapeOptions); + } + + shape.setMap(overviewer.map); + } } } }, @@ -512,7 +534,7 @@ var overviewer = { // the width and height of all the highest-zoom tiles combined, // inverted var perPixel = 1.0 / (overviewerConfig.CONST.tileSize * - Math.pow(2, overviewerConfig.map.maxZoom)); + Math.pow(2, overviewerConfig.map.zoomLevels)); // This information about where the center column is may change with // a different drawing implementation -- check it again after any @@ -520,13 +542,13 @@ var overviewer = { // point (0, 0, 127) is at (0.5, 0.0) of tile (tiles/2 - 1, tiles/2) // so the Y coordinate is at 0.5, and the X is at 0.5 - - // ((tileSize / 2) / (tileSize * 2^maxZoom)) - // or equivalently, 0.5 - (1 / 2^(maxZoom + 1)) - var lng = 0.5 - (1.0 / Math.pow(2, overviewerConfig.map.maxZoom + 1)); + // ((tileSize / 2) / (tileSize * 2^zoomLevels)) + // or equivalently, 0.5 - (1 / 2^(zoomLevels + 1)) + var lng = 0.5 - (1.0 / Math.pow(2, overviewerConfig.map.zoomLevels + 1)); var lat = 0.5; - // the following metrics mimic those in ChunkRenderer.chunk_render - // in "chunk.py" or, equivalently, chunk_render in src/iterate.c + // the following metrics mimic those in + // chunk_render in src/iterate.c // each block on X axis adds 12px to x and subtracts 6px from y lng += 12 * x * perPixel; @@ -564,11 +586,11 @@ var overviewer = { // the width and height of all the highest-zoom tiles combined, // inverted var perPixel = 1.0 / (overviewerConfig.CONST.tileSize * - Math.pow(2, overviewerConfig.map.maxZoom)); + Math.pow(2, overviewerConfig.map.zoomLevels)); // Revert base positioning // See equivalent code in fromWorldToLatLng() - lng -= 0.5 - (1.0 / Math.pow(2, overviewerConfig.map.maxZoom + 1)); + lng -= 0.5 - (1.0 / Math.pow(2, overviewerConfig.map.zoomLevels + 1)); lat -= 0.5; // I'll admit, I plugged this into Wolfram Alpha: @@ -617,7 +639,10 @@ var overviewer = { var coordsDiv = document.createElement('DIV'); coordsDiv.id = 'coordsDiv'; coordsDiv.innerHTML = ''; - overviewer.map.controls[google.maps.ControlPosition.BOTTOM_LEFT].push(coordsDiv); + if (overviewerConfig.map.controls.coordsBox) { + overviewer.map.controls[google.maps.ControlPosition.BOTTOM_LEFT].push(coordsDiv); + } + // Update coords on mousemove google.maps.event.addListener(overviewer.map, 'mousemove', function (event) { var worldcoords = overviewer.util.fromLatLngToWorld(event.latLng.lat(), event.latLng.lng()); @@ -684,7 +709,7 @@ var overviewer = { overviewer.util.createDropDown('Regions', items); } - if (overviewer.collections.overlays.length > 0) { + if (overviewerConfig.map.controls.overlays && overviewer.collections.overlays.length > 0) { // overlay maps control var items = []; for (i in overviewer.collections.overlays) { @@ -712,6 +737,9 @@ var overviewer = { } overviewer.util.createDropDown('Overlays', items); } + + // call out to create search box, as it's pretty complicated + overviewer.util.createSearchBox(); }, /** * Reusable method for creating drop-down menus @@ -832,8 +860,10 @@ var overviewer = { $(searchDropDown).fadeOut(); } }); - - overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(searchControl); + + if (overviewerConfig.map.controls.searchBox) { + overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(searchControl); + } }, /** * Create the pop-up infobox for when you click on a region, this can't diff --git a/overviewer_core/data/web_assets/overviewerConfig.js b/overviewer_core/data/web_assets/overviewerConfig.js index ce38479..bdb6015 100644 --- a/overviewer_core/data/web_assets/overviewerConfig.js +++ b/overviewer_core/data/web_assets/overviewerConfig.js @@ -50,9 +50,18 @@ var overviewerConfig = { */ 'mapType': true, /** - * The small box at the bottom that displays the link to the current map view. + * The coordsBox control is the box showing the XYZ coordinates + * under the cursor. */ - 'link': true + 'coordsBox': true, + /** + * The overlays control is the drop-down box for selecting overlays. + */ + 'overlays': true, + /** + * The searchBox control is the search box for markers. + */ + 'searchBox': true }, /** * The zoom level when the page is loaded without a specific zoom setting @@ -66,6 +75,12 @@ var overviewerConfig = { * This controls how close you can zoom in. */ 'maxZoom': {maxzoom}, + /** + * This tells us how many total zoom levels Overviewer rendered. + * DO NOT change this, even if you change minZoom and maxZoom, because + * it's used for marker position calculations and map resizing. + */ + 'zoomLevels': {zoomlevels}, /** * Center on this point, in world coordinates. Should be an array, ex: * [0,0,0] diff --git a/overviewer_core/googlemap.py b/overviewer_core/googlemap.py index 7d4dc7e..3b2475e 100644 --- a/overviewer_core/googlemap.py +++ b/overviewer_core/googlemap.py @@ -114,6 +114,8 @@ class MapGen(object): "{minzoom}", str(0)) config = config.replace( "{maxzoom}", str(zoomlevel)) + config = config.replace( + "{zoomlevels}", str(zoomlevel)) config = config.replace("{spawn_coords}", json.dumps(list(self.world.spawn))) diff --git a/overviewer_core/quadtree.py b/overviewer_core/quadtree.py index fc00b01..2e77576 100644 --- a/overviewer_core/quadtree.py +++ b/overviewer_core/quadtree.py @@ -120,7 +120,7 @@ class QuadtreeGen(object): indexfile = os.path.join(self.destdir, "overviewerConfig.js") if not os.path.exists(indexfile): return -1 - matcher = re.compile(r"maxZoom.*:\s*(\d+)") + matcher = re.compile(r"zoomLevels(?:\'|\")\s*:\s*(\d+)") p = -1 for line in open(indexfile, "r"): res = matcher.search(line) @@ -426,21 +426,21 @@ class QuadtreeGen(object): needs_rerender = False get_region_mtime = world.get_region_mtime for col, row, chunkx, chunky, regionfile in chunks: - + region, regionMtime = get_region_mtime(regionfile) + + # don't even check if it's not in the regionlist + if self.world.regionlist and os.path.abspath(region._filename) not in self.world.regionlist: + continue + # bail early if forcerender is set if self.forcerender: needs_rerender = True break # check region file mtime first. - region,regionMtime = get_region_mtime(regionfile) if regionMtime <= tile_mtime: continue - # don't even check if it's not in the regionlist - if self.world.regionlist and os.path.abspath(region._filename) not in self.world.regionlist: - continue - # checking chunk mtime if region.get_chunk_timestamp(chunkx, chunky) > tile_mtime: needs_rerender = True diff --git a/overviewer_core/src/iterate.c b/overviewer_core/src/iterate.c index 8adbf57..f215531 100644 --- a/overviewer_core/src/iterate.c +++ b/overviewer_core/src/iterate.c @@ -173,12 +173,18 @@ generate_pseudo_data(RenderState *state, unsigned char ancilData) { data = (check_adjacent_blocks(state, x, y, z, state->block) ^ 0x0f); return data; } - - + } else if (state->block == 20) { /* glass */ + /* an aditional bit for top is added to the 4 bits of check_adjacent_blocks */ + if ((z != 127) && (getArrayByte3D(state->blocks, x, y, z+1) == 20)) { + data = 0; + } else { + data = 16; + } + data = (check_adjacent_blocks(state, x, y, z, state->block) ^ 0x0f) | data; + return data; } else if (state->block == 85) { /* fences */ return check_adjacent_blocks(state, x, y, z, state->block); - } else if (state->block == 55) { /* redstone */ /* three addiotional bit are added, one for on/off state, and * another two for going-up redstone wire in the same block @@ -352,6 +358,10 @@ chunk_render(PyObject *self, PyObject *args) { up_right_blocks_py = PyObject_GetAttrString(state.self, "up_right_blocks"); state.up_right_blocks = up_right_blocks_py; + /* set up the random number generator again for each chunk + so tallgrass is in the same place, no matter what mode is used */ + srand(1); + for (state.x = 15; state.x > -1; state.x--) { for (state.y = 0; state.y < 16; state.y++) { PyObject *blockid = NULL; @@ -364,7 +374,7 @@ chunk_render(PyObject *self, PyObject *args) { for (state.z = 0; state.z < 128; state.z++) { state.imgy -= 12; - /* get blockid */ + /* get blockid */ state.block = getArrayByte3D(blocks_py, state.x, state.y, state.z); if (state.block == 0) { continue; @@ -400,7 +410,10 @@ chunk_render(PyObject *self, PyObject *args) { PyObject *tmp; unsigned char ancilData = getArrayByte3D(state.blockdata_expanded, state.x, state.y, state.z); - if ((state.block == 85) || (state.block == 9) || (state.block == 55) || (state.block == 54) || (state.block == 2) || (state.block == 90)) { + if ((state.block == 2) || (state.block == 9) || + (state.block == 20) || (state.block == 54) || + (state.block == 55) || (state.block == 85) || + (state.block == 90)) { ancilData = generate_pseudo_data(&state, ancilData); } @@ -419,14 +432,29 @@ chunk_render(PyObject *self, PyObject *args) { if (t != NULL && t != Py_None) { PyObject *src, *mask, *mask_light; + int randx = 0, randy = 0; src = PyTuple_GetItem(t, 0); mask = PyTuple_GetItem(t, 1); mask_light = PyTuple_GetItem(t, 2); if (mask == Py_None) mask = src; + + if (state.block == 31) { + /* add a random offset to the postion of the tall grass to make it more wild */ + randx = rand() % 6 + 1 - 3; + randy = rand() % 6 + 1 - 3; + state.imgx += randx; + state.imgy += randy; + } render_mode_draw(rendermode, src, mask, mask_light); + + if (state.block == 31) { + /* undo the random offsets */ + state.imgx -= randx; + state.imgy -= randy; + } } } diff --git a/overviewer_core/src/rendermode-normal.c b/overviewer_core/src/rendermode-normal.c index 9d79f68..547fb8c 100644 --- a/overviewer_core/src/rendermode-normal.c +++ b/overviewer_core/src/rendermode-normal.c @@ -154,7 +154,6 @@ rendermode_normal_occluded(void *data, RenderState *state, int x, int y, int z) static void rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObject *mask_light) { RenderModeNormal *self = (RenderModeNormal *)data; - int randx = 0,randy = 0; unsigned char data_byte; /* first, check to see if we should use biome-compatible src, mask */ @@ -162,11 +161,6 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject * if (state->block == 18) { src = mask = self->leaf_texture; } else if (state->block == 31) { - /* add a random offset to the postion of the tall grass to make it more wild */ - randx = rand() % 6 + 1 - 3; - randy = rand() % 6 + 1 - 3; - state->imgx = state->imgx + randx; - state->imgy = state->imgy + randy; data_byte = getArrayByte3D(state->blockdata_expanded, state->x, state->y, state->z); if (data_byte == 1) { src = mask = self->tall_grass_texture; diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py index f1da1b2..f7d4dfb 100644 --- a/overviewer_core/textures.py +++ b/overviewer_core/textures.py @@ -28,7 +28,7 @@ import util import composite _find_file_local_path = None -def _find_file(filename, mode="rb"): +def _find_file(filename, mode="rb", verbose=False): """Searches for the given file and returns an open handle to it. This searches the following locations in this order: @@ -47,11 +47,13 @@ def _find_file(filename, mode="rb"): if _find_file_local_path: path = os.path.join(_find_file_local_path, filename) if os.path.exists(path): + if verbose: print "Found %s in '%s'" % (filename, path) return open(path, mode) programdir = util.get_program_path() path = os.path.join(programdir, filename) if os.path.exists(path): + if verbose: print "Found %s in '%s'" % (filename, path) return open(path, mode) path = os.path.join(programdir, "overviewer_core", "data", "textures", filename) @@ -61,11 +63,13 @@ def _find_file(filename, mode="rb"): # windows special case, when the package dir doesn't exist path = os.path.join(programdir, "textures", filename) if os.path.exists(path): + if verbose: print "Found %s in '%s'" % (filename, path) return open(path, mode) if sys.platform == "darwin": path = os.path.join("/Applications/Minecraft", filename) if os.path.exists(path): + if verbose: print "Found %s in '%s'" % (filename, path) return open(path, mode) # Find minecraft.jar. @@ -85,6 +89,7 @@ def _find_file(filename, mode="rb"): if os.path.exists(jarpath): try: jar = zipfile.ZipFile(jarpath) + if verbose: print "Found %s in '%s'" % (filename, jarpath) return jar.open(filename) except (KeyError, IOError): pass @@ -321,34 +326,34 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None A non transparent block uses top, side 3 and side 4. - If top is a tuple then first member is the top image and the second - member is an increment (integer) from 0 to 12. This increment will - used to crop the side images to look like a block and to paste all - the images increment pixels lower. Using increment = 6 will create - a half-block. + If top is a tuple then first item is the top image and the second + item is an increment (integer) from 0 to 16 (pixels in the + original minecraft texture). This increment will be used to crop the + side images and to paste the top image increment pixels lower, so if + you use an increment of 8, it willll draw a half-block. - NOTE: this method uses the top of the texture image (as done in - minecraft with beds) + NOTE: this method uses the bottom of the texture image (as done in + minecraft with beds and cackes) """ increment = 0 if isinstance(top, tuple): - increment = top[1] - crop_height = int(increment * 16./12.) + increment = int(round((top[1] / 16.)*12.)) # range increment in the block height in pixels (half texture size) + crop_height = increment top = top[0] if side1 != None: side1 = side1.copy() - ImageDraw.Draw(side1).rectangle((0, 16 - crop_height,16,16),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(side1).rectangle((0, 0,16,crop_height),outline=(0,0,0,0),fill=(0,0,0,0)) if side2 != None: side2 = side2.copy() - ImageDraw.Draw(side2).rectangle((0, 16 - crop_height,16,16),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(side2).rectangle((0, 0,16,crop_height),outline=(0,0,0,0),fill=(0,0,0,0)) if side3 != None: side3 = side3.copy() - ImageDraw.Draw(side3).rectangle((0, 16 - crop_height,16,16),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(side3).rectangle((0, 0,16,crop_height),outline=(0,0,0,0),fill=(0,0,0,0)) if side4 != None: side4 = side4.copy() - ImageDraw.Draw(side4).rectangle((0, 16 - crop_height,16,16),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(side4).rectangle((0, 0,16,crop_height),outline=(0,0,0,0),fill=(0,0,0,0)) img = Image.new("RGBA", (24,24), (38,92,255,0)) @@ -362,7 +367,7 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None side1 = ImageEnhance.Brightness(side1).enhance(0.9) side1.putalpha(sidealpha) - composite.alpha_over(img, side1, (0,0 + increment), side1) + composite.alpha_over(img, side1, (0,0), side1) if side2 != None : @@ -373,11 +378,11 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None side2 = ImageEnhance.Brightness(side2).enhance(0.8) side2.putalpha(sidealpha2) - composite.alpha_over(img, side2, (12,0 + increment), side2) + composite.alpha_over(img, side2, (12,0), side2) if bottom != None : bottom = transform_image(bottom, blockID) - composite.alpha_over(img, bottom, (0,12), top) + composite.alpha_over(img, bottom, (0,12), bottom) # front sides if side3 != None : @@ -388,7 +393,7 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None side3 = ImageEnhance.Brightness(side3).enhance(0.9) side3.putalpha(sidealpha) - composite.alpha_over(img, side3, (0,6 + increment), side3) + composite.alpha_over(img, side3, (0,6), side3) if side4 != None : side4 = transform_image_side(side4, blockID) @@ -399,7 +404,7 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None side4 = ImageEnhance.Brightness(side4).enhance(0.8) side4.putalpha(sidealpha) - composite.alpha_over(img, side4, (12,6 + increment), side4) + composite.alpha_over(img, side4, (12,6), side4) if top != None : top = transform_image(top, blockID) @@ -420,7 +425,7 @@ def _build_blockimages(): # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 topids = [ -1, 1, 0, 2, 16, 4, -1, 17,205,205,237,237, 18, 19, 32, 33, # 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 - 34, -1, 52, 48, 49,160,144, -1,176, 74, -1, -1, -1, -1, 11, -1, + 34, -1, 52, 48, -1,160,144, -1,176, 74, -1, -1, -1, -1, 11, -1, # 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 55, -1, -1, -1, -1, 13, 12, 29, 28, 23, 22, -1, -1, 7, 9, 4, # 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 @@ -437,7 +442,7 @@ def _build_blockimages(): # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 sideids = [ -1, 1, 3, 2, 16, 4, -1, 17,205,205,237,237, 18, 19, 32, 33, # 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 - 34, -1, 52, 48, 49,160,144, -1,192, 74, -1, -1,- 1, -1, 11, -1, + 34, -1, 52, 48, -1,160,144, -1,192, 74, -1, -1,- 1, -1, 11, -1, # 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 55, -1, -1, -1, -1, 13, 12, 29, 28, 23, 22, -1, -1, 7, 8, 35, # 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 @@ -548,33 +553,35 @@ def generate_special_texture(blockID, data): return generate_texture_tuple(img, blockID) - if blockID == 9: # spring water, flowing water and waterfall water - - watertexture = _load_image("water.png") + if blockID == 9 or blockID == 20: # spring water, flowing water and waterfall water, AND glass + # water and glass share the way to be rendered + if blockID == 9: + texture = _load_image("water.png") + else: + texture = terrain_images[49] if (data & 0b10000) == 16: - top = watertexture + top = texture else: top = None if (data & 0b0001) == 1: - side1 = watertexture # top left + side1 = texture # top left else: side1 = None if (data & 0b1000) == 8: - side2 = watertexture # top right + side2 = texture # top right else: side2 = None if (data & 0b0010) == 2: - side3 = watertexture # bottom left + side3 = texture # bottom left else: side3 = None if (data & 0b0100) == 4: - side4 = watertexture # bottom right + side4 = texture # bottom right else: side4 = None img = _build_full_block(top,None,None,side3,side4) - return generate_texture_tuple(img, blockID) @@ -600,7 +607,7 @@ def generate_special_texture(blockID, data): if blockID == 26: # bed - increment = 5 + increment = 8 left_face = None right_face = None if data & 0x8 == 0x8: # head of the bed @@ -644,6 +651,7 @@ def generate_special_texture(blockID, data): return generate_texture_tuple(img, blockID) + if blockID == 31: # tall grass if data == 0: # dead shrub texture = terrain_images[55] @@ -656,8 +664,134 @@ def generate_special_texture(blockID, data): img = _build_block(texture, texture, blockID) return generate_texture_tuple(img,31) + + + if blockID in (29,33): # sticky and normal body piston. + if blockID == 29: # sticky + piston_t = terrain_images[106].copy() + else: # normal + piston_t = terrain_images[107].copy() + # other textures + side_t = terrain_images[108].copy() + back_t = terrain_images[109].copy() + interior_t = terrain_images[110].copy() + + if data & 0x08 == 0x08: # pushed out, non full blocks, tricky stuff + # remove piston texture from piston body + ImageDraw.Draw(side_t).rectangle((0, 0,16,3),outline=(0,0,0,0),fill=(0,0,0,0)) + if data & 0x07 == 0x0: # down + side_t = side_t.rotate(180) + img = _build_full_block(back_t ,None ,None ,side_t, side_t) + + elif data & 0x07 == 0x1: # up + img = _build_full_block((interior_t, 4) ,None ,None ,side_t, side_t) + + elif data & 0x07 == 0x2: # east + img = _build_full_block(side_t , None, None ,side_t.rotate(90), back_t) + + elif data & 0x07 == 0x3: # west + img = _build_full_block(side_t.rotate(180) ,None ,None ,side_t.rotate(270), None) + temp = transform_image_side(interior_t, blockID) + temp = temp.transpose(Image.FLIP_LEFT_RIGHT) + composite.alpha_over(img, temp, (9,5), temp) + + elif data & 0x07 == 0x4: # north + img = _build_full_block(side_t.rotate(90) ,None ,None , None, side_t.rotate(270)) + temp = transform_image_side(interior_t, blockID) + composite.alpha_over(img, temp, (3,5), temp) + + elif data & 0x07 == 0x5: # south + img = _build_full_block(side_t.rotate(270) ,None , None ,back_t, side_t.rotate(90)) + + else: # pushed in, normal full blocks, easy stuff + if data & 0x07 == 0x0: # down + side_t = side_t.rotate(180) + img = _build_full_block(back_t ,None ,None ,side_t, side_t) + elif data & 0x07 == 0x1: # up + img = _build_full_block(piston_t ,None ,None ,side_t, side_t) + elif data & 0x07 == 0x2: # east + img = _build_full_block(side_t ,None ,None ,side_t.rotate(90), back_t) + elif data & 0x07 == 0x3: # west + img = _build_full_block(side_t.rotate(180) ,None ,None ,side_t.rotate(270), piston_t) + elif data & 0x07 == 0x4: # north + img = _build_full_block(side_t.rotate(90) ,None ,None ,piston_t, side_t.rotate(270)) + elif data & 0x07 == 0x5: # south + img = _build_full_block(side_t.rotate(270) ,None ,None ,back_t, side_t.rotate(90)) + + + return generate_texture_tuple(img, blockID) + + + if blockID == 34: # piston extension (sticky and normal) + if (data & 0x8) == 0x8: # sticky + piston_t = terrain_images[106].copy() + else: # normal + piston_t = terrain_images[107].copy() + + # other textures + side_t = terrain_images[108].copy() + back_t = terrain_images[107].copy() + # crop piston body + ImageDraw.Draw(side_t).rectangle((0, 4,16,16),outline=(0,0,0,0),fill=(0,0,0,0)) + + # generate the horizontal piston extension stick + h_stick = Image.new("RGBA", (24,24), (38,92,255,0)) + temp = transform_image_side(side_t, blockID) + composite.alpha_over(h_stick, temp, (1,7), temp) + temp = transform_image(side_t.rotate(90)) + composite.alpha_over(h_stick, temp, (1,1), temp) + # Darken it + sidealpha = h_stick.split()[3] + h_stick = ImageEnhance.Brightness(h_stick).enhance(0.85) + h_stick.putalpha(sidealpha) + + # generate the vertical piston extension stick + v_stick = Image.new("RGBA", (24,24), (38,92,255,0)) + temp = transform_image_side(side_t.rotate(90), blockID) + composite.alpha_over(v_stick, temp, (12,6), temp) + temp = temp.transpose(Image.FLIP_LEFT_RIGHT) + composite.alpha_over(v_stick, temp, (1,6), temp) + # Darken it + sidealpha = v_stick.split()[3] + v_stick = ImageEnhance.Brightness(v_stick).enhance(0.85) + v_stick.putalpha(sidealpha) + + # Piston orientation is stored in the 3 first bits + if data & 0x07 == 0x0: # down + side_t = side_t.rotate(180) + img = _build_full_block((back_t, 12) ,None ,None ,side_t, side_t) + composite.alpha_over(img, v_stick, (0,-3), v_stick) + elif data & 0x07 == 0x1: # up + img = Image.new("RGBA", (24,24), (38,92,255,0)) + img2 = _build_full_block(piston_t ,None ,None ,side_t, side_t) + composite.alpha_over(img, v_stick, (0,4), v_stick) + composite.alpha_over(img, img2, (0,0), img2) + elif data & 0x07 == 0x2: # east + img = _build_full_block(side_t ,None ,None ,side_t.rotate(90), None) + temp = transform_image_side(back_t, blockID).transpose(Image.FLIP_LEFT_RIGHT) + composite.alpha_over(img, temp, (2,2), temp) + composite.alpha_over(img, h_stick, (6,3), h_stick) + elif data & 0x07 == 0x3: # west + img = Image.new("RGBA", (24,24), (38,92,255,0)) + img2 = _build_full_block(side_t.rotate(180) ,None ,None ,side_t.rotate(270), piston_t) + composite.alpha_over(img, h_stick, (0,0), h_stick) + composite.alpha_over(img, img2, (0,0), img2) + elif data & 0x07 == 0x4: # north + img = _build_full_block(side_t.rotate(90) ,None ,None , piston_t, side_t.rotate(270)) + composite.alpha_over(img, h_stick.transpose(Image.FLIP_LEFT_RIGHT), (0,0), h_stick.transpose(Image.FLIP_LEFT_RIGHT)) + elif data & 0x07 == 0x5: # south + img = Image.new("RGBA", (24,24), (38,92,255,0)) + img2 = _build_full_block(side_t.rotate(270) ,None ,None ,None, side_t.rotate(90)) + temp = transform_image_side(back_t, blockID) + composite.alpha_over(img2, temp, (10,2), temp) + composite.alpha_over(img, img2, (0,0), img2) + composite.alpha_over(img, h_stick.transpose(Image.FLIP_LEFT_RIGHT), (-3,2), h_stick.transpose(Image.FLIP_LEFT_RIGHT)) + + return generate_texture_tuple(img, blockID) + + if blockID == 35: # wool if data == 0: # white top = side = terrain_images[64] @@ -1076,7 +1210,7 @@ def generate_special_texture(blockID, data): # mask out the high bits to figure out the orientation img = Image.new("RGBA", (24,24), (38,92,255,0)) - if (data & 0x03) == 0: + if (data & 0x03) == 0: # northeast corner if not swung: tex = transform_image_side(raw_door) composite.alpha_over(img, tex, (0,6), tex) @@ -1086,7 +1220,7 @@ def generate_special_texture(blockID, data): tex = tex.transpose(Image.FLIP_LEFT_RIGHT) composite.alpha_over(img, tex, (0,0), tex) - if (data & 0x03) == 1: + if (data & 0x03) == 1: # southeast corner if not swung: tex = transform_image_side(raw_door).transpose(Image.FLIP_LEFT_RIGHT) composite.alpha_over(img, tex, (0,0), tex) @@ -1094,7 +1228,7 @@ def generate_special_texture(blockID, data): tex = transform_image_side(raw_door) composite.alpha_over(img, tex, (12,0), tex) - if (data & 0x03) == 2: + if (data & 0x03) == 2: # southwest corner if not swung: tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT)) composite.alpha_over(img, tex, (12,0), tex) @@ -1102,7 +1236,7 @@ def generate_special_texture(blockID, data): tex = transform_image_side(raw_door).transpose(Image.FLIP_LEFT_RIGHT) composite.alpha_over(img, tex, (12,6), tex) - if (data & 0x03) == 3: + if (data & 0x03) == 3: # northwest corner if not swung: tex = transform_image_side(raw_door.transpose(Image.FLIP_LEFT_RIGHT)).transpose(Image.FLIP_LEFT_RIGHT) composite.alpha_over(img, tex, (12,6), tex) @@ -1388,21 +1522,21 @@ def generate_special_texture(blockID, data): img = Image.new("RGBA", (24,24), (38,92,255,0)) - composite.alpha_over(img, side, (1,12), side) - composite.alpha_over(img, otherside, (11,13), otherside) # workaround, fixes a hole - composite.alpha_over(img, otherside, (12,12), otherside) + composite.alpha_over(img, side, (1,6), side) + composite.alpha_over(img, otherside, (11,7), otherside) # workaround, fixes a hole + composite.alpha_over(img, otherside, (12,6), otherside) composite.alpha_over(img, top, (0,6), top) return generate_texture_tuple(img, blockID) - if blockID in (93, 94): # redstone repeaters, ON and OFF + if blockID in (93, 94): # redstone repeaters (diodes), ON and OFF # NOTE: this function uses the redstone torches generated above, # this must run after the function of the torches. top = terrain_images[131] if blockID == 93 else terrain_images[147] side = terrain_images[5] - increment = 9 + increment = 13 if (data & 0x3) == 0: # pointing east pass @@ -1527,7 +1661,7 @@ def generate_special_texture(blockID, data): img = _build_full_block(None, None, None, texture, None) elif data & 0x4 == 0: # closed trapdoor - img = _build_full_block((texture, 9), None, None, texture, texture) + img = _build_full_block((texture, 12), None, None, texture, texture) return generate_texture_tuple(img, blockID) @@ -1604,9 +1738,10 @@ def getBiomeData(worlddir, chunkX, chunkY): # (when adding new blocks here and in generate_special_textures, # please, if possible, keep the ascending order of blockid value) -special_blocks = set([ 2, 6, 9, 17, 18, 26, 23, 27, 28, 31, 35, 43, 44, - 50, 51, 53, 54, 55, 58, 59, 61, 62, 63, 64, 65, 66, - 67, 68, 71, 75, 76, 85, 86, 90, 91, 92, 93, 94, 96]) +special_blocks = set([ 2, 6, 9, 17, 18, 20, 26, 23, 27, 28, 29, 31, 33, + 34, 35, 43, 44, 50, 51, 53, 54, 55, 58, 59, 61, 62, + 63, 64, 65, 66, 67, 68, 71, 75, 76, 85, 86, 90, 91, + 92, 93, 94, 96]) # this is a map of special blockIDs to a list of all # possible values for ancillary data that it might have. @@ -1616,10 +1751,14 @@ special_map = {} special_map[6] = range(16) # saplings: usual, spruce, birch and future ones (rendered as usual saplings) special_map[9] = range(32) # water: spring,flowing, waterfall, and others (unknown) ancildata values, uses pseudo data special_map[17] = range(3) # wood: normal, birch and pine +special_map[20] = range(32) # glass, used to only render the exterior surface, uses pseudo data special_map[26] = range(12) # bed, orientation special_map[23] = range(6) # dispensers, orientation special_map[27] = range(14) # powered rail, orientation/slope and powered/unpowered special_map[28] = range(6) # detector rail, orientation/slope +special_map[29] = (0,1,2,3,4,5,8,9,10,11,12,13) # sticky piston body, orientation, pushed in/out +special_map[33] = (0,1,2,3,4,5,8,9,10,11,12,13) # normal piston body, orientation, pushed in/out +special_map[34] = (0,1,2,3,4,5,8,9,10,11,12,13) # normal and sticky piston extension, orientation, sticky/normal special_map[35] = range(16) # wool, colored and white special_map[43] = range(4) # stone, sandstone, wooden and cobblestone double-slab special_map[44] = range(4) # stone, sandstone, wooden and cobblestone slab @@ -1670,9 +1809,10 @@ biome_tall_fern_texture = None biome_leaf_texture = None specialblockmap = None -def generate(path=None): - global _find_file_local_path +def generate(path=None,texture_size=24): + global _find_file_local_path, texture_dimensions _find_file_local_path = path + texture_dimensions = (texture_size, texture_size) # This maps terainids to 16x16 images global terrain_images @@ -1696,3 +1836,30 @@ def generate(path=None): for blockID in special_blocks: for data in special_map[blockID]: specialblockmap[(blockID, data)] = generate_special_texture(blockID, data) + + if texture_size != 24: + # rescale biome textures. + biome_grass_texture = biome_grass_texture.resize(texture_dimensions, Image.ANTIALIAS) + biome_leaf_texture = biome_leaf_texture.resize(texture_dimensions, Image.ANTIALIAS) + biome_tall_grass_texture = biome_tall_grass_texture.resize(texture_dimensions, Image.ANTIALIAS) + biome_tall_fern_texture = biome_tall_fern_texture.resize(texture_dimensions, Image.ANTIALIAS) + + # rescale the normal block images + for i in range(len(blockmap)): + if blockmap[i] != None: + block = blockmap[i] + alpha = block[1] + block = block[0] + block.putalpha(alpha) + scaled_block = block.resize(texture_dimensions, Image.ANTIALIAS) + blockmap[i] = generate_texture_tuple(scaled_block, i) + + # rescale the special block images + for blockid, data in iter(specialblockmap): + block = specialblockmap[(blockid,data)] + if block != None: + alpha = block[1] + block = block[0] + block.putalpha(alpha) + scaled_block = block.resize(texture_dimensions, Image.ANTIALIAS) + specialblockmap[(blockid,data)] = generate_texture_tuple(scaled_block, blockid) diff --git a/overviewer_core/world.py b/overviewer_core/world.py index 1344d10..fbd61a9 100644 --- a/overviewer_core/world.py +++ b/overviewer_core/world.py @@ -209,25 +209,27 @@ class World(object): chunkX = spawnX/16 chunkY = spawnZ/16 - ## 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] - if data is not None: - level = data['Level'] - blockArray = numpy.frombuffer(level['Blocks'], dtype=numpy.uint8).reshape((16,16,128)) + try: + ## 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] + if data is not None: + level = data['Level'] + blockArray = numpy.frombuffer(level['Blocks'], dtype=numpy.uint8).reshape((16,16,128)) - ## The block for spawn *within* the chunk - inChunkX = spawnX - (chunkX*16) - inChunkZ = spawnZ - (chunkY*16) + ## The block for spawn *within* the chunk + inChunkX = spawnX - (chunkX*16) + inChunkZ = spawnZ - (chunkY*16) - ## find the first air block - while (blockArray[inChunkX, inChunkZ, spawnY] != 0): - spawnY += 1 - if spawnY == 128: - break - + ## find the first air block + while (blockArray[inChunkX, inChunkZ, spawnY] != 0): + spawnY += 1 + if spawnY == 128: + break + except ChunkCorrupt: + #ignore corrupt spawn, and continue + pass self.POI.append( dict(x=spawnX, y=spawnY, z=spawnZ, msg="Spawn", type="spawn", chunk=(chunkX, chunkY))) self.spawn = (spawnX, spawnY, spawnZ) diff --git a/setup.py b/setup.py index 04d5c42..9c14a39 100755 --- a/setup.py +++ b/setup.py @@ -6,6 +6,7 @@ from distutils.command.build import build from distutils.command.clean import clean from distutils.command.build_ext import build_ext from distutils.command.sdist import sdist +from distutils.cmd import Command from distutils.dir_util import remove_tree from distutils.sysconfig import get_python_inc from distutils import log @@ -74,6 +75,17 @@ def recursive_data_files(src, dest=None): ret.append((current_dest, current_sources)) return ret +# helper to create a 'package_data'-type sequence recursively for a given dir +def recursive_package_data(src, package_dir='overviewer_core'): + full_src = os.path.join(package_dir, src) + ret = [] + for dirpath, dirnames, filenames in os.walk(full_src, topdown=False): + current_path = os.path.relpath(dirpath, package_dir) + for filename in filenames: + ret.append(os.path.join(current_path, filename)) + + return ret + # # py2exe options # @@ -105,9 +117,8 @@ if py2app is not None: setup_kwargs['packages'] = ['overviewer_core'] setup_kwargs['scripts'] = ['overviewer.py'] -setup_kwargs['package_data'] = {'overviewer_core': - ['data/textures/*', - 'data/web_assets/*']} +setup_kwargs['package_data'] = {'overviewer_core': recursive_package_data('data/textures') + recursive_package_data('data/web_assets')} + if py2exe is None: setup_kwargs['data_files'] = [('share/doc/minecraft-overviewer', doc_files)] @@ -175,12 +186,15 @@ class CustomClean(clean): pretty_fname) versionpath = os.path.join("overviewer_core", "overviewer_version.py") - try: - if not self.dry_run: - os.remove(versionpath) - log.info("removing '%s'", versionpath) - except OSError: - log.warn("'%s' could not be cleaned -- permission denied", versionpath) + if os.path.exists(versionpath): + try: + if not self.dry_run: + os.remove(versionpath) + log.info("removing '%s'", versionpath) + except OSError: + log.warn("'%s' could not be cleaned -- permission denied", versionpath) + else: + log.debug("'%s' does not exist -- can't clean it", versionpath) # now try to purge all *.pyc files for root, dirs, files in os.walk(os.path.join(os.path.dirname(__file__), ".")): @@ -203,7 +217,7 @@ def generate_version_py(): f.write(outstr) f.close() except: - print "WARNING: failed to build overview_version file" + print "WARNING: failed to build overviewer_version file" class CustomSDist(sdist): def run(self): @@ -232,11 +246,32 @@ class CustomBuildExt(build_ext): self.inplace = True build_ext.build_extensions(self) +class CheckTerrain(Command): + user_options=[] + def initialize_options(self): + pass + def finalize_options(self): + pass + def run(self): + from overviewer_core.textures import _find_file + import hashlib + import zipfile + print "checking..." + try: + f = _find_file("terrain.png", verbose=True) + except IOError: + log.error("Could not find the file terrain.png") + return + + h = hashlib.sha1() + h.update(f.read()) + log.info("Hash of terrain.png file is: %s", h.hexdigest()) setup_kwargs['cmdclass']['clean'] = CustomClean setup_kwargs['cmdclass']['sdist'] = CustomSDist setup_kwargs['cmdclass']['build'] = CustomBuild setup_kwargs['cmdclass']['build_ext'] = CustomBuildExt +setup_kwargs['cmdclass']['check_terrain'] = CheckTerrain ### setup(**setup_kwargs)