0

Merge remote branch 'origin/master' into JSObserver

This commit is contained in:
Andrew Chin
2012-06-10 14:59:17 -04:00
36 changed files with 1179 additions and 151 deletions

View File

@@ -87,6 +87,7 @@ directory.
dump['CONST']['image'] = {
'defaultMarker': 'signpost.png',
'signMarker': 'signpost_icon.png',
'bedMarker': 'bed.png',
'compass': 'compass_upper-left.png',
'spawnMarker': 'http://google-maps-icons.googlecode.com/files/home.png',
'queryMarker': 'http://google-maps-icons.googlecode.com/files/regroup.png'

View File

View File

@@ -0,0 +1,214 @@
#!/usr/bin/python2
'''
genPOI.py
Scans regionsets for TileEntities and Entities, filters them, and writes out
POI/marker info.
A markerSet is list of POIs to display on a tileset. It has a display name,
and a group name.
markersDB.js holds a list of POIs in each group
markers.js holds a list of which markerSets are attached to each tileSet
'''
import os
import logging
import json
import sys
from optparse import OptionParser
from overviewer_core import logger
from overviewer_core import nbt
from overviewer_core import configParser, world
def handleSigns(rset, outputdir, render, rname):
# if we're already handled the POIs for this region regionset, do nothing
if hasattr(rset, "_pois"):
return
logging.info("Looking for entities in %r", rset)
filters = render['markers']
rset._pois = dict(TileEntities=[], Entities=[])
for (x,z,mtime) in rset.iterate_chunks():
data = rset.get_chunk(x,z)
rset._pois['TileEntities'] += data['TileEntities']
rset._pois['Entities'] += data['Entities']
logging.info("Done.")
def handlePlayers(rset, render, worldpath):
if not hasattr(rset, "_pois"):
rset._pois = dict(TileEntities=[], Entities=[])
# only handle this region set once
if 'Players' in rset._pois:
return
dimension = {'overworld': 0,
'nether': -1,
'end': 1,
'default': 0}[render['dimension']]
playerdir = os.path.join(worldpath, "players")
if os.path.isdir(playerdir):
playerfiles = os.listdir(playerdir)
isSinglePlayer = False
else:
playerfiles = [os.path.join(worldpath, "level.dat")]
isSinglePlayer = True
rset._pois['Players'] = []
for playerfile in playerfiles:
try:
data = nbt.load(os.path.join(playerdir, playerfile))[1]
if isSinglePlayer:
data = data['Data']['Player']
except IOError:
logging.warning("Skipping bad player dat file %r", playerfile)
continue
playername = playerfile.split(".")[0]
if isSinglePlayer:
playername = 'Player'
if data['Dimension'] == dimension:
# Position at last logout
data['id'] = "Player"
data['EntityId'] = playername
data['x'] = int(data['Pos'][0])
data['y'] = int(data['Pos'][1])
data['z'] = int(data['Pos'][2])
rset._pois['Players'].append(data)
if "SpawnX" in data and dimension == 0:
# Spawn position (bed or main spawn)
spawn = {"id": "PlayerSpawn",
"EntityId": playername,
"x": data['SpawnX'],
"y": data['SpawnY'],
"z": data['SpawnZ']}
rset._pois['Players'].append(spawn)
def main():
if os.path.basename(sys.argv[0]) == """genPOI.py""":
helptext = """genPOI.py
%prog --config=<config file> [--quiet]"""
else:
helptext = """genPOI
%prog --genpoi --config=<config file> [--quiet]"""
logger.configure()
parser = OptionParser(usage=helptext)
parser.add_option("--config", dest="config", action="store", help="Specify the config file to use.")
parser.add_option("--quiet", dest="quiet", action="count", help="Reduce logging output")
options, args = parser.parse_args()
if not options.config:
parser.print_help()
return
if options.quiet > 0:
logger.configure(logging.WARN, False)
# Parse the config file
mw_parser = configParser.MultiWorldParser()
mw_parser.parse(options.config)
try:
config = mw_parser.get_validated_config()
except Exception:
logging.exception("An error was encountered with your configuration. See the info below.")
return 1
destdir = config['outputdir']
# saves us from creating the same World object over and over again
worldcache = {}
markersets = set()
markers = dict()
for rname, render in config['renders'].iteritems():
try:
worldpath = config['worlds'][render['world']]
except KeyError:
logging.error("Render %s's world is '%s', but I could not find a corresponding entry in the worlds dictionary.",
rname, render['world'])
return 1
render['worldname_orig'] = render['world']
render['world'] = worldpath
# find or create the world object
if (render['world'] not in worldcache):
w = world.World(render['world'])
worldcache[render['world']] = w
else:
w = worldcache[render['world']]
rset = w.get_regionset(render['dimension'])
if rset == None: # indicates no such dimension was found:
logging.error("Sorry, you requested dimension '%s' for %s, but I couldn't find it", render['dimension'], render_name)
return 1
for f in render['markers']:
d = dict(icon="signpost_icon.png", createInfoWindow=True)
d.update(f)
markersets.add(((d['name'], d['filterFunction']), rset))
name = f['name'].replace(" ","_") + hex(hash(f['filterFunction']))[-4:] + "_" + hex(hash(rset))[-4:]
try:
l = markers[rname]
l.append(dict(groupName=name, displayName = f['name'], icon=d['icon'], createInfoWindow=d['createInfoWindow']))
except KeyError:
markers[rname] = [dict(groupName=name, displayName=f['name'], icon=d['icon'], createInfoWindow=d['createInfoWindow']),]
handleSigns(rset, os.path.join(destdir, rname), render, rname)
handlePlayers(rset, render, worldpath)
logging.info("Done scanning regions")
logging.info("Writing out javascript files")
markerSetDict = dict()
for (flter, rset) in markersets:
# generate a unique name for this markerset. it will not be user visible
filter_name = flter[0]
filter_function = flter[1]
name = filter_name.replace(" ","_") + hex(hash(filter_function))[-4:] + "_" + hex(hash(rset))[-4:]
markerSetDict[name] = dict(created=False, raw=[], name=filter_name)
for poi in rset._pois['TileEntities']:
result = filter_function(poi)
if result:
d = dict(x=poi['x'], y=poi['y'], z=poi['z'], text=result)
if "icon" in poi:
d.update({"icon": poi['icon']})
if "createInfoWindow" in poi:
d.update({"createInfoWindow": poi['createInfoWindow']})
markerSetDict[name]['raw'].append(d)
for poi in rset._pois['Players']:
result = filter_function(poi)
if result:
d = dict(x=poi['x'], y=poi['y'], z=poi['z'], text=result)
if "icon" in poi:
d.update({"icon": poi['icon']})
if "createInfoWindow" in poi:
d.update({"createInfoWindow": poi['createInfoWindow']})
markerSetDict[name]['raw'].append(d)
#print markerSetDict
with open(os.path.join(destdir, "markersDB.js"), "w") as output:
output.write("var markersDB=")
json.dump(markerSetDict, output, indent=2)
output.write(";\n");
with open(os.path.join(destdir, "markers.js"), "w") as output:
output.write("var markers=")
json.dump(markers, output, indent=2)
output.write(";\n");
with open(os.path.join(destdir, "baseMarkers.js"), "w") as output:
output.write("overviewer.util.injectMarkerScript('markersDB.js');\n")
output.write("overviewer.util.injectMarkerScript('markers.js');\n")
output.write("overviewer.collections.haveSigns=true;\n")
logging.info("Done")
if __name__ == "__main__":
main()

View File

@@ -67,6 +67,8 @@ overviewer.util = {
signs.registerEvents(signs);
}
var overlayControl = new overviewer.views.OverlayControlView();
var spawnmarker = new overviewer.views.SpawnIconView();
// Update coords on mousemove
@@ -85,6 +87,9 @@ overviewer.util = {
compass.render();
spawnmarker.render();
// update list of spawn overlays
overlayControl.render();
// re-center on the last viewport
var currentWorldView = overviewer.mapModel.get("currentWorldView");
if (currentWorldView.options.lastViewport) {
@@ -117,8 +122,6 @@ overviewer.util = {
});
var worldSelector = new overviewer.views.WorldSelectorView({tagName:'DIV'});
overviewer.collections.worlds.bind("add", worldSelector.render, worldSelector);
// hook up some events
@@ -129,6 +132,11 @@ overviewer.util = {
// Jump to the hash if given
overviewer.util.initHash();
// create this control after initHash so it can correctly select the current world
var worldSelector = new overviewer.views.WorldSelectorView({tagName:'DIV'});
overviewer.collections.worlds.bind("add", worldSelector.render, worldSelector);
overviewer.util.initializeMarkers();
/*

View File

@@ -4,8 +4,19 @@ overviewer.views= {}
overviewer.views.WorldView = Backbone.View.extend({
initialize: function(opts) {
this.options.mapTypes = [];
this.options.overlayMapTypes = [];
this.options.mapTypeIds = [];
this.options.overlayMapTypeIds = [];
var curTileSet = this.model.get("tileSets").at(0);
var spawn = curTileSet.get("spawn");
if (spawn == "false") {
var spawn = [0,64,0];
}
this.options.lastViewport = [spawn[0],spawn[1],spawn[2],curTileSet.get("defaultZoom")];
this.model.get("tileSets").each(function(tset, index, list) {
// ignore overlays:
var ops = {
getTileUrl: overviewer.gmap.getTileUrlGenerator(tset.get("path"), tset.get("base"), tset.get("imgextension")),
'tileSize': new google.maps.Size(
@@ -20,11 +31,24 @@ overviewer.views.WorldView = Backbone.View.extend({
newMapType.shortname = tset.get("name");
newMapType.alt = "Minecraft " + tset.get("name") + " Map";
newMapType.projection = new overviewer.classes.MapProjection();
this.options.mapTypes.push(newMapType);
this.options.mapTypeIds.push(overviewerConfig.CONST.mapDivId + this.model.get("name") + tset.get("name"));
newMapType._ov_tileSet = tset;
if (tset.get("isOverlay")) {
newMapType.tiles = tset.get("tilesets");
this.options.overlayMapTypes.push(newMapType);
this.options.overlayMapTypeIds.push(overviewerConfig.CONST.mapDivId + this.model.get("name") + tset.get("name"));
} else {
this.options.mapTypes.push(newMapType);
this.options.mapTypeIds.push(overviewerConfig.CONST.mapDivId + this.model.get("name") + tset.get("name"));
}
}, this);
this.model.get("tileSets").each(function(tset, index, list) {
// ignore non-overlays:
if (!tset.get("isOverlay")) { return; };
});
},
});
@@ -33,6 +57,8 @@ overviewer.views.WorldView = Backbone.View.extend({
overviewer.views.WorldSelectorView = Backbone.View.extend({
initialize: function() {
if(overviewer.collections.worldViews.length > 1) {
$(this.el).addClass("customControl");
// a div will have already been created for us, we just
// need to register it with the google maps control
var selectBox = document.createElement('select');
@@ -40,6 +66,9 @@ overviewer.views.WorldSelectorView = Backbone.View.extend({
var o = document.createElement("option");
o.value = elem.model.get("name");
o.innerHTML = elem.model.get("name");
if (elem.model == overviewer.mapModel.get("currentWorldView").model) {
o.selected=true;
}
$(o).data("viewObj", elem);
selectBox.appendChild(o);
@@ -139,17 +168,16 @@ overviewer.views.GoogleMapView = Backbone.View.extend({
var curWorld = this.model.get("currentWorldView").model;
var curTset = curWorld.get("tileSets").at(0);
var spawn = curTset.get("spawn");
if (spawn == "false") {
var spawn = [0,64,0];
}
var mapcenter = overviewer.util.fromWorldToLatLng(
spawn[0],
spawn[1],
spawn[2],
curTset);
/*
var defaultCenter = overviewer.util.fromWorldToLatLng(
overviewerConfig.map.center[0],
overviewerConfig.map.center[1],
overviewerConfig.map.center[2],
curTset.get("defaultZoom"));
*/
var lat = 0.62939453125;// TODO defaultCenter.lat();
var lng = 0.38525390625; // TODO defaultCenter.lng();
var mapcenter = new google.maps.LatLng(lat, lng);
this.options.mapTypes=[];
this.options.mapTypeIds=[];
@@ -208,7 +236,7 @@ overviewer.views.GoogleMapView = Backbone.View.extend({
var gmapCurrent = overviewer.map.getMapTypeId();
for (id in currentWorldView.options.mapTypeIds) {
if (currentWorldView.options.mapTypeIds[id] == gmapCurrent) {
this.options.currentTileSet = currentWorldView.model.get("tileSets").at(id);
this.options.currentTileSet = currentWorldView.options.mapTypes[id]._ov_tileSet;
}
}
@@ -220,6 +248,114 @@ overviewer.views.GoogleMapView = Backbone.View.extend({
});
/**
* OverlayControlView
*/
overviewer.views.OverlayControlView = Backbone.View.extend({
/** OverlayControlVIew::initialize
*/
initialize: function(opts) {
$(this.el).addClass("customControl");
overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(this.el);
},
registerEvents: function(me) {
overviewer.mapModel.bind("change:currentWorldView", me.render, me);
},
/**
* OverlayControlView::render
*/
render: function() {
this.el.innerHTML="";
// hide all visible overlays:
overviewer.map.overlayMapTypes.clear()
// if this world has no overlays, don't create this control
var mapTypes = overviewer.mapModel.get('currentWorldView').options.overlayMapTypes;
if (mapTypes.length == 0) { return; }
var controlText = document.createElement('DIV');
controlText.innerHTML = "Overlays";
var controlBorder = document.createElement('DIV');
$(controlBorder).addClass('top');
this.el.appendChild(controlBorder);
controlBorder.appendChild(controlText);
var dropdownDiv = document.createElement('DIV');
$(dropdownDiv).addClass('dropDown');
this.el.appendChild(dropdownDiv);
dropdownDiv.innerHTML='';
$(controlText).click(function() {
$(controlBorder).toggleClass('top-active');
$(dropdownDiv).toggle();
});
var currentTileSetPath = overviewer.mapView.options.currentTileSet.get('path');
for (i in mapTypes) {
var mt = mapTypes[i];
// if this overlay specifies a list of valid tilesets, then skip over any invalid tilesets
if ((mt.tiles.length > 0) && (mt.tiles.indexOf(currentTileSetPath) ==-1)) {
continue;
}
this.addItem({label: mt.name,
name: mt.name,
mt: mt,
action: function(this_item, checked) {
if (checked) {
overviewer.map.overlayMapTypes.push(this_item.mt);
} else {
var idx_to_delete = -1;
overviewer.map.overlayMapTypes.forEach(function(e, j) {
if (e == this_item.mt) {
idx_to_delete = j;
}
});
if (idx_to_delete >= 0) {
overviewer.map.overlayMapTypes.removeAt(idx_to_delete);
}
}
}
});
}
},
addItem: function(item) {
var itemDiv = document.createElement('div');
var itemInput = document.createElement('input');
itemInput.type='checkbox';
// if this overlay is already visible, set the checkbox
// to checked
overviewer.map.overlayMapTypes.forEach(function(e, j) {
if (e == item.mt) {
itemInput.checked=true;
}
});
// give it a name
$(itemInput).attr("_mc_overlayname", item.name);
jQuery(itemInput).click((function(local_item) {
return function(e) {
item.action(local_item, e.target.checked);
};
})(item));
this.$(".dropDown")[0].appendChild(itemDiv);
itemDiv.appendChild(itemInput);
var textNode = document.createElement('text');
textNode.innerHTML = item.label + '<br/>';
itemDiv.appendChild(textNode);
}
});
/**
@@ -285,7 +421,7 @@ overviewer.views.SignControlView = Backbone.View.extend({
//var dataRoot = overviewer.collections.markerInfo[curMarkerSet];
var dataRoot = markers[curMarkerSet];
this.el.innerHTML=""
this.el.innerHTML="";
// if we have no markerSets for this tileset, do nothing:
if (!dataRoot) { return; }
@@ -322,7 +458,6 @@ overviewer.views.SignControlView = Backbone.View.extend({
}});
}
iconURL = overviewerConfig.CONST.image.signMarker;
//dataRoot['markers'] = [];
//
for (i in dataRoot) {
@@ -330,16 +465,25 @@ overviewer.views.SignControlView = Backbone.View.extend({
if (!markersDB[groupName].created) {
for (j in markersDB[groupName].raw) {
var entity = markersDB[groupName].raw[j];
if (entity['icon']) {
iconURL = entity['icon'];
} else {
iconURL = dataRoot[i].icon;
}
var marker = new google.maps.Marker({
'position': overviewer.util.fromWorldToLatLng(entity.x,
entity.y, entity.z, overviewer.mapView.options.currentTileSet),
'map': overviewer.map,
'title': jQuery.trim(entity.Text1 + "\n" + entity.Text2 + "\n" + entity.Text3 + "\n" + entity.Text4),
'title': jQuery.trim(entity.text),
'icon': iconURL,
'visible': false
});
if (entity['id'] == 'Sign') {
if(entity['createInfoWindow'] == true) {
overviewer.util.createMarkerInfoWindow(marker);
} else {
if(dataRoot[i].createInfoWindow == true) {
overviewer.util.createMarkerInfoWindow(marker);
}
}
jQuery.extend(entity, {markerObj: marker});
}
@@ -368,13 +512,13 @@ overviewer.views.SignControlView = Backbone.View.extend({
var textNode = document.createElement('text');
if(item.icon) {
textNode.innerHTML = '<img width="15" height="15" src="' +
item.icon + '">' + item.label + '<br/>';
item.icon + '">' + item.label + '&nbsp;<br/>';
} else {
textNode.innerHTML = item.label + '<br/>';
textNode.innerHTML = item.label + '&nbsp;<br/>';
}
itemDiv.appendChild(textNode);
itemDiv.style.whiteSpace = "nowrap";
},
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

View File

@@ -40,6 +40,17 @@ body {
font-family: Arial, sans-serif;
}
.customControl > select {
font-size: 12px;
line-height: 160%;
text-align: center;
border: 1px solid #A9BBDF;
border-radius: 2px 2px;
box-shadow: rgba(0, 0, 0, 0.347656) 2px 2px 3px;
}
.customControl > div.top {
font-size: 12px;
line-height: 160%;

View File

@@ -254,7 +254,7 @@ class ANSIColorFormatter(HighlightingFormatter):
# No coloring if it's not to be highlighted or colored
return logging.Formatter.format(self, record)
def configure(loglevel=logging.INFO, verbose=False):
def configure(loglevel=logging.INFO, verbose=False, simple=False):
"""Configures the root logger to our liking
For a non-standard loglevel, pass in the level with which to configure the handler.
@@ -267,15 +267,17 @@ def configure(loglevel=logging.INFO, verbose=False):
logger = logging.getLogger()
outstream = sys.stderr
outstream = sys.stdout
if simple:
formatter = DumbFormatter(verbose)
if platform.system() == 'Windows':
elif platform.system() == 'Windows':
# Our custom output stream processor knows how to deal with select ANSI
# color escape sequences
outstream = WindowsOutputStream()
outstream = WindowsOutputStream(outstream)
formatter = ANSIColorFormatter(verbose)
elif sys.stderr.isatty():
elif outstream.isatty():
# terminal logging with ANSI color
formatter = ANSIColorFormatter(verbose)

View File

@@ -52,6 +52,10 @@ class Nether(RenderPrimitive):
class HeightFading(RenderPrimitive):
name = "height-fading"
options = {
# 128 is *WRONG*, it should be 64. but we're grandfathered in for now
"sealevel": ("target sea level", 128),
}
black_color = Image.new("RGB", (24,24), (0,0,0))
white_color = Image.new("RGB", (24,24), (255,255,255))
@@ -62,6 +66,15 @@ class Depth(RenderPrimitive):
"min": ("lowest level of blocks to render", 0),
"max": ("highest level of blocks to render", 255),
}
class Exposed(RenderPrimitive):
name = "exposed"
options = {
"mode": ("0 = exposed blocks only, 1 = unexposed blocks only", 0),
}
class NoFluids(RenderPrimitive):
name = "no-fluids"
class EdgeLines(RenderPrimitive):
name = "edge-lines"
@@ -182,12 +195,21 @@ class Overlay(RenderPrimitive):
class SpawnOverlay(Overlay):
name = "overlay-spawn"
class SlimeOverlay(Overlay):
name = "overlay-slime"
class MineralOverlay(Overlay):
name = "overlay-mineral"
options = {
'minerals' : ('a list of (blockid, (r, g, b)) tuples for coloring minerals', None),
}
class Hide(RenderPrimitive):
name = "hide"
options = {
'blocks' : ('a list of blockids or (blockid, data) tuples of blocks to hide', []),
}
# Built-in rendermodes for your convenience!
normal = [Base(), EdgeLines()]
lighting = [Base(), EdgeLines(), Lighting()]

View File

@@ -75,11 +75,13 @@ renders = Setting(required=True, default=util.OrderedDict(),
"nomarkers": Setting(required=False, validator=validateBool, default=None),
"texturepath": Setting(required=False, validator=validateTexturePath, default=None),
"renderchecks": Setting(required=False, validator=validateInt, default=None),
"rerenderprob": Setting(required=True, validator=validateFloat, default=0),
"rerenderprob": Setting(required=True, validator=validateRerenderprob, default=0),
"crop": Setting(required=False, validator=validateCrop, default=None),
"changelist": Setting(required=False, validator=validateStr, default=None),
"markers": Setting(required=False, validator=validateMarkers, default=[]),
"overlay": Setting(required=False, validator=validateOverlays, default=[]),
"showspawn": Setting(required=False, validator=validateBool, default=True),
"base": Setting(required=False, validator=validateStr, default=""),
# Remove this eventually (once people update their configs)
"worldname": Setting(required=False, default=None,
@@ -98,9 +100,10 @@ processes = Setting(required=True, validator=int, default=-1)
# ends up adding overhead and isn't worth it.
memcached_host = Setting(required=False, validator=str, default=None)
if platform.system() == 'Windows' or not sys.stderr.isatty():
# TODO clean up this ugly in sys.argv hack
if platform.system() == 'Windows' or not sys.stdout.isatty() or "--simple" in sys.argv:
obs = LoggingObserver()
else:
obs = ProgressBarObserver()
obs = ProgressBarObserver(fd=sys.stdout)
observer = Setting(required=True, validator=validateObserver, default=obs)

View File

@@ -17,6 +17,12 @@ class Setting(object):
self.validator = validator
self.default = default
def expand_path(p):
p = os.path.expanduser(p)
p = os.path.expandvars(p)
p = os.path.abspath(p)
return p
def checkBadEscape(s):
fixed = False
fixed_string = s
@@ -45,15 +51,29 @@ def checkBadEscape(s):
def validateMarkers(filterlist):
if type(filterlist) != list:
raise ValidationException("Markers must specify a list of filters")
raise ValidationException("Markers must specify a list of filters. This has recently changed, so check the docs.")
for x in filterlist:
if not callable(x):
raise ValidationException("%r must be a function"% x)
if type(x) != dict:
raise ValidationException("Markers must specify a list of dictionaries. This has recently changed, so check the docs.")
if "name" not in x:
raise ValidationException("Must define a name")
if "filterFunction" not in x:
raise ValidationException("Must define a filter function")
if not callable(x['filterFunction']):
raise ValidationException("%r must be a function"% x['filterFunction'])
return filterlist
def validateOverlays(renderlist):
if type(renderlist) != list:
raise ValidationException("Overlay must specify a list of renders")
for x in renderlist:
if validateStr(x) == '':
raise ValidationException("%r must be a string"% x)
return renderlist
def validateWorldPath(worldpath):
_, worldpath = checkBadEscape(worldpath)
abs_path = os.path.abspath(os.path.expanduser(worldpath))
abs_path = expand_path(worldpath)
if not os.path.exists(os.path.join(abs_path, "level.dat")):
raise ValidationException("No level.dat file in '%s'. Are you sure you have the right path?" % (abs_path,))
return abs_path
@@ -99,10 +119,10 @@ def validateNorthDirection(direction):
raise ValidationException("%r is not a valid north direction" % direction)
return intdir
def validateStochastic(s):
def validateRerenderprob(s):
val = float(s)
if val < 0 or val > 1:
raise ValidationException("%r is not a valid stochastic value. Should be between 0.0 and 1.0" % s)
if val < 0 or val >= 1:
raise ValidationException("%r is not a valid rerender probability value. Should be between 0.0 and 1.0." % s)
return val
def validateImgFormat(fmt):
@@ -144,7 +164,7 @@ def validateOptImg(opt):
def validateTexturePath(path):
# Expand user dir in directories strings
path = os.path.expanduser(path)
path = expand_path(path)
# TODO assert this path exists?
return path
@@ -170,7 +190,7 @@ def validateOutputDir(d):
_, d = checkBadEscape(d)
if not d.strip():
raise ValidationException("You must specify a valid output directory")
return os.path.abspath(d)
return expand_path(d)
def validateCrop(value):
if len(value) != 4:

View File

@@ -24,11 +24,6 @@
#include "overviewer.h"
/* like (a * b + 127) / 255), but much faster on most platforms
from PIL's _imaging.c */
#define MULDIV255(a, b, tmp) \
(tmp = (a) * (b) + 128, ((((tmp) >> 8) + (tmp)) >> 8))
typedef struct {
PyObject_HEAD
Imaging image;

View File

@@ -432,7 +432,6 @@ generate_pseudo_data(RenderState *state, unsigned char ancilData) {
PyObject*
chunk_render(PyObject *self, PyObject *args) {
RenderState state;
PyObject *regionset;
PyObject *modeobj;
PyObject *blockmap;
@@ -453,7 +452,7 @@ chunk_render(PyObject *self, PyObject *args) {
PyObject *t = NULL;
if (!PyArg_ParseTuple(args, "OiiiOiiOO", &state.regionset, &state.chunkx, &state.chunky, &state.chunkz, &state.img, &xoff, &yoff, &modeobj, &state.textures))
if (!PyArg_ParseTuple(args, "OOiiiOiiOO", &state.world, &state.regionset, &state.chunkx, &state.chunky, &state.chunkz, &state.img, &xoff, &yoff, &modeobj, &state.textures))
return NULL;
/* set up the render mode */

View File

@@ -26,13 +26,18 @@
// increment this value if you've made a change to the c extesion
// and want to force users to rebuild
#define OVERVIEWER_EXTENSION_VERSION 30
#define OVERVIEWER_EXTENSION_VERSION 37
/* Python PIL, and numpy headers */
#include <Python.h>
#include <Imaging.h>
#include <numpy/arrayobject.h>
/* like (a * b + 127) / 255), but much faster on most platforms
from PIL's _imaging.c */
#define MULDIV255(a, b, tmp) \
(tmp = (a) * (b) + 128, ((((tmp) >> 8) + (tmp)) >> 8))
/* macro for getting a value out of various numpy arrays the 3D arrays have
interesting, swizzled coordinates because minecraft (anvil) stores blocks
in y/z/x order for 3D, z/x order for 2D */
@@ -83,6 +88,7 @@ typedef struct {
} ChunkData;
typedef struct {
/* the regionset object, and chunk coords */
PyObject *world;
PyObject *regionset;
int chunkx, chunky, chunkz;

View File

@@ -29,46 +29,53 @@ typedef struct {
typedef struct {
const char* name;
float temperature;
float rainfall;
unsigned int r, g, b;
} Biome;
/* each entry in this table is yanked *directly* out of the minecraft source
* temp/rainfall are taken from what MCP calls setTemperatureRainfall
*
* Some biomes, like Swamp, do a bit of post-processing by multiplying on a
* hard-coded color. The RGB tuple used follows the temp/rainfall.
* 255, 255, 255 is white, which means do nothing
*
* keep in mind the x/y coordinate in the color tables is found *after*
* multiplying rainfall and temperature for the second coordinate, *and* the
* origin is in the lower-right. <3 biomes.
*/
static Biome biome_table[] = {
/* 0 */
{"Ocean", 0.5, 0.5},
{"Plains", 0.8, 0.4},
{"Desert", 2.0, 0.0},
{"Extreme Hills", 0.2, 0.3},
{"Forest", 0.7, 0.8},
{"Ocean", 0.5, 0.5, 255, 255, 255},
{"Plains", 0.8, 0.4, 255, 255, 255},
{"Desert", 2.0, 0.0, 255, 255, 255},
{"Extreme Hills", 0.2, 0.3, 255, 255, 255},
{"Forest", 0.7, 0.8, 255, 255, 255},
/* 5 */
{"Taiga", 0.05, 0.8},
{"Swampland", 0.8, 0.9},
{"River", 0.5, 0.5},
{"Hell", 2.0, 0.0},
{"Sky", 0.5, 0.5},
{"Taiga", 0.05, 0.8, 255, 255, 255},
{"Swampland", 0.8, 0.9, 205, 128, 255},
{"River", 0.5, 0.5, 255, 255, 255},
{"Hell", 2.0, 0.0, 255, 255, 255},
{"Sky", 0.5, 0.5, 255, 255, 255},
/* 10 */
{"FrozenOcean", 0.0, 0.5},
{"FrozenRiver", 0.0, 0.5},
{"Ice Plains", 0.0, 0.5},
{"Ice Mountains", 0.0, 0.5},
{"MushroomIsland", 0.9, 1.0},
{"FrozenOcean", 0.0, 0.5, 255, 255, 255},
{"FrozenRiver", 0.0, 0.5, 255, 255, 255},
{"Ice Plains", 0.0, 0.5, 255, 255, 255},
{"Ice Mountains", 0.0, 0.5, 255, 255, 255},
{"MushroomIsland", 0.9, 1.0, 255, 255, 255},
/* 15 */
{"MushroomIslandShore", 0.9, 1.0},
{"Beach", 0.8, 0.4},
{"DesertHills", 2.0, 0.0},
{"ForestHills", 0.7, 0.8},
{"TaigaHills", 0.05, 0.8},
{"MushroomIslandShore", 0.9, 1.0, 255, 255, 255},
{"Beach", 0.8, 0.4, 255, 255, 255},
{"DesertHills", 2.0, 0.0, 255, 255, 255},
{"ForestHills", 0.7, 0.8, 255, 255, 255},
{"TaigaHills", 0.05, 0.8, 255, 255, 255},
/* 20 */
{"Extreme Hills Edge", 0.2, 0.3},
{"Jungle", 2.0, 0.45}, /* <-- GUESS, but a good one */
{"Jungle Mountains", 2.0, 0.45}, /* <-- also a guess */
{"Extreme Hills Edge", 0.2, 0.3, 255, 255, 255},
{"Jungle", 2.0, 0.45, 255, 255, 255}, /* <-- GUESS, but a good one */
{"Jungle Mountains", 2.0, 0.45, 255, 255, 255}, /* <-- also a guess */
};
#define NUM_BIOMES (sizeof(biome_table) / sizeof(Biome))
@@ -206,16 +213,19 @@ base_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObjec
};
if (color_table) {
unsigned char biome;
int dx, dz;
unsigned char tablex, tabley;
float temp = 0.0, rain = 0.0;
unsigned int multr = 0, multg = 0, multb = 0;
int tmp;
PyObject *color = NULL;
if (self->use_biomes) {
/* average over all neighbors */
for (dx = -1; dx <= 1; dx++) {
for (dz = -1; dz <= 1; dz++) {
unsigned char biome = get_data(state, BIOMES, state->x + dx, state->y, state->z + dz);
biome = get_data(state, BIOMES, state->x + dx, state->y, state->z + dz);
if (biome >= NUM_BIOMES) {
/* note -- biome 255 shows up on map borders.
who knows what it is? certainly not I.
@@ -225,14 +235,24 @@ base_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObjec
temp += biome_table[biome].temperature;
rain += biome_table[biome].rainfall;
multr += biome_table[biome].r;
multg += biome_table[biome].g;
multb += biome_table[biome].b;
}
}
temp /= 9.0;
rain /= 9.0;
multr /= 9;
multg /= 9;
multb /= 9;
} else {
/* don't use biomes, just use the default */
temp = biome_table[DEFAULT_BIOME].temperature;
rain = biome_table[DEFAULT_BIOME].rainfall;
multr = biome_table[DEFAULT_BIOME].r;
multg = biome_table[DEFAULT_BIOME].g;
multb = biome_table[DEFAULT_BIOME].b;
}
/* second coordinate is actually scaled to fit inside the triangle
@@ -258,6 +278,11 @@ base_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObjec
g = PyInt_AsLong(PyTuple_GET_ITEM(color, 1));
b = PyInt_AsLong(PyTuple_GET_ITEM(color, 2));
Py_DECREF(color);
/* do the after-coloration */
r = MULDIV255(r, multr, tmp);
g = MULDIV255(g, multg, tmp);
b = MULDIV255(b, multb, tmp);
}
/* final coloration */

View File

@@ -38,23 +38,24 @@ edge_lines_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, P
Imaging img_i = imaging_python_to_c(state->img);
unsigned char ink[] = {0, 0, 0, 255 * self->opacity};
unsigned short side_block;
int x = state->x, y = state->y, z = state->z;
int increment=0;
if (state->block == 44 && ((state->block_data & 0x8) == 0 )) // half-step BUT no upsidown half-step
if ((state->block == 44 || state->block == 126) && ((state->block_data & 0x8) == 0 )) // half-steps BUT no upsidown half-steps
increment=6;
else if ((state->block == 78) || (state->block == 93) || (state->block == 94)) // snow, redstone repeaters (on and off)
increment=9;
/* +X side */
side_block = get_data(state, BLOCKS, state->x+1, state->y, state->z);
if (side_block != state->block && is_transparent(side_block)) {
side_block = get_data(state, BLOCKS, x+1, y, z);
if (side_block != state->block && (is_transparent(side_block) || render_mode_hidden(state->rendermode, x+1, y, z))) {
ImagingDrawLine(img_i, state->imgx+12, state->imgy+1+increment, state->imgx+22+1, state->imgy+5+1+increment, &ink, 1);
ImagingDrawLine(img_i, state->imgx+12, state->imgy+increment, state->imgx+22+1, state->imgy+5+increment, &ink, 1);
}
/* -Z side */
side_block = get_data(state, BLOCKS, state->x, state->y, state->z-1);
if (side_block != state->block && is_transparent(side_block)) {
side_block = get_data(state, BLOCKS, x, y, z-1);
if (side_block != state->block && (is_transparent(side_block) || render_mode_hidden(state->rendermode, x, y, z-1))) {
ImagingDrawLine(img_i, state->imgx, state->imgy+6+1+increment, state->imgx+12+1, state->imgy+1+increment, &ink, 1);
ImagingDrawLine(img_i, state->imgx, state->imgy+6+increment, state->imgx+12+1, state->imgy+increment, &ink, 1);
}

View File

@@ -0,0 +1,109 @@
/*
* 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"
typedef struct {
unsigned int mode; /* 0 = exposed only, 1 = unexposed only */
} PrimitiveExposed;
static int
exposed_start(void *data, RenderState *state, PyObject *support) {
PrimitiveExposed *self = (PrimitiveExposed *)data;
if (!render_mode_parse_option(support, "mode", "I", &(self->mode)))
return 1;
return 0;
}
static int
exposed_hidden(void *data, RenderState *state, int x, int y, int z) {
PrimitiveExposed *self = (PrimitiveExposed *)data;
/* Unset these flags if seeming exposure from any of these directions would
* be due to not having data there.
*/
int validMinusX = 1;
int validPlusX = 1;
int validMinusY = 1;
int validPlusY = 1;
int validMinusZ = 1;
int validPlusZ = 1;
/* special handling for section boundaries */
/* If the neighboring section has no block data, ignore exposure from that
* direction
*/
if (x == 0 && (!(state->chunks[0][1].loaded) || state->chunks[0][1].sections[state->chunky].blocks == NULL)) {
/* No data in -x direction */
validMinusX = 0;
}
if (x == 15 && (!(state->chunks[2][1].loaded) || state->chunks[2][1].sections[state->chunky].blocks == NULL)) {
/* No data in +x direction */
validPlusX = 0;
}
if (y == 0 && (state->chunky - 1 < 0 || state->chunks[1][1].sections[state->chunky - 1].blocks == NULL)) {
/* No data in -y direction */
validMinusY = 0;
}
if (y == 15 && (state->chunky + 1 >= SECTIONS_PER_CHUNK || state->chunks[1][1].sections[state->chunky + 1].blocks == NULL)) {
/* No data in +y direction */
validPlusY = 0;
}
if (z == 0 && (!(state->chunks[1][0].loaded) || state->chunks[1][0].sections[state->chunky].blocks == NULL)) {
/* No data in -z direction */
validMinusZ = 0;
}
if (z == 15 && (!(state->chunks[1][2].loaded) || state->chunks[1][2].sections[state->chunky].blocks == NULL)) {
/* No data in +z direction */
validPlusZ = 0;
}
/* If any of the 6 blocks adjacent to us are transparent, we're exposed */
if( (validMinusX && is_transparent(get_data(state, BLOCKS, x-1, y, z))) ||
(validPlusX && is_transparent(get_data(state, BLOCKS, x+1, y, z))) ||
(validMinusY && is_transparent(get_data(state, BLOCKS, x, y-1, z))) ||
(validPlusY && is_transparent(get_data(state, BLOCKS, x, y+1, z))) ||
(validMinusZ && is_transparent(get_data(state, BLOCKS, x, y, z-1))) ||
(validPlusZ && is_transparent(get_data(state, BLOCKS, x, y, z+1 ))) ) {
/* Block is exposed */
/* Returns 1 and hides us if we're rendering unexposed blocks, 0 and
* shows us if we're rendering exposed blocks
*/
return self->mode;
}
/* We have no valid evidence that the block is exposed */
return !(self->mode); /* Hide in normal mode, reveal in inverted mode */
}
RenderPrimitiveInterface primitive_exposed = {
"exposed", sizeof(PrimitiveExposed),
exposed_start,
NULL,
NULL,
exposed_hidden,
NULL,
};

View File

@@ -20,12 +20,16 @@
typedef struct {
PyObject *black_color;
PyObject *white_color;
unsigned int sealevel;
} PrimitiveHeightFading;
static int
height_fading_start(void *data, RenderState *state, PyObject *support) {
PrimitiveHeightFading *self = (PrimitiveHeightFading *)data;
if (!render_mode_parse_option(support, "sealevel", "I", &(self->sealevel)))
return 1;
self->black_color = PyObject_GetAttrString(support, "black_color");
self->white_color = PyObject_GetAttrString(support, "white_color");
@@ -50,7 +54,7 @@ height_fading_draw(void *data, RenderState *state, PyObject *src, PyObject *mask
PyObject *height_color = self->white_color;
/* current formula requires y to be between 0 and 127, so scale it */
y = (y * 128) / (16 * SECTIONS_PER_CHUNK);
y = (y * 128) / (2 * self->sealevel);
/* negative alpha => darkness, positive => light */
alpha = (1.0 / (1 + expf((70 - y) / 11.0))) * 0.6 - 0.55;

View File

@@ -0,0 +1,117 @@
/*
* 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"
struct HideRule {
unsigned short blockid;
unsigned char has_data;
unsigned char data;
};
typedef struct {
struct HideRule* rules;
} RenderPrimitiveHide;
static int
hide_start(void *data, RenderState *state, PyObject *support) {
PyObject *opt;
RenderPrimitiveHide* self = (RenderPrimitiveHide *)data;
self->rules = NULL;
if (!render_mode_parse_option(support, "blocks", "O", &(opt)))
return 1;
if (opt && opt != Py_None) {
Py_ssize_t blocks_size = 0, i;
if (!PyList_Check(opt)) {
PyErr_SetString(PyExc_TypeError, "'blocks' must be a list");
return 1;
}
blocks_size = PyList_GET_SIZE(opt);
self->rules = calloc(blocks_size + 1, sizeof(struct HideRule));
if (self->rules == NULL) {
return 1;
}
for (i = 0; i < blocks_size; i++) {
PyObject *block = PyList_GET_ITEM(opt, i);
if (PyInt_Check(block)) {
/* format 1: just a block id */
self->rules[i].blockid = PyInt_AsLong(block);
self->rules[i].has_data = 0;
} else if (PyArg_ParseTuple(block, "Hb", &(self->rules[i].blockid), &(self->rules[i].data))) {
/* format 2: (blockid, data) */
self->rules[i].has_data = 1;
} else {
/* format not recognized */
free(self->rules);
self->rules = NULL;
return 1;
}
}
}
return 0;
}
static void
hide_finish(void *data, RenderState *state) {
RenderPrimitiveHide *self = (RenderPrimitiveHide *)data;
if (self->rules) {
free(self->rules);
}
}
static int
hide_hidden(void *data, RenderState *state, int x, int y, int z) {
RenderPrimitiveHide *self = (RenderPrimitiveHide *)data;
unsigned int i;
unsigned short block;
if (self->rules == NULL)
return 0;
block = get_data(state, BLOCKS, x, y, z);
for (i = 0; self->rules[i].blockid != 0; i++) {
if (block == self->rules[i].blockid) {
unsigned char data;
if (!(self->rules[i].has_data))
return 1;
data = get_data(state, DATA, x, y, z);
if (data == self->rules[i].data)
return 1;
}
}
return 0;
}
RenderPrimitiveInterface primitive_hide = {
"hide",
sizeof(RenderPrimitiveHide),
hide_start,
hide_finish,
NULL,
hide_hidden,
NULL,
};

View File

@@ -202,7 +202,7 @@ lighting_is_face_occluded(RenderState *state, int skip_sides, int x, int y, int
/* this face isn't visible, so don't draw anything */
return 1;
}
} else if (skip_sides) {
} else if (!skip_sides) {
unsigned short block = get_data(state, BLOCKS, x, y, z);
if (!is_transparent(block)) {
/* the same thing but for adjacent chunks, this solves an
@@ -242,8 +242,8 @@ lighting_start(void *data, RenderState *state, PyObject *support) {
RenderPrimitiveLighting* self;
self = (RenderPrimitiveLighting *)data;
/* skip sides by default */
self->skip_sides = 1;
/* don't skip sides by default */
self->skip_sides = 0;
if (!render_mode_parse_option(support, "strength", "f", &(self->strength)))
return 1;

View File

@@ -0,0 +1,37 @@
/*
* 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 int
no_fluids_start(void *data, RenderState *state, PyObject *support) {
return 0;
}
static int
no_fluids_hidden(void *data, RenderState *state, int x, int y, int z) {
return block_has_property(state->block, FLUID);
}
RenderPrimitiveInterface primitive_no_fluids = {
"no-fluids", 0,
no_fluids_start,
NULL,
NULL,
no_fluids_hidden,
NULL,
};

View File

@@ -90,6 +90,7 @@ overlay_mineral_start(void *data, RenderState *state, PyObject *support) {
/* now do custom initializations */
self = (RenderPrimitiveMineral *)data;
// opt is a borrowed reference. do not deref
if (!render_mode_parse_option(support, "minerals", "O", &(opt)))
return 1;
if (opt && opt != Py_None) {
@@ -119,7 +120,6 @@ overlay_mineral_start(void *data, RenderState *state, PyObject *support) {
} else {
self->minerals = default_minerals;
}
Py_XDECREF(opt);
/* setup custom color */
self->parent.get_color = get_color;

View File

@@ -0,0 +1,126 @@
/*
* 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 "overlay.h"
#include <math.h>
typedef struct {
/* inherits from overlay */
RenderPrimitiveOverlay parent;
long seed;
} RenderPrimitiveSlime;
/*
* random_* are a re-implementation of java's Random() class
* since Minecraft's slime algorithm depends on it
* http://docs.oracle.com/javase/1.4.2/docs/api/java/util/Random.html
*/
static void random_set_seed(long *seed, long new_seed) {
*seed = (new_seed ^ 0x5deece66dL) & ((1L << 48) - 1);
}
static int random_next(long *seed, int bits) {
*seed = (*seed * 0x5deece66dL + 0xbL) & ((1L << 48) - 1);
return (int)(*seed >> (48 - bits));
}
static int random_next_int(long *seed, int n) {
int bits, val;
if (n <= 0) {
/* invalid */
return 0;
}
if ((n & -n) == n) {
/* n is a power of two */
return (int)((n * (long)random_next(seed, 31)) >> 31);
}
do {
bits = random_next(seed, 31);
val = bits % n;
} while (bits - val + (n - 1) < 0);
return val;
}
static int is_slime(long map_seed, long chunkx, long chunkz) {
/* lots of magic numbers, but they're all correct! I swear! */
long seed;
random_set_seed(&seed, map_seed + (chunkx * chunkx * 0x4c1906L) + (chunkx * 0x5ac0dbL) + (chunkz * chunkz * 0x4307a7L) + (chunkz * 0x5f24fL) ^ 0x3ad8025fL);
return (random_next_int(&seed, 10) == 0);
}
static void get_color(void *data, RenderState *state,
unsigned char *r, unsigned char *g, unsigned char *b, unsigned char *a) {
RenderPrimitiveSlime *self = (RenderPrimitiveSlime *)data;
/* set a nice, pretty green color */
*r = 40;
*g = 230;
*b = 40;
/* default to no overlay, until told otherwise */
*a = 0;
if (is_slime(self->seed, state->chunkx, state->chunkz)) {
/* slimes can spawn! */
*a = 240;
}
}
static int
overlay_slime_start(void *data, RenderState *state, PyObject *support) {
RenderPrimitiveSlime *self;
PyObject *pyseed;
/* first, chain up */
int ret = primitive_overlay.start(data, state, support);
if (ret != 0)
return ret;
/* now do custom initializations */
self = (RenderPrimitiveSlime *)data;
self->parent.get_color = get_color;
pyseed = PyObject_GetAttrString(state->world, "seed");
if (!pyseed)
return 1;
self->seed = PyInt_AsLong(pyseed);
Py_DECREF(pyseed);
if (PyErr_Occurred())
return 1;
return 0;
}
static void
overlay_slime_finish(void *data, RenderState *state) {
/* chain up */
primitive_overlay.finish(data, state);
}
RenderPrimitiveInterface primitive_overlay_slime = {
"overlay-slime",
sizeof(RenderPrimitiveSlime),
overlay_slime_start,
overlay_slime_finish,
NULL,
NULL,
overlay_draw,
};

View File

@@ -82,8 +82,8 @@ overlay_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyOb
/* 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);
alpha_over_full(state->img, self->white_color, self->facemask_top, a/255.f, state->imgx, state->imgy + increment, 0, 0);
tint_with_mask(state->img, r, g, b, 255, self->facemask_top, state->imgx, state->imgy + increment, 0, 0);
}
}

View File

@@ -213,7 +213,7 @@ class Textures(object):
if verbose: logging.info("Found %s in '%s'", filename, path)
return open(path, mode)
raise IOError("Could not find the file `{0}'. Try specifying the 'texturepath' option in your config file. Set it to the directory where I can find {0}.".format(filename))
raise IOError("Could not find the file `{0}'. Try specifying the 'texturepath' option in your config file. Set it to the directory where I can find {0}. Also see <http://docs.overviewer.org/en/latest/running/#installing-the-textures>".format(filename))
def load_image(self, filename):
"""Returns an image object"""
@@ -1336,6 +1336,8 @@ block(blockid=41, top_index=23)
block(blockid=42, top_index=22)
# double slabs and slabs
# these wooden slabs are unobtainable without cheating, they are still
# here because lots of pre-1.3 worlds use this blocks
@material(blockid=[43, 44], data=range(16), transparent=(44,), solid=True)
def slabs(self, blockid, data):
texture = data & 7
@@ -3169,3 +3171,52 @@ block(blockid=123, top_index=211)
# active redstone lamp
block(blockid=124, top_index=212)
# wooden double and normal slabs
# these are the new wooden slabs, blockids 43 44 still have wooden
# slabs, but those are unobtainable without cheating
@material(blockid=[125, 126], data=range(16), transparent=(44,), solid=True)
def slabs(self, blockid, data):
texture = data & 7
if texture== 0: # oak
top = side = self.terrain_images[4]
elif texture== 1: # spruce
top = side = self.terrain_images[198]
elif texture== 2: # birch
top = side = self.terrain_images[214]
elif texture== 3: # jungle
top = side = self.terrain_images[199]
else:
return None
if blockid == 125: # double slab
return self.build_block(top, side)
# cut the side texture in half
mask = side.crop((0,8,16,16))
side = Image.new(side.mode, side.size, self.bgcolor)
alpha_over(side, mask,(0,0,16,8), mask)
# plain slab
top = self.transform_image_top(top)
side = self.transform_image_side(side)
otherside = side.transpose(Image.FLIP_LEFT_RIGHT)
sidealpha = side.split()[3]
side = ImageEnhance.Brightness(side).enhance(0.9)
side.putalpha(sidealpha)
othersidealpha = otherside.split()[3]
otherside = ImageEnhance.Brightness(otherside).enhance(0.8)
otherside.putalpha(othersidealpha)
# upside down slab
delta = 0
if data & 8 == 8:
delta = 6
img = Image.new("RGBA", (24,24), self.bgcolor)
alpha_over(img, side, (0,12 - delta), side)
alpha_over(img, otherside, (12,12 - delta), otherside)
alpha_over(img, top, (0,6 - delta), top)
return img

View File

@@ -17,6 +17,7 @@ import itertools
import logging
import os
import os.path
import sys
import shutil
import random
import functools
@@ -32,6 +33,7 @@ from .util import roundrobin
from . import nbt
from .files import FileReplacer
from .optimizeimages import optimize_image
import rendermodes
import c_overviewer
"""
@@ -163,12 +165,14 @@ class TileSet(object):
"""
def __init__(self, regionsetobj, assetmanagerobj, texturesobj, options, outputdir):
def __init__(self, worldobj, regionsetobj, assetmanagerobj, texturesobj, options, outputdir):
"""Construct a new TileSet object with the given configuration options
dictionary.
options is a dictionary of configuration parameters (strings mapping to
values) that are interpreted by the rendering engine.
worldobj is the World object that regionsetobj is from.
regionsetobj is the RegionSet object that is used to render the tiles.
@@ -268,6 +272,7 @@ class TileSet(object):
"""
self.options = options
self.world = worldobj
self.regionset = regionsetobj
self.am = assetmanagerobj
self.textures = texturesobj
@@ -355,7 +360,7 @@ class TileSet(object):
# Only pickle the initial state. Don't pickle anything resulting from the
# do_preprocessing step
def __getstate__(self):
return self.regionset, self.am, self.textures, self.options, self.outputdir
return self.world, self.regionset, self.am, self.textures, self.options, self.outputdir
def __setstate__(self, state):
self.__init__(*state)
@@ -507,19 +512,25 @@ class TileSet(object):
"""
def bgcolorformat(color):
return "#%02x%02x%02x" % color[0:3]
isOverlay = not any(isinstance(x, rendermodes.Base) for x in self.options.get("rendermode"))
d = dict(name = self.options.get('title'),
zoomLevels = self.treedepth,
minZoom = 0,
defaultZoom = 1,
maxZoom = self.treedepth,
path = self.options.get('name'),
base = '',
base = self.options.get('base'),
bgcolor = bgcolorformat(self.options.get('bgcolor')),
world = self.options.get('worldname_orig') +
(" - " + self.options.get('dimension') if self.options.get('dimension') != 'default' else ''),
last_rendertime = self.max_chunk_mtime,
imgextension = self.imgextension,
isOverlay = isOverlay
)
if isOverlay:
d.update({"tilesets": self.options.get("overlay")})
if (self.regionset.get_type() == "overworld" and self.options.get("showspawn", True)):
d.update({"spawn": self.options.get("spawn")})
else:
@@ -580,7 +591,6 @@ class TileSet(object):
self.xradius = xradius
self.yradius = yradius
def _rearrange_tiles(self):
"""If the target size of the tree is not the same as the existing size
on disk, do some re-arranging
@@ -940,7 +950,7 @@ class TileSet(object):
# draw the chunk!
try:
c_overviewer.render_loop(self.regionset, chunkx, chunky,
c_overviewer.render_loop(self.world, self.regionset, chunkx, chunky,
chunkz, tileimg, xpos, ypos,
self.options['rendermode'], self.textures)
except nbt.CorruptionError:
@@ -948,8 +958,9 @@ class TileSet(object):
# get_chunk()
logging.debug("Skipping the render of corrupt chunk at %s,%s and moving on.", chunkx, chunkz)
except Exception, e:
logging.warning("Could not render chunk %s,%s for some reason. I'm going to ignore this and continue", chunkx, chunkz)
logging.debug("Full error was:", exc_info=1)
logging.error("Could not render chunk %s,%s for some reason. This is likely a render primitive option error.", chunkx, chunkz)
logging.error("Full error was:", exc_info=1)
sys.exit(1)
## Semi-handy routine for debugging the drawing routine
## Draw the outline of the top of the chunk

View File

@@ -136,7 +136,12 @@ class World(object):
except KeyError:
# but very old ones might not? so we'll just go with the world dir name if they don't
self.name = os.path.basename(os.path.realpath(self.worlddir))
try:
# level.dat also has a RandomSeed attribute
self.seed = data['RandomSeed']
except KeyError:
self.seed = 0 # oh well
# TODO figure out where to handle regionlists
@@ -204,6 +209,7 @@ class World(object):
if section['Y'] == targetSection:
blockArray = section['Blocks']
return blockArray[inChunkX, inChunkZ, y % 16]
return 0
@@ -304,7 +310,7 @@ class RegionSet(object):
* For each chunk section:
* The "Blocks" byte string is transformed into a 16x16x16 numpy array
* The AddBlocks array, if it exists, is bitshifted left 8 bits and
* The Add array, if it exists, is bitshifted left 8 bits and
added into the Blocks array
* The "SkyLight" byte string is transformed into a 16x16x128 numpy
array
@@ -380,11 +386,11 @@ class RegionSet(object):
# Cast up to uint16, blocks can have up to 12 bits of data
blocks = blocks.astype(numpy.uint16)
blocks = blocks.reshape((16,16,16))
if "AddBlocks" in section:
if "Add" in section:
# This section has additional bits to tack on to the blocks
# array. AddBlocks is a packed array with 4 bits per slot, so
# array. Add is a packed array with 4 bits per slot, so
# it needs expanding
additional = numpy.frombuffer(section['AddBlocks'], dtype=numpy.uint8)
additional = numpy.frombuffer(section['Add'], dtype=numpy.uint8)
additional = additional.astype(numpy.uint16).reshape((16,16,8))
additional_expanded = numpy.empty((16,16,16), dtype=numpy.uint16)
additional_expanded[:,:,::2] = (additional & 0x0F) << 8
@@ -392,7 +398,7 @@ class RegionSet(object):
blocks += additional_expanded
del additional
del additional_expanded
del section['AddBlocks'] # Save some memory
del section['Add'] # Save some memory
section['Blocks'] = blocks
# Turn the skylight array into a 16x16x16 matrix. The array comes
@@ -483,6 +489,8 @@ class RegionSet(object):
p = f.split(".")
x = int(p[1])
y = int(p[2])
if abs(x) > 500000 or abs(y) > 500000:
logging.warning("Holy shit what is up with region file %s !?" % f)
yield (x, y, path)
class RegionSetWrapper(object):