0

Merge branch 'master' into my_genpoi

Conflicts:
	overviewer_core/aux_files/genPOI.py
This commit is contained in:
MasterofJOKers
2015-02-08 14:19:21 +01:00
15 changed files with 664 additions and 42 deletions

View File

@@ -377,6 +377,31 @@ Observers
**Required**
``RConObserver(target, password[, port][, pct_interval])``
This Observer will announce render progress with the server's ``say``
command through RCon.
* ``target=<address>``
Address of the target Minecraft server.
**Required**
* ``password=<rcon password>``
The server's rcon password.
**Required**
* ``port=<port number>``
Port on which the Minecraft server listens for incoming RCon connections.
**Default:** ``25575``
* ``pct_interval=<update rate, in percent>``
Percentage interval in which the progress should be announced, the same as
for ``ServerAnnounceObserver``.
**Default:** ``10``
Custom web assets
@@ -683,8 +708,8 @@ Image options
Using image optimizers will increase render times significantly.
This option specifies which additional tools overviewer should use to
optimize the filesize of png tiles.
The tools used must be placed somewhere, where overviewer can find them, for
optimize the filesize of rendered tiles.
The tools used must be placed somewhere where overviewer can find them, for
example the "PATH" environment variable or a directory like /usr/bin.
The option is a list of Optimizer objects, which are then executed in
@@ -766,6 +791,34 @@ Image options
**Default:** ``False``
``jpegoptim``
jpegoptim can do both lossy and lossless JPEG optimisation. If no options are specified,
jpegoptim will only do lossless optimisations.
Available settings:
``quality``
A number between 0 and 100 that corresponds to the jpeg quality level. If the input
image has a lower quality specified than the output image, jpegoptim will only do
lossless optimisations.
If this option is specified and the above condition does not apply, jpegoptim will
do lossy optimisation.
**Default:** ``None`` *(= Unspecified)*
``target_size``
Either a percentage of the original filesize (e.g. ``"50%"``) or a target filesize
in kilobytes (e.g. ``15``). jpegoptim will then try to reach this as its target size.
If specified, jpegoptim will do lossy optimisation.
.. warning::
This appears to have a greater performance impact than just setting ``quality``.
Unless predictable filesizes are a thing you need, you should probably use ``quality``
instead.
**Default:** ``None`` *(= Unspecified)*
**Default:** ``[]``
Zoom
@@ -1099,6 +1152,32 @@ MineralOverlay
MineralOverlay(minerals=[(64,(255,255,0)), (13,(127,0,127))])
StructureOverlay
Color the map according to patterns of blocks. With this rail overlays
or overlays for other small structures can be realized. It can also be
a MineralOverlay with alpha support.
This Overlay colors according to a patterns that are specified as
multiple tuples of the form ``(relx, rely, relz, blockid)``. So
by specifying ``(0, -1, 0, 4)`` the block below the current one has to
be a cobblestone.
One color is then specified as
``((relblockid1, relblockid2, ...), (r, g, b, a))`` where the
``relblockid*`` are relative coordinates and the blockid as specified
above. The ``relblockid*`` must match all at the same time for the
color to apply.
Example::
MineralOverlay(minerals=[(((0, 0, 0, 66), (0, -1, 0, 4)), (255, 0, 0, 255)),
(((0, 0, 0, 27), (0, -1, 0, 4)), (0, 255, 0, 255))])
In this example all rails(66) on top of cobblestone are rendered in
pure red. And all powerrails(27) are rendered in green.
If ``minerals`` is not provided, a default rail coloring is used.
BiomeOverlay
Color the map according to the biome at that point. Either use on
top of other modes or on top of ClearBase to create a pure overlay.

View File

@@ -194,7 +194,6 @@ is typically correct.
generation, and ONLY generates markers. See :ref:`signsmarkers` on how to
configure POI options.
.. cmdoption:: -p <procs>, --processes <procs>
This specifies the number of worker processes to spawn on the local machine
@@ -203,6 +202,17 @@ is typically correct.
This option can also be specified in the config file as :ref:`processes <processes>`
.. cmdoption:: --skip-scan
.. note::
Don't use this flag without first reading :ref:`signsmarkers`!
When generating POI markers, this option prevents scanning for entities and
tile entities, and only creates the markers specified in the config file.
This considerably speeds up the POI marker generation process if no entities
or tile entities are being used for POI markers. See :ref:`signsmarkers` on
how to configure POI options.
.. cmdoption:: -v, --verbose
Activate a more verbose logging format and turn on debugging output. This

View File

@@ -50,6 +50,16 @@ def main():
# bootstrap the logger with defaults
logger.configure()
if os.name == "posix":
if os.geteuid() == 0:
logging.warning("You are running Overviewer as root. "
"It is recommended that you never do this, "
"as it is dangerous for your system. If you are running "
"into permission errors, fix your file/directory "
"permissions instead. Overviewer does not need access to "
"critical system resources and therefore does not require "
"root access.")
try:
cpus = multiprocessing.cpu_count()
except NotImplementedError:
@@ -478,8 +488,8 @@ dir but you forgot to put quotes around the directory, since it contains spaces.
logging.error("Sorry, I can't find anything to render! Are you sure there are .mca files in the world directory?")
return 1
if rset == None: # indicates no such dimension was found:
logging.error("Sorry, you requested dimension '%s' for %s, but I couldn't find it", render['dimension'][0], render_name)
return 1
logging.warn("Sorry, you requested dimension '%s' for %s, but I couldn't find it", render['dimension'][0], render_name)
continue
#################
# Apply any regionset transformations here
@@ -518,6 +528,12 @@ dir but you forgot to put quotes around the directory, since it contains spaces.
tset = tileset.TileSet(w, rset, assetMrg, tex, tileSetOpts, tileset_dir)
tilesets.append(tset)
# If none of the requested dimenstions exist, tilesets will be empty
if not tilesets:
logging.error("There are no tilesets to render! There's nothing to do, so exiting.")
return 1
# Do tileset preprocessing here, before we start dispatching jobs
logging.info("Preprocessing...")
for ts in tilesets:

View File

@@ -385,8 +385,8 @@ def main():
# get the regionset for this dimension
rset = w.get_regionset(render['dimension'][1])
if rset == None: # indicates no such dimension was found:
logging.error("Sorry, you requested dimension '%s' for the render '%s', but I couldn't find it", render['dimension'][0], rname)
return 1
logging.warn("Sorry, you requested dimension '%s' for the render '%s', but I couldn't find it", render['dimension'][0], rname)
continue
# find filters for this render
for f in render['markers']:
@@ -462,6 +462,7 @@ def main():
with open(os.path.join(destdir, "baseMarkers.js"), "w") as output:
output.write("overviewer.util.injectMarkerScript('markersDB.js');\n")
output.write("overviewer.util.injectMarkerScript('markers.js');\n")
output.write("overviewer.util.injectMarkerScript('regions.js');\n")
output.write("overviewer.collections.haveSigns=true;\n")
logging.info("Done")

View File

@@ -496,6 +496,26 @@ overviewer.views.SignControlView = Backbone.View.extend({
'strokeColor': entity['strokeColor']
});
dataRoot[i].markerObjs.push(polyline);
}
// Polygons
if (typeof entity['polygon'] != 'undefined') {
var polypath = new Array();
for (point in entity.polygon) {
polypath.push(overviewer.util.fromWorldToLatLng(entity.polygon[point].x, entity.polygon[point].y, entity.polygon[point].z, overviewer.mapView.options.currentTileSet));
}
var polygon = new google.maps.Polygon({
'clickable': false,
'fillColor': entity['fillColor'],
'fillOpacity': entity['fillOpacity'],
'map': overviewer.map,
'path': polypath,
'strokeColor': entity['strokeColor'],
'strokeOpacity': entity['strokeOpacity'],
'visible': false
});
dataRoot[i].markerObjs.push(polygon);
}
}
dataRoot[i].created = true;

View File

@@ -1,9 +1,34 @@
// This is just an example. You can run some scripts to generate regions that
// will be drawn here.
overviewer.collections.regionDatas.push([
// {"color": "#FFAA00", "opacity": 0.5, "closed": true, "path": [
// {"x": 0, "y": 0, "z": 0},
// {"x": 0, "y": 10, "z": 0},
// {"x": 0, "y": 0, "z": 10}
// ]},
]);
/*
var groupName = "Regions";
var displayName = "Areas";
var world = "top";
markersDB[groupName] = {
"raw": [
{
"fillColor": "#00FF00",
"fillOpacity": 0.2,
"strokeColor": "#FF0000",
"strokeOpacity": 1,
"polygon" : [
{"x": 347, "y": 67, "z": 95},
{"x": 347, "y": 77, "z": 95},
{"x": 347, "y": 77, "z": 105},
{"x": 347, "y": 67, "z": 105},
{"x": 347, "y": 67, "z": 105}
]}
],
"name": "Regions",
"created": false
}
markers[world].push(
{
"groupName": groupName,
"icon": "signpost_icon.png",
"createInfoWindow": false,
"displayName": displayName,
"checked": true
});
*/

View File

@@ -65,7 +65,9 @@ class NBTFileReader(object):
# compile the unpacker's into a classes
_byte = struct.Struct("b")
_short = struct.Struct(">h")
_ushort = struct.Struct(">H")
_int = struct.Struct(">i")
_uint = struct.Struct(">I")
_long = struct.Struct(">q")
_float = struct.Struct(">f")
_double = struct.Struct(">d")
@@ -128,17 +130,17 @@ class NBTFileReader(object):
return self._double.unpack(bytes)[0]
def _read_tag_byte_array(self):
length = self._read_tag_int()
length = self._uint.unpack(self._file.read(4))[0]
bytes = self._file.read(length)
return bytes
def _read_tag_int_array(self):
length = self._read_tag_int()
length = self._uint.unpack(self._file.read(4))[0]
int_bytes = self._file.read(length*4)
return struct.unpack(">%ii" % length, int_bytes)
def _read_tag_string(self):
length = self._read_tag_short()
length = self._ushort.unpack(self._file.read(2))[0]
# Read the string
string = self._file.read(length)
# decode it and return
@@ -146,7 +148,7 @@ class NBTFileReader(object):
def _read_tag_list(self):
tagid = self._read_tag_byte()
length = self._read_tag_int()
length = self._uint.unpack(self._file.read(4))[0]
read_method = self._read_tagmap[tagid]
l = []

View File

@@ -19,6 +19,7 @@ import progressbar
import sys
import os
import json
import rcon
class Observer(object):
"""Base class that defines the observer interface.
@@ -392,3 +393,43 @@ class ServerAnnounceObserver(Observer):
self.target_handle.write('say %s\n' % output)
self.target_handle.flush()
# Fair amount of code duplication incoming
# Perhaps both ServerAnnounceObserver and RConObserver
# could share the percentage interval code.
class RConObserver(Observer):
"""Send the output to a Minecraft server via rcon"""
def __init__(self, target, password, port=25575, pct_interval=10):
self.pct_interval = pct_interval
self.conn = rcon.RConConnection(target, port)
self.conn.login(password)
self.last_update = 0
super(RConObserver, self).__init__()
def start(self, max_value):
self._send_output("Starting render of %d total tiles" % max_value)
super(RConObserver, self).start(max_value)
def finish(self):
self._send_output("Render complete!")
super(RConObserver, self).finish()
self.conn.close()
def update(self, current_value):
super(RConObserver, self).update(current_value)
if self._need_update():
self._send_output('Rendered %d of %d tiles, %d%% complete' %
(self.get_current_value(), self.get_max_value(),
self.get_percentage()))
self.last_update = current_value
def _need_update(self):
return self.get_percentage() - \
(self.last_update * 100.0 / self.get_max_value()) >= self.pct_interval
def _send_output(self, output):
self.conn.command("say", output)

View File

@@ -121,6 +121,42 @@ class optipng(Optimizer, PNGOptimizer):
def is_crusher(self):
return True
class jpegoptim(Optimizer, JPEGOptimizer):
binaryname = "jpegoptim"
crusher = True
quality = None
target_size = None
def __init__(self, quality = None, target_size = None):
if quality is not None:
if quality < 0 or quality > 100:
raise Exception("Invalid target quality %d for jpegoptim" % quality)
self.quality = quality
if target_size is not None:
self.target_size = target_size
def optimize(self, img):
args = [self.binaryname, "-q", "-p"]
if self.quality is not None:
args.append("-m" + str(self.quality))
if self.target_size is not None:
args.append("-S" + str(self.target_size))
args.append(img)
Optimizer.fire_and_forget(self, args)
def is_crusher(self):
# Technically, optimisation is lossless if input image quality
# is below target quality, but this is irrelevant in this case
if (self.quality is not None) or (self.target_size is not None):
return False
else:
return True
def optimize_image(imgpath, imgformat, optimizers):
for opt in optimizers:

95
overviewer_core/rcon.py Normal file
View File

@@ -0,0 +1,95 @@
# 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/>.
import socket
#from enum import Enum
import struct
import select
class RConException(Exception):
def __init__(self, request_id, reason):
self.request_id = request_id
self.reason = reason
def __str__(self):
return ("Failed RCon request with request ID %d, reason %s" %
(self.request_id, self.reason))
# In D, enums are just that, enums. They're a group of named constants,
# sometimes with a tag, sometimes anonymous.
# In Python, Enums use the same syntax as class objects that derive from
# the "Enum" base class, even though they are not normal python classes
# and work as singletons anyway, but instead of using a different syntax,
# Python instead decided to have a chapter in their docs about how Enums
# are different from regular classes while looking exactly the same.
# You can look at said document of failure right here:
# https://docs.python.org/3/library/enum.html#how-are-enums-different
#
# "D has too much shit going on for me" -- agrif, 2014
#
# Fortunately, we're not allowed to use Enums in Python 2.
#class RConType(Enum):
# command = 2
# login = 3
class RConConnection():
rid = 0
def __init__(self, target, port):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.connect((target, port))
def send(self, t, payload):
self.rid = self.rid + 1
header = struct.pack("<iii",
len(payload) + 4 + 4 + 2, # rid, type and padding
self.rid,
t)
data = header + payload + '\x00\x00'
self.sock.send(data)
toread = select.select([self.sock], [], [], 30)
if not toread:
raise RConException(self.rid, "Request timed out.")
res_len, res_id, res_type = struct.unpack("<iii", self.sock.recv(12))
res_data = self.sock.recv(res_len - 4 - 4)
res_data = res_data[:-2]
if res_id == -1:
if t == 3:
raise RConException(self.rid, "Login failed.")
else:
raise RConException(self.rid, "Request failed due to invalid login.")
elif res_id != self.rid:
raise RConException(self.rid,
"Received unexpected response number: %d" % res_id)
return res_data
def login(self, password):
self.send(3, password)
def command(self, com, args):
self.send(2, com + " " + args)
def close(self):
self.sock.close()

View File

@@ -205,6 +205,19 @@ class SpawnOverlay(Overlay):
class SlimeOverlay(Overlay):
name = "overlay-slime"
class StructureOverlay(Overlay):
name = "overlay-structure"
options = {
'structures': ('a list of ((((relx, rely, relz), blockid), ...), (r, g, b, a)) tuples for coloring minerals',
[(((0, 0, 0, 66), (0, -1, 0, 4)), (255, 0, 0, 255)),
(((0, 0, 0, 27), (0, -1, 0, 4)), (0, 255, 0, 255)),
(((0, 0, 0, 28), (0, -1, 0, 4)), (255, 255, 0, 255)),
(((0, 0, 0, 157), (0, -1, 0, 4)), (255, 100, 0, 255)),
]),
}
class MineralOverlay(Overlay):
name = "overlay-mineral"
options = {

View File

@@ -0,0 +1,242 @@
/*
* 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"
typedef enum { false, true } bool;
typedef struct {
/* inherits from overlay */
RenderPrimitiveOverlay parent;
void *structures;
int numcolors;
} RenderPrimitiveStructure;
struct Condition{
int relx, rely, relz;
unsigned char block;
};
struct Color {
int numconds;
struct Condition *conditions;
unsigned char r, g, b, a;
};
static void get_color(void *data,
RenderState *state,
unsigned char *r,
unsigned char *g,
unsigned char *b,
unsigned char *a) {
/**
* Calculate the color at the current position and store the values to r,g,b,a.
**/
RenderPrimitiveStructure *self = (RenderPrimitiveStructure *)data;
int x = state->x, z = state->z, y_max, y, col, cond;
struct Color *structures = (struct Color *)(self->structures);
struct Condition * c = NULL;
bool all = true;
y_max = state->y + 1;
/**
* Check for every color in every y level if all its Conditions are met.
* If all conditions are met for one y level set r,b,g,a accordingly.
**/
// iterate over all the colors
for ( col = 0; col < self->numcolors; col++) {
// iterate over all y levels
for (y = state->chunky * -16; y <= y_max; y++) {
// iterate over all the conditions
for (cond = 0; cond < structures[col].numconds; cond++) {
all = true;
c = (struct Condition *)&structures[col].conditions[cond];
// check if the condition does apply and break from the conditions loop if not.
if(!(c->block == get_data(state, BLOCKS, x+c->relx, y+c->rely, z+c->relz))) {
all = false;
break;
}
}
if (all){
// set the color
*r = structures[col].r;
*g = structures[col].g;
*b = structures[col].b;
*a = structures[col].a;
return;
}
}
}
return;
}
static int overlay_structure_start(void *data, RenderState *state, PyObject *support) {
/**
* Initializing the search for structures by parsing the arguments and storing them into
* appropriate structures. If no arguments are passed create and use default values.
**/
PyObject *opt;
RenderPrimitiveStructure* self;
/* first, chain up */
int ret = primitive_overlay.start(data, state, support);
if (ret != 0)
return ret;
/* now do custom initializations */
self = (RenderPrimitiveStructure *)data;
// opt is a borrowed reference. do not deref
// store the structures python object into opt.
if (!render_mode_parse_option(support, "structures", "O", &(opt)))
return 1;
/**
* Check if a sane option was passed.
**/
if (opt && opt != Py_None) {
struct Color *structures = NULL;
struct Condition *cond = NULL;
Py_ssize_t structures_size = 0, i, cond_size = 0, n = 0;
bool cont = true;
opt = PySequence_Fast(opt, "expected a sequence");
if (!opt) {
PyErr_SetString(PyExc_TypeError, "'structures' must be a a sequence");
return 1;
}
structures_size = PySequence_Fast_GET_SIZE(opt);
// Getting space on the heap and do not forget to set self->numcolors.
structures = self->structures = calloc(structures_size, sizeof(struct Color));
self->numcolors = structures_size;
if (structures == NULL) {
PyErr_SetString(PyExc_MemoryError, "failed to allocate memory");
return 1;
}
/**
* Try to parse the definitions of conditions and colors.
**/
if (cont) {
for (i = 0; i < structures_size; i++) {
PyObject *structure = PyList_GET_ITEM(opt, i);
// condspy holding the conditions tuple of variable length (python object)
PyObject *condspy;
// colorpy holding the 4 tuple with r g b a values of the color
PyObject *colorpy;
// getting the condspy and colorpy out of the structures.
if (!PyArg_ParseTuple(structure, "OO", &condspy, &colorpy)) {
// Exception set automatically
free(structures);
self->structures = NULL;
return 1;
}
// Parse colorpy into a c-struct.
if (!PyArg_ParseTuple( colorpy, "bbbb",
&structures[i].r,
&structures[i].g,
&structures[i].b,
&structures[i].a)) {
free(structures);
self->structures = NULL;
return 1;
}
// Convert condspy to a fast sequence
condspy = PySequence_Fast(condspy, "Failed to parse conditions");
if(condspy == NULL) {
free(structures);
self->structures = NULL;
return 1;
}
// get the number of conditions.
structures[i].numconds = PySequence_Fast_GET_SIZE(condspy);
// reserve enough memory for the conditions.
cond = calloc(structures[i].numconds, sizeof(struct Condition));
structures[i].conditions = cond;
if (structures[i].conditions == NULL) {
PyErr_SetString(PyExc_MemoryError, "failed to allocate memory");
free(structures);
self->structures = NULL;
return 1;
}
// iterate over all the conditions and read them.
for (n = 0; n < structures[i].numconds; n++) {
PyObject *ccond = PySequence_Fast_GET_ITEM(condspy, n);
if(!PyArg_ParseTuple( ccond, "iiib",
&cond[n].relx,
&cond[n].rely,
&cond[n].relz,
&cond[n].block)){
int x = 0;
for(x = 0; x < structures_size; x++){
free(structures[x].conditions);
}
free(structures);
self->structures = NULL;
return 1;
}
}
}
}
}
/* setup custom color */
self->parent.get_color = get_color;
return 0;
}
static void overlay_structure_finish(void *data, RenderState *state) {
/* first free all *our* stuff */
RenderPrimitiveStructure* self = (RenderPrimitiveStructure *)data;
int i = 0;
if(self->structures) {
// freeing the nested structure
struct Color * m = self->structures;
for(i = 0; i < self->numcolors; i++){
if(m[i].conditions)
free(m[i].conditions);
}
}
if (self->structures) {
free(self->structures);
self->structures = NULL;
}
/* now, chain up */
primitive_overlay.finish(data, state);
}
RenderPrimitiveInterface primitive_overlay_structure = {
"overlay-structure",
sizeof(RenderPrimitiveStructure),
overlay_structure_start,
overlay_structure_finish,
NULL,
NULL,
overlay_draw,
};

View File

@@ -3437,7 +3437,7 @@ def huge_mushroom(self, blockid, data):
img = self.build_full_block(cap, None, None, cap, porous)
if data == 2: # east side
img = self.build_full_block(cap, None, None, porous, porous)
img = self.build_full_block(cap, None, None, porous, cap)
if data == 3: # south-east corner
img = self.build_full_block(cap, None, None, porous, cap)

View File

@@ -689,6 +689,25 @@ class TileSet(object):
# 2 is now 2/1
# 3 is now 3/0
# then all that needs to be done is to regenerate the new top level
def rollback_mkdir(dnum):
p = getpath("new" + str(dnum))
if os.path.exists(p):
os.rmdir(p)
def rollback_filerename(dnum):
newnum = (3,2,1,0)[dnum]
qimg = getpath("new%d/%d.%s" % (dnum, newnum, self.imgextension))
qdir = getpath("new%d/%d" % (dnum, newnum))
if os.path.exists(qimg):
os.rename(qimg, getpath("%d.%s" % (dnum, self.imgextension)))
if os.path.exists(qdir):
os.rename(qdir, getpath(str(dnum)))
def rollback_dirrename(dnum):
os.rename(getpath(str(dnum)), getpath("new" + str(dnum)))
for dirnum in range(4):
newnum = (3,2,1,0)[dirnum]
@@ -698,12 +717,31 @@ class TileSet(object):
files = [str(dirnum)+"."+self.imgextension, str(dirnum)]
newfiles = [str(newnum)+"."+self.imgextension, str(newnum)]
os.mkdir(newdirpath)
for f, newf in zip(files, newfiles):
p = getpath(f)
if os.path.exists(p):
os.rename(p, getpath(newdir, newf))
os.rename(newdirpath, getpath(str(dirnum)))
try:
try:
os.mkdir(newdirpath)
try:
for f, newf in zip(files, newfiles):
p = getpath(f)
if os.path.exists(p):
os.rename(p, getpath(newdir, newf))
except:
rollback_filerename(dirnum)
raise
except:
rollback_mkdir(dirnum)
raise
os.rename(newdirpath, getpath(str(dirnum)))
except:
logging.warning("Overviewer was interrupted during tile "
"re-arrangement.")
logging.warning("Rolling back changes...")
# Moonwalk the fuck out of here
for lastdir in range(dirnum - 1, -1, -1):
rollback_dirrename(lastdir)
rollback_filerename(lastdir)
rollback_mkdir(lastdir)
raise
def _decrease_depth(self):
"""If the map size decreases, or perhaps the user has a depth override

View File

@@ -105,8 +105,12 @@ class World(object):
# Hard-code this to only work with format version 19133, "Anvil"
if not ('version' in data and data['version'] == 19133):
logging.critical("Sorry, This version of Minecraft-Overviewer only works with the 'Anvil' chunk format")
raise ValueError("World at %s is not compatible with Overviewer" % self.worlddir)
if 'version' in data and data['version'] == 0:
logging.debug("Note: Allowing a version of zero in level.dat!")
## XXX temporary fix for #1194
else:
logging.critical("Sorry, This version of Minecraft-Overviewer only works with the 'Anvil' chunk format")
raise ValueError("World at %s is not compatible with Overviewer" % self.worlddir)
# This isn't much data, around 15 keys and values for vanilla worlds.
self.leveldat = data