From cb363df3cd17547019a6bcb5635e11620c8cd441 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Wed, 20 Oct 2010 22:11:34 -0400 Subject: [PATCH 1/9] Initial work on generating markers.js from signposts Details: * A queue object is passed to all renderers, allowing each process to avoid using shared memory when recording signpost data. * New overviewer.dat file that stores persistent data between runs. Currently used to store information on signs. markers.js is generated by merging the stored POI list with the newly generated POI list. * POIs are tagged with their type (e.g. "spawn" or "sign"). This should be useful if different types of POIs needs to be handled/displayed differently Known bugs: * If you delete the last sign in a chunk, it won't be removed from markers.js --- chunk.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++------ quadtree.py | 22 ++++++++++++++++++++++ world.py | 38 ++++++++++++++++++++++++++++++++++--- 3 files changed, 105 insertions(+), 9 deletions(-) diff --git a/chunk.py b/chunk.py index aefcab7..270679b 100644 --- a/chunk.py +++ b/chunk.py @@ -81,6 +81,11 @@ def get_blockdata_array(level): in a similar manner to skylight data""" return numpy.frombuffer(level['Data'], dtype=numpy.uint8).reshape((16,16,64)) +def get_tileentity_data(level): + """Returns the TileEntities TAG_List from chunk dat file""" + data = level['TileEntities'] + return data + def iterate_chunkblocks(xoff,yoff): """Iterates over the 16x16x128 blocks of a chunk in rendering order. Yields (x,y,z,imgx,imgy) @@ -100,12 +105,12 @@ def iterate_chunkblocks(xoff,yoff): transparent_blocks = set([0, 6, 8, 9, 18, 20, 37, 38, 39, 40, 44, 50, 51, 52, 53, 59, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 74, 75, 76, 77, 78, 79, 81, 83, 85]) -def render_and_save(chunkfile, cachedir, worldobj, cave=False): +def render_and_save(chunkfile, cachedir, worldobj, cave=False, queue=None): """Used as the entry point for the multiprocessing workers (since processes can't target bound methods) or to easily render and save one chunk Returns the image file location""" - a = ChunkRenderer(chunkfile, cachedir, worldobj) + a = ChunkRenderer(chunkfile, cachedir, worldobj, queue) try: return a.render_and_save(cave) except ChunkCorrupt: @@ -128,21 +133,29 @@ class ChunkCorrupt(Exception): pass class ChunkRenderer(object): - def __init__(self, chunkfile, cachedir, worldobj): + def __init__(self, chunkfile, cachedir, worldobj, queue): """Make a new chunk renderer for the given chunkfile. chunkfile should be a full path to the .dat file to process cachedir is a directory to save the resulting chunk images to """ + self.queue = queue + if not os.path.exists(chunkfile): raise ValueError("Could not find chunkfile") self.chunkfile = chunkfile destdir, filename = os.path.split(self.chunkfile) + filename_split = filename.split(".") + chunkcoords = filename_split[1:3] - chunkcoords = filename.split(".")[1:3] self.coords = map(world.base36decode, chunkcoords) self.blockid = ".".join(chunkcoords) - self.world = worldobj + # chunk coordinates (useful to converting local block coords to + # global block coords) + self.chunkX = int(filename_split[1], base=36) + self.chunkY = int(filename_split[2], base=36) + + self.world = worldobj # Cachedir here is the base directory of the caches. We need to go 2 # levels deeper according to the chunk file. Get the last 2 components # of destdir and use that @@ -293,7 +306,7 @@ class ChunkRenderer(object): is up to date, this method doesn't render anything. """ blockid = self.blockid - + oldimg, oldimg_path = self.find_oldimage(cave) if oldimg: @@ -474,6 +487,8 @@ class ChunkRenderer(object): # Odd elements get the upper 4 bits blockData_expanded[:,:,1::2] = blockData >> 4 + tileEntities = get_tileentity_data(self.level) + # Each block is 24x24 # The next block on the X axis adds 12px to x and subtracts 6px from y in the image @@ -504,6 +519,33 @@ class ChunkRenderer(object): else: t = textures.blockmap[blockid] + + + # see if we want to do anything else with this chunk + if blockid in (63, 68): # signs + # find the sign text from the TileEntities list + print "Found a sign!" + for entity in tileEntities: + if entity['id'] == 'Sign': + print "adding to POI list" + # TODO assert that the x,y,z of this entity matches + # the x,y,z of this block + + # convert the blockID coordinates from local chunk + # coordinates to global world coordinates + newPOI = dict(type="sign", + x= x+(self.chunkX*16), + y= z, + z= y+(self.chunkY*16), + msg="%s\n%s\n%s\n%s" % + (entity['Text1'], entity['Text2'], entity['Text3'], entity['Text4']), + chunk= (self.chunkX, self.chunkY), + ) + print "new POI: %s" % newPOI + self.queue.put(["newpoi", newPOI]) + break + + if not t: continue diff --git a/quadtree.py b/quadtree.py index af4d15e..56811a0 100644 --- a/quadtree.py +++ b/quadtree.py @@ -25,6 +25,7 @@ import collections import json import logging import util +import cPickle from PIL import Image @@ -143,12 +144,33 @@ class QuadtreeGen(object): if not os.path.exists(tileDir): os.mkdir(tileDir) blank.save(os.path.join(tileDir, "blank."+self.imgformat)) + if skipjs: return + # since we will only discover PointsOfInterest in chunks that need to be + # [re]rendered, POIs like signs in unchanged chunks will not be listed + # in self.world.POI. To make sure we don't remove these from markers.js + # we need to merge self.world.POI with the persistant data in world.PersistentData + + # + modifiedChunks = map(lambda x: x['chunk'], filter(lambda x: x['type'] != 'spawn', self.world.POI)) + + for item in self.world.persistentData['POI']: + # if this previously discovered POI isn't in a modified chunk, keep it + if item['chunk'] not in modifiedChunks and item['type'] != 'spawn': + self.world.POI.append(item) + # else discard it, because self.world.POI will contain it (or not if it + # was deleted) + # write out the default marker table with open(os.path.join(self.destdir, "markers.js"), 'w') as output: output.write("var markerData=%s" % json.dumps(self.world.POI)) + + # save persistent data + self.world.persistentData['POI'] = self.world.POI + with open(self.world.pickleFile,"wb") as f: + cPickle.dump(self.world.persistentData,f) # write out the default (empty, but documented) region table with open(os.path.join(self.destdir, "regions.js"), 'w') as output: diff --git a/world.py b/world.py index 8ffdef4..09ed516 100644 --- a/world.py +++ b/world.py @@ -19,6 +19,7 @@ import os.path import multiprocessing import sys import logging +import cPickle import numpy @@ -105,6 +106,20 @@ class WorldRenderer(object): # a list of dictionaries, see below for an example self.POI = [] + # if it exists, open overviewer.dat, and read in the data structure + # info self.persistentData. This dictionary can hold any information + # that may be needed between runs. + # Currently only holds into about POIs (more more details, see quadtree) + self.pickleFile = os.path.join(self.cachedir,"overviewer.dat") + if os.path.exists(self.pickleFile): + with open(self.pickleFile,"rb") as p: + self.persistentData = cPickle.load(p) + else: + # some defaults + self.persistentData = dict(POI=[]) + + + def _get_chunk_renderset(self): """Returns a set of (col, row) chunks that should be rendered. Returns None if all chunks should be rendered""" @@ -180,7 +195,8 @@ class WorldRenderer(object): spawnY += 1 - self.POI.append( dict(x=spawnX, y=spawnY, z=spawnZ, msg="Spawn")) + self.POI.append( dict(x=spawnX, y=spawnY, z=spawnZ, + msg="Spawn", type="spawn", chunk=(inChunkX,inChunkZ))) def go(self, procs): """Starts the render. This returns when it is finished""" @@ -242,6 +258,9 @@ class WorldRenderer(object): inclusion_set = self._get_chunk_renderset() results = {} + manager = multiprocessing.Manager() + q = manager.Queue() + if processes == 1: # Skip the multiprocessing stuff logging.debug("Rendering chunks synchronously since you requested 1 process") @@ -254,9 +273,15 @@ class WorldRenderer(object): results[(col, row)] = imgpath continue - result = chunk.render_and_save(chunkfile, self.cachedir, self, cave=self.caves) + result = chunk.render_and_save(chunkfile, self.cachedir, self, cave=self.caves, queue=q) results[(col, row)] = result if i > 0: + try: + item = q.get(block=False) + if item[0] == "newpoi": + self.POI.append(item[1]) + except: + pass if 1000 % i == 0 or i % 1000 == 0: logging.info("{0}/{1} chunks rendered".format(i, len(chunks))) else: @@ -274,13 +299,20 @@ class WorldRenderer(object): result = pool.apply_async(chunk.render_and_save, args=(chunkfile,self.cachedir,self), - kwds=dict(cave=self.caves)) + kwds=dict(cave=self.caves, queue=q)) asyncresults.append((col, row, result)) pool.close() for i, (col, row, result) in enumerate(asyncresults): results[(col, row)] = result.get() + try: + item = q.get(block=False) + if item[0] == "newpoi": + self.POI.append(item[1]) + + except: + pass if i > 0: if 1000 % i == 0 or i % 1000 == 0: logging.info("{0}/{1} chunks rendered".format(i, len(asyncresults))) From f2b34dff7adfe8cc83ca8f671c55ddf1713340e7 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sun, 24 Oct 2010 00:19:27 -0400 Subject: [PATCH 2/9] Improved handling of signposts --- chunk.py | 50 ++++++++++++++++++++++++-------------------------- quadtree.py | 10 +--------- world.py | 9 +++++++-- 3 files changed, 32 insertions(+), 37 deletions(-) diff --git a/chunk.py b/chunk.py index 270679b..4ca0d19 100644 --- a/chunk.py +++ b/chunk.py @@ -520,32 +520,6 @@ class ChunkRenderer(object): else: t = textures.blockmap[blockid] - - # see if we want to do anything else with this chunk - if blockid in (63, 68): # signs - # find the sign text from the TileEntities list - print "Found a sign!" - for entity in tileEntities: - if entity['id'] == 'Sign': - print "adding to POI list" - # TODO assert that the x,y,z of this entity matches - # the x,y,z of this block - - # convert the blockID coordinates from local chunk - # coordinates to global world coordinates - newPOI = dict(type="sign", - x= x+(self.chunkX*16), - y= z, - z= y+(self.chunkY*16), - msg="%s\n%s\n%s\n%s" % - (entity['Text1'], entity['Text2'], entity['Text3'], entity['Text4']), - chunk= (self.chunkX, self.chunkY), - ) - print "new POI: %s" % newPOI - self.queue.put(["newpoi", newPOI]) - break - - if not t: continue @@ -644,6 +618,30 @@ class ChunkRenderer(object): if y != 0 and blocks[x,y-1,z] == 0: draw.line(((imgx,imgy+6+increment), (imgx+12,imgy+increment)), fill=(0,0,0), width=1) + + for entity in tileEntities: + if entity['id'] == 'Sign': + + # convert the blockID coordinates from local chunk + # coordinates to global world coordinates + newPOI = dict(type="sign", + x= entity['x'], + y= entity['z'], + z= entity['y'], + msg="%s\n%s\n%s\n%s" % + (entity['Text1'], entity['Text2'], entity['Text3'], entity['Text4']), + chunk= (self.chunkX, self.chunkY), + ) + self.queue.put(["newpoi", newPOI]) + + + # check to see if there are any signs in the persistentData list that are from this chunk. + # if so, remove them from the persistentData list (since they're have been added to the world.POI + # list above. + self.queue.put(['removePOI', (self.chunkX, self.chunkY)]) + + + return img # Render 3 blending masks for lighting diff --git a/quadtree.py b/quadtree.py index 56811a0..c013c62 100644 --- a/quadtree.py +++ b/quadtree.py @@ -153,15 +153,7 @@ class QuadtreeGen(object): # in self.world.POI. To make sure we don't remove these from markers.js # we need to merge self.world.POI with the persistant data in world.PersistentData - # - modifiedChunks = map(lambda x: x['chunk'], filter(lambda x: x['type'] != 'spawn', self.world.POI)) - - for item in self.world.persistentData['POI']: - # if this previously discovered POI isn't in a modified chunk, keep it - if item['chunk'] not in modifiedChunks and item['type'] != 'spawn': - self.world.POI.append(item) - # else discard it, because self.world.POI will contain it (or not if it - # was deleted) + self.world.POI += filter(lambda x: x['type'] != 'spawn', self.world.persistentData['POI']) # write out the default marker table with open(os.path.join(self.destdir, "markers.js"), 'w') as output: diff --git a/world.py b/world.py index 09ed516..925feb0 100644 --- a/world.py +++ b/world.py @@ -17,6 +17,7 @@ import functools import os import os.path import multiprocessing +import Queue import sys import logging import cPickle @@ -280,7 +281,9 @@ class WorldRenderer(object): item = q.get(block=False) if item[0] == "newpoi": self.POI.append(item[1]) - except: + elif item[0] == "removePOI": + self.persistentData['POI'] = filter(lambda x: x['chunk'] != item[1], self.persistentData['POI']) + except Queue.Empty: pass if 1000 % i == 0 or i % 1000 == 0: logging.info("{0}/{1} chunks rendered".format(i, len(chunks))) @@ -310,8 +313,10 @@ class WorldRenderer(object): item = q.get(block=False) if item[0] == "newpoi": self.POI.append(item[1]) + elif item[0] == "removePOI": + self.persistentData['POI'] = filter(lambda x: x['chunk'] != item[1], self.persistentData['POI']) - except: + except Queue.Empty: pass if i > 0: if 1000 % i == 0 or i % 1000 == 0: From 2c49113bd2d731a15a90ccdfe1e89ff7b16b9dcb Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Tue, 2 Nov 2010 19:39:22 -0400 Subject: [PATCH 3/9] Render the new blocks from the Boo update. The following are now rendered: * Pumpkins * Jack-O-Lanterns (look identical to pumpkins at the moment, to match the ingame rendering) * Bloodstone * Slow Sand * Lightstone --- textures.py | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/textures.py b/textures.py index b942168..9e675d8 100644 --- a/textures.py +++ b/textures.py @@ -267,8 +267,8 @@ def _build_blockimages(): 36, 37, 80, -1, 65, 4, 25,101, 98, 24, 43, -1, 86, 1, 1, -1, # Torch from above? leaving out fire. Redstone wire? Crops/furnaces handled elsewhere. sign post # 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 -1, -1, -1, 16, -1, -1, -1, -1, -1, 51, 51, -1, -1, 1, 66, 67, # door,ladder left out. Minecart rail orientation - # 80 81 82 83 84 - 66, 69, 72, 73, 74 # clay? + # 80 81 82 83 84 85 86 87 88 89 90 91 + 66, 69, 72, 73, 74, -1,102,103,104,105,-1, 102 # clay? ] # NOTE: For non-block textures, the sideid is ignored, but can't be -1 @@ -284,8 +284,8 @@ def _build_blockimages(): 36, 37, 80, -1, 65, 4, 25,101, 98, 24, 43, -1, 86, 44, 61, -1, # 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 -1, -1, -1, 16, -1, -1, -1, -1, -1, 51, 51, -1, -1, 1, 66, 67, - # 80 81 82 83 84 - 66, 69, 72, 73, 74 + # 80 81 82 83 84 85 86 87 88 89 90 91 + 66, 69, 72, 73, 74,-1 ,118,103,104,105, -1, 118 ] # This maps block id to the texture that goes on the side of the block @@ -392,6 +392,19 @@ def generate_special_texture(blockID, data): img.paste(side2, (12,6), side2) img.paste(top, (0,0), top) return (img.convert("RGB"), img.split()[3]) + + if blockID in (86,91): # jack-o-lantern + print "generating special for pumpkins" + top = transform_image(terrain_images[102]) + side1 = transform_image_side(terrain_images[118]) + side2 = transform_image_side(terrain_images[119]).transpose(Image.FLIP_LEFT_RIGHT) + + img = Image.new("RGBA", (24,24), (38,92,255,0)) + + img.paste(side1, (0,6), side1) + img.paste(side2, (12,6), side2) + img.paste(top, (0,0), top) + return (img.convert("RGB"), img.split()[3]) if blockID == 62: # lit furnace top = transform_image(terrain_images[1]) @@ -489,7 +502,7 @@ def generate_special_texture(blockID, data): # This set holds block ids that require special pre-computing. These are typically # things that require ancillary data to render properly (i.e. ladder plus orientation) -special_blocks = set([66,59,61,62, 65,64,71]) +special_blocks = set([66,59,61,62, 65,64,71,91,86]) # this is a map of special blockIDs to a list of all # possible values for ancillary data that it might have. @@ -501,6 +514,11 @@ special_map[62] = (0,) # burning furnace special_map[65] = (2,3,4,5) # ladder special_map[64] = range(16) # wooden door special_map[71] = range(16) # iron door +special_map[91] = range(5) # jack-o-lantern +special_map[86] = range(5) # pumpkin +# apparently pumpkins and jack-o-lanterns have ancillary data, but it's unknown +# what that data represents. For now, assume that the range for data is 0 to 5 +# like torches specialblockmap = {} From 290269f8fea445e6dfc769a59b0a0b5e5ebe33dd Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Tue, 2 Nov 2010 20:46:56 -0400 Subject: [PATCH 4/9] Corrected pumpkin rendering, Hacky tinting for grass, leaves * Pumpkins and Jack-o-lanterns now face the correct direction * Pumpkins and jack-o-lanterns now have different textures (thanks alexjurkiewicz). one is lit, the other is not * a bad looking green tint is applied to grass and to leaves Someone please make this better --- textures.py | 45 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/textures.py b/textures.py index 9e675d8..4a2ab49 100644 --- a/textures.py +++ b/textures.py @@ -394,10 +394,10 @@ def generate_special_texture(blockID, data): return (img.convert("RGB"), img.split()[3]) if blockID in (86,91): # jack-o-lantern - print "generating special for pumpkins" top = transform_image(terrain_images[102]) - side1 = transform_image_side(terrain_images[118]) - side2 = transform_image_side(terrain_images[119]).transpose(Image.FLIP_LEFT_RIGHT) + frontID = 119 if blockID == 86 else 120 + side1 = transform_image_side(terrain_images[frontID]) + side2 = transform_image_side(terrain_images[118]).transpose(Image.FLIP_LEFT_RIGHT) img = Image.new("RGBA", (24,24), (38,92,255,0)) @@ -496,13 +496,43 @@ def generate_special_texture(blockID, data): return (img.convert("RGB"), img.split()[3]) + if blockID == 2: # grass + top = transform_image(tintTexture(terrain_images[0],(0,255,0,255))) + side1 = transform_image_side(terrain_images[3]) + side2 = transform_image_side(terrain_images[3]).transpose(Image.FLIP_LEFT_RIGHT) + + img = Image.new("RGBA", (24,24), (38,92,255,0)) + + img.paste(side1, (0,6), side1) + img.paste(side2, (12,6), side2) + img.paste(top, (0,0), top) + return (img.convert("RGB"), img.split()[3]) + + if blockID == 18: # leaves + t = tintTexture(terrain_images[52], (0, 255, 0, 255)) + top = transform_image(t) + side1 = transform_image_side(t) + side2 = transform_image_side(t).transpose(Image.FLIP_LEFT_RIGHT) + + img = Image.new("RGBA", (24,24), (38,92,255,0)) + + img.paste(side1, (0,6), side1) + img.paste(side2, (12,6), side2) + img.paste(top, (0,0), top) + return (img.convert("RGB"), img.split()[3]) + return None +def tintTexture(im, c): + color_map = [] + for component in c: + color_map.extend(int(component/255.0*i) for i in range(256)) + return im.point(color_map) # This set holds block ids that require special pre-computing. These are typically # things that require ancillary data to render properly (i.e. ladder plus orientation) -special_blocks = set([66,59,61,62, 65,64,71,91,86]) +special_blocks = set([66,59,61,62, 65,64,71,91,86,2,18]) # this is a map of special blockIDs to a list of all # possible values for ancillary data that it might have. @@ -519,6 +549,13 @@ special_map[86] = range(5) # pumpkin # apparently pumpkins and jack-o-lanterns have ancillary data, but it's unknown # what that data represents. For now, assume that the range for data is 0 to 5 # like torches +special_map[2] = (0,) # grass +special_map[18] = range(16) # leaves +# grass and leaves are now graysacle in terrain.png +# we treat them as special so we can manually tint them +# it is unknown how the specific tint (biomes) is calculated + +# leaves have ancilary data, but its meaning is unknown (age perhaps?) specialblockmap = {} From 0b6461083bb3f7b5498f3c9715ce18f44be2987c Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Wed, 3 Nov 2010 22:57:47 -0400 Subject: [PATCH 5/9] Modified tintTexture() to look more like the original grass color --- textures.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/textures.py b/textures.py index 4a2ab49..1805f74 100644 --- a/textures.py +++ b/textures.py @@ -21,7 +21,7 @@ from cStringIO import StringIO import math import numpy -from PIL import Image, ImageEnhance +from PIL import Image, ImageEnhance, ImageOps import util @@ -497,7 +497,7 @@ def generate_special_texture(blockID, data): return (img.convert("RGB"), img.split()[3]) if blockID == 2: # grass - top = transform_image(tintTexture(terrain_images[0],(0,255,0,255))) + top = transform_image(tintTexture(terrain_images[0],(170,255,50))) side1 = transform_image_side(terrain_images[3]) side2 = transform_image_side(terrain_images[3]).transpose(Image.FLIP_LEFT_RIGHT) @@ -509,7 +509,7 @@ def generate_special_texture(blockID, data): return (img.convert("RGB"), img.split()[3]) if blockID == 18: # leaves - t = tintTexture(terrain_images[52], (0, 255, 0, 255)) + t = tintTexture(terrain_images[52], (170, 255, 50)) top = transform_image(t) side1 = transform_image_side(t) side2 = transform_image_side(t).transpose(Image.FLIP_LEFT_RIGHT) @@ -525,10 +525,10 @@ def generate_special_texture(blockID, data): return None def tintTexture(im, c): - color_map = [] - for component in c: - color_map.extend(int(component/255.0*i) for i in range(256)) - return im.point(color_map) + # apparently converting to grayscale drops the alpha channel? + i = ImageOps.colorize(ImageOps.grayscale(im), (0,0,0), c) + i.putalpha(im.split()[3]); # copy the alpha band back in. assuming RGBA + return i # This set holds block ids that require special pre-computing. These are typically # things that require ancillary data to render properly (i.e. ladder plus orientation) From c96b3363b2b31002f1868826ea3bb8d00e4857b9 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sat, 6 Nov 2010 15:27:52 -0400 Subject: [PATCH 6/9] Fixed bug in signpost handling code Two of the coordinates were flipped in markers.js Also, overviewer.dat is now removed with -d --- chunk.py | 4 ++-- gmap.py | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/chunk.py b/chunk.py index a2c90dd..454a7de 100644 --- a/chunk.py +++ b/chunk.py @@ -626,8 +626,8 @@ class ChunkRenderer(object): # coordinates to global world coordinates newPOI = dict(type="sign", x= entity['x'], - y= entity['z'], - z= entity['y'], + y= entity['y'], + z= entity['z'], msg="%s\n%s\n%s\n%s" % (entity['Text1'], entity['Text2'], entity['Text3'], entity['Text4']), chunk= (self.chunkX, self.chunkY), diff --git a/gmap.py b/gmap.py index 1021c43..43f37b0 100755 --- a/gmap.py +++ b/gmap.py @@ -145,6 +145,12 @@ def delete_all(worlddir, tiledir): logging.info("Deleting {0}".format(filepath)) os.unlink(filepath) + # delete the overviewer.dat persistant data file + datfile = os.path.join(worlddir,"overviewer.dat") + if os.path.exists(datfile): + os.unlink(datfile) + logging.info("Deleting {0}".format(datfile)) + def list_worlds(): "Prints out a brief summary of saves found in the default directory" print From 57a9e8123dc4e8a309a602ca37a1917f427f6162 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sat, 6 Nov 2010 15:29:04 -0400 Subject: [PATCH 7/9] New findSigns.py contrib script This can be used to create an complete overviewer.dat file without having to re-render your entire world. See the top of the script for usage details --- contrib/findSigns.py | 64 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 contrib/findSigns.py diff --git a/contrib/findSigns.py b/contrib/findSigns.py new file mode 100644 index 0000000..f68411e --- /dev/null +++ b/contrib/findSigns.py @@ -0,0 +1,64 @@ +#!/usr/bin/python + +''' +This script will scan through every chunk looking for signs and write out an +updated overviewer.dat file. This can be useful if your overviewer.dat file +is either out-of-date or non-existant. + +To run, simply give a path to your world directory, for example: + + python contrib/findSigns.py ../world.test/ + +Once that is done, simply re-run the overviewer to generate markers.js: + + python gmap.py ../world.test/ output_dir/ + +Note: if your cachedir is not the same as your world-dir, you'll need to manually +move overviewer.dat into the correct location. + +''' +import sys +import re +import os +import cPickle + +sys.path.append(".") +import nbt + +from pprint import pprint + +worlddir = sys.argv[1] +if os.path.exists(worlddir): + print "Scanning chunks in ", worlddir +else: + sys.exit("Bad WorldDir") + +matcher = re.compile(r"^c\..*\.dat$") + +POI = [] + +for dirpath, dirnames, filenames in os.walk(worlddir): + for f in filenames: + if matcher.match(f): + full = os.path.join(dirpath, f) + #print "inspecting %s" % full + data = nbt.load(full)[1]['Level']['TileEntities'] + for entity in data: + if entity['id'] == 'Sign': + newPOI = dict(type="sign", + x= entity['x'], + y= entity['y'], + z= entity['z'], + msg="%s\n%s\n%s\n%s" % + (entity['Text1'], entity['Text2'], entity['Text3'], entity['Text4']), + chunk= (entity['x']/16, entity['z']/16), + ) + POI.append(newPOI) + print "Found sign at (%d, %d, %d): %r" % (newPOI['x'], newPOI['y'], newPOI['z'], newPOI['msg']) + + + +pickleFile = os.path.join(worlddir,"overviewer.dat") +with open(pickleFile,"wb") as f: + cPickle.dump(dict(POI=POI), f) + From 16aca4c093756a5e5df3822eaeff2664c7e3d482 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sat, 6 Nov 2010 22:04:38 -0400 Subject: [PATCH 8/9] Fancy icons for signposts and spawn, plus info windows for signposts Thanks to gabrielcrowe for the signpost icon. --- quadtree.py | 4 ++++ template.html | 37 ++++++++++++++++++++++++++------- web_assets/signpost-shadow.png | Bin 0 -> 368 bytes web_assets/signpost.png | Bin 0 -> 708 bytes web_assets/signpost_icon.png | Bin 0 -> 253 bytes web_assets/style.css | 18 ++++++++++++++++ 6 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 web_assets/signpost-shadow.png create mode 100644 web_assets/signpost.png create mode 100644 web_assets/signpost_icon.png create mode 100644 web_assets/style.css diff --git a/quadtree.py b/quadtree.py index c013c62..39176bb 100644 --- a/quadtree.py +++ b/quadtree.py @@ -144,6 +144,10 @@ class QuadtreeGen(object): if not os.path.exists(tileDir): os.mkdir(tileDir) blank.save(os.path.join(tileDir, "blank."+self.imgformat)) + # copy web assets into destdir: + for root, dirs, files in os.walk(os.path.join(util.get_program_path(), "web_assets")): + for f in files: + shutil.copy(os.path.join(root, f), self.destdir) if skipjs: return diff --git a/template.html b/template.html index 96ccae2..42d925a 100644 --- a/template.html +++ b/template.html @@ -2,11 +2,7 @@ - + diff --git a/web_assets/compass.png b/web_assets/compass.png new file mode 100644 index 0000000000000000000000000000000000000000..4b0c25541a82502f4b0036544d15361d3e7a4ff4 GIT binary patch literal 7403 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 KKbLh*2~7Y?H&tW+ literal 0 HcmV?d00001