Adds Set class; Adds ShieldManager, allowing shield orbits to be rebalanced.
authordsc <david.schoonover@gmail.com>
Mon, 11 Apr 2011 11:26:10 +0000 (04:26 -0700)
committerdsc <david.schoonover@gmail.com>
Mon, 11 Apr 2011 11:26:10 +0000 (04:26 -0700)
data/types/items.yaml
data/types/levels.yaml
src/ezl/struct/index.cjs
src/ezl/struct/set.cjs
src/tanks/fx/index.cjs
src/tanks/fx/shieldmanager.cjs [new file with mode: 0644]
src/tanks/item/shieldgen.cjs
src/tanks/thing/component.cjs
src/tanks/thing/shield.cjs
src/tanks/thing/thing.cjs
src/tanks/thing/unit.cjs

index b11c909..152265f 100644 (file)
@@ -55,4 +55,7 @@ types:
         desc: 'Generates four rotating shield spheres, each of which absorbs one impact.'
         tags: [ 'armor' ]
         symbol: tanks/item/shieldgen.ShieldGenerator
+        art:
+            map_icon: '/img/items/orbital_shield_x4-25x25.png'
+            inv_icon: '/img/items/orbital_shield_x4-50x50.png'
     
index 2e90be2..0d9b6fa 100644 (file)
@@ -143,8 +143,8 @@ types:
             align: 2
             loc: [775,325]
         items:
-          # - type: shield_gen # right next to start
-          #   loc: [325,475]
+          - type: shield_gen # right next to start
+            loc: [325,475]
           - type: rockets
             loc: [75,275]
           # - type: nitro
index a3427d5..2eec106 100644 (file)
@@ -1,7 +1,10 @@
 var Y = require('Y').Y
 ,   namedtuple = require('ezl/struct/namedtuple')
+,   set = require('ezl/struct/set')
 ;
 Y.core.extend(exports, {
     'namedtuple' : namedtuple,
-    'NamedTuple' : namedtuple.NamedTuple
+    'NamedTuple' : namedtuple.NamedTuple,
+    'set'        : set,
+    'Set'        : set.Set
 });
index 0488796..cee557d 100644 (file)
@@ -8,7 +8,7 @@ var Y           = require('Y').Y
 
 Set =
 exports['Set'] =
-YCollection.subclass('Set', null, function setupSet(Set){
+YCollection.subclass('Set', function setupSet(Set){
     Y.extend(this, {
         _byId : {},
     });
@@ -48,32 +48,38 @@ YCollection.subclass('Set', null, function setupSet(Set){
         throw new Error(arguments.callee.name+' is unsupported for type '+this.className+'!');
     };
     
+    this['_getIdSafe'] =
     function _getIdSafe(v){
-        switch (typeof v) {
+        var t = typeof v
+        ,   t0 = t.charAt(0)
+        ;
+        switch (t) {
             case 'undefined':
-                return undefined;
+                return t0;
             break;
             
             case 'boolean':
             case 'string':
             case 'number':
-                return v;
+                return t0+v;
             break;
             
             case 'object':
             case 'function':
             default:
+                if ( v === null )
+                    return 'n';
                 if ( !('__id__' in v) )
-                    return undefined;
+                    return t0+'u';
                 else
-                    return v.__id__;
+                    return t0+'_'+v.__id__;
             break;
         }
-    }
+    };
     
     this['_getId'] =
     function _getId(v){
-        var id = _getIdSafe(v);
+        var id = this._getIdSafe(v);
         if (id === undefined)
             throw new Error('All Set elements must be hashable! v='+v);
         else
@@ -82,19 +88,20 @@ YCollection.subclass('Set', null, function setupSet(Set){
     
     this['has'] =
     function has(v){
-        return (_getIdSafe(v) in this._byId);
+        return (this._getIdSafe(v) in this._byId);
     };
     
-    function addIt(v){
+    this['_addOne'] =
+    function _addOne(v){
         var id = this._getId(v);
         
-        if (!(id in this._byId)) {
+        if ( !(id in this._byId) ) {
             this._byId[id] = v;
             this._o.push(v);
         }
         
         return this;
-    }
+    };
     
     this['add'] =
     this['push'] =
@@ -103,9 +110,9 @@ YCollection.subclass('Set', null, function setupSet(Set){
         var L = arguments.length;
         
         if (L === 1)
-            addIt.call(this, v);
+            this._addOne.call(this, v);
         else if (L > 1)
-            slice.call(arguments,0).forEach(addIt, this);
+            slice.call(arguments,0).forEach(this._addOne, this);
         
         return this;
     };
@@ -113,7 +120,7 @@ YCollection.subclass('Set', null, function setupSet(Set){
     this['update'] =
     this['extend'] =
     function update(vs){
-        Y(vs).forEach(addIt, this);
+        Y(vs).forEach(this._addOne, this);
         return this;
     };
     
@@ -122,7 +129,8 @@ YCollection.subclass('Set', null, function setupSet(Set){
         return this.clone().update(vs);
     };
     
-    function removeIt(v){
+    this['_removeOne'] =
+    function _removeOne(v){
         var id = this._getId(v);
         
         if ( id in this._byId ) {
@@ -131,7 +139,7 @@ YCollection.subclass('Set', null, function setupSet(Set){
         }
         
         return this;
-    }
+    };
     
     this['remove'] =
     this['discard'] =
@@ -139,9 +147,9 @@ YCollection.subclass('Set', null, function setupSet(Set){
         var L = arguments.length;
         
         if (L === 1)
-            removeIt.call(this, v);
+            this._removeOne.call(this, v);
         else if (L > 1)
-            slice.call(arguments,0).forEach(removeIt, this);
+            slice.call(arguments,0).forEach(this._removeOne, this);
         
         return this;
     };
@@ -152,11 +160,16 @@ YCollection.subclass('Set', null, function setupSet(Set){
         if (!this._o.length) return;
         
         var v = this._o.shift()
-        ,   id = this._getId(v);
+        ,   id = this._getIdSafe(v);
         delete this._byId[id];
         return v;
     };
     
+    this['first'] =
+    function first(){
+        return this._o[0];
+    };
+    
     this['unique'] = op.kThis;
     
     /**
index 698054c..e0ad2ad 100644 (file)
@@ -1,8 +1,10 @@
 var Y = require('Y').Y
 ,   barrel    = require('tanks/fx/barrel')
 ,   explosion = require('tanks/fx/explosion')
+,   shieldman = require('tanks/fx/shieldmanager')
 ;
 Y.core.extend(exports, {
-       'Barrel'    : barrel.Barrel,
-       'Explosion' : explosion.Explosion
+    'Barrel'        : barrel.Barrel,
+    'Explosion'     : explosion.Explosion,
+    'ShieldManager' : shieldman.ShieldManager
 });
diff --git a/src/tanks/fx/shieldmanager.cjs b/src/tanks/fx/shieldmanager.cjs
new file mode 100644 (file)
index 0000000..30a91f6
--- /dev/null
@@ -0,0 +1,90 @@
+var Y = require('Y').Y
+,   evt = require('evt')
+,   Set = require('ezl/struct/set').Set
+
+,   PI       = Math.PI
+,   TWO_PI   = 2 * Math.PI
+,
+
+
+ShieldManager =
+exports['ShieldManager'] =
+evt.subclass('ShieldManager', {
+    maxShields : Infinity,
+    
+    owner : null,
+    shields : null,
+    
+    
+    init : function initShieldManager(owner, max){
+        this.shields = new Set();
+        this.owner = owner;
+        if (max !== undefined)
+            this.maxShields = max;
+    },
+    
+    /**
+     * Adds a shield to the cluster. If no shield is supplied, does nothing.
+     * If shield is already present or at max, cluster may still be rebalanced.
+     * @param {Shield} shield Shield to add.
+     * @param {Boolean} [rebalance=false] Whether to rebalance the shield cluster to be evenly spaced.
+     * @param {Number} [duration=15] Duration over which cluster will be rebalanced (ticks) (15 ticks ~ 500ms).
+     * @return {this}
+     */
+    add : function addShield(sh, rebalance, duration){
+        if (!sh)
+            return this;
+        if (this.maxShields > this.shields.length && !this.shields.has(sh)) {
+            this.shields.add(sh);
+            this.owner.addComponent(sh);
+        }
+        if (rebalance)
+            this.rebalance(duration);
+        return this;
+    },
+    
+    /**
+     * Removes a shield from the cluster. If no shield is supplied, does nothing.
+     * @param {Shield} shield Shield to remove.
+     * @param {Boolean} [rebalance=false] Whether to rebalance the shield cluster to be evenly spaced.
+     * @param {Number} [duration=15] Duration over which cluster will be rebalanced (ticks) (15 ticks ~ 500ms).
+     * @return {this}
+     */
+    remove : function removeShield(sh, rebalance, duration){
+        if (!sh)
+            return this;
+        this.shields.remove(sh);
+        this.owner.removeComponent(sh);
+        if (rebalance)
+            this.rebalance(duration);
+        return this;
+    },
+    
+    /**
+     * Moves shield orbs such that they are evenly spaced.
+     * @param {Number} [duration=15] Duration over which cluster will be rebalanced (ticks) (15 ticks ~ 500ms).
+     * @return {this}
+     */
+    rebalance : function rebalanceShields(duration){
+        duration = duration || 15;
+        var startPos = this.shields.first().radialPosition
+        ,   spacing = TWO_PI / this.shields.length
+        ;
+        // console.log('rebalance: start=%s, spacing=%s', startPos.toFixed(3), spacing.toFixed(3));
+        this.shields.forEach(function(sh, i){
+            var r  = startPos + spacing*i
+            ,   dr = r - sh.radialPosition
+            ;
+            if ( dr > PI )
+                dr -= TWO_PI;
+            else if ( dr < -PI )
+                dr += TWO_PI;
+            sh.modifyOrbit(dr, duration);
+            // console.log('  [%s] r=%s, saw=%s, dr=%s', i, r.toFixed(3), sh.radialPosition.toFixed(3), dr.toFixed(3));
+        });
+        return this;
+    }
+    
+    
+})
+;
index 0911bd6..f1e441c 100644 (file)
@@ -31,10 +31,13 @@ Item.subclass('ShieldGenerator', {
     onAcquired : function onAcquired(evt, container){
         Item.fn.onAcquired.call(this, evt, container);
         
+        var owner = this.owner;
         for (var i=0, di = 1/this.spheres; i<1; i += di) {
-            // Component.init will add it to the owner's bookkeeping
-            Shield.create('shield', this.owner, i * TWO_PI);
+            var s = Shield.create('shield', owner, i * TWO_PI);
+            // owner.addComponent(s);
+            owner.shields.add(s);
         }
+        owner.shields.rebalance();
     },
     
     render : function render(parent){
index 18bbb48..91c3b67 100644 (file)
@@ -33,9 +33,10 @@ Thing.subclass('Component', {
     
     
     init : function initComponent(owner){
-        this.owner = owner.addComponent(this);
+        this.owner = owner;
         this.game  = owner.game;
         
+        // owner.addComponent(this);
         Thing.init.call(this, owner.align);
     }
     
index 7f57b54..568bfc1 100644 (file)
@@ -44,14 +44,17 @@ Component.subclass('Shield', {
     radius : 0,
     radsPerTick : 0,
     radialPosition : 0,
+    radialStart : 0,
     
+    tweakRads : 0,
+    tweakDuration : 0,
     
     
     init : function initShield(owner, radialPosition){
         Component.init.call(this, owner);
         
         var obb = owner.bbox, oloc = owner.loc;
-        this.radialPosition = radialPosition || 0;
+        this.radialStart = this.radialPosition = radialPosition || 0;
         this.radius = this.orbit + SQRT_TWO * Math.max(obb.width, obb.height) / 2;
         this.radsPerTick = TWO_PI * this.stats.move.val / 1000 * MS_PER_FRAME;
         
@@ -60,24 +63,50 @@ Component.subclass('Shield', {
         this.act(0);
     },
     
-    testCollide : function testCollide(agent, bbox, to, traversal){
-        return agent.isProjectile && (agent.align !== this.align);
+    setRadialPosition : function setRadialPosition(radialPosition){
+        this.radialPosition = this.trajectory.tCurrent = (radialPosition % TWO_PI);
+        return this;
+    },
+    
+    /**
+     * @param {Number} deltaRads Change in radial position to enact (radians).
+     * @param {Number} duration Number of ticks over which to make change.
+     * @return {this}
+     */
+    modifyOrbit : function modifyOrbit(deltaRads, duration){
+        this.tweakRads = deltaRads / duration;
+        this.tweakDuration = duration;
+        return this;
     },
     
-    act : function act(elapsed, now){
+    act : function act(elapsed, now, ticks){
         if (this.dead) return this;
         
         var oloc = this.owner.loc
-        ,   tr = this.trajectory;
+        ,   tr = this.trajectory
+        ,   rads = this.radsPerTick + this.tweakRads
+        ;
         tr.x = oloc.x; tr.y = oloc.y;
         
         var tvsl = new Traversal(this)
-        ,   to = tvsl.traverse(this.radsPerTick);
+        ,   to = tvsl.traverse(rads)
+        ;
         
+        this.radialPosition = tr.tCurrent % TWO_PI;
         this.game.moveThingTo(this, to.x,to.y);
+        
+        this.tweakDuration--;
+        if (this.tweakDuration <= 0) {
+            this.tweakDuration = this.tweakRads = 0;
+        }
+        
         return this;
     },
     
+    testCollide : function testCollide(agent, bbox, to, traversal){
+        return agent.isProjectile && (agent.align !== this.align);
+    },
+    
     render : function render(parent){
         this.remove();
         
index 0a35c23..c65e8e7 100644 (file)
@@ -19,7 +19,7 @@ var Y           = require('Y').Y
 
 
 /**
- * A Thing is any object that can be added to the Map.
+ * A Thing is any object that can (usually) be added to the Map.
  */
 Thing =
 exports['Thing'] =
@@ -71,6 +71,7 @@ new evt.Class('Thing', {
     isShield     : false,
     
     isActive     : true,        // Agent takes actions?
+    addToMap     : true,        // Add Agent as distinct object on map? (Some components are treated as part of their owner for map purposes)
     isRenderable : false,       // Agent will present itself for rendering when ready // FIXME: stupid hack
     isReflective : false,       // Projectiles bounce off agent rather than explode?
     hasInventory : false,       // Agent can acquire items?
index f56999f..16220c3 100644 (file)
@@ -9,6 +9,7 @@ var Y = require('Y').Y
 ,   Lootable         = require('tanks/mixins/lootable').Lootable
 ,   LinearTrajectory = require('tanks/map/pathing/lineartrajectory').LinearTrajectory
 ,   Traversal        = require('tanks/map/pathing/traversal').Traversal
+,   ShieldManager    = require('tanks/fx/shieldmanager').ShieldManager
 
 ,   _X = 0, _Y = 1
 ,   RECOIL_DURATION  = 250              // duration recoil push lasts (ms)
@@ -37,6 +38,7 @@ Thing.subclass('Unit', {
     
     align : null,
     buffs : null,
+    shields : null,
     
     nShots : 0,
     
@@ -46,6 +48,7 @@ Thing.subclass('Unit', {
         this.recoil = Y.extend({}, this.recoil);
         this.colors = Y.extend({}, this.colors);
         this.projectile = this.defaultProjectile = Bullet.lookup(this.projectile);
+        this.shields = new ShieldManager(this);
     },
     
     onBulletDeath : function onBulletDeath(evt){