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/README.rst b/README.rst index 11f9f10..942e4cb 100644 --- a/README.rst +++ b/README.rst @@ -169,6 +169,10 @@ Options *Note*: Currently only the overviewer.dat file is deleted when you run with this option +--forcerender + Force re-rendering the entire map (or the given regionlist). This + is an easier way to completely re-render without deleting the map. + --regionlist=regionlist Use this option to specify manually a list of regions to consider for updating. Without this option, every chunk in every region is checked for 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 1bf6790..f86a83d 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,10 +36,19 @@ 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 + if hasattr(sys, "frozen"): + print "Something has gone wrong importing the c_overviewer extension. Please" + print "make sure the 2008 and 2010 redistributable packages from Microsoft" + print "are installed." + sys.exit(1) + + ## 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)" @@ -48,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: @@ -67,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] @@ -115,14 +124,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) @@ -167,6 +176,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 84% rename from chunk.py rename to overviewer_core/chunk.py index f8aa1f6..6a3344b 100644 --- a/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, @@ -321,67 +321,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.png b/overviewer_core/data/web_assets/compass.png similarity index 100% rename from web_assets/compass.png rename to overviewer_core/data/web_assets/compass.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 85% rename from web_assets/overviewer.js rename to overviewer_core/data/web_assets/overviewer.js index ea87a19..1d7fe60 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)); // This information about where the center column is may change with // a different drawing implementation -- check it again after any @@ -498,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; @@ -542,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: @@ -595,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()); @@ -662,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) { @@ -690,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 @@ -758,6 +808,79 @@ 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"; + searchInput.value = "Sign Search" + searchInput.title = "Sign Search" + + /* Hey dawg, I heard you like functions. + * So we defined a function inside your function. + */ + searchInput.onfocus = function() { + if (searchInput.value == "Sign Search") { + searchInput.value = ""; + } + }; + searchInput.onblur = function() { + if (searchInput.value == "") { + searchInput.value = "Sign Search"; + } + }; + + 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 ce38479..bdb6015 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 48fede1..a4fb156 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 @@ -98,7 +104,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) @@ -109,6 +119,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))) @@ -131,9 +143,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 fc00b01..2e77576 100644 --- a/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/rendernode.py b/overviewer_core/rendernode.py similarity index 98% rename from rendernode.py rename to overviewer_core/rendernode.py index 6f6c76e..054e2ea 100644 --- a/rendernode.py +++ b/overviewer_core/rendernode.py @@ -132,8 +132,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 88% rename from src/iterate.c rename to overviewer_core/src/iterate.c index d86ecbf..42fac6d 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) || (state->block == 79)) { /* glass and ice */ + /* 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,8 +412,18 @@ 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)) { + state.block_data = ancilData; + /* block that need pseudo ancildata: + * grass, water, glass, chest, restone wire, + * ice, fence and portal. */ + if ((state.block == 2) || (state.block == 9) || + (state.block == 20) || (state.block == 54) || + (state.block == 55) || (state.block == 79) || + (state.block == 85) || (state.block == 90)) { ancilData = generate_pseudo_data(&state, ancilData); + state.block_pdata = ancilData; + } else { + state.block_pdata = 0; } tmp = PyTuple_New(2); @@ -421,14 +441,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 98% rename from src/overviewer.h rename to overviewer_core/src/overviewer.h index a0c5316..e7057e7 100644 --- a/src/overviewer.h +++ b/overviewer_core/src/overviewer.h @@ -70,6 +70,8 @@ typedef struct { /* the block position and type, and the block array */ int x, y, z; unsigned char block; + unsigned char block_data; + unsigned char block_pdata; PyObject *blockdata_expanded; PyObject *blocks; PyObject *up_left_blocks; 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 92% rename from src/rendermode-lighting.c rename to overviewer_core/src/rendermode-lighting.c index c515db5..057331a 100644 --- a/src/rendermode-lighting.c +++ b/overviewer_core/src/rendermode-lighting.c @@ -293,7 +293,22 @@ rendermode_lighting_draw(void *data, RenderState *state, PyObject *src, PyObject self = (RenderModeLighting *)data; x = state->x, y = state->y, z = state->z; - if (is_transparent(state->block)) { + if ((state->block == 9) || (state->block == 79)) { /* special case for water and ice */ + /* looks like we need a new case for lighting, there are + * blocks that are transparent for occlusion calculations and + * need per-face shading if the face is drawn. */ + if ((state->block_pdata & 16) == 16) { + do_shading_with_mask(self, state, x, y, z+1, self->facemasks[0]); + } + if ((state->block_pdata & 2) == 2) { /* bottom left */ + do_shading_with_mask(self, state, x-1, y, z, self->facemasks[1]); + } + if ((state->block_pdata & 4) == 4) { /* bottom right */ + do_shading_with_mask(self, state, x, y+1, z, self->facemasks[2]); + } + /* leaves are transparent for occlusion calculations but they + * per face-shading to look as in game */ + } else if (is_transparent(state->block) && (state->block != 18)) { /* transparent: do shading on whole block */ do_shading_with_mask(self, state, x, y, z, mask_light); } else { 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 81% rename from textures.py rename to overviewer_core/textures.py index 5e2db3b..c6ad4a1 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,13 +425,13 @@ 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 36, 37, -1, -1, 65, -1, -1, -1, 50, 24, -1, -1, 86, -1, -1, -1, # 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 - -1, -1, -1, -1, -1, -1, -1, -1, -1, 51, 51, -1, -1, -1, 66, 67, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 51, 51, -1, -1, -1, 66, -1, # 80 81 82 83 84 85 86 87 88 89 90 91 66, 69, 72, 73, 75, -1,102,103,104,105,-1, 102 # clay? ] @@ -431,13 +442,13 @@ 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 36, 37, -1, -1, 65, -1, -1,101, 50, 24, -1, -1, 86, -1, -1, -1, # 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 - -1, -1, -1, -1, -1, -1, -1, -1, -1, 51, 51, -1, -1, -1, 66, 67, + -1, -1, -1, -1, -1, -1, -1, -1, -1, 51, 51, -1, -1, -1, 66, -1, # 80 81 82 83 84 85 86 87 88 89 90 91 66, 70, 72, 73, 74,-1 ,118,103,104,105, -1, 118 ] @@ -502,9 +513,7 @@ def generate_texture_tuple(img, blockid): def generate_special_texture(blockID, data): """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) + # blocks need to be handled here (and in chunk.py) if blockID == 2: # grass # data & 0x10 means SNOW sides @@ -542,33 +551,41 @@ 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 or blockID == 79: # spring water, flowing water and waterfall water, AND glass, AND ice + # water,glass and ice share the way to be rendered + if blockID == 9: + texture = _load_image("water.png") + elif blockID == 20: + texture = terrain_images[49] + else: + texture = terrain_images[67] 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) @@ -594,7 +611,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 @@ -638,6 +655,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] @@ -650,8 +668,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), 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] @@ -719,7 +863,7 @@ def generate_special_texture(blockID, data): # 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)) @@ -745,17 +889,17 @@ def generate_special_texture(blockID, data): 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) @@ -765,7 +909,7 @@ def generate_special_texture(blockID, data): 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) @@ -812,14 +956,14 @@ def generate_special_texture(blockID, data): 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) @@ -936,7 +1080,7 @@ def generate_special_texture(blockID, data): 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) @@ -980,7 +1124,7 @@ def generate_special_texture(blockID, data): 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) @@ -1031,7 +1175,7 @@ def generate_special_texture(blockID, data): 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 +1213,8 @@ def generate_special_texture(blockID, data): 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 +1224,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) @@ -1088,7 +1232,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) @@ -1096,7 +1240,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) @@ -1108,7 +1252,7 @@ def generate_special_texture(blockID, data): 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 +1277,7 @@ def generate_special_texture(blockID, data): 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 +1360,7 @@ def generate_special_texture(blockID, data): 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 +1394,6 @@ def generate_special_texture(blockID, data): 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 +1413,10 @@ def generate_special_texture(blockID, data): 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 +1444,7 @@ def generate_special_texture(blockID, data): 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 +1494,7 @@ def generate_special_texture(blockID, data): 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 +1523,21 @@ def generate_special_texture(blockID, data): 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 +1552,22 @@ def generate_special_texture(blockID, data): 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,7 +1672,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) @@ -1598,9 +1749,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, 79, 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. @@ -1610,10 +1762,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 @@ -1622,7 +1778,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 @@ -1635,13 +1791,14 @@ special_map[68] = (2,3,4,5) # wall sing, orientation special_map[71] = range(16) # iron door, open/close and orientation special_map[75] = (1,2,3,4,5) # off redstone torch, orientation special_map[76] = (1,2,3,4,5) # on redstone torch, orientation +special_map[79] = range(32) # ice, used to only render the exterior surface, uses pseudo data special_map[85] = range(17) # fences, all the possible combination, uses pseudo data 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 @@ -1656,6 +1813,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 @@ -1664,9 +1822,12 @@ 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,bgc = (26,26,26,0)): + 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 @@ -1690,3 +1851,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/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 93% rename from world.py rename to overviewer_core/world.py index 1344d10..fbd61a9 100644 --- a/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 old mode 100644 new mode 100755 index a5baa97..e105bf6 --- a/setup.py +++ b/setup.py @@ -1,25 +1,90 @@ -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): + if dest is None: + dest = src + + ret = [] + for dirpath, dirnames, filenames in os.walk(src): + current_dest = os.path.relpath(dirpath, src) + if current_dest == '.': + current_dest = dest + else: + current_dest = os.path.join(dest, current_dest) + + current_sources = map(lambda p: os.path.join(dirpath, p), filenames) + + 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 @@ -27,9 +92,9 @@ setup_kwargs['cmdclass'] = {} 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']), - ('web_assets', glob.glob('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 @@ -37,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 # @@ -52,25 +138,31 @@ except AttributeError: try: pil_include = os.environ['PIL_INCLUDE_DIR'].split(os.pathsep) except: - pil_include = [] + pil_include = [ os.path.join(get_python_inc(plat_specific=1), 'Imaging') ] + if not os.path.exists(pil_include[0]): + pil_include = [ ] + # 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 @@ -79,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: @@ -92,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": @@ -101,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"