From: dsc Date: Thu, 31 Mar 2011 12:29:10 +0000 (-0700) Subject: Adds recoil. X-Git-Url: http://git.less.ly:3516/?a=commitdiff_plain;h=13df4b41b667932e0b73120f952b28f25e87c0e7;p=tanks.git Adds recoil. --- diff --git a/data/types/units.yaml b/data/types/units.yaml index fab1473..feb94b7 100644 --- a/data/types/units.yaml +++ b/data/types/units.yaml @@ -5,28 +5,29 @@ defaults: projectile: normal lootTable : '' stats: - hp : 1 # health - move : 1.0 # move speed (squares/sec or orbits/sec) - rotate : 1.0 # rotation speed (full-turns/sec) - power : 1 # attack power - speed : 0.5 # attack cool (sec) - accuracy : 1.0 # chance of shooting where aiming - shots : 5 # max projectiles in the air at once - sight : 5 # distance this unit can see (squares) + hp : 1 # health + move : 1.0 # move speed (squares/sec or orbits/sec) + rotate : 1.0 # rotation speed (full-turns/sec) + power : 1 # attack power + recoil : 1.0 # pushback on shoot (1/5 squares) + speed : 0.5 # attack cool (sec) + accuracy : 1.0 # chance of shooting where aiming + shots : 5 # max projectiles in the air at once + sight : 5 # distance this unit can see (squares) cooldowns: attack: stats.speed.val ai: - path : 1.00 # calculate a path to enemy - dodge : 1.00 # dodge an incoming bullet - shootIncoming : 0.25 # shoot down incoming bullet - shootEnemy : 0.75 # shoot at enemy tank if in range + path : 1.00 # calculate a path to enemy + dodge : 1.00 # dodge an incoming bullet + shootIncoming : 0.25 # shoot down incoming bullet + shootEnemy : 0.75 # shoot at enemy tank if in range barrel: - width : unit.width * 0.75 - height : unit.height / 6 - originX : 2 - originY : 50% - x : 50% - y : 50% + width : unit.width * 0.75 + height : unit.height / 6 + originX : 2 + originY : 50% + x : 50% + y : 50% art: map_icon: '' inv_icon: '' @@ -105,11 +106,12 @@ types: symbol: tanks/thing/tank.Tank lootTable : rich stats: - hp : 1 - move : 0.45 - power : 1 - speed : 0.35 - shots : 3 + hp : 1 + move : 0.45 + power : 1 + recoil : 0.5 + speed : 0.35 + shots : 3 ai: path : 1.0 dodge : 1.0 @@ -142,11 +144,12 @@ types: tags: [ 'tower' ] symbol: tanks/thing/tower.Tower stats: - hp : 1 - move : 0 - power : 1 - speed : 0.5 - shots : 3 + hp : 1 + move : 0 + power : 1 + recoil : 0 + speed : 0.5 + shots : 3 ai: shootIncoming : 0.10 shootEnemy : 0.35 diff --git a/src/tanks/map/pathing/circular-trajectory.cjs b/src/tanks/map/pathing/circulartrajectory.cjs similarity index 100% rename from src/tanks/map/pathing/circular-trajectory.cjs rename to src/tanks/map/pathing/circulartrajectory.cjs diff --git a/src/tanks/map/pathing/index.cjs b/src/tanks/map/pathing/index.cjs index ea3502a..76e73e4 100644 --- a/src/tanks/map/pathing/index.cjs +++ b/src/tanks/map/pathing/index.cjs @@ -10,5 +10,5 @@ require('Y').Y 'Map' : require('tanks/map/pathing/map').Map, 'Traversal' : require('tanks/map/pathing/traversal').Traversal, 'LinearTrajectory' : require('tanks/map/pathing/lineartrajectory').LinearTrajectory, - 'CircularTrajectory' : require('tanks/map/pathing/circular-trajectory').CircularTrajectory + 'CircularTrajectory' : require('tanks/map/pathing/circulartrajectory').CircularTrajectory }); diff --git a/src/tanks/map/pathing/traversal.cjs b/src/tanks/map/pathing/traversal.cjs index 826b027..70b2aa4 100644 --- a/src/tanks/map/pathing/traversal.cjs +++ b/src/tanks/map/pathing/traversal.cjs @@ -16,6 +16,7 @@ Y.subclass('Traversal', { isBlocked : false, to : null, // furthest point reached bbox : null, // current agent position (cloned) + consumed : 0, // time consumed so far remaining : 0, // time left unconsumed due to blocker blocker : null, // blocking object side : null, // collision side of blocker (Line) @@ -179,6 +180,7 @@ Y.subclass('Traversal', { consumeTime : function consumeTime(t){ this.trajectory.tCurrent += t; + this.consumed += t; this.remaining = Math.max(0, this.remaining-t); return this; } diff --git a/src/tanks/thing/player.cjs b/src/tanks/thing/player.cjs index af6c56d..bbf2e07 100644 --- a/src/tanks/thing/player.cjs +++ b/src/tanks/thing/player.cjs @@ -77,6 +77,8 @@ Tank.subclass('Player', { if (this.dead) return this; + this.applyRecoilMove(); + var action; if (this.replayMode) { action = this.nextMove; diff --git a/src/tanks/thing/shield.cjs b/src/tanks/thing/shield.cjs index fad604b..7f57b54 100644 --- a/src/tanks/thing/shield.cjs +++ b/src/tanks/thing/shield.cjs @@ -6,7 +6,7 @@ var Y = require('Y').Y , Thing = require('tanks/thing/thing').Thing , Component = require('tanks/thing/component').Component , Traversal = require('tanks/map/pathing/traversal').Traversal -, CircularTrajectory = require('tanks/map/pathing/circular-trajectory').CircularTrajectory +, CircularTrajectory = require('tanks/map/pathing/circulartrajectory').CircularTrajectory , SQRT_TWO = Math.sqrt(2) , TWO_PI = 2 * Math.PI diff --git a/src/tanks/thing/tank.cjs b/src/tanks/thing/tank.cjs index bed6e6d..da1fc80 100644 --- a/src/tanks/thing/tank.cjs +++ b/src/tanks/thing/tank.cjs @@ -38,6 +38,7 @@ Unit.subclass('Tank', function(Tank){ barrel : '#D43B24' }, + hasBarrel : true, barrel: { width : 'unit.width * 0.75', height : 'unit.height / 6', @@ -71,6 +72,12 @@ Unit.subclass('Tank', function(Tank){ forceCurrentMove : false, currentMoveLimit : -1, + recoil : { + trajectory : null, + remaining : 0, + speed : 0 + }, + nShots : 0 }); @@ -99,6 +106,8 @@ Unit.subclass('Tank', function(Tank){ this.elapsed = elapsed; this.now = now; + if (mobile) this.applyRecoilMove(); + // Check to see if we should obey our last decision, and not recalc if (mobile && this.forceCurrentMove && this.forceCurrentMove() && this.currentMoveLimit > now) { // console.log('forced!', this.currentMove); @@ -159,47 +168,47 @@ Unit.subclass('Tank', function(Tank){ * @param {Number} [x] Target X coordinate. * @param {Number} [y] Target Y coordinate. */ - this['shoot'] = - function shoot(x,y){ - if ( this.nShots >= this.stats.shots.val || !this.cooldowns.attack.ready ) - return null; - - if (x instanceof Array) { y = x[_Y]; x = x[_X]; } - var xydef = (x !== undefined && y !== undefined); - if (xydef) - this.rotateBarrel(x,y); - - // Additional space on each side which must be clear around the - // shot to ensure we don't shoot ourself in the foot (literally) - var WIGGLE = 2 - , Projectile = this.projectile - , pw2 = Projectile.fn.width/2, ph2 = Projectile.fn.height/2 - - , tloc = this.getTurretLoc() - , tx = tloc.x, ty = tloc.y - - , x1 = tx - pw2 - WIGGLE, y1 = ty - ph2 - WIGGLE - , x2 = tx + pw2 + WIGGLE, y2 = ty + ph2 + WIGGLE - , blockers = this.game.map.get(x1,y1, x2,y2).filter(filterShoot, this) - ; - - if ( blockers.size() ) - return null; // console.log('squelch!', blockers); - - if (!xydef) { - var theta = this.barrelShape.transform.rotate - , sin = Math.sin(theta), cos = Math.cos(theta); - x = tx + REF_SIZE*cos; - y = ty + REF_SIZE*sin; - } - - this.cooldowns.attack.activate(this.now); - this.nShots++; - - var p = new Projectile(this, tx,ty, x,y); - p.on('destroy', this.onBulletDeath); - return p; - }; + // this['shoot'] = + // function shoot(x,y){ + // if ( this.nShots >= this.stats.shots.val || !this.cooldowns.attack.ready ) + // return null; + // + // if (x instanceof Array) { y = x[_Y]; x = x[_X]; } + // var xydef = (x !== undefined && y !== undefined); + // if (xydef) + // this.rotateBarrel(x,y); + // + // // Additional space on each side which must be clear around the + // // shot to ensure we don't shoot ourself in the foot (literally) + // var WIGGLE = 2 + // , Projectile = this.projectile + // , pw2 = Projectile.fn.width/2, ph2 = Projectile.fn.height/2 + // + // , tloc = this.getTurretLoc() + // , tx = tloc.x, ty = tloc.y + // + // , x1 = tx - pw2 - WIGGLE, y1 = ty - ph2 - WIGGLE + // , x2 = tx + pw2 + WIGGLE, y2 = ty + ph2 + WIGGLE + // , blockers = this.game.map.get(x1,y1, x2,y2).filter(filterShoot, this) + // ; + // + // if ( blockers.size() ) + // return null; // console.log('squelch!', blockers); + // + // if (!xydef) { + // var theta = this.barrelShape.transform.rotate + // , sin = Math.sin(theta), cos = Math.cos(theta); + // x = tx + REF_SIZE*cos; + // y = ty + REF_SIZE*sin; + // } + // + // this.cooldowns.attack.activate(this.now); + // this.nShots++; + // + // var p = new Projectile(this, tx,ty, x,y); + // p.on('destroy', this.onBulletDeath); + // return p; + // }; function filterShoot(v){ return (v !== this) && (v.density === DensityType.BOUNDARY || (v.isWall && v.density === DensityType.DENSE)); @@ -214,20 +223,20 @@ Unit.subclass('Tank', function(Tank){ /** * @return {this} */ - this['move'] = - function move(x,y){ - if (x instanceof Array) { y=x[_Y]; x=x[_X]; } - - var loc = this.loc; - this.trajectory = new LinearTrajectory(this, loc.x,loc.y, x,y, this.movePerMs()); - - var tvsl = new Traversal(this) - , to = tvsl.traverse(this.elapsed, x,y) ; - - this.game.moveThingTo(this, to.x,to.y); - - return this; - }; + // this['move'] = + // function move(x,y){ + // if (x instanceof Array) { y=x[_Y]; x=x[_X]; } + // + // var loc = this.loc; + // this.trajectory = new LinearTrajectory(this, loc.x,loc.y, x,y, this.movePerMs()); + // + // var tvsl = new Traversal(this) + // , to = tvsl.traverse(this.elapsed, x,y) ; + // + // this.game.moveThingTo(this, to.x,to.y); + // + // return this; + // }; this['continueMove'] = function continueMove(){ @@ -372,6 +381,10 @@ Unit.subclass('Tank', function(Tank){ return new Vec(x,y); }; + this['getBarrelAngle'] = + function getBarrelAngle(){ + return this.barrelShape.transform.rotate; + }; this['rotateBarrel'] = function rotateBarrel(x,y){ diff --git a/src/tanks/thing/thing.cjs b/src/tanks/thing/thing.cjs index 4982eea..0a35c23 100644 --- a/src/tanks/thing/thing.cjs +++ b/src/tanks/thing/thing.cjs @@ -74,6 +74,7 @@ new evt.Class('Thing', { 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? + hasBarrel : false, dropOnDeath : false, // Agent drops all items in inventory on death? // Location diff --git a/src/tanks/thing/tower.cjs b/src/tanks/thing/tower.cjs index b8ca0cc..a62a373 100644 --- a/src/tanks/thing/tower.cjs +++ b/src/tanks/thing/tower.cjs @@ -27,6 +27,7 @@ exports['Tower'] = Unit.subclass('Tower', function(Tower){ Y.core.descriptors(this, { + isUnit : true, isCombatant : true, lootTable : '', @@ -35,6 +36,7 @@ Unit.subclass('Tower', function(Tower){ barrel : '#244792' }, + hasBarrel : true, barrel: { width : 'unit.width * 0.75', height : 'unit.height / 6', diff --git a/src/tanks/thing/unit.cjs b/src/tanks/thing/unit.cjs index ea00ed2..d081c34 100644 --- a/src/tanks/thing/unit.cjs +++ b/src/tanks/thing/unit.cjs @@ -1,20 +1,99 @@ var Y = require('Y').Y +, Vec = require('ezl/math/vec').Vec + +, constants = require('tanks/constants') +, BoundsType = constants.BoundsType +, DensityType = constants.DensityType , Thing = require('tanks/thing/thing').Thing , LinearTrajectory = require('tanks/map/pathing/lineartrajectory').LinearTrajectory , Traversal = require('tanks/map/pathing/traversal').Traversal + +, _X = 0, _Y = 1 +, RECOIL_DURATION = 250 // duration recoil push lasts (ms) +, RECOIL_DIST_UNIT = REF_SIZE * 0.2 // unit distance for recoil=1.0 stat (px) , Unit = exports['Unit'] = Thing.subclass('Unit', { - isUnit : true, + isUnit : true, isCombatant : true, + hasBarrel : false, + recoil : { + remaining : 0, + speed : 0, + vec : null + }, nShots : 0, init : function initUnit(align){ Thing.init.call(this, align); + this.recoil = Y.extend({}, this.recoil); + }, + + + /** + * Fires this agent's cannon. If a target location is omitted, the shot + * will be fired in the direction of the tank's current barrel rotation. + * + * @param {Number} [x] Target X coordinate. + * @param {Number} [y] Target Y coordinate. + */ + shoot : function shoot(x,y){ + if ( !this.ableToShoot() ) + return null; + + if (x instanceof Array) { y = x[_Y]; x = x[_X]; } + var xydef = (x !== undefined && y !== undefined); + if (xydef && this.hasBarrel) + this.rotateBarrel(x,y); + + // Additional space on each side which must be clear around the + // shot to ensure we don't shoot ourself in the foot (literally) + var WIGGLE = 2 + , Projectile = this.projectile + , pw2 = Projectile.fn.width/2, ph2 = Projectile.fn.height/2 + + , tloc = this.getTurretLoc() + , tx = tloc.x, ty = tloc.y + + , x1 = tx - pw2 - WIGGLE, y1 = ty - ph2 - WIGGLE + , x2 = tx + pw2 + WIGGLE, y2 = ty + ph2 + WIGGLE + , blockers = this.game.map.get(x1,y1, x2,y2).filter(this._filterShoot, this) + ; + + if ( blockers.size() ) + return null; // console.log('squelch!', blockers); + + if (!xydef && this.hasBarrel) { + var theta = this.getBarrelAngle() + , sin = Math.sin(theta), cos = Math.cos(theta); + x = tx + REF_SIZE*cos; + y = ty + REF_SIZE*sin; + } + + this.cooldowns.attack.activate(this.now); + this.nShots++; + + // Recoil + // TODO: allow stacking recoil + var recoil_dist = this.stats.recoil.val * RECOIL_DIST_UNIT + , recoil_speed = recoil_dist / RECOIL_DURATION + ; + this.recoil.vec = new Vec(tx-x, ty-y); + this.recoil.remaining = RECOIL_DURATION; + this.recoil.speed = recoil_speed; + this.applyRecoilMove(); + + var p = new Projectile(this, tx,ty, x,y); + p.on('destroy', this.onBulletDeath); + return p; + }, + _filterShoot : function filterShoot(v){ + return (v !== this) + && (v.density === DensityType.BOUNDARY || (v.isWall && v.density === DensityType.DENSE)); }, ableToShoot : function ableToShoot(){ @@ -31,8 +110,42 @@ Thing.subclass('Unit', { , to = tvsl.traverse(this.elapsed, x,y) ; this.game.moveThingTo(this, to.x,to.y); + // this.applyRecoilMove(); return this; - } + }, + + applyRecoilMove : function applyRecoilMove(){ + var r = this.recoil + , remaining = Math.min(r.remaining, this.elapsed) + ; + + if ( remaining <= 0 ) + return this; + + var vec = r.vec + , loc = this.loc + , traj = new LinearTrajectory(this, loc.x,loc.y, loc.x+vec.x,loc.y+vec.y, r.speed) + , tvsl = new Traversal(this, traj) + , to = tvsl.traverse(remaining) ; + + remaining = (r.remaining -= tvsl.consumed); + this.game.moveThingTo(this, to.x,to.y); + + if ( remaining <= 0 ) { + r.remaining = r.speed = 0; + r.vec = null; + } + + return this; + }, + + /** + * Calculates the location of the bullet-spawning point on this Thing. + */ + getTurretLoc : function getTurretLoc(){ return this.loc; }, + getBarrelAngle : function getBarrelAngle(){ return 0; }, + rotateBarrel : function rotateBarrel(){}, + rotateBarrelRelPage : function rotateBarrelRelPage(){} }) ;