Adds recoil.
authordsc <david.schoonover@gmail.com>
Thu, 31 Mar 2011 12:29:10 +0000 (05:29 -0700)
committerdsc <david.schoonover@gmail.com>
Thu, 31 Mar 2011 12:29:10 +0000 (05:29 -0700)
data/types/units.yaml
src/tanks/map/pathing/circulartrajectory.cjs [moved from src/tanks/map/pathing/circular-trajectory.cjs with 100% similarity]
src/tanks/map/pathing/index.cjs
src/tanks/map/pathing/traversal.cjs
src/tanks/thing/player.cjs
src/tanks/thing/shield.cjs
src/tanks/thing/tank.cjs
src/tanks/thing/thing.cjs
src/tanks/thing/tower.cjs
src/tanks/thing/unit.cjs

index fab1473..feb94b7 100644 (file)
@@ -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
index ea3502a..76e73e4 100644 (file)
@@ -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
 });
index 826b027..70b2aa4 100644 (file)
@@ -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;
     }
index af6c56d..bbf2e07 100644 (file)
@@ -77,6 +77,8 @@ Tank.subclass('Player', {
         if (this.dead)
             return this;
         
+        this.applyRecoilMove();
+        
         var action;
         if (this.replayMode) {
             action = this.nextMove;
index fad604b..7f57b54 100644 (file)
@@ -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
index bed6e6d..da1fc80 100644 (file)
@@ -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){
index 4982eea..0a35c23 100644 (file)
@@ -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
index b8ca0cc..a62a373 100644 (file)
@@ -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',
index ea00ed2..d081c34 100644 (file)
@@ -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(){}
 })
 ;