|
@ -1,13 +1,7 @@ |
|
|
#!/usr/bin/env python2 |
|
|
#!/usr/bin/env python2 |
|
|
# -*- coding: utf-8 -*- |
|
|
# -*- coding: utf-8 -*- |
|
|
|
|
|
|
|
|
# This program is free software. It comes without any warranty, to |
|
|
# Made by Jogge, modified by: celeron55, expertmm |
|
|
# the extent permitted by applicable law. You can redistribute it |
|
|
|
|
|
# and/or modify it under the terms of the WTFPL |
|
|
|
|
|
# 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-29: j0gge: initial release |
|
|
# 2011-05-30: celeron55: simultaneous support for sectors/sectors2, removed |
|
|
# 2011-05-30: celeron55: simultaneous support for sectors/sectors2, removed |
|
|
# 2011-06-02: j0gge: command line parameters, coordinates, players, ... |
|
|
# 2011-06-02: j0gge: command line parameters, coordinates, players, ... |
|
@ -16,6 +10,9 @@ |
|
|
# 2011-07-30: WF: Support for content types extension, refactoring |
|
|
# 2011-07-30: WF: Support for content types extension, refactoring |
|
|
# 2011-07-30: erlehmann: PEP 8 compliance. |
|
|
# 2011-07-30: erlehmann: PEP 8 compliance. |
|
|
# 2016-03-08: expertmm: geometry and region params |
|
|
# 2016-03-08: expertmm: geometry and region params |
|
|
|
|
|
# 2017-03-17: expertmm: removed license from this file (this file should fall |
|
|
|
|
|
# under the license of minetest) |
|
|
|
|
|
# 2017-04-12: expertmm: PEP8 compliance |
|
|
|
|
|
|
|
|
# Requires Python Imaging Library: http://www.pythonware.com/products/pil/ |
|
|
# Requires Python Imaging Library: http://www.pythonware.com/products/pil/ |
|
|
|
|
|
|
|
@ -89,12 +86,14 @@ def int_to_hex4(i): |
|
|
def getBlockAsInteger(p): |
|
|
def getBlockAsInteger(p): |
|
|
return p[2]*16777216 + p[1]*4096 + p[0] |
|
|
return p[2]*16777216 + p[1]*4096 + p[0] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def unsignedToSigned(i, max_positive): |
|
|
def unsignedToSigned(i, max_positive): |
|
|
if i < max_positive: |
|
|
if i < max_positive: |
|
|
return i |
|
|
return i |
|
|
else: |
|
|
else: |
|
|
return i - 2*max_positive |
|
|
return i - 2*max_positive |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def getIntegerAsBlock(i): |
|
|
def getIntegerAsBlock(i): |
|
|
x = unsignedToSigned(i % 4096, 2048) |
|
|
x = unsignedToSigned(i % 4096, 2048) |
|
|
i = int((i - x) / 4096) |
|
|
i = int((i - x) / 4096) |
|
@ -103,6 +102,7 @@ def getIntegerAsBlock(i): |
|
|
z = unsignedToSigned(i % 4096, 2048) |
|
|
z = unsignedToSigned(i % 4096, 2048) |
|
|
return x, y, z |
|
|
return x, y, z |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def limit(i, l, h): |
|
|
def limit(i, l, h): |
|
|
if(i > h): |
|
|
if(i > h): |
|
|
i = h |
|
|
i = h |
|
@ -110,17 +110,24 @@ def limit(i, l, h): |
|
|
i = l |
|
|
i = l |
|
|
return i |
|
|
return i |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def readU8(f): |
|
|
def readU8(f): |
|
|
return ord(f.read(1)) |
|
|
return ord(f.read(1)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def readU16(f): |
|
|
def readU16(f): |
|
|
return ord(f.read(1))*256 + ord(f.read(1)) |
|
|
return ord(f.read(1))*256 + ord(f.read(1)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def readU32(f): |
|
|
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)) |
|
|
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): |
|
|
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) |
|
|
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] |
|
|
usagetext = """minetestmapper.py [options] |
|
|
-i/--input <world_path> |
|
|
-i/--input <world_path> |
|
@ -226,24 +233,24 @@ if geometry_string is not None: |
|
|
nonchunky_xmax = nonchunky_xmin + this_width - 1 # inclusive rect |
|
|
nonchunky_xmax = nonchunky_xmin + this_width - 1 # inclusive rect |
|
|
nonchunky_zmin = z |
|
|
nonchunky_zmin = z |
|
|
nonchunky_zmax = nonchunky_zmin + this_height - 1 # inclusive rect |
|
|
nonchunky_zmax = nonchunky_zmin + this_height - 1 # inclusive rect |
|
|
print("#geometry:") |
|
|
print("#geometry:" + "\n" + |
|
|
print("# x:" + str(x)) |
|
|
"# x:" + str(x) + "\n" + |
|
|
print("# z:" + str(z)) |
|
|
"# z:" + str(z) + "\n" + |
|
|
print("# width:" + str(this_width)) |
|
|
"# width:" + str(this_width) + "\n" + |
|
|
print("# height:" + str(this_height)) |
|
|
"# height:" + str(this_height) + "\n" + |
|
|
print("region:") |
|
|
"region:" + "\n" + |
|
|
print(" xmin:" + str(nonchunky_xmin)) |
|
|
" xmin:" + str(nonchunky_xmin) + "\n" + |
|
|
print(" xmax:" + str(nonchunky_xmax)) |
|
|
" xmax:" + str(nonchunky_xmax) + "\n" + |
|
|
print(" zmin:" + str(nonchunky_zmin)) |
|
|
" zmin:" + str(nonchunky_zmin) + "\n" + |
|
|
print(" zmax:" + str(nonchunky_zmax)) |
|
|
" zmax:" + str(nonchunky_zmax)) |
|
|
else: |
|
|
else: |
|
|
print("ERROR: Missing coordinates in '" + geometry_string + |
|
|
print("ERROR: Missing coordinates in '" + geometry_string + |
|
|
"' for geometry (must be in the form: x:z+width+height)") |
|
|
"' for geometry (must be in the form: x:z+width+height)") |
|
|
usage() |
|
|
usage() |
|
|
sys.exit(2) |
|
|
sys.exit(2) |
|
|
else: |
|
|
else: |
|
|
print("ERROR: Incorrect value '" + geometry_string + |
|
|
print("ERROR: Incorrect geometry syntax '" + geometry_string + |
|
|
"' for geometry (must be in the form: x:z+width+height)") |
|
|
"' (must be in the form: x:z+width+height)") |
|
|
usage() |
|
|
usage() |
|
|
sys.exit(2) |
|
|
sys.exit(2) |
|
|
elif region_string is not None: |
|
|
elif region_string is not None: |
|
@ -258,11 +265,11 @@ elif region_string is not None: |
|
|
nonchunky_xmax = int(xmax_string) |
|
|
nonchunky_xmax = int(xmax_string) |
|
|
nonchunky_zmin = int(zmin_string) |
|
|
nonchunky_zmin = int(zmin_string) |
|
|
nonchunky_zmax = int(zmax_string) |
|
|
nonchunky_zmax = int(zmax_string) |
|
|
print("region:") |
|
|
print("region:" + "\n" + |
|
|
print(" xmin:" + str(nonchunky_xmin)) |
|
|
" xmin:" + str(nonchunky_xmin) + "\n" + |
|
|
print(" xmax:" + str(nonchunky_xmax)) |
|
|
" xmax:" + str(nonchunky_xmax) + "\n" + |
|
|
print(" zmin:" + str(nonchunky_zmin)) |
|
|
" zmin:" + str(nonchunky_zmin) + "\n" + |
|
|
print(" zmax:" + str(nonchunky_zmax)) |
|
|
" zmax:" + str(nonchunky_zmax)) |
|
|
else: |
|
|
else: |
|
|
print("ERROR: Incorrect value '" + region_string + |
|
|
print("ERROR: Incorrect value '" + region_string + |
|
|
"' for region (must be in the form: xmin:xmax,zmin:zmax)") |
|
|
"' for region (must be in the form: xmin:xmax,zmin:zmax)") |
|
@ -411,8 +418,8 @@ maxz = max(zlist) |
|
|
w = (maxx - minx) * 16 + 16 |
|
|
w = (maxx - minx) * 16 + 16 |
|
|
h = (maxz - minz) * 16 + 16 |
|
|
h = (maxz - minz) * 16 + 16 |
|
|
|
|
|
|
|
|
print("Result image (w=" + str(w) + " h=" + str(h) + ") will be written to " |
|
|
print("Result image (w=" + str(w) + " h=" + str(h) + ") will be written to " + |
|
|
+ output) |
|
|
output) |
|
|
|
|
|
|
|
|
im = Image.new("RGB", (w + border, h + border), bgcolor) |
|
|
im = Image.new("RGB", (w + border, h + border), bgcolor) |
|
|
draw = ImageDraw.Draw(im) |
|
|
draw = ImageDraw.Draw(im) |
|
@ -427,15 +434,19 @@ starttime = time.time() |
|
|
|
|
|
|
|
|
CONTENT_WATER = 2 |
|
|
CONTENT_WATER = 2 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def content_is_ignore(d): |
|
|
def content_is_ignore(d): |
|
|
return d in [0, "ignore"] |
|
|
return d in [0, "ignore"] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def content_is_water(d): |
|
|
def content_is_water(d): |
|
|
return d in [2, 9] |
|
|
return d in [2, 9] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def content_is_air(d): |
|
|
def content_is_air(d): |
|
|
return d in [126, 127, 254, "air"] |
|
|
return d in [126, 127, 254, "air"] |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def read_content(mapdata, version, datapos): |
|
|
def read_content(mapdata, version, datapos): |
|
|
if version >= 24: |
|
|
if version >= 24: |
|
|
return (mapdata[datapos*2] << 8) | (mapdata[datapos*2 + 1]) |
|
|
return (mapdata[datapos*2] << 8) | (mapdata[datapos*2 + 1]) |
|
@ -450,13 +461,14 @@ def read_content(mapdata, version, datapos): |
|
|
raise Exception("Unsupported map format: " + str(version)) |
|
|
raise Exception("Unsupported map format: " + str(version)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def read_mapdata(mapdata, version, pixellist, water, day_night_differs, id_to_name): |
|
|
def read_mapdata(mapdata, version, pixellist, water, day_night_differs, |
|
|
|
|
|
id_to_name): |
|
|
global stuff # oh my :-) |
|
|
global stuff # oh my :-) |
|
|
global unknown_node_names |
|
|
global unknown_node_names |
|
|
global unknown_node_ids |
|
|
global unknown_node_ids |
|
|
|
|
|
|
|
|
if(len(mapdata) < 4096): |
|
|
if(len(mapdata) < 4096): |
|
|
print("bad: " + xhex + "/" + zhex + "/" + yhex + " " + \ |
|
|
print("bad: " + xhex + "/" + zhex + "/" + yhex + " " + |
|
|
str(len(mapdata))) |
|
|
str(len(mapdata))) |
|
|
else: |
|
|
else: |
|
|
chunkxpos = xpos * 16 |
|
|
chunkxpos = xpos * 16 |
|
@ -481,25 +493,31 @@ def read_mapdata(mapdata, version, pixellist, water, day_night_differs, id_to_na |
|
|
elif content_is_water(content): |
|
|
elif content_is_water(content): |
|
|
water[(x, z)] += 1 |
|
|
water[(x, z)] += 1 |
|
|
# Add dummy stuff for drawing sea without seabed |
|
|
# Add dummy stuff for drawing sea without seabed |
|
|
stuff[(chunkxpos + x, chunkzpos + z)] = ( |
|
|
stuff[(chunkxpos + x, chunkzpos + z)] = (chunkypos + y, |
|
|
chunkypos + y, content, water[(x, z)], day_night_differs) |
|
|
content, |
|
|
|
|
|
water[(x, z)], |
|
|
|
|
|
day_night_differs) |
|
|
elif content in colors: |
|
|
elif content in colors: |
|
|
# Memorize information on the type and height of |
|
|
# Memorize information on the type and height of |
|
|
# the block and for drawing the picture. |
|
|
# the block and for drawing the picture. |
|
|
stuff[(chunkxpos + x, chunkzpos + z)] = ( |
|
|
stuff[(chunkxpos + x, chunkzpos + z)] = (chunkypos + y, |
|
|
chunkypos + y, content, water[(x, z)], day_night_differs) |
|
|
content, |
|
|
|
|
|
water[(x, z)], |
|
|
|
|
|
day_night_differs) |
|
|
pixellist.remove((x, z)) |
|
|
pixellist.remove((x, z)) |
|
|
break |
|
|
break |
|
|
else: |
|
|
else: |
|
|
if type(content) == str: |
|
|
if type(content) == str: |
|
|
if content not in unknown_node_names: |
|
|
if content not in unknown_node_names: |
|
|
unknown_node_names.append(content) |
|
|
unknown_node_names.append(content) |
|
|
#print("unknown node: %s/%s/%s x: %d y: %d z: %d block name: %s" |
|
|
# print("unknown node: %s/%s/%s x: %d y: %d z: %d" + |
|
|
|
|
|
# " block name: %s" |
|
|
# % (xhex, zhex, yhex, x, y, z, content)) |
|
|
# % (xhex, zhex, yhex, x, y, z, content)) |
|
|
else: |
|
|
else: |
|
|
if content not in unknown_node_ids: |
|
|
if content not in unknown_node_ids: |
|
|
unknown_node_ids.append(content) |
|
|
unknown_node_ids.append(content) |
|
|
#print("unknown node: %s/%s/%s x: %d y: %d z: %d block id: %x" |
|
|
# print("unknown node: %s/%s/%s x: %d y: %d z: %d" + |
|
|
|
|
|
# " block id: %x" |
|
|
# % (xhex, zhex, yhex, x, y, z, content)) |
|
|
# % (xhex, zhex, yhex, x, y, z, content)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -520,10 +538,10 @@ for n in range(len(xlist)): |
|
|
remaining_s = time_guess - dtime |
|
|
remaining_s = time_guess - dtime |
|
|
remaining_minutes = int(remaining_s / 60) |
|
|
remaining_minutes = int(remaining_s / 60) |
|
|
remaining_s -= remaining_minutes * 60 |
|
|
remaining_s -= remaining_minutes * 60 |
|
|
print("Processing sector " + str(n) + " of " + str(len(xlist)) |
|
|
print("Processing sector " + str(n) + " of " + str(len(xlist)) + |
|
|
+ " (" + str(round(100.0 * n / len(xlist), 1)) + "%)" |
|
|
" (" + str(round(100.0 * n / len(xlist), 1)) + "%)" + |
|
|
+ " (ETA: " + str(remaining_minutes) + "m " |
|
|
" (ETA: " + str(remaining_minutes) + "m " + |
|
|
+ str(int(remaining_s)) + "s)") |
|
|
str(int(remaining_s)) + "s)") |
|
|
|
|
|
|
|
|
xpos = xlist[n] |
|
|
xpos = xlist[n] |
|
|
zpos = zlist[n] |
|
|
zpos = zlist[n] |
|
@ -543,7 +561,9 @@ for n in range(len(xlist)): |
|
|
if cur: |
|
|
if cur: |
|
|
psmin = getBlockAsInteger((xpos, -2048, zpos)) |
|
|
psmin = getBlockAsInteger((xpos, -2048, zpos)) |
|
|
psmax = getBlockAsInteger((xpos, 2047, zpos)) |
|
|
psmax = getBlockAsInteger((xpos, 2047, zpos)) |
|
|
cur.execute("SELECT `pos` FROM `blocks` WHERE `pos`>=? AND `pos`<=? AND (`pos` - ?) % 4096 = 0", (psmin, psmax, psmin)) |
|
|
cur.execute("SELECT `pos` FROM `blocks` WHERE `pos`>=?" |
|
|
|
|
|
" AND `pos`<=? AND (`pos` - ?) % 4096 = 0", |
|
|
|
|
|
(psmin, psmax, psmin)) |
|
|
while True: |
|
|
while True: |
|
|
r = cur.fetchone() |
|
|
r = cur.fetchone() |
|
|
if not r: |
|
|
if not r: |
|
@ -596,7 +616,8 @@ for n in range(len(xlist)): |
|
|
|
|
|
|
|
|
if sectortype == "sqlite": |
|
|
if sectortype == "sqlite": |
|
|
ps = getBlockAsInteger((xpos, ypos, zpos)) |
|
|
ps = getBlockAsInteger((xpos, ypos, zpos)) |
|
|
cur.execute("SELECT `data` FROM `blocks` WHERE `pos`==? LIMIT 1", (ps,)) |
|
|
cur.execute("SELECT `data` FROM `blocks`" |
|
|
|
|
|
" WHERE `pos`==? LIMIT 1", (ps,)) |
|
|
r = cur.fetchone() |
|
|
r = cur.fetchone() |
|
|
if not r: |
|
|
if not r: |
|
|
continue |
|
|
continue |
|
@ -605,7 +626,8 @@ for n in range(len(xlist)): |
|
|
if sectortype == "old": |
|
|
if sectortype == "old": |
|
|
filename = path + "sectors/" + sector1 + "/" + yhex.lower() |
|
|
filename = path + "sectors/" + sector1 + "/" + yhex.lower() |
|
|
else: |
|
|
else: |
|
|
filename = path + "sectors2/" + sector2 + "/" + yhex.lower() |
|
|
filename = path + "sectors2/" + sector2 + "/" + \ |
|
|
|
|
|
yhex.lower() |
|
|
f = file(filename, "rb") |
|
|
f = file(filename, "rb") |
|
|
|
|
|
|
|
|
# Let's just memorize these even though it's not really necessary. |
|
|
# Let's just memorize these even though it's not really necessary. |
|
@ -638,7 +660,7 @@ for n in range(len(xlist)): |
|
|
mapdata = [] |
|
|
mapdata = [] |
|
|
|
|
|
|
|
|
# Reuse the unused tail of the file |
|
|
# Reuse the unused tail of the file |
|
|
f.close(); |
|
|
f.close() |
|
|
f = cStringIO.StringIO(dec_o.unused_data) |
|
|
f = cStringIO.StringIO(dec_o.unused_data) |
|
|
# print("unused data: "+repr(dec_o.unused_data)) |
|
|
# print("unused data: "+repr(dec_o.unused_data)) |
|
|
|
|
|
|
|
@ -651,7 +673,7 @@ for n in range(len(xlist)): |
|
|
metaliststr = [] |
|
|
metaliststr = [] |
|
|
|
|
|
|
|
|
# Reuse the unused tail of the file |
|
|
# Reuse the unused tail of the file |
|
|
f.close(); |
|
|
f.close() |
|
|
f = cStringIO.StringIO(dec_o.unused_data) |
|
|
f = cStringIO.StringIO(dec_o.unused_data) |
|
|
# print("* dec_o.unused_data: "+repr(dec_o.unused_data)) |
|
|
# print("* dec_o.unused_data: "+repr(dec_o.unused_data)) |
|
|
data_after_node_metadata = dec_o.unused_data |
|
|
data_after_node_metadata = dec_o.unused_data |
|
@ -711,14 +733,16 @@ for n in range(len(xlist)): |
|
|
readS32(f) |
|
|
readS32(f) |
|
|
readS32(f) |
|
|
readS32(f) |
|
|
|
|
|
|
|
|
read_mapdata(mapdata, version, pixellist, water, day_night_differs, id_to_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 |
|
|
# After finding all the pixels in the sector, we can move on to |
|
|
# the next sector without having to continue the Y axis. |
|
|
# the next sector without having to continue the Y axis. |
|
|
if(len(pixellist) == 0): |
|
|
if(len(pixellist) == 0): |
|
|
break |
|
|
break |
|
|
except Exception as e: |
|
|
except Exception as e: |
|
|
print("Error at ("+str(xpos)+","+str(ypos)+","+str(zpos)+"): "+str(e)) |
|
|
print("Error at (" + str(xpos) + "," + str(ypos) + "," + |
|
|
|
|
|
str(zpos) + "): " + str(e)) |
|
|
sys.stdout.write("Block data: ") |
|
|
sys.stdout.write("Block data: ") |
|
|
for c in r[0]: |
|
|
for c in r[0]: |
|
|
sys.stdout.write("%2.2x " % ord(c)) |
|
|
sys.stdout.write("%2.2x " % ord(c)) |
|
@ -748,10 +772,10 @@ for (x, z) in stuff.iterkeys(): |
|
|
remaining_s = time_guess - dtime |
|
|
remaining_s = time_guess - dtime |
|
|
remaining_minutes = int(remaining_s / 60) |
|
|
remaining_minutes = int(remaining_s / 60) |
|
|
remaining_s -= remaining_minutes * 60 |
|
|
remaining_s -= remaining_minutes * 60 |
|
|
print("Drawing pixel " + str(n) + " of " + str(listlen) |
|
|
print("Drawing pixel " + str(n) + " of " + str(listlen) + |
|
|
+ " (" + str(round(100.0 * n / listlen, 1)) + "%)" |
|
|
" (" + str(round(100.0 * n / listlen, 1)) + "%)" + |
|
|
+ " (ETA: " + str(remaining_minutes) + "m " |
|
|
" (ETA: " + str(remaining_minutes) + "m " + |
|
|
+ str(int(remaining_s)) + "s)") |
|
|
str(int(remaining_s)) + "s)") |
|
|
n += 1 |
|
|
n += 1 |
|
|
|
|
|
|
|
|
(r, g, b) = colors[stuff[(x, z)][1]] |
|
|
(r, g, b) = colors[stuff[(x, z)][1]] |
|
@ -842,7 +866,8 @@ if drawplayers: |
|
|
x = (int(float(position[0]) / 10 - minx * 16)) |
|
|
x = (int(float(position[0]) / 10 - minx * 16)) |
|
|
z = int(h - (float(position[2]) / 10 - minz * 16)) |
|
|
z = int(h - (float(position[2]) / 10 - minz * 16)) |
|
|
draw.ellipse((x - 2 + border, z - 2 + border, |
|
|
draw.ellipse((x - 2 + border, z - 2 + border, |
|
|
x + 2 + border, z + 2 + border), outline=playercolor) |
|
|
x + 2 + border, z + 2 + border), |
|
|
|
|
|
outline=playercolor) |
|
|
draw.text((x + 2 + border, z + 2 + border), name, |
|
|
draw.text((x + 2 + border, z + 2 + border), name, |
|
|
font=font, fill=playercolor) |
|
|
font=font, fill=playercolor) |
|
|
f.close() |
|
|
f.close() |
|
|