Adds Tower enemies.
authordsc <david.schoonover@gmail.com>
Sat, 26 Mar 2011 14:10:20 +0000 (07:10 -0700)
committerdsc <david.schoonover@gmail.com>
Sat, 26 Mar 2011 14:10:20 +0000 (07:10 -0700)
data/types/levels.yaml
data/types/units.yaml
src/tanks/thing/index.cjs
src/tanks/thing/tank.cjs
src/tanks/thing/tower.cjs [new file with mode: 0644]
src/tanks/thing/unit.cjs [new file with mode: 0644]

index c970003..2e90be2 100644 (file)
@@ -124,7 +124,7 @@ types:
           - type: player
             align: 1
             loc: [375,475]
-          - type: green
+          - type: tower
             align: 2
             loc: [75,25]
           - type: green
@@ -143,12 +143,12 @@ 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
-            loc: [325,25]
+          # - type: nitro
+          #   loc: [325,25]
     
     small_test:
         name: Da Small Test
@@ -370,4 +370,5 @@ types:
           - type: green
             align: 2
             loc: [225,325]
+    
 
index d1913d0..fab1473 100644 (file)
@@ -136,4 +136,22 @@ types:
             body  : '#0A9CFF'
             shine : '#195FBC'
     
+    tower:
+        name: Tower
+        desc: A stoic defense tower.
+        tags: [ 'tower' ]
+        symbol: tanks/thing/tower.Tower
+        stats:
+            hp    : 1
+            move  : 0
+            power : 1
+            speed : 0.5
+            shots : 3
+        ai:
+            shootIncoming : 0.10
+            shootEnemy    : 0.35
+        colors:
+            body   : '#980011'
+            barrel : '#D43B24'
+        
     
index e1b534d..d4fd8a5 100644 (file)
@@ -5,12 +5,16 @@ var Y = require('Y').Y
 ,   shield    = require('tanks/thing/shield')
 ,   tank      = require('tanks/thing/tank')
 ,   thing     = require('tanks/thing/thing')
+,   tower     = require('tanks/thing/tower')
+// ,   unit      = require('tanks/thing/unit')
 ;
 Y.core.extend(exports, {
-       'Bullet'    : bullet.Bullet,
-       'Component' : component.Component,
-       'Player'    : player.Player,
-       'Shield'    : shield.Shield,
-       'Tank'      : tank.Tank,
-       'Thing'     : thing.Thing
+    'Bullet'    : bullet.Bullet,
+    'Component' : component.Component,
+    'Player'    : player.Player,
+    'Shield'    : shield.Shield,
+    'Tank'      : tank.Tank,
+    'Thing'     : thing.Thing,
+    'Tower'     : tower.Tower,
+    // 'Unit'      : unit.Unit
 });
index ffe8c34..a30cf84 100644 (file)
@@ -91,12 +91,15 @@ Thing.subclass('Tank', function(Tank){
     
     this['act'] =
     function act(elapsed, now){
-        var ai = this.ai, map = this.game.map;
+        var ai = this.ai
+        ,   map = this.game.map
+        ,   mobile = this.stats.move.val > 0
+        ;
         this.elapsed = elapsed;
         this.now = now;
         
         // Check to see if we should obey our last decision, and not recalc
-        if (this.forceCurrentMove && this.forceCurrentMove() && this.currentMoveLimit > now) {
+        if (mobile && this.forceCurrentMove && this.forceCurrentMove() && this.currentMoveLimit > now) {
             // console.log('forced!', this.currentMove);
             this.continueMove();
             return this;
@@ -118,7 +121,7 @@ Thing.subclass('Tank', function(Tank){
         }
         
         // Dodge incoming bullet
-        if (ai.dodge.ready) {
+        if (mobile && ai.dodge.ready) {
             var bs = map.willCollide(this, map.findNearBullets(this, 71), 5);
             // console.log('['+TICKS+':'+this.id, this, '] Dodge bullets?', bs.size() && bs);
             if (bs.size()) {
@@ -142,7 +145,7 @@ Thing.subclass('Tank', function(Tank){
         }
         
         // Nothing to shoot at? Move toward something
-        this.continueMove();
+        if (mobile) this.continueMove();
         this.components.invoke('act', elapsed, now);
         return this;
     };
diff --git a/src/tanks/thing/tower.cjs b/src/tanks/thing/tower.cjs
new file mode 100644 (file)
index 0000000..393dc7d
--- /dev/null
@@ -0,0 +1,270 @@
+var Y  = require('Y').Y
+,   op = require('Y/op')
+
+,   vec           = require('ezl/math/vec')
+,   Vec           = vec.Vec
+,   Cooldown      = require('ezl/loop').Cooldown
+,   CooldownGauge = require('ezl/widget').CooldownGauge
+,   shape         = require('ezl/shape')
+,   Circle        = shape.Circle
+
+,   constants   = require('tanks/constants')
+,   BoundsType  = constants.BoundsType
+,   DensityType = constants.DensityType
+,   LinearTrajectory = require('tanks/map/pathing/linear-trajectory').LinearTrajectory
+,   Traversal   = require('tanks/map/pathing/traversal').Traversal
+,   Thing       = require('tanks/thing/thing').Thing
+,   Bullet      = require('tanks/thing/bullet').Bullet
+,   Lootable    = require('tanks/mixins/lootable').Lootable
+,   Barrel      = require('tanks/fx/barrel').Barrel
+
+,   _X = 0, _Y = 1
+,
+
+
+Tower =
+exports['Tower'] =
+Thing.subclass('Tower', function(Tower){
+    
+    Y.core.descriptors(this, {
+        isCombatant : true,
+        lootTable : '',
+        
+        colors : {
+            body   : '#980011',
+            barrel : '#244792'
+        },
+        
+        barrel: {
+            width   : 'unit.width * 0.75',
+            height  : 'unit.height / 6',
+            originX : 2,
+            originY : '50%',
+            x       : '50%',
+            y       : '50%'
+        },
+        
+        // Bounding box
+        width  : REF_SIZE*0.55,
+        height : REF_SIZE*0.55,
+        
+        // Attributes
+        stats : {},
+        
+        // AI "Cooldowns" (max frequency of each action per sec)
+        ai : {},
+        
+        projectile : 'bullet',
+        defaultProjectile : null,
+        
+        /// Instance ///
+        
+        align : null,
+        buffs : null,
+        
+        nShots : 0
+    });
+    
+    this['init'] =
+    function initTower(align){
+        Thing.init.call(this, align);
+        this.projectile = this.defaultProjectile = Bullet.lookup(this.projectile);
+        this.colors = Y.extend({}, this.colors);
+        this.atkGauge = new CooldownGauge(this.cooldowns.attack, this.width+1,this.height+1);
+        this.onBulletDeath = this.onBulletDeath.bind(this);
+    };
+    
+    Lootable.mixInto(Tower);
+    
+    this['onBulletDeath'] =
+    function onBulletDeath(evt){ this.nShots--; };
+    
+    
+    
+    this['act'] =
+    function act(elapsed, now){
+        var ai = this.ai, map = this.game.map;
+        this.elapsed = elapsed;
+        this.now = now;
+        
+        // Try to shoot down nearby bullets
+        if (ai.shootIncoming.ready && this.ableToShoot()) {
+            var bs = map.willCollide(this,  map.findNearBullets(this, 25) );
+            // console.log('['+TICKS+':'+this.id, this, '] Shoot down bullets?', bs.size() && bs);
+            if ( bs.size() ) {
+                ai.shootIncoming.activate(now);
+                var b = map.closestOf(this, bs);
+                // console.log('  --> Incoming! Shoot it down!', b);
+                this.shoot(b.loc.x, b.loc.y);
+                return this;
+            }
+        }
+        
+        // Try to blow up nearby tanks
+        if (ai.shootEnemy.ready && (this.stats.shots.val - this.nShots > 1)) {
+            var t = map.findNearEnemiesInSight(this).shift();
+            // console.log('['+TICKS+':'+this.id, this, '] Shoot at enemies?', t);
+            if (t) {
+                ai.shootEnemy.activate(now);
+                // console.log('  --> I gotcha!', t);
+                this.shoot(t.loc.x, t.loc.y);
+                return this;
+            }
+        }
+        
+        this.components.invoke('act', elapsed, now);
+        return this;
+    };
+    
+    
+    /**
+     * 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.
+     */
+    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));
+    }
+    
+    this['ableToShoot'] =
+    function ableToShoot(){
+        return this.nShots < this.stats.shots.val && this.cooldowns.attack.ready;
+    };
+    
+    
+    
+    this['getTurretLoc'] =
+    function getTurretLoc(){
+        var WIGGLE = 2
+        ,   loc    = this.loc
+        ,   barrel = this.barrelShape
+        
+        ,   theta  = barrel.transform.rotate
+        ,   sin = Math.sin(theta),  cos = Math.cos(theta)
+        
+        // sqrt(2)/2 * (P.width + WIGGLE)
+        // is max diagonal to ensure we don't overlap with the firing unit
+        ,   pw  = this.projectile.fn.width
+        ,   len = barrel.bbox.width + 0.707*(pw+WIGGLE)
+        
+        ,   x = loc.x + len*cos
+        ,   y = loc.y + len*sin
+        ;
+        return new Vec(x,y);
+    };
+    
+    
+    
+    
+    /// Rendering Methods ///
+    
+    /**
+     * Sets up unit appearance for minimal updates. Called once at start,
+     * or when the world needs to be redrawn from scratch.
+     */
+    this['render'] =
+    function render(parent){
+        if (this.shape) this.shape.remove();
+        
+        var colors = this.colors
+        ,   w = this.width,  w2 = w/2
+        ,   h = this.height, h2 = h/2
+        ,   r  = w / 2
+        ;
+        
+        this.shape = 
+            new Circle(r)
+                .appendTo( parent )
+                .position(this.loc.x, this.loc.y)
+                .fill(colors.body)
+            ;
+        
+        // TODO: Bleeds outside of circle
+        // if (this.showAttackCooldown)
+        //     this.shape.append(this.atkGauge);
+        
+        var b = Y.map(this.barrel, calcValue, this);
+        
+        this.barrelShape =
+            new Barrel(b.width,b.height, b.originX,b.originY)
+                .appendTo( this.shape ) // have to append early to avoid problems with relative positioning
+                .position(b.x, b.y)
+                // .position(w2-2, h2-bh/2)
+                // .origin(2, bh/2)
+                .fill( colors.barrel ) ;
+        
+        return this;
+    };
+    
+    
+    this['rotateBarrel'] =
+    function rotateBarrel(x,y){
+        this.barrelShape.rotate(this.angleTo(x,y));
+        return this;
+    };
+    
+    this['rotateBarrelRelPage'] =
+    function rotateBarrelRelPage(pageX, pageY){
+        var shape  = this.shape
+        ,   off = shape.offset()
+        ,   w = this.width, h = this.height
+        ,   x = off.left + w/2 - pageX
+        ,   y = off.top  + h/2 - pageY
+        ,   theta = Math.atan2(-y,-x)
+        ;
+        this.barrelShape.rotate(theta);
+        return this;
+    };
+    
+});
+
+var NUM_OR_PERCENT = /^\s*[\d\.]+\s*%?\s*$/;
+function calcValue(v){
+    var unit = this;
+    return ( NUM_OR_PERCENT.test(v) ? v : eval(v) );
+}
+
diff --git a/src/tanks/thing/unit.cjs b/src/tanks/thing/unit.cjs
new file mode 100644 (file)
index 0000000..e69de29