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

5
.mailmap Normal file
View File

@@ -0,0 +1,5 @@
Andrew Brown <brownan@gmail.com> <andrew@fry.(none)>
Alex Headley <aheadley@waysaboutstuff.com> <aheadley@nexcess.net>
Alex Headley <aheadley@waysaboutstuff.com> aheadley
Michael Fallows <michael@fallo.ws> redorkulated
Maciej Malecki <maciej.malecki@hotmail.com> Maciej Małecki

View File

@@ -39,28 +39,46 @@ Short-term Contributions
These contributors have made specific changes for a particular bug fix or These contributors have made specific changes for a particular bug fix or
feature. feature.
* Albireo <kappa7194@hotmail.it>
* arrai <array.of.intellect@gmail.com> * arrai <array.of.intellect@gmail.com>
* asmodai <asmodai@in-nomine.org>
* Mark Barnes <mark.e.barnes@gmail.com>
* Kyle Brantley <kyle@averageurl.com> * Kyle Brantley <kyle@averageurl.com>
* but2002 <barryt_9@hotmail.com> * but2002 <barryt_9@hotmail.com>
* Eric Carr <eric@carr.no> * Eric Carr <eric@carr.no>
* cbarber <CraigBarber@taryx.com> * cbarber <CraigBarber@taryx.com>
* Carter Charbonneau <zcarterc@gmail.com>
* Alex Cline <cline@vivisimo.com> * Alex Cline <cline@vivisimo.com>
* Andrew Clunis <andrew@orospakr.ca> * Andrew Clunis <andrew@orospakr.ca>
* CounterPillow <spam@tes-cheese.ch> * CounterPillow <spam@tes-cheese.ch>
* Johannes Dewender <github@JonnyJD.net>
* Michael Fallows <michael@fallo.ws> * Michael Fallows <michael@fallo.ws>
* Ryan Finnie <ryan@feh.colobox.com>
* Stephen Fluin <stephen@mistuph.com> * Stephen Fluin <stephen@mistuph.com>
* Pierre Guinoiseau <pierre@guinoiseau.eu>
* Lucas Hereld <duckman@piratehook.com>
* Benjamin Herr <ben@0x539.de> * Benjamin Herr <ben@0x539.de>
* Ryan Hitchman <hitchmanr@gmail.com> * Ryan Hitchman <hitchmanr@gmail.com>
* Jenny <jennytoo@gmail.com> * Jenny <jennytoo@gmail.com>
* Michael Jensen <emjay1988@gmail.com> * Michael Jensen <emjay1988@gmail.com>
* Sean Kilgore <krystalogik@gmail.com>
* Johan Kiviniemi <devel@johan.kiviniemi.name> * Johan Kiviniemi <devel@johan.kiviniemi.name>
* Philip Kovac <pkovac@cs.uml.edu>
* Thomas Lake <tswsl1989@sucs.org> * Thomas Lake <tswsl1989@sucs.org>
* Maciej Malecki <maciej.malecki@hotmail.com> * Maciej Malecki <maciej.malecki@hotmail.com>
* Ryan McCue <ryanmccue@cubegames.net> * Ryan McCue <ryanmccue@cubegames.net>
* Zach McCullough <nosrepa@gmail.com>
* Mike <mike@snowcrash.ca>
* Morlok8k <otis.spankmeyer@gmail.com> * Morlok8k <otis.spankmeyer@gmail.com>
* Adam Novak <interfect@gmail.com>
* Richard Pastrick <rpastric@contre.us>
* Ryan Rector <rmrector@gmail.com> * Ryan Rector <rmrector@gmail.com>
* Jason Scheirer <jason.scheirer@gmail.com> * Jason Scheirer <jason.scheirer@gmail.com>
* Gregory Short <gshort2@gmail.com> * Gregory Short <gshort2@gmail.com>
* Sam Steele <sam@sigbox.c99.org> * Sam Steele <sam@sigbox.c99.org>
* stoneLeaf <owi.stoneleaf@gmail.com>
* timwolla <timwolla@mail.develfusion.com> * timwolla <timwolla@mail.develfusion.com>
* TJ09 <TJ09@localhost>
* untergrundbiber <untergrundbiber@github>
* Philippe Villiers <kissifrot@gmail.com>
* Jeffrey Warren <warren@mit.edu> * Jeffrey Warren <warren@mit.edu>

116
contrib/contributors.py Executable file
View File

@@ -0,0 +1,116 @@
#!/usr/bin/python2
"""Update the contributor list
Alias handling is done by git with .mailmap
New contributors are merged in the short-term list.
Moving them to a "higher" list should be a manual process.
"""
import fileinput
from subprocess import Popen, PIPE
def format_contributor(contributor):
return " * {0} {1}".format(
" ".join(contributor["name"]),
contributor["email"])
def main():
# generate list of contributors
contributors = []
p_git = Popen(["git", "shortlog", "-se"], stdout=PIPE)
for line in p_git.stdout:
contributors.append({
'count': int(line.split("\t")[0].strip()),
'name': line.split("\t")[1].split()[0:-1],
'email': line.split("\t")[1].split()[-1]
})
# cache listed contributors
old_contributors = []
with open("CONTRIBUTORS.rst", "r") as contrib_file:
for line in contrib_file:
if "@" in line:
old_contributors.append({
'name': line.split()[1:-1],
'email': line.split()[-1]
})
old = map(lambda x: (x['name'], x['email']), old_contributors)
old_emails = map(lambda x: x['email'], old_contributors)
old_names = map(lambda x: x['name'], old_contributors)
# check which contributors are new
new_contributors = []
update_mailmap = False
for contributor in contributors:
if (contributor['name'], contributor['email']) in old:
# this exact combination already in the list
pass
elif (contributor['email'] not in old_emails
and contributor['name'] not in old_names):
# name AND email are not in the list
new_contributors.append(contributor)
elif contributor['email'] in old_emails:
# email is listed, but with another name
old_name = filter(lambda x: x['email'] == contributor['email'],
old_contributors)[0]['name']
print "new alias %s for %s %s ?" % (
" ".join(contributor['name']),
" ".join(old_name),
contributor['email'])
update_mailmap = True
elif contributor['name'] in old_names:
# probably a new email for a previous contributor
other_mail = filter(lambda x: x['name'] == contributor['name'],
old_contributors)[0]['email']
print "new email %s for %s %s ?" % (
contributor['email'],
" ".join(contributor['name']),
other_mail)
update_mailmap = True
if update_mailmap:
print "Please update .mailmap"
# sort on the last word of the name
new_contributors = sorted(new_contributors,
key=lambda x: x['name'][-1].lower())
# show new contributors to be merged to the list
if new_contributors:
print "inserting:"
for contributor in new_contributors:
print format_contributor(contributor)
# merge with alphabetical (by last part of name) contributor list
i = 0
short_term_found = False
for line in fileinput.input("CONTRIBUTORS.rst", inplace=1):
if not short_term_found:
print line,
if "Short-term" in line:
short_term_found = True
else:
if i >= len(new_contributors) or "@" not in line:
print line,
else:
listed_name = line.split()[-2].lower()
contributor = new_contributors[i]
# insert all new contributors that fit here
while listed_name > contributor["name"][-1].lower():
print format_contributor(contributor)
i += 1
if i < len(new_contributors):
contributor = new_contributors[i]
else:
break
print line,
# append remaining contributors
with open("CONTRIBUTORS.rst", "a") as contrib_file:
while i < len(new_contributors):
contrib_file.write(format_contributor(new_contributors[i]) + "\n")
i += 1
if __name__ == "__main__":
main()

View File

@@ -70,7 +70,7 @@ A more complicated example
renders["survivalnight"] = { renders["survivalnight"] = {
"world": "survival", "world": "survival",
"title": "Survival Daytime", "title": "Survival Nighttime",
"rendermode": smooth_night, "rendermode": smooth_night,
"dimension": "overworld", "dimension": "overworld",
} }
@@ -461,11 +461,19 @@ values. The valid configuration keys are listed below.
**Default:** ``#1a1a1a`` **Default:** ``#1a1a1a``
``base``
Allows you to specify a remote location for the tile folder, useful if you
rsync your map's images to a remote server. Leave a trailing slash and point
to the location that contains the tile folders for each render, not the
tiles folder itself. For example, if the tile images start at
http://domain.com/map/world_day/ you want to set this to http://domain.com/map/
.. _option_texture_pack: .. _option_texture_pack:
``texturepath`` ``texturepath``
This is a where a specific texture pack can be found to be used during this render. This is a where a specific texture pack can be found to be used during this render.
It can be either a folder or a directory. Its value should be a string. It can be either a folder or a zip file containing the texture pack.
Its value should be a string.
.. _crop: .. _crop:
@@ -574,6 +582,14 @@ values. The valid configuration keys are listed below.
**Default:** ``[]`` (an empty list) **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 other renders.
**Default:** ``[]`` (an empty list)
``showspawn`` ``showspawn``
This is a boolean, and defaults to ``True``. If set to ``False``, then the spawn This is a boolean, and defaults to ``True``. If set to ``False``, then the spawn
icon will not be displayed on the rendered map. icon will not be displayed on the rendered map.
@@ -628,6 +644,13 @@ Nether
HeightFading HeightFading
Draws a colored overlay on the blocks that fades them out according to their Draws a colored overlay on the blocks that fades them out according to their
height. height.
**Options**
sealevel
sealevel of the word you're rendering. Note that the default,
128, is usually *incorrect* for most worlds. You should
probably set this to 64. Default: 128
Depth Depth
Only renders blocks between the specified min and max heights. Only renders blocks between the specified min and max heights.
@@ -640,6 +663,17 @@ Depth
max max
highest level of blocks to render. Default: 255 highest level of blocks to render. Default: 255
Exposed
Only renders blocks that are exposed (adjacent to a transparent block).
**Options**
mode
when set to 1, inverts the render mode, only drawing unexposed blocks. Default: 0
NoFluids
Don't render fluid blocks (water, lava).
EdgeLines EdgeLines
Draw edge lines on the back side of blocks, to help distinguish them from Draw edge lines on the back side of blocks, to help distinguish them from
the background. the background.
@@ -658,6 +692,15 @@ Cave
only_lit only_lit
Only render lit caves. Default: False 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 DepthTinting
Tint blocks a color according to their depth (height) from bedrock. Useful Tint blocks a color according to their depth (height) from bedrock. Useful
mainly for cave renders. mainly for cave renders.
@@ -687,16 +730,16 @@ ClearBase
Forces the background to be transparent. Use this in place of Base Forces the background to be transparent. Use this in place of Base
for rendering pure overlays. for rendering pure overlays.
.. warning::
Overlays are currently not functional in this branch of code. We are
working on them. Please inquire in :ref:`IRC<help>` for more information.
SpawnOverlay SpawnOverlay
Color the map red in areas where monsters can spawn. Either use Color the map red in areas where monsters can spawn. Either use
this on top of other modes, or on top of ClearBase to create a this on top of other modes, or on top of ClearBase to create a
pure overlay. pure overlay.
SlimeOverlay
Color the map green in chunks where slimes can spawn. Either use
this on top of other modes, or on top of ClearBase to create a
pure overlay.
MineralOverlay MineralOverlay
Color the map according to what minerals can be found Color the map according to what minerals can be found
underneath. Either use this on top of other modes, or on top of underneath. Either use this on top of other modes, or on top of

View File

@@ -17,7 +17,7 @@ So let's get started!
.. note:: .. note::
This page is still under construction This page is continually under construction
.. contents:: .. contents::
@@ -82,7 +82,7 @@ pre-rendered sprite (a small image). The basic idea is to iterate over the
blocks of the world and draw these sprites to the appropriate location on the blocks of the world and draw these sprites to the appropriate location on the
map. map.
These are the high-level tasks The Overviewer must preform in rendering a map: These are the high-level tasks The Overviewer must perform in rendering a map:
1. Render each block sprite from the textures 1. Render each block sprite from the textures
2. Scan the chunks of the world and determine which tiles need rendering 2. Scan the chunks of the world and determine which tiles need rendering
@@ -143,7 +143,7 @@ transformations can be chained together simply by multiplying the transformation
matrices together, only one transformation is actually done. matrices together, only one transformation is actually done.
This can be seen in the function This can be seen in the function
:func:`overviewer_core.textures.transform_image`. It preforms three steps: :func:`overviewer_core.textures.transform_image`. It performs three steps:
1. The texture is re-sized to 17 by 17 pixels. This is done because the diagonal 1. The texture is re-sized to 17 by 17 pixels. This is done because the diagonal
of a square with sides 17 is approximately 24, which is the target size for of a square with sides 17 is approximately 24, which is the target size for

View File

@@ -246,7 +246,7 @@ If you want or need to provide your own textures, you have several options:
overviewer.exe. overviewer.exe.
* Specify any terrain.png or texture pack you want with the * Specify any terrain.png or texture pack you want with the
:ref:`texture_pack<option_texture_pack>` option. :ref:`texturepath<option_texturepath>` option.
If you copy your world before you render it If you copy your world before you render it
------------------------------------------- -------------------------------------------

View File

@@ -20,51 +20,100 @@ Filter Functions
---------------- ----------------
A filter function is a python function that is used to figure out if a given POI 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 should be part of a markerSet of not, and to control how it is displayed.
(a dictionary, also know as an associative array), and return a boolean:: 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): def signFilter(poi):
"All signs" if poi['id'] == 'Sign':
return 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 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 the chunk file. It could also be a special entity representing a player's location
of entity is a sign. For more information of TileEntities and Entities, see 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 `Chunk Format <http://www.minecraftwiki.net/wiki/Chunk_format>`_ page on
the Minecraft Wiki. the Minecraft Wiki.
.. note:: A more complicated filter function can construct a more customized display text::
The doc string ("All signs" in this example) is important. It is the label
that appears in your rendered map
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 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 functions are provided. See the :ref:`predefined_filter_functions` section for
details. 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 Render Dictionary Key
--------------------- ---------------------
Each render can specify a list of zero or more filter functions. Each of these 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 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'] = { renders['myrender'] = {
'world': 'myworld', 'world': 'myworld',
'title': "Example", 'title': "Example",
'markers': [allFilter, anotherFilter], 'markers': [dict(name="All signs", filterFunction=signFilter),
dict(name="Chests", filterFunction=chestFilter, icon="chest.png", createInfoWindow=False)]
} }
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)
``createInfoWindow``
Optional. Specifies whether or not the icon displays an info window on click. Defaults to True
Generating the POI Markers Generating the POI Markers

View File

@@ -89,6 +89,8 @@ def main():
help="Print less output. You can specify this option multiple times.") help="Print less output. You can specify this option multiple times.")
parser.add_option("-v", "--verbose", dest="verbose", action="count", default=0, parser.add_option("-v", "--verbose", dest="verbose", action="count", default=0,
help="Print more output. You can specify this option multiple times.") 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) # create a group for "plugin exes" (the concept of a plugin exe is only loosly defined at this point)
exegroup = OptionGroup(parser, "Other Scripts", exegroup = OptionGroup(parser, "Other Scripts",
@@ -104,9 +106,9 @@ def main():
if options.genpoi: if options.genpoi:
# remove the "--genpoi" option from sys.argv before running genPI # remove the "--genpoi" option from sys.argv before running genPI
sys.argv.remove("--genpoi") sys.argv.remove("--genpoi")
sys.path.append(".") #sys.path.append(".")
g = __import__("genPOI", {}, {}) g = __import__("overviewer_core.aux_files", {}, {}, ["genPOI"])
g.main() g.genPOI.main()
return 0 return 0
if options.help: if options.help:
parser.print_help() parser.print_help()
@@ -114,7 +116,8 @@ def main():
# re-configure the logger now that we've processed the command line options # re-configure the logger now that we've processed the command line options
logger.configure(logging.INFO + 10*options.quiet - 10*options.verbose, 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, # 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 return 1
# Parse the config file # Parse the config file
mw_parser = configParser.MultiWorldParser()
mw_parser.parse(options.config) mw_parser.parse(options.config)
# Add in the command options here, perhaps overriding values specified in # 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 # Now parse and return the validated config
try: try:
config = mw_parser.get_validated_config() config = mw_parser.get_validated_config()
except Exception: except Exception as ex:
logging.exception("An error was encountered with your configuration. See the info below.") 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 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): if render.get('forcerender', False):
render['renderchecks'] = 2 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'] destdir = config['outputdir']
if not destdir: if not destdir:
logging.error("You must specify the output directory in your config file.") logging.error("You must specify the output directory in your config file.")
@@ -401,9 +421,9 @@ 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 # 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 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"]) 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 tileSetOpts.update({"spawn": w.find_true_spawn()}) # TODO find a better way to do this
tset = tileset.TileSet(rset, assetMrg, tex, tileSetOpts, tileset_dir) tset = tileset.TileSet(w, rset, assetMrg, tex, tileSetOpts, tileset_dir)
tilesets.append(tset) tilesets.append(tset)
# Do tileset preprocessing here, before we start dispatching jobs # Do tileset preprocessing here, before we start dispatching jobs

View File

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

View File

View File

@@ -17,6 +17,7 @@ markers.js holds a list of which markerSets are attached to each tileSet
import os import os
import logging import logging
import json import json
import sys
from optparse import OptionParser from optparse import OptionParser
from overviewer_core import logger from overviewer_core import logger
@@ -25,7 +26,8 @@ from overviewer_core import configParser, world
def handleSigns(rset, outputdir, render, rname): def handleSigns(rset, outputdir, render, rname):
# if we're already handled the POIs for this region regionset, do nothing
if hasattr(rset, "_pois"): if hasattr(rset, "_pois"):
return return
@@ -39,10 +41,64 @@ def handleSigns(rset, outputdir, render, rname):
rset._pois['TileEntities'] += data['TileEntities'] rset._pois['TileEntities'] += data['TileEntities']
rset._pois['Entities'] += data['Entities'] 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(): def main():
helptext = """genPOI
%prog --config=<config file>""" 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() logger.configure()
@@ -97,26 +153,47 @@ def main():
return 1 return 1
for f in render['markers']: for f in render['markers']:
markersets.add((f, rset)) d = dict(icon="signpost_icon.png", createInfoWindow=True)
name = f.__name__ + hex(hash(f))[-4:] + "_" + hex(hash(rset))[-4:] d.update(f)
markersets.add(((d['name'], d['filterFunction']), rset))
name = f['name'].replace(" ","_") + hex(hash(f['filterFunction']))[-4:] + "_" + hex(hash(rset))[-4:]
try: try:
l = markers[rname] l = markers[rname]
l.append(dict(groupName=name, displayName = f.__doc__)) l.append(dict(groupName=name, displayName = f['name'], icon=d['icon'], createInfoWindow=d['createInfoWindow']))
except KeyError: except KeyError:
markers[rname] = [dict(groupName=name, displayName=f.__doc__),] markers[rname] = [dict(groupName=name, displayName=f['name'], icon=d['icon'], createInfoWindow=d['createInfoWindow']),]
handleSigns(rset, os.path.join(destdir, rname), render, rname) handleSigns(rset, os.path.join(destdir, rname), render, rname)
handlePlayers(rset, render, worldpath)
logging.info("Done scanning regions") logging.info("Done scanning regions")
logging.info("Writing out javascript files") logging.info("Writing out javascript files")
markerSetDict = dict() markerSetDict = dict()
for (flter, rset) in markersets: for (flter, rset) in markersets:
# generate a unique name for this markerset. it will not be user visible # generate a unique name for this markerset. it will not be user visible
name = flter.__name__ + hex(hash(flter))[-4:] + "_" + hex(hash(rset))[-4:] filter_name = flter[0]
markerSetDict[name] = dict(created=False, raw=[]) 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']: for poi in rset._pois['TileEntities']:
if flter(poi): result = filter_function(poi)
markerSetDict[name]['raw'].append(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 #print markerSetDict
with open(os.path.join(destdir, "markersDB.js"), "w") as output: with open(os.path.join(destdir, "markersDB.js"), "w") as output:

View File

@@ -67,6 +67,8 @@ overviewer.util = {
signs.registerEvents(signs); signs.registerEvents(signs);
} }
var overlayControl = new overviewer.views.OverlayControlView();
var spawnmarker = new overviewer.views.SpawnIconView(); var spawnmarker = new overviewer.views.SpawnIconView();
// Update coords on mousemove // Update coords on mousemove
@@ -85,6 +87,9 @@ overviewer.util = {
compass.render(); compass.render();
spawnmarker.render(); spawnmarker.render();
// update list of spawn overlays
overlayControl.render();
// re-center on the last viewport // re-center on the last viewport
var currentWorldView = overviewer.mapModel.get("currentWorldView"); var currentWorldView = overviewer.mapModel.get("currentWorldView");
if (currentWorldView.options.lastViewport) { 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 // hook up some events
@@ -129,6 +132,11 @@ overviewer.util = {
// Jump to the hash if given // Jump to the hash if given
overviewer.util.initHash(); 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(); overviewer.util.initializeMarkers();
/* /*

View File

@@ -4,8 +4,19 @@ overviewer.views= {}
overviewer.views.WorldView = Backbone.View.extend({ overviewer.views.WorldView = Backbone.View.extend({
initialize: function(opts) { initialize: function(opts) {
this.options.mapTypes = []; this.options.mapTypes = [];
this.options.overlayMapTypes = [];
this.options.mapTypeIds = []; 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) { this.model.get("tileSets").each(function(tset, index, list) {
// ignore overlays:
var ops = { var ops = {
getTileUrl: overviewer.gmap.getTileUrlGenerator(tset.get("path"), tset.get("base"), tset.get("imgextension")), getTileUrl: overviewer.gmap.getTileUrlGenerator(tset.get("path"), tset.get("base"), tset.get("imgextension")),
'tileSize': new google.maps.Size( 'tileSize': new google.maps.Size(
@@ -20,11 +31,24 @@ overviewer.views.WorldView = Backbone.View.extend({
newMapType.shortname = tset.get("name"); newMapType.shortname = tset.get("name");
newMapType.alt = "Minecraft " + tset.get("name") + " Map"; newMapType.alt = "Minecraft " + tset.get("name") + " Map";
newMapType.projection = new overviewer.classes.MapProjection(); newMapType.projection = new overviewer.classes.MapProjection();
newMapType._ov_tileSet = tset;
this.options.mapTypes.push(newMapType);
this.options.mapTypeIds.push(overviewerConfig.CONST.mapDivId + this.model.get("name") + tset.get("name")); 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);
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({ overviewer.views.WorldSelectorView = Backbone.View.extend({
initialize: function() { initialize: function() {
if(overviewer.collections.worldViews.length > 1) { if(overviewer.collections.worldViews.length > 1) {
$(this.el).addClass("customControl");
// a div will have already been created for us, we just // a div will have already been created for us, we just
// need to register it with the google maps control // need to register it with the google maps control
var selectBox = document.createElement('select'); var selectBox = document.createElement('select');
@@ -40,6 +66,9 @@ overviewer.views.WorldSelectorView = Backbone.View.extend({
var o = document.createElement("option"); var o = document.createElement("option");
o.value = elem.model.get("name"); o.value = elem.model.get("name");
o.innerHTML = 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); $(o).data("viewObj", elem);
selectBox.appendChild(o); selectBox.appendChild(o);
@@ -139,17 +168,16 @@ overviewer.views.GoogleMapView = Backbone.View.extend({
var curWorld = this.model.get("currentWorldView").model; var curWorld = this.model.get("currentWorldView").model;
var curTset = curWorld.get("tileSets").at(0); 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.mapTypes=[];
this.options.mapTypeIds=[]; this.options.mapTypeIds=[];
@@ -208,7 +236,7 @@ overviewer.views.GoogleMapView = Backbone.View.extend({
var gmapCurrent = overviewer.map.getMapTypeId(); var gmapCurrent = overviewer.map.getMapTypeId();
for (id in currentWorldView.options.mapTypeIds) { for (id in currentWorldView.options.mapTypeIds) {
if (currentWorldView.options.mapTypeIds[id] == gmapCurrent) { 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 = overviewer.collections.markerInfo[curMarkerSet];
var dataRoot = markers[curMarkerSet]; var dataRoot = markers[curMarkerSet];
this.el.innerHTML="" this.el.innerHTML="";
// if we have no markerSets for this tileset, do nothing: // if we have no markerSets for this tileset, do nothing:
if (!dataRoot) { return; } if (!dataRoot) { return; }
@@ -322,7 +458,6 @@ overviewer.views.SignControlView = Backbone.View.extend({
}}); }});
} }
iconURL = overviewerConfig.CONST.image.signMarker;
//dataRoot['markers'] = []; //dataRoot['markers'] = [];
// //
for (i in dataRoot) { for (i in dataRoot) {
@@ -330,16 +465,25 @@ overviewer.views.SignControlView = Backbone.View.extend({
if (!markersDB[groupName].created) { if (!markersDB[groupName].created) {
for (j in markersDB[groupName].raw) { for (j in markersDB[groupName].raw) {
var entity = markersDB[groupName].raw[j]; var entity = markersDB[groupName].raw[j];
if (entity['icon']) {
iconURL = entity['icon'];
} else {
iconURL = dataRoot[i].icon;
}
var marker = new google.maps.Marker({ var marker = new google.maps.Marker({
'position': overviewer.util.fromWorldToLatLng(entity.x, 'position': overviewer.util.fromWorldToLatLng(entity.x,
entity.y, entity.z, overviewer.mapView.options.currentTileSet), entity.y, entity.z, overviewer.mapView.options.currentTileSet),
'map': overviewer.map, 'map': overviewer.map,
'title': jQuery.trim(entity.Text1 + "\n" + entity.Text2 + "\n" + entity.Text3 + "\n" + entity.Text4), 'title': jQuery.trim(entity.text),
'icon': iconURL, 'icon': iconURL,
'visible': false 'visible': false
}); });
if (entity['id'] == 'Sign') { if(entity['createInfoWindow'] == true) {
overviewer.util.createMarkerInfoWindow(marker); overviewer.util.createMarkerInfoWindow(marker);
} else {
if(dataRoot[i].createInfoWindow == true) {
overviewer.util.createMarkerInfoWindow(marker);
}
} }
jQuery.extend(entity, {markerObj: marker}); jQuery.extend(entity, {markerObj: marker});
} }
@@ -368,13 +512,13 @@ overviewer.views.SignControlView = Backbone.View.extend({
var textNode = document.createElement('text'); var textNode = document.createElement('text');
if(item.icon) { if(item.icon) {
textNode.innerHTML = '<img width="15" height="15" src="' + textNode.innerHTML = '<img width="15" height="15" src="' +
item.icon + '">' + item.label + '<br/>'; item.icon + '">' + item.label + '&nbsp;<br/>';
} else { } else {
textNode.innerHTML = item.label + '<br/>'; textNode.innerHTML = item.label + '&nbsp;<br/>';
} }
itemDiv.appendChild(textNode); 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; 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 { .customControl > div.top {
font-size: 12px; font-size: 12px;
line-height: 160%; line-height: 160%;

View File

@@ -254,7 +254,7 @@ class ANSIColorFormatter(HighlightingFormatter):
# No coloring if it's not to be highlighted or colored # No coloring if it's not to be highlighted or colored
return logging.Formatter.format(self, record) 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 """Configures the root logger to our liking
For a non-standard loglevel, pass in the level with which to configure the handler. 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() 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 # Our custom output stream processor knows how to deal with select ANSI
# color escape sequences # color escape sequences
outstream = WindowsOutputStream() outstream = WindowsOutputStream(outstream)
formatter = ANSIColorFormatter(verbose) formatter = ANSIColorFormatter(verbose)
elif sys.stderr.isatty(): elif outstream.isatty():
# terminal logging with ANSI color # terminal logging with ANSI color
formatter = ANSIColorFormatter(verbose) formatter = ANSIColorFormatter(verbose)

View File

@@ -52,6 +52,10 @@ class Nether(RenderPrimitive):
class HeightFading(RenderPrimitive): class HeightFading(RenderPrimitive):
name = "height-fading" 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)) black_color = Image.new("RGB", (24,24), (0,0,0))
white_color = Image.new("RGB", (24,24), (255,255,255)) 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), "min": ("lowest level of blocks to render", 0),
"max": ("highest level of blocks to render", 255), "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): class EdgeLines(RenderPrimitive):
name = "edge-lines" name = "edge-lines"
@@ -182,12 +195,21 @@ class Overlay(RenderPrimitive):
class SpawnOverlay(Overlay): class SpawnOverlay(Overlay):
name = "overlay-spawn" name = "overlay-spawn"
class SlimeOverlay(Overlay):
name = "overlay-slime"
class MineralOverlay(Overlay): class MineralOverlay(Overlay):
name = "overlay-mineral" name = "overlay-mineral"
options = { options = {
'minerals' : ('a list of (blockid, (r, g, b)) tuples for coloring minerals', None), '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! # Built-in rendermodes for your convenience!
normal = [Base(), EdgeLines()] normal = [Base(), EdgeLines()]
lighting = [Base(), EdgeLines(), Lighting()] 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), "nomarkers": Setting(required=False, validator=validateBool, default=None),
"texturepath": Setting(required=False, validator=validateTexturePath, default=None), "texturepath": Setting(required=False, validator=validateTexturePath, default=None),
"renderchecks": Setting(required=False, validator=validateInt, 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), "crop": Setting(required=False, validator=validateCrop, default=None),
"changelist": Setting(required=False, validator=validateStr, default=None), "changelist": Setting(required=False, validator=validateStr, default=None),
"markers": Setting(required=False, validator=validateMarkers, default=[]), "markers": Setting(required=False, validator=validateMarkers, default=[]),
"overlay": Setting(required=False, validator=validateOverlays, default=[]),
"showspawn": Setting(required=False, validator=validateBool, default=True), "showspawn": Setting(required=False, validator=validateBool, default=True),
"base": Setting(required=False, validator=validateStr, default=""),
# Remove this eventually (once people update their configs) # Remove this eventually (once people update their configs)
"worldname": Setting(required=False, default=None, "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. # ends up adding overhead and isn't worth it.
memcached_host = Setting(required=False, validator=str, default=None) 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() obs = LoggingObserver()
else: else:
obs = ProgressBarObserver() obs = ProgressBarObserver(fd=sys.stdout)
observer = Setting(required=True, validator=validateObserver, default=obs) observer = Setting(required=True, validator=validateObserver, default=obs)

View File

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

View File

@@ -24,11 +24,6 @@
#include "overviewer.h" #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 { typedef struct {
PyObject_HEAD PyObject_HEAD
Imaging image; Imaging image;

View File

@@ -432,7 +432,6 @@ generate_pseudo_data(RenderState *state, unsigned char ancilData) {
PyObject* PyObject*
chunk_render(PyObject *self, PyObject *args) { chunk_render(PyObject *self, PyObject *args) {
RenderState state; RenderState state;
PyObject *regionset;
PyObject *modeobj; PyObject *modeobj;
PyObject *blockmap; PyObject *blockmap;
@@ -453,7 +452,7 @@ chunk_render(PyObject *self, PyObject *args) {
PyObject *t = NULL; 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; return NULL;
/* set up the render mode */ /* set up the render mode */

View File

@@ -26,13 +26,18 @@
// increment this value if you've made a change to the c extesion // increment this value if you've made a change to the c extesion
// and want to force users to rebuild // and want to force users to rebuild
#define OVERVIEWER_EXTENSION_VERSION 30 #define OVERVIEWER_EXTENSION_VERSION 37
/* Python PIL, and numpy headers */ /* Python PIL, and numpy headers */
#include <Python.h> #include <Python.h>
#include <Imaging.h> #include <Imaging.h>
#include <numpy/arrayobject.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 /* macro for getting a value out of various numpy arrays the 3D arrays have
interesting, swizzled coordinates because minecraft (anvil) stores blocks interesting, swizzled coordinates because minecraft (anvil) stores blocks
in y/z/x order for 3D, z/x order for 2D */ in y/z/x order for 3D, z/x order for 2D */
@@ -83,6 +88,7 @@ typedef struct {
} ChunkData; } ChunkData;
typedef struct { typedef struct {
/* the regionset object, and chunk coords */ /* the regionset object, and chunk coords */
PyObject *world;
PyObject *regionset; PyObject *regionset;
int chunkx, chunky, chunkz; int chunkx, chunky, chunkz;

View File

@@ -29,46 +29,53 @@ typedef struct {
typedef struct { typedef struct {
const char* name; const char* name;
float temperature; float temperature;
float rainfall; float rainfall;
unsigned int r, g, b;
} Biome; } Biome;
/* each entry in this table is yanked *directly* out of the minecraft source /* each entry in this table is yanked *directly* out of the minecraft source
* temp/rainfall are taken from what MCP calls setTemperatureRainfall * 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* * keep in mind the x/y coordinate in the color tables is found *after*
* multiplying rainfall and temperature for the second coordinate, *and* the * multiplying rainfall and temperature for the second coordinate, *and* the
* origin is in the lower-right. <3 biomes. * origin is in the lower-right. <3 biomes.
*/ */
static Biome biome_table[] = { static Biome biome_table[] = {
/* 0 */ /* 0 */
{"Ocean", 0.5, 0.5}, {"Ocean", 0.5, 0.5, 255, 255, 255},
{"Plains", 0.8, 0.4}, {"Plains", 0.8, 0.4, 255, 255, 255},
{"Desert", 2.0, 0.0}, {"Desert", 2.0, 0.0, 255, 255, 255},
{"Extreme Hills", 0.2, 0.3}, {"Extreme Hills", 0.2, 0.3, 255, 255, 255},
{"Forest", 0.7, 0.8}, {"Forest", 0.7, 0.8, 255, 255, 255},
/* 5 */ /* 5 */
{"Taiga", 0.05, 0.8}, {"Taiga", 0.05, 0.8, 255, 255, 255},
{"Swampland", 0.8, 0.9}, {"Swampland", 0.8, 0.9, 205, 128, 255},
{"River", 0.5, 0.5}, {"River", 0.5, 0.5, 255, 255, 255},
{"Hell", 2.0, 0.0}, {"Hell", 2.0, 0.0, 255, 255, 255},
{"Sky", 0.5, 0.5}, {"Sky", 0.5, 0.5, 255, 255, 255},
/* 10 */ /* 10 */
{"FrozenOcean", 0.0, 0.5}, {"FrozenOcean", 0.0, 0.5, 255, 255, 255},
{"FrozenRiver", 0.0, 0.5}, {"FrozenRiver", 0.0, 0.5, 255, 255, 255},
{"Ice Plains", 0.0, 0.5}, {"Ice Plains", 0.0, 0.5, 255, 255, 255},
{"Ice Mountains", 0.0, 0.5}, {"Ice Mountains", 0.0, 0.5, 255, 255, 255},
{"MushroomIsland", 0.9, 1.0}, {"MushroomIsland", 0.9, 1.0, 255, 255, 255},
/* 15 */ /* 15 */
{"MushroomIslandShore", 0.9, 1.0}, {"MushroomIslandShore", 0.9, 1.0, 255, 255, 255},
{"Beach", 0.8, 0.4}, {"Beach", 0.8, 0.4, 255, 255, 255},
{"DesertHills", 2.0, 0.0}, {"DesertHills", 2.0, 0.0, 255, 255, 255},
{"ForestHills", 0.7, 0.8}, {"ForestHills", 0.7, 0.8, 255, 255, 255},
{"TaigaHills", 0.05, 0.8}, {"TaigaHills", 0.05, 0.8, 255, 255, 255},
/* 20 */ /* 20 */
{"Extreme Hills Edge", 0.2, 0.3}, {"Extreme Hills Edge", 0.2, 0.3, 255, 255, 255},
{"Jungle", 2.0, 0.45}, /* <-- GUESS, but a good one */ {"Jungle", 2.0, 0.45, 255, 255, 255}, /* <-- GUESS, but a good one */
{"Jungle Mountains", 2.0, 0.45}, /* <-- also a guess */ {"Jungle Mountains", 2.0, 0.45, 255, 255, 255}, /* <-- also a guess */
}; };
#define NUM_BIOMES (sizeof(biome_table) / sizeof(Biome)) #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) { if (color_table) {
unsigned char biome;
int dx, dz; int dx, dz;
unsigned char tablex, tabley; unsigned char tablex, tabley;
float temp = 0.0, rain = 0.0; float temp = 0.0, rain = 0.0;
unsigned int multr = 0, multg = 0, multb = 0;
int tmp;
PyObject *color = NULL; PyObject *color = NULL;
if (self->use_biomes) { if (self->use_biomes) {
/* average over all neighbors */ /* average over all neighbors */
for (dx = -1; dx <= 1; dx++) { for (dx = -1; dx <= 1; dx++) {
for (dz = -1; dz <= 1; dz++) { 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) { if (biome >= NUM_BIOMES) {
/* note -- biome 255 shows up on map borders. /* note -- biome 255 shows up on map borders.
who knows what it is? certainly not I. 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; temp += biome_table[biome].temperature;
rain += biome_table[biome].rainfall; rain += biome_table[biome].rainfall;
multr += biome_table[biome].r;
multg += biome_table[biome].g;
multb += biome_table[biome].b;
} }
} }
temp /= 9.0; temp /= 9.0;
rain /= 9.0; rain /= 9.0;
multr /= 9;
multg /= 9;
multb /= 9;
} else { } else {
/* don't use biomes, just use the default */ /* don't use biomes, just use the default */
temp = biome_table[DEFAULT_BIOME].temperature; temp = biome_table[DEFAULT_BIOME].temperature;
rain = biome_table[DEFAULT_BIOME].rainfall; 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 /* 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)); g = PyInt_AsLong(PyTuple_GET_ITEM(color, 1));
b = PyInt_AsLong(PyTuple_GET_ITEM(color, 2)); b = PyInt_AsLong(PyTuple_GET_ITEM(color, 2));
Py_DECREF(color); Py_DECREF(color);
/* do the after-coloration */
r = MULDIV255(r, multr, tmp);
g = MULDIV255(g, multg, tmp);
b = MULDIV255(b, multb, tmp);
} }
/* final coloration */ /* 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); Imaging img_i = imaging_python_to_c(state->img);
unsigned char ink[] = {0, 0, 0, 255 * self->opacity}; unsigned char ink[] = {0, 0, 0, 255 * self->opacity};
unsigned short side_block; unsigned short side_block;
int x = state->x, y = state->y, z = state->z;
int increment=0; 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; increment=6;
else if ((state->block == 78) || (state->block == 93) || (state->block == 94)) // snow, redstone repeaters (on and off) else if ((state->block == 78) || (state->block == 93) || (state->block == 94)) // snow, redstone repeaters (on and off)
increment=9; increment=9;
/* +X side */ /* +X side */
side_block = get_data(state, BLOCKS, state->x+1, state->y, state->z); side_block = get_data(state, BLOCKS, x+1, y, z);
if (side_block != state->block && is_transparent(side_block)) { 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+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); ImagingDrawLine(img_i, state->imgx+12, state->imgy+increment, state->imgx+22+1, state->imgy+5+increment, &ink, 1);
} }
/* -Z side */ /* -Z side */
side_block = get_data(state, BLOCKS, state->x, state->y, state->z-1); side_block = get_data(state, BLOCKS, x, y, z-1);
if (side_block != state->block && is_transparent(side_block)) { 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+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); 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 { typedef struct {
PyObject *black_color; PyObject *black_color;
PyObject *white_color; PyObject *white_color;
unsigned int sealevel;
} PrimitiveHeightFading; } PrimitiveHeightFading;
static int static int
height_fading_start(void *data, RenderState *state, PyObject *support) { height_fading_start(void *data, RenderState *state, PyObject *support) {
PrimitiveHeightFading *self = (PrimitiveHeightFading *)data; 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->black_color = PyObject_GetAttrString(support, "black_color");
self->white_color = PyObject_GetAttrString(support, "white_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; PyObject *height_color = self->white_color;
/* current formula requires y to be between 0 and 127, so scale it */ /* 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 */ /* negative alpha => darkness, positive => light */
alpha = (1.0 / (1 + expf((70 - y) / 11.0))) * 0.6 - 0.55; 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 */ /* this face isn't visible, so don't draw anything */
return 1; return 1;
} }
} else if (skip_sides) { } else if (!skip_sides) {
unsigned short block = get_data(state, BLOCKS, x, y, z); unsigned short block = get_data(state, BLOCKS, x, y, z);
if (!is_transparent(block)) { if (!is_transparent(block)) {
/* the same thing but for adjacent chunks, this solves an /* the same thing but for adjacent chunks, this solves an
@@ -242,8 +242,8 @@ lighting_start(void *data, RenderState *state, PyObject *support) {
RenderPrimitiveLighting* self; RenderPrimitiveLighting* self;
self = (RenderPrimitiveLighting *)data; self = (RenderPrimitiveLighting *)data;
/* skip sides by default */ /* don't skip sides by default */
self->skip_sides = 1; self->skip_sides = 0;
if (!render_mode_parse_option(support, "strength", "f", &(self->strength))) if (!render_mode_parse_option(support, "strength", "f", &(self->strength)))
return 1; 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 */ /* now do custom initializations */
self = (RenderPrimitiveMineral *)data; self = (RenderPrimitiveMineral *)data;
// opt is a borrowed reference. do not deref
if (!render_mode_parse_option(support, "minerals", "O", &(opt))) if (!render_mode_parse_option(support, "minerals", "O", &(opt)))
return 1; return 1;
if (opt && opt != Py_None) { if (opt && opt != Py_None) {
@@ -119,7 +120,6 @@ overlay_mineral_start(void *data, RenderState *state, PyObject *support) {
} else { } else {
self->minerals = default_minerals; self->minerals = default_minerals;
} }
Py_XDECREF(opt);
/* setup custom color */ /* setup custom color */
self->parent.get_color = get_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 */ /* do the overlay */
if (a > 0) { if (a > 0) {
alpha_over(state->img, self->white_color, 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, a, self->facemask_top, 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) if verbose: logging.info("Found %s in '%s'", filename, path)
return open(path, mode) 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): def load_image(self, filename):
"""Returns an image object""" """Returns an image object"""
@@ -1336,6 +1336,8 @@ block(blockid=41, top_index=23)
block(blockid=42, top_index=22) block(blockid=42, top_index=22)
# double slabs and slabs # 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) @material(blockid=[43, 44], data=range(16), transparent=(44,), solid=True)
def slabs(self, blockid, data): def slabs(self, blockid, data):
texture = data & 7 texture = data & 7
@@ -3169,3 +3171,52 @@ block(blockid=123, top_index=211)
# active redstone lamp # active redstone lamp
block(blockid=124, top_index=212) 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 logging
import os import os
import os.path import os.path
import sys
import shutil import shutil
import random import random
import functools import functools
@@ -32,6 +33,7 @@ from .util import roundrobin
from . import nbt from . import nbt
from .files import FileReplacer from .files import FileReplacer
from .optimizeimages import optimize_image from .optimizeimages import optimize_image
import rendermodes
import c_overviewer 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 """Construct a new TileSet object with the given configuration options
dictionary. dictionary.
options is a dictionary of configuration parameters (strings mapping to options is a dictionary of configuration parameters (strings mapping to
values) that are interpreted by the rendering engine. 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. regionsetobj is the RegionSet object that is used to render the tiles.
@@ -268,6 +272,7 @@ class TileSet(object):
""" """
self.options = options self.options = options
self.world = worldobj
self.regionset = regionsetobj self.regionset = regionsetobj
self.am = assetmanagerobj self.am = assetmanagerobj
self.textures = texturesobj self.textures = texturesobj
@@ -355,7 +360,7 @@ class TileSet(object):
# Only pickle the initial state. Don't pickle anything resulting from the # Only pickle the initial state. Don't pickle anything resulting from the
# do_preprocessing step # do_preprocessing step
def __getstate__(self): 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): def __setstate__(self, state):
self.__init__(*state) self.__init__(*state)
@@ -507,19 +512,25 @@ class TileSet(object):
""" """
def bgcolorformat(color): def bgcolorformat(color):
return "#%02x%02x%02x" % color[0:3] 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'), d = dict(name = self.options.get('title'),
zoomLevels = self.treedepth, zoomLevels = self.treedepth,
minZoom = 0, minZoom = 0,
defaultZoom = 1, defaultZoom = 1,
maxZoom = self.treedepth, maxZoom = self.treedepth,
path = self.options.get('name'), path = self.options.get('name'),
base = '', base = self.options.get('base'),
bgcolor = bgcolorformat(self.options.get('bgcolor')), bgcolor = bgcolorformat(self.options.get('bgcolor')),
world = self.options.get('worldname_orig') + world = self.options.get('worldname_orig') +
(" - " + self.options.get('dimension') if self.options.get('dimension') != 'default' else ''), (" - " + self.options.get('dimension') if self.options.get('dimension') != 'default' else ''),
last_rendertime = self.max_chunk_mtime, last_rendertime = self.max_chunk_mtime,
imgextension = self.imgextension, 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)): if (self.regionset.get_type() == "overworld" and self.options.get("showspawn", True)):
d.update({"spawn": self.options.get("spawn")}) d.update({"spawn": self.options.get("spawn")})
else: else:
@@ -580,7 +591,6 @@ class TileSet(object):
self.xradius = xradius self.xradius = xradius
self.yradius = yradius self.yradius = yradius
def _rearrange_tiles(self): def _rearrange_tiles(self):
"""If the target size of the tree is not the same as the existing size """If the target size of the tree is not the same as the existing size
on disk, do some re-arranging on disk, do some re-arranging
@@ -940,7 +950,7 @@ class TileSet(object):
# draw the chunk! # draw the chunk!
try: try:
c_overviewer.render_loop(self.regionset, chunkx, chunky, c_overviewer.render_loop(self.world, self.regionset, chunkx, chunky,
chunkz, tileimg, xpos, ypos, chunkz, tileimg, xpos, ypos,
self.options['rendermode'], self.textures) self.options['rendermode'], self.textures)
except nbt.CorruptionError: except nbt.CorruptionError:
@@ -948,8 +958,9 @@ class TileSet(object):
# get_chunk() # get_chunk()
logging.debug("Skipping the render of corrupt chunk at %s,%s and moving on.", chunkx, chunkz) logging.debug("Skipping the render of corrupt chunk at %s,%s and moving on.", chunkx, chunkz)
except Exception, e: 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.error("Could not render chunk %s,%s for some reason. This is likely a render primitive option error.", chunkx, chunkz)
logging.debug("Full error was:", exc_info=1) logging.error("Full error was:", exc_info=1)
sys.exit(1)
## Semi-handy routine for debugging the drawing routine ## Semi-handy routine for debugging the drawing routine
## Draw the outline of the top of the chunk ## Draw the outline of the top of the chunk

View File

@@ -136,7 +136,12 @@ class World(object):
except KeyError: except KeyError:
# but very old ones might not? so we'll just go with the world dir name if they don't # 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)) 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 # TODO figure out where to handle regionlists
@@ -204,6 +209,7 @@ class World(object):
if section['Y'] == targetSection: if section['Y'] == targetSection:
blockArray = section['Blocks'] blockArray = section['Blocks']
return blockArray[inChunkX, inChunkZ, y % 16] return blockArray[inChunkX, inChunkZ, y % 16]
return 0
@@ -304,7 +310,7 @@ class RegionSet(object):
* For each chunk section: * For each chunk section:
* The "Blocks" byte string is transformed into a 16x16x16 numpy array * 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 added into the Blocks array
* The "SkyLight" byte string is transformed into a 16x16x128 numpy * The "SkyLight" byte string is transformed into a 16x16x128 numpy
array array
@@ -380,11 +386,11 @@ class RegionSet(object):
# Cast up to uint16, blocks can have up to 12 bits of data # Cast up to uint16, blocks can have up to 12 bits of data
blocks = blocks.astype(numpy.uint16) blocks = blocks.astype(numpy.uint16)
blocks = blocks.reshape((16,16,16)) 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 # 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 # 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 = additional.astype(numpy.uint16).reshape((16,16,8))
additional_expanded = numpy.empty((16,16,16), dtype=numpy.uint16) additional_expanded = numpy.empty((16,16,16), dtype=numpy.uint16)
additional_expanded[:,:,::2] = (additional & 0x0F) << 8 additional_expanded[:,:,::2] = (additional & 0x0F) << 8
@@ -392,7 +398,7 @@ class RegionSet(object):
blocks += additional_expanded blocks += additional_expanded
del additional del additional
del additional_expanded del additional_expanded
del section['AddBlocks'] # Save some memory del section['Add'] # Save some memory
section['Blocks'] = blocks section['Blocks'] = blocks
# Turn the skylight array into a 16x16x16 matrix. The array comes # Turn the skylight array into a 16x16x16 matrix. The array comes
@@ -483,6 +489,8 @@ class RegionSet(object):
p = f.split(".") p = f.split(".")
x = int(p[1]) x = int(p[1])
y = int(p[2]) 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) yield (x, y, path)
class RegionSetWrapper(object): class RegionSetWrapper(object):

View File

@@ -108,13 +108,13 @@ if py2exe is not None:
setup_kwargs['data_files'] += recursive_data_files('overviewer_core/data/web_assets', 'web_assets') setup_kwargs['data_files'] += recursive_data_files('overviewer_core/data/web_assets', 'web_assets')
setup_kwargs['data_files'] += recursive_data_files('overviewer_core/data/js_src', 'js_src') setup_kwargs['data_files'] += recursive_data_files('overviewer_core/data/js_src', 'js_src')
setup_kwargs['data_files'] += recursive_data_files('contrib', 'contrib') setup_kwargs['data_files'] += recursive_data_files('contrib', 'contrib')
setup_kwargs['data_files'] += [('', ['genPOI.py'])]
setup_kwargs['zipfile'] = None setup_kwargs['zipfile'] = None
if platform.system() == 'Windows' and '64bit' in platform.architecture(): if platform.system() == 'Windows' and '64bit' in platform.architecture():
b = 3 b = 3
else: else:
b = 1 b = 1
setup_kwargs['options']['py2exe'] = {'bundle_files' : b, 'excludes': 'Tkinter', 'includes':['fileinput', 'overviewer_core.items']} setup_kwargs['options']['py2exe'] = {'bundle_files' : b, 'excludes': 'Tkinter', 'includes':
['fileinput', 'overviewer_core.items', 'overviewer_core.aux_files.genPOI']}
# #
# py2app options # py2app options
@@ -129,7 +129,7 @@ if py2app is not None:
# script, package, and data # script, package, and data
# #
setup_kwargs['packages'] = ['overviewer_core'] setup_kwargs['packages'] = ['overviewer_core', 'overviewer_core/aux_files']
setup_kwargs['scripts'] = ['overviewer.py'] setup_kwargs['scripts'] = ['overviewer.py']
setup_kwargs['package_data'] = {'overviewer_core': recursive_package_data('data/textures') + recursive_package_data('data/web_assets') + recursive_package_data('data/js_src')} setup_kwargs['package_data'] = {'overviewer_core': recursive_package_data('data/textures') + recursive_package_data('data/web_assets') + recursive_package_data('data/js_src')}