Merge branch 'master' into my_genpoi
Conflicts: overviewer_core/aux_files/genPOI.py
This commit is contained in:
@@ -377,6 +377,31 @@ Observers
|
|||||||
|
|
||||||
**Required**
|
**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
|
Custom web assets
|
||||||
@@ -683,8 +708,8 @@ Image options
|
|||||||
Using image optimizers will increase render times significantly.
|
Using image optimizers will increase render times significantly.
|
||||||
|
|
||||||
This option specifies which additional tools overviewer should use to
|
This option specifies which additional tools overviewer should use to
|
||||||
optimize the filesize of png tiles.
|
optimize the filesize of rendered tiles.
|
||||||
The tools used must be placed somewhere, where overviewer can find them, for
|
The tools used must be placed somewhere where overviewer can find them, for
|
||||||
example the "PATH" environment variable or a directory like /usr/bin.
|
example the "PATH" environment variable or a directory like /usr/bin.
|
||||||
|
|
||||||
The option is a list of Optimizer objects, which are then executed in
|
The option is a list of Optimizer objects, which are then executed in
|
||||||
@@ -766,6 +791,34 @@ Image options
|
|||||||
|
|
||||||
**Default:** ``False``
|
**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:** ``[]``
|
**Default:** ``[]``
|
||||||
|
|
||||||
Zoom
|
Zoom
|
||||||
@@ -1099,6 +1152,32 @@ MineralOverlay
|
|||||||
|
|
||||||
MineralOverlay(minerals=[(64,(255,255,0)), (13,(127,0,127))])
|
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
|
BiomeOverlay
|
||||||
Color the map according to the biome at that point. Either use on
|
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.
|
top of other modes or on top of ClearBase to create a pure overlay.
|
||||||
|
|||||||
@@ -194,7 +194,6 @@ is typically correct.
|
|||||||
generation, and ONLY generates markers. See :ref:`signsmarkers` on how to
|
generation, and ONLY generates markers. See :ref:`signsmarkers` on how to
|
||||||
configure POI options.
|
configure POI options.
|
||||||
|
|
||||||
|
|
||||||
.. cmdoption:: -p <procs>, --processes <procs>
|
.. cmdoption:: -p <procs>, --processes <procs>
|
||||||
|
|
||||||
This specifies the number of worker processes to spawn on the local machine
|
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>`
|
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
|
.. cmdoption:: -v, --verbose
|
||||||
|
|
||||||
Activate a more verbose logging format and turn on debugging output. This
|
Activate a more verbose logging format and turn on debugging output. This
|
||||||
|
|||||||
@@ -50,6 +50,16 @@ def main():
|
|||||||
# bootstrap the logger with defaults
|
# bootstrap the logger with defaults
|
||||||
logger.configure()
|
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:
|
try:
|
||||||
cpus = multiprocessing.cpu_count()
|
cpus = multiprocessing.cpu_count()
|
||||||
except NotImplementedError:
|
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?")
|
logging.error("Sorry, I can't find anything to render! Are you sure there are .mca files in the world directory?")
|
||||||
return 1
|
return 1
|
||||||
if rset == None: # indicates no such dimension was found:
|
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)
|
logging.warn("Sorry, you requested dimension '%s' for %s, but I couldn't find it", render['dimension'][0], render_name)
|
||||||
return 1
|
continue
|
||||||
|
|
||||||
#################
|
#################
|
||||||
# Apply any regionset transformations here
|
# 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)
|
tset = tileset.TileSet(w, rset, assetMrg, tex, tileSetOpts, tileset_dir)
|
||||||
tilesets.append(tset)
|
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
|
# Do tileset preprocessing here, before we start dispatching jobs
|
||||||
logging.info("Preprocessing...")
|
logging.info("Preprocessing...")
|
||||||
for ts in tilesets:
|
for ts in tilesets:
|
||||||
|
|||||||
@@ -385,8 +385,8 @@ def main():
|
|||||||
# get the regionset for this dimension
|
# get the regionset for this dimension
|
||||||
rset = w.get_regionset(render['dimension'][1])
|
rset = w.get_regionset(render['dimension'][1])
|
||||||
if rset == None: # indicates no such dimension was found:
|
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)
|
logging.warn("Sorry, you requested dimension '%s' for the render '%s', but I couldn't find it", render['dimension'][0], rname)
|
||||||
return 1
|
continue
|
||||||
|
|
||||||
# find filters for this render
|
# find filters for this render
|
||||||
for f in render['markers']:
|
for f in render['markers']:
|
||||||
@@ -462,6 +462,7 @@ def main():
|
|||||||
with open(os.path.join(destdir, "baseMarkers.js"), "w") as output:
|
with open(os.path.join(destdir, "baseMarkers.js"), "w") as output:
|
||||||
output.write("overviewer.util.injectMarkerScript('markersDB.js');\n")
|
output.write("overviewer.util.injectMarkerScript('markersDB.js');\n")
|
||||||
output.write("overviewer.util.injectMarkerScript('markers.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")
|
output.write("overviewer.collections.haveSigns=true;\n")
|
||||||
logging.info("Done")
|
logging.info("Done")
|
||||||
|
|
||||||
|
|||||||
@@ -496,6 +496,26 @@ overviewer.views.SignControlView = Backbone.View.extend({
|
|||||||
'strokeColor': entity['strokeColor']
|
'strokeColor': entity['strokeColor']
|
||||||
});
|
});
|
||||||
dataRoot[i].markerObjs.push(polyline);
|
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;
|
dataRoot[i].created = true;
|
||||||
|
|||||||
@@ -1,9 +1,34 @@
|
|||||||
// This is just an example. You can run some scripts to generate regions that
|
// This is just an example. You can run some scripts to generate regions that
|
||||||
// will be drawn here.
|
/*
|
||||||
overviewer.collections.regionDatas.push([
|
var groupName = "Regions";
|
||||||
// {"color": "#FFAA00", "opacity": 0.5, "closed": true, "path": [
|
var displayName = "Areas";
|
||||||
// {"x": 0, "y": 0, "z": 0},
|
var world = "top";
|
||||||
// {"x": 0, "y": 10, "z": 0},
|
|
||||||
// {"x": 0, "y": 0, "z": 10}
|
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
|
||||||
|
});
|
||||||
|
*/
|
||||||
@@ -65,7 +65,9 @@ class NBTFileReader(object):
|
|||||||
# compile the unpacker's into a classes
|
# compile the unpacker's into a classes
|
||||||
_byte = struct.Struct("b")
|
_byte = struct.Struct("b")
|
||||||
_short = struct.Struct(">h")
|
_short = struct.Struct(">h")
|
||||||
|
_ushort = struct.Struct(">H")
|
||||||
_int = struct.Struct(">i")
|
_int = struct.Struct(">i")
|
||||||
|
_uint = struct.Struct(">I")
|
||||||
_long = struct.Struct(">q")
|
_long = struct.Struct(">q")
|
||||||
_float = struct.Struct(">f")
|
_float = struct.Struct(">f")
|
||||||
_double = struct.Struct(">d")
|
_double = struct.Struct(">d")
|
||||||
@@ -128,17 +130,17 @@ class NBTFileReader(object):
|
|||||||
return self._double.unpack(bytes)[0]
|
return self._double.unpack(bytes)[0]
|
||||||
|
|
||||||
def _read_tag_byte_array(self):
|
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)
|
bytes = self._file.read(length)
|
||||||
return bytes
|
return bytes
|
||||||
|
|
||||||
def _read_tag_int_array(self):
|
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)
|
int_bytes = self._file.read(length*4)
|
||||||
return struct.unpack(">%ii" % length, int_bytes)
|
return struct.unpack(">%ii" % length, int_bytes)
|
||||||
|
|
||||||
def _read_tag_string(self):
|
def _read_tag_string(self):
|
||||||
length = self._read_tag_short()
|
length = self._ushort.unpack(self._file.read(2))[0]
|
||||||
# Read the string
|
# Read the string
|
||||||
string = self._file.read(length)
|
string = self._file.read(length)
|
||||||
# decode it and return
|
# decode it and return
|
||||||
@@ -146,7 +148,7 @@ class NBTFileReader(object):
|
|||||||
|
|
||||||
def _read_tag_list(self):
|
def _read_tag_list(self):
|
||||||
tagid = self._read_tag_byte()
|
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]
|
read_method = self._read_tagmap[tagid]
|
||||||
l = []
|
l = []
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import progressbar
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
import rcon
|
||||||
|
|
||||||
class Observer(object):
|
class Observer(object):
|
||||||
"""Base class that defines the observer interface.
|
"""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.write('say %s\n' % output)
|
||||||
self.target_handle.flush()
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -121,6 +121,42 @@ class optipng(Optimizer, PNGOptimizer):
|
|||||||
def is_crusher(self):
|
def is_crusher(self):
|
||||||
return True
|
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):
|
def optimize_image(imgpath, imgformat, optimizers):
|
||||||
for opt in optimizers:
|
for opt in optimizers:
|
||||||
|
|||||||
95
overviewer_core/rcon.py
Normal file
95
overviewer_core/rcon.py
Normal 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()
|
||||||
@@ -205,6 +205,19 @@ class SpawnOverlay(Overlay):
|
|||||||
class SlimeOverlay(Overlay):
|
class SlimeOverlay(Overlay):
|
||||||
name = "overlay-slime"
|
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):
|
class MineralOverlay(Overlay):
|
||||||
name = "overlay-mineral"
|
name = "overlay-mineral"
|
||||||
options = {
|
options = {
|
||||||
|
|||||||
242
overviewer_core/src/primitives/overlay-structure.c
Normal file
242
overviewer_core/src/primitives/overlay-structure.c
Normal 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,
|
||||||
|
};
|
||||||
|
|
||||||
@@ -3437,7 +3437,7 @@ def huge_mushroom(self, blockid, data):
|
|||||||
img = self.build_full_block(cap, None, None, cap, porous)
|
img = self.build_full_block(cap, None, None, cap, porous)
|
||||||
|
|
||||||
if data == 2: # east side
|
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
|
if data == 3: # south-east corner
|
||||||
img = self.build_full_block(cap, None, None, porous, cap)
|
img = self.build_full_block(cap, None, None, porous, cap)
|
||||||
|
|||||||
@@ -689,6 +689,25 @@ class TileSet(object):
|
|||||||
# 2 is now 2/1
|
# 2 is now 2/1
|
||||||
# 3 is now 3/0
|
# 3 is now 3/0
|
||||||
# then all that needs to be done is to regenerate the new top level
|
# 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):
|
for dirnum in range(4):
|
||||||
newnum = (3,2,1,0)[dirnum]
|
newnum = (3,2,1,0)[dirnum]
|
||||||
|
|
||||||
@@ -698,12 +717,31 @@ class TileSet(object):
|
|||||||
files = [str(dirnum)+"."+self.imgextension, str(dirnum)]
|
files = [str(dirnum)+"."+self.imgextension, str(dirnum)]
|
||||||
newfiles = [str(newnum)+"."+self.imgextension, str(newnum)]
|
newfiles = [str(newnum)+"."+self.imgextension, str(newnum)]
|
||||||
|
|
||||||
|
try:
|
||||||
|
try:
|
||||||
os.mkdir(newdirpath)
|
os.mkdir(newdirpath)
|
||||||
|
try:
|
||||||
for f, newf in zip(files, newfiles):
|
for f, newf in zip(files, newfiles):
|
||||||
p = getpath(f)
|
p = getpath(f)
|
||||||
if os.path.exists(p):
|
if os.path.exists(p):
|
||||||
os.rename(p, getpath(newdir, newf))
|
os.rename(p, getpath(newdir, newf))
|
||||||
|
except:
|
||||||
|
rollback_filerename(dirnum)
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
rollback_mkdir(dirnum)
|
||||||
|
raise
|
||||||
os.rename(newdirpath, getpath(str(dirnum)))
|
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):
|
def _decrease_depth(self):
|
||||||
"""If the map size decreases, or perhaps the user has a depth override
|
"""If the map size decreases, or perhaps the user has a depth override
|
||||||
|
|||||||
@@ -105,6 +105,10 @@ class World(object):
|
|||||||
|
|
||||||
# Hard-code this to only work with format version 19133, "Anvil"
|
# Hard-code this to only work with format version 19133, "Anvil"
|
||||||
if not ('version' in data and data['version'] == 19133):
|
if not ('version' in data and data['version'] == 19133):
|
||||||
|
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")
|
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)
|
raise ValueError("World at %s is not compatible with Overviewer" % self.worlddir)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user