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: ''
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
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
'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
});
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)
consumeTime : function consumeTime(t){
this.trajectory.tCurrent += t;
+ this.consumed += t;
this.remaining = Math.max(0, this.remaining-t);
return this;
}
if (this.dead)
return this;
+ this.applyRecoilMove();
+
var action;
if (this.replayMode) {
action = this.nextMove;
, 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
barrel : '#D43B24'
},
+ hasBarrel : true,
barrel: {
width : 'unit.width * 0.75',
height : 'unit.height / 6',
forceCurrentMove : false,
currentMoveLimit : -1,
+ recoil : {
+ trajectory : null,
+ remaining : 0,
+ speed : 0
+ },
+
nShots : 0
});
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);
* @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));
/**
* @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(){
return new Vec(x,y);
};
+ this['getBarrelAngle'] =
+ function getBarrelAngle(){
+ return this.barrelShape.transform.rotate;
+ };
this['rotateBarrel'] =
function rotateBarrel(x,y){
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
Unit.subclass('Tower', function(Tower){
Y.core.descriptors(this, {
+ isUnit : true,
isCombatant : true,
lootTable : '',
barrel : '#244792'
},
+ hasBarrel : true,
barrel: {
width : 'unit.width * 0.75',
height : 'unit.height / 6',
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(){
, 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(){}
})
;