From: dsc Date: Mon, 11 Apr 2011 11:26:10 +0000 (-0700) Subject: Adds Set class; Adds ShieldManager, allowing shield orbits to be rebalanced. X-Git-Url: http://git.less.ly:3516/?a=commitdiff_plain;h=1293e0ff2ee2d94fe9d8894cfe226ae7b80179c1;p=tanks.git Adds Set class; Adds ShieldManager, allowing shield orbits to be rebalanced. --- diff --git a/data/types/items.yaml b/data/types/items.yaml index b11c909..152265f 100644 --- a/data/types/items.yaml +++ b/data/types/items.yaml @@ -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' diff --git a/data/types/levels.yaml b/data/types/levels.yaml index 2e90be2..0d9b6fa 100644 --- a/data/types/levels.yaml +++ b/data/types/levels.yaml @@ -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 diff --git a/src/ezl/struct/index.cjs b/src/ezl/struct/index.cjs index a3427d5..2eec106 100644 --- a/src/ezl/struct/index.cjs +++ b/src/ezl/struct/index.cjs @@ -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 }); diff --git a/src/ezl/struct/set.cjs b/src/ezl/struct/set.cjs index 0488796..cee557d 100644 --- a/src/ezl/struct/set.cjs +++ b/src/ezl/struct/set.cjs @@ -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; /** diff --git a/src/tanks/fx/index.cjs b/src/tanks/fx/index.cjs index 698054c..e0ad2ad 100644 --- a/src/tanks/fx/index.cjs +++ b/src/tanks/fx/index.cjs @@ -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 index 0000000..30a91f6 --- /dev/null +++ b/src/tanks/fx/shieldmanager.cjs @@ -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; + } + + +}) +; diff --git a/src/tanks/item/shieldgen.cjs b/src/tanks/item/shieldgen.cjs index 0911bd6..f1e441c 100644 --- a/src/tanks/item/shieldgen.cjs +++ b/src/tanks/item/shieldgen.cjs @@ -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){ diff --git a/src/tanks/thing/component.cjs b/src/tanks/thing/component.cjs index 18bbb48..91c3b67 100644 --- a/src/tanks/thing/component.cjs +++ b/src/tanks/thing/component.cjs @@ -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); } diff --git a/src/tanks/thing/shield.cjs b/src/tanks/thing/shield.cjs index 7f57b54..568bfc1 100644 --- a/src/tanks/thing/shield.cjs +++ b/src/tanks/thing/shield.cjs @@ -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(); diff --git a/src/tanks/thing/thing.cjs b/src/tanks/thing/thing.cjs index 0a35c23..c65e8e7 100644 --- a/src/tanks/thing/thing.cjs +++ b/src/tanks/thing/thing.cjs @@ -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? diff --git a/src/tanks/thing/unit.cjs b/src/tanks/thing/unit.cjs index f56999f..16220c3 100644 --- a/src/tanks/thing/unit.cjs +++ b/src/tanks/thing/unit.cjs @@ -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){