diff --git a/README.rst b/README.rst index dd77116..f3f23b9 100644 --- a/README.rst +++ b/README.rst @@ -92,10 +92,10 @@ biome-accurate tinting, the Overviewer can use biome data produced by the Minecraft Biome Extractor tool. This tool can be downloaded from: http://www.minecraftforum.net/viewtopic.php?f=25&t=80902 -If the EXTRACTEDBIOMES folder is present in the world directory, then the -Overviewer will use the biome data to tint grass and leaves automatically -- -there is no command line option to turn this feature on. If this folder does -not exist, then the Overviewer will use a static tinting for grass and leaves. +If the "biomes" folder is present in the world directory, then the Overviewer +will use the biome data to tint grass and leaves automatically -- there is no +command line option to turn this feature on. If this folder does not exist, +then the Overviewer will use a static tinting for grass and leaves. Compiling the C Extension (optional) ------------------------------------ diff --git a/chunk.py b/chunk.py index 4bd5b75..a642aab 100644 --- a/chunk.py +++ b/chunk.py @@ -48,8 +48,13 @@ image def get_lvldata(filename, x, y): """Takes a filename and chunkcoords and returns the Level struct, which contains all the level info""" - - d = nbt.load_from_region(filename, x, y) + + try: + d = nbt.load_from_region(filename, x, y) + except Exception, e: + logging.warning("Error opening chunk (%i, %i) in %s. It may be corrupt. %s", x, y, filename, e) + raise ChunkCorrupt(str(e)) + if not d: raise NoSuchChunk(x,y) return d[1]['Level'] @@ -96,21 +101,6 @@ def get_tileentity_data(level): data = level['TileEntities'] return data -def iterate_chunkblocks(xoff,yoff): - """Iterates over the 16x16x128 blocks of a chunk in rendering order. - Yields (x,y,z,imgx,imgy) - x,y,z is the block coordinate in the chunk - imgx,imgy is the image offset in the chunk image where that block should go - """ - for x in xrange(15,-1,-1): - for y in xrange(16): - imgx = xoff + x*12 + y*12 - imgy = yoff - x*6 + y*6 + 128*12 + 16*12//2 - for z in xrange(128): - yield x,y,z,imgx,imgy - imgy -= 12 - - # This set holds blocks ids that can be seen through, for occlusion calculations transparent_blocks = set([0, 6, 8, 9, 18, 20, 37, 38, 39, 40, 44, 50, 51, 52, 53, 55, 59, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 74, 75, 76, 77, 78, 79, 81, 83, 85]) @@ -207,11 +197,8 @@ class ChunkRenderer(object): try: self._level = get_lvldata(self.regionfile, self.chunkX, self.chunkY) except NoSuchChunk, e: - #logging.debug("Skipping non-existant chunk") + logging.debug("Skipping non-existant chunk") raise - except Exception, e: - logging.warning("Error opening chunk file %s. It may be corrupt. %s", self.regionfile, e) - raise ChunkCorrupt(str(e)) return self._level level = property(_load_level) @@ -543,9 +530,9 @@ class ChunkRenderer(object): if self.world.useBiomeData: biomeColorData = textures.getBiomeData(self.world.worlddir, self.chunkX, self.chunkY) - # in the 8x8 block of biome data, what chunk is this?l - startX = (self.chunkX - int(math.floor(self.chunkX/8)*8)) - startY = (self.chunkY - int(math.floor(self.chunkY/8)*8)) + # in the 32x32 block of biome data, what chunk is this?l + startX = self.chunkX % 32 + startY = self.chunkY % 32 # Each block is 24x24 # The next block on the X axis adds 12px to x and subtracts 6px from y in the image @@ -557,201 +544,214 @@ class ChunkRenderer(object): if not img: img = Image.new("RGBA", (384, 1728), (38,92,255,0)) - for x,y,z,imgx,imgy in iterate_chunkblocks(xoff,yoff): - # make sure we're drawing within the image boundaries - # 24 == approximate width, height of one block - if imgx >= img.size[0] + 24 or imgx <= -24: - continue - if imgy >= img.size[1] + 24 or imgy <= -24: - continue - - blockid = blocks[x,y,z] - - # the following blocks don't have textures that can be pre-computed from the blockid - # alone. additional data is required. - # TODO torches, redstone torches, crops, ladders, stairs, - # levers, doors, buttons, and signs all need to be handled here (and in textures.py) - - ## minecart track, crops, ladder, doors, etc. - if blockid in textures.special_blocks: - # also handle furnaces here, since one side has a different texture than the other - ancilData = blockData_expanded[x,y,z] - try: - if blockid in pseudo_ancildata_blocks: - - pseudo_ancilData = self.generate_pseudo_ancildata(x,y,z,blockid) - ancilData = pseudo_ancilData - t = textures.specialblockmap[(blockid, ancilData)] - except KeyError: - t = None - - else: - t = textures.blockmap[blockid] - - if not t: - continue - - if self.world.useBiomeData: - if blockid == 2: #grass - index = biomeColorData[ ((startY*16)+y) * 128 + (startX*16) + x] - c = textures.grasscolor[index] - - # only tint the top texture - t = textures.prepareGrassTexture(c) - elif blockid == 18: # leaves - index = biomeColorData[ ((startY*16)+y) * 128 + (startX*16) + x] - c = textures.foliagecolor[index] - - t = textures.prepareLeafTexture(c) - - - - - # Check if this block is occluded - if cave and ( - x == 0 and y != 15 and z != 127 - ): - # If it's on the x face, only render if there's a - # transparent block in the y+1 direction OR the z-1 - # direction - if ( - blocks[x,y+1,z] not in transparent_blocks and - blocks[x,y,z+1] not in transparent_blocks - ): - continue - elif cave and ( - y == 15 and x != 0 and z != 127 - ): - # If it's on the facing y face, only render if there's - # a transparent block in the x-1 direction OR the z-1 - # direction - if ( - blocks[x-1,y,z] not in transparent_blocks and - blocks[x,y,z+1] not in transparent_blocks - ): - continue - elif cave and ( - y == 15 and x == 0 and z != 127 - ): - # If it's on the facing edge, only render if what's - # above it is transparent - if ( - blocks[x,y,z+1] not in transparent_blocks - ): - continue - elif (left_blocks == None and right_blocks == None): - # Normal block or not cave mode, check sides for - # transparentcy or render if it's a border chunk. - - if ( - x != 0 and y != 15 and z != 127 and - blocks[x-1,y,z] not in transparent_blocks and - blocks[x,y+1,z] not in transparent_blocks and - blocks[x,y,z+1] not in transparent_blocks - ): - continue - - elif (left_blocks != None and right_blocks == None): - - if ( - # If it has the left face covered check for - # transparent blocks in left face - y != 15 and z != 127 and - (left_blocks[15,y,z] if x == 0 else blocks[x - 1,y,z]) not in transparent_blocks and - blocks[x,y+1,z] not in transparent_blocks and - blocks[x,y,z+1] not in transparent_blocks - ): - continue - - elif (left_blocks == None and right_blocks != None): - - if ( - # If it has the right face covered check for - # transparent blocks in right face - x != 0 and z != 127 and - blocks[x-1,y,z] not in transparent_blocks and - (right_blocks[x,0,z] if y == 15 else blocks[x,y + 1,z]) not in transparent_blocks and - blocks[x,y,z+1] not in transparent_blocks - ): - continue + for x in xrange(15,-1,-1): + for y in xrange(16): + imgx = xoff + x*12 + y*12 + imgy = yoff - x*6 + y*6 + 128*12 + 16*12//2 + imgy += 12 - elif ( - # If it's a interior chunk check for transparent blocks - # in the adjacent chunks. - z != 127 and - (left_blocks[15,y,z] if x == 0 else blocks[x - 1,y,z]) not in transparent_blocks and - (right_blocks[x,0,z] if y == 15 else blocks[x,y + 1,z]) not in transparent_blocks and - blocks[x,y,z+1] not in transparent_blocks - # Don't render if all sides aren't transparent - ): + # make sure we're drawing within the image boundaries + # 24 == approximate width, height of one block + if imgx >= img.size[0] + 24 or imgx <= -24: continue - # Draw the actual block on the image. For cave images, - # tint the block with a color proportional to its depth - if cave: - # no lighting for cave -- depth is probably more useful - composite.alpha_over(img, Image.blend(t[0],depth_colors[z],0.3), (imgx, imgy), t[1]) - else: - if not self.quadtree.lighting: - # no lighting at all - composite.alpha_over(img, t[0], (imgx, imgy), t[1]) - elif blockid in transparent_blocks: - # transparent means draw the whole - # block shaded with the current - # block's light - black_coeff, _ = self.get_lighting_coefficient(x, y, z) - if self.quadtree.spawn and black_coeff > 0.8 and blockid in solid_blocks and not ( - blockid in nospawn_blocks or ( - z != 127 and (blocks[x,y,z+1] in solid_blocks or blocks[x,y,z+1] in fluid_blocks) - ) - ): - composite.alpha_over(img, Image.blend(t[0], red_color, black_coeff), (imgx, imgy), t[1]) - else: - composite.alpha_over(img, Image.blend(t[0], black_color, black_coeff), (imgx, imgy), t[1]) - else: - # draw each face lit appropriately, - # but first just draw the block - composite.alpha_over(img, t[0], (imgx, imgy), t[1]) + for z in xrange(128): + imgy -= 12 - # top face - black_coeff, face_occlude = self.get_lighting_coefficient(x, y, z + 1) - # Use red instead of black for spawnable blocks - if self.quadtree.spawn and black_coeff > 0.8 and blockid in solid_blocks and not ( - blockid in nospawn_blocks or ( - z != 127 and (blocks[x,y,z+1] in solid_blocks or blocks[x,y,z+1] in fluid_blocks) - ) - ): - over_color = red_color + # similar check for y, as above + if imgy >= img.size[1] + 24 or imgy <= -24: + continue + + blockid = blocks[x,y,z] + + # the following blocks don't have textures that can be pre-computed from the blockid + # alone. additional data is required. + # TODO torches, redstone torches, crops, ladders, stairs, + # levers, doors, buttons, and signs all need to be handled here (and in textures.py) + + ## minecart track, crops, ladder, doors, etc. + if blockid in textures.special_blocks: + # also handle furnaces here, since one side has a different texture than the other + ancilData = blockData_expanded[x,y,z] + try: + if blockid in pseudo_ancildata_blocks: + + pseudo_ancilData = self.generate_pseudo_ancildata(x,y,z,blockid) + ancilData = pseudo_ancilData + t = textures.specialblockmap[(blockid, ancilData)] + except KeyError: + t = None + else: - over_color = black_color + t = textures.blockmap[blockid] - if not face_occlude: - composite.alpha_over(img, over_color, (imgx, imgy), ImageEnhance.Brightness(facemasks[0]).enhance(black_coeff)) - - # left face - black_coeff, face_occlude = self.get_lighting_coefficient(x - 1, y, z) - if not face_occlude: - composite.alpha_over(img, over_color, (imgx, imgy), ImageEnhance.Brightness(facemasks[1]).enhance(black_coeff)) + if not t: + continue - # right face - black_coeff, face_occlude = self.get_lighting_coefficient(x, y + 1, z) - if not face_occlude: - composite.alpha_over(img, over_color, (imgx, imgy), ImageEnhance.Brightness(facemasks[2]).enhance(black_coeff)) + if self.world.useBiomeData: + # 16 : number of blocks in a chunk (in one direction) + # 32 : number of chunks in a region (and biome file) in one direction + # so 16 * 32 == 512 : number of blocks in biome file, in one direction + if blockid == 2: #grass + index = biomeColorData[ ((startY*16)+y) * 512 + (startX*16) + x] + c = textures.grasscolor[index] - # Draw edge lines - if blockid in (44,): # step block - increment = 6 - elif blockid in (78,): # snow - increment = 9 - else: - increment = 0 + # only tint the top texture + t = textures.prepareGrassTexture(c) + elif blockid == 18: # leaves + index = biomeColorData[ ((startY*16)+y) * 512 + (startX*16) + x] + c = textures.foliagecolor[index] - if blockid not in transparent_blocks or blockid in (78,): #special case snow so the outline is still drawn - draw = ImageDraw.Draw(img) - if x != 15 and blocks[x+1,y,z] == 0: - draw.line(((imgx+12,imgy+increment), (imgx+22,imgy+5+increment)), fill=(0,0,0), width=1) - if y != 0 and blocks[x,y-1,z] == 0: - draw.line(((imgx,imgy+6+increment), (imgx+12,imgy+increment)), fill=(0,0,0), width=1) + t = textures.prepareLeafTexture(c) + + + + + # Check if this block is occluded + if cave and ( + x == 0 and y != 15 and z != 127 + ): + # If it's on the x face, only render if there's a + # transparent block in the y+1 direction OR the z-1 + # direction + if ( + blocks[x,y+1,z] not in transparent_blocks and + blocks[x,y,z+1] not in transparent_blocks + ): + continue + elif cave and ( + y == 15 and x != 0 and z != 127 + ): + # If it's on the facing y face, only render if there's + # a transparent block in the x-1 direction OR the z-1 + # direction + if ( + blocks[x-1,y,z] not in transparent_blocks and + blocks[x,y,z+1] not in transparent_blocks + ): + continue + elif cave and ( + y == 15 and x == 0 and z != 127 + ): + # If it's on the facing edge, only render if what's + # above it is transparent + if ( + blocks[x,y,z+1] not in transparent_blocks + ): + continue + elif (left_blocks == None and right_blocks == None): + # Normal block or not cave mode, check sides for + # transparentcy or render if it's a border chunk. + + if ( + x != 0 and y != 15 and z != 127 and + blocks[x-1,y,z] not in transparent_blocks and + blocks[x,y+1,z] not in transparent_blocks and + blocks[x,y,z+1] not in transparent_blocks + ): + continue + + elif (left_blocks != None and right_blocks == None): + + if ( + # If it has the left face covered check for + # transparent blocks in left face + y != 15 and z != 127 and + (left_blocks[15,y,z] if x == 0 else blocks[x - 1,y,z]) not in transparent_blocks and + blocks[x,y+1,z] not in transparent_blocks and + blocks[x,y,z+1] not in transparent_blocks + ): + continue + + elif (left_blocks == None and right_blocks != None): + + if ( + # If it has the right face covered check for + # transparent blocks in right face + x != 0 and z != 127 and + blocks[x-1,y,z] not in transparent_blocks and + (right_blocks[x,0,z] if y == 15 else blocks[x,y + 1,z]) not in transparent_blocks and + blocks[x,y,z+1] not in transparent_blocks + ): + continue + + elif ( + # If it's a interior chunk check for transparent blocks + # in the adjacent chunks. + z != 127 and + (left_blocks[15,y,z] if x == 0 else blocks[x - 1,y,z]) not in transparent_blocks and + (right_blocks[x,0,z] if y == 15 else blocks[x,y + 1,z]) not in transparent_blocks and + blocks[x,y,z+1] not in transparent_blocks + # Don't render if all sides aren't transparent + ): + continue + + # Draw the actual block on the image. For cave images, + # tint the block with a color proportional to its depth + if cave: + # no lighting for cave -- depth is probably more useful + composite.alpha_over(img, Image.blend(t[0],depth_colors[z],0.3), (imgx, imgy), t[1]) + else: + if not self.quadtree.lighting: + # no lighting at all + composite.alpha_over(img, t[0], (imgx, imgy), t[1]) + elif blockid in transparent_blocks: + # transparent means draw the whole + # block shaded with the current + # block's light + black_coeff, _ = self.get_lighting_coefficient(x, y, z) + if self.quadtree.spawn and black_coeff > 0.8 and blockid in solid_blocks and not ( + blockid in nospawn_blocks or ( + z != 127 and (blocks[x,y,z+1] in solid_blocks or blocks[x,y,z+1] in fluid_blocks) + ) + ): + composite.alpha_over(img, Image.blend(t[0], red_color, black_coeff), (imgx, imgy), t[1]) + else: + composite.alpha_over(img, Image.blend(t[0], black_color, black_coeff), (imgx, imgy), t[1]) + else: + # draw each face lit appropriately, + # but first just draw the block + composite.alpha_over(img, t[0], (imgx, imgy), t[1]) + + # top face + black_coeff, face_occlude = self.get_lighting_coefficient(x, y, z + 1) + # Use red instead of black for spawnable blocks + if self.quadtree.spawn and black_coeff > 0.8 and blockid in solid_blocks and not ( + blockid in nospawn_blocks or ( + z != 127 and (blocks[x,y,z+1] in solid_blocks or blocks[x,y,z+1] in fluid_blocks) + ) + ): + over_color = red_color + else: + over_color = black_color + + if not face_occlude: + composite.alpha_over(img, over_color, (imgx, imgy), ImageEnhance.Brightness(facemasks[0]).enhance(black_coeff)) + + # left face + black_coeff, face_occlude = self.get_lighting_coefficient(x - 1, y, z) + if not face_occlude: + composite.alpha_over(img, over_color, (imgx, imgy), ImageEnhance.Brightness(facemasks[1]).enhance(black_coeff)) + + # right face + black_coeff, face_occlude = self.get_lighting_coefficient(x, y + 1, z) + if not face_occlude: + composite.alpha_over(img, over_color, (imgx, imgy), ImageEnhance.Brightness(facemasks[2]).enhance(black_coeff)) + + # Draw edge lines + if blockid in (44,): # step block + increment = 6 + elif blockid in (78,): # snow + increment = 9 + else: + increment = 0 + + if blockid not in transparent_blocks or blockid in (78,): #special case snow so the outline is still drawn + draw = ImageDraw.Draw(img) + if x != 15 and blocks[x+1,y,z] == 0: + draw.line(((imgx+12,imgy+increment), (imgx+22,imgy+5+increment)), fill=(0,0,0), width=1) + if y != 0 and blocks[x,y-1,z] == 0: + draw.line(((imgx,imgy+6+increment), (imgx+12,imgy+increment)), fill=(0,0,0), width=1) for entity in tileEntities: diff --git a/gmap.py b/gmap.py index 64b08bc..57edb0c 100755 --- a/gmap.py +++ b/gmap.py @@ -70,6 +70,13 @@ def main(): if not os.path.exists(worlddir): # world given is either world number, or name worlds = world.get_worlds() + + # if there are no worlds found at all, exit now + if not worlds: + parser.print_help() + print "\nInvalid world path" + sys.exit(1) + try: worldnum = int(worlddir) worlddir = worlds[worldnum]['path'] @@ -79,13 +86,13 @@ def main(): worlddir = worlds[worlddir]['path'] except KeyError: # it's not a number, name, or path - print "Invalid world name or path" parser.print_help() + print "Invalid world name or path" sys.exit(1) except KeyError: # it was an invalid number - print "Invalid world number" parser.print_help() + print "Invalid world number" sys.exit(1) if len(args) != 2: @@ -128,7 +135,7 @@ def main(): if not composite.extension_alpha_over: logging.info("Notice: alpha_over extension not found; using default PIL paste()") - useBiomeData = os.path.exists(os.path.join(worlddir, 'EXTRACTEDBIOMES')) + useBiomeData = os.path.exists(os.path.join(worlddir, 'biomes')) if not useBiomeData: logging.info("Notice: Not using biome data for tinting") diff --git a/quadtree.py b/quadtree.py index 8c1a593..1edbb9e 100644 --- a/quadtree.py +++ b/quadtree.py @@ -120,7 +120,7 @@ class QuadtreeGen(object): yradius >= worldobj.maxrow and -yradius <= worldobj.minrow: break else: - raise ValueError("Your map is waaaay too big!") + raise ValueError("Your map is waaaay too big! Use the '-z' or '--zoom' options.") self.p = p else: diff --git a/textures.py b/textures.py index 2478260..da6b35e 100644 --- a/textures.py +++ b/textures.py @@ -283,7 +283,7 @@ def _build_blockimages(): # 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 36, 37, 80, -1, 65, 4, 25, -1, 98, 24, 43, -1, 86, -1, -1, -1, # Torch from above? leaving out fire. Redstone wire? Crops/furnaces handled elsewhere. sign post # 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 - -1, -1, -1, 16, -1, -1, -1, -1, -1, 51, 51, -1, -1, 1, 66, 67, # door,ladder left out. Minecart rail orientation + -1, -1, -1, 16, -1, -1, -1, -1, -1, 51, 51, -1, -1, -1, 66, 67, # door,ladder left out. Minecart rail orientation # 80 81 82 83 84 85 86 87 88 89 90 91 66, 69, 72, 73, 74, -1,102,103,104,105,-1, 102 # clay? ] @@ -300,7 +300,7 @@ def _build_blockimages(): # 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 36, 37, 80, -1, 65, 4, 25,101, 98, 24, 43, -1, 86, -1, -1, -1, # 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 - -1, -1, -1, 16, -1, -1, -1, -1, -1, 51, 51, -1, -1, 1, 66, 67, + -1, -1, -1, 16, -1, -1, -1, -1, -1, 51, 51, -1, -1, -1, 66, 67, # 80 81 82 83 84 85 86 87 88 89 90 91 66, 69, 72, 73, 74,-1 ,118,103,104,105, -1, 118 ] @@ -824,9 +824,9 @@ def prepareBiomeData(worlddir): if grasscolor and foliagecolor: return - biomeDir = os.path.join(worlddir, "EXTRACTEDBIOMES") + biomeDir = os.path.join(worlddir, "biomes") if not os.path.exists(biomeDir): - raise Exception("EXTRACTEDBIOMES not found") + raise Exception("biomes not found") # try to find the biome color images. If _find_file can't locate them # then try looking in the EXTRACTEDBIOMES folder @@ -850,16 +850,13 @@ def getBiomeData(worlddir, chunkX, chunkY): global currentBiomeFile, currentBiomeData - biomeFile = "%d.%d.biome" % ( - int(math.floor(chunkX/8)*8), - int(math.floor(chunkY/8)*8) - ) + biomeFile = "b.%d.%d.biome" % (chunkX // 32, chunkY // 32) if biomeFile == currentBiomeFile: return currentBiomeData currentBiomeFile = biomeFile - f = open(os.path.join(worlddir, "EXTRACTEDBIOMES", biomeFile), "rb") + f = open(os.path.join(worlddir, "biomes", biomeFile), "rb") rawdata = f.read() f.close()