forked from Mirrorlandia_minetest/minetest
Update minetestmapper.py to support the current map format (and previous ones)
This commit is contained in:
parent
e74668ef7f
commit
ff85e2343c
@ -8,8 +8,8 @@ f 128 79 0 # CONTENT_CHEST
|
|||||||
15 103 78 42 # CONTENT_FENCE
|
15 103 78 42 # CONTENT_FENCE
|
||||||
1e 162 119 53 # CONTENT_RAIL
|
1e 162 119 53 # CONTENT_RAIL
|
||||||
1f 154 110 40 # CONTENT_LADDER
|
1f 154 110 40 # CONTENT_LADDER
|
||||||
20 255 204 0 # CONTENT_LAVA
|
20 255 100 0 # CONTENT_LAVA
|
||||||
21 255 204 0 # CONTENT_LAVASOURCE
|
21 255 100 0 # CONTENT_LAVASOURCE
|
||||||
800 107 134 51 # CONTENT_GRASS
|
800 107 134 51 # CONTENT_GRASS
|
||||||
801 86 58 31 # CONTENT_TREE
|
801 86 58 31 # CONTENT_TREE
|
||||||
802 48 95 8 # CONTENT_LEAVES
|
802 48 95 8 # CONTENT_LEAVES
|
||||||
@ -22,7 +22,7 @@ f 128 79 0 # CONTENT_CHEST
|
|||||||
80b 199 199 199 # CONTENT_STEEL
|
80b 199 199 199 # CONTENT_STEEL
|
||||||
80c 183 183 222 # CONTENT_GLASS
|
80c 183 183 222 # CONTENT_GLASS
|
||||||
80d 219 202 178 # CONTENT_MOSSYCOBBLE
|
80d 219 202 178 # CONTENT_MOSSYCOBBLE
|
||||||
80e 78 154 6 # CONTENT_GRAVEL
|
80e 70 70 70 # CONTENT_GRAVEL
|
||||||
80f 204 0 0 # CONTENT_SANDSTONE
|
80f 204 0 0 # CONTENT_SANDSTONE
|
||||||
810 0 215 0 # CONTENT_CACTUS
|
810 0 215 0 # CONTENT_CACTUS
|
||||||
811 170 50 25 # CONTENT_BRICK
|
811 170 50 25 # CONTENT_BRICK
|
||||||
@ -34,3 +34,45 @@ f 128 79 0 # CONTENT_CHEST
|
|||||||
817 255 153 255 # CONTENT_NC
|
817 255 153 255 # CONTENT_NC
|
||||||
818 102 50 255 # CONTENT_NC_RB
|
818 102 50 255 # CONTENT_NC_RB
|
||||||
819 200 0 0 # CONTENT_APPLE
|
819 200 0 0 # CONTENT_APPLE
|
||||||
|
|
||||||
|
default:stone 128 128 128
|
||||||
|
default:stone_with_coal 50 50 50
|
||||||
|
default:water_flowing 39 66 106
|
||||||
|
default:torch 255 255 0
|
||||||
|
default:water_source 39 66 106
|
||||||
|
default:sign_wall 117 86 41
|
||||||
|
default:chest 128 79 0
|
||||||
|
default:furnace 118 118 118
|
||||||
|
default:fence_wood 103 78 42
|
||||||
|
default:rail 162 119 53
|
||||||
|
default:ladder 154 110 40
|
||||||
|
default:lava_flowing 255 100 0
|
||||||
|
default:lava_source 255 100 0
|
||||||
|
default:dirt_with_grass 107 134 51
|
||||||
|
default:tree 86 58 31
|
||||||
|
default:leaves 48 95 8
|
||||||
|
default:dirt_with_grass_and_footsteps 102 129 38
|
||||||
|
default:mese 178 178 0
|
||||||
|
default:dirt 101 84 36
|
||||||
|
default:wood 104 78 42
|
||||||
|
default:sand 210 194 156
|
||||||
|
default:cobble 123 123 123
|
||||||
|
default:steelblock 199 199 199
|
||||||
|
default:glass 183 183 222
|
||||||
|
default:mossycobble 219 202 178
|
||||||
|
default:gravel 70 70 70
|
||||||
|
default:sandstone 204 0 0
|
||||||
|
default:cactus 0 215 0
|
||||||
|
default:brick 170 50 25
|
||||||
|
default:clay 104 78 42
|
||||||
|
default:papyrus 58 105 18
|
||||||
|
default:bookshelf 196 160 0
|
||||||
|
default:jungletree 205 190 121
|
||||||
|
default:junglegrass 62 101 25
|
||||||
|
default:nyancat 255 153 255
|
||||||
|
default:nyancat_rainbow 102 50 255
|
||||||
|
default:apple 200 0 0
|
||||||
|
default:desert_sand 210 180 50
|
||||||
|
default:desert_stone 150 100 30
|
||||||
|
default:dry_shrub 100 80 40
|
||||||
|
|
||||||
|
262
util/minetestmapper.py
Normal file → Executable file
262
util/minetestmapper.py
Normal file → Executable file
@ -1,4 +1,4 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python2
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
# This program is free software. It comes without any warranty, to
|
# This program is free software. It comes without any warranty, to
|
||||||
@ -30,6 +30,7 @@ import getopt
|
|||||||
import sys
|
import sys
|
||||||
import array
|
import array
|
||||||
import cStringIO
|
import cStringIO
|
||||||
|
import traceback
|
||||||
from PIL import Image, ImageDraw, ImageFont, ImageColor
|
from PIL import Image, ImageDraw, ImageFont, ImageColor
|
||||||
|
|
||||||
TRANSLATION_TABLE = {
|
TRANSLATION_TABLE = {
|
||||||
@ -108,9 +109,34 @@ def limit(i, l, h):
|
|||||||
i = l
|
i = l
|
||||||
return i
|
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():
|
def usage():
|
||||||
print("TODO: Help")
|
print(usagetext)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
opts, args = getopt.getopt(sys.argv[1:], "hi:o:", ["help", "input=",
|
opts, args = getopt.getopt(sys.argv[1:], "hi:o:", ["help", "input=",
|
||||||
"output=", "bgcolor=", "scalecolor=", "origincolor=",
|
"output=", "bgcolor=", "scalecolor=", "origincolor=",
|
||||||
@ -122,7 +148,7 @@ except getopt.GetoptError as err:
|
|||||||
usage()
|
usage()
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
path = "../world/"
|
path = None
|
||||||
output = "map.png"
|
output = "map.png"
|
||||||
border = 0
|
border = 0
|
||||||
scalecolor = "black"
|
scalecolor = "black"
|
||||||
@ -167,6 +193,10 @@ for o, a in opts:
|
|||||||
else:
|
else:
|
||||||
assert False, "unhandled option"
|
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:] != "\\":
|
if path[-1:] != "/" and path[-1:] != "\\":
|
||||||
path = path + "/"
|
path = path + "/"
|
||||||
|
|
||||||
@ -178,12 +208,29 @@ except IOError:
|
|||||||
f = file(os.path.join(os.path.dirname(__file__), "colors.txt"))
|
f = file(os.path.join(os.path.dirname(__file__), "colors.txt"))
|
||||||
for line in f:
|
for line in f:
|
||||||
values = string.split(line)
|
values = string.split(line)
|
||||||
colors[int(values[0], 16)] = (
|
if len(values) < 4:
|
||||||
int(values[1]),
|
continue
|
||||||
int(values[2]),
|
identifier = values[0]
|
||||||
int(values[3]))
|
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()
|
f.close()
|
||||||
|
|
||||||
|
#print("colors: "+repr(colors))
|
||||||
|
#sys.exit(1)
|
||||||
|
|
||||||
xlist = []
|
xlist = []
|
||||||
zlist = []
|
zlist = []
|
||||||
|
|
||||||
@ -236,6 +283,10 @@ if os.path.exists(path + "sectors"):
|
|||||||
xlist.append(x)
|
xlist.append(x)
|
||||||
zlist.append(z)
|
zlist.append(z)
|
||||||
|
|
||||||
|
if len(xlist) == 0 or len(zlist) == 0:
|
||||||
|
print("World does not exist.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
# Get rid of doubles
|
# Get rid of doubles
|
||||||
xlist, zlist = zip(*sorted(set(zip(xlist, zlist))))
|
xlist, zlist = zip(*sorted(set(zip(xlist, zlist))))
|
||||||
|
|
||||||
@ -247,7 +298,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("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)
|
im = Image.new("RGB", (w + border, h + border), bgcolor)
|
||||||
draw = ImageDraw.Draw(im)
|
draw = ImageDraw.Draw(im)
|
||||||
@ -255,18 +307,24 @@ impix = im.load()
|
|||||||
|
|
||||||
stuff = {}
|
stuff = {}
|
||||||
|
|
||||||
|
unknown_node_names = []
|
||||||
|
unknown_node_ids = []
|
||||||
|
|
||||||
starttime = time.time()
|
starttime = time.time()
|
||||||
|
|
||||||
CONTENT_WATER = 2
|
CONTENT_WATER = 2
|
||||||
|
|
||||||
|
def content_is_ignore(d):
|
||||||
|
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]
|
return d in [126, 127, 254, "air"]
|
||||||
|
|
||||||
def read_content(mapdata, version, datapos):
|
def read_content(mapdata, version, datapos):
|
||||||
if version == 20:
|
if version >= 20:
|
||||||
if mapdata[datapos] < 0x80:
|
if mapdata[datapos] < 0x80:
|
||||||
return mapdata[datapos]
|
return mapdata[datapos]
|
||||||
else:
|
else:
|
||||||
@ -277,16 +335,10 @@ def read_content(mapdata, version, datapos):
|
|||||||
raise Exception("Unsupported map format: " + str(version))
|
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 :-)
|
global stuff # oh my :-)
|
||||||
|
global unknown_node_names
|
||||||
dec_o = zlib.decompressobj()
|
global unknown_node_ids
|
||||||
try:
|
|
||||||
mapdata = array.array("B", dec_o.decompress(f.read()))
|
|
||||||
except:
|
|
||||||
mapdata = []
|
|
||||||
|
|
||||||
f.close()
|
|
||||||
|
|
||||||
if(len(mapdata) < 4096):
|
if(len(mapdata) < 4096):
|
||||||
print("bad: " + xhex + "/" + zhex + "/" + yhex + " " + \
|
print("bad: " + xhex + "/" + zhex + "/" + yhex + " " + \
|
||||||
@ -301,7 +353,15 @@ def read_mapdata(f, version, pixellist, water, day_night_differs):
|
|||||||
for y in reversed(range(16)):
|
for y in reversed(range(16)):
|
||||||
datapos = x + y * 16 + z * 256
|
datapos = x + y * 16 + z * 256
|
||||||
content = read_content(mapdata, version, datapos)
|
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
|
pass
|
||||||
elif content_is_water(content):
|
elif content_is_water(content):
|
||||||
water[(x, z)] += 1
|
water[(x, z)] += 1
|
||||||
@ -316,8 +376,16 @@ def read_mapdata(f, version, pixellist, water, day_night_differs):
|
|||||||
pixellist.remove((x, z))
|
pixellist.remove((x, z))
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
print("strange block: %s/%s/%s x: %d y: %d z: %d \
|
if type(content) == str:
|
||||||
block id: %x" % (xhex, zhex, yhex, x, y, z, content))
|
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.
|
# Go through all sectors.
|
||||||
for n in range(len(xlist)):
|
for n in range(len(xlist)):
|
||||||
@ -405,36 +473,128 @@ for n in range(len(xlist)):
|
|||||||
|
|
||||||
# Go through the Y axis from top to bottom.
|
# Go through the Y axis from top to bottom.
|
||||||
for ypos in reversed(ylist):
|
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":
|
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
|
||||||
f = cStringIO.StringIO(r[0])
|
f = cStringIO.StringIO(r[0])
|
||||||
else:
|
|
||||||
if sectortype == "old":
|
|
||||||
filename = path + "sectors/" + sector1 + "/" + yhex.lower()
|
|
||||||
else:
|
else:
|
||||||
filename = path + "sectors2/" + sector2 + "/" + yhex.lower()
|
if sectortype == "old":
|
||||||
f = file(filename, "rb")
|
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.
|
# Let's just memorize these even though it's not really necessary.
|
||||||
version = ord(f.read(1))
|
version = readU8(f)
|
||||||
flags = f.read(1)
|
flags = f.read(1)
|
||||||
|
|
||||||
|
#print("version="+str(version))
|
||||||
|
#print("flags="+str(version))
|
||||||
|
|
||||||
# Checking day and night differs -flag
|
# Check flags
|
||||||
day_night_differs = ((ord(flags) & 2) != 0)
|
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)
|
||||||
|
|
||||||
read_mapdata(f, version, pixellist, water, day_night_differs)
|
# 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))
|
||||||
|
|
||||||
# After finding all the pixels in the sector, we can move on to
|
# zlib-compressed node metadata list
|
||||||
# the next sector without having to continue the Y axis.
|
dec_o = zlib.decompressobj()
|
||||||
if(len(pixellist) == 0):
|
try:
|
||||||
break
|
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")
|
print("Drawing image")
|
||||||
# Drawing the picture
|
# Drawing the picture
|
||||||
@ -558,3 +718,15 @@ if drawplayers:
|
|||||||
|
|
||||||
print("Saving")
|
print("Saving")
|
||||||
im.save(output)
|
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)
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user