0

Merge branch 'master' into py-package

Conflicts:
	overviewer_core/data/config.js
	overviewer_core/data/web_assets/functions.js
	overviewer_core/data/web_assets/style.css
	setup.py
	web_assets/overviewer.css
	web_assets/style.css
This commit is contained in:
Aaron Griffith
2011-05-10 20:19:10 -04:00
32 changed files with 2373 additions and 859 deletions

View File

@@ -114,13 +114,15 @@ 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, 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, 92])
transparent_blocks = set([ 0, 6, 8, 9, 18, 20, 26, 27, 28, 30, 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])
# 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,
23, 24, 25, 35, 41, 42, 43, 44, 45, 46, 47, 48, 49, 53, 54, 56, 57, 58, 60,
61, 62, 64, 65, 66, 67, 71, 73, 74, 78, 79, 80, 81, 82, 84, 86, 87, 88, 89, 91, 92])
61, 62, 67, 73, 74, 78, 79, 80, 81, 82, 84, 86, 87, 88, 89, 91])
# This set holds block ids that are fluid blocks
fluid_blocks = set([8,9,10,11])
@@ -284,7 +286,14 @@ class ChunkRenderer(object):
self._load_up_right()
return self._up_right_blocks
up_right_blocks = property(_load_up_right_blocks)
def _load_up_right_skylight(self):
"""Loads and returns lower-right skylight array"""
if not hasattr(self, "_up_right_skylight"):
self._load_up_right()
return self._up_right_skylight
up_right_skylight = property(_load_up_right_skylight)
def _load_up_left(self):
"""Loads and sets data from upper-left chunk"""
chunk_path = self.world.get_region_path(self.chunkX, self.chunkY - 1)
@@ -305,6 +314,13 @@ class ChunkRenderer(object):
return self._up_left_blocks
up_left_blocks = property(_load_up_left_blocks)
def _load_up_left_skylight(self):
"""Loads and returns lower-right skylight array"""
if not hasattr(self, "_up_left_skylight"):
self._load_up_left()
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
@@ -449,7 +465,7 @@ def generate_facemasks():
return (top, left, right)
facemasks = generate_facemasks()
black_color = Image.new("RGB", (24,24), (0,0,0))
red_color = Image.new("RGB", (24,24), (229,36,38))
white_color = Image.new("RGB", (24,24), (255,255,255))
# Render 128 different color images for color coded depth blending in cave mode
def generate_depthcolors():
@@ -458,8 +474,10 @@ def generate_depthcolors():
g = 0
b = 0
for z in range(128):
img = Image.new("RGB", (24,24), (r,g,b))
depth_colors.append(img)
depth_colors.append(r)
depth_colors.append(g)
depth_colors.append(b)
if z < 32:
g += 7
elif z < 64:

View File

@@ -4,7 +4,8 @@ import os.path
import logging
class OptionsResults(object):
pass
def get(self, *args):
return self.__dict__.get(*args)
class ConfigOptionParser(object):
def __init__(self, **kwargs):

View File

@@ -1,55 +0,0 @@
var config = {
fileExt: '{imgformat}',
tileSize: 384,
defaultZoom: 2,
maxZoom: {maxzoom},
// center on this point, in world coordinates, ex:
//center: [0,0,0],
center: {spawn_coords},
cacheMinutes: 0, // Change this to have browsers automatically request new images every x minutes
bg_color: '#1A1A1A',
debug: false
};
/* signGroups -- A list of signpost groups. A signpost can fall into zero, one, or more than one
* group. See below for some examples.
*
* Required:
* label : string. Displayed in the drop down menu control.
* match : function. Applied to each marker (from markers.js). It is returns true if the marker
* Should be part of the group.
*
* Optional:
* checked : boolean. Set to true to have the group visible by default
* icon : string. Used to specify an icon url.
*/
var signGroups = [
// {label: "'To'", checked: false, match: function(s) {return s.msg.match(/to/)}},
// {label: "Storage", match: function(s) {return s.msg.match(/storage/i) || s.msg.match(/dirt/i) || s.msg.match(/sand/)}},
// {label: "Below Sealevel", match: function(s) { return s.y<64;}},
// {label: "Info", match: function(s) { return s.msg.match("\\[info\\]");}, icon:"http://google-maps-icons.googlecode.com/files/info.png"},
{label: "All", match: function(s) {return true}},
];
/* mapTypeData -- a list of alternate map renderings available. At least one rendering must be
* listed. When more than one are provided, controls to switch between them are provided, with
* the first one being the default.
*
* Required:
* label : string. Displayed on the control.
* path : string. Location of the rendered tiles.
* Optional:
* base : string. Base of the url path for tile locations, useful for serving tiles from a different server than the js/html server.
var mapTypeData=[
{'label': 'Unlit', 'path': 'tiles'},
// {'label': 'Day', 'path': 'lighting/tiles'},
// {'label': 'Night', 'path': 'night/tiles'},
// {'label': 'Spawn', 'path': 'spawn/tiles', 'base': 'http://example.cdn.amazon.com/'}
];
*/
var mapTypeData = {maptypedata};

View File

@@ -0,0 +1,157 @@
var overviewerConfig = {
/**
* These are things that will probably not need to be changed by the user,
* but are there because otherwise changing them is a giant PITA.
*/
'CONST': {
/**
* Height and width of the tiles in pixels (I think).
*/
'tileSize': 384,
/**
* Various images used for markers and stuff.
*/
'image': {
'defaultMarker': 'signpost.png',
'signMarker': 'signpost_icon.png',
'compass': 'compass.png',
'spawnMarker': 'http://google-maps-icons.googlecode.com/files/home.png',
'queryMarker': 'http://google-maps-icons.googlecode.com/files/regroup.png'
},
'mapDivId': 'mcmap',
'regionStrokeWeight': 2
},
/**
* General map settings.
*/
'map': {
/**
* Control the visibility of various controls.
*/
'controls': {
/**
* Pan control is the hand with the arrows around it in the upper left.
*/
'pan': true,
/**
* Zoom control is the zoom slider bar in the upper left.
*/
'zoom': true,
/**
* Spawn control is the "Spawn" button that centers the map on spawn.
*/
'spawn': true,
/**
* The compass in the upper right.
*/
'compass': true,
/**
* The mapType control is the slider for selecting different map types.
*/
'mapType': true,
/**
* The small box at the bottom that displays the link to the current map view.
*/
'link': true
},
/**
* The zoom level when the page is loaded without a specific zoom setting
*/
'defaultZoom': 0,
/**
* This controls how far you can zoom out.
*/
'minZoom': {minzoom},
/**
* This controls how close you can zoom in.
*/
'maxZoom': {maxzoom},
/**
* Center on this point, in world coordinates. Should be an array, ex:
* [0,0,0]
*/
'center': {spawn_coords},
/**
* Set this to tell browsers how long they should cache tiles in minutes.
*/
'cacheMinutes': 0,
/**
* Set to true to turn on debug mode, which adds a grid to the map along
* with co-ordinates and a bunch of console output.
*/
'debug': false,
},
/**
* Group definitions for objects that are partially selectable (signs and
* regions).
*/
'objectGroups': {
/* signs -- A list of signpost groups. A signpost can fall into zero,
* one, or more than one group. See below for some examples.
*
* Required:
* label : string. Displayed in the drop down menu control.
* match : function. Applied to each marker (from markers.js). It
* is returns true if the marker should be part
* of the group.
*
* Optional:
* checked : boolean. Set to true to have the group visible by default
* icon : string. Used to specify an icon url.
*/
'signs': [
//{label: "'To'", checked: false, match: function(s) {return s.msg.match(/to/)}},
//{label: "Storage", match: function(s) {return s.msg.match(/storage/i) || s.msg.match(/dirt/i) || s.msg.match(/sand/)}},
//{label: "Below Sealevel", match: function(s) { return s.y<64;}},
//{label: "Info", match: function(s) { return s.msg.match("\\[info\\]");}, icon:"http://google-maps-icons.googlecode.com/files/info.png"},
{'label':'All', 'match':function(sign){return true;}}
],
/* regions -- A list of region groups. A region can fall into zero,
* one, or more than one group. See below for some examples.
* Regions have been designed to work with the WorldGuard Overviewer
* Region importer at @link http://goo.gl/dc0tV but your
* host must support php in order to run WG2OvR. You can also continue
* to use any other region format.
*
* Required:
* label : string. Displayed in the drop down menu control.
* clickable : boolean. Will determine if we should generate an
* experimental info window that shows details
* about the clicked region.
* NOTE: if a region (as defined in region.js)
* does not have a label, this will default to
* false.
* match : function. Applied to each region (from region.js). It
* returns true if the region should be part of
* the group.
*
* Optional:
* checked : boolean. Set to true to have the group visible by default
*/
'regions': [
//{'label':'All','clickable':true,'match':function(region){return true;}}
]
},
/* mapTypes -- a list of alternate map renderings available. At least one
* rendering must be listed. When more than one are provided, controls to
* switch between them are provided, with the first one being the default.
*
* Required:
* label : string. Displayed on the control.
* path : string. Location of the rendered tiles.
* Optional:
* base : string. Base of the url path for tile locations, useful
* for serving tiles from a different server than
* the js/html server.
* imgformat : string. File extension used for these tiles. Defaults to png.
* overlay : bool. If true, this tile set will be treated like an overlay
* Example:
* 'mapTypes': [
* {'label': 'Day', 'path': 'lighting/tiles'},
* {'label': 'Night', 'path': 'night/tiles', 'imgformat': 'jpg'},
* {'label': 'Spawn', 'path': 'spawn/tiles', 'base': 'http://example.cdn.amazon.com/'},
* {'label': 'Overlay', 'path': 'overlay/tiles', 'overlay': true}
* ]
*/
'mapTypes': {maptypedata}
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 672 B

View File

@@ -1,468 +0,0 @@
var markerCollection = {}; // holds groups of markers
var map;
var markersInit = false;
var regionsInit = false;
var prevInfoWindow = null;
function prepareSignMarker(marker, item) {
var c = "<div class=\"infoWindow\"><img src=\"signpost.png\" /><p>" + item.msg.replace(/\n/g,"<br/>") + "</p></div>";
var infowindow = new google.maps.InfoWindow({content: c
});
google.maps.event.addListener(marker, 'click', function() {
if (prevInfoWindow)
prevInfoWindow.close()
infowindow.open(map,marker);
prevInfoWindow = infowindow
});
}
function drawMapControls() {
// viewstate link
var viewStateDiv = document.createElement('DIV');
//<div id="link" style="border:1px solid black;background-color:white;color:black;position:absolute;top:5px;right:5px"></div>
viewStateDiv.id="link";
map.controls[google.maps.ControlPosition.BOTTOM_RIGHT].push(viewStateDiv);
// compass rose, in the top right corner
var compassDiv = document.createElement('DIV');
compassDiv.style.padding = '5px';
var compassImg = document.createElement('IMG');
compassImg.src="compass.png";
compassDiv.appendChild(compassImg);
compassDiv.index = 0;
map.controls[google.maps.ControlPosition.TOP_RIGHT].push(compassDiv);
if (signGroups.length > 0) {
// signpost display control
//
var signControl = document.createElement("DIV");
signControl.id = "signControl"; // let's let a style sheet do most of the styling here
var controlBorder = document.createElement("DIV");
controlBorder.id="top";
signControl.appendChild(controlBorder);
var controlText = document.createElement("DIV");
controlBorder.appendChild(controlText);
controlText.innerHTML = "Signposts";
var dropdownDiv = document.createElement("DIV");
$(controlText).click(function() {
$(dropdownDiv).toggle();
});
dropdownDiv.id="dropDown";
signControl.appendChild(dropdownDiv);
dropdownDiv.innerHTML="";
map.controls[google.maps.ControlPosition.TOP_RIGHT].push(signControl);
var hasSignGroup = false;
for (idx in signGroups) {
var item = signGroups[idx];
//console.log(item);
label = item.label;
hasSignGroup = true;
var d = document.createElement("div");
var n = document.createElement("input");
n.type="checkbox";
$(n).data("label",label);
jQuery(n).click(function(e) {
var t = $(e.target);
jQuery.each(markerCollection[t.data("label")], function(i,elem) {elem.setVisible(e.target.checked);});
});
if (item.checked) {
n.checked = true;
jQuery.each(markerCollection[label], function(i,elem) {elem.setVisible(n.checked);});
}
dropdownDiv.appendChild(d);
d.appendChild(n)
var textNode = document.createElement("text");
textNode.innerHTML = label + "<br/>";
d.appendChild(textNode);
}
}
}
function initRegions() {
if (regionsInit) { return; }
regionsInit = true;
for (i in regionData) {
var region = regionData[i];
var converted = new google.maps.MVCArray();
for (j in region.path) {
var point = region.path[j];
converted.push(fromWorldToLatLng(point.x, point.y, point.z));
}
if (region.closed) {
new google.maps.Polygon({clickable: false,
geodesic: false,
map: map,
strokeColor: region.color,
strokeOpacity: region.opacity,
strokeWeight: 2,
fillColor: region.color,
fillOpacity: region.opacity * 0.25,
zIndex: i,
paths: converted
});
} else {
new google.maps.Polyline({clickable: false,
geodesic: false,
map: map,
strokeColor: region.color,
strokeOpacity: region.opacity,
strokeWeight: 2,
zIndex: i,
path: converted
});
}
}
}
function initMarkers() {
if (markersInit) { return; }
markersInit = true;
// first, give all collections an empty array to work with
for (i in signGroups) {
markerCollection[signGroups[i].label] = [];
}
for (i in markerData) {
var item = markerData[i];
// a default:
var iconURL = '';
if (item.type == 'spawn') {
// don't filter spawn, always display
iconURL = 'http://google-maps-icons.googlecode.com/files/home.png';
var converted = fromWorldToLatLng(item.x, item.y, item.z);
var marker = new google.maps.Marker({position: converted,
map: map,
title: jQuery.trim(item.msg),
icon: iconURL
});
continue;
}
var matched = false;
for (idx in signGroups) {
var signGroup = signGroups[idx];
var testfunc = signGroup.match;
var label = signGroup.label;
if (testfunc(item)) {
matched = true;
if (item.type == 'sign') { iconURL = 'signpost_icon.png';}
//console.log(signGroup.icon);
if (signGroup.icon) { iconURL = signGroup.icon;
}
var converted = fromWorldToLatLng(item.x, item.y, item.z);
var marker = new google.maps.Marker({position: converted,
map: map,
title: jQuery.trim(item.msg),
icon: iconURL,
visible: false
});
markerCollection[label].push(marker);
if (item.type == 'sign') {
prepareSignMarker(marker, item);
}
}
}
if (!matched) {
// is this signpost doesn't match any of the groups in config.js, add it automatically to the "__others__" group
if (item.type == 'sign') { iconURL = 'signpost_icon.png';}
var converted = fromWorldToLatLng(item.x, item.y, item.z);
var marker = new google.maps.Marker({position: converted,
map: map,
title: jQuery.trim(item.msg),
icon: iconURL,
visible: false
});
if (markerCollection["__others__"]) {
markerCollection["__others__"].push(marker);
} else {
markerCollection["__others__"] = [marker];
}
if (item.type == 'sign') {
prepareSignMarker(marker, item);
}
}
}
}
function makeLink() {
var a=location.href.substring(0,location.href.lastIndexOf(location.search))
+ "?lat=" + map.getCenter().lat().toFixed(6)
+ "&lng=" + map.getCenter().lng().toFixed(6)
+ "&zoom=" + map.getZoom();
document.getElementById("link").innerHTML = a;
}
function initialize() {
var query = location.search.substring(1);
var defaultCenter = fromWorldToLatLng(config.center[0],
config.center[1],
config.center[2]);
var lat = defaultCenter.lat();
var lng = defaultCenter.lng();
var zoom = config.defaultZoom;
var pairs = query.split("&");
for (var i=0; i<pairs.length; i++) {
// break each pair at the first "=" to obtain the argname and value
var pos = pairs[i].indexOf("=");
var argname = pairs[i].substring(0,pos).toLowerCase();
var value = pairs[i].substring(pos+1).toLowerCase();
// process each possible argname
if (argname == "lat") {lat = parseFloat(value);}
if (argname == "lng") {lng = parseFloat(value);}
if (argname == "zoom") {zoom = parseInt(value);}
}
var mapTyepControlToggle = false
if (mapTypeIds.length > 1) {
mapTyepControlToggle = true
}
var mapOptions = {
zoom: zoom,
center: new google.maps.LatLng(lat, lng),
navigationControl: true,
scaleControl: false,
mapTypeControl: mapTyepControlToggle,
mapTypeControlOptions: {
mapTypeIds: mapTypeIds
},
mapTypeId: mapTypeIdDefault,
streetViewControl: false,
backgroundColor: config.bg_color,
};
map = new google.maps.Map(document.getElementById('mcmap'), mapOptions);
if(config.debug) {
map.overlayMapTypes.insertAt(0, new CoordMapType(new google.maps.Size(config.tileSize, config.tileSize)));
google.maps.event.addListener(map, 'click', function(event) {
//console.log("latLng; " + event.latLng.lat() + ", " + event.latLng.lng());
var pnt = map.getProjection().fromLatLngToPoint(event.latLng);
//console.log("point: " + pnt);
var pxx = pnt.x * config.tileSize * Math.pow(2, config.maxZoom);
var pxy = pnt.y * config.tileSize * Math.pow(2, config.maxZoom);
//console.log("pixel: " + pxx + ", " + pxy);
});
}
// Now attach the coordinate map type to the map's registry
for (idx in MCMapType) {
map.mapTypes.set('mcmap' + MCMapType[idx].name, MCMapType[idx]);
}
// We can now set the map to use the 'coordinate' map type
map.setMapTypeId(mapTypeIdDefault);
// initialize the markers and regions
initMarkers();
initRegions();
drawMapControls();
//makeLink();
// Make the link again whenever the map changes
google.maps.event.addListener(map, 'zoom_changed', function() {
makeLink();
});
google.maps.event.addListener(map, 'center_changed', function() {
makeLink();
});
}
// our custom projection maps Latitude to Y, and Longitude to X as normal,
// but it maps the range [0.0, 1.0] to [0, tileSize] in both directions
// so it is easier to position markers, etc. based on their position
// (find their position in the lowest-zoom image, and divide by tileSize)
function MCMapProjection() {
this.inverseTileSize = 1.0 / config.tileSize;
}
MCMapProjection.prototype.fromLatLngToPoint = function(latLng) {
var x = latLng.lng() * config.tileSize;
var y = latLng.lat() * config.tileSize;
return new google.maps.Point(x, y);
};
MCMapProjection.prototype.fromPointToLatLng = function(point) {
var lng = point.x * this.inverseTileSize;
var lat = point.y * this.inverseTileSize;
return new google.maps.LatLng(lat, lng);
};
// helper to get map LatLng from world coordinates
// takes arguments in X, Y, Z order
// (arguments are *out of order*, because within the function we use
// the axes like the rest of Minecraft Overviewer -- with the Z and Y
// flipped from normal minecraft usage.)
function fromWorldToLatLng(x, z, y)
{
// the width and height of all the highest-zoom tiles combined, inverted
var perPixel = 1.0 / (config.tileSize * Math.pow(2, config.maxZoom));
// This information about where the center column is may change with a different
// drawing implementation -- check it again after any drawing overhauls!
// 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, config.maxZoom + 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
// each block on X axis adds 12px to x and subtracts 6px from y
lng += 12 * x * perPixel;
lat -= 6 * x * perPixel;
// each block on Y axis adds 12px to x and adds 6px to y
lng += 12 * y * perPixel;
lat += 6 * y * perPixel;
// each block down along Z adds 12px to y
lat += 12 * (128 - z) * perPixel;
// add on 12 px to the X coordinate to center our point
lng += 12 * perPixel;
return new google.maps.LatLng(lat, lng);
}
function getTileUrlGenerator(path, path_base) {
return function(tile, zoom) {
var url = path;
var url_base = ( path_base ? path_base : '' );
if(tile.x < 0 || tile.x >= Math.pow(2, zoom) || tile.y < 0 || tile.y >= Math.pow(2, zoom)) {
url += '/blank';
} else if(zoom == 0) {
url += '/base';
} else {
for(var z = zoom - 1; z >= 0; --z) {
var x = Math.floor(tile.x / Math.pow(2, z)) % 2;
var y = Math.floor(tile.y / Math.pow(2, z)) % 2;
url += '/' + (x + 2 * y);
}
}
url = url + '.' + config.fileExt;
if(config.cacheMinutes > 0) {
var d = new Date();
url += '?c=' + Math.floor(d.getTime() / (1000 * 60 * config.cacheMinutes));
}
return(url_base + url);
}
}
var MCMapOptions = new Array;
var MCMapType = new Array;
var mapTypeIdDefault = null;
var mapTypeIds = [];
for (idx in mapTypeData) {
var view = mapTypeData[idx];
MCMapOptions[view.label] = {
getTileUrl: getTileUrlGenerator(view.path, view.base),
tileSize: new google.maps.Size(config.tileSize, config.tileSize),
maxZoom: config.maxZoom,
minZoom: 0,
isPng: !(config.fileExt.match(/^png$/i) == null)
};
MCMapType[view.label] = new google.maps.ImageMapType(MCMapOptions[view.label]);
MCMapType[view.label].name = view.label;
MCMapType[view.label].alt = "Minecraft " + view.label + " Map";
MCMapType[view.label].projection = new MCMapProjection();
if (mapTypeIdDefault == null) {
mapTypeIdDefault = 'mcmap' + view.label;
}
mapTypeIds.push('mcmap' + view.label);
}
function CoordMapType() {
}
function CoordMapType(tileSize) {
this.tileSize = tileSize;
}
CoordMapType.prototype.getTile = function(coord, zoom, ownerDocument) {
var div = ownerDocument.createElement('DIV');
div.innerHTML = "(" + coord.x + ", " + coord.y + ", " + zoom + ")";
div.innerHTML += "<br />";
div.innerHTML += MCMapOptions.getTileUrl(coord, zoom);
div.style.width = this.tileSize.width + 'px';
div.style.height = this.tileSize.height + 'px';
div.style.fontSize = '10';
div.style.borderStyle = 'solid';
div.style.borderWidth = '1px';
div.style.borderColor = '#AAAAAA';
return div;
};

View File

@@ -2,18 +2,16 @@
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<link rel="stylesheet" href="style.css" type="text/css" />
<script type="text/javascript" src="config.js"></script>
<script type="text/javascript"
src="http://maps.google.com/maps/api/js?sensor=false">
</script>
<link rel="stylesheet" href="overviewer.css" type="text/css" />
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.3/jquery.min.js"></script>
<script type="text/javascript" src="functions.js"></script>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript" src="overviewerConfig.js"></script>
<script type="text/javascript" src="overviewer.js"></script>
<script type="text/javascript" src="markers.js"></script>
<script type="text/javascript" src="regions.js"></script>
</head>
<!-- Generated at: {time} -->
<body onload="initialize()">
<div id="mcmap" style="width:100%; height:100%"></div>
<body onload="overviewer.util.initialize()">
<div id="mcmap"></div>
</body>
</html>

View File

@@ -1,6 +1,18 @@
html { height: 100% }
body { height: 100%; margin: 0px; padding: 0px ; background-color: #000; }
#mcmap { height: 100% }
html {
height: 100%;
}
body {
height: 100%;
margin: 0px;
padding: 0px;
background-color: #000;
}
#mcmap {
width: 100%;
height: 100%;
}
.infoWindow {
height: 100px;
@@ -17,13 +29,13 @@ body { height: 100%; margin: 0px; padding: 0px ; background-color: #000; }
font-family: monospace;
}
#signControl {
#customControl {
padding: 5px;
height: 15px;
font-family: Arial, sans-serif;
}
#signControl > div#top {
#customControl > div#top {
background-color: #fff;
border: 2px solid #000;
text-align: center;
@@ -33,7 +45,14 @@ body { height: 100%; margin: 0px; padding: 0px ; background-color: #000; }
cursor: pointer;
}
#signControl > div#dropDown {
#customControl > div#dropDown {
border: 1px solid #000;
font-size: 12px;
background-color: #fff;
display: none;
}
#customControl > div#button {
border: 1px solid #000;
font-size: 12px;
background-color: #fff;

View File

@@ -0,0 +1,903 @@
var overviewer = {
/**
* This holds the map, probably the most important var in this file
*/
'map': null,
/**
* These are collections of data used in various places
*/
'collections': {
/**
* A list of lists of raw marker data objects, this will allow for an
* arbitrary number of marker data sources. This replaces the old
* markerData var from markers.js. Now you can add markers by including
* a file with:
* overviewer.collections.markerDatas.push([<your list of markers>]);
*/
'markerDatas': [],
/**
* The actual Marker objects are stored here.
*/
'markers': {},
/**
* Same as markerDatas, list of lists of raw region objects.
*/
'regionDatas': [],
/**
* The actual Region objects.
*/
'regions': {},
/**
* Overlay mapTypes (like Spawn) will go in here.
*/
'overlays': [],
/**
* MapTypes that aren't overlays will end up in here.
*/
'mapTypes': {},
/**
* The mapType names are in here.
*/
'mapTypeIds': [],
/**
* This is the current infoWindow object, we keep track of it so that
* there is only one open at a time.
*/
'infoWindow': null
},
'util': {
/**
* General initialization function, called when the page is loaded.
* Probably shouldn't need changing unless some very different kind of new
* feature gets added.
*/
'initialize': function() {
overviewer.util.initializeClassPrototypes();
overviewer.util.initializeMapTypes();
overviewer.util.initializeMap();
overviewer.util.initializeMarkers();
overviewer.util.initializeRegions();
overviewer.util.createMapControls();
},
/**
* This adds some methods to these classes because Javascript is stupid
* and this seems like the best way to avoid re-creating the same methods
* on each object at object creation time.
*/
'initializeClassPrototypes': function() {
overviewer.classes.MapProjection.prototype.fromLatLngToPoint = function(latLng) {
var x = latLng.lng() * overviewerConfig.CONST.tileSize;
var y = latLng.lat() * overviewerConfig.CONST.tileSize;
return new google.maps.Point(x, y);
};
overviewer.classes.MapProjection.prototype.fromPointToLatLng = function(point) {
var lng = point.x * this.inverseTileSize;
var lat = point.y * this.inverseTileSize;
return new google.maps.LatLng(lat, lng);
};
overviewer.classes.CoordMapType.prototype.getTile = function(coord, zoom, ownerDocument) {
var div = ownerDocument.createElement('DIV');
div.innerHTML = '(' + coord.x + ', ' + coord.y + ', ' + zoom +
')' + '<br />';
//TODO: figure out how to get the current mapType, I think this
//will add the maptile url to the grid thing once it works
//div.innerHTML += overviewer.collections.mapTypes[0].getTileUrl(coord, zoom);
//this should probably just have a css class
div.style.width = this.tileSize.width + 'px';
div.style.height = this.tileSize.height + 'px';
div.style.fontSize = '10px';
div.style.borderStyle = 'solid';
div.style.borderWidth = '1px';
div.style.borderColor = '#AAAAAA';
return div;
};
},
/**
* 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
*/
'initializeMapTypes': function() {
var mapOptions = {};
for (i in overviewerConfig.mapTypes) {
var view = overviewerConfig.mapTypes[i];
var imageFormat = view.imgformat ? view.imgformat : 'png';
mapOptions[view.label] = {
'getTileUrl': overviewer.gmap.getTileUrlGenerator(view.path,
view.base, imageFormat),
'tileSize': new google.maps.Size(
overviewerConfig.CONST.tileSize,
overviewerConfig.CONST.tileSize),
'maxZoom': overviewerConfig.map.maxZoom,
'minZoom': overviewerConfig.map.minZoom,
'isPng': imageFormat.toLowerCase() == 'png'
}
overviewer.collections.mapTypes[view.label] = new google.maps.ImageMapType(
mapOptions[view.label]);
overviewer.collections.mapTypes[view.label].name = view.label;
overviewer.collections.mapTypes[view.label].alt = 'Minecraft ' +
view.label + ' Map';
overviewer.collections.mapTypes[view.label].projection =
new overviewer.classes.MapProjection();
if (view.overlay) {
overviewer.collections.overlays.push(
overviewer.collections.mapTypes[view.label]);
} else {
overviewer.collections.mapTypeIds.push(
overviewerConfig.CONST.mapDivId + view.label);
}
}
},
/**
* This is where the magic happens. We setup the map with all it's
* options. The query string is also parsed here so we can know if
* we should be looking at a particular point on the map or just use
* the default view.
*/
'initializeMap': function() {
var defaultCenter = overviewer.util.fromWorldToLatLng(
overviewerConfig.map.center[0], overviewerConfig.map.center[1],
overviewerConfig.map.center[2]);
var lat = defaultCenter.lat();
var lng = defaultCenter.lng();
var zoom = overviewerConfig.map.defaultZoom;
var mapcenter;
queryParams = overviewer.util.parseQueryString();
if (queryParams.lat) {
lat = parseFloat(queryParams.lat);
}
if (queryParams.lng) {
lng = parseFloat(queryParams.lng);
}
if (queryParams.zoom) {
if (queryParams.zoom == 'max') {
zoom = overviewerConfig.map.maxZoom;
} else if (queryParams.zoom == 'min') {
zoom = overviewerConfig.map.minZoom;
} else {
zoom = parseInt(queryParams.zoom);
if (zoom < 0 && zoom + overviewerConfig.map.maxZoom >= 0) {
//if zoom is negative, try to treat as "zoom out from max zoom"
zoom += overviewerConfig.map.maxZoom;
} else {
//fall back to default zoom
zoom = overviewerConfig.map.defaultZoom;
}
}
}
if (queryParams.x && queryParams.y && queryParams.z) {
mapcenter = overviewer.util.fromWorldToLatLng(queryParams.x,
queryParams.y, queryParams.z);
// Add a market indicating the user-supplied position
overviewer.collections.markerDatas.push([{
'msg': 'Coordinates ' + queryParams.x + ', ' +
queryParams.y + ', ' + queryParams.z,
'y': parseFloat(queryParams.y),
'x': parseFloat(queryParams.x),
'z': parseFloat(queryParams.z),
'type': 'querypos'}]);
} else {
mapcenter = new google.maps.LatLng(lat, lng);
}
var mapOptions = {
zoom: zoom,
center: mapcenter,
panControl: overviewerConfig.map.controls.pan,
scaleControl: false,
mapTypeControl: overviewerConfig.map.controls.mapType &&
overviewer.collections.mapTypeIds.length > 1,
mapTypeControlOptions: {
mapTypeIds: overviewer.collections.mapTypeIds
},
mapTypeId: overviewer.util.getDefaultMapTypeId(),
streetViewControl: false,
zoomControl: overviewerConfig.map.controls.zoom,
backgroundColor: overviewer.util.getMapTypeBackgroundColor(
overviewer.util.getDefaultMapTypeId())
};
overviewer.map = new google.maps.Map(document.getElementById(
overviewerConfig.CONST.mapDivId), mapOptions);
if (overviewerConfig.map.debug) {
overviewer.map.overlayMapTypes.insertAt(0,
new overviewer.classes.CoordMapType(new google.maps.Size(
overviewerConfig.CONST.tileSize,
overviewerConfig.CONST.tileSize)));
google.maps.event.addListener(overviewer.map, 'click', function(event) {
overviewer.util.debug('latLng: (' + event.latLng.lat() +
', ' + event.latLng.lng() + ')');
var pnt = overviewer.map.getProjection().fromLatLngToPoint(event.latLng);
overviewer.util.debug('point: ' + pnt);
var pxx = pnt.x * overviewerConfig.CONST.tileSize *
Math.pow(2, overviewerConfig.map.maxZoom);
var pxy = pnt.y * overviewerConfig.CONST.tileSize *
Math.pow(2, overviewerConfig.map.maxZoom);
overviewer.util.debug('pixel: (' + pxx + ', ' + pxy + ')');
});
}
// Now attach the coordinate map type to the map's registry
for (i in overviewer.collections.mapTypes) {
overviewer.map.mapTypes.set(overviewerConfig.CONST.mapDivId +
overviewer.collections.mapTypes[i].name,
overviewer.collections.mapTypes[i]);
}
// Make the link again whenever the map changes
google.maps.event.addListener(overviewer.map, 'maptypeid_changed', function() {
$('#'+overviewerConfig.CONST.mapDivId).css(
'background-color', overviewer.util.getMapTypeBackgroundColor(
overviewer.map.getMapTypeId()));
});
// We can now set the map to use the 'coordinate' map type
overviewer.map.setMapTypeId(overviewer.util.getDefaultMapTypeId());
},
/**
* Read through overviewer.collections.markerDatas and create Marker
* objects and stick them in overviewer.collections.markers . This
* should probably be done differently at some point so that we can
* support markers that change position more easily.
*/
'initializeMarkers': function() {
//first, give all collections an empty array to work with
for (i in overviewerConfig.objectGroups.signs) {
overviewer.util.debug('Found sign group: ' +
overviewerConfig.objectGroups.signs[i].label);
overviewer.collections.markers[
overviewerConfig.objectGroups.signs[i].label] = [];
}
for (i in overviewer.collections.markerDatas) {
var markerData = overviewer.collections.markerDatas[i];
for (j in markerData) {
var item = markerData[j];
// a default:
var iconURL = '';
if (item.type == 'spawn') {
// don't filter spawn, always display
var marker = new google.maps.Marker({
'position': overviewer.util.fromWorldToLatLng(item.x,
item.y, item.z),
'map': overviewer.map,
'title': jQuery.trim(item.msg),
'icon': overviewerConfig.CONST.image.spawnMarker
});
continue;
}
if (item.type == 'querypos') {
// Set on page load if MC x/y/z coords are given in the
// query string
var marker = new google.maps.Marker({
'position': overviewer.util.fromWorldToLatLng(item.x,
item.y, item.z),
'map': overviewer.map,
'title': jQuery.trim(item.msg),
'icon': overviewerConfig.CONST.image.queryMarker
});
continue;
}
var matched = false;
for (j in overviewerConfig.objectGroups.signs) {
var signGroup = overviewerConfig.objectGroups.signs[j];
var label = signGroup.label;
if (signGroup.match(item)) {
matched = true;
// can add custom types of images for externally defined
// item types, like 'command' here.
if (item.type == 'sign') {
iconURL = overviewerConfig.CONST.image.signMarker;
}
overviewer.util.debug('Sign icon: ' + signGroup.icon);
if (signGroup.icon) {
iconURL = signGroup.icon;
}
var marker = new google.maps.Marker({
'position': overviewer.util.fromWorldToLatLng(item.x,
item.y, item.z),
'map': overviewer.map,
'title': jQuery.trim(item.msg),
'icon': iconURL,
'visible': false
});
overviewer.util.debug(label);
overviewer.collections.markers[label].push(marker);
if (item.type == 'sign') {
overviewer.util.createMarkerInfoWindow(marker);
}
}
}
if (!matched) {
// is this signpost doesn't match any of the groups in
// config.js, add it automatically to the "__others__" group
if (item.type == 'sign') {
iconURL = overviewerConfig.CONST.image.signMarker;
}
var marker = new google.maps.Marker({
'position': overviewer.util.fromWorldToLatLng(item.x,
item.y, item.z),
'map': overviewer.map,
'title': jQuery.trim(item.msg),
'icon': iconURL,
'visible': false
});
if (overviewer.collections.markers['__others__']) {
overviewer.collections.markers['__others__'].push(marker);
} else {
overviewer.collections.markers['__others__'] = [marker];
}
if (item.type == 'sign') {
overviewer.util.createMarkerInfoWindow(marker, item);
}
}
}
}
},
/**
* Same as initializeMarkers() for the most part.
*/
'initializeRegions': function() {
for (i in overviewerConfig.objectGroups.regions) {
overviewer.collections.regions[overviewerConfig.objectGroups.regions[i].label] = [];
}
for (i in overviewer.collections.regionDatas) {
var regionData = overviewer.collections.regionDatas[i];
for (j in regionData) {
var region = regionData[j];
// pull all the points out of the regions file.
var converted = new google.maps.MVCArray();
for (k in region.path) {
var point = region.path[k];
converted.push(overviewer.util.fromWorldToLatLng(
point.x, point.y, point.z));
}
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';
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;
} else {
var strokeOpacity = region.strokeOpacity;
var fillOpacity = region.fillOpacity;
}
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);
}
}
}
}
},
/**
* Change the map's div's background color according to the mapType's bg_color setting
*
* @param string mapTypeId
* @return string
*/
'getMapTypeBackgroundColor': function(mapTypeId) {
for(i in overviewerConfig.mapTypes) {
if( overviewerConfig.CONST.mapDivId +
overviewerConfig.mapTypes[i].label == mapTypeId ) {
overviewer.util.debug('Found background color for: ' +
overviewerConfig.mapTypes[i].bg_color);
return overviewerConfig.mapTypes[i].bg_color;
}
}
},
/**
* Gee, I wonder what this does.
*
* @param string msg
*/
'debug': function(msg) {
if (overviewerConfig.map.debug) {
console.log(msg);
}
},
/**
* Simple helper function to split the query string into key/value
* pairs. Doesn't do any type conversion but both are lowercase'd.
*
* @return Object
*/
'parseQueryString': function() {
var results = {};
var queryString = location.search.substring(1);
var pairs = queryString.split('&');
for (i in pairs) {
var pos = pairs[i].indexOf('=');
var key = pairs[i].substring(0,pos).toLowerCase();
var value = pairs[i].substring(pos+1).toLowerCase();
overviewer.util.debug( 'Found GET paramter: ' + key + ' = ' + value);
results[key] = value;
}
return results;
},
/**
* Set the link (at the bottom of the screen) to the current view.
* TODO: make this preserve the mapTypeId as well
*/
'setViewUrl': function() {
var displayZoom = overviewer.map.getZoom();
if (displayZoom == overviewerConfig.map.maxZoom) {
displayZoom = 'max';
} else {
displayZoom -= overviewerConfig.map.maxZoom;
}
var point;
var point = overviewer.util.fromLatLngToWorld(
overviewer.map.getCenter().lat(), overviewer.map.getCenter().lng());
var viewUrl = location.href.substring(0, location.href.lastIndexOf(
location.search))
+ '?x=' + Math.floor(point.x)
+ '&y=' + Math.floor(point.y)
+ '&z=' + Math.floor(point.z)
+ '&zoom=' + displayZoom;
document.getElementById('link').innerHTML = viewUrl;
},
'getDefaultMapTypeId': function() {
return overviewer.collections.mapTypeIds[0];
},
/**
* helper to get map LatLng from world coordinates takes arguments in
* X, Y, Z order (arguments are *out of order*, because within the
* function we use the axes like the rest of Minecraft Overviewer --
* with the Z and Y flipped from normal minecraft usage.)
*
* @param int x
* @param int z
* @param int y
*
* @return google.maps.LatLng
*/
'fromWorldToLatLng': function(x, z, y) {
// 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));
// This information about where the center column is may change with
// a different drawing implementation -- check it again after any
// drawing overhauls!
// 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));
var lat = 0.5;
// the following metrics mimic those in ChunkRenderer.chunk_render
// in "chunk.py" or, equivalently, chunk_render in src/iterate.c
// each block on X axis adds 12px to x and subtracts 6px from y
lng += 12 * x * perPixel;
lat -= 6 * x * perPixel;
// each block on Y axis adds 12px to x and adds 6px to y
lng += 12 * y * perPixel;
lat += 6 * y * perPixel;
// each block down along Z adds 12px to y
lat += 12 * (128 - z) * perPixel;
// add on 12 px to the X coordinate to center our point
lng += 12 * perPixel;
return new google.maps.LatLng(lat, lng);
},
/**
* The opposite of fromWorldToLatLng
* NOTE: X, Y and Z in this function are Minecraft world definitions
* (that is, X is horizontal, Y is altitude and Z is vertical).
*
* @param float lat
* @param float lng
*
* @return Array
*/
'fromLatLngToWorld': function(lat, lng) {
// Initialize world x/y/z object to be returned
var point = Array();
point.x = 0;
point.y = 64;
point.z = 0;
// 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));
// Revert base positioning
// See equivalent code in fromWorldToLatLng()
lng -= 0.5 - (1.0 / Math.pow(2, overviewerConfig.map.maxZoom + 1));
lat -= 0.5;
// 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
// A (lng) and B (lat). But Wolfram Alpha did. :) I'd welcome
// suggestions for splitting this up into long form and documenting
// it. -RF
point.x = (lng - 2 * lat) / (24 * perPixel)
point.z = (lng + 2 * lat) / (24 * perPixel)
// Adjust for the fact that we we can't figure out what Y is given
// only latitude and longitude, so assume Y=64.
point.x += 64 + 1;
point.z -= 64 + 2;
return point;
},
/**
* Create and draw the various map controls and other related things
* like the compass, current view link, etc.
*/
'createMapControls': function() {
// viewstate link (little link to where you're looking at the map,
// normally bottom left)
var viewStateDiv = document.createElement('DIV');
viewStateDiv.id='link';
// add it to the map, bottom left.
if (overviewerConfig.map.controls.link) {
google.maps.event.addListener(overviewer.map, 'zoom_changed', function() {
overviewer.util.setViewUrl();
});
google.maps.event.addListener(overviewer.map, 'center_changed', function() {
overviewer.util.setViewUrl();
});
overviewer.map.controls[google.maps.ControlPosition.BOTTOM_LEFT].push(viewStateDiv);
}
// compass rose, in the top right corner
var compassDiv = document.createElement('DIV');
compassDiv.style.padding = '5px';
var compassImg = document.createElement('IMG');
compassImg.src = overviewerConfig.CONST.image.compass;
compassDiv.appendChild(compassImg);
compassDiv.index = 0;
// add it to the map, top right.
if (overviewerConfig.map.controls.compass) {
overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(compassDiv);
}
// Spawn button
var homeControlDiv = document.createElement('DIV');
var homeControl = new overviewer.classes.HomeControl(homeControlDiv);
homeControlDiv.id = 'customControl';
homeControlDiv.index = 1;
if (overviewerConfig.map.controls.spawn) {
overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(homeControlDiv);
}
// only need to create the control if there are items in the list.
// as defined in config.js
if (overviewerConfig.objectGroups.signs.length > 0) {
// signpost display control
var items = [];
for (i in overviewerConfig.objectGroups.signs) {
var signGroup = overviewerConfig.objectGroups.signs[i];
var iconURL = signGroup.icon;
if(!iconURL) {
iconURL = overviewerConfig.CONST.image.defaultMarker;
}
items.push({
'label': signGroup.label,
'checked': signGroup.checked,
'icon': iconURL,
'action': function(n, item, checked) {
jQuery.each(overviewer.collections.markers[item.label],
function(i, elem) {
elem.setVisible(checked);
}
);
overviewer.util.debug('Adding sign item: ' + item);
}
});
}
overviewer.util.createDropDown('Signposts', items);
}
// if there are any regions data, lets show the option to hide/show them.
if (overviewerConfig.objectGroups.regions.length > 0) {
// region display control
var items = [];
for (i in overviewerConfig.objectGroups.regions) {
var regionGroup = overviewerConfig.objectGroups.regions[i];
items.push({
'label': regionGroup.label,
'checked': regionGroup.checked,
'action': function(n, item, checked) {
jQuery.each(overviewer.collections.regions[item.label],
function(i,elem) {
// Thanks to LeastWeasel for this line!
elem.setMap(checked ? overviewer.map : null);
});
overviewer.util.debug('Adding region item: ' + item);
}
});
}
overviewer.util.createDropDown('Regions', items);
}
if (overviewer.collections.overlays.length > 0) {
// overlay maps control
var items = [];
for (i in overviewer.collections.overlays) {
var overlay = overviewer.collections.overlays[i];
items.push({
'label': overlay.name,
'checked': false,
'overlay': overlay,
'action': function(i, item, checked) {
if (checked) {
overviewer.map.overlayMapTypes.push(item.overlay);
} else {
var idx_to_delete = -1;
overviewer.map.overlayMapTypes.forEach(function(e, j) {
if (e == item.overlay) {
idx_to_delete = j;
}
});
if (idx_to_delete >= 0) {
overviewer.map.overlayMapTypes.removeAt(idx_to_delete);
}
}
}
});
}
overviewer.util.createDropDown('Overlays', items);
}
},
/**
* Reusable method for creating drop-down menus
*
* @param string title
* @param array items
*/
'createDropDown': function(title, items) {
var control = document.createElement('DIV');
// let's let a style sheet do most of the styling here
control.id = 'customControl';
var controlText = document.createElement('DIV');
controlText.innerHTML = title;
var controlBorder = document.createElement('DIV');
controlBorder.id='top';
control.appendChild(controlBorder);
controlBorder.appendChild(controlText);
var dropdownDiv = document.createElement('DIV');
dropdownDiv.id='dropDown';
control.appendChild(dropdownDiv);
dropdownDiv.innerHTML='';
// add the functionality to toggle visibility of the items
$(controlText).click(function() {
$(dropdownDiv).toggle();
});
// add that control box we've made back to the map.
overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(control);
for(i in items) {
// create the visible elements of the item
var item = items[i];
overviewer.util.debug(item);
var itemDiv = document.createElement('div');
var itemInput = document.createElement('input');
itemInput.type='checkbox';
// give it a name
$(itemInput).data('label',item.label);
jQuery(itemInput).click((function(local_idx, local_item) {
return function(e) {
item.action(local_idx, local_item, e.target.checked);
};
})(i, item));
// if its checked, its gotta do something, do that here.
if (item.checked) {
itemInput.checked = true;
item.action(i, item, item.checked);
}
dropdownDiv.appendChild(itemDiv);
itemDiv.appendChild(itemInput);
var textNode = document.createElement('text');
if(item.icon) {
textNode.innerHTML = '<img width="15" height="15" src="' +
item.icon + '">' + item.label + '<br/>';
} else {
textNode.innerHTML = item.label + '<br/>';
}
itemDiv.appendChild(textNode);
}
},
/**
* 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
* closures or something.
*
* @param google.maps.Polygon|google.maps.Polyline shape
*/
'createRegionInfoWindow': function(shape) {
var infowindow = new google.maps.InfoWindow();
google.maps.event.addListener(shape, 'click', function(event, i) {
if (overviewer.collections.infoWindow) {
overviewer.collections.infoWindow.close();
}
// Replace our Info Window's content and position
var contentString = '<b>Region: ' + shape.name + '</b><br />' +
'Clicked Location: <br />' + event.latLng.lat() + ', ' +
event.latLng.lng() + '<br />';
infowindow.setContent(contentString);
infowindow.setPosition(event.latLng);
infowindow.open(overviewer.map);
overviewer.collections.infoWindow = infowindow;
});
},
/**
* Same as createRegionInfoWindow()
*
* @param google.maps.Marker marker
*/
'createMarkerInfoWindow': function(marker) {
var windowContent = '<div class="infoWindow"><img src="' + marker.icon +
'"/><p>' + marker.title.replace(/\n/g,'<br/>') + '</p></div>';
var infowindow = new google.maps.InfoWindow({
'content': windowContent
});
google.maps.event.addListener(marker, 'click', function() {
if (overviewer.collections.infoWindow) {
overviewer.collections.infoWindow.close();
}
infowindow.open(overviewer.map, marker);
overviewer.collections.infoWindow = infowindow;
});
}
},
/**
* The various classes needed in this file.
*/
'classes': {
/**
* This is the button that centers the map on spawn. Not sure why we
* need a separate class for this and not some of the other controls.
*
* @param documentElement controlDiv
*/
'HomeControl': function(controlDiv) {
controlDiv.style.padding = '5px';
// Set CSS for the control border
var control = document.createElement('DIV');
control.id='top';
control.title = 'Click to center the map on the Spawn';
controlDiv.appendChild(control);
// Set CSS for the control interior
var controlText = document.createElement('DIV');
controlText.innerHTML = 'Spawn';
controlText.id='button';
control.appendChild(controlText);
// Setup the click event listeners: simply set the map to map center
// as definned below
google.maps.event.addDomListener(control, 'click', function() {
overviewer.map.panTo(overviewer.util.fromWorldToLatLng(
overviewerConfig.map.center[0],
overviewerConfig.map.center[1],
overviewerConfig.map.center[2]));
});
},
/**
* Our custom projection maps Latitude to Y, and Longitude to X as
* normal, but it maps the range [0.0, 1.0] to [0, tileSize] in both
* directions so it is easier to position markers, etc. based on their
* position (find their position in the lowest-zoom image, and divide
* by tileSize)
*/
'MapProjection' : function() {
this.inverseTileSize = 1.0 / overviewerConfig.CONST.tileSize;
},
/**
* This is a mapType used only for debugging, to draw a grid on the screen
* showing the tile co-ordinates and tile path. Currently the tile path
* part does not work.
*
* @param google.maps.Size tileSize
*/
'CoordMapType': function(tileSize) {
this.tileSize = tileSize;
}
},
/**
* Stuff that we give to the google maps code instead of using ourselves
* goes in here.
*
* Also, why do I keep writing these comments as if I'm multiple people? I
* should probably stop that.
*/
'gmap': {
/**
* Generate a function to get the path to a tile at a particular location
* and zoom level.
*
* @param string path
* @param string pathBase
* @param string pathExt
*/
'getTileUrlGenerator': function(path, pathBase, pathExt) {
return function(tile, zoom) {
var url = path;
var urlBase = ( pathBase ? pathBase : '' );
if(tile.x < 0 || tile.x >= Math.pow(2, zoom) ||
tile.y < 0 || tile.y >= Math.pow(2, zoom)) {
url += '/blank';
} else if(zoom == 0) {
url += '/base';
} else {
for(var z = zoom - 1; z >= 0; --z) {
var x = Math.floor(tile.x / Math.pow(2, z)) % 2;
var y = Math.floor(tile.y / Math.pow(2, z)) % 2;
url += '/' + (x + 2 * y);
}
}
url = url + '.' + pathExt;
if(overviewerConfig.map.cacheMinutes > 0) {
var d = new Date();
url += '?c=' + Math.floor(d.getTime() /
(1000 * 60 * overviewerConfig.map.cacheMinutes));
}
return(urlBase + url);
}
}
}
};

View File

@@ -23,6 +23,7 @@ from time import strftime, gmtime
import json
import util
from c_overviewer import get_render_mode_inheritance
"""
This module has routines related to generating a Google Maps-based
@@ -59,25 +60,25 @@ def mirror_dir(src, dst, entities=None):
# if this stills throws an error, let it propagate up
class MapGen(object):
def __init__(self, quadtrees, skipjs=False, web_assets_hook=None):
def __init__(self, quadtrees, configInfo):
"""Generates a Google Maps interface for the given list of
quadtrees. All of the quadtrees must have the same destdir,
image format, and world.
Note:tiledir for each quadtree should be unique. By default the tiledir is determined by the rendermode"""
self.skipjs = skipjs
self.web_assets_hook = web_assets_hook
self.skipjs = configInfo.get('skipjs', None)
self.web_assets_hook = configInfo.get('web_assets_hook', None)
self.bg_color = configInfo.get('bg_color')
if not len(quadtrees) > 0:
raise ValueError("there must be at least one quadtree to work on")
self.destdir = quadtrees[0].destdir
self.imgformat = quadtrees[0].imgformat
self.world = quadtrees[0].world
self.p = quadtrees[0].p
for i in quadtrees:
if i.destdir != self.destdir or i.imgformat != self.imgformat or i.world != self.world:
raise ValueError("all the given quadtrees must have the same destdir")
if i.destdir != self.destdir or i.world != self.world:
raise ValueError("all the given quadtrees must have the same destdir and world")
self.quadtrees = quadtrees
@@ -85,32 +86,38 @@ class MapGen(object):
"""Writes out config.js, marker.js, and region.js
Copies web assets into the destdir"""
zoomlevel = self.p
imgformat = self.imgformat
configpath = os.path.join(util.get_program_path(), "config.js")
configpath = os.path.join(util.get_program_path(), "overviewerConfig.js")
config = open(configpath, 'r').read()
config = config.replace(
"{maxzoom}", str(zoomlevel))
"{minzoom}", str(0))
config = config.replace(
"{imgformat}", str(imgformat))
"{maxzoom}", str(zoomlevel))
config = config.replace("{spawn_coords}",
json.dumps(list(self.world.spawn)))
#config = config.replace("{bg_color}", self.bg_color)
# create generated map type data, from given quadtrees
maptypedata = map(lambda q: {'label' : q.rendermode.capitalize(),
'path' : q.tiledir}, self.quadtrees)
'path' : q.tiledir,
'bg_color': self.bg_color,
'overlay' : 'overlay' in get_render_mode_inheritance(q.rendermode),
'imgformat' : q.imgformat},
self.quadtrees)
config = config.replace("{maptypedata}", json.dumps(maptypedata))
with open(os.path.join(self.destdir, "config.js"), 'w') as output:
with open(os.path.join(self.destdir, "overviewerConfig.js"), 'w') as output:
output.write(config)
bgcolor = (int(self.bg_color[1:3],16), int(self.bg_color[3:5],16), int(self.bg_color[5:7],16), 0)
blank = Image.new("RGBA", (1,1), bgcolor)
# Write a blank image
for quadtree in self.quadtrees:
blank = Image.new("RGBA", (1,1))
tileDir = os.path.join(self.destdir, quadtree.tiledir)
if not os.path.exists(tileDir): os.mkdir(tileDir)
blank.save(os.path.join(tileDir, "blank."+self.imgformat))
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)
@@ -144,13 +151,13 @@ class MapGen(object):
# write out the default marker table
with open(os.path.join(self.destdir, "markers.js"), 'w') as output:
output.write("var markerData=[\n")
output.write("overviewer.collections.markerDatas.push([\n")
for marker in self.world.POI:
output.write(json.dumps(marker))
if marker != self.world.POI[-1]:
output.write(",")
output.write("\n")
output.write("]\n")
output.write("]);\n")
# save persistent data
self.world.persistentData['POI'] = self.world.POI
@@ -159,11 +166,11 @@ class MapGen(object):
# write out the default (empty, but documented) region table
with open(os.path.join(self.destdir, "regions.js"), 'w') as output:
output.write('var regionData=[\n')
output.write('overviewer.collections.regionDatas.push([\n')
output.write(' // {"color": "#FFAA00", "opacity": 0.5, "closed": true, "path": [\n')
output.write(' // {"x": 0, "y": 0, "z": 0},\n')
output.write(' // {"x": 0, "y": 10, "z": 0},\n')
output.write(' // {"x": 0, "y": 0, "z": 10}\n')
output.write(' // ]},\n')
output.write('];')
output.write(']);')

View File

@@ -34,6 +34,7 @@ from PIL import Image
import nbt
import chunk
from c_overviewer import get_render_mode_inheritance
from optimizeimages import optimize_image
import composite
@@ -48,7 +49,7 @@ def iterate_base4(d):
return itertools.product(xrange(4), repeat=d)
class QuadtreeGen(object):
def __init__(self, worldobj, destdir, depth=None, tiledir=None, imgformat=None, optimizeimg=None, rendermode="normal"):
def __init__(self, worldobj, destdir, bgcolor, depth=None, tiledir=None, imgformat=None, imgquality=95, optimizeimg=None, rendermode="normal"):
"""Generates a quadtree from the world given into the
given dest directory
@@ -60,16 +61,18 @@ class QuadtreeGen(object):
"""
assert(imgformat)
self.imgformat = imgformat
self.imgquality = imgquality
self.optimizeimg = optimizeimg
self.lighting = rendermode in ("lighting", "night", "spawn")
self.night = rendermode in ("night", "spawn")
self.spawn = rendermode in ("spawn",)
self.bgcolor = bgcolor
self.rendermode = rendermode
# force png renderformat if we're using an overlay mode
if 'overlay' in get_render_mode_inheritance(rendermode):
self.imgformat = "png"
# Make the destination dir
if not os.path.exists(destdir):
os.mkdir(destdir)
os.makedirs(os.path.abspath(destdir))
if tiledir is None:
tiledir = rendermode
self.tiledir = tiledir
@@ -89,7 +92,7 @@ class QuadtreeGen(object):
yradius >= worldobj.maxrow and -yradius <= worldobj.minrow:
break
else:
raise ValueError("Your map is waaaay too big! Use the '-z' or '--zoom' options.")
raise ValueError("Your map is waaaay too big! Use the 'zoom' option in 'settings.py'.")
self.p = p
else:
@@ -113,10 +116,10 @@ class QuadtreeGen(object):
returns -1 if it couldn't be detected, file not found, or nothing in
config.js matched
"""
indexfile = os.path.join(self.destdir, "config.js")
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"maxZoom.*:\s*(\d+)")
p = -1
for line in open(indexfile, "r"):
res = matcher.search(line)
@@ -320,7 +323,7 @@ class QuadtreeGen(object):
#logging.debug("writing out innertile {0}".format(imgpath))
# Create the actual image now
img = Image.new("RGBA", (384, 384), (38,92,255,0))
img = Image.new("RGBA", (384, 384), self.bgcolor)
# we'll use paste (NOT alpha_over) for quadtree generation because
# this is just straight image stitching, not alpha blending
@@ -334,7 +337,7 @@ class QuadtreeGen(object):
# Save it
if self.imgformat == 'jpg':
img.save(imgpath, quality=95, subsampling=0)
img.save(imgpath, quality=self.imgquality, subsampling=0)
else: # png
img.save(imgpath)
@@ -442,7 +445,7 @@ class QuadtreeGen(object):
#logging.debug("writing out worldtile {0}".format(imgpath))
# Compile this image
tileimg = Image.new("RGBA", (width, height), (38,92,255,0))
tileimg = Image.new("RGBA", (width, height), self.bgcolor)
world = self.world
rendermode = self.rendermode
@@ -461,7 +464,10 @@ class QuadtreeGen(object):
pass
# Save them
tileimg.save(imgpath)
if self.imgformat == 'jpg':
tileimg.save(imgpath, quality=self.imgquality, subsampling=0)
else: # png
tileimg.save(imgpath)
if self.optimizeimg:
optimize_image(imgpath, self.imgformat, self.optimizeimg)

View File

@@ -149,7 +149,6 @@ class RenderNode(object):
total += 4**q.p
if q.p > max_p:
max_p = q.p
q.go(procs)
self.max_p = max_p
# Render the highest level of tiles from the chunks
results = collections.deque()

View File

@@ -273,7 +273,8 @@ alpha_over_wrap(PyObject *self, PyObject *args)
* also, it multiplies instead of doing an over operation
*/
PyObject *
tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, unsigned char sb,
tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg,
unsigned char sb, unsigned char sa,
PyObject *mask, int dx, int dy, int xsize, int ysize) {
/* libImaging handles */
Imaging imDest, imMask;
@@ -332,9 +333,11 @@ tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, unsigned char
out++;
*out = MULDIV255(*out, sb, tmp1);
out++;
*out = MULDIV255(*out, sa, tmp1);
out++;
} else if (*inmask == 0) {
/* do nothing -- source is fully transparent */
out += 3;
out += 4;
} else {
/* general case */
@@ -345,9 +348,10 @@ tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, unsigned char
out++;
*out = MULDIV255(*out, (255 - *inmask) + MULDIV255(sb, *inmask, tmp1), tmp2);
out++;
*out = MULDIV255(*out, (255 - *inmask) + MULDIV255(sa, *inmask, tmp1), tmp2);
out++;
}
out++;
inmask += mask_stride;
}
}

View File

@@ -155,7 +155,12 @@ generate_pseudo_data(RenderState *state, unsigned char ancilData) {
int x = state->x, y = state->y, z = state->z;
unsigned char data = 0;
if (state->block == 9) { /* water */
if (state->block == 2) { /* grass */
/* return 0x10 if grass is covered in snow */
if (z < 127 && getArrayByte3D(state->blocks, x, y, z+1) == 78)
return 0x10;
return ancilData;
} else if (state->block == 9) { /* water */
/* an aditional bit for top is added to the 4 bits of check_adjacent_blocks */
if ((ancilData == 0) || (ancilData >= 10)) { /* static water, only top, and unkown ancildata values */
data = 16;
@@ -207,9 +212,75 @@ generate_pseudo_data(RenderState *state, unsigned char ancilData) {
if ((above_level_data & 0x08)) { /* draw top right going up redstonewire */
final_data = final_data | 0x10;
}
return final_data;
return final_data;
} else if (state-> block == 54) { /* chests */
/* the top 2 bits are used to store the type of chest
* (single or double), the 2 bottom bits are used for
* orientation, look textures.py for more information. */
/* if placed alone chests always face west, return 0 to make a
* chest facing west */
unsigned char chest_data = 0, air_data = 0, final_data = 0;
/* search for chests */
chest_data = check_adjacent_blocks(state, x, y, z, 54);
/* search for air */
air_data = check_adjacent_blocks(state, x, y, z, 0);
if (chest_data == 1) { /* another chest in the east */
final_data = final_data | 0x8; /* only can face to north or south */
if ( (air_data & 0x2) == 2 ) {
final_data = final_data | 0x1; /* facing north */
} else {
final_data = final_data | 0x3; /* facing south */
}
} else if (chest_data == 2) { /* in the north */
final_data = final_data | 0x4; /* only can face to east or west */
if ( !((air_data & 0x4) == 4) ) { /* 0 = west */
final_data = final_data | 0x2; /* facing east */
}
} else if (chest_data == 4) { /*in the west */
final_data = final_data | 0x4;
if ( (air_data & 0x2) == 2 ) {
final_data = final_data | 0x1; /* facing north */
} else {
final_data = final_data | 0x3; /* facing south */
}
} else if (chest_data == 8) { /*in the south */
final_data = final_data | 0x8;
if ( !((air_data & 0x4) == 4) ) {
final_data = final_data | 0x2; /* facing east */
}
} else if (chest_data == 0) {
/* Single chest, determine the orientation */
if ( ((air_data & 0x8) == 0) && ((air_data & 0x2) == 2) ) { /* block in +x and no block in -x */
final_data = final_data | 0x1; /* facing north */
} else if ( ((air_data & 0x2) == 0) && ((air_data & 0x8) == 8)) {
final_data = final_data | 0x3;
} else if ( ((air_data & 0x4) == 0) && ((air_data & 0x1) == 1)) {
final_data = final_data | 0x2;
} /* else, facing west, value = 0 */
} else {
/* more than one adjacent chests! render as normal chest */
return 0;
}
return final_data;
} else if (state->block == 90) {
return check_adjacent_blocks(state, x, y, z, state->block);
}
return 0;
}
@@ -329,7 +400,7 @@ chunk_render(PyObject *self, PyObject *args) {
PyObject *tmp;
unsigned char ancilData = getArrayByte3D(blockdata_expanded, state.x, state.y, state.z);
if ((state.block == 85) || (state.block == 9) || (state.block == 55)) {
if ((state.block == 85) || (state.block == 9) || (state.block == 55) || (state.block == 54) || (state.block == 2) || (state.block == 90)) {
ancilData = generate_pseudo_data(&state, ancilData);
}

View File

@@ -25,14 +25,24 @@ PyObject *get_extension_version(PyObject *self, PyObject *args) {
static PyMethodDef COverviewerMethods[] = {
{"alpha_over", alpha_over_wrap, METH_VARARGS,
"alpha over composite function"},
{"render_loop", chunk_render, METH_VARARGS,
"Renders stuffs"},
{"get_render_modes", get_render_modes, METH_VARARGS,
"returns available render modes"},
{"get_render_mode_info", get_render_mode_info, METH_VARARGS,
"returns info for a particular render mode"},
{"get_render_mode_parent", get_render_mode_parent, METH_VARARGS,
"returns parent for a particular render mode"},
{"get_render_mode_inheritance", get_render_mode_inheritance, METH_VARARGS,
"returns inheritance chain for a particular render mode"},
{"get_render_mode_children", get_render_mode_children, METH_VARARGS,
"returns (direct) children for a particular render mode"},
{"extension_version", get_extension_version, METH_VARARGS,
"Returns the extension version"},
{NULL, NULL, 0, NULL} /* Sentinel */
};

View File

@@ -26,7 +26,7 @@
// increment this value if you've made a change to the c extesion
// and want to force users to rebuild
#define OVERVIEWER_EXTENSION_VERSION 3
#define OVERVIEWER_EXTENSION_VERSION 5
/* Python PIL, and numpy headers */
#include <Python.h>
@@ -48,7 +48,8 @@ PyObject *alpha_over(PyObject *dest, PyObject *src, PyObject *mask,
PyObject *alpha_over_full(PyObject *dest, PyObject *src, PyObject *mask, float overall_alpha,
int dx, int dy, int xsize, int ysize);
PyObject *alpha_over_wrap(PyObject *self, PyObject *args);
PyObject *tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg, unsigned char sb,
PyObject *tint_with_mask(PyObject *dest, unsigned char sr, unsigned char sg,
unsigned char sb, unsigned char sa,
PyObject *mask, int dx, int dy, int xsize, int ysize);
/* in iterate.c */

View File

@@ -0,0 +1,234 @@
/*
* This file is part of the Minecraft Overviewer.
*
* Minecraft Overviewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Minecraft Overviewer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with the Overviewer. If not, see <http://www.gnu.org/licenses/>.
*/
#include "overviewer.h"
#include <math.h>
//~
//~ /* figures out the black_coeff from a given skylight and blocklight, used in
//~ lighting calculations -- note this is *different* from the one in
//~ rendermode-lighting.c (the "skylight - 11" part) */
//~ static float calculate_darkness(unsigned char skylight, unsigned char blocklight) {
//~ return 1.0f - powf(0.8f, 15.0 - MAX(blocklight, skylight - 11));
//~ }
static int
rendermode_cave_occluded(void *data, RenderState *state) {
int x = state->x, y = state->y, z = state->z, dz = 0;
RenderModeCave* self;
self = (RenderModeCave *)data;
/* check if the block is touching skylight */
if (z != 127) {
if (getArrayByte3D(self->skylight, x, y, z+1) != 0) {
return 1;
}
if ((x == 15)) {
if (self->up_right_skylight != Py_None) {
if (getArrayByte3D(self->up_right_skylight, 0, y, z) != 0) {
return 1;
}
}
} else {
if (getArrayByte3D(self->skylight, x+1, y, z) != 0) {
return 1;
}
}
if (x == 0) {
if (self->left_skylight != Py_None) {
if (getArrayByte3D(self->left_skylight, 15, y, z) != 0) {
return 1;
}
}
} else {
if (getArrayByte3D(self->skylight, x-1, y, z) != 0) {
return 1;
}
}
if (y == 15) {
if (self->right_skylight != Py_None) {
if (getArrayByte3D(self->right_skylight, 0, y, z) != 0) {
return 1;
}
}
} else {
if (getArrayByte3D(self->skylight, x, y+1, z) != 0) {
return 1;
}
}
if (y == 0) {
if (self->up_left_skylight != Py_None) {
if (getArrayByte3D(self->up_left_skylight, 15, y, z) != 0) {
return 1;
}
}
} else {
if (getArrayByte3D(self->skylight, x, y-1, z) != 0) {
return 1;
}
}
/* check for normal occlusion */
/* use ajacent chunks, if not you get blocks spreaded in chunk edges */
if ( (x == 0) && (y != 15) ) {
if (state->left_blocks != Py_None) {
if (!is_transparent(getArrayByte3D(state->left_blocks, 15, y, z)) &&
!is_transparent(getArrayByte3D(state->blocks, x, y, z+1)) &&
!is_transparent(getArrayByte3D(state->blocks, x, y+1, z))) {
return 1;
}
} else {
return 1;
}
}
if ( (x != 0) && (y == 15) ) {
if (state->right_blocks != Py_None) {
if (!is_transparent(getArrayByte3D(state->blocks, x-1, y, z)) &&
!is_transparent(getArrayByte3D(state->right_blocks, x, 0, z)) &&
!is_transparent(getArrayByte3D(state->blocks, x, y, z+1))) {
return 1;
}
} else {
return 1;
}
}
if ( (x == 0) && (y == 15) ) {
if ((state->left_blocks != Py_None) &&
(state->right_blocks != Py_None)) {
if (!is_transparent(getArrayByte3D(state->left_blocks, 15, y, z)) &&
!is_transparent(getArrayByte3D(state->right_blocks, x, 0, z)) &&
!is_transparent(getArrayByte3D(state->blocks, x, y, z+1))) {
return 1;
}
} else {
return 1;
}
}
if ( (x != 0) && (y != 15) &&
!is_transparent(getArrayByte3D(state->blocks, x-1, y, z)) &&
!is_transparent(getArrayByte3D(state->blocks, x, y, z+1)) &&
!is_transparent(getArrayByte3D(state->blocks, x, y+1, z))) {
return 1;
}
} else { /* if z == 127 skip */
return 1;
}
/* check for lakes and seas and don't render them */
/* at this point of the code the block has no skylight
* and is not occluded, but a deep sea can fool these
* 2 tests */
if ((getArrayByte3D(state->blocks, x, y, z) == 9) ||
(getArrayByte3D(state->blocks, x, y, z+1) == 9)) {
for (dz = z+1; dz < 127; dz++) { /* go up and check for skylight */
if (getArrayByte3D(self->skylight, x, y, dz) != 0) {
return 1;
}
if (getArrayByte3D(state->blocks, x, y, dz) != 9) {
/* we are out of the water! and there's no skylight
* , i.e. is a cave lake or something similar */
return 0;
}
}
}
return 0;
}
static int
rendermode_cave_start(void *data, RenderState *state) {
RenderModeCave* self;
int ret;
self = (RenderModeCave *)data;
/* first, chain up */
ret = rendermode_normal.start(data, state);
if (ret != 0)
return ret;
/* if there's skylight we are in the surface! */
self->skylight = PyObject_GetAttrString(state->self, "skylight");
self->left_skylight = PyObject_GetAttrString(state->self, "left_skylight");
self->right_skylight = PyObject_GetAttrString(state->self, "right_skylight");
self->up_left_skylight = PyObject_GetAttrString(state->self, "up_left_skylight");
self->up_right_skylight = PyObject_GetAttrString(state->self, "up_right_skylight");
/* colors for tinting */
self->depth_colors = PyObject_GetAttrString(state->chunk, "depth_colors");
return 0;
}
static void
rendermode_cave_finish(void *data, RenderState *state) {
RenderModeCave* self;
self = (RenderModeCave *)data;
Py_DECREF(self->skylight);
Py_DECREF(self->left_skylight);
Py_DECREF(self->right_skylight);
Py_DECREF(self->up_left_skylight);
Py_DECREF(self->up_right_skylight);
Py_DECREF(self->depth_colors);
rendermode_normal.finish(data, state);
}
static void
rendermode_cave_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) {
RenderModeCave* self;
int z, r, g, b;
self = (RenderModeCave *)data;
z = state->z;
r = 0, g = 0, b = 0;
/* draw the normal block */
rendermode_normal.draw(data, state, src, mask);
/* get the colors and tint and tint */
/* TODO TODO for a nether mode there isn't tinting! */
r = PyInt_AsLong(PyList_GetItem(self->depth_colors, 0 + z*3));
g = PyInt_AsLong(PyList_GetItem(self->depth_colors, 1 + z*3));
b = PyInt_AsLong(PyList_GetItem(self->depth_colors, 2 + z*3));
tint_with_mask(state->img, r, g, b, 255, mask, state->imgx, state->imgy, 0, 0);
}
RenderModeInterface rendermode_cave = {
"cave", "render only caves in normal mode",
&rendermode_normal,
sizeof(RenderModeCave),
rendermode_cave_start,
rendermode_cave_finish,
rendermode_cave_occluded,
rendermode_cave_draw,
};

View File

@@ -229,6 +229,7 @@ rendermode_lighting_draw(void *data, RenderState *state, PyObject *src, PyObject
RenderModeInterface rendermode_lighting = {
"lighting", "draw shadows from the lighting data",
&rendermode_normal,
sizeof(RenderModeLighting),
rendermode_lighting_start,
rendermode_lighting_finish,

View File

@@ -61,6 +61,7 @@ rendermode_night_draw(void *data, RenderState *state, PyObject *src, PyObject *m
RenderModeInterface rendermode_night = {
"night", "like \"lighting\", except at night",
&rendermode_lighting,
sizeof(RenderModeNight),
rendermode_night_start,
rendermode_night_finish,

View File

@@ -120,17 +120,8 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject *
RenderModeNormal *self = (RenderModeNormal *)data;
/* first, check to see if we should use biome-compatible src, mask */
if (self->biome_data) {
switch (state->block) {
case 2:
src = mask = self->grass_texture;
break;
case 18:
src = mask = self->leaf_texture;
break;
default:
break;
};
if (self->biome_data && state->block == 18) {
src = mask = self->leaf_texture;
}
/* draw the block! */
@@ -147,9 +138,12 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject *
switch (state->block) {
case 2:
/* grass */
/* grass -- skip for snowgrass */
if (state->z < 127 && getArrayByte3D(state->blocks, state->x, state->y, state->z+1) == 78)
break;
color = PySequence_GetItem(self->grasscolor, index);
facemask = self->facemask_top;
facemask = self->grass_texture;
alpha_over(state->img, self->grass_texture, self->grass_texture, state->imgx, state->imgy, 0, 0);
break;
case 18:
/* leaves */
@@ -169,7 +163,7 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject *
b = PyInt_AsLong(PyTuple_GET_ITEM(color, 2));
Py_DECREF(color);
tint_with_mask(state->img, r, g, b, facemask, state->imgx, state->imgy, 0, 0);
tint_with_mask(state->img, r, g, b, 255, facemask, state->imgx, state->imgy, 0, 0);
}
}
@@ -183,7 +177,7 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject *
int increment=0;
if (state->block == 44) // half-step
increment=6;
else if (state->block == 78) // snow
else if ((state->block == 78) || (state->block == 93) || (state->block == 94)) // snow, redstone repeaters (on and off)
increment=9;
if ((state->x == 15) && (state->up_right_blocks != Py_None)) {
@@ -221,6 +215,7 @@ rendermode_normal_draw(void *data, RenderState *state, PyObject *src, PyObject *
RenderModeInterface rendermode_normal = {
"normal", "nothing special, just render the blocks",
NULL,
sizeof(RenderModeNormal),
rendermode_normal_start,
rendermode_normal_finish,

View File

@@ -0,0 +1,137 @@
/*
* This file is part of the Minecraft Overviewer.
*
* Minecraft Overviewer is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Minecraft Overviewer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
* Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with the Overviewer. If not, see <http://www.gnu.org/licenses/>.
*/
#include "overviewer.h"
static void get_color(void *data, RenderState *state,
unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *a) {
*r = 200;
*g = 200;
*b = 255;
*a = 155;
}
static int
rendermode_overlay_start(void *data, RenderState *state) {
PyObject *facemasks_py;
RenderModeOverlay *self = (RenderModeOverlay *)data;
facemasks_py = PyObject_GetAttrString(state->chunk, "facemasks");
/* borrowed reference, needs to be incref'd if we keep it */
self->facemask_top = PyTuple_GetItem(facemasks_py, 0);
Py_INCREF(self->facemask_top);
Py_DECREF(facemasks_py);
self->white_color = PyObject_GetAttrString(state->chunk, "white_color");
self->solid_blocks = PyObject_GetAttrString(state->chunk, "solid_blocks");
self->fluid_blocks = PyObject_GetAttrString(state->chunk, "fluid_blocks");
self->get_color = get_color;
return 0;
}
static void
rendermode_overlay_finish(void *data, RenderState *state) {
RenderModeOverlay *self = (RenderModeOverlay *)data;
Py_DECREF(self->facemask_top);
Py_DECREF(self->white_color);
Py_DECREF(self->solid_blocks);
Py_DECREF(self->fluid_blocks);
}
static int
rendermode_overlay_occluded(void *data, RenderState *state) {
int x = state->x, y = state->y, z = state->z;
if ( (x != 0) && (y != 15) && (z != 127) &&
!is_transparent(getArrayByte3D(state->blocks, x-1, y, z)) &&
!is_transparent(getArrayByte3D(state->blocks, x, y, z+1)) &&
!is_transparent(getArrayByte3D(state->blocks, x, y+1, z))) {
return 1;
}
return 0;
}
static void
rendermode_overlay_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) {
RenderModeOverlay *self = (RenderModeOverlay *)data;
unsigned char r, g, b, a;
PyObject *top_block_py, *block_py;
// exactly analogous to edge-line code for these special blocks
int increment=0;
if (state->block == 44) // half-step
increment=6;
else if (state->block == 78) // snow
increment=9;
/* clear the draw space -- set alpha to 0 within mask */
tint_with_mask(state->img, 255, 255, 255, 0, mask, state->imgx, state->imgy, 0, 0);
/* skip rendering the overlay if we can't see it */
if (state->z != 127) {
unsigned char top_block = getArrayByte3D(state->blocks, state->x, state->y, state->z+1);
if (!is_transparent(top_block)) {
return;
}
/* check to be sure this block is solid/fluid */
top_block_py = PyInt_FromLong(top_block);
if (PySequence_Contains(self->solid_blocks, top_block_py) ||
PySequence_Contains(self->fluid_blocks, top_block_py)) {
/* top block is fluid or solid, skip drawing */
Py_DECREF(top_block_py);
return;
}
Py_DECREF(top_block_py);
}
/* check to be sure this block is solid/fluid */
block_py = PyInt_FromLong(state->block);
if (!PySequence_Contains(self->solid_blocks, block_py) &&
!PySequence_Contains(self->fluid_blocks, block_py)) {
/* not fluid or solid, skip drawing the overlay */
Py_DECREF(block_py);
return;
}
Py_DECREF(block_py);
/* get our color info */
self->get_color(data, state, &r, &g, &b, &a);
/* do the overlay */
if (a > 0) {
alpha_over(state->img, self->white_color, self->facemask_top, state->imgx, state->imgy + increment, 0, 0);
tint_with_mask(state->img, r, g, b, a, self->facemask_top, state->imgx, state->imgy + increment, 0, 0);
}
}
RenderModeInterface rendermode_overlay = {
"overlay", "base rendermode for informational overlays",
NULL,
sizeof(RenderModeOverlay),
rendermode_overlay_start,
rendermode_overlay_finish,
rendermode_overlay_occluded,
rendermode_overlay_draw,
};

View File

@@ -18,21 +18,66 @@
#include "overviewer.h"
#include <math.h>
static void get_color(void *data, RenderState *state,
unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *a) {
RenderModeSpawn* self = (RenderModeSpawn *)data;
int x = state->x, y = state->y, z = state->z;
int z_light = z + 1;
unsigned char blocklight, skylight;
PyObject *block_py;
/* set a nice, pretty red color */
*r = 229;
*g = 36;
*b = 38;
/* default to no overlay, until told otherwise */
*a = 0;
block_py = PyInt_FromLong(state->block);
if (PySequence_Contains(self->nospawn_blocks, block_py)) {
/* nothing can spawn on this */
Py_DECREF(block_py);
return;
}
Py_DECREF(block_py);
blocklight = getArrayByte3D(self->blocklight, x, y, MIN(127, z_light));
/* if we're at the top, force 15 (brightest!) skylight */
if (z_light == 128) {
skylight = 15;
} else {
skylight = getArrayByte3D(self->skylight, x, y, z_light);
}
if (MAX(blocklight, skylight) <= 7) {
/* hostile mobs spawn in daylight */
*a = 240;
} else if (MAX(blocklight, skylight - 11) <= 7) {
/* hostile mobs spawn at night */
*a = 150;
}
}
static int
rendermode_spawn_start(void *data, RenderState *state) {
RenderModeSpawn* self;
/* first, chain up */
int ret = rendermode_night.start(data, state);
int ret = rendermode_overlay.start(data, state);
if (ret != 0)
return ret;
/* now do custom initializations */
self = (RenderModeSpawn *)data;
self->solid_blocks = PyObject_GetAttrString(state->chunk, "solid_blocks");
self->nospawn_blocks = PyObject_GetAttrString(state->chunk, "nospawn_blocks");
self->fluid_blocks = PyObject_GetAttrString(state->chunk, "fluid_blocks");
self->red_color = PyObject_GetAttrString(state->chunk, "red_color");
self->blocklight = PyObject_GetAttrString(state->self, "blocklight");
self->skylight = PyObject_GetAttrString(state->self, "skylight");
/* setup custom color */
self->parent.get_color = get_color;
return 0;
}
@@ -40,85 +85,31 @@ rendermode_spawn_start(void *data, RenderState *state) {
static void
rendermode_spawn_finish(void *data, RenderState *state) {
/* first free all *our* stuff */
RenderModeSpawn* self = (RenderModeSpawn *)data;
RenderModeSpawn* self = (RenderModeSpawn *)data;
Py_DECREF(self->solid_blocks);
Py_DECREF(self->nospawn_blocks);
Py_DECREF(self->fluid_blocks);
Py_DECREF(self->blocklight);
Py_DECREF(self->skylight);
/* now, chain up */
rendermode_night.finish(data, state);
rendermode_overlay.finish(data, state);
}
static int
rendermode_spawn_occluded(void *data, RenderState *state) {
/* no special occlusion here */
return rendermode_night.occluded(data, state);
return rendermode_overlay.occluded(data, state);
}
static void
rendermode_spawn_draw(void *data, RenderState *state, PyObject *src, PyObject *mask) {
/* different versions of self (spawn, lighting) */
RenderModeSpawn* self = (RenderModeSpawn *)data;
RenderModeLighting *lighting = (RenderModeLighting *)self;
int x = state->x, y = state->y, z = state->z;
PyObject *old_black_color = NULL;
/* figure out the appropriate darkness:
this block for transparents, the block above for non-transparent */
float darkness = 0.0;
if (is_transparent(state->block)) {
darkness = get_lighting_coefficient((RenderModeLighting *)self, state, x, y, z, NULL);
} else {
darkness = get_lighting_coefficient((RenderModeLighting *)self, state, x, y, z+1, NULL);
}
/* if it's dark enough... */
if (darkness > 0.8) {
PyObject *block_py = PyInt_FromLong(state->block);
/* make sure it's solid */
if (PySequence_Contains(self->solid_blocks, block_py)) {
int spawnable = 1;
/* not spawnable if its in the nospawn list */
if (PySequence_Contains(self->nospawn_blocks, block_py))
spawnable = 0;
/* check the block above for solid or fluid */
if (spawnable && z != 127) {
PyObject *top_block_py = PyInt_FromLong(getArrayByte3D(state->blocks, x, y, z+1));
if (PySequence_Contains(self->solid_blocks, top_block_py) ||
PySequence_Contains(self->fluid_blocks, top_block_py)) {
spawnable = 0;
}
Py_DECREF(top_block_py);
}
/* if we passed all the checks, replace black_color with red_color */
if (spawnable) {
old_black_color = lighting->black_color;
lighting->black_color = self->red_color;
}
}
Py_DECREF(block_py);
}
/* draw normally */
rendermode_night.draw(data, state, src, mask);
/* reset black_color, if needed */
if (old_black_color != NULL) {
lighting->black_color = old_black_color;
}
rendermode_overlay.draw(data, state, src, mask);
}
RenderModeInterface rendermode_spawn = {
"spawn", "draws red where monsters can spawn at night",
"spawn", "draws a red overlay where monsters can spawn at night",
&rendermode_overlay,
sizeof(RenderModeSpawn),
rendermode_spawn_start,
rendermode_spawn_finish,

View File

@@ -26,6 +26,7 @@ static RenderModeInterface *render_modes[] = {
&rendermode_lighting,
&rendermode_night,
&rendermode_spawn,
&rendermode_cave,
NULL
};
@@ -97,5 +98,87 @@ PyObject *get_render_mode_info(PyObject *self, PyObject *args) {
}
Py_DECREF(info);
Py_RETURN_NONE;
return PyErr_Format(PyExc_ValueError, "invalid rendermode: \"%s\"", rendermode);
}
/* bindings -- get parent's name */
PyObject *get_render_mode_parent(PyObject *self, PyObject *args) {
const char *rendermode;
unsigned int i;
if (!PyArg_ParseTuple(args, "s", &rendermode))
return NULL;
for (i = 0; render_modes[i] != NULL; i++) {
if (strcmp(render_modes[i]->name, rendermode) == 0) {
if (render_modes[i]->parent) {
/* has parent */
return PyString_FromString(render_modes[i]->parent->name);
} else {
/* no parent */
Py_RETURN_NONE;
}
}
}
return PyErr_Format(PyExc_ValueError, "invalid rendermode: \"%s\"", rendermode);
}
/* bindings -- get list of inherited parents */
PyObject *get_render_mode_inheritance(PyObject *self, PyObject *args) {
const char *rendermode;
PyObject *parents;
unsigned int i;
RenderModeInterface *iface = NULL;
if (!PyArg_ParseTuple(args, "s", &rendermode))
return NULL;
parents = PyList_New(0);
if (!parents)
return NULL;
for (i = 0; render_modes[i] != NULL; i++) {
if (strcmp(render_modes[i]->name, rendermode) == 0) {
iface = render_modes[i];
break;
}
}
if (!iface) {
Py_DECREF(parents);
return PyErr_Format(PyExc_ValueError, "invalid rendermode: \"%s\"", rendermode);
}
while (iface) {
PyObject *name = PyString_FromString(iface->name);
PyList_Append(parents, name);
Py_DECREF(name);
iface = iface->parent;
}
PyList_Reverse(parents);
return parents;
}
/* bindings -- get list of (direct) children */
PyObject *get_render_mode_children(PyObject *self, PyObject *args) {
const char *rendermode;
PyObject *children;
unsigned int i;
if (!PyArg_ParseTuple(args, "s", &rendermode))
return NULL;
children = PyList_New(0);
if (!children)
return NULL;
for (i = 0; render_modes[i] != NULL; i++) {
if (render_modes[i]->parent && strcmp(render_modes[i]->parent->name, rendermode) == 0) {
PyObject *child_name = PyString_FromString(render_modes[i]->name);
PyList_Append(children, child_name);
Py_DECREF(child_name);
}
}
return children;
}

View File

@@ -38,12 +38,15 @@
#include <Python.h>
/* rendermode interface */
typedef struct {
typedef struct _RenderModeInterface RenderModeInterface;
struct _RenderModeInterface {
/* the name of this mode */
const char* name;
/* the short description of this render mode */
const char* description;
/* the rendermode this is derived from, or NULL */
RenderModeInterface *parent;
/* the size of the local storage for this rendermode */
unsigned int data_size;
@@ -54,13 +57,16 @@ typedef struct {
int (*occluded)(void *, RenderState *);
/* last two arguments are img and mask, from texture lookup */
void (*draw)(void *, RenderState *, PyObject *, PyObject *);
} RenderModeInterface;
};
/* figures out the render mode to use from the given ChunkRenderer */
RenderModeInterface *get_render_mode(RenderState *state);
/* python bindings */
PyObject *get_render_modes(PyObject *self, PyObject *args);
PyObject *get_render_mode_info(PyObject *self, PyObject *args);
PyObject *get_render_mode_parent(PyObject *self, PyObject *args);
PyObject *get_render_mode_inheritance(PyObject *self, PyObject *args);
PyObject *get_render_mode_children(PyObject *self, PyObject *args);
/* individual rendermode interface declarations follow */
@@ -79,6 +85,20 @@ typedef struct {
} RenderModeNormal;
extern RenderModeInterface rendermode_normal;
/* OVERLAY */
typedef struct {
/* top facemask and white color image, for drawing overlays */
PyObject *facemask_top, *white_color;
/* only show overlay on top of solid or fluid blocks */
PyObject *solid_blocks, *fluid_blocks;
/* can be overridden in derived classes to control
overlay alpha and color
last four vars are r, g, b, a out */
void (*get_color)(void *, RenderState *,
unsigned char *, unsigned char *, unsigned char *, unsigned char *);
} RenderModeOverlay;
extern RenderModeInterface rendermode_overlay;
/* LIGHTING */
typedef struct {
/* inherits from normal render mode */
@@ -109,14 +129,31 @@ extern RenderModeInterface rendermode_night;
/* SPAWN */
typedef struct {
/* inherits from night */
RenderModeNight parent;
/* inherits from overlay */
RenderModeOverlay parent;
/* used to figure out which blocks are spawnable */
PyObject *solid_blocks, *nospawn_blocks, *fluid_blocks;
/* replacement for black_color */
PyObject *red_color;
PyObject *nospawn_blocks;
PyObject *skylight, *blocklight;
} RenderModeSpawn;
extern RenderModeInterface rendermode_spawn;
/* CAVE */
typedef struct {
/* render blocks with lighting mode */
RenderModeNormal parent;
/* data used to know where the surface is */
PyObject *skylight;
PyObject *left_skylight;
PyObject *right_skylight;
PyObject *up_left_skylight;
PyObject *up_right_skylight;
/* colors used for tinting */
PyObject *depth_colors;
} RenderModeCave;
extern RenderModeInterface rendermode_cave;
#endif /* __RENDERMODES_H_INCLUDED__ */

View File

@@ -121,14 +121,9 @@ def transform_image(img, blockID=None):
"""
if blockID in (81,92): # cacti and cake
# Resize to 15x15, since the cactus and the cake textures are a little smaller than the other textures
img = img.resize((15, 15), Image.BILINEAR)
else:
# Resize to 17x17, since the diagonal is approximately 24 pixels, a nice
# even number that can be split in half twice
img = img.resize((17, 17), Image.BILINEAR)
# Resize to 17x17, since the diagonal is approximately 24 pixels, a nice
# even number that can be split in half twice
img = img.resize((17, 17), Image.ANTIALIAS)
# Build the Affine transformation matrix for this perspective
transform = numpy.matrix(numpy.identity(3))
@@ -168,7 +163,7 @@ def transform_image_side(img, blockID=None):
img = n
# Size of the cube side before shear
img = img.resize((12,12))
img = img.resize((12,12), Image.ANTIALIAS)
# Apply shear
transform = numpy.matrix(numpy.identity(3))
@@ -184,7 +179,7 @@ def transform_image_slope(img, blockID=None):
in the -y direction (reflect for +x direction). Used for minetracks"""
# Take the same size as trasform_image_side
img = img.resize((12,12))
img = img.resize((12,12), Image.ANTIALIAS)
# Apply shear
transform = numpy.matrix(numpy.identity(3))
@@ -210,9 +205,8 @@ def _build_block(top, side, blockID=None):
return img
side = transform_image_side(side, blockID)
otherside = side.transpose(Image.FLIP_LEFT_RIGHT)
# Darken the sides slightly. These methods also affect the alpha layer,
# so save them first (we don't want to "darken" the alpha layer making
# the block transparent)
@@ -224,10 +218,7 @@ def _build_block(top, side, blockID=None):
otherside.putalpha(othersidealpha)
## special case for non-block things
# TODO once torches are handled by generate_special_texture, remove
# them from this list
if blockID in (37,38,6,39,40,50,83,75,76): ## flowers, sapling, mushrooms, regular torch, reeds,
# redstone torch on, redstone torch off
if blockID in (37,38,6,39,40,83,30): ## flowers, sapling, mushrooms, reeds, web
#
# instead of pasting these blocks at the cube edges, place them in the middle:
# and omit the top
@@ -237,9 +228,9 @@ def _build_block(top, side, blockID=None):
if blockID in (81,): # cacti!
composite.alpha_over(img, side, (2,6), side)
composite.alpha_over(img, otherside, (10,6), otherside)
composite.alpha_over(img, top, (0,2), top)
composite.alpha_over(img, side, (1,6), side)
composite.alpha_over(img, otherside, (11,6), otherside)
composite.alpha_over(img, top, (0,0), top)
elif blockID in (44,): # half step
# shift each texture down 6 pixels
composite.alpha_over(img, side, (0,12), side)
@@ -277,9 +268,37 @@ def _build_full_block(top, side1, side2, side3, side4, bottom=None, blockID=None
side3 is in the -x (bottom left, north)
side4 is in the +y (bottom right, west)
A non transparent block uses top, side 3 and side 4
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.
NOTE: this method uses the top of the texture image (as done in
minecraft with beds)
"""
increment = 0
if isinstance(top, tuple):
increment = top[1]
crop_height = int(increment * 16./12.)
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))
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))
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))
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))
img = Image.new("RGBA", (24,24), (38,92,255,0))
# first back sides
@@ -292,7 +311,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), side1)
composite.alpha_over(img, side1, (0,0 + increment), side1)
if side2 != None :
@@ -303,7 +322,7 @@ 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), side2)
composite.alpha_over(img, side2, (12,0 + increment), side2)
if bottom != None :
bottom = transform_image(bottom, blockID)
@@ -318,7 +337,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), side3)
composite.alpha_over(img, side3, (0,6 + increment), side3)
if side4 != None :
side4 = transform_image_side(side4, blockID)
@@ -329,11 +348,11 @@ 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), side4)
composite.alpha_over(img, side4, (12,6 + increment), side4)
if top != None :
top = transform_image(top, blockID)
composite.alpha_over(img, top, (0,0), top)
composite.alpha_over(img, top, (0, increment), top)
return img
@@ -348,34 +367,34 @@ def _build_blockimages():
# texture array (terrain_images), which comes from terrain.png's cells, left to right top to
# bottom.
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
topids = [ -1, 1, 0, 2, 16, 4, 15, 17,205,205,237,237, 18, 19, 32, 33,
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, -1, -1, # Cloths are left out, sandstone (it has top, side, and bottom wich is ignored here), note block
34, -1, 52, 48, 49,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
-1, -1, -1, -1, -1, 13, 12, 29, 28, 23, 22, -1, -1, 7, 8, 35, # Gold/iron blocks? Doublestep? TNT from above?
-1, -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, 25, -1, 98, 24, -1, -1, 86, -1, -1, -1, # Torch from above? leaving out fire. Redstone wire? Crops/furnaces handled elsewhere. sign post
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, # door,ladder left out. Minecart rail orientation, redstone torches
-1, -1, -1, -1, -1, -1, -1, -1, -1, 51, 51, -1, -1, -1, 66, 67,
# 80 81 82 83 84 85 86 87 88 89 90 91
66, 69, 72, 73, 74, -1,102,103,104,105,-1, 102 # clay?
66, 69, 72, 73, 75, -1,102,103,104,105,-1, 102 # clay?
]
# NOTE: For non-block textures, the sideid is ignored, but can't be -1
# And side textures of all block types
# 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
sideids = [ -1, 1, 3, 2, 16, 4, 15, 17,205,205,237,237, 18, 19, 32, 33,
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, -1, -1,
34, -1, 52, 48, 49,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
-1, -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, 25,101, 98, 24, -1, -1, 86, -1, -1, -1,
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,
# 80 81 82 83 84 85 86 87 88 89 90 91
66, 69, 72, 73, 74,-1 ,118,103,104,105, -1, 118
66, 70, 72, 73, 74,-1 ,118,103,104,105, -1, 118
]
# This maps block id to the texture that goes on the side of the block
@@ -433,11 +452,41 @@ def generate_special_texture(blockID, data):
# all need to behandled here (and in chunkpy)
if blockID == 2: # grass
top = tintTexture(terrain_images[0],(115,175,71))
img = _build_block(top, terrain_images[3], 2)
# data & 0x10 means SNOW sides
side_img = terrain_images[3]
if data & 0x10:
side_img = terrain_images[68]
img = _build_block(terrain_images[0], side_img, 2)
if not data & 0x10:
colored = tintTexture(biome_grass_texture, (115, 175, 71))
composite.alpha_over(img, colored, (0, 0), colored)
return (img.convert("RGB"), img.split()[3])
if blockID == 6: # saplings
# The bottom two bits are used fo the sapling type, the top two
# bits are used as a grow-counter for the tree.
if data & 0x3 == 0: # usual saplings
toptexture = terrain_images[15]
sidetexture = terrain_images[15]
if data & 0x3 == 1: # spruce sapling
toptexture = terrain_images[63]
sidetexture = terrain_images[63]
if data & 0x3 == 2: # birch sapling
toptexture = terrain_images[79]
sidetexture = terrain_images[79]
if data & 0x3 == 3: # unused usual sapling
toptexture = terrain_images[15]
sidetexture = terrain_images[15]
img = _build_block(toptexture, sidetexture, blockID)
return (img.convert("RGB"),img.split()[3])
if blockID == 9: # spring water, flowing water and waterfall water
watertexture = _load_image("water.png")
@@ -488,17 +537,51 @@ def generate_special_texture(blockID, data):
t = tintTexture(terrain_images[52], (37, 118, 25))
img = _build_block(t, t, 18)
return (img.convert("RGB"), img.split()[3])
if blockID == 23: # dispenser
top = transform_image(terrain_images[62])
side1 = transform_image_side(terrain_images[46])
side2 = transform_image_side(terrain_images[45]).transpose(Image.FLIP_LEFT_RIGHT)
img = Image.new("RGBA", (24,24), (38,92,255,0))
composite.alpha_over(img, side1, (0,6), side1)
composite.alpha_over(img, side2, (12,6), side2)
composite.alpha_over(img, top, (0,0), top)
if blockID == 26: # bed
increment = 5
left_face = None
right_face = None
if data & 0x8 == 0x8: # head of the bed
top = terrain_images[135]
if data & 0x00 == 0x00: # head pointing to West
top = top.copy().rotate(270)
left_face = terrain_images[151]
right_face = terrain_images[152]
if data & 0x01 == 0x01: # ... North
top = top.rotate(270)
left_face = terrain_images[152]
right_face = terrain_images[151]
if data & 0x02 == 0x02: # East
top = top.rotate(180)
left_face = terrain_images[151].transpose(Image.FLIP_LEFT_RIGHT)
right_face = None
if data & 0x03 == 0x03: # South
right_face = None
right_face = terrain_images[151].transpose(Image.FLIP_LEFT_RIGHT)
else: # foot of the bed
top = terrain_images[134]
if data & 0x00 == 0x00: # head pointing to West
top = top.rotate(270)
left_face = terrain_images[150]
right_face = None
if data & 0x01 == 0x01: # ... North
top = top.rotate(270)
left_face = None
right_face = terrain_images[150]
if data & 0x02 == 0x02: # East
top = top.rotate(180)
left_face = terrain_images[150].transpose(Image.FLIP_LEFT_RIGHT)
right_face = terrain_images[149].transpose(Image.FLIP_LEFT_RIGHT)
if data & 0x03 == 0x03: # South
left_face = terrain_images[149]
right_face = terrain_images[150].transpose(Image.FLIP_LEFT_RIGHT)
top = (top, increment)
img = _build_full_block(top, None, None, left_face, right_face)
return (img.convert("RGB"), img.split()[3])
@@ -731,6 +814,48 @@ def generate_special_texture(blockID, data):
return (img.convert("RGB"), img.split()[3])
if blockID == 54: # chests
# First to bits of the pseudo data store if it's a single chest
# or it's a double chest, first half or second half.
# The to last bits store the orientation.
top = terrain_images[25]
side = terrain_images[26]
if data & 12 == 0: # single chest
front = terrain_images[27]
back = terrain_images[26]
elif data & 12 == 4: # double, first half
front = terrain_images[41]
back = terrain_images[57]
elif data & 12 == 8: # double, second half
front = terrain_images[42]
back = terrain_images[58]
else: # just in case
front = terrain_images[25]
side = terrain_images[25]
back = terrain_images[25]
if data & 3 == 0: # facing west
img = _build_full_block(top, None, None, side, front)
elif data & 3 == 1: # north
img = _build_full_block(top, None, None, front, side)
elif data & 3 == 2: # east
img = _build_full_block(top, None, None, side, back)
elif data & 3 == 3: # south
img = _build_full_block(top, None, None, back, side)
else:
img = _build_full_block(top, None, None, back, side)
return (img.convert("RGB"), img.split()[3])
if blockID == 55: # redstone wire
@@ -832,29 +957,28 @@ def generate_special_texture(blockID, data):
return (img.convert("RGB"), img.split()[3])
if blockID == 61: #furnace
top = transform_image(terrain_images[62])
side1 = transform_image_side(terrain_images[45])
side2 = transform_image_side(terrain_images[44]).transpose(Image.FLIP_LEFT_RIGHT)
if blockID in (61, 62, 23): #furnace and burning furnace
top = terrain_images[62]
side = terrain_images[45]
img = Image.new("RGBA", (24,24), (38,92,255,0))
if blockID == 61:
front = terrain_images[44]
composite.alpha_over(img, side1, (0,6), side1)
composite.alpha_over(img, side2, (12,6), side2)
composite.alpha_over(img, top, (0,0), top)
return (img.convert("RGB"), img.split()[3])
elif blockID == 62:
front = terrain_images[45+16]
elif blockID == 23:
front = terrain_images[46]
if blockID == 62: # lit furnace
top = transform_image(terrain_images[62])
side1 = transform_image_side(terrain_images[45])
side2 = transform_image_side(terrain_images[45+16]).transpose(Image.FLIP_LEFT_RIGHT)
if data == 3: # pointing west
img = _build_full_block(top, None, None, side, front)
elif data == 4: # pointing north
img = _build_full_block(top, None, None, front, side)
else: # in any other direction the front can't be seen
img = _build_full_block(top, None, None, side, side)
img = Image.new("RGBA", (24,24), (38,92,255,0))
composite.alpha_over(img, side1, (0,6), side1)
composite.alpha_over(img, side2, (12,6), side2)
composite.alpha_over(img, top, (0,0), top)
return (img.convert("RGB"), img.split()[3])
@@ -938,11 +1062,27 @@ def generate_special_texture(blockID, data):
return (img.convert("RGB"), img.split()[3])
if blockID == 66: # minetrack:
raw_straight = terrain_images[128]
raw_corner = terrain_images[112]
if blockID in (27, 28, 66): # minetrack:
if blockID == 27: # powered rail
if data & 0x8 == 0: # unpowered
raw_straight = terrain_images[163]
raw_corner = terrain_images[112] # they don't exist but make the code
# much simplier
elif data & 0x8 == 0x8: # powered
raw_straight = terrain_images[179]
raw_corner = terrain_images[112] # leave corners for code simplicity
# filter the 'powered' bit
data = data & 0x7
elif blockID == 28: # detector rail
raw_straight = terrain_images[195]
raw_corner = terrain_images[112] # leave corners for code simplicity
elif blockID == 66: # normal rail
raw_straight = terrain_images[128]
raw_corner = terrain_images[112]
## use transform_image to scale and shear
if data == 0:
track = transform_image(raw_straight, blockID)
@@ -1034,9 +1174,9 @@ def generate_special_texture(blockID, data):
# Compose the fence big stick
fence_big = Image.new("RGBA", (24,24), (38,92,255,0))
composite.alpha_over(fence_big,fence_side, (4,4),fence_side)
composite.alpha_over(fence_big,fence_other_side, (8,4),fence_other_side)
composite.alpha_over(fence_big,fence_top, (-1,1),fence_top)
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)
# Now render the small sticks.
# Create needed images
@@ -1098,16 +1238,36 @@ def generate_special_texture(blockID, data):
if blockID in (86,91): # pumpkins, jack-o-lantern
top = transform_image(terrain_images[102])
top = terrain_images[102]
frontID = 119 if blockID == 86 else 120
side1 = transform_image_side(terrain_images[frontID])
side2 = transform_image_side(terrain_images[118]).transpose(Image.FLIP_LEFT_RIGHT)
front = terrain_images[frontID]
side = terrain_images[118]
if data == 0: # pointing west
img = _build_full_block(top, None, None, side, front)
elif data == 1: # pointing north
img = _build_full_block(top, None, None, front, side)
else: # in any other direction the front can't be seen
img = _build_full_block(top, None, None, side, side)
return (img.convert("RGB"), img.split()[3])
if blockID == 90: # portal
portaltexture = _load_image("portal.png")
img = Image.new("RGBA", (24,24), (38,92,255,0))
composite.alpha_over(img, side1, (0,6), side1)
composite.alpha_over(img, side2, (12,6), side2)
composite.alpha_over(img, top, (0,0), top)
side = transform_image_side(portaltexture)
otherside = side.transpose(Image.FLIP_TOP_BOTTOM)
if data in (1,4):
composite.alpha_over(img, side, (5,4), side)
if data in (2,8):
composite.alpha_over(img, otherside, (5,4), otherside)
return (img.convert("RGB"), img.split()[3])
@@ -1128,13 +1288,129 @@ def generate_special_texture(blockID, data):
img = Image.new("RGBA", (24,24), (38,92,255,0))
composite.alpha_over(img, side, (2,12), side)
composite.alpha_over(img, otherside, (10,12), otherside)
composite.alpha_over(img, top, (0,8), top)
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, top, (0,6), top)
return (img.convert("RGB"), img.split()[3])
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.
top = terrain_images[131] if blockID == 93 else terrain_images[147]
side = terrain_images[5]
increment = 9
#~ composite.alpha_over(img, side, (2,6), side)
#~ composite.alpha_over(img, otherside, (10,6), otherside)
#~ composite.alpha_over(img, top, (0,2), top)
if (data & 0x3) == 0: # pointing east
pass
if (data & 0x3) == 1: # pointing south
top = top.rotate(270)
if (data & 0x3) == 2: # pointing west
top = top.rotate(180)
if (data & 0x3) == 3: # pointing north
top = top.rotate(90)
img = _build_full_block( (top, increment), None, None, side, side)
# 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))
# touch up the 3d effect with big rectangles, just in case, for other texture packs
ImageDraw.Draw(torch).rectangle((0,24,10,15),outline=(0,0,0,0),fill=(0,0,0,0))
ImageDraw.Draw(torch).rectangle((12,15,24,24),outline=(0,0,0,0),fill=(0,0,0,0))
# torch positions for every redstone torch orientation.
#
# This is a horrible list of torch orientations. I tried to
# obtain these orientations by rotating the positions for one
# orientation, but pixel rounding is horrible and messes the
# torches.
if (data & 0x3) == 0: # pointing east
if (data & 0xC) == 0: # one tick delay
moving_torch = (1,1)
static_torch = (-3,-1)
elif (data & 0xC) == 4: # two ticks delay
moving_torch = (2,2)
static_torch = (-3,-1)
elif (data & 0xC) == 8: # three ticks delay
moving_torch = (3,2)
static_torch = (-3,-1)
elif (data & 0xC) == 12: # four ticks delay
moving_torch = (4,3)
static_torch = (-3,-1)
elif (data & 0x3) == 1: # pointing south
if (data & 0xC) == 0: # one tick delay
moving_torch = (1,1)
static_torch = (5,-1)
elif (data & 0xC) == 4: # two ticks delay
moving_torch = (2,0)
static_torch = (5,-1)
elif (data & 0xC) == 8: # three ticks delay
moving_torch = (3,0)
static_torch = (5,-1)
elif (data & 0xC) == 12: # four ticks delay
moving_torch = (4,-1)
static_torch = (5,-1)
elif (data & 0x3) == 2: # pointing west
if (data & 0xC) == 0: # one tick delay
moving_torch = (1,1)
static_torch = (5,3)
elif (data & 0xC) == 4: # two ticks delay
moving_torch = (0,0)
static_torch = (5,3)
elif (data & 0xC) == 8: # three ticks delay
moving_torch = (-1,0)
static_torch = (5,3)
elif (data & 0xC) == 12: # four ticks delay
moving_torch = (-2,-1)
static_torch = (5,3)
elif (data & 0x3) == 3: # pointing north
if (data & 0xC) == 0: # one tick delay
moving_torch = (1,1)
static_torch = (-3,3)
elif (data & 0xC) == 4: # two ticks delay
moving_torch = (2,0)
static_torch = (-3,3)
elif (data & 0xC) == 8: # three ticks delay
moving_torch = (3,0)
static_torch = (-3,3)
elif (data & 0xC) == 12: # four ticks delay
moving_torch = (4,-1)
static_torch = (-3,3)
# this paste order it's ok for east and south orientation
# but it's wrong for north and west orientations. But using the
# default texture pack the torches are small enough to no overlap.
composite.alpha_over(img, torch, static_torch, torch)
composite.alpha_over(img, torch, moving_torch, torch)
return (img.convert("RGB"), img.split()[3])
@@ -1147,7 +1423,7 @@ def tintTexture(im, c):
return i
# generate biome (still grayscale) leaf, grass textures
biome_grass_texture = _build_block(terrain_images[0], terrain_images[3], 2)
biome_grass_texture = _build_block(terrain_images[0], terrain_images[38], 2)
biome_leaf_texture = _build_block(terrain_images[52], terrain_images[52], 18)
@@ -1215,28 +1491,34 @@ 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, 9, 17, 18, 23, 35, 43, 44, 50, 51, 53, 55, 58, 59, \
61, 62, 64, 65, 66, 67, 71, 75, 76, 85, 86, 91, 92])
special_blocks = set([ 2, 6, 9, 17, 18, 26, 23, 27, 28, 35, 43, 44, 50,
51, 53, 54, 55, 58, 59, 61, 62, 64, 65, 66, 67, 71,
75, 76, 85, 86, 90, 91, 92, 93, 94])
# this is a map of special blockIDs to a list of all
# possible values for ancillary data that it might have.
special_map = {}
special_map[9] = range(32) # water: spring,flowing, waterfall, and others (unknown) ancildata values.
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(4) # wood: normal, birch and pine
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[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
special_map[50] = (1,2,3,4,5) # torch, position in the block
special_map[51] = range(16) # fire, position in the block (not implemented)
special_map[53] = range(4) # wooden stairs, orientation
special_map[55] = range(128) # redstone wire, all the possible combinations
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[59] = range(8) # crops, grow from 0 to 7
special_map[61] = range(6) # furnace, orientation (not implemented)
special_map[62] = range(6) # burning furnace, orientation (not implemented)
special_map[61] = range(6) # furnace, orientation
special_map[62] = range(6) # burning furnace, orientation
special_map[64] = range(16) # wooden door, open/close and orientation
special_map[65] = (2,3,4,5) # ladder, orientation
special_map[66] = range(10) # minecrart tracks, orientation, slope
@@ -1244,18 +1526,22 @@ special_map[67] = range(4) # cobblestone stairs, 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[85] = range(17) # fences, all the possible combination
special_map[86] = range(5) # pumpkin, orientation (not implemented)
special_map[91] = range(5) # jack-o-lantern, orientation (not implemented)
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)
# grass and leaves are graysacle in terrain.png
# we treat them as special so we can manually tint them
# it is unknown how the specific tint (biomes) is calculated
special_map[2] = range(11) # grass, grass has not ancildata but is used
# in the mod WildGrass, and this small fix
# shows the map as expected, and is harmless
# for normal maps
# also, 0x10 means SNOW sides
special_map[2] = range(11) + [0x10,] # grass, grass has not ancildata but is
# used in the mod WildGrass, and this
# small fix shows the map as expected,
# and is harmless for normal maps
special_map[18] = range(16) # leaves, birch, normal or pine leaves (not implemented)

View File

@@ -41,7 +41,7 @@ def findGitVersion():
with open(os.path.join(this_dir,".git","HEAD")) as f:
data = f.read().strip()
if data.startswith("ref: "):
if not os.path.exists(os.path.join(this_dir,data[5:])):
if not os.path.exists(os.path.join(this_dir, ".git", data[5:])):
return data
with open(os.path.join(this_dir, ".git", data[5:])) as g:
return g.read().strip()

View File

@@ -16,6 +16,7 @@
import functools
import os
import os.path
from glob import glob
import multiprocessing
import Queue
import sys
@@ -287,12 +288,10 @@ class World(object):
p = f.split(".")
yield (int(p[1]), int(p[2]), join(self.worlddir, 'region', f))
else:
for dirpath, dirnames, filenames in os.walk(os.path.join(self.worlddir, 'region')):
if not dirnames and filenames and "DIM-1" not in dirpath:
for f in filenames:
if f.startswith("r.") and f.endswith(".mcr"):
p = f.split(".")
yield (int(p[1]), int(p[2]), join(dirpath, f))
for path in glob(os.path.join(self.worlddir, 'region') + "/r.*.*.mcr"):
dirpath, f = os.path.split(path)
p = f.split(".")
yield (int(p[1]), int(p[2]), join(dirpath, f))
def get_save_dir():
"""Returns the path to the local saves directory