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