diff --git a/.gitignore b/.gitignore index 5871f26..1918894 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ *.pyc -build +MANIFEST +build/ +dist/ +Minecraft_Overviewer.egg-info terrain.png cachedir* @@ -14,10 +17,13 @@ ImPlatform.h Imaging.h # various forms of compiled c_overviewer extensions -c_overviewer.so -c_overviewer.pyd -c_overviewer_d.pyd -c_overviewer.dylib +overviewer_core/c_overviewer.so +overviewer_core/c_overviewer.pyd +overviewer_core/c_overviewer_d.pyd +overviewer_core/c_overviewer.dylib + +# generated version file +overviewer_core/overviewer_version.py # Mac OS X noise .DS_Store diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst index ae100b4..174ebe7 100644 --- a/CONTRIBUTORS.rst +++ b/CONTRIBUTORS.rst @@ -4,7 +4,8 @@ Contributors This file contains a list of every person who has contributed code to Overviewer. It was created from the git commit log, and should include -everyone, but we may have missed a few. +everyone, but we may have missed a few and it is manually updated +now. If you feel like you've been left out, feel free to tell us! Not currently included (but hopefully soon) are countless testers and bug reporters that helped fixed the many bugs that have popped up in the course of @@ -40,14 +41,18 @@ feature. * arrai * Kyle Brantley + * but2002 + * Eric Carr * cbarber * Alex Cline + * Andrew Clunis * CounterPillow * Stephen Fluin * Benjamin Herr * Ryan Hitchman * Jenny * Michael Jensen + * Maciej MaƂecki * Ryan McCue * Morlok8k * Gregory Short diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..ecc747a --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,9 @@ +include COPYING.txt +include README.rst +include CONTRIBUTORS.rst +include overviewer.py +include sample.settings.py +recursive-include contrib/ *.py +recursive-include overviewer_core/ *.py +recursive-include overviewer_core/src/ *.c *.h +recursive-include overviewer_core/data/ * diff --git a/blockcounter.py b/contrib/blockcounter.py similarity index 100% rename from blockcounter.py rename to contrib/blockcounter.py diff --git a/overviewer.py b/overviewer.py index 371793b..80f4b1e 100755 --- a/overviewer.py +++ b/overviewer.py @@ -22,14 +22,13 @@ if not (sys.version_info[0] == 2 and sys.version_info[1] >= 6): import os import os.path -from configParser import ConfigOptionParser import re import subprocess import multiprocessing import time import logging -import util import platform +from overviewer_core import util logging.basicConfig(level=logging.INFO,format="%(asctime)s [%(levelname)s] %(message)s") @@ -37,7 +36,7 @@ this_dir = util.get_program_path() # make sure the c_overviewer extension is available try: - import c_overviewer + from overviewer_core import c_overviewer except ImportError: ## if this is a frozen windows package, the following error messages about ## building the c_overviewer extension are not appropriate @@ -49,7 +48,7 @@ except ImportError: ## try to find the build extension - ext = os.path.join(this_dir, "c_overviewer.%s" % ("pyd" if platform.system() == "Windows" else "so")) + ext = os.path.join(this_dir, "overviewer_core", "c_overviewer.%s" % ("pyd" if platform.system() == "Windows" else "so")) if os.path.exists(ext): print "Something has gone wrong importing the c_overviewer extension. Please" print "make sure it is up-to-date (clean and rebuild)" @@ -57,14 +56,17 @@ except ImportError: print "You need to compile the c_overviewer module to run Minecraft Overviewer." print "Run `python setup.py build`, or see the README for details." + import traceback + traceback.print_exc() sys.exit(1) + if hasattr(sys, "frozen"): pass # we don't bother with a compat test since it should always be in sync elif "extension_version" in dir(c_overviewer): # check to make sure the binary matches the headers - if os.path.exists(os.path.join(this_dir, "src", "overviewer.h")): - with open(os.path.join(this_dir, "src", "overviewer.h")) as f: + if os.path.exists(os.path.join(this_dir, "overviewer_core", "src", "overviewer.h")): + with open(os.path.join(this_dir, "overviewer_core", "src", "overviewer.h")) as f: lines = f.readlines() lines = filter(lambda x: x.startswith("#define OVERVIEWER_EXTENSION_VERSION"), lines) if lines: @@ -76,12 +78,10 @@ else: print "Please rebuild your c_overviewer module. It is out of date!" sys.exit(1) +from overviewer_core.configParser import ConfigOptionParser +from overviewer_core import optimizeimages, world, quadtree +from overviewer_core import googlemap, rendernode -import optimizeimages -import world -import quadtree -import googlemap -import rendernode helptext = """ %prog [OPTIONS] @@ -126,14 +126,14 @@ def main(): if options.version: - print "Minecraft-Overviewer" - print "Git version: %s" % util.findGitVersion() try: - import overviewer_version - if hasattr(sys, "frozen"): - print "py2exe version build on %s" % overviewer_version.BUILD_DATE - print "Build machine: %s %s" % (overviewer_version.BUILD_PLATFORM, overviewer_version.BUILD_OS) + import overviewer_core.overviewer_version as overviewer_version + print "Minecraft-Overviewer %s" % overviewer_version.VERSION + print "Git commit: %s" % overviewer_version.HASH + print "built on %s" % overviewer_version.BUILD_DATE + print "Build machine: %s %s" % (overviewer_version.BUILD_PLATFORM, overviewer_version.BUILD_OS) except: + print "version info not found" pass sys.exit(0) @@ -178,6 +178,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/__init__.py b/overviewer_core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/chunk.py b/overviewer_core/chunk.py similarity index 85% rename from chunk.py rename to overviewer_core/chunk.py index 908df1f..b6116dd 100644 --- a/chunk.py +++ b/overviewer_core/chunk.py @@ -127,10 +127,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, @@ -334,67 +334,6 @@ class ChunkRenderer(object): return self._up_left_skylight up_left_skylight = property(_load_up_left_skylight) - 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/composite.py b/overviewer_core/composite.py similarity index 100% rename from composite.py rename to overviewer_core/composite.py diff --git a/configParser.py b/overviewer_core/configParser.py similarity index 100% rename from configParser.py rename to overviewer_core/configParser.py diff --git a/textures/fire.png b/overviewer_core/data/textures/fire.png similarity index 100% rename from textures/fire.png rename to overviewer_core/data/textures/fire.png diff --git a/textures/lava.png b/overviewer_core/data/textures/lava.png similarity index 100% rename from textures/lava.png rename to overviewer_core/data/textures/lava.png diff --git a/textures/portal.png b/overviewer_core/data/textures/portal.png similarity index 100% rename from textures/portal.png rename to overviewer_core/data/textures/portal.png diff --git a/textures/water.png b/overviewer_core/data/textures/water.png similarity index 100% rename from textures/water.png rename to overviewer_core/data/textures/water.png diff --git a/web_assets/compass_lower-left.png b/overviewer_core/data/web_assets/compass_lower-left.png similarity index 100% rename from web_assets/compass_lower-left.png rename to overviewer_core/data/web_assets/compass_lower-left.png diff --git a/web_assets/compass_lower-right.png b/overviewer_core/data/web_assets/compass_lower-right.png similarity index 100% rename from web_assets/compass_lower-right.png rename to overviewer_core/data/web_assets/compass_lower-right.png diff --git a/web_assets/compass_upper-left.png b/overviewer_core/data/web_assets/compass_upper-left.png similarity index 100% rename from web_assets/compass_upper-left.png rename to overviewer_core/data/web_assets/compass_upper-left.png diff --git a/web_assets/compass_upper-right.png b/overviewer_core/data/web_assets/compass_upper-right.png similarity index 100% rename from web_assets/compass_upper-right.png rename to overviewer_core/data/web_assets/compass_upper-right.png diff --git a/web_assets/control-bg-active.png b/overviewer_core/data/web_assets/control-bg-active.png similarity index 100% rename from web_assets/control-bg-active.png rename to overviewer_core/data/web_assets/control-bg-active.png diff --git a/web_assets/control-bg.png b/overviewer_core/data/web_assets/control-bg.png similarity index 100% rename from web_assets/control-bg.png rename to overviewer_core/data/web_assets/control-bg.png diff --git a/web_assets/index.html b/overviewer_core/data/web_assets/index.html similarity index 100% rename from web_assets/index.html rename to overviewer_core/data/web_assets/index.html diff --git a/web_assets/overviewer.css b/overviewer_core/data/web_assets/overviewer.css similarity index 78% rename from web_assets/overviewer.css rename to overviewer_core/data/web_assets/overviewer.css index f4bdd98..d875cdb 100644 --- a/web_assets/overviewer.css +++ b/overviewer_core/data/web_assets/overviewer.css @@ -100,3 +100,34 @@ body { background-color: #fff; /* fallback */ background-color: rgba(255,255,255,0.8); } + +#searchControl { + padding: 5px; + height: 20px; + font-family: Arial, sans-serif; +} + +#searchControl > input { + border: 2px solid #000; + font-size: 12pt; + width: 20em; + background-colour: #fff; +} + +div#searchDropDown { + border: 1px solid #000; + width: 17em; + font-size: 14pt; + background-color: #fff; + display: none; +} + +div.searchResultItem { + overflow: hidden; + text-overflow: ellipsis; +} + +div.searchResultItem img { + width: 24px; + height: 24px; +} diff --git a/web_assets/overviewer.js b/overviewer_core/data/web_assets/overviewer.js similarity index 86% rename from web_assets/overviewer.js rename to overviewer_core/data/web_assets/overviewer.js index aca2d8a..eab2dc8 100644 --- a/web_assets/overviewer.js +++ b/overviewer_core/data/web_assets/overviewer.js @@ -96,6 +96,25 @@ var overviewer = { return div; }; }, + /** + * Quote an arbitrary string for use in a regex matcher. + * WTB parametized regexes, JavaScript... + * + * From http://kevin.vanzonneveld.net + * original by: booeyOH + * improved by: Ates Goral (http://magnetiq.com) + * improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) + * bugfixed by: Onno Marsman + * example 1: preg_quote("$40"); + * returns 1: '\$40' + * example 2: preg_quote("*RRRING* Hello?"); + * returns 2: '\*RRRING\* Hello\?' + * example 3: preg_quote("\\.+*?[^]$(){}=!<>|:"); + * returns 3: '\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:' + */ + "pregQuote": function(str) { + return (str+'').replace(/([\\\.\+\*\?\[\^\]\$\(\)\{\}\=\!\<\>\|\:])/g, "\\$1"); + }, /** * Setup the varous mapTypes before we actually create the map. This used * to be a bunch of crap down at the bottom of functions.js @@ -146,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); } @@ -291,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; } @@ -317,6 +342,7 @@ var overviewer = { 'icon': iconURL, 'visible': false }); + item.marker = marker; overviewer.util.debug(label); overviewer.collections.markers[label].push(marker); if (item.type == 'sign') { @@ -339,6 +365,7 @@ var overviewer = { 'icon': iconURL, 'visible': false }); + item.marker = marker; if (overviewer.collections.markers['__others__']) { overviewer.collections.markers['__others__'].push(marker); } else { @@ -370,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); + } } } }, @@ -490,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)); if(overviewerConfig.map.north_direction == 'upper-right'){ x = -x-1; @@ -511,13 +555,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)) - lat = 0.5; - 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; @@ -555,14 +599,13 @@ 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.zoomLevels + 1)); lat -= 0.5; - lng -= 0.5 - (1.0 / Math.pow(2, overviewerConfig.map.maxZoom + 1)); - // I'll admit, I plugged this into Wolfram Alpha: // a = (x * 12 * r) + (z * 12 * r), b = (z * 6 * r) - (x * 6 * r) // And I don't know the math behind solving for for X and Z given @@ -622,7 +665,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()); @@ -689,7 +735,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) { @@ -717,6 +763,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 @@ -785,6 +834,63 @@ var overviewer = { itemDiv.appendChild(textNode); } }, + /** + * Create search box and dropdown in the top right google maps area. + */ + 'createSearchBox': function() { + var searchControl = document.createElement("div"); + searchControl.id = "searchControl"; + + var searchInput = document.createElement("input"); + searchInput.type = "text"; + + searchControl.appendChild(searchInput); + + var searchDropDown = document.createElement("div"); + searchDropDown.id = "searchDropDown"; + searchControl.appendChild(searchDropDown); + + $(searchInput).keyup(function(e) { + var newline_stripper = new RegExp("[\\r\\n]", "g") + if(searchInput.value.length !== 0) { + $(searchDropDown).fadeIn(); + + $(searchDropDown).empty(); + + overviewer.collections.markerDatas.forEach(function(marker_list) { + marker_list.forEach(function(sign) { + var regex = new RegExp(overviewer.util.pregQuote(searchInput.value), "mi"); + if(sign.msg.match(regex)) { + if(sign.marker !== undefined && sign.marker.getVisible()) { + var t = document.createElement("div"); + t.className = "searchResultItem"; + var i = document.createElement("img"); + i.src = sign.marker.getIcon(); + t.appendChild(i); + var s = document.createElement("span"); + + $(s).text(sign.msg.replace(newline_stripper, "")); + t.appendChild(s); + searchDropDown.appendChild(t); + $(t).click(function(e) { + $(searchDropDown).fadeOut(); + overviewer.map.setZoom(7); + overviewer.map.setCenter(sign.marker.getPosition()); + }); + + } + } + }); + }); + } else { + $(searchDropDown).fadeOut(); + } + }); + + 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 * be done in-line because of stupid Javascript scoping problems with diff --git a/web_assets/overviewerConfig.js b/overviewer_core/data/web_assets/overviewerConfig.js similarity index 90% rename from web_assets/overviewerConfig.js rename to overviewer_core/data/web_assets/overviewerConfig.js index 6a39735..c99398e 100644 --- a/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/web_assets/signpost-shadow.png b/overviewer_core/data/web_assets/signpost-shadow.png similarity index 100% rename from web_assets/signpost-shadow.png rename to overviewer_core/data/web_assets/signpost-shadow.png diff --git a/web_assets/signpost.png b/overviewer_core/data/web_assets/signpost.png similarity index 100% rename from web_assets/signpost.png rename to overviewer_core/data/web_assets/signpost.png diff --git a/web_assets/signpost_icon.png b/overviewer_core/data/web_assets/signpost_icon.png similarity index 100% rename from web_assets/signpost_icon.png rename to overviewer_core/data/web_assets/signpost_icon.png diff --git a/googlemap.py b/overviewer_core/googlemap.py similarity index 86% rename from googlemap.py rename to overviewer_core/googlemap.py index a6c6724..82b4869 100644 --- a/googlemap.py +++ b/overviewer_core/googlemap.py @@ -24,6 +24,7 @@ import json import util from c_overviewer import get_render_mode_inheritance +import overviewer_version """ This module has routines related to generating a Google Maps-based @@ -52,10 +53,15 @@ def mirror_dir(src, dst, entities=None): elif os.path.isfile(os.path.join(src,entry)): try: shutil.copy(os.path.join(src, entry), os.path.join(dst, entry)) - except IOError: - # maybe permission problems? - os.chmod(os.path.join(src, entry), stat.S_IRUSR) - os.chmod(os.path.join(dst, entry), stat.S_IWUSR) + except IOError as outer: + try: + # maybe permission problems? + src_stat = os.stat(os.path.join(src, entry)) + os.chmod(os.path.join(src, entry), src_stat.st_mode | stat.S_IRUSR) + dst_stat = os.stat(os.path.join(dst, entry)) + os.chmod(os.path.join(dst, entry), dst_stat.st_mode | stat.S_IWUSR) + except OSError: # we don't care if this fails + pass shutil.copy(os.path.join(src, entry), os.path.join(dst, entry)) # if this stills throws an error, let it propagate up @@ -99,7 +105,11 @@ class MapGen(object): blank.save(os.path.join(tileDir, "blank."+quadtree.imgformat)) # copy web assets into destdir: - mirror_dir(os.path.join(util.get_program_path(), "web_assets"), self.destdir) + global_assets = os.path.join(util.get_program_path(), "overviewer_core", "data", "web_assets") + if not os.path.isdir(global_assets): + global_assets = os.path.join(util.get_program_path(), "web_assets") + mirror_dir(global_assets, self.destdir) + # do the same with the local copy, if we have it if self.web_assets_path: mirror_dir(self.web_assets_path, self.destdir) @@ -110,6 +120,8 @@ class MapGen(object): "{minzoom}", str(0)) config = config.replace( "{maxzoom}", str(zoomlevel)) + config = config.replace( + "{zoomlevels}", str(zoomlevel)) config = config.replace( "{north_direction}", self.north_direction) @@ -134,9 +146,9 @@ class MapGen(object): indexpath = os.path.join(self.destdir, "index.html") index = open(indexpath, 'r').read() - index = index.replace( - "{time}", str(strftime("%a, %d %b %Y %H:%M:%S %Z", localtime()))) - index = index.replace("{version}", util.findGitVersion()) + index = index.replace("{time}", str(strftime("%a, %d %b %Y %H:%M:%S %Z", localtime()))) + versionstr = "%s (%s)" % (overviewer_version.VERSION, overviewer_version.HASH[:7]) + index = index.replace("{version}", versionstr) with open(os.path.join(self.destdir, "index.html"), 'w') as output: output.write(index) diff --git a/nbt.py b/overviewer_core/nbt.py similarity index 100% rename from nbt.py rename to overviewer_core/nbt.py diff --git a/optimizeimages.py b/overviewer_core/optimizeimages.py similarity index 100% rename from optimizeimages.py rename to overviewer_core/optimizeimages.py diff --git a/quadtree.py b/overviewer_core/quadtree.py similarity index 99% rename from quadtree.py rename to overviewer_core/quadtree.py index 4f46879..96ddb01 100644 --- a/quadtree.py +++ b/overviewer_core/quadtree.py @@ -121,7 +121,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) @@ -427,21 +427,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/rendernode.py b/overviewer_core/rendernode.py similarity index 98% rename from rendernode.py rename to overviewer_core/rendernode.py index 10db6c6..4303e12 100644 --- a/rendernode.py +++ b/overviewer_core/rendernode.py @@ -133,8 +133,8 @@ class RenderNode(object): else: if not complete % 1000 == 0: return - logging.info("{0}/{1} tiles complete on level {2}/{3}".format( - complete, total, level, self.max_p)) + logging.info("{0}/{1} ({4}%) tiles complete on level {2}/{3}".format( + complete, total, level, self.max_p, '%.1f' % ( (100.0 * complete) / total) )) def go(self, procs): """Renders all tiles""" diff --git a/src/Draw.c b/overviewer_core/src/Draw.c similarity index 100% rename from src/Draw.c rename to overviewer_core/src/Draw.c diff --git a/src/composite.c b/overviewer_core/src/composite.c similarity index 100% rename from src/composite.c rename to overviewer_core/src/composite.c diff --git a/src/endian.c b/overviewer_core/src/endian.c similarity index 100% rename from src/endian.c rename to overviewer_core/src/endian.c diff --git a/src/iterate.c b/overviewer_core/src/iterate.c similarity index 90% rename from src/iterate.c rename to overviewer_core/src/iterate.c index d86ecbf..5639ae9 100644 --- a/src/iterate.c +++ b/overviewer_core/src/iterate.c @@ -33,13 +33,13 @@ PyObject *init_chunk_render(PyObject *self, PyObject *args) { return NULL; } - textures = PyImport_ImportModule("textures"); + textures = PyImport_ImportModule("overviewer_core.textures"); /* ensure none of these pointers are NULL */ if ((!textures)) { return NULL; } - chunk_mod = PyImport_ImportModule("chunk"); + chunk_mod = PyImport_ImportModule("overviewer_core.chunk"); /* ensure none of these pointers are NULL */ if ((!chunk_mod)) { return NULL; @@ -174,12 +174,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 @@ -354,6 +360,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; @@ -366,7 +376,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; @@ -402,7 +412,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); } @@ -421,14 +434,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; + } rendermode->draw(rm_data, &state, src, mask, mask_light); + + if (state.block == 31) { + /* undo the random offsets */ + state.imgx -= randx; + state.imgy -= randy; + } } } diff --git a/src/main.c b/overviewer_core/src/main.c similarity index 100% rename from src/main.c rename to overviewer_core/src/main.c diff --git a/src/overviewer.h b/overviewer_core/src/overviewer.h similarity index 100% rename from src/overviewer.h rename to overviewer_core/src/overviewer.h diff --git a/src/rendermode-cave.c b/overviewer_core/src/rendermode-cave.c similarity index 100% rename from src/rendermode-cave.c rename to overviewer_core/src/rendermode-cave.c diff --git a/src/rendermode-lighting.c b/overviewer_core/src/rendermode-lighting.c similarity index 100% rename from src/rendermode-lighting.c rename to overviewer_core/src/rendermode-lighting.c diff --git a/src/rendermode-night.c b/overviewer_core/src/rendermode-night.c similarity index 100% rename from src/rendermode-night.c rename to overviewer_core/src/rendermode-night.c diff --git a/src/rendermode-normal.c b/overviewer_core/src/rendermode-normal.c similarity index 97% rename from src/rendermode-normal.c rename to overviewer_core/src/rendermode-normal.c index cee7431..cbc2d49 100644 --- a/src/rendermode-normal.c +++ b/overviewer_core/src/rendermode-normal.c @@ -125,7 +125,6 @@ rendermode_normal_occluded(void *data, RenderState *state) { 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 */ @@ -133,11 +132,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/src/rendermode-overlay.c b/overviewer_core/src/rendermode-overlay.c similarity index 100% rename from src/rendermode-overlay.c rename to overviewer_core/src/rendermode-overlay.c diff --git a/src/rendermode-spawn.c b/overviewer_core/src/rendermode-spawn.c similarity index 100% rename from src/rendermode-spawn.c rename to overviewer_core/src/rendermode-spawn.c diff --git a/src/rendermodes.c b/overviewer_core/src/rendermodes.c similarity index 100% rename from src/rendermodes.c rename to overviewer_core/src/rendermodes.c diff --git a/src/rendermodes.h b/overviewer_core/src/rendermodes.h similarity index 100% rename from src/rendermodes.h rename to overviewer_core/src/rendermodes.h diff --git a/textures.py b/overviewer_core/textures.py similarity index 84% rename from textures.py rename to overviewer_core/textures.py index 8662ad1..cd9439a 100644 --- a/textures.py +++ b/overviewer_core/textures.py @@ -14,6 +14,7 @@ # with the Overviewer. If not, see . import sys +import imp import os import os.path import zipfile @@ -27,13 +28,13 @@ 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: * the textures_path given in the config file (if present) - * The program dir (same dir as this file) - * The program dir / textures + * The program dir (same dir as overviewer.py) + * The overviewer_core textures dir * On Darwin, in /Applications/Minecraft * Inside minecraft.jar, which is looked for at these locations @@ -46,20 +47,29 @@ 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, "textures", filename) + path = os.path.join(programdir, "overviewer_core", "data", "textures", filename) if os.path.exists(path): return open(path, mode) + elif hasattr(sys, "frozen") or imp.is_frozen("__main__"): + # 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. @@ -79,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 @@ -151,13 +162,13 @@ def transform_image_side(img, blockID=None): # (don't just crop img, since we want the size of # img to be unchanged mask = img.crop((0,8,16,16)) - n = Image.new(img.mode, img.size, (38,92,255,0)) + n = Image.new(img.mode, img.size, bgcolor) composite.alpha_over(n, mask,(0,0,16,8), mask) img = n if blockID in (78,): # snow # make the top three quarters transparent mask = img.crop((0,12,16,16)) - n = Image.new(img.mode, img.size, (38,92,255,0)) + n = Image.new(img.mode, img.size, bgcolor) composite.alpha_over(n, mask,(0,12,16,16), mask) img = n @@ -235,7 +246,7 @@ def _build_block(top, side, blockID=None): top and side should be 16x16 image objects. Returns a 24x24 image """ - img = Image.new("RGBA", (24,24), (38,92,255,0)) + img = Image.new("RGBA", (24,24), bgcolor) original_texture = top.copy() top = transform_image(top, blockID) @@ -315,36 +326,36 @@ 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)) + img = Image.new("RGBA", (24,24), bgcolor) # first back sides if side1 != None : @@ -356,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 : @@ -367,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 : @@ -382,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) @@ -393,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) @@ -414,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 @@ -431,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 @@ -502,12 +513,11 @@ def generate_texture_tuple(img, blockid): def generate_special_texture(blockID, data, north_direction): """Generates a special texture, such as a correctly facing minecraft track""" - #print "%s has ancillary data: %X" %(blockID, data) - # TODO ladders, stairs, levers, buttons, and signs - # all need to behandled here (and in chunkpy) data = convert_data(blockID, data, north_direction) + # blocks need to be handled here (and in chunk.py) + if blockID == 2: # grass # data & 0x10 means SNOW sides side_img = terrain_images[3] @@ -544,33 +554,39 @@ def generate_special_texture(blockID, data, north_direction): 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) + # if nothing shown do not draw at all + if top == side3 == side4 == None: + return None + img = _build_full_block(top,None,None,side3,side4) return generate_texture_tuple(img, blockID) @@ -596,7 +612,7 @@ def generate_special_texture(blockID, data, north_direction): if blockID == 26: # bed - increment = 5 + increment = 8 left_face = None right_face = None if data & 0x8 == 0x8: # head of the bed @@ -640,6 +656,7 @@ def generate_special_texture(blockID, data, north_direction): return generate_texture_tuple(img, blockID) + if blockID == 31: # tall grass if data == 0: # dead shrub texture = terrain_images[55] @@ -652,8 +669,134 @@ def generate_special_texture(blockID, data, north_direction): 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), bgcolor) + 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), bgcolor) + 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), bgcolor) + 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), bgcolor) + 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), bgcolor) + 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] @@ -710,6 +853,7 @@ def generate_special_texture(blockID, data, north_direction): if blockID in (50,75,76): # torch, off redstone torch, on redstone torch + # choose the proper texture if blockID == 50: # torch small = terrain_images[80] @@ -720,7 +864,7 @@ def generate_special_texture(blockID, data, north_direction): # compose a torch bigger than the normal # (better for doing transformations) - torch = Image.new("RGBA", (16,16), (38,92,255,0)) + torch = Image.new("RGBA", (16,16), bgcolor) composite.alpha_over(torch,small,(-4,-3)) composite.alpha_over(torch,small,(-5,-2)) composite.alpha_over(torch,small,(-3,-2)) @@ -746,17 +890,17 @@ def generate_special_texture(blockID, data, north_direction): elif data == 5: # standing on the floor # compose a "3d torch". - img = Image.new("RGBA", (24,24), (38,92,255,0)) + img = Image.new("RGBA", (24,24), bgcolor) small_crop = small.crop((2,2,14,14)) slice = small_crop.copy() ImageDraw.Draw(slice).rectangle((6,0,12,12),outline=(0,0,0,0),fill=(0,0,0,0)) ImageDraw.Draw(slice).rectangle((0,0,4,12),outline=(0,0,0,0),fill=(0,0,0,0)) - composite.alpha_over(img, slice, (6,4)) - composite.alpha_over(img, small_crop, (5,5)) - composite.alpha_over(img, small_crop, (6,5)) - composite.alpha_over(img, slice, (6,6)) + composite.alpha_over(img, slice, (7,5)) + composite.alpha_over(img, small_crop, (6,6)) + composite.alpha_over(img, small_crop, (7,6)) + composite.alpha_over(img, slice, (7,7)) return generate_texture_tuple(img, blockID) @@ -766,7 +910,7 @@ def generate_special_texture(blockID, data, north_direction): side1 = transform_image_side(firetexture) side2 = transform_image_side(firetexture).transpose(Image.FLIP_LEFT_RIGHT) - img = Image.new("RGBA", (24,24), (38,92,255,0)) + img = Image.new("RGBA", (24,24), bgcolor) composite.alpha_over(img, side1, (12,0), side1) composite.alpha_over(img, side2, (0,0), side2) @@ -813,14 +957,14 @@ def generate_special_texture(blockID, data, north_direction): composite.alpha_over(img, tmp2, (0,6)) elif data == 1: # ascending north - img = Image.new("RGBA", (24,24), (38,92,255,0)) # first paste the texture in the back + img = Image.new("RGBA", (24,24), bgcolor) # first paste the texture in the back tmp1 = transform_image(half_block_r) composite.alpha_over(img, tmp1, (0,6)) tmp2 = _build_full_block(half_block_l, None, None, texture, side) composite.alpha_over(img, tmp2) elif data == 2: # ascending west - img = Image.new("RGBA", (24,24), (38,92,255,0)) # first paste the texture in the back + img = Image.new("RGBA", (24,24), bgcolor) # first paste the texture in the back tmp1 = transform_image(half_block_u) composite.alpha_over(img, tmp1, (0,6)) tmp2 = _build_full_block(half_block_d, None, None, side, texture) @@ -937,7 +1081,7 @@ def generate_special_texture(blockID, data, north_direction): bottom = redstone_wire_t.copy().rotate(90) else: - bottom = Image.new("RGBA", (16,16), (38,92,255,0)) + bottom = Image.new("RGBA", (16,16), bgcolor) if (data & 0b0001) == 1: composite.alpha_over(bottom,branch_top_left) @@ -981,7 +1125,7 @@ def generate_special_texture(blockID, data, north_direction): crop2 = transform_image_side(raw_crop, blockID) crop3 = crop2.transpose(Image.FLIP_LEFT_RIGHT) - img = Image.new("RGBA", (24,24), (38,92,255,0)) + img = Image.new("RGBA", (24,24), bgcolor) composite.alpha_over(img, crop1, (0,12), crop1) composite.alpha_over(img, crop2, (6,3), crop2) composite.alpha_over(img, crop3, (6,3), crop3) @@ -1014,6 +1158,7 @@ def generate_special_texture(blockID, data, north_direction): if blockID == 63: # singposts + texture = terrain_images[4].copy() # cut the planks to the size of a signpost ImageDraw.Draw(texture).rectangle((0,12,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) @@ -1031,7 +1176,7 @@ def generate_special_texture(blockID, data, north_direction): texture_stick = texture_stick.resize((12,12), Image.ANTIALIAS) ImageDraw.Draw(texture_stick).rectangle((2,0,12,12),outline=(0,0,0,0),fill=(0,0,0,0)) - img = Image.new("RGBA", (24,24), (38,92,255,0)) + img = Image.new("RGBA", (24,24), bgcolor) # W N ~90 E S ~270 angles = (330.,345.,0.,15.,30.,55.,95.,120.,150.,165.,180.,195.,210.,230.,265.,310.) @@ -1069,8 +1214,8 @@ def generate_special_texture(blockID, data, north_direction): swung=False # mask out the high bits to figure out the orientation - img = Image.new("RGBA", (24,24), (38,92,255,0)) - if (data & 0x03) == 0: + img = Image.new("RGBA", (24,24), bgcolor) + if (data & 0x03) == 0: # northeast corner if not swung: tex = transform_image_side(raw_door) composite.alpha_over(img, tex, (0,6), tex) @@ -1080,7 +1225,7 @@ def generate_special_texture(blockID, data, north_direction): 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) @@ -1088,7 +1233,7 @@ def generate_special_texture(blockID, data, north_direction): 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) @@ -1096,7 +1241,7 @@ def generate_special_texture(blockID, data, north_direction): 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) @@ -1108,7 +1253,7 @@ def generate_special_texture(blockID, data, north_direction): if blockID == 65: # ladder - img = Image.new("RGBA", (24,24), (38,92,255,0)) + img = Image.new("RGBA", (24,24), bgcolor) raw_texture = terrain_images[83] #print "ladder is facing: %d" % data if data == 5: @@ -1133,7 +1278,7 @@ def generate_special_texture(blockID, data, north_direction): if blockID in (27, 28, 66): # minetrack: - img = Image.new("RGBA", (24,24), (38,92,255,0)) + img = Image.new("RGBA", (24,24), bgcolor) if blockID == 27: # powered rail if data & 0x8 == 0: # unpowered @@ -1216,7 +1361,7 @@ def generate_special_texture(blockID, data, north_direction): texture.putpixel((x,y),(0,0,0,255)) """ - img = Image.new("RGBA", (24,24), (38,92,255,0)) + img = Image.new("RGBA", (24,24), bgcolor) incrementx = 0 if data == 2: # east @@ -1250,7 +1395,6 @@ def generate_special_texture(blockID, data, north_direction): ImageDraw.Draw(fence_top).rectangle((0,0,15,5),outline=(0,0,0,0),fill=(0,0,0,0)) ImageDraw.Draw(fence_top).rectangle((0,10,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) - ImageDraw.Draw(fence_side).rectangle((0,0,15,0),outline=(0,0,0,0),fill=(0,0,0,0)) ImageDraw.Draw(fence_side).rectangle((0,0,5,15),outline=(0,0,0,0),fill=(0,0,0,0)) ImageDraw.Draw(fence_side).rectangle((10,0,15,15),outline=(0,0,0,0),fill=(0,0,0,0)) @@ -1270,10 +1414,10 @@ def generate_special_texture(blockID, data, north_direction): fence_other_side.putalpha(othersidealpha) # Compose the fence big stick - fence_big = Image.new("RGBA", (24,24), (38,92,255,0)) + fence_big = Image.new("RGBA", (24,24), bgcolor) composite.alpha_over(fence_big,fence_side, (5,4),fence_side) composite.alpha_over(fence_big,fence_other_side, (7,4),fence_other_side) - composite.alpha_over(fence_big,fence_top, (0,1),fence_top) + composite.alpha_over(fence_big,fence_top, (0,0),fence_top) # Now render the small sticks. # Create needed images @@ -1301,7 +1445,7 @@ def generate_special_texture(blockID, data, north_direction): fence_small_side.putalpha(sidealpha) # Create img to compose the fence - img = Image.new("RGBA", (24,24), (38,92,255,0)) + img = Image.new("RGBA", (24,24), bgcolor) # Position of fence small sticks in img. # These postitions are strange because the small sticks of the @@ -1351,7 +1495,7 @@ def generate_special_texture(blockID, data, north_direction): if blockID == 90: # portal portaltexture = _load_image("portal.png") - img = Image.new("RGBA", (24,24), (38,92,255,0)) + img = Image.new("RGBA", (24,24), bgcolor) side = transform_image_side(portaltexture) otherside = side.transpose(Image.FLIP_TOP_BOTTOM) @@ -1380,23 +1524,21 @@ def generate_special_texture(blockID, data, north_direction): otherside = ImageEnhance.Brightness(otherside).enhance(0.8) otherside.putalpha(othersidealpha) - img = Image.new("RGBA", (24,24), (38,92,255,0)) + img = Image.new("RGBA", (24,24), bgcolor) - 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 - # NOTE: this function uses the redstone torches generated above, - # this must run after the function of the torches. - + if blockID in (93, 94): # redstone repeaters (diodes), ON and OFF + # generate the diode 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 @@ -1411,12 +1553,22 @@ def generate_special_texture(blockID, data, north_direction): top = top.rotate(90) img = _build_full_block( (top, increment), None, None, side, side) + + # compose a "3d" redstone torch + t = terrain_images[115].copy() if blockID == 93 else terrain_images[99].copy() + torch = Image.new("RGBA", (24,24), bgcolor) + + t_crop = t.crop((2,2,14,14)) + slice = t_crop.copy() + ImageDraw.Draw(slice).rectangle((6,0,12,12),outline=(0,0,0,0),fill=(0,0,0,0)) + ImageDraw.Draw(slice).rectangle((0,0,4,12),outline=(0,0,0,0),fill=(0,0,0,0)) + + composite.alpha_over(torch, slice, (6,4)) + composite.alpha_over(torch, t_crop, (5,5)) + composite.alpha_over(torch, t_crop, (6,5)) + composite.alpha_over(torch, slice, (6,6)) # paste redstone torches everywhere! - t = specialblockmap[(75,5)] if blockID == 93 else specialblockmap[(76,5)] - torch = t[0].copy() # textures are stored as tuples (RGB,A) - torch.putalpha(t[1]) - # the torch is too tall for the repeater, crop the bottom. ImageDraw.Draw(torch).rectangle((0,16,24,24),outline=(0,0,0,0),fill=(0,0,0,0)) @@ -1521,10 +1673,11 @@ def generate_special_texture(blockID, data, north_direction): 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) + return None def convert_data(blockID, data, north_direction): @@ -1830,9 +1983,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. @@ -1842,10 +1996,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 @@ -1854,7 +2012,7 @@ special_map[51] = range(16) # fire, position in the block (not implemented) special_map[53] = range(4) # wooden stairs, orientation special_map[54] = range(12) # chests, orientation and type (single or double), uses pseudo data special_map[55] = range(128) # redstone wire, all the possible combinations, uses pseudo data -special_map[58] = (0,) # crafting table +special_map[58] = (0,) # crafting table, it has 2 different sides special_map[59] = range(8) # crops, grow from 0 to 7 special_map[61] = range(6) # furnace, orientation special_map[62] = range(6) # burning furnace, orientation @@ -1871,9 +2029,9 @@ special_map[85] = range(17) # fences, all the possible combination, uses pseudo special_map[86] = range(5) # pumpkin, orientation special_map[90] = (1,2,4,8) # portal, in 2 orientations, 4 cases, uses pseudo data special_map[91] = range(5) # jack-o-lantern, orientation -special_map[92] = range(6) # cake! -special_map[93] = range(16) # OFF redstone repeater, orientation and delay (delay not implemented) -special_map[94] = range(16) # ON redstone repeater, orientation and delay (delay not implemented) +special_map[92] = range(6) # cake, eaten amount, (not implemented) +special_map[93] = range(16) # OFF redstone repeater, orientation and delay +special_map[94] = range(16) # ON redstone repeater, orientation and delay special_map[96] = range(8) # trapdoor, open, closed, orientation # grass and leaves are graysacle in terrain.png @@ -1888,6 +2046,7 @@ special_map[18] = range(16) # leaves, birch, normal or pine leaves (not implemen special_map[31] = range(3) # tall grass, dead shrub, fern and tall grass itself # placeholders that are generated in generate() +bgcolor = None terrain_images = None blockmap = None biome_grass_texture = None @@ -1896,11 +2055,15 @@ biome_tall_fern_texture = None biome_leaf_texture = None specialblockmap = None -def generate(north_direction, path=None): +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 _find_file_local_path = path + texture_dimensions = (texture_size, texture_size) # This maps terainids to 16x16 images global terrain_images @@ -1924,3 +2087,30 @@ def generate(north_direction, path=None): for blockID in special_blocks: for data in special_map[blockID]: specialblockmap[(blockID, data)] = generate_special_texture(blockID, data, north_direction) + + 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/util.py b/overviewer_core/util.py similarity index 68% rename from util.py rename to overviewer_core/util.py index 7a0323d..2bd15b5 100644 --- a/util.py +++ b/overviewer_core/util.py @@ -21,19 +21,22 @@ import imp import os import os.path import sys +from subprocess import Popen, PIPE def get_program_path(): if hasattr(sys, "frozen") or imp.is_frozen("__main__"): return os.path.dirname(sys.executable) else: try: - return os.path.dirname(__file__) + # normally, we're in ./overviewer_core/util.py + # we want ./ + return os.path.dirname(os.path.dirname(__file__)) except NameError: return os.path.dirname(sys.argv[0]) - -def findGitVersion(): +# does not require git, very likely to work everywhere +def findGitHash(): this_dir = get_program_path() if os.path.exists(os.path.join(this_dir,".git")): with open(os.path.join(this_dir,".git","HEAD")) as f: @@ -46,6 +49,24 @@ def findGitVersion(): else: return data else: + try: + import overviewer_version + return overviewer_version.HASH + except: + return "unknown" + +def findGitVersion(): + try: + p = Popen(['git', 'describe', '--tags'], stdout=PIPE, stderr=PIPE) + p.stderr.close() + line = p.stdout.readlines()[0] + if line.startswith('release-'): + line = line.split('-', 1)[1] + # turn 0.1.2-50-somehash into 0.1.2-50 + # and 0.1.3 into 0.1.3 + line = '-'.join(line.split('-', 2)[:2]) + return line.strip() + except: try: import overviewer_version return overviewer_version.VERSION diff --git a/world.py b/overviewer_core/world.py similarity index 92% rename from world.py rename to overviewer_core/world.py index 1d580d8..6ca3d86 100644 --- a/world.py +++ b/overviewer_core/world.py @@ -196,12 +196,17 @@ class World(object): in the chunk coordinate system, and figures out the row and column in the image each one should be. Returns (col, row).""" + # columns are determined by the sum of the chunk coords, rows are the + # difference # change this function, and you MUST change unconvert_coords return (chunkx + chunky, chunky - chunkx) def unconvert_coords(self, col, row): """Undoes what convert_coords does. Returns (chunkx, chunky).""" - return ((col - row) / 2, (row + col) / 2) + + # col + row = chunky + chunky => (col + row)/2 = chunky + # col - row = chunkx + chunkx => (col - row)/2 = chunkx + return ((col - row) / 2, (col + row) / 2) def findTrueSpawn(self): """Adds the true spawn location to self.POI. The spawn Y coordinate @@ -229,25 +234,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, self.north_direction)[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, self.north_direction)[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=disp_spawnX, y=spawnY, z=disp_spawnZ, msg="Spawn", type="spawn", chunk=(chunkX, chunkY))) self.spawn = (disp_spawnX, spawnY, disp_spawnZ) @@ -314,7 +321,6 @@ class World(object): world. Returns (regionx, regiony, filename)""" - join = os.path.join if regionlist is not None: for path in regionlist: diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index da767ed..e105bf6 --- a/setup.py +++ b/setup.py @@ -1,26 +1,61 @@ -from distutils.core import setup, Extension +#!/usr/bin/env python + +from distutils.core import setup +from distutils.extension import Extension 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 -import os, os.path +import sys, os, os.path import glob import platform import time +import overviewer_core.util as util try: import py2exe except ImportError: py2exe = None +try: + import py2app + from setuptools.extension import Extension +except ImportError: + py2app = None + # now, setup the keyword arguments for setup -# (because we don't know until runtime if py2exe is available) +# (because we don't know until runtime if py2exe/py2app is available) setup_kwargs = {} -setup_kwargs['options'] = {} setup_kwargs['ext_modules'] = [] setup_kwargs['cmdclass'] = {} +setup_kwargs['options'] = {} + +# +# metadata +# + +# Utility function to read the README file. +# Used for the long_description. It's nice, because now 1) we have a top level +# README file and 2) it's easier to type in the README file than to put a raw +# string in below ... +def read(fname): + return open(os.path.join(os.path.dirname(__file__), fname)).read() + +setup_kwargs['name'] = 'Minecraft-Overviewer' +setup_kwargs['version'] = util.findGitVersion() +setup_kwargs['description'] = 'Generates large resolution images of a Minecraft map.' +setup_kwargs['url'] = 'http://overviewer.org/' +setup_kwargs['author'] = 'Andrew Brown' +setup_kwargs['author_email'] = 'brownan@gmail.com' +setup_kwargs['license'] = 'GNU General Public License v3' +setup_kwargs['long_description'] = read('README.rst') + +# top-level files that should be included as documentation +doc_files = ['COPYING.txt', 'README.rst', 'CONTRIBUTORS.rst', 'sample.settings.py'] # helper to create a 'data_files'-type sequence recursively for a given dir def recursive_data_files(src, dest=None): @@ -40,15 +75,26 @@ 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 # if py2exe is not None: setup_kwargs['console'] = ['overviewer.py'] - setup_kwargs['data_files'] = [('textures', ['textures/lava.png', 'textures/water.png', 'textures/fire.png', 'textures/portal.png']), - ('', ['COPYING.txt', 'README.rst'])] - setup_kwargs['data_files'] += recursive_data_files('web_assets') + setup_kwargs['data_files'] = [('', doc_files)] + setup_kwargs['data_files'] += recursive_data_files('overviewer_core/data/textures', 'textures') + setup_kwargs['data_files'] += recursive_data_files('overviewer_core/data/web_assets', 'web_assets') setup_kwargs['zipfile'] = None if platform.system() == 'Windows' and '64bit' in platform.architecture(): b = 3 @@ -56,6 +102,27 @@ if py2exe is not None: b = 1 setup_kwargs['options']['py2exe'] = {'bundle_files' : b, 'excludes': 'Tkinter'} +# +# py2app options +# + +if py2app is not None: + setup_kwargs['app'] = ['overviewer.py'] + setup_kwargs['options']['py2app'] = {'argv_emulation' : False} + setup_kwargs['setup_requires'] = ['py2app'] + +# +# script, package, and data +# + +setup_kwargs['packages'] = ['overviewer_core'] +setup_kwargs['scripts'] = ['overviewer.py'] +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)] + + # # c_overviewer extension # @@ -79,20 +146,23 @@ except: # used to figure out what files to compile render_modes = ['normal', 'overlay', 'lighting', 'night', 'spawn', 'cave'] -c_overviewer_files = ['src/main.c', 'src/composite.c', 'src/iterate.c', 'src/endian.c', 'src/rendermodes.c'] -c_overviewer_files += map(lambda mode: 'src/rendermode-%s.c' % (mode,), render_modes) -c_overviewer_files += ['src/Draw.c'] -c_overviewer_includes = ['src/overviewer.h', 'src/rendermodes.h'] +c_overviewer_files = ['main.c', 'composite.c', 'iterate.c', 'endian.c', 'rendermodes.c'] +c_overviewer_files += map(lambda mode: 'rendermode-%s.c' % (mode,), render_modes) +c_overviewer_files += ['Draw.c'] +c_overviewer_includes = ['overviewer.h', 'rendermodes.h'] + +c_overviewer_files = map(lambda s: 'overviewer_core/src/'+s, c_overviewer_files) +c_overviewer_includes = map(lambda s: 'overviewer_core/src/'+s, c_overviewer_includes) + +setup_kwargs['ext_modules'].append(Extension('overviewer_core.c_overviewer', c_overviewer_files, include_dirs=['.', numpy_include] + pil_include, depends=c_overviewer_includes, extra_link_args=[])) -setup_kwargs['ext_modules'].append(Extension('c_overviewer', c_overviewer_files, include_dirs=['.', numpy_include] + pil_include, depends=c_overviewer_includes, extra_link_args=[])) # tell build_ext to build the extension in-place # (NOT in build/) setup_kwargs['options']['build_ext'] = {'inplace' : 1} -# tell the build command to only run build_ext -build.sub_commands = [('build_ext', None)] # custom clean command to remove in-place extension +# and the version file class CustomClean(clean): def run(self): # do the normal cleanup @@ -101,7 +171,7 @@ class CustomClean(clean): # try to remove '_composite.{so,pyd,...}' extension, # regardless of the current system's extension name convention build_ext = self.get_finalized_command('build_ext') - pretty_fname = build_ext.get_ext_filename('c_overviewer') + pretty_fname = build_ext.get_ext_filename('overviewer_core.c_overviewer') fname = pretty_fname if os.path.exists(fname): try: @@ -114,8 +184,55 @@ class CustomClean(clean): else: log.debug("'%s' does not exist -- can't clean it", pretty_fname) + + versionpath = os.path.join("overviewer_core", "overviewer_version.py") + 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) -class CustomBuild(build_ext): + # now try to purge all *.pyc files + for root, dirs, files in os.walk(os.path.join(os.path.dirname(__file__), ".")): + for f in files: + if f.endswith(".pyc"): + if self.dry_run: + log.warn("Would remove %s", os.path.join(root,f)) + else: + os.remove(os.path.join(root, f)) + +def generate_version_py(): + try: + outstr = "" + outstr += "VERSION=%r\n" % util.findGitVersion() + outstr += "HASH=%r\n" % util.findGitHash() + outstr += "BUILD_DATE=%r\n" % time.asctime() + outstr += "BUILD_PLATFORM=%r\n" % platform.processor() + outstr += "BUILD_OS=%r\n" % platform.platform() + f = open("overviewer_core/overviewer_version.py", "w") + f.write(outstr) + f.close() + except: + print "WARNING: failed to build overviewer_version file" + +class CustomSDist(sdist): + def run(self): + # generate the version file + generate_version_py() + sdist.run(self) + +class CustomBuild(build): + def run(self): + # generate the version file + generate_version_py() + build.run(self) + print "\nBuild Complete" + +class CustomBuildExt(build_ext): def build_extensions(self): c = self.compiler.compiler_type if c == "msvc": @@ -123,32 +240,39 @@ class CustomBuild(build_ext): for e in self.extensions: e.extra_link_args.append("/MANIFEST") + # build in place, and in the build/ tree + self.inplace = False + build_ext.build_extensions(self) + 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 -if py2exe is not None: -# define a subclass of py2exe to build our version file on the fly - class CustomPy2exe(py2exe.build_exe.py2exe): - def run(self): - try: - import util - f = open("overviewer_version.py", "w") - f.write("VERSION=%r\n" % util.findGitVersion()) - f.write("BUILD_DATE=%r\n" % time.asctime()) - f.write("BUILD_PLATFORM=%r\n" % platform.processor()) - f.write("BUILD_OS=%r\n" % platform.platform()) - f.close() - setup_kwargs['data_files'].append(('.', ['overviewer_version.py'])) - except: - print "WARNING: failed to build overview_version file" - py2exe.build_exe.py2exe.run(self) - setup_kwargs['cmdclass']['py2exe'] = CustomPy2exe + 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']['build_ext'] = CustomBuild +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) - -print "\nBuild Complete"