15 103 78 42 # CONTENT_FENCE\r
1e 162 119 53 # CONTENT_RAIL\r
1f 154 110 40 # CONTENT_LADDER\r
-20 255 204 0 # CONTENT_LAVA\r
-21 255 204 0 # CONTENT_LAVASOURCE\r
+20 255 100 0 # CONTENT_LAVA\r
+21 255 100 0 # CONTENT_LAVASOURCE\r
800 107 134 51 # CONTENT_GRASS\r
801 86 58 31 # CONTENT_TREE\r
802 48 95 8 # CONTENT_LEAVES\r
80b 199 199 199 # CONTENT_STEEL\r
80c 183 183 222 # CONTENT_GLASS\r
80d 219 202 178 # CONTENT_MOSSYCOBBLE\r
-80e 78 154 6 # CONTENT_GRAVEL\r
+80e 70 70 70 # CONTENT_GRAVEL\r
80f 204 0 0 # CONTENT_SANDSTONE\r
810 0 215 0 # CONTENT_CACTUS\r
811 170 50 25 # CONTENT_BRICK\r
817 255 153 255 # CONTENT_NC\r
818 102 50 255 # CONTENT_NC_RB\r
819 200 0 0 # CONTENT_APPLE\r
+\r
+default:stone 128 128 128\r
+default:stone_with_coal 50 50 50\r
+default:water_flowing 39 66 106\r
+default:torch 255 255 0\r
+default:water_source 39 66 106\r
+default:sign_wall 117 86 41\r
+default:chest 128 79 0\r
+default:furnace 118 118 118\r
+default:fence_wood 103 78 42\r
+default:rail 162 119 53\r
+default:ladder 154 110 40\r
+default:lava_flowing 255 100 0\r
+default:lava_source 255 100 0\r
+default:dirt_with_grass 107 134 51\r
+default:tree 86 58 31\r
+default:leaves 48 95 8\r
+default:dirt_with_grass_and_footsteps 102 129 38\r
+default:mese 178 178 0\r
+default:dirt 101 84 36\r
+default:wood 104 78 42\r
+default:sand 210 194 156\r
+default:cobble 123 123 123\r
+default:steelblock 199 199 199\r
+default:glass 183 183 222\r
+default:mossycobble 219 202 178\r
+default:gravel 70 70 70\r
+default:sandstone 204 0 0\r
+default:cactus 0 215 0\r
+default:brick 170 50 25\r
+default:clay 104 78 42\r
+default:papyrus 58 105 18\r
+default:bookshelf 196 160 0\r
+default:jungletree 205 190 121\r
+default:junglegrass 62 101 25\r
+default:nyancat 255 153 255\r
+default:nyancat_rainbow 102 50 255\r
+default:apple 200 0 0\r
+default:desert_sand 210 180 50\r
+default:desert_stone 150 100 30\r
+default:dry_shrub 100 80 40\r
+\r
-#!/usr/bin/env python
+#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# This program is free software. It comes without any warranty, to
import sys
import array
import cStringIO
+import traceback
from PIL import Image, ImageDraw, ImageFont, ImageColor
TRANSLATION_TABLE = {
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("TODO: Help")
+ print(usagetext)
+
try:
opts, args = getopt.getopt(sys.argv[1:], "hi:o:", ["help", "input=",
"output=", "bgcolor=", "scalecolor=", "origincolor=",
usage()
sys.exit(2)
-path = "../world/"
+path = None
output = "map.png"
border = 0
scalecolor = "black"
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 + "/"
f = file(os.path.join(os.path.dirname(__file__), "colors.txt"))
for line in f:
values = string.split(line)
- colors[int(values[0], 16)] = (
- int(values[1]),
- int(values[2]),
- int(values[3]))
+ 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 = []
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))))
w = (maxx - minx) * 16 + 16
h = (maxz - minz) * 16 + 16
-print("w=" + str(w) + " h=" + str(h))
+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)
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]
+ return d in [126, 127, 254, "air"]
def read_content(mapdata, version, datapos):
- if version == 20:
+ if version >= 20:
if mapdata[datapos] < 0x80:
return mapdata[datapos]
else:
raise Exception("Unsupported map format: " + str(version))
-def read_mapdata(f, version, pixellist, water, day_night_differs):
+def read_mapdata(mapdata, version, pixellist, water, day_night_differs, id_to_name):
global stuff # oh my :-)
-
- dec_o = zlib.decompressobj()
- try:
- mapdata = array.array("B", dec_o.decompress(f.read()))
- except:
- mapdata = []
-
- f.close()
+ global unknown_node_names
+ global unknown_node_ids
if(len(mapdata) < 4096):
print("bad: " + xhex + "/" + zhex + "/" + yhex + " " + \
for y in reversed(range(16)):
datapos = x + y * 16 + z * 256
content = read_content(mapdata, version, datapos)
- if content_is_air(content):
+ # 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
pixellist.remove((x, z))
break
else:
- print("strange block: %s/%s/%s x: %d y: %d z: %d \
-block id: %x" % (xhex, zhex, yhex, x, y, z, content))
+ 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)):
# 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)
+ 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()
+ 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:
- filename = path + "sectors2/" + sector2 + "/" + yhex.lower()
- f = file(filename, "rb")
-
- # Let's just memorize these even though it's not really necessary.
- version = ord(f.read(1))
- flags = f.read(1)
-
- # Checking day and night differs -flag
- day_night_differs = ((ord(flags) & 2) != 0)
-
- read_mapdata(f, version, pixellist, water, day_night_differs)
-
- # 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
+ 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)
+
+ 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
+
+ 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
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)
+