diff --git a/.travis.yml b/.travis.yml
index f58105e8e..7f3df8746 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,10 +4,10 @@ python:
- "2.7"
# - "3.2"
env:
- - MC_VERSION=1.8
+ - MC_VERSION=1.9
before_install:
- - wget http://hg.effbot.org/pil-117/raw/f356a1f64271/libImaging/Imaging.h
- - wget http://hg.effbot.org/pil-117/raw/f356a1f64271/libImaging/ImPlatform.h
+ - wget https://raw.githubusercontent.com/python-pillow/Pillow/master/libImaging/Imaging.h
+ - wget https://raw.githubusercontent.com/python-pillow/Pillow/master/libImaging/ImPlatform.h
install:
- pip install -q pillow
- pip install -q numpy
@@ -22,8 +22,7 @@ notifications:
email: false
irc:
channels:
- - "irc.freenode.org#overviewer"
- skip_join: true
+ - "chat.freenode.net#overviewer"
template:
- "\x0313Minecraft-Overviewer\x03/\x0306%{branch}\x03 \x0314%{commit}\x03 %{build_url} %{message}"
# matrix:
diff --git a/MANIFEST.in b/MANIFEST.in
index 5de8dd902..3e0ff040b 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -4,10 +4,10 @@ include CONTRIBUTORS.rst
include overviewer.py
include contribManager.py
include sample_config.py
-recursive-include overviewer_core/ *.py
-recursive-include overviewer_core/src/ *.c *.h
-recursive-include overviewer_core/src/primitives/ *.c *.h
-recursive-include overviewer_core/data/ *
-recursive-include contrib/ *.py
-recursive-include docs/ *
+recursive-include overviewer_core *.py
+recursive-include overviewer_core/src *.c *.h
+recursive-include overviewer_core/src/primitives *.c *.h
+recursive-include overviewer_core/data *
+recursive-include contrib *.py
+recursive-include docs *
prune docs/_build
diff --git a/README.rst b/README.rst
index f9adc1f20..763680d79 100644
--- a/README.rst
+++ b/README.rst
@@ -85,4 +85,4 @@ https://github.com/overviewer/Minecraft-Overviewer/issues
Feel free to comment on issues, report new issues, and vote on issues that are
important to you.
-.. |Build Status| image:: https://secure.travis-ci.org/overviewer/Minecraft-Overviewer.png?branch=master
+.. |Build Status| image:: https://secure.travis-ci.org/overviewer/Minecraft-Overviewer.svg?branch=master
diff --git a/docs/building.rst b/docs/building.rst
index bb264e9f8..833ce08ec 100644
--- a/docs/building.rst
+++ b/docs/building.rst
@@ -72,6 +72,36 @@ then try the following::
If the build was successful, there should be a c_overviewer.pyd file in your current working directory.
+Building with mingw-w64 and msys2
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This is the recommended way to build on Windows without MSVC.
+
+1. Install msys2 by following **all** the instructions on
+ `the msys2 installation page `_.
+
+2. Install the dependencies::
+
+ pacman -S git mingw-w64-x86_64-python2-numpy mingw-w64-x86_64-python2-Pillow mingw-w64-x86_64-python2 mingw-w64-x86_64-toolchain
+
+3. Clone the Minecraft-Overviewer git repository::
+
+ git clone https://github.com/overviewer/Minecraft-Overviewer.git
+
+ The source code will be downloaded to your msys2 home directory, e.g.
+ ``C:\msys2\home\Potato\Minecraft-Overviewer``
+
+4. Close the msys2 shell. Instead, open the MinGW64 shell.
+
+5. Build the Overviewer by changing your current working directory into the source
+ directory and executing the build script::
+
+ cd Minecraft-Overviewer
+ python2 setup.py build
+
+After it finishes, you should now be able to execute ``overviewer.py`` from the MINGW64
+shell.
+
Building with mingw
~~~~~~~~~~~~~~~~~~~
diff --git a/docs/config.rst b/docs/config.rst
index b56fc8f1d..14904a45a 100644
--- a/docs/config.rst
+++ b/docs/config.rst
@@ -1172,13 +1172,13 @@ StructureOverlay
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))])
+ StructureOverlay(structures=[(((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.
+ If ``structures`` is not provided, a default rail coloring is used.
BiomeOverlay
Color the map according to the biome at that point. Either use on
diff --git a/docs/signs.rst b/docs/signs.rst
index 36d651e71..512cf2d84 100644
--- a/docs/signs.rst
+++ b/docs/signs.rst
@@ -220,6 +220,9 @@ POI markers. For example::
.. note::
A --genpoi run will NOT generate a map render, it will only generate markers.
+If all went well, you will see a "Markers" button in the upper-right corner of
+your map.
+
genPOI.py
---------
diff --git a/overviewer_core/aux_files/genPOI.py b/overviewer_core/aux_files/genPOI.py
index abb754431..70ecaa961 100755
--- a/overviewer_core/aux_files/genPOI.py
+++ b/overviewer_core/aux_files/genPOI.py
@@ -24,14 +24,17 @@
import sys
import time
import urllib2
+import datetime
from collections import defaultdict
+from contextlib import closing
from multiprocessing import Pool
from optparse import OptionParser
from overviewer_core import logger
from overviewer_core import nbt
from overviewer_core import configParser, world
+from overviewer_core.files import FileReplacer
UUID_LOOKUP_URL = 'https://sessionserver.mojang.com/session/minecraft/profile/'
@@ -161,7 +164,7 @@ def handleEntities(rset, config, config_path, filters, markers):
if numbuckets == 1:
for (x, z, mtime) in rset.iterate_chunks():
try:
- data = rset.get_chunk(x, z)
+ data = rset.get_chunk(x, z, entities_only=True)
for poi in itertools.chain(data['TileEntities'], data['Entities']):
if poi['id'] == 'Sign': # kill me
poi = signWrangler(poi)
@@ -210,9 +213,22 @@ class PlayerDict(dict):
def load_cache(cls, outputdir):
cache_file = os.path.join(outputdir, "uuidcache.dat")
if os.path.exists(cache_file):
- gz = gzip.GzipFile(cache_file)
- cls.uuid_cache = json.load(gz)
- logging.info("Loaded UUID cache from %r with %d entries", cache_file, len(cls.uuid_cache.keys()))
+ try:
+ with closing(gzip.GzipFile(cache_file)) as gz:
+ cls.uuid_cache = json.load(gz)
+ logging.info("Loaded UUID cache from %r with %d entries",
+ cache_file, len(cls.uuid_cache.keys()))
+ except (ValueError, IOError):
+ logging.warning("Failed to load UUID cache -- it might be corrupt")
+ cls.uuid_cache = {}
+ corrupted_cache = cache_file + ".corrupted." + datetime.datetime.now().isoformat()
+ try:
+ os.rename(cache_file, corrupted_cache)
+ logging.warning("If %s does not appear to contain meaningful data, you may safely delete it", corrupted_cache)
+ except OSError:
+ logging.warning("Failed to backup corrupted UUID cache")
+
+ logging.info("Initialized an empty UUID cache")
else:
cls.uuid_cache = {}
logging.info("Initialized an empty UUID cache")
@@ -220,9 +236,12 @@ def load_cache(cls, outputdir):
@classmethod
def save_cache(cls, outputdir):
cache_file = os.path.join(outputdir, "uuidcache.dat")
- gz = gzip.GzipFile(cache_file, "wb")
- json.dump(cls.uuid_cache, gz)
- logging.info("Wrote UUID cache with %d entries", len(cls.uuid_cache.keys()))
+
+ with FileReplacer(cache_file) as cache_file_name:
+ with closing(gzip.GzipFile(cache_file_name, "wb")) as gz:
+ json.dump(cls.uuid_cache, gz)
+ logging.info("Wrote UUID cache with %d entries",
+ len(cls.uuid_cache.keys()))
def __getitem__(self, item):
if item == "EntityId":
@@ -280,7 +299,7 @@ def handlePlayers(worldpath, filters, markers):
data.use_uuid = useUUIDs
if isSinglePlayer:
data = data['Data']['Player']
- except IOError:
+ except (IOError, TypeError):
logging.warning("Skipping bad player dat file %r", playerfile)
continue
@@ -513,7 +532,8 @@ def main():
logging.info("Done handling POIs")
logging.info("Writing out javascript files")
- PlayerDict.save_cache(destdir)
+ if not options.skipplayers:
+ PlayerDict.save_cache(destdir)
with open(os.path.join(destdir, "markersDB.js"), "w") as output:
output.write("var markersDB=")
diff --git a/overviewer_core/data/web_assets/index.html b/overviewer_core/data/web_assets/index.html
index 1408efa03..3fd28127a 100644
--- a/overviewer_core/data/web_assets/index.html
+++ b/overviewer_core/data/web_assets/index.html
@@ -10,7 +10,7 @@
-
+
diff --git a/overviewer_core/observer.py b/overviewer_core/observer.py
index e068acbd2..0e9bcd9f3 100644
--- a/overviewer_core/observer.py
+++ b/overviewer_core/observer.py
@@ -234,7 +234,7 @@ def __init__(self, outputdir, minrefresh=5, messages=False):
else:
raise Exception("JSObserver: messages parameter must be a dictionary with three entries: totalTiles, renderCompleted and renderProgress")
if not os.path.exists(outputdir):
- raise Exception("JSObserver: Output directory specified (%s) doesn't appear to exist. This should be the same as the Overviewer output directory")
+ raise Exception("JSObserver: Output directory specified (%s) doesn't appear to exist. This should be the same as the Overviewer output directory" % outputdir)
self.logfile = open(os.path.join(outputdir, "progress.json"), "w+", 0)
self.json["message"]="Render starting..."
diff --git a/overviewer_core/rcon.py b/overviewer_core/rcon.py
index 52a14348f..d815b8e66 100644
--- a/overviewer_core/rcon.py
+++ b/overviewer_core/rcon.py
@@ -68,7 +68,13 @@ def send(self, t, payload):
if not toread:
raise RConException(self.rid, "Request timed out.")
- res_len, res_id, res_type = struct.unpack("
#include
#include
+/* Fix Pillow on mingw-w64 which includes windows.h in Imaging.h */
+#undef TRANSPARENT
/* like (a * b + 127) / 255), but much faster on most platforms
from PIL's _imaging.c */
diff --git a/overviewer_core/src/primitives/lighting.c b/overviewer_core/src/primitives/lighting.c
index 199b79aa9..1fd818b7f 100644
--- a/overviewer_core/src/primitives/lighting.c
+++ b/overviewer_core/src/primitives/lighting.c
@@ -95,7 +95,7 @@ calculate_light_color_fancy_night(void *data,
* may (and probably should) pass NULL.
*/
-inline unsigned char
+unsigned char
estimate_blocklevel(RenderPrimitiveLighting *self, RenderState *state,
int x, int y, int z, int *authoratative) {
@@ -138,7 +138,7 @@ estimate_blocklevel(RenderPrimitiveLighting *self, RenderState *state,
blocklevel = get_data(state, BLOCKLIGHT, x, y, z);
/* no longer a guess */
- if (!(block == 44 || block == 53 || block == 67 || block == 108 || block == 109 || block == 180 || block == 182) && authoratative) {
+ if (!(block == 44 || block == 53 || block == 67 || block == 108 || block == 109 || block == 180 || block == 182 || block == 205) && authoratative) {
*authoratative = 1;
}
@@ -160,7 +160,8 @@ get_lighting_color(RenderPrimitiveLighting *self, RenderState *state,
/* special half-step handling, stairs handling */
/* Anvil also needs to be here, blockid 145 */
if (block == 44 || block == 53 || block == 67 || block == 108 || block == 109 || block == 114 ||
- block == 128 || block == 134 || block == 135 || block == 136 || block == 145 || block == 156 || block == 163 || block == 164 || block == 180 || block == 182) {
+ block == 128 || block == 134 || block == 135 || block == 136 || block == 145 || block == 156 ||
+ block == 163 || block == 164 || block == 180 || block == 182 || block == 203 || block == 205) {
unsigned int upper_block;
/* stairs and half-blocks take the skylevel from the upper block if it's transparent */
@@ -171,7 +172,8 @@ get_lighting_color(RenderPrimitiveLighting *self, RenderState *state,
upper_block = get_data(state, BLOCKS, x, y + upper_counter, z);
} while (upper_block == 44 || upper_block == 53 || upper_block == 67 || upper_block == 108 ||
upper_block == 109 || upper_block == 114 || upper_block == 128 || upper_block == 134 ||
- upper_block == 135 || upper_block == 136 || upper_block == 156 || upper_block == 163 || upper_block == 164 || upper_block == 180 || upper_block == 182);
+ upper_block == 135 || upper_block == 136 || upper_block == 156 || upper_block == 163 ||
+ upper_block == 164 || upper_block == 180 || upper_block == 182 || upper_block == 203 || upper_block == 205);
if (is_transparent(upper_block)) {
skylevel = get_data(state, SKYLIGHT, x, y + upper_counter, z);
} else {
diff --git a/overviewer_core/textures.py b/overviewer_core/textures.py
index 144d762e3..655e02e19 100644
--- a/overviewer_core/textures.py
+++ b/overviewer_core/textures.py
@@ -310,7 +310,7 @@ def search_dir(base):
if verbose: logging.info("Found %s in '%s'", filename, path)
return open(path, mode)
- raise TextureException("Could not find the textures while searching for '{0}'. Try specifying the 'texturepath' option in your config file.\nSet it to the path to a Minecraft Resource pack.\nAlternately, install the Minecraft client (which includes textures)\nAlso see \n(Remember, this version of Overviewer requires a 1.8-compatible resource pack)\n(Also note that I won't automatically use snapshots; you'll have to use the texturepath option to use a snapshot jar)".format(filename))
+ raise TextureException("Could not find the textures while searching for '{0}'. Try specifying the 'texturepath' option in your config file.\nSet it to the path to a Minecraft Resource pack.\nAlternately, install the Minecraft client (which includes textures)\nAlso see \n(Remember, this version of Overviewer requires a 1.9-compatible resource pack)\n(Also note that I won't automatically use snapshots; you'll have to use the texturepath option to use a snapshot jar)".format(filename))
def load_image_texture(self, filename):
# Textures may be animated or in a different resolution than 16x16.
@@ -706,6 +706,15 @@ def build_full_block(self, top, side1, side2, side3, side4, bottom=None):
top = self.transform_image_top(top)
alpha_over(img, top, (0, increment), top)
+ # Manually touch up 6 pixels that leave a gap because of how the
+ # shearing works out. This makes the blocks perfectly tessellate-able
+ for x,y in [(13,23), (17,21), (21,19)]:
+ # Copy a pixel to x,y from x-1,y
+ img.putpixel((x,y), img.getpixel((x-1,y)))
+ for x,y in [(3,4), (7,2), (11,0)]:
+ # Copy a pixel to x,y from x+1,y
+ img.putpixel((x,y), img.getpixel((x+1,y)))
+
return img
def build_sprite(self, side):
@@ -1557,7 +1566,7 @@ def flower(self, blockid, data):
# 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, 181, 182], data=range(16), transparent=(44,182,), solid=True)
+@material(blockid=[43, 44, 181, 182, 204, 205], data=range(16), transparent=(44,182,205), solid=True)
def slabs(self, blockid, data):
if blockid == 44 or blockid == 182:
texture = data & 7
@@ -1605,8 +1614,11 @@ def slabs(self, blockid, data):
top = side = self.load_image_texture("assets/minecraft/textures/blocks/red_sandstone_top.png");
else:
return None
+ elif blockid == 204 or blockid == 205: # purpur slab (single=205 double=204)
+ top = side = self.load_image_texture("assets/minecraft/textures/blocks/purpur_block.png");
+
- if blockid == 43 or blockid == 181: # double slab
+ if blockid == 43 or blockid == 181 or blockid == 204: # double slab
return self.build_block(top, side)
# cut the side texture in half
@@ -1740,7 +1752,7 @@ def fire(self, blockid, data):
block(blockid=52, top_image="assets/minecraft/textures/blocks/mob_spawner.png", transparent=True)
# wooden, cobblestone, red brick, stone brick, netherbrick, sandstone, spruce, birch, jungle, quartz, and red sandstone stairs.
-@material(blockid=[53,67,108,109,114,128,134,135,136,156,163,164,180], data=range(128), transparent=True, solid=True, nospawn=True)
+@material(blockid=[53,67,108,109,114,128,134,135,136,156,163,164,180,203], data=range(128), transparent=True, solid=True, nospawn=True)
def stairs(self, blockid, data):
# preserve the upside-down bit
upside_down = data & 0x4
@@ -1779,6 +1791,8 @@ def stairs(self, blockid, data):
texture = self.load_image_texture("assets/minecraft/textures/blocks/planks_big_oak.png").copy()
elif blockid == 180: # red sandstone stairs
texture = self.load_image_texture("assets/minecraft/textures/blocks/red_sandstone_normal.png").copy()
+ elif blockid == 203: # purpur stairs
+ texture = self.load_image_texture("assets/minecraft/textures/blocks/purpur_block.png").copy()
outside_l = texture.copy()
outside_r = texture.copy()
@@ -2063,18 +2077,18 @@ def chests(self, blockid, data):
def wire(self, blockid, data):
if data & 0b1000000 == 64: # powered redstone wire
- redstone_wire_t = self.load_image_texture("assets/minecraft/textures/blocks/redstone_dust_line.png")
+ redstone_wire_t = self.load_image_texture("assets/minecraft/textures/blocks/redstone_dust_line0.png").rotate(90)
redstone_wire_t = self.tint_texture(redstone_wire_t,(255,0,0))
- redstone_cross_t = self.load_image_texture("assets/minecraft/textures/blocks/redstone_dust_cross.png")
+ redstone_cross_t = self.load_image_texture("assets/minecraft/textures/blocks/redstone_dust_dot.png")
redstone_cross_t = self.tint_texture(redstone_cross_t,(255,0,0))
else: # unpowered redstone wire
- redstone_wire_t = self.load_image_texture("assets/minecraft/textures/blocks/redstone_dust_line.png")
+ redstone_wire_t = self.load_image_texture("assets/minecraft/textures/blocks/redstone_dust_line0.png").rotate(90)
redstone_wire_t = self.tint_texture(redstone_wire_t,(48,0,0))
- redstone_cross_t = self.load_image_texture("assets/minecraft/textures/blocks/redstone_dust_cross.png")
+ redstone_cross_t = self.load_image_texture("assets/minecraft/textures/blocks/redstone_dust_dot.png")
redstone_cross_t = self.tint_texture(redstone_cross_t,(48,0,0))
# generate an image per redstone direction
@@ -2101,26 +2115,24 @@ def wire(self, blockid, data):
# generate the bottom texture
if data & 0b111111 == 0:
bottom = redstone_cross_t.copy()
-
- elif data & 0b1111 == 10: #= 0b1010 redstone wire in the x direction
- bottom = redstone_wire_t.copy()
-
- elif data & 0b1111 == 5: #= 0b0101 redstone wire in the y direction
- bottom = redstone_wire_t.copy().rotate(90)
-
+
+ # see iterate.c for where these masks come from
+ has_x = (data & 0b1010) > 0
+ has_z = (data & 0b0101) > 0
+ if has_x and has_z:
+ bottom = redstone_cross_t.copy()
+ if has_x:
+ alpha_over(bottom, redstone_wire_t.copy())
+ if has_z:
+ alpha_over(bottom, redstone_wire_t.copy().rotate(90))
+
else:
- bottom = Image.new("RGBA", (16,16), self.bgcolor)
- if (data & 0b0001) == 1:
- alpha_over(bottom,branch_top_left)
-
- if (data & 0b1000) == 8:
- alpha_over(bottom,branch_top_right)
-
- if (data & 0b0010) == 2:
- alpha_over(bottom,branch_bottom_left)
-
- if (data & 0b0100) == 4:
- alpha_over(bottom,branch_bottom_right)
+ if has_x:
+ bottom = redstone_wire_t.copy()
+ elif has_z:
+ bottom = redstone_wire_t.copy().rotate(90)
+ elif data & 0b1111 == 0:
+ bottom = redstone_cross_t.copy()
# check for going up redstone wire
if data & 0b100000 == 32:
@@ -2153,9 +2165,9 @@ def crafting_table(self, blockid, data):
img = self.build_full_block(top, None, None, side3, side4, None)
return img
-# crops
+# crops with 8 data values (like wheat)
@material(blockid=59, data=range(8), transparent=True, nospawn=True)
-def crops(self, blockid, data):
+def crops8(self, blockid, data):
raw_crop = self.load_image_texture("assets/minecraft/textures/blocks/wheat_stage_%d.png" % data)
crop1 = self.transform_image_top(raw_crop)
crop2 = self.transform_image_side(raw_crop)
@@ -2167,13 +2179,25 @@ def crops(self, blockid, data):
alpha_over(img, crop3, (6,3), crop3)
return img
-# farmland
-@material(blockid=60, data=range(9), solid=True)
+# farmland and grass path (15/16 blocks)
+@material(blockid=[60,208], data=range(9), solid=True)
def farmland(self, blockid, data):
- top = self.load_image_texture("assets/minecraft/textures/blocks/farmland_wet.png")
- if data == 0:
- top = self.load_image_texture("assets/minecraft/textures/blocks/farmland_dry.png")
- return self.build_block(top, self.load_image_texture("assets/minecraft/textures/blocks/dirt.png"))
+ if blockid == 60:
+ side = self.load_image_texture("assets/minecraft/textures/blocks/dirt.png")
+ top = self.load_image_texture("assets/minecraft/textures/blocks/farmland_wet.png")
+ if data == 0:
+ top = self.load_image_texture("assets/minecraft/textures/blocks/farmland_dry.png")
+ # dirt.png is 16 pixels tall, so we need to crop it before building full block
+ side = side.crop((0, 1, 16, 16))
+ return self.build_full_block((top, 1), side, side, side, side)
+
+ else:
+ top = self.load_image_texture("assets/minecraft/textures/blocks/grass_path_top.png")
+ side = self.load_image_texture("assets/minecraft/textures/blocks/grass_path_side.png")
+ # side already has 1 transparent pixel at the top, so it doesn't need to be modified
+ # just shift the top image down 1 pixel
+ return self.build_full_block((top, 1), side, side, side, side)
+
# signposts
@material(blockid=63, data=range(16), transparent=True)
@@ -3772,8 +3796,8 @@ def cauldron(self, blockid, data):
return img
-# end portal
-@material(blockid=119, transparent=True, nodata=True)
+# end portal and end_gateway
+@material(blockid=[119,209], transparent=True, nodata=True)
def end_portal(self, blockid, data):
img = Image.new("RGBA", (24,24), self.bgcolor)
# generate a black texure with white, blue and grey dots resembling stars
@@ -3783,7 +3807,9 @@ def end_portal(self, blockid, data):
x = randint(0,15)
y = randint(0,15)
t.putpixel((x,y),color)
-
+ if blockid == 209: # end_gateway
+ return self.build_block(t, t)
+
t = self.transform_image_top(t)
alpha_over(img, t, (0,0), t)
@@ -4000,7 +4026,21 @@ def cocoa_plant(self, blockid, data):
return img
# command block
-block(blockid=137, top_image="assets/minecraft/textures/blocks/command_block.png")
+@material(blockid=[137,210,211], solid=True, nodata=True)
+def command_block(self, blockid, data):
+ if blockid == 210:
+ front = self.load_image_texture("assets/minecraft/textures/blocks/repeating_command_block_front.png")
+ side = self.load_image_texture("assets/minecraft/textures/blocks/repeating_command_block_side.png")
+ back = self.load_image_texture("assets/minecraft/textures/blocks/repeating_command_block_back.png")
+ elif blockid == 211:
+ front = self.load_image_texture("assets/minecraft/textures/blocks/chain_command_block_front.png")
+ side = self.load_image_texture("assets/minecraft/textures/blocks/chain_command_block_side.png")
+ back = self.load_image_texture("assets/minecraft/textures/blocks/chain_command_block_back.png")
+ else:
+ front = self.load_image_texture("assets/minecraft/textures/blocks/command_block_front.png")
+ side = self.load_image_texture("assets/minecraft/textures/blocks/command_block_side.png")
+ back = self.load_image_texture("assets/minecraft/textures/blocks/command_block_back.png")
+ return self.build_full_block(side, side, back, front, side)
# beacon block
# at the moment of writing this, it seems the beacon block doens't use
@@ -4025,12 +4065,15 @@ def beacon(self, blockid, data):
return img
-# cobblestone and mossy cobblestone walls
+# cobblestone and mossy cobblestone walls, chorus plants
# one additional bit of data value added for mossy and cobblestone
-@material(blockid=139, data=range(32), transparent=True, nospawn=True)
+@material(blockid=[139, 199], data=range(32), transparent=True, nospawn=True)
def cobblestone_wall(self, blockid, data):
+ # chorus plants
+ if blockid == 199:
+ t = self.load_image_texture("assets/minecraft/textures/blocks/chorus_plant.png").copy()
# no rotation, uses pseudo data
- if data & 0b10000 == 0:
+ elif data & 0b10000 == 0:
# cobblestone
t = self.load_image_texture("assets/minecraft/textures/blocks/cobblestone.png").copy()
else:
@@ -4154,17 +4197,22 @@ def cobblestone_wall(self, blockid, data):
return img
-# carrots and potatoes
+# carrots, potatoes
@material(blockid=[141,142], data=range(8), transparent=True, nospawn=True)
-def crops(self, blockid, data):
- if data != 7: # when growing they look the same
- # data = 7 -> fully grown, everything else is growing
- # this seems to work, but still not sure
- raw_crop = self.load_image_texture("assets/minecraft/textures/blocks/potatoes_stage_%d.png" % (data % 3))
- elif blockid == 141: # carrots
- raw_crop = self.load_image_texture("assets/minecraft/textures/blocks/carrots_stage_3.png")
+def crops4(self, blockid, data):
+ # carrots and potatoes have 8 data, but only 4 visual stages
+ stage = {0:0,
+ 1:0,
+ 2:1,
+ 3:1,
+ 4:2,
+ 5:2,
+ 6:2,
+ 7:3}[data]
+ if blockid == 141: # carrots
+ raw_crop = self.load_image_texture("assets/minecraft/textures/blocks/carrots_stage_%d.png" % stage)
else: # potatoes
- raw_crop = self.load_image_texture("assets/minecraft/textures/blocks/potatoes_stage_3.png")
+ raw_crop = self.load_image_texture("assets/minecraft/textures/blocks/potatoes_stage_%d.png" % stage)
crop1 = self.transform_image_top(raw_crop)
crop2 = self.transform_image_side(raw_crop)
crop3 = crop2.transpose(Image.FLIP_LEFT_RIGHT)
@@ -4405,3 +4453,66 @@ def flower(self, blockid, data):
alpha_over(img, bloom_tex.resize((14, 11), Image.ANTIALIAS), (5,5))
return img
+
+# chorus flower
+@material(blockid=200, data=range(6), solid=True)
+def chorus_flower(self, blockid, data):
+ # aged 5, dead
+ if data == 5:
+ texture = self.load_image_texture("assets/minecraft/textures/blocks/chorus_flower_dead.png")
+ else:
+ texture = self.load_image_texture("assets/minecraft/textures/blocks/chorus_flower.png")
+
+ return self.build_block(texture,texture)
+
+# purpur block
+block(blockid=201, top_image="assets/minecraft/textures/blocks/purpur_block.png")
+
+# purpur pilar
+@material(blockid=202, data=range(12) , solid=True)
+def purpur_pillar(self, blockid, data):
+ pillar_orientation = data & 12
+ top=self.load_image_texture("assets/minecraft/textures/blocks/purpur_pillar_top.png")
+ side=self.load_image_texture("assets/minecraft/textures/blocks/purpur_pillar.png")
+ if pillar_orientation == 0: # east-west orientation
+ return self.build_block(top, side)
+ elif pillar_orientation == 4: # east-west orientation
+ return self.build_full_block(side.rotate(90), None, None, top, side.rotate(90))
+ elif pillar_orientation == 8: # north-south orientation
+ return self.build_full_block(side, None, None, side.rotate(270), top)
+
+# end brick
+block(blockid=206, top_image="assets/minecraft/textures/blocks/end_bricks.png")
+
+# frosted ice
+@material(blockid=212, data=range(4), solid=True)
+def frosted_ice(self, blockid, data):
+ img = self.load_image_texture("assets/minecraft/textures/blocks/frosted_ice_%d.png" % data)
+ return self.build_block(img, img)
+
+# structure block
+@material(blockid=255, data=range(4), solid=True)
+def structure_block(self, blockid, data):
+ if data == 0:
+ img = self.load_image_texture("assets/minecraft/textures/blocks/structure_block_save.png")
+ elif data == 1:
+ img = self.load_image_texture("assets/minecraft/textures/blocks/structure_block_load.png")
+ elif data == 2:
+ img = self.load_image_texture("assets/minecraft/textures/blocks/structure_block_corner.png")
+ elif data == 3:
+ img = self.load_image_texture("assets/minecraft/textures/blocks/structure_block_data.png")
+ return self.build_block(img, img)
+
+# beetroots
+@material(blockid=207, data=range(4), transparent=True, nospawn=True)
+def crops(self, blockid, data):
+ raw_crop = self.load_image_texture("assets/minecraft/textures/blocks/beetroots_stage_%d.png" % data)
+ crop1 = self.transform_image_top(raw_crop)
+ crop2 = self.transform_image_side(raw_crop)
+ crop3 = crop2.transpose(Image.FLIP_LEFT_RIGHT)
+
+ img = Image.new("RGBA", (24,24), self.bgcolor)
+ alpha_over(img, crop1, (0,12), crop1)
+ alpha_over(img, crop2, (6,3), crop2)
+ alpha_over(img, crop3, (6,3), crop3)
+ return img
diff --git a/overviewer_core/world.py b/overviewer_core/world.py
index 910362f6a..be1f762f1 100644
--- a/overviewer_core/world.py
+++ b/overviewer_core/world.py
@@ -312,7 +312,7 @@ def _get_regionobj(self, regionfilename):
return region
#@log_other_exceptions
- def get_chunk(self, x, z):
+ def get_chunk(self, x, z, entities_only=False):
"""Returns a dictionary object representing the "Level" NBT Compound
structure for a chunk given its x, z coordinates. The coordinates given
are chunk coordinates. Raises ChunkDoesntExist exception if the given
@@ -395,66 +395,21 @@ def get_chunk(self, x, z):
chunk_data['Biomes'] = biomes
for section in chunk_data['Sections']:
+ if not entities_only:
+ section['Blocks'] = extract_blocks(section)
+ try:
+ section['SkyLight'] = extract_skylight(section)
+ section['BlockLight'] = extract_blocklight(section)
+ except ValueError:
+ # Iv'e seen at least 1 case where numpy raises a ValueError
+ # during the reshapes. I'm not sure what's going on here,
+ # but let's treat this as a corrupt chunk error.
+ logging.warning("There was a problem reading chunk %d,%d. It might be corrupt. I am giving up and will not render this particular chunk.", x, z)
- # Turn the Blocks array into a 16x16x16 numpy matrix of shorts,
- # adding in the additional block array if included.
- blocks = numpy.frombuffer(section['Blocks'], dtype=numpy.uint8)
- # Cast up to uint16, blocks can have up to 12 bits of data
- blocks = blocks.astype(numpy.uint16)
- blocks = blocks.reshape((16,16,16))
- if "Add" in section:
- # This section has additional bits to tack on to the blocks
- # array. Add is a packed array with 4 bits per slot, so
- # it needs expanding
- additional = numpy.frombuffer(section['Add'], dtype=numpy.uint8)
- additional = additional.astype(numpy.uint16).reshape((16,16,8))
- additional_expanded = numpy.empty((16,16,16), dtype=numpy.uint16)
- additional_expanded[:,:,::2] = (additional & 0x0F) << 8
- additional_expanded[:,:,1::2] = (additional & 0xF0) << 4
- blocks += additional_expanded
- del additional
- del additional_expanded
- del section['Add'] # Save some memory
- section['Blocks'] = blocks
-
- # Turn the skylight array into a 16x16x16 matrix. The array comes
- # packed 2 elements per byte, so we need to expand it.
- try:
- skylight = numpy.frombuffer(section['SkyLight'], dtype=numpy.uint8)
- skylight = skylight.reshape((16,16,8))
- skylight_expanded = numpy.empty((16,16,16), dtype=numpy.uint8)
- skylight_expanded[:,:,::2] = skylight & 0x0F
- skylight_expanded[:,:,1::2] = (skylight & 0xF0) >> 4
- del skylight
- section['SkyLight'] = skylight_expanded
-
- # Turn the BlockLight array into a 16x16x16 matrix, same as SkyLight
- blocklight = numpy.frombuffer(section['BlockLight'], dtype=numpy.uint8)
- blocklight = blocklight.reshape((16,16,8))
- blocklight_expanded = numpy.empty((16,16,16), dtype=numpy.uint8)
- blocklight_expanded[:,:,::2] = blocklight & 0x0F
- blocklight_expanded[:,:,1::2] = (blocklight & 0xF0) >> 4
- del blocklight
- section['BlockLight'] = blocklight_expanded
-
- # Turn the Data array into a 16x16x16 matrix, same as SkyLight
- data = numpy.frombuffer(section['Data'], dtype=numpy.uint8)
- data = data.reshape((16,16,8))
- data_expanded = numpy.empty((16,16,16), dtype=numpy.uint8)
- data_expanded[:,:,::2] = data & 0x0F
- data_expanded[:,:,1::2] = (data & 0xF0) >> 4
- del data
- section['Data'] = data_expanded
- except ValueError:
- # iv'e seen at least 1 case where numpy raises a value error during the reshapes. i'm not
- # sure what's going on here, but let's treat this as a corrupt chunk error
- logging.warning("There was a problem reading chunk %d,%d. It might be corrupt. I am giving up and will not render this particular chunk.", x, z)
-
- logging.debug("Full traceback:", exc_info=1)
- raise nbt.CorruptChunkError()
-
+ logging.debug("Full traceback:", exc_info=1)
+ raise nbt.CorruptChunkError()
+ section['Data'] = extract_data(section)
return chunk_data
-
def iterate_chunks(self):
"""Returns an iterator over all chunk metadata in this world. Iterates
@@ -538,6 +493,59 @@ def _iterate_regionfiles(self):
logging.warning("Holy shit what is up with region file %s !?" % f)
yield (x, y, os.path.join(self.regiondir, f))
+def extract_blocks(section):
+ # Turn the Blocks array into a 16x16x16 numpy matrix of shorts,
+ # adding in the additional block array if included.
+ blocks = numpy.frombuffer(section['Blocks'], dtype=numpy.uint8)
+ # Cast up to uint16, blocks can have up to 12 bits of data
+ blocks = blocks.astype(numpy.uint16)
+ blocks = blocks.reshape((16,16,16))
+ if 'Add' in section:
+ # This section has additional bits to tack on to the blocks
+ # array. Add is a packed array with 4 bits per slot, so
+ # it needs expanding
+ additional = numpy.frombuffer(section['Add'], dtype=numpy.uint8)
+ additional = additional.astype(numpy.uint16).reshape((16,16,8))
+ additional_expanded = numpy.empty((16,16,16), dtype=numpy.uint16)
+ additional_expanded[:,:,::2] = (additional & 0x0F) << 8
+ additional_expanded[:,:,1::2] = (additional & 0xF0) << 4
+ blocks += additional_expanded
+ del additional
+ del additional_expanded
+ del section['Add'] # Save some memory
+ return blocks
+
+def extract_skylight(section):
+ # Turn the skylight array into a 16x16x16 matrix. The array comes
+ # packed 2 elements per byte, so we need to expand it.
+ skylight = numpy.frombuffer(section['SkyLight'], dtype=numpy.uint8)
+ skylight = skylight.reshape((16,16,8))
+ skylight_expanded = numpy.empty((16,16,16), dtype=numpy.uint8)
+ skylight_expanded[:,:,::2] = skylight & 0x0F
+ skylight_expanded[:,:,1::2] = (skylight & 0xF0) >> 4
+ del skylight
+ return skylight_expanded
+
+def extract_blocklight(section):
+ # Turn the BlockLight array into a 16x16x16 matrix, same as SkyLight
+ blocklight = numpy.frombuffer(section['BlockLight'], dtype=numpy.uint8)
+ blocklight = blocklight.reshape((16,16,8))
+ blocklight_expanded = numpy.empty((16,16,16), dtype=numpy.uint8)
+ blocklight_expanded[:,:,::2] = blocklight & 0x0F
+ blocklight_expanded[:,:,1::2] = (blocklight & 0xF0) >> 4
+ del blocklight
+ return blocklight_expanded
+
+def extract_data(section):
+ # Turn the Data array into a 16x16x16 matrix, same as SkyLight
+ data = numpy.frombuffer(section['Data'], dtype=numpy.uint8)
+ data = data.reshape((16,16,8))
+ data_expanded = numpy.empty((16,16,16), dtype=numpy.uint8)
+ data_expanded[:,:,::2] = data & 0x0F
+ data_expanded[:,:,1::2] = (data & 0xF0) >> 4
+ del data
+ return data_expanded
+
class RegionSetWrapper(object):
"""This is the base class for all "wrappers" of RegionSet objects. A
wrapper is an object that acts similarly to a subclass: some methods are