0

Merge remote branch 'source/master' into remote-tiles

Conflicts:
	overviewer.py
This commit is contained in:
Fabian Norman
2012-05-02 01:04:13 -07:00
19 changed files with 550 additions and 71 deletions

View File

@@ -552,6 +552,18 @@ values. The valid configuration keys are listed below.
**Default:** ``[]`` (an empty list)
.. _option_overlay:
``overlay``
This specifies which renders that this render will be displayed on top of.
It should be a list of renders.
.. warning::
At this time, this feature is not fully implemented.
**Default:** ``[]`` (an empty list)
``showspawn``
This is a boolean, and defaults to ``True``. If set to ``False``, then the spawn
icon will not be displayed on the rendered map.
@@ -636,6 +648,15 @@ Cave
only_lit
Only render lit caves. Default: False
Hide
Hide blocks based on blockid. Blocks hidden in this way will be
treated exactly the same as air.
**Options**
minerals
A list of block ids, or (blockid, data) tuples to hide.
DepthTinting
Tint blocks a color according to their depth (height) from bedrock. Useful
mainly for cave renders.

View File

@@ -20,51 +20,97 @@ Filter Functions
----------------
A filter function is a python function that is used to figure out if a given POI
should be part of a markerSet of not. The function should accept one argument
(a dictionary, also know as an associative array), and return a boolean::
should be part of a markerSet of not, and to control how it is displayed.
The function should accept one argument (a dictionary, also know as an associative
array), and return a string representing the text to be displayed. For example::
def signFilter(poi):
"All signs"
return poi['id'] == 'Sign'
if poi['id'] == 'Sign':
return "\n".join([poi['Text1'], poi['Text2'], poi['Text3'], poi['Text4']])
If a POI doesn't match, the filter can return None (which is the default if a python
functions runs off the end without an explicit 'return').
The single argument will either a TileEntity, or an Entity taken directly from
the chunk file. In this example, this function returns true only if the type
of entity is a sign. For more information of TileEntities and Entities, see
the chunk file. It could also be a special entity representing a player's location
or a player's spawn. See below for more details.
In this example, this function returns all 4 lines from the sign
if the entity is a sign.
For more information of TileEntities and Entities, see
the `Chunk Format <http://www.minecraftwiki.net/wiki/Chunk_format>`_ page on
the Minecraft Wiki.
.. note::
The doc string ("All signs" in this example) is important. It is the label
that appears in your rendered map
A more complicated filter function can construct a more customized display text::
A more advanced filter may also look at other entity fields, such as the sign text::
def chestFilter(poi):
if poi['id'] == "Chest":
return "Chest with %d items" % len(poi['Items'])
def goldFilter(poi):
"Gold"
return poi['id'] == 'Sign' and (\
'gold' in poi['Text1'] or
'gold' in poi['Text2'])
This looks for the word 'gold' in either the first or second line of the signtext.
Since writing these filters can be a little tedious, a set of predefined filters
functions are provided. See the :ref:`predefined_filter_functions` section for
details.
Special POIs
------------
There are currently two special types of POIs. They each have a special id:
PlayerSpawn
Used to indicate the spawn location of a player. The player's name is set
in the ``EntityId`` key, and the location is in the x,y,z keys
Player
Used to indicate the last known location of a player. The player's name is set
in the ``EntityId`` key, and the location is in the x,y,z keys.
.. note::
The player location is taken from level.dat (in the case of a single-player world)
or the player.dat files (in the case of a multi-player server). The locations are
only written to these files when the world is saved, so this won't give you real-time
player location information.
Here's an example that displays icons for each player::
def playerIcons(poi):
if poi['id'] == 'Player':
poi['icon'] = "http://overviewer.org/avatar/%s" % poi['EntityId']
return "Last known location for %s" % poi['EntityId']
Note how each POI can get a different icon by setting ``poi['icon']``
Render Dictionary Key
---------------------
Each render can specify a list of zero or more filter functions. Each of these
filter functions become a selectable item in the 'Signs' drop-down menu in the
rendered map. For example::
rendered map. Previously, this used to be a list of functions. Now it is a list
of dictionaries. For example::
renders['myrender'] = {
'world': 'myworld',
'title': "Example",
'markers': [allFilter, anotherFilter],
'markers': [dict(name="All signs", filterFunction=signFilter),
dict(name="Chests", filterFunction=chestFilter, icon="chest.png")]
}
The following keys are accepted in the marker dictionary:
``name``
This is the text that is displayed in the 'Signs' dropdown.
``filterFunction``
This is the filter function. It must accept at least 1 argument (the POI to filter),
and it must return either None or a string.
``icon``
Optional. Specifies the icon to use for POIs in this group. If omitted, it defaults
to a signpost icon. Note that each POI can have different icon by setting the key 'icon'
on the POI itself (this can be done by modifying the POI in the filter function. See the
example above)
Generating the POI Markers

View File

@@ -17,6 +17,7 @@ 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
@@ -26,6 +27,7 @@ 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
@@ -39,10 +41,64 @@ def handleSigns(rset, outputdir, render, rname):
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 --config=<config file>"""
%prog --genpoi --config=<config file> [--quiet]"""
logger.configure()
@@ -97,26 +153,43 @@ def main():
return 1
for f in render['markers']:
markersets.add((f, rset))
name = f.__name__ + hex(hash(f))[-4:] + "_" + hex(hash(rset))[-4:]
d = dict(icon="signpost_icon.png")
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.__doc__))
l.append(dict(groupName=name, displayName = f['name'], icon=d['icon']))
except KeyError:
markers[rname] = [dict(groupName=name, displayName=f.__doc__),]
markers[rname] = [dict(groupName=name, displayName=f['name'], icon=d['icon']),]
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
name = flter.__name__ + hex(hash(flter))[-4:] + "_" + hex(hash(rset))[-4:]
markerSetDict[name] = dict(created=False, raw=[])
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']:
if flter(poi):
markerSetDict[name]['raw'].append(poi)
result = filter_function(poi)
if result:
d = dict(x=poi['x'], y=poi['y'], z=poi['z'], text=result, createInfoWindow=True)
if "icon" in poi:
d.update({"icon": poi['icon']})
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, createInfoWindow=True)
if "icon" in poi:
d.update({"icon": poi['icon']})
markerSetDict[name]['raw'].append(d)
#print markerSetDict
with open(os.path.join(destdir, "markersDB.js"), "w") as output:

View File

@@ -89,6 +89,8 @@ def main():
help="Print less output. You can specify this option multiple times.")
parser.add_option("-v", "--verbose", dest="verbose", action="count", default=0,
help="Print more output. You can specify this option multiple times.")
parser.add_option("--simple-output", dest="simple", action="store_true", default=False,
help="Use a simple output format, with no colors or progress bars")
# create a group for "plugin exes" (the concept of a plugin exe is only loosly defined at this point)
exegroup = OptionGroup(parser, "Other Scripts",
@@ -114,7 +116,8 @@ def main():
# re-configure the logger now that we've processed the command line options
logger.configure(logging.INFO + 10*options.quiet - 10*options.verbose,
options.verbose > 0)
verbose=options.verbose > 0,
simple=options.simple)
##########################################################################
# This section of main() runs in response to any one-time options we have,
@@ -233,7 +236,6 @@ dir but you forgot to put quotes around the directory, since it contains spaces.
return 1
# Parse the config file
mw_parser = configParser.MultiWorldParser()
mw_parser.parse(options.config)
# Add in the command options here, perhaps overriding values specified in
@@ -244,8 +246,12 @@ dir but you forgot to put quotes around the directory, since it contains spaces.
# Now parse and return the validated config
try:
config = mw_parser.get_validated_config()
except Exception:
except Exception as ex:
if options.verbose:
logging.exception("An error was encountered with your configuration. See the info below.")
else: # no need to print scary traceback! just
logging.error("An error was encountered with your configuration.")
logging.error(str(ex))
return 1
@@ -302,6 +308,20 @@ dir but you forgot to put quotes around the directory, since it contains spaces.
if render.get('forcerender', False):
render['renderchecks'] = 2
# check if overlays are set, if so, make sure that those renders exist
if render.get('overlay', []) != []:
for x in render.get('overlay'):
if x != rname:
try:
renderLink = config['renders'][x]
except KeyError:
logging.error("Render %s's overlay is '%s', but I could not find a corresponding entry in the renders dictionary.",
rname, x)
return 1
else:
logging.error("Render %s's overlay contains itself.", rname)
return 1
destdir = config['outputdir']
if not destdir:
logging.error("You must specify the output directory in your config file.")
@@ -401,7 +421,7 @@ dir but you forgot to put quotes around the directory, since it contains spaces.
# only pass to the TileSet the options it really cares about
render['name'] = render_name # perhaps a hack. This is stored here for the asset manager
tileSetOpts = util.dict_subset(render, ["name", "imgformat", "renderchecks", "rerenderprob", "bgcolor", "imgquality", "optimizeimg", "rendermode", "worldname_orig", "title", "dimension", "changelist","showspawn","base"])
tileSetOpts = util.dict_subset(render, ["name", "imgformat", "renderchecks", "rerenderprob", "bgcolor", "imgquality", "optimizeimg", "rendermode", "worldname_orig", "title", "dimension", "changelist","showspawn", "overlay","base"])
tileSetOpts.update({"spawn": w.find_true_spawn()}) # TODO find a better way to do this
tset = tileset.TileSet(rset, assetMrg, tex, tileSetOpts, tileset_dir)
tilesets.append(tset)

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

@@ -63,6 +63,8 @@ overviewer.util = {
signs.registerEvents(signs);
}
var overlayControl = new overviewer.views.OverlayControlView();
var spawnmarker = new overviewer.views.SpawnIconView();
// Update coords on mousemove
@@ -81,6 +83,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) {
@@ -113,8 +118,6 @@ overviewer.util = {
});
var worldSelector = new overviewer.views.WorldSelectorView({tagName:'DIV'});
overviewer.collections.worlds.bind("add", worldSelector.render, worldSelector);
// hook up some events
@@ -125,6 +128,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(
@@ -21,10 +32,22 @@ overviewer.views.WorldView = Backbone.View.extend({
newMapType.alt = "Minecraft " + tset.get("name") + " Map";
newMapType.projection = new overviewer.classes.MapProjection();
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 +56,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 +65,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);
@@ -119,17 +147,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=[];
@@ -200,6 +227,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);
}
});
/**
@@ -265,7 +400,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; }
@@ -302,7 +437,6 @@ overviewer.views.SignControlView = Backbone.View.extend({
}});
}
iconURL = overviewerConfig.CONST.image.signMarker;
//dataRoot['markers'] = [];
//
for (i in dataRoot) {
@@ -310,15 +444,20 @@ 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) {
overviewer.util.createMarkerInfoWindow(marker);
}
jQuery.extend(entity, {markerObj: marker});

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

@@ -188,6 +188,12 @@ class MineralOverlay(Overlay):
'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

@@ -79,6 +79,7 @@ renders = Setting(required=True, default=util.OrderedDict(),
"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=None),
@@ -99,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

@@ -45,12 +45,24 @@ 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 "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))

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 30
#define OVERVIEWER_EXTENSION_VERSION 31
/* Python PIL, and numpy headers */
#include <Python.h>

View File

@@ -206,6 +206,7 @@ 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;
@@ -215,7 +216,7 @@ base_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObjec
/* 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.
@@ -257,6 +258,18 @@ base_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, PyObjec
r = PyInt_AsLong(PyTuple_GET_ITEM(color, 0));
g = PyInt_AsLong(PyTuple_GET_ITEM(color, 1));
b = PyInt_AsLong(PyTuple_GET_ITEM(color, 2));
/* swamp hack
All values are guessed. They are probably somewhat odd or
completely wrong, but this looks okay for me, and I'm male,
so I can only distinct about 10 different colors anyways.
Blame my Y-Chromosone. */
if(biome == 6) {
r = PyInt_AsLong(PyTuple_GET_ITEM(color, 0)) * 0.8;
g = PyInt_AsLong(PyTuple_GET_ITEM(color, 1)) / 2.0;
b = PyInt_AsLong(PyTuple_GET_ITEM(color, 2)) * 1.0;
}
Py_DECREF(color);
}

View File

@@ -38,6 +38,7 @@ 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
@@ -46,15 +47,15 @@ edge_lines_draw(void *data, RenderState *state, PyObject *src, PyObject *mask, P
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,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

@@ -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

@@ -32,6 +32,7 @@ from .util import roundrobin
from . import nbt
from .files import FileReplacer
from .optimizeimages import optimize_image
import rendermodes
import c_overviewer
"""
@@ -507,6 +508,8 @@ 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,
@@ -519,7 +522,11 @@ class TileSet(object):
(" - " + 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: