2
0

minetestmapper.py 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752
  1. #!/usr/bin/env python2
  2. # -*- coding: utf-8 -*-
  3. # This program is free software. It comes without any warranty, to
  4. # the extent permitted by applicable law. You can redistribute it
  5. # and/or modify it under the terms of the Do What The Fuck You Want
  6. # To Public License, Version 2, as published by Sam Hocevar. See
  7. # COPYING for more details.
  8. # Made by Jogge, modified by celeron55
  9. # 2011-05-29: j0gge: initial release
  10. # 2011-05-30: celeron55: simultaneous support for sectors/sectors2, removed
  11. # 2011-06-02: j0gge: command line parameters, coordinates, players, ...
  12. # 2011-06-04: celeron55: added #!/usr/bin/python2 and converted \r\n to \n
  13. # to make it easily executable on Linux
  14. # 2011-07-30: WF: Support for content types extension, refactoring
  15. # 2011-07-30: erlehmann: PEP 8 compliance.
  16. # Requires Python Imaging Library: http://www.pythonware.com/products/pil/
  17. # Some speed-up: ...lol, actually it slows it down.
  18. #import psyco ; psyco.full()
  19. #from psyco.classes import *
  20. import zlib
  21. import os
  22. import string
  23. import time
  24. import getopt
  25. import sys
  26. import array
  27. import cStringIO
  28. import traceback
  29. from PIL import Image, ImageDraw, ImageFont, ImageColor
  30. TRANSLATION_TABLE = {
  31. 1: 0x800, # CONTENT_GRASS
  32. 4: 0x801, # CONTENT_TREE
  33. 5: 0x802, # CONTENT_LEAVES
  34. 6: 0x803, # CONTENT_GRASS_FOOTSTEPS
  35. 7: 0x804, # CONTENT_MESE
  36. 8: 0x805, # CONTENT_MUD
  37. 10: 0x806, # CONTENT_CLOUD
  38. 11: 0x807, # CONTENT_COALSTONE
  39. 12: 0x808, # CONTENT_WOOD
  40. 13: 0x809, # CONTENT_SAND
  41. 18: 0x80a, # CONTENT_COBBLE
  42. 19: 0x80b, # CONTENT_STEEL
  43. 20: 0x80c, # CONTENT_GLASS
  44. 22: 0x80d, # CONTENT_MOSSYCOBBLE
  45. 23: 0x80e, # CONTENT_GRAVEL
  46. 24: 0x80f, # CONTENT_SANDSTONE
  47. 25: 0x810, # CONTENT_CACTUS
  48. 26: 0x811, # CONTENT_BRICK
  49. 27: 0x812, # CONTENT_CLAY
  50. 28: 0x813, # CONTENT_PAPYRUS
  51. 29: 0x814} # CONTENT_BOOKSHELF
  52. def hex_to_int(h):
  53. i = int(h, 16)
  54. if(i > 2047):
  55. i -= 4096
  56. return i
  57. def hex4_to_int(h):
  58. i = int(h, 16)
  59. if(i > 32767):
  60. i -= 65536
  61. return i
  62. def int_to_hex3(i):
  63. if(i < 0):
  64. return "%03X" % (i + 4096)
  65. else:
  66. return "%03X" % i
  67. def int_to_hex4(i):
  68. if(i < 0):
  69. return "%04X" % (i + 65536)
  70. else:
  71. return "%04X" % i
  72. def getBlockAsInteger(p):
  73. return p[2]*16777216 + p[1]*4096 + p[0]
  74. def unsignedToSigned(i, max_positive):
  75. if i < max_positive:
  76. return i
  77. else:
  78. return i - 2*max_positive
  79. def getIntegerAsBlock(i):
  80. x = unsignedToSigned(i % 4096, 2048)
  81. i = int((i - x) / 4096)
  82. y = unsignedToSigned(i % 4096, 2048)
  83. i = int((i - y) / 4096)
  84. z = unsignedToSigned(i % 4096, 2048)
  85. return x,y,z
  86. def limit(i, l, h):
  87. if(i > h):
  88. i = h
  89. if(i < l):
  90. i = l
  91. return i
  92. def readU8(f):
  93. return ord(f.read(1))
  94. def readU16(f):
  95. return ord(f.read(1))*256 + ord(f.read(1))
  96. def readU32(f):
  97. return ord(f.read(1))*256*256*256 + ord(f.read(1))*256*256 + ord(f.read(1))*256 + ord(f.read(1))
  98. def readS32(f):
  99. 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)
  100. usagetext = """minetestmapper.py [options]
  101. -i/--input <world_path>
  102. -o/--output <output_image.png>
  103. --bgcolor <color>
  104. --scalecolor <color>
  105. --playercolor <color>
  106. --origincolor <color>
  107. --drawscale
  108. --drawplayers
  109. --draworigin
  110. --drawunderground
  111. Color format: '#000000'"""
  112. def usage():
  113. print(usagetext)
  114. try:
  115. opts, args = getopt.getopt(sys.argv[1:], "hi:o:", ["help", "input=",
  116. "output=", "bgcolor=", "scalecolor=", "origincolor=",
  117. "playercolor=", "draworigin", "drawplayers", "drawscale",
  118. "drawunderground"])
  119. except getopt.GetoptError as err:
  120. # print help information and exit:
  121. print(str(err)) # will print something like "option -a not recognized"
  122. usage()
  123. sys.exit(2)
  124. path = None
  125. output = "map.png"
  126. border = 0
  127. scalecolor = "black"
  128. bgcolor = "white"
  129. origincolor = "red"
  130. playercolor = "red"
  131. drawscale = False
  132. drawplayers = False
  133. draworigin = False
  134. drawunderground = False
  135. sector_xmin = -1500 / 16
  136. sector_xmax = 1500 / 16
  137. sector_zmin = -1500 / 16
  138. sector_zmax = 1500 / 16
  139. for o, a in opts:
  140. if o in ("-h", "--help"):
  141. usage()
  142. sys.exit()
  143. elif o in ("-i", "--input"):
  144. path = a
  145. elif o in ("-o", "--output"):
  146. output = a
  147. elif o == "--bgcolor":
  148. bgcolor = ImageColor.getrgb(a)
  149. elif o == "--scalecolor":
  150. scalecolor = ImageColor.getrgb(a)
  151. elif o == "--playercolor":
  152. playercolor = ImageColor.getrgb(a)
  153. elif o == "--origincolor":
  154. origincolor = ImageColor.getrgb(a)
  155. elif o == "--drawscale":
  156. drawscale = True
  157. border = 40
  158. elif o == "--drawplayers":
  159. drawplayers = True
  160. elif o == "--draworigin":
  161. draworigin = True
  162. elif o == "--drawunderground":
  163. drawunderground = True
  164. else:
  165. assert False, "unhandled option"
  166. if path is None:
  167. print("Please select world path (eg. -i ../worlds/yourworld) (or use --help)")
  168. sys.exit(1)
  169. if path[-1:] != "/" and path[-1:] != "\\":
  170. path = path + "/"
  171. # Load color information for the blocks.
  172. colors = {}
  173. try:
  174. f = file("colors.txt")
  175. except IOError:
  176. f = file(os.path.join(os.path.dirname(__file__), "colors.txt"))
  177. for line in f:
  178. values = string.split(line)
  179. if len(values) < 4:
  180. continue
  181. identifier = values[0]
  182. is_hex = True
  183. for c in identifier:
  184. if c not in "0123456789abcdefABCDEF":
  185. is_hex = False
  186. break
  187. if is_hex:
  188. colors[int(values[0], 16)] = (
  189. int(values[1]),
  190. int(values[2]),
  191. int(values[3]))
  192. else:
  193. colors[values[0]] = (
  194. int(values[1]),
  195. int(values[2]),
  196. int(values[3]))
  197. f.close()
  198. #print("colors: "+repr(colors))
  199. #sys.exit(1)
  200. xlist = []
  201. zlist = []
  202. # List all sectors to memory and calculate the width and heigth of the
  203. # resulting picture.
  204. conn = None
  205. cur = None
  206. if os.path.exists(path + "map.sqlite"):
  207. import sqlite3
  208. conn = sqlite3.connect(path + "map.sqlite")
  209. cur = conn.cursor()
  210. cur.execute("SELECT `pos` FROM `blocks`")
  211. while True:
  212. r = cur.fetchone()
  213. if not r:
  214. break
  215. x, y, z = getIntegerAsBlock(r[0])
  216. if x < sector_xmin or x > sector_xmax:
  217. continue
  218. if z < sector_zmin or z > sector_zmax:
  219. continue
  220. xlist.append(x)
  221. zlist.append(z)
  222. if os.path.exists(path + "sectors2"):
  223. for filename in os.listdir(path + "sectors2"):
  224. for filename2 in os.listdir(path + "sectors2/" + filename):
  225. x = hex_to_int(filename)
  226. z = hex_to_int(filename2)
  227. if x < sector_xmin or x > sector_xmax:
  228. continue
  229. if z < sector_zmin or z > sector_zmax:
  230. continue
  231. xlist.append(x)
  232. zlist.append(z)
  233. if os.path.exists(path + "sectors"):
  234. for filename in os.listdir(path + "sectors"):
  235. x = hex4_to_int(filename[:4])
  236. z = hex4_to_int(filename[-4:])
  237. if x < sector_xmin or x > sector_xmax:
  238. continue
  239. if z < sector_zmin or z > sector_zmax:
  240. continue
  241. xlist.append(x)
  242. zlist.append(z)
  243. if len(xlist) == 0 or len(zlist) == 0:
  244. print("World does not exist.")
  245. sys.exit(1)
  246. # Get rid of doubles
  247. xlist, zlist = zip(*sorted(set(zip(xlist, zlist))))
  248. minx = min(xlist)
  249. minz = min(zlist)
  250. maxx = max(xlist)
  251. maxz = max(zlist)
  252. w = (maxx - minx) * 16 + 16
  253. h = (maxz - minz) * 16 + 16
  254. print("Result image (w=" + str(w) + " h=" + str(h) + ") will be written to "
  255. + output)
  256. im = Image.new("RGB", (w + border, h + border), bgcolor)
  257. draw = ImageDraw.Draw(im)
  258. impix = im.load()
  259. stuff = {}
  260. unknown_node_names = []
  261. unknown_node_ids = []
  262. starttime = time.time()
  263. CONTENT_WATER = 2
  264. def content_is_ignore(d):
  265. return d in [0, "ignore"]
  266. def content_is_water(d):
  267. return d in [2, 9]
  268. def content_is_air(d):
  269. return d in [126, 127, 254, "air"]
  270. def read_content(mapdata, version, datapos):
  271. if version >= 24:
  272. return (mapdata[datapos*2] << 8) | (mapdata[datapos*2 + 1])
  273. elif version >= 20:
  274. if mapdata[datapos] < 0x80:
  275. return mapdata[datapos]
  276. else:
  277. return (mapdata[datapos] << 4) | (mapdata[datapos + 0x2000] >> 4)
  278. elif 16 <= version < 20:
  279. return TRANSLATION_TABLE.get(mapdata[datapos], mapdata[datapos])
  280. else:
  281. raise Exception("Unsupported map format: " + str(version))
  282. def read_mapdata(mapdata, version, pixellist, water, day_night_differs, id_to_name):
  283. global stuff # oh my :-)
  284. global unknown_node_names
  285. global unknown_node_ids
  286. if(len(mapdata) < 4096):
  287. print("bad: " + xhex + "/" + zhex + "/" + yhex + " " + \
  288. str(len(mapdata)))
  289. else:
  290. chunkxpos = xpos * 16
  291. chunkypos = ypos * 16
  292. chunkzpos = zpos * 16
  293. content = 0
  294. datapos = 0
  295. for (x, z) in reversed(pixellist):
  296. for y in reversed(range(16)):
  297. datapos = x + y * 16 + z * 256
  298. content = read_content(mapdata, version, datapos)
  299. # Try to convert id to name
  300. try:
  301. content = id_to_name[content]
  302. except KeyError:
  303. pass
  304. if content_is_ignore(content):
  305. pass
  306. elif content_is_air(content):
  307. pass
  308. elif content_is_water(content):
  309. water[(x, z)] += 1
  310. # Add dummy stuff for drawing sea without seabed
  311. stuff[(chunkxpos + x, chunkzpos + z)] = (
  312. chunkypos + y, content, water[(x, z)], day_night_differs)
  313. elif content in colors:
  314. # Memorize information on the type and height of
  315. # the block and for drawing the picture.
  316. stuff[(chunkxpos + x, chunkzpos + z)] = (
  317. chunkypos + y, content, water[(x, z)], day_night_differs)
  318. pixellist.remove((x, z))
  319. break
  320. else:
  321. if type(content) == str:
  322. if content not in unknown_node_names:
  323. unknown_node_names.append(content)
  324. #print("unknown node: %s/%s/%s x: %d y: %d z: %d block name: %s"
  325. # % (xhex, zhex, yhex, x, y, z, content))
  326. else:
  327. if content not in unknown_node_ids:
  328. unknown_node_ids.append(content)
  329. #print("unknown node: %s/%s/%s x: %d y: %d z: %d block id: %x"
  330. # % (xhex, zhex, yhex, x, y, z, content))
  331. # Go through all sectors.
  332. for n in range(len(xlist)):
  333. #if n > 500:
  334. # break
  335. if n % 200 == 0:
  336. nowtime = time.time()
  337. dtime = nowtime - starttime
  338. try:
  339. n_per_second = 1.0 * n / dtime
  340. except ZeroDivisionError:
  341. n_per_second = 0
  342. if n_per_second != 0:
  343. seconds_per_n = 1.0 / n_per_second
  344. time_guess = seconds_per_n * len(xlist)
  345. remaining_s = time_guess - dtime
  346. remaining_minutes = int(remaining_s / 60)
  347. remaining_s -= remaining_minutes * 60
  348. print("Processing sector " + str(n) + " of " + str(len(xlist))
  349. + " (" + str(round(100.0 * n / len(xlist), 1)) + "%)"
  350. + " (ETA: " + str(remaining_minutes) + "m "
  351. + str(int(remaining_s)) + "s)")
  352. xpos = xlist[n]
  353. zpos = zlist[n]
  354. xhex = int_to_hex3(xpos)
  355. zhex = int_to_hex3(zpos)
  356. xhex4 = int_to_hex4(xpos)
  357. zhex4 = int_to_hex4(zpos)
  358. sector1 = xhex4.lower() + zhex4.lower()
  359. sector2 = xhex.lower() + "/" + zhex.lower()
  360. ylist = []
  361. sectortype = ""
  362. if cur:
  363. psmin = getBlockAsInteger((xpos, -2048, zpos))
  364. psmax = getBlockAsInteger((xpos, 2047, zpos))
  365. cur.execute("SELECT `pos` FROM `blocks` WHERE `pos`>=? AND `pos`<=? AND (`pos` - ?) % 4096 = 0", (psmin, psmax, psmin))
  366. while True:
  367. r = cur.fetchone()
  368. if not r:
  369. break
  370. pos = getIntegerAsBlock(r[0])[1]
  371. ylist.append(pos)
  372. sectortype = "sqlite"
  373. try:
  374. for filename in os.listdir(path + "sectors/" + sector1):
  375. if(filename != "meta"):
  376. pos = int(filename, 16)
  377. if(pos > 32767):
  378. pos -= 65536
  379. ylist.append(pos)
  380. sectortype = "old"
  381. except OSError:
  382. pass
  383. if sectortype == "":
  384. try:
  385. for filename in os.listdir(path + "sectors2/" + sector2):
  386. if(filename != "meta"):
  387. pos = int(filename, 16)
  388. if(pos > 32767):
  389. pos -= 65536
  390. ylist.append(pos)
  391. sectortype = "new"
  392. except OSError:
  393. pass
  394. if sectortype == "":
  395. continue
  396. ylist.sort()
  397. # Make a list of pixels of the sector that are to be looked for.
  398. pixellist = []
  399. water = {}
  400. for x in range(16):
  401. for z in range(16):
  402. pixellist.append((x, z))
  403. water[(x, z)] = 0
  404. # Go through the Y axis from top to bottom.
  405. for ypos in reversed(ylist):
  406. try:
  407. #print("("+str(xpos)+","+str(ypos)+","+str(zpos)+")")
  408. yhex = int_to_hex4(ypos)
  409. if sectortype == "sqlite":
  410. ps = getBlockAsInteger((xpos, ypos, zpos))
  411. cur.execute("SELECT `data` FROM `blocks` WHERE `pos`==? LIMIT 1", (ps,))
  412. r = cur.fetchone()
  413. if not r:
  414. continue
  415. f = cStringIO.StringIO(r[0])
  416. else:
  417. if sectortype == "old":
  418. filename = path + "sectors/" + sector1 + "/" + yhex.lower()
  419. else:
  420. filename = path + "sectors2/" + sector2 + "/" + yhex.lower()
  421. f = file(filename, "rb")
  422. # Let's just memorize these even though it's not really necessary.
  423. version = readU8(f)
  424. flags = f.read(1)
  425. #print("version="+str(version))
  426. #print("flags="+str(version))
  427. # Check flags
  428. is_underground = ((ord(flags) & 1) != 0)
  429. day_night_differs = ((ord(flags) & 2) != 0)
  430. lighting_expired = ((ord(flags) & 4) != 0)
  431. generated = ((ord(flags) & 8) != 0)
  432. #print("is_underground="+str(is_underground))
  433. #print("day_night_differs="+str(day_night_differs))
  434. #print("lighting_expired="+str(lighting_expired))
  435. #print("generated="+str(generated))
  436. if version >= 22:
  437. content_width = readU8(f)
  438. params_width = readU8(f)
  439. # Node data
  440. dec_o = zlib.decompressobj()
  441. try:
  442. mapdata = array.array("B", dec_o.decompress(f.read()))
  443. except:
  444. mapdata = []
  445. # Reuse the unused tail of the file
  446. f.close();
  447. f = cStringIO.StringIO(dec_o.unused_data)
  448. #print("unused data: "+repr(dec_o.unused_data))
  449. # zlib-compressed node metadata list
  450. dec_o = zlib.decompressobj()
  451. try:
  452. metaliststr = array.array("B", dec_o.decompress(f.read()))
  453. # And do nothing with it
  454. except:
  455. metaliststr = []
  456. # Reuse the unused tail of the file
  457. f.close();
  458. f = cStringIO.StringIO(dec_o.unused_data)
  459. #print("* dec_o.unused_data: "+repr(dec_o.unused_data))
  460. data_after_node_metadata = dec_o.unused_data
  461. if version <= 21:
  462. # mapblockobject_count
  463. readU16(f)
  464. if version == 23:
  465. readU8(f) # Unused node timer version (always 0)
  466. if version == 24:
  467. ver = readU8(f)
  468. if ver == 1:
  469. num = readU16(f)
  470. for i in range(0,num):
  471. readU16(f)
  472. readS32(f)
  473. readS32(f)
  474. static_object_version = readU8(f)
  475. static_object_count = readU16(f)
  476. for i in range(0, static_object_count):
  477. # u8 type (object type-id)
  478. object_type = readU8(f)
  479. # s32 pos_x_nodes * 10000
  480. pos_x_nodes = readS32(f)/10000
  481. # s32 pos_y_nodes * 10000
  482. pos_y_nodes = readS32(f)/10000
  483. # s32 pos_z_nodes * 10000
  484. pos_z_nodes = readS32(f)/10000
  485. # u16 data_size
  486. data_size = readU16(f)
  487. # u8[data_size] data
  488. data = f.read(data_size)
  489. timestamp = readU32(f)
  490. #print("* timestamp="+str(timestamp))
  491. id_to_name = {}
  492. if version >= 22:
  493. name_id_mapping_version = readU8(f)
  494. num_name_id_mappings = readU16(f)
  495. #print("* num_name_id_mappings: "+str(num_name_id_mappings))
  496. for i in range(0, num_name_id_mappings):
  497. node_id = readU16(f)
  498. name_len = readU16(f)
  499. name = f.read(name_len)
  500. #print(str(node_id)+" = "+name)
  501. id_to_name[node_id] = name
  502. # Node timers
  503. if version >= 25:
  504. timer_size = readU8(f)
  505. num = readU16(f)
  506. for i in range(0,num):
  507. readU16(f)
  508. readS32(f)
  509. readS32(f)
  510. read_mapdata(mapdata, version, pixellist, water, day_night_differs, id_to_name)
  511. # After finding all the pixels in the sector, we can move on to
  512. # the next sector without having to continue the Y axis.
  513. if(len(pixellist) == 0):
  514. break
  515. except Exception as e:
  516. print("Error at ("+str(xpos)+","+str(ypos)+","+str(zpos)+"): "+str(e))
  517. sys.stdout.write("Block data: ")
  518. for c in r[0]:
  519. sys.stdout.write("%2.2x "%ord(c))
  520. sys.stdout.write(os.linesep)
  521. sys.stdout.write("Data after node metadata: ")
  522. for c in data_after_node_metadata:
  523. sys.stdout.write("%2.2x "%ord(c))
  524. sys.stdout.write(os.linesep)
  525. traceback.print_exc()
  526. print("Drawing image")
  527. # Drawing the picture
  528. starttime = time.time()
  529. n = 0
  530. for (x, z) in stuff.iterkeys():
  531. if n % 500000 == 0:
  532. nowtime = time.time()
  533. dtime = nowtime - starttime
  534. try:
  535. n_per_second = 1.0 * n / dtime
  536. except ZeroDivisionError:
  537. n_per_second = 0
  538. if n_per_second != 0:
  539. listlen = len(stuff)
  540. seconds_per_n = 1.0 / n_per_second
  541. time_guess = seconds_per_n * listlen
  542. remaining_s = time_guess - dtime
  543. remaining_minutes = int(remaining_s / 60)
  544. remaining_s -= remaining_minutes * 60
  545. print("Drawing pixel " + str(n) + " of " + str(listlen)
  546. + " (" + str(round(100.0 * n / listlen, 1)) + "%)"
  547. + " (ETA: " + str(remaining_minutes) + "m "
  548. + str(int(remaining_s)) + "s)")
  549. n += 1
  550. (r, g, b) = colors[stuff[(x, z)][1]]
  551. dnd = stuff[(x, z)][3] # day/night differs?
  552. if not dnd and not drawunderground:
  553. if stuff[(x, z)][2] > 0: # water
  554. (r, g, b) = colors[CONTENT_WATER]
  555. else:
  556. continue
  557. # Comparing heights of a couple of adjacent blocks and changing
  558. # brightness accordingly.
  559. try:
  560. c = stuff[(x, z)][1]
  561. c1 = stuff[(x - 1, z)][1]
  562. c2 = stuff[(x, z + 1)][1]
  563. dnd1 = stuff[(x - 1, z)][3]
  564. dnd2 = stuff[(x, z + 1)][3]
  565. if not dnd:
  566. d = -69
  567. elif not content_is_water(c1) and not content_is_water(c2) and \
  568. not content_is_water(c):
  569. y = stuff[(x, z)][0]
  570. y1 = stuff[(x - 1, z)][0] if dnd1 else y
  571. y2 = stuff[(x, z + 1)][0] if dnd2 else y
  572. d = ((y - y1) + (y - y2)) * 12
  573. else:
  574. d = 0
  575. if(d > 36):
  576. d = 36
  577. r = limit(r + d, 0, 255)
  578. g = limit(g + d, 0, 255)
  579. b = limit(b + d, 0, 255)
  580. except:
  581. pass
  582. # Water
  583. if(stuff[(x, z)][2] > 0):
  584. r = int(r * .15 + colors[2][0] * .85)
  585. g = int(g * .15 + colors[2][1] * .85)
  586. b = int(b * .15 + colors[2][2] * .85)
  587. impix[x - minx * 16 + border, h - 1 - (z - minz * 16) + border] = (r, g, b)
  588. if draworigin:
  589. draw.ellipse((minx * -16 - 5 + border, h - minz * -16 - 6 + border,
  590. minx * -16 + 5 + border, h - minz * -16 + 4 + border),
  591. outline=origincolor)
  592. font = ImageFont.load_default()
  593. if drawscale:
  594. draw.text((24, 0), "X", font=font, fill=scalecolor)
  595. draw.text((2, 24), "Z", font=font, fill=scalecolor)
  596. for n in range(int(minx / -4) * -4, maxx, 4):
  597. draw.text((minx * -16 + n * 16 + 2 + border, 0), str(n * 16),
  598. font=font, fill=scalecolor)
  599. draw.line((minx * -16 + n * 16 + border, 0,
  600. minx * -16 + n * 16 + border, border - 1), fill=scalecolor)
  601. for n in range(int(maxz / 4) * 4, minz, -4):
  602. draw.text((2, h - 1 - (n * 16 - minz * 16) + border), str(n * 16),
  603. font=font, fill=scalecolor)
  604. draw.line((0, h - 1 - (n * 16 - minz * 16) + border, border - 1,
  605. h - 1 - (n * 16 - minz * 16) + border), fill=scalecolor)
  606. if drawplayers:
  607. try:
  608. for filename in os.listdir(path + "players"):
  609. f = file(path + "players/" + filename)
  610. lines = f.readlines()
  611. name = ""
  612. position = []
  613. for line in lines:
  614. p = string.split(line)
  615. if p[0] == "name":
  616. name = p[2]
  617. print(filename + ": name = " + name)
  618. if p[0] == "position":
  619. position = string.split(p[2][1:-1], ",")
  620. print(filename + ": position = " + p[2])
  621. if len(name) > 0 and len(position) == 3:
  622. x = (int(float(position[0]) / 10 - minx * 16))
  623. z = int(h - (float(position[2]) / 10 - minz * 16))
  624. draw.ellipse((x - 2 + border, z - 2 + border,
  625. x + 2 + border, z + 2 + border), outline=playercolor)
  626. draw.text((x + 2 + border, z + 2 + border), name,
  627. font=font, fill=playercolor)
  628. f.close()
  629. except OSError:
  630. pass
  631. print("Saving")
  632. im.save(output)
  633. if unknown_node_names:
  634. sys.stdout.write("Unknown node names:")
  635. for name in unknown_node_names:
  636. sys.stdout.write(" "+name)
  637. sys.stdout.write(os.linesep)
  638. if unknown_node_ids:
  639. sys.stdout.write("Unknown node ids:")
  640. for node_id in unknown_node_ids:
  641. sys.stdout.write(" "+str(hex(node_id)))
  642. sys.stdout.write(os.linesep)