mirror of
				https://github.com/luanti-org/luanti.git
				synced 2025-10-31 23:45:22 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			753 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			753 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env python2
 | |
| # -*- coding: utf-8 -*-
 | |
| 
 | |
| # This program is free software. It comes without any warranty, to
 | |
| # the extent permitted by applicable law. You can redistribute it
 | |
| # and/or modify it under the terms of the Do What The Fuck You Want
 | |
| # To Public License, Version 2, as published by Sam Hocevar. See
 | |
| # COPYING for more details.
 | |
| 
 | |
| # Made by Jogge, modified by celeron55
 | |
| # 2011-05-29: j0gge: initial release
 | |
| # 2011-05-30: celeron55: simultaneous support for sectors/sectors2, removed
 | |
| # 2011-06-02: j0gge: command line parameters, coordinates, players, ...
 | |
| # 2011-06-04: celeron55: added #!/usr/bin/python2 and converted \r\n to \n
 | |
| #                        to make it easily executable on Linux
 | |
| # 2011-07-30: WF: Support for content types extension, refactoring
 | |
| # 2011-07-30: erlehmann: PEP 8 compliance.
 | |
| 
 | |
| # Requires Python Imaging Library: http://www.pythonware.com/products/pil/
 | |
| 
 | |
| # Some speed-up: ...lol, actually it slows it down.
 | |
| #import psyco ; psyco.full()
 | |
| #from psyco.classes import *
 | |
| 
 | |
| import zlib
 | |
| import os
 | |
| import string
 | |
| import time
 | |
| import getopt
 | |
| import sys
 | |
| import array
 | |
| import cStringIO
 | |
| import traceback
 | |
| from PIL import Image, ImageDraw, ImageFont, ImageColor
 | |
| 
 | |
| TRANSLATION_TABLE = {
 | |
|     1: 0x800,  # CONTENT_GRASS
 | |
|     4: 0x801,  # CONTENT_TREE
 | |
|     5: 0x802,  # CONTENT_LEAVES
 | |
|     6: 0x803,  # CONTENT_GRASS_FOOTSTEPS
 | |
|     7: 0x804,  # CONTENT_MESE
 | |
|     8: 0x805,  # CONTENT_MUD
 | |
|     10: 0x806,  # CONTENT_CLOUD
 | |
|     11: 0x807,  # CONTENT_COALSTONE
 | |
|     12: 0x808,  # CONTENT_WOOD
 | |
|     13: 0x809,  # CONTENT_SAND
 | |
|     18: 0x80a,  # CONTENT_COBBLE
 | |
|     19: 0x80b,  # CONTENT_STEEL
 | |
|     20: 0x80c,  # CONTENT_GLASS
 | |
|     22: 0x80d,  # CONTENT_MOSSYCOBBLE
 | |
|     23: 0x80e,  # CONTENT_GRAVEL
 | |
|     24: 0x80f,  # CONTENT_SANDSTONE
 | |
|     25: 0x810,  # CONTENT_CACTUS
 | |
|     26: 0x811,  # CONTENT_BRICK
 | |
|     27: 0x812,  # CONTENT_CLAY
 | |
|     28: 0x813,  # CONTENT_PAPYRUS
 | |
|     29: 0x814}  # CONTENT_BOOKSHELF
 | |
| 
 | |
| 
 | |
| def hex_to_int(h):
 | |
|     i = int(h, 16)
 | |
|     if(i > 2047):
 | |
|         i -= 4096
 | |
|     return i
 | |
| 
 | |
| 
 | |
| def hex4_to_int(h):
 | |
|     i = int(h, 16)
 | |
|     if(i > 32767):
 | |
|         i -= 65536
 | |
|     return i
 | |
| 
 | |
| 
 | |
| def int_to_hex3(i):
 | |
|     if(i < 0):
 | |
|         return "%03X" % (i + 4096)
 | |
|     else:
 | |
|         return "%03X" % i
 | |
| 
 | |
| 
 | |
| def int_to_hex4(i):
 | |
|     if(i < 0):
 | |
|         return "%04X" % (i + 65536)
 | |
|     else:
 | |
|         return "%04X" % i
 | |
| 
 | |
| 
 | |
| def getBlockAsInteger(p):
 | |
|     return p[2]*16777216 + p[1]*4096 + p[0]
 | |
| 
 | |
| def unsignedToSigned(i, max_positive):
 | |
|     if i < max_positive:
 | |
|         return i
 | |
|     else:
 | |
|         return i - 2*max_positive
 | |
| 
 | |
| def getIntegerAsBlock(i):
 | |
|     x = unsignedToSigned(i % 4096, 2048)
 | |
|     i = int((i - x) / 4096)
 | |
|     y = unsignedToSigned(i % 4096, 2048)
 | |
|     i = int((i - y) / 4096)
 | |
|     z = unsignedToSigned(i % 4096, 2048)
 | |
|     return x,y,z
 | |
| 
 | |
| def limit(i, l, h):
 | |
|     if(i > h):
 | |
|         i = h
 | |
|     if(i < l):
 | |
|         i = l
 | |
|     return i
 | |
| 
 | |
| def readU8(f):
 | |
|     return ord(f.read(1))
 | |
| 
 | |
| def readU16(f):
 | |
|     return ord(f.read(1))*256 + ord(f.read(1))
 | |
| 
 | |
| def readU32(f):
 | |
|     return ord(f.read(1))*256*256*256 + ord(f.read(1))*256*256 + ord(f.read(1))*256 + ord(f.read(1))
 | |
| 
 | |
| def readS32(f):
 | |
|     return unsignedToSigned(ord(f.read(1))*256*256*256 + ord(f.read(1))*256*256 + ord(f.read(1))*256 + ord(f.read(1)), 2**31)
 | |
| 
 | |
| usagetext = """minetestmapper.py [options]
 | |
|   -i/--input <world_path>
 | |
|   -o/--output <output_image.png>
 | |
|   --bgcolor <color>
 | |
|   --scalecolor <color>
 | |
|   --playercolor <color>
 | |
|   --origincolor <color>
 | |
|   --drawscale
 | |
|   --drawplayers
 | |
|   --draworigin
 | |
|   --drawunderground
 | |
| Color format: '#000000'"""
 | |
| 
 | |
| def usage():
 | |
|     print(usagetext)
 | |
| 
 | |
| try:
 | |
|     opts, args = getopt.getopt(sys.argv[1:], "hi:o:", ["help", "input=",
 | |
|         "output=", "bgcolor=", "scalecolor=", "origincolor=",
 | |
|         "playercolor=", "draworigin", "drawplayers", "drawscale",
 | |
|         "drawunderground"])
 | |
| except getopt.GetoptError as err:
 | |
|     # print help information and exit:
 | |
|     print(str(err))  # will print something like "option -a not recognized"
 | |
|     usage()
 | |
|     sys.exit(2)
 | |
| 
 | |
| path = None
 | |
| output = "map.png"
 | |
| border = 0
 | |
| scalecolor = "black"
 | |
| bgcolor = "white"
 | |
| origincolor = "red"
 | |
| playercolor = "red"
 | |
| drawscale = False
 | |
| drawplayers = False
 | |
| draworigin = False
 | |
| drawunderground = False
 | |
| 
 | |
| sector_xmin = -1500 / 16
 | |
| sector_xmax = 1500 / 16
 | |
| sector_zmin = -1500 / 16
 | |
| sector_zmax = 1500 / 16
 | |
| 
 | |
| for o, a in opts:
 | |
|     if o in ("-h", "--help"):
 | |
|         usage()
 | |
|         sys.exit()
 | |
|     elif o in ("-i", "--input"):
 | |
|         path = a
 | |
|     elif o in ("-o", "--output"):
 | |
|         output = a
 | |
|     elif o == "--bgcolor":
 | |
|         bgcolor = ImageColor.getrgb(a)
 | |
|     elif o == "--scalecolor":
 | |
|         scalecolor = ImageColor.getrgb(a)
 | |
|     elif o == "--playercolor":
 | |
|         playercolor = ImageColor.getrgb(a)
 | |
|     elif o == "--origincolor":
 | |
|         origincolor = ImageColor.getrgb(a)
 | |
|     elif o == "--drawscale":
 | |
|         drawscale = True
 | |
|         border = 40
 | |
|     elif o == "--drawplayers":
 | |
|         drawplayers = True
 | |
|     elif o == "--draworigin":
 | |
|         draworigin = True
 | |
|     elif o == "--drawunderground":
 | |
|         drawunderground = True
 | |
|     else:
 | |
|         assert False, "unhandled option"
 | |
| 
 | |
| if path is None:
 | |
|     print("Please select world path (eg. -i ../worlds/yourworld) (or use --help)")
 | |
|     sys.exit(1)
 | |
| 
 | |
| if path[-1:] != "/" and path[-1:] != "\\":
 | |
|     path = path + "/"
 | |
| 
 | |
| # Load color information for the blocks.
 | |
| colors = {}
 | |
| try:
 | |
|     f = file("colors.txt")
 | |
| except IOError:
 | |
|     f = file(os.path.join(os.path.dirname(__file__), "colors.txt"))
 | |
| for line in f:
 | |
|     values = string.split(line)
 | |
|     if len(values) < 4:
 | |
|         continue
 | |
|     identifier = values[0]
 | |
|     is_hex = True
 | |
|     for c in identifier:
 | |
|         if c not in "0123456789abcdefABCDEF":
 | |
|             is_hex = False
 | |
|             break
 | |
|     if is_hex:
 | |
|         colors[int(values[0], 16)] = (
 | |
|             int(values[1]),
 | |
|             int(values[2]),
 | |
|             int(values[3]))
 | |
|     else:
 | |
|         colors[values[0]] = (
 | |
|             int(values[1]),
 | |
|             int(values[2]),
 | |
|             int(values[3]))
 | |
| f.close()
 | |
| 
 | |
| #print("colors: "+repr(colors))
 | |
| #sys.exit(1)
 | |
| 
 | |
| xlist = []
 | |
| zlist = []
 | |
| 
 | |
| # List all sectors to memory and calculate the width and heigth of the
 | |
| # resulting picture.
 | |
| 
 | |
| conn = None
 | |
| cur = None
 | |
| if os.path.exists(path + "map.sqlite"):
 | |
|     import sqlite3
 | |
|     conn = sqlite3.connect(path + "map.sqlite")
 | |
|     cur = conn.cursor()
 | |
|     
 | |
|     cur.execute("SELECT `pos` FROM `blocks`")
 | |
|     while True:
 | |
|         r = cur.fetchone()
 | |
|         if not r:
 | |
|             break
 | |
|         
 | |
|         x, y, z = getIntegerAsBlock(r[0])
 | |
|         
 | |
|         if x < sector_xmin or x > sector_xmax:
 | |
|             continue
 | |
|         if z < sector_zmin or z > sector_zmax:
 | |
|             continue
 | |
|         
 | |
|         xlist.append(x)
 | |
|         zlist.append(z)
 | |
| 
 | |
| if os.path.exists(path + "sectors2"):
 | |
|     for filename in os.listdir(path + "sectors2"):
 | |
|         for filename2 in os.listdir(path + "sectors2/" + filename):
 | |
|             x = hex_to_int(filename)
 | |
|             z = hex_to_int(filename2)
 | |
|             if x < sector_xmin or x > sector_xmax:
 | |
|                 continue
 | |
|             if z < sector_zmin or z > sector_zmax:
 | |
|                 continue
 | |
|             xlist.append(x)
 | |
|             zlist.append(z)
 | |
| 
 | |
| if os.path.exists(path + "sectors"):
 | |
|     for filename in os.listdir(path + "sectors"):
 | |
|         x = hex4_to_int(filename[:4])
 | |
|         z = hex4_to_int(filename[-4:])
 | |
|         if x < sector_xmin or x > sector_xmax:
 | |
|             continue
 | |
|         if z < sector_zmin or z > sector_zmax:
 | |
|             continue
 | |
|         xlist.append(x)
 | |
|         zlist.append(z)
 | |
| 
 | |
| if len(xlist) == 0 or len(zlist) == 0:
 | |
|     print("World does not exist.")
 | |
|     sys.exit(1)
 | |
| 
 | |
| # Get rid of doubles
 | |
| xlist, zlist = zip(*sorted(set(zip(xlist, zlist))))
 | |
| 
 | |
| minx = min(xlist)
 | |
| minz = min(zlist)
 | |
| maxx = max(xlist)
 | |
| maxz = max(zlist)
 | |
| 
 | |
| w = (maxx - minx) * 16 + 16
 | |
| h = (maxz - minz) * 16 + 16
 | |
| 
 | |
| print("Result image (w=" + str(w) + " h=" + str(h) + ") will be written to "
 | |
|         + output)
 | |
| 
 | |
| im = Image.new("RGB", (w + border, h + border), bgcolor)
 | |
| draw = ImageDraw.Draw(im)
 | |
| impix = im.load()
 | |
| 
 | |
| stuff = {}
 | |
| 
 | |
| unknown_node_names = []
 | |
| unknown_node_ids = []
 | |
| 
 | |
| starttime = time.time()
 | |
| 
 | |
| CONTENT_WATER = 2
 | |
| 
 | |
| def content_is_ignore(d):
 | |
|     return d in [0, "ignore"]
 | |
| 
 | |
| def content_is_water(d):
 | |
|     return d in [2, 9]
 | |
| 
 | |
| def content_is_air(d):
 | |
|     return d in [126, 127, 254, "air"]
 | |
| 
 | |
| def read_content(mapdata, version, datapos):
 | |
|     if version >= 24:
 | |
|         return (mapdata[datapos*2] << 8) | (mapdata[datapos*2 + 1])
 | |
|     elif version >= 20:
 | |
|         if mapdata[datapos] < 0x80:
 | |
|             return mapdata[datapos]
 | |
|         else:
 | |
|             return (mapdata[datapos] << 4) | (mapdata[datapos + 0x2000] >> 4)
 | |
|     elif 16 <= version < 20:
 | |
|         return TRANSLATION_TABLE.get(mapdata[datapos], mapdata[datapos])
 | |
|     else:
 | |
|         raise Exception("Unsupported map format: " + str(version))
 | |
| 
 | |
| 
 | |
| def read_mapdata(mapdata, version, pixellist, water, day_night_differs, id_to_name):
 | |
|     global stuff  # oh my :-)
 | |
|     global unknown_node_names
 | |
|     global unknown_node_ids
 | |
| 
 | |
|     if(len(mapdata) < 4096):
 | |
|         print("bad: " + xhex + "/" + zhex + "/" + yhex + " " + \
 | |
|             str(len(mapdata)))
 | |
|     else:
 | |
|         chunkxpos = xpos * 16
 | |
|         chunkypos = ypos * 16
 | |
|         chunkzpos = zpos * 16
 | |
|         content = 0
 | |
|         datapos = 0
 | |
|         for (x, z) in reversed(pixellist):
 | |
|             for y in reversed(range(16)):
 | |
|                 datapos = x + y * 16 + z * 256
 | |
|                 content = read_content(mapdata, version, datapos)
 | |
|                 # Try to convert id to name
 | |
|                 try:
 | |
|                     content = id_to_name[content]
 | |
|                 except KeyError:
 | |
|                     pass
 | |
| 
 | |
|                 if content_is_ignore(content):
 | |
|                     pass
 | |
|                 elif content_is_air(content):
 | |
|                     pass
 | |
|                 elif content_is_water(content):
 | |
|                     water[(x, z)] += 1
 | |
|                     # Add dummy stuff for drawing sea without seabed
 | |
|                     stuff[(chunkxpos + x, chunkzpos + z)] = (
 | |
|                         chunkypos + y, content, water[(x, z)], day_night_differs)
 | |
|                 elif content in colors:
 | |
|                     # Memorize information on the type and height of
 | |
|                     # the block and for drawing the picture.
 | |
|                     stuff[(chunkxpos + x, chunkzpos + z)] = (
 | |
|                         chunkypos + y, content, water[(x, z)], day_night_differs)
 | |
|                     pixellist.remove((x, z))
 | |
|                     break
 | |
|                 else:
 | |
|                     if type(content) == str:
 | |
|                         if content not in unknown_node_names:
 | |
|                             unknown_node_names.append(content)
 | |
|                         #print("unknown node: %s/%s/%s x: %d y: %d z: %d block name: %s"
 | |
|                         #        % (xhex, zhex, yhex, x, y, z, content))
 | |
|                     else:
 | |
|                         if content not in unknown_node_ids:
 | |
|                             unknown_node_ids.append(content)
 | |
|                         #print("unknown node: %s/%s/%s x: %d y: %d z: %d block id: %x"
 | |
|                         #        % (xhex, zhex, yhex, x, y, z, content))
 | |
| 
 | |
| 
 | |
| # Go through all sectors.
 | |
| for n in range(len(xlist)):
 | |
|     #if n > 500:
 | |
|     #   break
 | |
|     if n % 200 == 0:
 | |
|         nowtime = time.time()
 | |
|         dtime = nowtime - starttime
 | |
|         try:
 | |
|             n_per_second = 1.0 * n / dtime
 | |
|         except ZeroDivisionError:
 | |
|             n_per_second = 0
 | |
|         if n_per_second != 0:
 | |
|             seconds_per_n = 1.0 / n_per_second
 | |
|             time_guess = seconds_per_n * len(xlist)
 | |
|             remaining_s = time_guess - dtime
 | |
|             remaining_minutes = int(remaining_s / 60)
 | |
|             remaining_s -= remaining_minutes * 60
 | |
|             print("Processing sector " + str(n) + " of " + str(len(xlist))
 | |
|                     + " (" + str(round(100.0 * n / len(xlist), 1)) + "%)"
 | |
|                     + " (ETA: " + str(remaining_minutes) + "m "
 | |
|                     + str(int(remaining_s)) + "s)")
 | |
| 
 | |
|     xpos = xlist[n]
 | |
|     zpos = zlist[n]
 | |
| 
 | |
|     xhex = int_to_hex3(xpos)
 | |
|     zhex = int_to_hex3(zpos)
 | |
|     xhex4 = int_to_hex4(xpos)
 | |
|     zhex4 = int_to_hex4(zpos)
 | |
| 
 | |
|     sector1 = xhex4.lower() + zhex4.lower()
 | |
|     sector2 = xhex.lower() + "/" + zhex.lower()
 | |
| 
 | |
|     ylist = []
 | |
| 
 | |
|     sectortype = ""
 | |
| 
 | |
|     if cur:
 | |
|         psmin = getBlockAsInteger((xpos, -2048, zpos))
 | |
|         psmax = getBlockAsInteger((xpos, 2047, zpos))
 | |
|         cur.execute("SELECT `pos` FROM `blocks` WHERE `pos`>=? AND `pos`<=? AND (`pos` - ?) % 4096 = 0", (psmin, psmax, psmin))
 | |
|         while True:
 | |
|             r = cur.fetchone()
 | |
|             if not r:
 | |
|                 break
 | |
|             pos = getIntegerAsBlock(r[0])[1]
 | |
|             ylist.append(pos)
 | |
|             sectortype = "sqlite"
 | |
|     try:
 | |
|         for filename in os.listdir(path + "sectors/" + sector1):
 | |
|             if(filename != "meta"):
 | |
|                 pos = int(filename, 16)
 | |
|                 if(pos > 32767):
 | |
|                     pos -= 65536
 | |
|                 ylist.append(pos)
 | |
|                 sectortype = "old"
 | |
|     except OSError:
 | |
|         pass
 | |
| 
 | |
|     if sectortype == "":
 | |
|         try:
 | |
|             for filename in os.listdir(path + "sectors2/" + sector2):
 | |
|                 if(filename != "meta"):
 | |
|                     pos = int(filename, 16)
 | |
|                     if(pos > 32767):
 | |
|                         pos -= 65536
 | |
|                     ylist.append(pos)
 | |
|                     sectortype = "new"
 | |
|         except OSError:
 | |
|             pass
 | |
| 
 | |
|     if sectortype == "":
 | |
|         continue
 | |
| 
 | |
|     ylist.sort()
 | |
| 
 | |
|     # Make a list of pixels of the sector that are to be looked for.
 | |
|     pixellist = []
 | |
|     water = {}
 | |
|     for x in range(16):
 | |
|         for z in range(16):
 | |
|             pixellist.append((x, z))
 | |
|             water[(x, z)] = 0
 | |
| 
 | |
|     # Go through the Y axis from top to bottom.
 | |
|     for ypos in reversed(ylist):
 | |
|         try:
 | |
|             #print("("+str(xpos)+","+str(ypos)+","+str(zpos)+")")
 | |
| 
 | |
|             yhex = int_to_hex4(ypos)
 | |
| 
 | |
|             if sectortype == "sqlite":
 | |
|                 ps = getBlockAsInteger((xpos, ypos, zpos))
 | |
|                 cur.execute("SELECT `data` FROM `blocks` WHERE `pos`==? LIMIT 1", (ps,))
 | |
|                 r = cur.fetchone()
 | |
|                 if not r:
 | |
|                     continue
 | |
|                 f = cStringIO.StringIO(r[0])
 | |
|             else:
 | |
|                 if sectortype == "old":
 | |
|                     filename = path + "sectors/" + sector1 + "/" + yhex.lower()
 | |
|                 else:
 | |
|                     filename = path + "sectors2/" + sector2 + "/" + yhex.lower()
 | |
|                 f = file(filename, "rb")
 | |
| 
 | |
|             # Let's just memorize these even though it's not really necessary.
 | |
|             version = readU8(f)
 | |
|             flags = f.read(1)
 | |
|             
 | |
|             #print("version="+str(version))
 | |
|             #print("flags="+str(version))
 | |
| 
 | |
|             # Check flags
 | |
|             is_underground = ((ord(flags) & 1) != 0)
 | |
|             day_night_differs = ((ord(flags) & 2) != 0)
 | |
|             lighting_expired = ((ord(flags) & 4) != 0)
 | |
|             generated = ((ord(flags) & 8) != 0)
 | |
|             
 | |
|             #print("is_underground="+str(is_underground))
 | |
|             #print("day_night_differs="+str(day_night_differs))
 | |
|             #print("lighting_expired="+str(lighting_expired))
 | |
|             #print("generated="+str(generated))
 | |
|             
 | |
|             if version >= 22:
 | |
|                 content_width = readU8(f)
 | |
|                 params_width = readU8(f)
 | |
| 
 | |
|             # Node data
 | |
|             dec_o = zlib.decompressobj()
 | |
|             try:
 | |
|                 mapdata = array.array("B", dec_o.decompress(f.read()))
 | |
|             except:
 | |
|                 mapdata = []
 | |
|             
 | |
|             # Reuse the unused tail of the file
 | |
|             f.close();
 | |
|             f = cStringIO.StringIO(dec_o.unused_data)
 | |
|             #print("unused data: "+repr(dec_o.unused_data))
 | |
| 
 | |
|             # zlib-compressed node metadata list
 | |
|             dec_o = zlib.decompressobj()
 | |
|             try:
 | |
|                 metaliststr = array.array("B", dec_o.decompress(f.read()))
 | |
|                 # And do nothing with it
 | |
|             except:
 | |
|                 metaliststr = []
 | |
|             
 | |
|             # Reuse the unused tail of the file
 | |
|             f.close();
 | |
|             f = cStringIO.StringIO(dec_o.unused_data)
 | |
|             #print("* dec_o.unused_data: "+repr(dec_o.unused_data))
 | |
|             data_after_node_metadata = dec_o.unused_data
 | |
| 
 | |
|             if version <= 21:
 | |
|                 # mapblockobject_count
 | |
|                 readU16(f)
 | |
| 
 | |
|             if version == 23:
 | |
|                 readU8(f) # Unused node timer version (always 0)
 | |
|             if version == 24:
 | |
|                 ver = readU8(f)
 | |
|                 if ver == 1:
 | |
|                     num = readU16(f)
 | |
|                     for i in range(0,num):
 | |
|                         readU16(f)
 | |
|                         readS32(f)
 | |
|                         readS32(f)
 | |
| 
 | |
|             static_object_version = readU8(f)
 | |
|             static_object_count = readU16(f)
 | |
|             for i in range(0, static_object_count):
 | |
|                 # u8 type (object type-id)
 | |
|                 object_type = readU8(f)
 | |
|                 # s32 pos_x_nodes * 10000
 | |
|                 pos_x_nodes = readS32(f)/10000
 | |
|                 # s32 pos_y_nodes * 10000
 | |
|                 pos_y_nodes = readS32(f)/10000
 | |
|                 # s32 pos_z_nodes * 10000
 | |
|                 pos_z_nodes = readS32(f)/10000
 | |
|                 # u16 data_size
 | |
|                 data_size = readU16(f)
 | |
|                 # u8[data_size] data
 | |
|                 data = f.read(data_size)
 | |
|             
 | |
|             timestamp = readU32(f)
 | |
|             #print("* timestamp="+str(timestamp))
 | |
|             
 | |
|             id_to_name = {}
 | |
|             if version >= 22:
 | |
|                 name_id_mapping_version = readU8(f)
 | |
|                 num_name_id_mappings = readU16(f)
 | |
|                 #print("* num_name_id_mappings: "+str(num_name_id_mappings))
 | |
|                 for i in range(0, num_name_id_mappings):
 | |
|                     node_id = readU16(f)
 | |
|                     name_len = readU16(f)
 | |
|                     name = f.read(name_len)
 | |
|                     #print(str(node_id)+" = "+name)
 | |
|                     id_to_name[node_id] = name
 | |
| 
 | |
|             # Node timers
 | |
|             if version >= 25:
 | |
|                 timer_size = readU8(f)
 | |
|                 num = readU16(f)
 | |
|                 for i in range(0,num):
 | |
|                     readU16(f)
 | |
|                     readS32(f)
 | |
|                     readS32(f)
 | |
| 
 | |
|             read_mapdata(mapdata, version, pixellist, water, day_night_differs, id_to_name)
 | |
| 
 | |
|             # After finding all the pixels in the sector, we can move on to
 | |
|             # the next sector without having to continue the Y axis.
 | |
|             if(len(pixellist) == 0):
 | |
|                 break
 | |
|         except Exception as e:
 | |
|             print("Error at ("+str(xpos)+","+str(ypos)+","+str(zpos)+"): "+str(e))
 | |
|             sys.stdout.write("Block data: ")
 | |
|             for c in r[0]:
 | |
|                 sys.stdout.write("%2.2x "%ord(c))
 | |
|             sys.stdout.write(os.linesep)
 | |
|             sys.stdout.write("Data after node metadata: ")
 | |
|             for c in data_after_node_metadata:
 | |
|                 sys.stdout.write("%2.2x "%ord(c))
 | |
|             sys.stdout.write(os.linesep)
 | |
|             traceback.print_exc()
 | |
| 
 | |
| print("Drawing image")
 | |
| # Drawing the picture
 | |
| starttime = time.time()
 | |
| n = 0
 | |
| for (x, z) in stuff.iterkeys():
 | |
|     if n % 500000 == 0:
 | |
|         nowtime = time.time()
 | |
|         dtime = nowtime - starttime
 | |
|         try:
 | |
|             n_per_second = 1.0 * n / dtime
 | |
|         except ZeroDivisionError:
 | |
|             n_per_second = 0
 | |
|         if n_per_second != 0:
 | |
|             listlen = len(stuff)
 | |
|             seconds_per_n = 1.0 / n_per_second
 | |
|             time_guess = seconds_per_n * listlen
 | |
|             remaining_s = time_guess - dtime
 | |
|             remaining_minutes = int(remaining_s / 60)
 | |
|             remaining_s -= remaining_minutes * 60
 | |
|             print("Drawing pixel " + str(n) + " of " + str(listlen)
 | |
|                     + " (" + str(round(100.0 * n / listlen, 1)) + "%)"
 | |
|                     + " (ETA: " + str(remaining_minutes) + "m "
 | |
|                     + str(int(remaining_s)) + "s)")
 | |
|     n += 1
 | |
| 
 | |
|     (r, g, b) = colors[stuff[(x, z)][1]]
 | |
| 
 | |
|     dnd = stuff[(x, z)][3]  # day/night differs?
 | |
|     if not dnd and not drawunderground:
 | |
|         if stuff[(x, z)][2] > 0:  # water
 | |
|             (r, g, b) = colors[CONTENT_WATER]
 | |
|         else:
 | |
|             continue
 | |
| 
 | |
|     # Comparing heights of a couple of adjacent blocks and changing
 | |
|     # brightness accordingly.
 | |
|     try:
 | |
|         c = stuff[(x, z)][1]
 | |
|         c1 = stuff[(x - 1, z)][1]
 | |
|         c2 = stuff[(x, z + 1)][1]
 | |
|         dnd1 = stuff[(x - 1, z)][3]
 | |
|         dnd2 = stuff[(x, z + 1)][3]
 | |
|         if not dnd:
 | |
|             d = -69
 | |
|         elif not content_is_water(c1) and not content_is_water(c2) and \
 | |
|             not content_is_water(c):
 | |
|             y = stuff[(x, z)][0]
 | |
|             y1 = stuff[(x - 1, z)][0] if dnd1 else y
 | |
|             y2 = stuff[(x, z + 1)][0] if dnd2 else y
 | |
|             d = ((y - y1) + (y - y2)) * 12
 | |
|         else:
 | |
|             d = 0
 | |
| 
 | |
|         if(d > 36):
 | |
|             d = 36
 | |
| 
 | |
|         r = limit(r + d, 0, 255)
 | |
|         g = limit(g + d, 0, 255)
 | |
|         b = limit(b + d, 0, 255)
 | |
|     except:
 | |
|         pass
 | |
| 
 | |
|     # Water
 | |
|     if(stuff[(x, z)][2] > 0):
 | |
|         r = int(r * .15 + colors[2][0] * .85)
 | |
|         g = int(g * .15 + colors[2][1] * .85)
 | |
|         b = int(b * .15 + colors[2][2] * .85)
 | |
| 
 | |
|     impix[x - minx * 16 + border, h - 1 - (z - minz * 16) + border] = (r, g, b)
 | |
| 
 | |
| 
 | |
| if draworigin:
 | |
|     draw.ellipse((minx * -16 - 5 + border, h - minz * -16 - 6 + border,
 | |
|         minx * -16 + 5 + border, h - minz * -16 + 4 + border),
 | |
|         outline=origincolor)
 | |
| 
 | |
| font = ImageFont.load_default()
 | |
| 
 | |
| if drawscale:
 | |
|     draw.text((24, 0), "X", font=font, fill=scalecolor)
 | |
|     draw.text((2, 24), "Z", font=font, fill=scalecolor)
 | |
| 
 | |
|     for n in range(int(minx / -4) * -4, maxx, 4):
 | |
|         draw.text((minx * -16 + n * 16 + 2 + border, 0), str(n * 16),
 | |
|             font=font, fill=scalecolor)
 | |
|         draw.line((minx * -16 + n * 16 + border, 0,
 | |
|             minx * -16 + n * 16 + border, border - 1), fill=scalecolor)
 | |
| 
 | |
|     for n in range(int(maxz / 4) * 4, minz, -4):
 | |
|         draw.text((2, h - 1 - (n * 16 - minz * 16) + border), str(n * 16),
 | |
|             font=font, fill=scalecolor)
 | |
|         draw.line((0, h - 1 - (n * 16 - minz * 16) + border, border - 1,
 | |
|             h - 1 - (n * 16 - minz * 16) + border), fill=scalecolor)
 | |
| 
 | |
| if drawplayers:
 | |
|     try:
 | |
|         for filename in os.listdir(path + "players"):
 | |
|             f = file(path + "players/" + filename)
 | |
|             lines = f.readlines()
 | |
|             name = ""
 | |
|             position = []
 | |
|             for line in lines:
 | |
|                 p = string.split(line)
 | |
|                 if p[0] == "name":
 | |
|                     name = p[2]
 | |
|                     print(filename + ": name = " + name)
 | |
|                 if p[0] == "position":
 | |
|                     position = string.split(p[2][1:-1], ",")
 | |
|                     print(filename + ": position = " + p[2])
 | |
|             if len(name) > 0 and len(position) == 3:
 | |
|                 x = (int(float(position[0]) / 10 - minx * 16))
 | |
|                 z = int(h - (float(position[2]) / 10 - minz * 16))
 | |
|                 draw.ellipse((x - 2 + border, z - 2 + border,
 | |
|                     x + 2 + border, z + 2 + border), outline=playercolor)
 | |
|                 draw.text((x + 2 + border, z + 2 + border), name,
 | |
|                     font=font, fill=playercolor)
 | |
|             f.close()
 | |
|     except OSError:
 | |
|         pass
 | |
| 
 | |
| print("Saving")
 | |
| im.save(output)
 | |
| 
 | |
| if unknown_node_names:
 | |
|     sys.stdout.write("Unknown node names:")
 | |
|     for name in unknown_node_names:
 | |
|         sys.stdout.write(" "+name)
 | |
|     sys.stdout.write(os.linesep)
 | |
| if unknown_node_ids:
 | |
|     sys.stdout.write("Unknown node ids:")
 | |
|     for node_id in unknown_node_ids:
 | |
|         sys.stdout.write(" "+str(hex(node_id)))
 | |
|     sys.stdout.write(os.linesep)
 | |
| 
 |