From: dsc Date: Sat, 26 Mar 2011 14:10:20 +0000 (-0700) Subject: Adds Tower enemies. X-Git-Url: http://git.less.ly:3516/?a=commitdiff_plain;h=3e6be4586fcb9383ba28ad31d0444def6fdf276d;p=tanks.git Adds Tower enemies. --- diff --git a/data/types/levels.yaml b/data/types/levels.yaml index c970003..2e90be2 100644 --- a/data/types/levels.yaml +++ b/data/types/levels.yaml @@ -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] + diff --git a/data/types/units.yaml b/data/types/units.yaml index d1913d0..fab1473 100644 --- a/data/types/units.yaml +++ b/data/types/units.yaml @@ -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' + diff --git a/src/tanks/thing/index.cjs b/src/tanks/thing/index.cjs index e1b534d..d4fd8a5 100644 --- a/src/tanks/thing/index.cjs +++ b/src/tanks/thing/index.cjs @@ -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 }); diff --git a/src/tanks/thing/tank.cjs b/src/tanks/thing/tank.cjs index ffe8c34..a30cf84 100644 --- a/src/tanks/thing/tank.cjs +++ b/src/tanks/thing/tank.cjs @@ -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 index 0000000..393dc7d --- /dev/null +++ b/src/tanks/thing/tower.cjs @@ -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 index 0000000..e69de29