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'
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
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
});
Set =
exports['Set'] =
-YCollection.subclass('Set', null, function setupSet(Set){
+YCollection.subclass('Set', function setupSet(Set){
Y.extend(this, {
_byId : {},
});
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
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'] =
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;
};
this['update'] =
this['extend'] =
function update(vs){
- Y(vs).forEach(addIt, this);
+ Y(vs).forEach(this._addOne, this);
return this;
};
return this.clone().update(vs);
};
- function removeIt(v){
+ this['_removeOne'] =
+ function _removeOne(v){
var id = this._getId(v);
if ( id in this._byId ) {
}
return this;
- }
+ };
this['remove'] =
this['discard'] =
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;
};
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;
/**
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
});
--- /dev/null
+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;
+ }
+
+
+})
+;
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){
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);
}
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;
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();
/**
- * 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'] =
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?
, 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)
align : null,
buffs : null,
+ shields : null,
nShots : 0,
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){