From 6b77d545557b65d47cb9b8ba6bae471ae8e02d3e Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sat, 3 Mar 2012 23:42:05 -0500 Subject: [PATCH 1/7] Reimplement signs/POIs --- genPOI.py | 132 +++++++++++++++++ overviewer_core/assetmanager.py | 18 +-- overviewer_core/data/js_src/overviewer.js | 9 +- overviewer_core/data/js_src/util.js | 36 ++++- overviewer_core/data/js_src/views.js | 156 +++++++++++++++++++++ overviewer_core/data/web_assets/index.html | 1 + overviewer_core/settingsDefinition.py | 1 + overviewer_core/settingsValidators.py | 7 + 8 files changed, 346 insertions(+), 14 deletions(-) create mode 100755 genPOI.py diff --git a/genPOI.py b/genPOI.py new file mode 100755 index 0000000..6bb0123 --- /dev/null +++ b/genPOI.py @@ -0,0 +1,132 @@ +#!/usr/bin/python2 + +''' +genPOI.py + +Scans regionsets for TileEntities and Entities, filters them, and writes out +POI/marker info. + +A markerSet is list of POIs to display on a tileset. It has a display name, +and a group name. + +markersDB.js holds a list of POIs in each group +markers.js holds a list of which markerSets are attached to each tileSet + + +''' +import os +import logging +import json +from optparse import OptionParser + +from overviewer_core import logger +from overviewer_core import nbt +from overviewer_core import configParser, world + +helptext = """ +%prog --config=""" + +logger.configure() + +def handleSigns(rset, outputdir, render, rname): + + if hasattr(rset, "_pois"): + return + + logging.info("Looking for entities in %r", rset) + + filters = render['markers'] + rset._pois = dict(TileEntities=[], Entities=[]) + + for (x,z,mtime) in rset.iterate_chunks(): + data = rset.get_chunk(x,z) + rset._pois['TileEntities'] += data['TileEntities'] + rset._pois['Entities'] += data['Entities'] + + +def main(): + parser = OptionParser(usage=helptext) + parser.add_option("--config", dest="config", action="store", help="Specify the config file to use.") + + options, args = parser.parse_args() + if not options.config: + parser.print_help() + return + + # Parse the config file + mw_parser = configParser.MultiWorldParser() + mw_parser.parse(options.config) + try: + config = mw_parser.get_validated_config() + except Exception: + logging.exception("An error was encountered with your configuration. See the info below.") + return 1 + + destdir = config['outputdir'] + # saves us from creating the same World object over and over again + worldcache = {} + + markersets = set() + markers = dict() + + for rname, render in config['renders'].iteritems(): + try: + worldpath = config['worlds'][render['world']] + except KeyError: + logging.error("Render %s's world is '%s', but I could not find a corresponding entry in the worlds dictionary.", + rname, render['world']) + return 1 + render['worldname_orig'] = render['world'] + render['world'] = worldpath + + # find or create the world object + if (render['world'] not in worldcache): + w = world.World(render['world']) + worldcache[render['world']] = w + else: + w = worldcache[render['world']] + + rset = w.get_regionset(render['dimension']) + if rset == None: # indicates no such dimension was found: + logging.error("Sorry, you requested dimension '%s' for %s, but I couldn't find it", render['dimension'], render_name) + return 1 + + for f in render['markers']: + markersets.add((f, rset)) + name = f.__name__ + hex(hash(f))[-4:] + "_" + hex(hash(rset))[-4:] + try: + l = markers[rname] + l.append(dict(groupName=name, displayName = f.__doc__)) + except KeyError: + markers[rname] = [dict(groupName=name, displayName=f.__doc__),] + + handleSigns(rset, os.path.join(destdir, rname), render, rname) + + logging.info("Done scanning regions") + logging.info("Writing out javascript files") + markerSetDict = dict() + for (flter, rset) in markersets: + # generate a unique name for this markerset. it will not be user visible + name = flter.__name__ + hex(hash(flter))[-4:] + "_" + hex(hash(rset))[-4:] + markerSetDict[name] = dict(created=False, raw=[]) + for poi in rset._pois['TileEntities']: + if flter(poi): + markerSetDict[name]['raw'].append(poi) + #print markerSetDict + + with open(os.path.join(destdir, "markersDB.js"), "w") as output: + output.write("var markersDB=") + json.dump(markerSetDict, output, indent=2) + output.write(";\n"); + with open(os.path.join(destdir, "markers.js"), "w") as output: + output.write("var markers=") + json.dump(markers, output, indent=2) + output.write(";\n"); + with open(os.path.join(destdir, "baseMarkers.js"), "w") as output: + output.write("overviewer.util.injectMarkerScript('markersDB.js');\n") + output.write("overviewer.util.injectMarkerScript('markers.js');\n") + output.write("overviewer.collections.haveSigns=true;\n") + logging.info("Done") + +if __name__ == "__main__": + main() diff --git a/overviewer_core/assetmanager.py b/overviewer_core/assetmanager.py index 131d986..6407c1d 100644 --- a/overviewer_core/assetmanager.py +++ b/overviewer_core/assetmanager.py @@ -42,11 +42,6 @@ directory. self.outputdir = outputdir self.renders = dict() - # stores Points Of Interest to be mapped with markers - # This is a dictionary of lists of dictionaries - # Each regionset's name is a key in this dictionary - self.POI = dict() - # look for overviewerConfig in self.outputdir try: with open(os.path.join(self.outputdir, "overviewerConfig.js")) as c: @@ -65,13 +60,6 @@ directory. return dict() - - def found_poi(self, regionset, poi_type, contents, chunkX, chunkY): - if regionset.name not in self.POI.keys(): - POI[regionset.name] = [] - # TODO based on the type, so something - POI[regionset.name].append - def initialize(self, tilesets): """Similar to finalize() but calls the tilesets' get_initial_data() instead of get_persistent_data() to compile the generated javascript @@ -152,6 +140,12 @@ directory. global_assets = os.path.join(util.get_program_path(), "web_assets") mirror_dir(global_assets, self.outputdir) + # write a dummy baseMarkers.js if none exists + if not os.path.exists(os.path.join(self.outputdir, "baseMarkers.js")): + with open(os.path.join(self.outputdir, "baseMarkers.js"), "w") as f: + f.write("// if you wants signs, please see genPOI.py\n"); + + # create overviewer.js from the source js files js_src = os.path.join(util.get_program_path(), "overviewer_core", "data", "js_src") if not os.path.isdir(js_src): diff --git a/overviewer_core/data/js_src/overviewer.js b/overviewer_core/data/js_src/overviewer.js index 2f9d651..a8ad80b 100644 --- a/overviewer_core/data/js_src/overviewer.js +++ b/overviewer_core/data/js_src/overviewer.js @@ -29,7 +29,14 @@ overviewer.collections = { */ 'infoWindow': null, - 'worldViews': [] + 'worldViews': [], + + 'haveSigns': false, + + /** + * Hold the raw marker data for each tilest + */ + 'markerInfo': {} }; overviewer.classes = { diff --git a/overviewer_core/data/js_src/util.js b/overviewer_core/data/js_src/util.js index 2daa770..143ba5f 100644 --- a/overviewer_core/data/js_src/util.js +++ b/overviewer_core/data/js_src/util.js @@ -58,6 +58,11 @@ overviewer.util = { var coordsdiv = new overviewer.views.CoordboxView({tagName: 'DIV'}); coordsdiv.render(); + if (overviewer.collections.haveSigns) { + var signs = new overviewer.views.SignControlView(); + signs.registerEvents(signs); + } + // Update coords on mousemove google.maps.event.addListener(overviewer.map, 'mousemove', function (event) { coordsdiv.updateCoords(event.latLng); @@ -102,6 +107,7 @@ overviewer.util = { overviewer.map.setZoom(zoom); } + }); var worldSelector = new overviewer.views.WorldSelectorView({tagName:'DIV'}); @@ -116,15 +122,43 @@ overviewer.util = { // Jump to the hash if given overviewer.util.initHash(); + overviewer.util.initializeMarkers(); /* overviewer.util.initializeMapTypes(); overviewer.util.initializeMap(); - overviewer.util.initializeMarkers(); overviewer.util.initializeRegions(); overviewer.util.createMapControls(); */ }, + + 'injectMarkerScript': function(url) { + var m = document.createElement('script'); m.type = 'text/javascript'; m.async = false; + m.src = url; + var s = document.getElementsByTagName('script')[0]; s.parentNode.appendChild(m); + }, + + 'initializeMarkers': function() { + return; + + }, + + 'createMarkerInfoWindow': function(marker) { + var windowContent = '

' + marker.title.replace(/\n/g,'
') + '

'; + var infowindow = new google.maps.InfoWindow({ + 'content': windowContent + }); + google.maps.event.addListener(marker, 'click', function() { + if (overviewer.collections.infoWindow) { + overviewer.collections.infoWindow.close(); + } + infowindow.open(overviewer.map, marker); + overviewer.collections.infoWindow = infowindow; + }); + }, + + /** * This adds some methods to these classes because Javascript is stupid * and this seems like the best way to avoid re-creating the same methods diff --git a/overviewer_core/data/js_src/views.js b/overviewer_core/data/js_src/views.js index 70867e0..7a1921e 100644 --- a/overviewer_core/data/js_src/views.js +++ b/overviewer_core/data/js_src/views.js @@ -219,3 +219,159 @@ overviewer.views.GoogleMapView = Backbone.View.extend({ }); + + +/** + * SignControlView + */ +overviewer.views.SignControlView = Backbone.View.extend({ + /** SignControlView::initialize + */ + initialize: function(opts) { + $(this.el).addClass("customControl"); + overviewer.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(this.el); + + }, + registerEvents: function(me) { + google.maps.event.addListener(overviewer.map, 'maptypeid_changed', function(event) { + overviewer.mapView.updateCurrentTileset(); + me.render(); + // hide markers, if necessary + // for each markerSet, check: + // if the markerSet isnot part of this tileset, hide all of the markers + var curMarkerSet = overviewer.mapView.options.currentTileSet.attributes.path; + console.log("sign control things %r is the new current tileset", curMarkerSet); + var dataRoot = markers[curMarkerSet]; + var groupsForThisTileSet = jQuery.map(dataRoot, function(elem, i) { return elem.groupName;}) + for (markerSet in markersDB) { + console.log("checking to see if markerset %r should be hidden (is it not in %r)", markerSet, groupsForThisTileSet); + if (jQuery.inArray(markerSet, groupsForThisTileSet) == -1){ + // hide these + console.log("nope, going to hide these"); + if (markersDB[markerSet].created) { + jQuery.each(markersDB[markerSet].raw, function(i, elem) { + //console.log("attempting to set %r to visible(%r)", elem.markerObj, checked); + elem.markerObj.setVisible(false); + }); + } + } else { + //make sure the checkbox is checked + //TODO fix this + //console.log("trying to checkbox for " + markerSet); + //console.log($("[_mc_groupname=" + markerSet + "]")); + } + + } + + }); + + }, + /** + * SignControlView::render + */ + render: function() { + + var curMarkerSet = overviewer.mapView.options.currentTileSet.attributes.path; + console.log(curMarkerSet); + //var dataRoot = overviewer.collections.markerInfo[curMarkerSet]; + var dataRoot = markers[curMarkerSet]; + + console.log(dataRoot); + + // before re-building this control, we need to hide all currently displayed signs + // TODO + + this.el.innerHTML="" + + var controlText = document.createElement('DIV'); + controlText.innerHTML = "Signs"; + + var controlBorder = document.createElement('DIV'); + $(controlBorder).addClass('top'); + this.el.appendChild(controlBorder); + controlBorder.appendChild(controlText); + + var dropdownDiv = document.createElement('DIV'); + $(dropdownDiv).addClass('dropDown'); + this.el.appendChild(dropdownDiv); + dropdownDiv.innerHTML=''; + + // add the functionality to toggle visibility of the items + $(controlText).click(function() { + $(controlBorder).toggleClass('top-active'); + $(dropdownDiv).toggle(); + }); + + + // add some menus + for (i in dataRoot) { + var group = dataRoot[i]; + console.log(group); + this.addItem({label: group.displayName, groupName:group.groupName, action:function(this_item, checked) { + console.log("%r is %r", this_item, checked); + console.log("group name is %r", this_item.groupName); + jQuery.each(markersDB[this_item.groupName].raw, function(i, elem) { + //console.log("attempting to set %r to visible(%r)", elem.markerObj, checked); + elem.markerObj.setVisible(checked); + }); + }}); + } + + iconURL = overviewerConfig.CONST.image.signMarker; + //dataRoot['markers'] = []; + // + for (i in dataRoot) { + var groupName = dataRoot[i].groupName; + if (!markersDB[groupName].created) { + for (j in markersDB[groupName].raw) { + var entity = markersDB[groupName].raw[j]; + var marker = new google.maps.Marker({ + 'position': overviewer.util.fromWorldToLatLng(entity.x, + entity.y, entity.z, overviewer.mapView.options.currentTileSet), + 'map': overviewer.map, + 'title': jQuery.trim(entity.Text1 + "\n" + entity.Text2 + "\n" + entity.Text3 + "\n" + entity.Text4), + 'icon': iconURL, + 'visible': false + }); + if (entity['id'] == 'Sign') { + overviewer.util.createMarkerInfoWindow(marker); + } + console.log("Added marker for %r", entity); + jQuery.extend(entity, {markerObj: marker}); + } + markersDB[groupName].created = true; + } + } + + + }, + addItem: function(item) { + var itemDiv = document.createElement('div'); + var itemInput = document.createElement('input'); + itemInput.type='checkbox'; + + // give it a name + $(itemInput).data('label',item.label); + $(itemInput).attr("_mc_groupname", item.groupName); + jQuery(itemInput).click((function(local_item) { + return function(e) { + item.action(local_item, e.target.checked); + }; + })(item)); + + this.$(".dropDown")[0].appendChild(itemDiv); + itemDiv.appendChild(itemInput); + var textNode = document.createElement('text'); + if(item.icon) { + textNode.innerHTML = '' + item.label + '
'; + } else { + textNode.innerHTML = item.label + '
'; + } + + itemDiv.appendChild(textNode); + + + }, +}); + diff --git a/overviewer_core/data/web_assets/index.html b/overviewer_core/data/web_assets/index.html index 0766727..e8010a0 100644 --- a/overviewer_core/data/web_assets/index.html +++ b/overviewer_core/data/web_assets/index.html @@ -17,6 +17,7 @@ + diff --git a/overviewer_core/settingsDefinition.py b/overviewer_core/settingsDefinition.py index 67fee81..5773dd2 100644 --- a/overviewer_core/settingsDefinition.py +++ b/overviewer_core/settingsDefinition.py @@ -75,6 +75,7 @@ renders = Setting(required=True, default=util.OrderedDict(), "rerenderprob": Setting(required=True, validator=validateFloat, default=0), "crop": Setting(required=False, validator=validateCrop, default=None), "changelist": Setting(required=False, validator=validateStr, default=None), + "markers": Setting(required=False, validator=validateMarkers, default=[]), # Remove this eventually (once people update their configs) "worldname": Setting(required=False, default=None, diff --git a/overviewer_core/settingsValidators.py b/overviewer_core/settingsValidators.py index f6fdd77..8aedc30 100644 --- a/overviewer_core/settingsValidators.py +++ b/overviewer_core/settingsValidators.py @@ -43,6 +43,13 @@ def checkBadEscape(s): fixed = True return (fixed, fixed_string) +def validateMarkers(filterlist): + if type(filterlist) != list: + raise ValidationException("Markers must specify a list of filters") + for x in filterlist: + if not callable(x): + raise ValidationException("%r must be a function"% x) + return filterlist def validateWorldPath(worldpath): _, worldpath = checkBadEscape(worldpath) From bfeae91e7834066d4c1a1f88e4a9f1332304d2a6 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sun, 11 Mar 2012 13:10:50 -0400 Subject: [PATCH 2/7] New docs on signs/markers --- docs/config.rst | 15 ++++++++++ docs/index.rst | 1 + docs/signs.rst | 75 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 docs/signs.rst diff --git a/docs/config.rst b/docs/config.rst index cac1d81..1affc01 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -443,6 +443,21 @@ values. The valid configuration keys are listed below. removed some tiles, you may need to do some manual deletion on the remote side. +.. _option_markers: + +``markers`` + This controls the display of markers, signs, and other points of interest + in the output HTML. It should be a list of filter functions. + + .. note:: + + Setting this configuration option alone does nothing. In order to get + markers and signs on our map, you must also run the genPO script. See + the :doc:`Signs and markers` section for more details and documenation. + + + **Default:** ``[]`` (an empty list) + .. _customrendermodes: Custom Rendermodes and Rendermode Primitives diff --git a/docs/index.rst b/docs/index.rst index 2447f22..5bbac6d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -181,6 +181,7 @@ Documentation Contents building running config + signs win_tut/windowsguide faq design/designdoc diff --git a/docs/signs.rst b/docs/signs.rst new file mode 100644 index 0000000..1a2177f --- /dev/null +++ b/docs/signs.rst @@ -0,0 +1,75 @@ +.. _signsmarkers: + +================= +Signs and Markers +================= + +The Overviewer can display signs, markers, and other points of interest on your +map. This works a little differently than it has in the past, so be sure to read +these docs carefully. + +In these docs, we use the term POI (or point of interest) to refer to entities and +tileentities. + + +Configuration File +================== + + +Filter Functions +---------------- + +A filter function is a python function that is used to figure out if a given POI +should be part of a markerSet of not. The function should accept one argument +(a dictionary, also know as an associative array), and return a boolean:: + + def signFilter(poi): + "All signs" + return poi['id'] == 'Sign' + +The single argument will either a TileEntity, or an Entity taken directly from +the chunk file. In this example, this function returns true only if the type +of entity is a sign. For more information of TileEntities and Entities, see +the `Chunk Format `_ page on +the Minecraft Wiki. + +.. note:: + The doc string ("All signs" in this example) is important. It is the label + that appears in your rendered map + +A more advanced filter may also look at other entity fields, such as the sign text:: + + def goldFilter(poi): + "Gold" + return poi['id'] == 'Sign' and (\ + 'gold' in poi['Text1] or + 'gold' in poi['Text2']) + +This looks for the word 'gold' in either the first or second line of the signtext. + +Since writing these filters can be a little tedious, a set of predefined filters +functions are provided. See the :ref:`predefined_filter_functions` section for +details. + +Render Dictionary Key +--------------------- + +Each render can specify a list of zero or more filter functions. Each of these +filter functions become a selectable item in the 'Signs' drop-down menu in the +rendered map. For example:: + + renders['myrender'] = { + 'world': 'myworld', + 'title': "Example", + 'markers': [allFilter, anotherFilter], + } + + + +.. _predefined_filter_functions: + +Predefined Filter Functions +=========================== + +TODO write some filter functions, then document them here + From aba0449477ec4f274a5810bfea202045a42efac9 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sun, 11 Mar 2012 13:30:49 -0400 Subject: [PATCH 3/7] Hide the 'Signs' control if the tileset has no signs --- overviewer_core/data/js_src/views.js | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/overviewer_core/data/js_src/views.js b/overviewer_core/data/js_src/views.js index 7a1921e..c5bd291 100644 --- a/overviewer_core/data/js_src/views.js +++ b/overviewer_core/data/js_src/views.js @@ -242,6 +242,18 @@ overviewer.views.SignControlView = Backbone.View.extend({ var curMarkerSet = overviewer.mapView.options.currentTileSet.attributes.path; console.log("sign control things %r is the new current tileset", curMarkerSet); var dataRoot = markers[curMarkerSet]; + if (!dataRoot) { + // this tileset has no signs, so hide all of them + for (markerSet in markersDB) { + if (markersDB[markerSet].created) { + jQuery.each(markersDB[markerSet].raw, function(i, elem) { + elem.markerObj.setVisible(false); + }); + } + } + + return; + } var groupsForThisTileSet = jQuery.map(dataRoot, function(elem, i) { return elem.groupName;}) for (markerSet in markersDB) { console.log("checking to see if markerset %r should be hidden (is it not in %r)", markerSet, groupsForThisTileSet); @@ -278,10 +290,11 @@ overviewer.views.SignControlView = Backbone.View.extend({ console.log(dataRoot); - // before re-building this control, we need to hide all currently displayed signs - // TODO - this.el.innerHTML="" + + // if we have no markerSets for this tileset, do nothing: + if (!dataRoot) { return; } + var controlText = document.createElement('DIV'); controlText.innerHTML = "Signs"; From 5b168ebdbdfac9d3363677dd6c90cb962ffb6346 Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sun, 11 Mar 2012 13:33:02 -0400 Subject: [PATCH 4/7] Removed console debugging output --- overviewer_core/data/js_src/util.js | 2 -- overviewer_core/data/js_src/views.js | 31 ---------------------------- 2 files changed, 33 deletions(-) diff --git a/overviewer_core/data/js_src/util.js b/overviewer_core/data/js_src/util.js index 143ba5f..0a1655f 100644 --- a/overviewer_core/data/js_src/util.js +++ b/overviewer_core/data/js_src/util.js @@ -273,7 +273,6 @@ overviewer.util = { var zoomLevels = model.get("zoomLevels"); var north_direction = model.get('north_direction'); - //console.log("fromWorldToLatLng: north_direction is %r", north_direction); // the width and height of all the highest-zoom tiles combined, // inverted @@ -440,7 +439,6 @@ overviewer.util = { // save this info is a nice easy to parse format var currentWorldView = overviewer.mapModel.get("currentWorldView"); currentWorldView.options.lastViewport = [x,y,z,zoom]; - //console.log("Updated lastViewport: %r" , [x,y,z,zoom]); window.location.replace("#/" + Math.floor(x) + "/" + Math.floor(y) + "/" + Math.floor(z) + "/" + zoom + "/" + w + "/" + maptype); }, 'updateHash': function() { diff --git a/overviewer_core/data/js_src/views.js b/overviewer_core/data/js_src/views.js index c5bd291..28ba007 100644 --- a/overviewer_core/data/js_src/views.js +++ b/overviewer_core/data/js_src/views.js @@ -3,13 +3,9 @@ overviewer.views= {} overviewer.views.WorldView = Backbone.View.extend({ initialize: function(opts) { - //console.log("WorldView::initialize()"); - //console.log(this.model.get("tileSets")); this.options.mapTypes = []; this.options.mapTypeIds = []; this.model.get("tileSets").each(function(tset, index, list) { - //console.log(" eaching"); - //console.log(" Working on tileset %s" , tset.get("name")); var ops = { getTileUrl: overviewer.gmap.getTileUrlGenerator(tset.get("path"), tset.get("base"), tset.get("imgextension")), 'tileSize': new google.maps.Size( @@ -57,7 +53,6 @@ overviewer.views.WorldSelectorView = Backbone.View.extend({ "change select": "changeWorld" }, changeWorld: function() { - //console.log("change world!"); var selectObj = this.$("select")[0]; var selectedOption = selectObj.options[selectObj.selectedIndex]; @@ -120,11 +115,8 @@ overviewer.views.CoordboxView = Backbone.View.extend({ overviewer.views.GoogleMapView = Backbone.View.extend({ initialize: function(opts) { - //console.log(this); this.options.map = null; var curWorld = this.model.get("currentWorldView").model; - //console.log("Current world:"); - //console.log(curWorld); var curTset = curWorld.get("tileSets").at(0); @@ -145,12 +137,6 @@ overviewer.views.GoogleMapView = Backbone.View.extend({ var mapOptions = {}; // - curWorld.get("tileSets").each(function(elem, index, list) { - //console.log("Setting up map for:"); - //console.log(elem); - //console.log("for %s generating url func with %s and %s", elem.get("name"), elem.get("path"), elem.get("base")); - - }); // init the map with some default options. use the first tileset in the first world this.options.mapOptions = { zoom: curTset.get("defaultZoom"), @@ -174,7 +160,6 @@ overviewer.views.GoogleMapView = Backbone.View.extend({ // register every ImageMapType with the map $.each(overviewer.collections.worldViews, function( index, worldView) { $.each(worldView.options.mapTypes, function(i_index, maptype) { - //console.log("registered %s with the maptype registery", worldView.model.get("name") + maptype.shortname); overviewer.map.mapTypes.set(overviewerConfig.CONST.mapDivId + worldView.model.get("name") + maptype.shortname , maptype); }); @@ -185,7 +170,6 @@ overviewer.views.GoogleMapView = Backbone.View.extend({ * Should be called when the current world has changed in GoogleMapModel */ render: function() { - //console.log("GoogleMapView::render()"); var view = this.model.get("currentWorldView"); this.options.mapOptions.mapTypeControlOptions = { mapTypeIds: view.options.mapTypeIds}; @@ -200,14 +184,11 @@ overviewer.views.GoogleMapView = Backbone.View.extend({ * Keeps track of the currently visible tileset */ updateCurrentTileset: function() { - //console.log("GoogleMapView::updateCurrentTileset()"); var currentWorldView = this.model.get("currentWorldView"); var gmapCurrent = overviewer.map.getMapTypeId(); for (id in currentWorldView.options.mapTypeIds) { if (currentWorldView.options.mapTypeIds[id] == gmapCurrent) { - //console.log("updating currenttileset"); this.options.currentTileSet = currentWorldView.model.get("tileSets").at(id); - //console.log(this); } } @@ -240,7 +221,6 @@ overviewer.views.SignControlView = Backbone.View.extend({ // for each markerSet, check: // if the markerSet isnot part of this tileset, hide all of the markers var curMarkerSet = overviewer.mapView.options.currentTileSet.attributes.path; - console.log("sign control things %r is the new current tileset", curMarkerSet); var dataRoot = markers[curMarkerSet]; if (!dataRoot) { // this tileset has no signs, so hide all of them @@ -256,13 +236,10 @@ overviewer.views.SignControlView = Backbone.View.extend({ } var groupsForThisTileSet = jQuery.map(dataRoot, function(elem, i) { return elem.groupName;}) for (markerSet in markersDB) { - console.log("checking to see if markerset %r should be hidden (is it not in %r)", markerSet, groupsForThisTileSet); if (jQuery.inArray(markerSet, groupsForThisTileSet) == -1){ // hide these - console.log("nope, going to hide these"); if (markersDB[markerSet].created) { jQuery.each(markersDB[markerSet].raw, function(i, elem) { - //console.log("attempting to set %r to visible(%r)", elem.markerObj, checked); elem.markerObj.setVisible(false); }); } @@ -284,12 +261,9 @@ overviewer.views.SignControlView = Backbone.View.extend({ render: function() { var curMarkerSet = overviewer.mapView.options.currentTileSet.attributes.path; - console.log(curMarkerSet); //var dataRoot = overviewer.collections.markerInfo[curMarkerSet]; var dataRoot = markers[curMarkerSet]; - console.log(dataRoot); - this.el.innerHTML="" // if we have no markerSets for this tileset, do nothing: @@ -319,12 +293,8 @@ overviewer.views.SignControlView = Backbone.View.extend({ // add some menus for (i in dataRoot) { var group = dataRoot[i]; - console.log(group); this.addItem({label: group.displayName, groupName:group.groupName, action:function(this_item, checked) { - console.log("%r is %r", this_item, checked); - console.log("group name is %r", this_item.groupName); jQuery.each(markersDB[this_item.groupName].raw, function(i, elem) { - //console.log("attempting to set %r to visible(%r)", elem.markerObj, checked); elem.markerObj.setVisible(checked); }); }}); @@ -349,7 +319,6 @@ overviewer.views.SignControlView = Backbone.View.extend({ if (entity['id'] == 'Sign') { overviewer.util.createMarkerInfoWindow(marker); } - console.log("Added marker for %r", entity); jQuery.extend(entity, {markerObj: marker}); } markersDB[groupName].created = true; From a757be510ab101d9311de7f502f4e366144f564d Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sun, 11 Mar 2012 13:48:54 -0400 Subject: [PATCH 5/7] Work around IE issue. This work around has the problem where the first tileset you view won't have the 'signs' control --- overviewer_core/data/js_src/views.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/overviewer_core/data/js_src/views.js b/overviewer_core/data/js_src/views.js index 28ba007..ea6a46c 100644 --- a/overviewer_core/data/js_src/views.js +++ b/overviewer_core/data/js_src/views.js @@ -216,6 +216,9 @@ overviewer.views.SignControlView = Backbone.View.extend({ registerEvents: function(me) { google.maps.event.addListener(overviewer.map, 'maptypeid_changed', function(event) { overviewer.mapView.updateCurrentTileset(); + + // workaround IE issue. bah! + if (typeof markers=="undefined") { return; } me.render(); // hide markers, if necessary // for each markerSet, check: From 9d97ff4e374b8978a94b8f63c4e0b7a638f72acf Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sun, 11 Mar 2012 14:17:04 -0400 Subject: [PATCH 6/7] Ensure sign checkboxes remain checked across tileset changes --- overviewer_core/data/js_src/views.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/overviewer_core/data/js_src/views.js b/overviewer_core/data/js_src/views.js index ea6a46c..b4ffbb9 100644 --- a/overviewer_core/data/js_src/views.js +++ b/overviewer_core/data/js_src/views.js @@ -246,12 +246,10 @@ overviewer.views.SignControlView = Backbone.View.extend({ elem.markerObj.setVisible(false); }); } - } else { - //make sure the checkbox is checked - //TODO fix this - //console.log("trying to checkbox for " + markerSet); - //console.log($("[_mc_groupname=" + markerSet + "]")); + markersDB[markerSet].checked=false; } + // make sure the checkboxes checked if necessary + $("[_mc_groupname=" + markerSet + "]").attr("checked", markersDB[markerSet].checked); } @@ -297,6 +295,7 @@ overviewer.views.SignControlView = Backbone.View.extend({ for (i in dataRoot) { var group = dataRoot[i]; this.addItem({label: group.displayName, groupName:group.groupName, action:function(this_item, checked) { + markersDB[this_item.groupName].checked = checked; jQuery.each(markersDB[this_item.groupName].raw, function(i, elem) { elem.markerObj.setVisible(checked); }); From 729fcda9643254e06e5994ede9b64b65e8f559fb Mon Sep 17 00:00:00 2001 From: Andrew Chin Date: Sun, 11 Mar 2012 17:59:29 -0400 Subject: [PATCH 7/7] Display a spawn marker --- overviewer.py | 1 + overviewer_core/data/js_src/overviewer.js | 7 ++++- overviewer_core/data/js_src/util.js | 3 ++ overviewer_core/data/js_src/views.js | 26 ++++++++++++++++ overviewer_core/tileset.py | 2 ++ overviewer_core/world.py | 37 ++++++++++++++++++----- 6 files changed, 68 insertions(+), 8 deletions(-) diff --git a/overviewer.py b/overviewer.py index d7f72d0..96ed27a 100755 --- a/overviewer.py +++ b/overviewer.py @@ -406,6 +406,7 @@ dir but you forgot to put quotes around the directory, since it contains spaces. # only pass to the TileSet the options it really cares about render['name'] = render_name # perhaps a hack. This is stored here for the asset manager tileSetOpts = util.dict_subset(render, ["name", "imgformat", "renderchecks", "rerenderprob", "bgcolor", "imgquality", "optimizeimg", "rendermode", "worldname_orig", "title", "dimension", "changelist"]) + tileSetOpts.update({"spawn": w.find_true_spawn()}) # TODO find a better way to do this tset = tileset.TileSet(rset, assetMrg, tex, tileSetOpts, tileset_dir) tilesets.append(tset) diff --git a/overviewer_core/data/js_src/overviewer.js b/overviewer_core/data/js_src/overviewer.js index a8ad80b..00dd283 100644 --- a/overviewer_core/data/js_src/overviewer.js +++ b/overviewer_core/data/js_src/overviewer.js @@ -36,7 +36,12 @@ overviewer.collections = { /** * Hold the raw marker data for each tilest */ - 'markerInfo': {} + 'markerInfo': {}, + + /** + * holds a reference to the spawn marker. + */ + 'spawnMarker': null, }; overviewer.classes = { diff --git a/overviewer_core/data/js_src/util.js b/overviewer_core/data/js_src/util.js index 0a1655f..0e738da 100644 --- a/overviewer_core/data/js_src/util.js +++ b/overviewer_core/data/js_src/util.js @@ -63,6 +63,8 @@ overviewer.util = { signs.registerEvents(signs); } + var spawnmarker = new overviewer.views.SpawnIconView(); + // Update coords on mousemove google.maps.event.addListener(overviewer.map, 'mousemove', function (event) { coordsdiv.updateCoords(event.latLng); @@ -77,6 +79,7 @@ overviewer.util = { overviewer.mapView.updateCurrentTileset(); compass.render(); + spawnmarker.render(); // re-center on the last viewport var currentWorldView = overviewer.mapModel.get("currentWorldView"); diff --git a/overviewer_core/data/js_src/views.js b/overviewer_core/data/js_src/views.js index b4ffbb9..323c1fc 100644 --- a/overviewer_core/data/js_src/views.js +++ b/overviewer_core/data/js_src/views.js @@ -359,3 +359,29 @@ overviewer.views.SignControlView = Backbone.View.extend({ }, }); +/** + * SpawnIconView + */ +overviewer.views.SpawnIconView = Backbone.View.extend({ + render: function() { + // + var curTileSet = overviewer.mapView.options.currentTileSet; + if (overviewer.collections.spawnMarker) { + overviewer.collections.spawnMarker.setMap(null); + overviewer.collections.spawnMarker = null; + } + var spawn = curTileSet.get("spawn"); + if (spawn) { + overviewer.collections.spawnMarker = new google.maps.Marker({ + 'position': overviewer.util.fromWorldToLatLng(spawn[0], + spawn[1], spawn[2], overviewer.mapView.options.currentTileSet), + 'map': overviewer.map, + 'title': 'spawn', + 'icon': overviewerConfig.CONST.image.spawnMarker, + 'visible': false + }); + overviewer.collections.spawnMarker.setVisible(true); + } + } +}); + diff --git a/overviewer_core/tileset.py b/overviewer_core/tileset.py index 8102328..8f78c97 100644 --- a/overviewer_core/tileset.py +++ b/overviewer_core/tileset.py @@ -518,6 +518,8 @@ class TileSet(object): last_rendertime = self.max_chunk_mtime, imgextension = self.imgextension, ) + if (self.regionset.get_type() == "overworld"): + d.update({"spawn": self.options.get("spawn")}) try: d['north_direction'] = self.regionset.north_dir except AttributeError: diff --git a/overviewer_core/world.py b/overviewer_core/world.py index 12a5d0a..7323a4a 100644 --- a/overviewer_core/world.py +++ b/overviewer_core/world.py @@ -19,6 +19,8 @@ import os.path from glob import glob import logging import hashlib +import time +import random import numpy @@ -91,8 +93,21 @@ class World(object): if not os.path.exists(os.path.join(self.worlddir, "level.dat")): raise ValueError("level.dat not found in %s" % self.worlddir) - # Hard-code this to only work with format version 19133, "Anvil" data = nbt.load(os.path.join(self.worlddir, "level.dat"))[1]['Data'] + # it seems that reading a level.dat file is unstable, particularly with respect + # to the spawnX,Y,Z variables. So we'll try a few times to get a good reading + # empirically, it seems that 0,50,0 is a "bad" reading + retrycount = 0 + while (data['SpawnX'] == 0 and data['SpawnY'] == 50 and data['SpawnZ'] ==0 ): + logging.debug("bad level read, retrying") + time.sleep(random.random()) + data = nbt.load(os.path.join(self.worlddir, "level.dat"))[1]['Data'] + retrycount += 1 + if retrycount > 10: + raise Exception("Failed to correctly read level.dat") + + + # Hard-code this to only work with format version 19133, "Anvil" if not ('version' in data and data['version'] == 19133): logging.critical("Sorry, This version of Minecraft-Overviewer only works with the 'Anvil' chunk format") raise ValueError("World at %s is not compatible with Overviewer" % self.worlddir) @@ -163,7 +178,7 @@ class World(object): # location ## read spawn info from level.dat - data = self.data + data = self.leveldat disp_spawnX = spawnX = data['SpawnX'] spawnY = data['SpawnY'] disp_spawnZ = spawnZ = data['SpawnZ'] @@ -175,24 +190,32 @@ class World(object): ## clamp spawnY to a sane value, in-chunk value if spawnY < 0: spawnY = 0 - if spawnY > 127: - spawnY = 127 + if spawnY > 255: + spawnY = 255 # Open up the chunk that the spawn is in - regionset = self.get_regionset(0) + regionset = self.get_regionset("overworld") try: chunk = regionset.get_chunk(chunkX, chunkZ) except ChunkDoesntExist: return (spawnX, spawnY, spawnZ) + + def getBlock(y): + "This is stupid and slow but I don't care" + targetSection = spawnY//16 + for section in chunk['Sections']: + if section['Y'] == targetSection: + blockArray = section['Blocks'] + return blockArray[inChunkX, inChunkZ, y % 16] + - blockArray = chunk['Blocks'] ## The block for spawn *within* the chunk inChunkX = spawnX - (chunkX*16) inChunkZ = spawnZ - (chunkZ*16) ## find the first air block - while (blockArray[inChunkX, inChunkZ, spawnY] != 0) and spawnY < 127: + while (getBlock(spawnY) != 0) and spawnY < 256: spawnY += 1 return spawnX, spawnY, spawnZ