Levels are now config-driven.
authordsc <david.schoonover@gmail.com>
Sun, 9 Jan 2011 04:28:31 +0000 (20:28 -0800)
committerdsc <david.schoonover@gmail.com>
Sun, 9 Jan 2011 04:28:31 +0000 (20:28 -0800)
37 files changed:
data/config.yaml
data/types/buffs.yaml
data/types/bullets.yaml
data/types/items.yaml
data/types/levels.yaml
data/types/units.yaml
lib/jquery.masonry-1.3.2.js [new file with mode: 0644]
lib/jquery.masonry-1.3.2.min.js [new file with mode: 0644]
lib/jquery.masonry.js [new symlink]
lib/mustache-0.3.1.js [changed mode: 0755->0644]
pavement.py
src/Y/modules/y.config.cjs
src/Y/modules/y.scaffold.cjs
src/Y/types/object.cjs
src/ezl/util/data/datafile.cjs
src/ezl/util/data/loader.cjs
src/tanks/config.cjs
src/tanks/game.cjs
src/tanks/map/pathmap.cjs
src/tanks/map/traversal.cjs
src/tanks/mixins/speciated.cjs
src/tanks/thing/bullet.cjs
src/tanks/thing/customtank.cjs [deleted file]
src/tanks/thing/fence.cjs [new file with mode: 0644]
src/tanks/thing/item.cjs
src/tanks/thing/player.cjs
src/tanks/thing/tank.cjs
src/tanks/thing/thing.cjs
src/tanks/thing/wall.cjs
src/tanks/ui/configui.cjs
src/tanks/ui/grid.cjs
src/tanks/ui/index.cjs
src/tanks/ui/main.cjs
src/tanks/ui/pathmapui.cjs
www/css/lttl.css
www/debug.html
www/deps.html

index e69de29..6b4336d 100644 (file)
@@ -0,0 +1,18 @@
+game:
+    timeDilation       : 1.0
+    gameoverDelay      : 1000
+    refSize            : &ref_size 50
+debug:
+    showFpsGraph       : false
+    # createGridTable    : false
+    # showGridCoords     : false
+ui:
+    createGridCanvas   : true
+    overlayOnPause     : true
+    showAttackCooldown : false
+    showCountdown      : true
+pathing:
+    gridSquare        : *ref_size
+    overlayAiPaths    : false
+    overlayPathmap    : false
+    traceTrajectories : false
index 4757a85..9a555e6 100644 (file)
@@ -1,6 +1,4 @@
-# Buff definitions
-name: buff
-lookup: .tanks.data.buffs
+name: buffs
 defaults:
     symbol: tanks/effects/buff.Buff
     timeout: -1 # the Infinity literal is not valid JSON
index e69de29..728a8db 100644 (file)
@@ -0,0 +1,17 @@
+name: bullets
+defaults:
+    symbol: tanks/thing/bullet.Bullet
+    art:
+        map_icon: ''
+        inv_icon: ''
+types:
+    normal:
+        color: '#FFF6AE'
+        bounceLimit: 1
+        stats:
+            move: 2.25
+    player_normal:
+        color: '#F7ADA6'
+        bounceLimit: 1
+        stats:
+            move: 2.25
index 8565072..bdd80b5 100644 (file)
@@ -1,6 +1,4 @@
-# Item definitions
-name: item
-lookup: .tanks.data.items
+name: items
 defaults:
     symbol: tanks/thing/item.Item
 types:
index a31d9cd..e6321db 100644 (file)
@@ -1,6 +1,4 @@
-# Level definitions
-name: level
-lookup: .tanks.data.levels
+name: levels
 defaults:
     symbol: tanks/map/level.Level
 types:
index 5b725bb..d310033 100644 (file)
@@ -1,9 +1,8 @@
-# Unit definitions
-name: unit
-lookup: .tanks.data.units
+name: units
 defaults:
     symbol: tanks/thing/thing.Thing
     level: 1
+    projectile: normal
     stats:
         hp            : 1           # health
         move          : 1.0         # move speed (squares/sec)
@@ -28,6 +27,7 @@ types:
         desc: "Don't hate the Player, hate the--Wait. No, that's fine."
         tags: [ 'tank' ]
         symbol: tanks/thing/player.Player
+        # projectile: player_normal
         stats:
             hp    : 1
             move  : 0.90
diff --git a/lib/jquery.masonry-1.3.2.js b/lib/jquery.masonry-1.3.2.js
new file mode 100644 (file)
index 0000000..e127b3a
--- /dev/null
@@ -0,0 +1,298 @@
+/*************************************************
+**  jQuery Masonry version 1.3.2
+**  Copyright David DeSandro, licensed MIT
+**  http://desandro.com/resources/jquery-masonry
+**************************************************/
+;(function($){  
+
+  /*!
+   * smartresize: debounced resize event for jQuery
+   * http://github.com/lrbabe/jquery-smartresize
+   *
+   * Copyright (c) 2009 Louis-Remi Babe
+   * Licensed under the GPL license.
+   * http://docs.jquery.com/License
+   *
+   */
+  var event = $.event,
+      resizeTimeout;
+
+  event.special.smartresize = {
+    setup: function() {
+      $(this).bind( "resize", event.special.smartresize.handler );
+    },
+    teardown: function() {
+      $(this).unbind( "resize", event.special.smartresize.handler );
+    },
+    handler: function( event, execAsap ) {
+      // Save the context
+      var context = this,
+          args = arguments;
+
+      // set correct event type
+      event.type = "smartresize";
+
+      if (resizeTimeout) { clearTimeout(resizeTimeout); }
+      resizeTimeout = setTimeout(function() {
+        jQuery.event.handle.apply( context, args );
+      }, execAsap === "execAsap"? 0 : 100);
+    }
+  };
+
+  $.fn.smartresize = function( fn ) {
+    return fn ? this.bind( "smartresize", fn ) : this.trigger( "smartresize", ["execAsap"] );
+  };
+
+
+
+  // masonry code begin
+  $.fn.masonry = function(options, callback) { 
+
+    // all my sweet methods
+    var msnry = {
+      getBricks : function($wall, props, opts) {
+        var hasItemSelector = (opts.itemSelector === undefined);
+        if ( opts.appendedContent === undefined ) {
+          // if not appendedContent
+          props.$bricks = hasItemSelector ?
+                $wall.children() :
+                $wall.find(opts.itemSelector);
+        } else {
+         //  if appendedContent...
+         props.$bricks = hasItemSelector ?
+                opts.appendedContent : 
+                opts.appendedContent.filter( opts.itemSelector );
+        }
+      },
+      
+      placeBrick : function($brick, setCount, setY, props, opts) {
+            // get the minimum Y value from the columns...
+        var minimumY = Math.min.apply(Math, setY),
+            setHeight = minimumY + $brick.outerHeight(true),
+            i = setY.length,
+            shortCol = i,
+            setSpan = props.colCount + 1 - i;
+        // Which column has the minY value, closest to the left
+        while (i--) {
+          if ( setY[i] == minimumY ) {
+            shortCol = i;
+          }
+        }
+            
+        var position = {
+          left: props.colW * shortCol + props.posLeft,
+          top: minimumY
+        };
+            
+        // position the brick
+        $brick.applyStyle(position, $.extend(true,{},opts.animationOptions) );
+
+        // apply setHeight to necessary columns
+        for ( i=0; i < setSpan; i++ ) {
+          props.colY[ shortCol + i ] = setHeight;
+        }
+      },
+      
+      setup : function($wall, opts, props) {
+        msnry.getBricks($wall, props, opts);
+
+        if ( props.masoned ) {
+          props.previousData = $wall.data('masonry');
+        }
+
+        if ( opts.columnWidth === undefined) {
+          props.colW = props.masoned ?
+              props.previousData.colW :
+              props.$bricks.outerWidth(true);
+        } else {
+          props.colW = opts.columnWidth;
+        }
+
+        props.colCount = Math.floor( $wall.width() / props.colW ) ;
+        props.colCount = Math.max( props.colCount, 1 );
+      },
+      
+      arrange : function($wall, opts, props) {
+        var i;
+
+        if ( !props.masoned || opts.appendedContent !== undefined ) {
+          // just the new bricks
+          props.$bricks.css( 'position', 'absolute' );
+        }
+
+        // if masonry hasn't been called before
+        if ( !props.masoned ) { 
+          $wall.css( 'position', 'relative' );
+
+          // get top left position of where the bricks should be
+          var $cursor = $( document.createElement('div') );
+          $wall.prepend( $cursor );
+          props.posTop =  Math.round( $cursor.position().top );
+          props.posLeft = Math.round( $cursor.position().left );
+          $cursor.remove();
+        } else {
+          props.posTop =  props.previousData.posTop;
+          props.posLeft = props.previousData.posLeft;
+        }
+        
+        // set up column Y array
+        if ( props.masoned && opts.appendedContent !== undefined ) {
+          // if appendedContent is set, use colY from last call
+          props.colY = props.previousData.colY;
+
+          /*
+          *  in the case that the wall is not resizeable,
+          *  but the colCount has changed from the previous time
+          *  masonry has been called
+          */
+          for ( i = props.previousData.colCount; i < props.colCount; i++) {
+            props.colY[i] = props.posTop;
+          }
+
+        } else {
+          // start new colY array, with starting values set to posTop
+          props.colY = [];
+          i = props.colCount;
+          while (i--) {
+            props.colY.push(props.posTop);
+          }
+        }
+
+        // are we animating the rearrangement?
+        // use plugin-ish syntax for css or animate
+        $.fn.applyStyle = ( props.masoned && opts.animate ) ? $.fn.animate : $.fn.css;
+
+
+        // layout logic
+        if ( opts.singleMode ) {
+          props.$bricks.each(function(){
+            var $brick = $(this);
+            msnry.placeBrick($brick, props.colCount, props.colY, props, opts);
+          });      
+        } else {
+          props.$bricks.each(function() {
+            var $brick = $(this),
+                //how many columns does this brick span
+                colSpan = Math.ceil( $brick.outerWidth(true) / props.colW);
+            colSpan = Math.min( colSpan, props.colCount );
+
+            if ( colSpan === 1 ) {
+              // if brick spans only one column, just like singleMode
+              msnry.placeBrick($brick, props.colCount, props.colY, props, opts);
+            } else {
+              // brick spans more than one column
+
+              //how many different places could this brick fit horizontally
+              var groupCount = props.colCount + 1 - colSpan,
+                  groupY = [];
+
+              // for each group potential horizontal position
+              for ( i=0; i < groupCount; i++ ) {
+                // make an array of colY values for that one group
+                var groupColY = props.colY.slice(i, i+colSpan);
+                // and get the max value of the array
+                groupY[i] = Math.max.apply(Math, groupColY);
+              }
+
+              msnry.placeBrick($brick, groupCount, groupY, props, opts);
+            }
+          }); //    /props.bricks.each(function() {
+        }  //     /layout logic
+
+        // set the height of the wall to the tallest column
+        props.wallH = Math.max.apply(Math, props.colY);
+        var wallCSS = { height: props.wallH - props.posTop };
+        $wall.applyStyle( wallCSS, $.extend(true,[],opts.animationOptions) );
+
+        // add masoned class first time around
+        if ( !props.masoned ) { 
+          // wait 1 millisec for quell transitions
+          setTimeout(function(){
+            $wall.addClass('masoned'); 
+          }, 1);
+        }
+
+        // provide props.bricks as context for the callback
+        callback.call( props.$bricks );
+
+        // set all data so we can retrieve it for appended appendedContent
+        //    or anyone else's crazy jquery fun
+        $wall.data('masonry', props );
+        
+      }, // /msnry.arrange
+      
+      resize : function($wall, opts, props) {
+        props.masoned = !!$wall.data('masonry');
+        var prevColCount = $wall.data('masonry').colCount;
+        msnry.setup($wall, opts, props);
+        if ( props.colCount != prevColCount ) {
+          msnry.arrange($wall, opts, props);
+        }
+      }
+    };
+
+
+    /*
+    *  let's begin
+    *  IN A WORLD...
+    */
+    return this.each(function() {  
+
+      var $wall = $(this),
+          props = {};
+
+      // checks if masonry has been called before on this object
+      props.masoned = !!$wall.data('masonry');
+    
+      var previousOptions = props.masoned ? $wall.data('masonry').options : {},
+          opts =  $.extend(
+                    {},
+                    $.fn.masonry.defaults,
+                    previousOptions,
+                    options
+                  ),
+          resizeOn = previousOptions.resizeable;
+
+      // should we save these options for next time?
+      props.options = opts.saveOptions ? opts : previousOptions;
+
+      //picked up from Paul Irish
+      callback = callback || function(){};
+
+      msnry.getBricks($wall, props, opts);
+
+      // if brickParent is empty, do nothing, go back home and eat chips
+      if ( !props.$bricks.length ) { 
+        return this; 
+      }
+
+      // call masonry layout
+      msnry.setup($wall, opts, props);
+      msnry.arrange($wall, opts, props);
+    
+      // binding window resizing
+      if ( !resizeOn && opts.resizeable ) {
+        $(window).bind('smartresize.masonry', function() { msnry.resize($wall, opts, props); } );
+      }
+      if ( resizeOn && !opts.resizeable ) { 
+        $(window).unbind('smartresize.masonry'); 
+      }
+       
+
+    });    //    /return this.each(function()
+  };      //    /$.fn.masonry = function(options)
+
+
+  // Default plugin options
+  $.fn.masonry.defaults = {
+    singleMode: false,
+    columnWidth: undefined,
+    itemSelector: undefined,
+    appendedContent: undefined,
+    saveOptions: true,
+    resizeable: true,
+    animate: false,
+    animationOptions: {}
+  };
+
+})(jQuery);
\ No newline at end of file
diff --git a/lib/jquery.masonry-1.3.2.min.js b/lib/jquery.masonry-1.3.2.min.js
new file mode 100644 (file)
index 0000000..f6e8a82
--- /dev/null
@@ -0,0 +1,12 @@
+/*************************************************
+**  jQuery Masonry version 1.3.2
+**  Copyright David DeSandro, licensed MIT
+**  http://desandro.com/resources/jquery-masonry
+**************************************************/
+(function(e){var n=e.event,o;n.special.smartresize={setup:function(){e(this).bind("resize",n.special.smartresize.handler)},teardown:function(){e(this).unbind("resize",n.special.smartresize.handler)},handler:function(j,l){var g=this,d=arguments;j.type="smartresize";o&&clearTimeout(o);o=setTimeout(function(){jQuery.event.handle.apply(g,d)},l==="execAsap"?0:100)}};e.fn.smartresize=function(j){return j?this.bind("smartresize",j):this.trigger("smartresize",["execAsap"])};e.fn.masonry=function(j,l){var g=
+{getBricks:function(d,b,a){var c=a.itemSelector===undefined;b.$bricks=a.appendedContent===undefined?c?d.children():d.find(a.itemSelector):c?a.appendedContent:a.appendedContent.filter(a.itemSelector)},placeBrick:function(d,b,a,c,h){b=Math.min.apply(Math,a);for(var i=b+d.outerHeight(true),f=a.length,k=f,m=c.colCount+1-f;f--;)if(a[f]==b)k=f;d.applyStyle({left:c.colW*k+c.posLeft,top:b},e.extend(true,{},h.animationOptions));for(f=0;f<m;f++)c.colY[k+f]=i},setup:function(d,b,a){g.getBricks(d,a,b);if(a.masoned)a.previousData=
+d.data("masonry");a.colW=b.columnWidth===undefined?a.masoned?a.previousData.colW:a.$bricks.outerWidth(true):b.columnWidth;a.colCount=Math.floor(d.width()/a.colW);a.colCount=Math.max(a.colCount,1)},arrange:function(d,b,a){var c;if(!a.masoned||b.appendedContent!==undefined)a.$bricks.css("position","absolute");if(a.masoned){a.posTop=a.previousData.posTop;a.posLeft=a.previousData.posLeft}else{d.css("position","relative");var h=e(document.createElement("div"));d.prepend(h);a.posTop=Math.round(h.position().top);
+a.posLeft=Math.round(h.position().left);h.remove()}if(a.masoned&&b.appendedContent!==undefined){a.colY=a.previousData.colY;for(c=a.previousData.colCount;c<a.colCount;c++)a.colY[c]=a.posTop}else{a.colY=[];for(c=a.colCount;c--;)a.colY.push(a.posTop)}e.fn.applyStyle=a.masoned&&b.animate?e.fn.animate:e.fn.css;b.singleMode?a.$bricks.each(function(){var i=e(this);g.placeBrick(i,a.colCount,a.colY,a,b)}):a.$bricks.each(function(){var i=e(this),f=Math.ceil(i.outerWidth(true)/a.colW);f=Math.min(f,a.colCount);
+if(f===1)g.placeBrick(i,a.colCount,a.colY,a,b);else{var k=a.colCount+1-f,m=[];for(c=0;c<k;c++){var p=a.colY.slice(c,c+f);m[c]=Math.max.apply(Math,p)}g.placeBrick(i,k,m,a,b)}});a.wallH=Math.max.apply(Math,a.colY);d.applyStyle({height:a.wallH-a.posTop},e.extend(true,[],b.animationOptions));a.masoned||setTimeout(function(){d.addClass("masoned")},1);l.call(a.$bricks);d.data("masonry",a)},resize:function(d,b,a){a.masoned=!!d.data("masonry");var c=d.data("masonry").colCount;g.setup(d,b,a);a.colCount!=c&&
+g.arrange(d,b,a)}};return this.each(function(){var d=e(this),b={};b.masoned=!!d.data("masonry");var a=b.masoned?d.data("masonry").options:{},c=e.extend({},e.fn.masonry.defaults,a,j),h=a.resizeable;b.options=c.saveOptions?c:a;l=l||function(){};g.getBricks(d,b,c);if(!b.$bricks.length)return this;g.setup(d,c,b);g.arrange(d,c,b);!h&&c.resizeable&&e(window).bind("smartresize.masonry",function(){g.resize(d,c,b)});h&&!c.resizeable&&e(window).unbind("smartresize.masonry")})};e.fn.masonry.defaults={singleMode:false,
+columnWidth:undefined,itemSelector:undefined,appendedContent:undefined,saveOptions:true,resizeable:true,animate:false,animationOptions:{}}})(jQuery);
\ No newline at end of file
diff --git a/lib/jquery.masonry.js b/lib/jquery.masonry.js
new file mode 120000 (symlink)
index 0000000..a7b9106
--- /dev/null
@@ -0,0 +1 @@
+jquery.masonry-1.3.2.js
\ No newline at end of file
old mode 100755 (executable)
new mode 100644 (file)
index da4fbb4..c8acb1a 100755 (executable)
@@ -21,8 +21,8 @@ def commonjs(*args, **kw):
 
 
 @task
-def data():
-    "Convert all yaml files to json."
+def build_data():
+    "Convert all yaml files to json"
     info('Building data files...')
     for dirpath, dirs, files in os.walk(DATA_DIR):
         indir  = path(dirpath)
@@ -35,17 +35,34 @@ def data():
                 out = outdir / f.replace('.yaml', '.json')
                 with in_.open('rU') as infile, out.open('w') as outfile:
                     json.dump(yaml.load(infile), outfile, indent=4)
+    
+    info('Injecting config JSON...')
+    conf = path('build/tanks/config.js')
+    conf_json = path('build/data/config.json')
+    with conf.open('rU') as f:
+        conf_txt = f.read()
+    with conf_json.open('rU') as jf, conf.open('w') as out:
+        conf_data = jf.read()
+        out.write( conf_txt.replace('/*CONFIG_JSON*/', conf_data) )
 
 @task
-@needs('data')
-def build():
-    "Builds the Tanks project"
+def build_scripts():
+    "Builds js modules"
     info('Building scripts...')
+    sh('rm build/tanks/config.js')
     tags = commonjs(script_tags=True, capture=True)
     with path('www/deps.html').open('w') as f:
         f.write(tags)
 
 @task
+@needs('build_data')
+@needs('build_scripts')
+def build():
+    "Builds the Tanks project"
+    pass
+
+
+@task
 def clean():
     "Cleans dep cache and build files"
     commonjs(clean=True)
index f812fde..55ebb00 100644 (file)
@@ -132,6 +132,37 @@ Y.YObject.subclass('Config', function(Config){
                 acc, this);
         },
         
+        /**
+         * Iterates over both items and groups of the config object
+         * in order, sorted lexographically on key.
+         */
+        sorted : function sorted(groupFn, itemFn, acc, context){
+            context = context || this;
+            return this._sorted(new Y.YArray(), this._o, groupFn, itemFn, acc, context);
+        },
+        
+        _sorted : function _sorted(path, obj, groupFn, itemFn, acc, context){
+            var self = this
+            ,   keys = (Y.isFunction(obj.keys) ? obj.keys() : Object.keys(obj)).sort();
+            return keys.reduce(function __sorted(acc, k){
+                    var chain = path.push(k).join('.')
+                    ,   v = self.getNested(chain)
+                    ;
+                    
+                    // Nested object -- recurse
+                    if ( Y.isPlainObject(v) ){
+                        acc = groupFn.call(context, acc, v, chain, self);
+                        acc = self._sorted(path, v, groupFn, itemFn, acc, context);
+                        
+                    // Normal value -- invoke iterator
+                    } else
+                        acc = itemFn.call(context, acc, v, chain, self);
+                    
+                    path.pop();
+                    return acc;
+                }, acc);
+        },
+        
         getDefault : function getDefault(k, def){
             return getNested(this._defaults, k, def);
         },
index 3f85341..a17a77e 100644 (file)
@@ -57,8 +57,8 @@ Y.subclass('Field', {
         this.id    = chain;
         this.def   = def;
         this.val   = this.old = val === undefined ? def : val;
-        this.key   = chain.split('.').pop();
-        this.label = camelToSpaces(this.key);
+        this.key   = chain.replace('.', '_');
+        this.label = camelToSpaces(chain.split('.').pop());
         
         var T = Y.typeName(def);
         this.cast = options.cast || type2parser[T];
@@ -72,7 +72,7 @@ Y.subclass('Field', {
         this.build()
             .update(this.val);
         
-        this.elField.bind('change', this.onChange.bind(this));
+        jQuery(this.selector).live('change', this.onChange.bind(this));
     },
     
     build : function build(){
@@ -94,11 +94,12 @@ Y.subclass('Field', {
                 })
                 .val(this.val)
                 .appendTo(el);
+        this.selector = 'input[name='+this.key+']';
         return this;
     },
     
     update : function update(val){
-        var el = this.elField;
+        var el = jQuery(this.selector);
         if (val !== this.val) {
             this.old = this.val;
             this.val = val;
@@ -106,7 +107,7 @@ Y.subclass('Field', {
                 'key'    : this.id,
                 'oldval' : this.old,
                 'newval' : this.val,
-                'el'     : this.elField
+                'el'     : jQuery(this.selector)
             });
             el.val(val);
         }
@@ -117,10 +118,11 @@ Y.subclass('Field', {
     },
     
     onChange : function onChange(evt){
+        var el = jQuery(this.selector);
         if (this.type === 'checkbox')
-            this.update( this.elField.attr('checked') );
+            this.update( el.attr('checked') );
         else
-            this.update( this.cast(this.elField.val()) );
+            this.update( this.cast(el.val()) );
     }
     
 })
@@ -130,14 +132,19 @@ Y.subclass('Field', {
 create =
 exports['create'] =
 function create(config, el){
-    var fields = {};
-    config.parts(
+    var data = {}
+    ,   groups = data.groups = {}
+    ,   fields = data.fields = {};
+    config.sorted(
         function createGroup(oldGroup, value, chain){
-            return jQuery('<fieldset/>')
+            var g = 
+            groups[chain] = 
+                    jQuery('<fieldset/>')
                         .attr('id', chain)
                         .addClass('group')
                         .append( jQuery('<legend/>').text(chain.split('.').pop()) )
                         .appendTo(el);
+            return g;
         },
         function createField(group, value, chain){
             var def = config.getDefault(chain)
@@ -159,7 +166,7 @@ function create(config, el){
             return group;
         },
         el);
-    return fields;
+    return data;
 }
 ;
 
index a383701..49ff70b 100644 (file)
@@ -189,8 +189,23 @@ YCollection.subclass('YObject', {
             if ( o[name] === value )
                 return name;
         return -1;
-    }
+    },
+    
+    'unzipped' : function unzipped(){
+        return this.reduce(function(acc, v, k){
+            acc.keys.push(k);
+            acc.values.push(v);
+            return acc;
+        }, { 'keys':[], 'values':[] });
+    },
     
+    'keys' : function keys(){
+        return this.unzipped().keys;
+    },
+    
+    'values' : function values(){
+        return this.unzipped().values;
+    }
     
 });
 
index 2bbf1f3..699ddf4 100644 (file)
@@ -14,8 +14,9 @@ new evt.Class('DataFile', {
     path : '',
     
     
-    init : function initDataFile(path){
+    init : function initDataFile(path, datastore){
         this.path = path;
+        this.datastore = datastore;
     },
     
     load : function load(){
@@ -51,13 +52,11 @@ new evt.Class('DataFile', {
         
         var types = Y(data.types)
         ,   defaults = data.defaults || {}
-        ,   lookup = this.resolve(data.lookup)
         ;
         if (!(types && Y.isFunction(types.forEach)))
             this.die('Specified types are not iterable! '+data.types);
         
         types.forEach(function(kv, id){
-            // TODO: process 'inherits' key -- requires resolving data-type path
             var props = Y.extend({}, deepcopy(data.defaults), kv)
             ,   symbol = props.symbol;
             
@@ -71,7 +70,7 @@ new evt.Class('DataFile', {
                 this.die('Cannot create types from data (symbol-class '+base+' from "'+symbol+'" is not Speciated)!');
             
             var type = base.speciate(id, props);
-            lookup[id] = type;
+            this.datastore[id] = type;
             
             // console.log('Added type '+id+':', type);
         }, this);
index 94e4186..b2f564a 100644 (file)
@@ -100,6 +100,7 @@ new evt.Class('Loader', {
             return;
         
         var job = self.queue.shift();
+        // console.log(self+'.nextJob() --> '+job);
         job.addEventListener('complete', function onJobComplete(evt){
             // console.log(self+'.onJobComplete('+job+')');
             job.removeEventListener('complete', arguments.callee);
index 23c4a96..b128344 100644 (file)
@@ -1,5 +1,7 @@
 //  -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*-
+//#exports defaults config dataLoader
 var Y        = require('Y').Y
+,   ensure   = require('Y/types/object').ensure
 ,   Config   = require('Y/modules/y.config').Config
 
 ,   Vec      = require('ezl/math').Vec
@@ -9,28 +11,9 @@ var Y        = require('Y').Y
 
 /// Config (Inserted Here) ///
 
+
 defaults =
-exports['defaults'] = {
-    game : {
-        timeDilation       : 1.0,
-        gameoverDelay      : 1000
-    },
-    ui : {
-        createGridCanvas   : true,
-        createGridTable    : false,
-        overlayOnPause     : true,
-        showGridCoords     : false,
-        showAttackCooldown : false,
-        showCountdown      : (document.location.host.toString() !== 'tanks.woo')
-    },
-    pathing : {
-        gridSquare        : REF_SIZE,
-        gridSquareMid     : new Vec(REF_SIZE/2, REF_SIZE/2),
-        overlayAiPaths    : false,
-        overlayPathmap    : false,
-        traceTrajectories : false
-    }
-}
+exports['defaults'] = /*CONFIG_JSON*/
 ,
 
 config =
@@ -42,17 +25,25 @@ config.addEventListener('set:pathing.gridSquare', function(evt){
     var sq = evt.data.newval;
     config.set('pathing.gridSquareMid', new Vec(sq/2, sq/2));
 });
-
+config.set('pathing.gridSquare', config.get('pathing.gridSquare'));
 
 
 /// Load Data Files ///
 
-var files = 
-    'types/buffs types/items types/units types/levels' // levels game
-        .split(' ')
-        .map(function(type){
-            return new DataFile('build/data/'+type+'.json');
-        });
+var loader;
+exports['dataLoader'] = function dataLoader(){
+    if (!loader){
+        var files = 
+            'types/buffs types/items types/bullets types/units types/levels' // levels game
+                .split(' ')
+                .map(function(type){
+                    var name = type.split('/').pop();
+                    ensure(tanks.data, name);
+                    return new DataFile('build/data/'+type+'.json', tanks.data[name]);
+                });
+        loader = new Loader(files);
+    }
+    return loader;
+};
 
-exports['dataLoader'] = new Loader(files);
 
index 5d8b883..b31c13d 100644 (file)
@@ -121,6 +121,14 @@ Y.subclass('Game', {
         
     },
     
+    debugTick : function debugTick(evt){
+        try {
+            this.tick(evt);
+        } catch(ex) {
+            console.error('tick('+evt+') '+ex, ex);
+        }
+    },
+    
     tickAnimations : function tickAnimations(animation){
         var running = animation.tick(ELAPSED, NOW);
         if (!running)
index d4d72ee..3a09ae2 100644 (file)
@@ -9,8 +9,8 @@ var Y          = require('Y').Y
 ,   Line       = math.Line
 
 ,   config     = require('tanks/config').config
-,   map        = require('tanks/map/map')
-,   Map        = map.Map
+,   PathingType = require('tanks/constants').PathingType
+,   Map        = require('tanks/map/map').Map
 ,   Wall       = require('tanks/thing/wall').Wall
 
 
@@ -291,7 +291,7 @@ Y.subclass('Square', new Vec(0,0), {
      * @private
      */
     _filterBlocked : function filterBlocked(v, r){
-        return v.blocking === map.BLOCKING && !v.isBoundary;
+        return v.blocking === PathingType.BLOCKING && !v.isBoundary;
     },
     
     getNeighbors : function getNeighbors(){
index f926a39..9d9237b 100644 (file)
@@ -1,5 +1,5 @@
 var Y = require('Y').Y
-,   map = require('tanks/map/map')
+,   PathingType = require('tanks/constants').PathingType
 ,
 
 Traversal =
@@ -93,12 +93,12 @@ Y.subclass('Traversal', {
         var blocking = blocker.blocking;
         
         // All blockers after the first are ignored
-        if ( this.ignore.has(blocker) || blocking === map.PASSABLE || this.isBlocked )
+        if ( this.ignore.has(blocker) || blocking === PathingType.PASSABLE || this.isBlocked )
             return false;
         
         // Only fire collision with this zone once per traversal
         // XXX: Zone will have to manage enterance/exit and provide a method for testing, hm -- this would obviate the main need for this.ignore
-        if ( blocking === map.ZONE ) {
+        if ( blocking === PathingType.ZONE ) {
             this.ignore.push(blocker);
             this.collide(blocker);
             return false;
index 24bcf8c..544c3d6 100644 (file)
@@ -10,7 +10,11 @@ Mixin.subclass('Speciated', {
     tags : [],
     
     __static__ : {
+        __species_id__ : 0,
+        
         id2name : function id2name(id){
+            if (typeof id === 'number')
+                return this.className+id;
             var name = id+'';
             // TODO: all YString methods to return YString :P But it'll proally break shit
             name = Y(name).capitalize();
@@ -19,12 +23,16 @@ Mixin.subclass('Speciated', {
         },
         
         speciate : function speciate(id, props){
+            if ( arguments.length < 2 ) {
+                props = id;
+                id = this.__species_id__++;
+            }
+            
             if ( this.__known__[id] )
                 throw new Error('A species of '+this.className+' already exists with the id '+id+'!');
             
             props = props || {};
             props.__species__ = id;
-            // delete props.id; // reserved for instance id
             
             var cls = this
             ,   speciesName = this.id2name(id)
index 22088e2..41be7dc 100644 (file)
@@ -8,7 +8,7 @@ var Y          = require('Y').Y
 ,   Circle     = shape.Circle
 
 ,   config     = require('tanks/config').config
-,   map        = require('tanks/map/map')
+,   PathingType = require('tanks/constants').PathingType
 ,   stat       = require('tanks/effects/stat')
 
 ,   Thing      = require('tanks/thing/thing').Thing
@@ -33,7 +33,7 @@ Thing.subclass('Bullet', {
     },
     
     // Instance
-    blocking    : map.BLOCKING,
+    blocking : PathingType.BLOCKING,
     isRenderable : true,
     
     bounces     : 0,
@@ -42,6 +42,8 @@ Thing.subclass('Bullet', {
     width  : 6,
     height : 6,
     
+    color: '#FFF6AE',
+    
     stats : {
         move : 2.0 // move speed (squares/sec)
     },
@@ -58,9 +60,6 @@ Thing.subclass('Bullet', {
         
         Thing.init.call(this, owner.align);
         
-        // var loc = owner.getTurretLoc()
-        // ,   x1  = loc.x, y1  = loc.y;
-        
         this.position(x1,y1);
         this.trajectory = new Trajectory(this, x1,y1, x2,y2, this.movePerMs);
         
@@ -122,7 +121,7 @@ Thing.subclass('Bullet', {
         ;
         
         // Ignore collisions with zones
-        if (unit.blocking === map.ZONE)
+        if (unit.blocking === PathingType.ZONE)
             return;
         
         // Reflection!
@@ -188,7 +187,7 @@ Thing.subclass('Bullet', {
         var loc = this.loc;
         this.shape = new Circle(this.width/2)
             .position(loc.x, loc.y)
-            .fill('#FFF6AE')
+            .fill(this.color)
             .appendTo( parent );
         this.shape.layer.attr('title', ''+loc);
         
diff --git a/src/tanks/thing/customtank.cjs b/src/tanks/thing/customtank.cjs
deleted file mode 100644 (file)
index 97dcecf..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-var Tank  = require('tanks/thing/tank').Tank
-,
-
-CustomTank =
-exports['CustomTank'] =
-Tank.subclass('CustomTank', {
-    
-    init : function initCustomTank(align, script){
-        Tank.init.call(this, align);
-        this.act = eval('(function(){ with(this){'+script+'} })');
-    }
-    
-});
diff --git a/src/tanks/thing/fence.cjs b/src/tanks/thing/fence.cjs
new file mode 100644 (file)
index 0000000..25e1c2b
--- /dev/null
@@ -0,0 +1,79 @@
+var Y     = require('Y').Y
+,   op    = require('Y/op')
+,   Rect  = require('ezl/shape').Rect
+,   Wall  = require('tanks/thing/wall').Wall
+,
+
+
+Fence =
+exports['Fence'] =
+Wall.subclass('Fence', {
+    align : 0, // 0 reserved for neutral units
+    
+    originX : 0,
+    originY : 0,
+    
+    isReflective : true,
+    isRenderable : true,
+    
+    // inactive
+    active : false,
+    createCooldowns : op.nop,
+    
+    // indestructable
+    dealDamage : op.nop,
+    
+    stats : {
+        hp    : Infinity,
+        move  : 0,
+        power : 0,
+        speed : 0,
+        shots : 0
+    },
+    
+    // Instance 
+    
+    isBoundary : false,
+    
+    
+    
+    init : function initFence(x,y, w,h){
+        this.width = w;
+        this.height = h;
+        this.isBoundary = !!isBoundary;
+        Wall.init.call(this, x,y, w,h);
+        this.position(x,y);
+    },
+    
+    
+    
+    render : function render(parent){
+        if (this.isBoundary)
+            return this;
+        
+        if (this.shape)
+            this.shape.remove();
+        
+        this.shape =
+            new Rect(this.width, this.height)
+                .position(this.loc.x, this.loc.y)
+                .fill('rgba(0,0,0, 0.25)')
+                .stroke('transparent', 0)
+                .appendTo( parent );
+        
+        return this;
+    },
+    
+    toString : function(){
+        var bb = this.bbox
+        ,   x1,y1, x2,y2;
+        if (bb){
+            x1 = bb.x1; y1 = bb.y1;
+            x2 = bb.x2; y2 = bb.y2;
+        } else {
+            x1=y1=x2=y2=NaN;
+        }
+        return this.className+'['+x1+','+y1+', '+x2+','+y2+'](w='+this.width+', h='+this.height+')';
+    }
+});
+
index 91f7e52..76dd7cc 100644 (file)
@@ -1,12 +1,12 @@
-var Y     = require('Y').Y
-,   op    = require('Y/op')
+var Y         = require('Y').Y
+,   op        = require('Y/op')
 
-,   shape = require('ezl/shape')
-,   Rect  = shape.Rect
+,   shape     = require('ezl/shape')
+,   Rect      = shape.Rect
 
-,   map   = require('tanks/map/map')
-,   Buff  = require('tanks/effects/buff').Buff
-,   Thing = require('tanks/thing/thing').Thing
+,   PathingType = require('tanks/constants').PathingType
+,   Buff      = require('tanks/effects/buff').Buff
+,   Thing     = require('tanks/thing/thing').Thing
 
 ,   ITEM_SIZE = REF_SIZE * 0.5
 ,
@@ -20,7 +20,7 @@ Thing.subclass('Item', {
     
     align : 0, // 0 reserved for neutral units
     
-    blocking : map.ZONE,
+    blocking : PathingType.ZONE,
     width    : ITEM_SIZE,
     height   : ITEM_SIZE,
     
index c81167c..617e498 100644 (file)
@@ -1,9 +1,8 @@
 //#ensure "jquery"
 //#ensure "jquery.hotkeys"
 
-var Y    = require('Y').Y
-,   map  = require('tanks/map/map')
-,   Tank = require('tanks/thing/tank').Tank
+var Y           = require('Y').Y
+,   Tank        = require('tanks/thing/tank').Tank
 ,   Inventoried = require('tanks/mixins/inventoried').Inventoried
 ,
 
@@ -13,8 +12,6 @@ exports['Player'] =
 Tank.subclass('Player', {
     __mixins__ : [ Inventoried ],
     
-    blocking : map.BLOCKING,
-    
     // Attributes
     stats: {
         hp        : 1,          // health
index 868ef97..7dabe64 100644 (file)
@@ -10,7 +10,6 @@ var Y             = require('Y').Y
 ,   Rect          = shape.Rect
 ,   Circle        = shape.Circle
 
-,   map           = require('tanks/map/map')
 ,   Thing         = require('tanks/thing/thing').Thing
 ,   Bullet        = require('tanks/thing/bullet').Bullet
 ,   Trajectory    = require('tanks/map/trajectory').Trajectory
@@ -35,7 +34,6 @@ Thing.subclass('Tank', function(Tank){
         },
         
         // Bounding box
-        blocking : map.BLOCKING,
         width  : REF_SIZE*0.55,
         height : REF_SIZE*0.55,
         
@@ -56,7 +54,7 @@ Thing.subclass('Tank', function(Tank){
             shootEnemy    : 0.75    // shoot at enemy tank if in range
         },
         
-  &nb