From: dsc Date: Thu, 18 Nov 2010 07:11:44 +0000 (-0800) Subject: Bullets now deal damage, kill each other on collision. X-Git-Url: http://git.less.ly:3516/?a=commitdiff_plain;h=9a1a427b6a418825d4095b4a262ceb9591bc70ea;p=tanks.git Bullets now deal damage, kill each other on collision. --- diff --git a/notes.md b/notes.md index de1c16c..04afe34 100644 --- a/notes.md +++ b/notes.md @@ -1,5 +1,9 @@ # Bugs -- Turret can fire through walls +- collision short-circuiting, collision events +- bullet collisions should explode both +- 5-shot limit + + # TODOs - Move game objects into namespace `tanks` @@ -8,10 +12,10 @@ # Notes -- TODO Replace *2 and /2 with shifts at compile-time +- Replace *2 and /2 with shifts at compile-time - Clipping will suck (masking is easy -- overflow:hidden) -- Should use unit vectors for trajectories? ---> matrix math for reflections? + + # The Tank AI Challenge diff --git a/src/Y/core.js b/src/Y/core.js index 493e4bf..3718818 100644 --- a/src/Y/core.js +++ b/src/Y/core.js @@ -5,22 +5,56 @@ function notWrapped(fn){ return fn && fn !== self && fn.__wraps__ !== self; } -function reduce(o, fn, acc, cxt){ +function reduce(o, fn, acc){ if ( !o ) return acc; - // fn = Function.toFunction(fn); + fn = Function.toFunction(fn); + var cxt = arguments[3] || o; if ( notWrapped(o.reduce) ) return o.reduce.apply(o, [fn, acc, cxt]); - cxt = cxt || o; for ( var name in o ) acc = fn.call(cxt, acc, o[name], name, o); return acc; } +function map(o, fn){ + if ( !o ) + return o; + + fn = Function.toFunction(fn); + var acc = {}, cxt = arguments[2] || o; + if ( notWrapped(o.map) ) + return o.map.apply(o, [fn, cxt]); + for ( var name in o ) + acc[name] = fn.call(cxt, o[name], name, o); + + return acc; +} + +function forEach(o, fn){ + map(o, fn, cxt); +} + +function filter(o, fn){ + if ( !o ) + return o; + + fn = Function.toFunction(fn); + var acc = {}, cxt = arguments[2] || o; + if ( notWrapped(o.filter) ) + return o.filter.apply(o, [fn, cxt]); + + for ( var name in o ) + if ( fn.call(cxt, o[name], name, o) ) + acc[name] = o[name]; + + return acc; +} + function set(o, key, value, def){ if ( o && key !== undefined ) o[key] = (value !== undefined ? value : def); diff --git a/src/Y/modules/y.event.js b/src/Y/modules/y.event.js index 7e085cf..d3d0be5 100644 --- a/src/Y/modules/y.event.js +++ b/src/Y/modules/y.event.js @@ -36,12 +36,12 @@ Y.YObject.subclass('YEvent', { }, addEventListener : function addEventListener(evt, fn){ - this.getQueue(evt).push(fn); + this.getQueue(evt).push(fn.toFunction()); return this.target; }, removeEventListener : function removeEventListener(evt, fn){ - this.getQueue(evt).remove(fn); + this.getQueue(evt).remove(fn.toFunction()); }, fire : function fire(evtname, trigger, data, async){ diff --git a/src/Y/y-core.js b/src/Y/y-core.js index 5bcfddb..a7abd34 100644 --- a/src/Y/y-core.js +++ b/src/Y/y-core.js @@ -1,8 +1,11 @@ -Y.reduce = reduce; -Y.set = dset; -Y.attr = dattr; -Y.extend = extend; +Y.reduce = reduce; +Y.map = map; +Y.forEach = forEach; +Y.filter = filter; +Y.set = dset; +Y.attr = dattr; +Y.extend = extend; Y.isA = isA; Y.isString = isString; diff --git a/src/Y/y-function.js b/src/Y/y-function.js index f9d6984..dde3287 100644 --- a/src/Y/y-function.js +++ b/src/Y/y-function.js @@ -287,6 +287,9 @@ YFunction.prototype.getName = Y.getName YFunction(Y); Y(Y.reduce); +Y(Y.map); +Y(Y.forEach); +Y(Y.filter); Y(Y.set); Y(Y.attr); Y(Y.extend); diff --git a/src/portal/shape/rect.js b/src/portal/shape/rect.js index 4bd8c76..0d84e51 100644 --- a/src/portal/shape/rect.js +++ b/src/portal/shape/rect.js @@ -13,6 +13,13 @@ Rect = new Y.Class('Rect', Shape, { drawShape : function drawShape(ctx){ ctx.rect(0,0, this.canvasWidth,this.canvasHeight); ctx.fill(); + }, + + toString : function(){ + var loc = this.loc + , x1 = loc.x, y1 = loc.y + , x2 = x1+this.layerWidth, y2 = y1 + this.layerHeight; + return this.className+'['+x1+','+y1+', '+x2+','+y2+'](w='+this.layerWidth+', h='+this.layerHeight+')'; } }); diff --git a/src/tanks/game/game-map.js b/src/tanks/game/game-map.js deleted file mode 100644 index 5986158..0000000 --- a/src/tanks/game/game-map.js +++ /dev/null @@ -1,134 +0,0 @@ - -Y(tanks.Game.prototype).extend({ - - initMap : function initMap(){ - var self = this; - - this.byId = {}; - this.units = new Y.YArray(); - this.bullets = new Y.YArray(); - this.blockers = new Y.YArray(); - - this.root = - this.grid = - new Grid(COLUMNS,ROWS, CELL_SIZE) - .appendTo(this.viewport); - - this.level = - new Level(this, COLUMNS*REF_SIZE, ROWS*REF_SIZE) - .appendTo(this.root); - - this.pathmap = this.level.pathmap; - - // Agent.addEventListener('create', function(evt){ - // self.addAgent(evt.instance); - // }); - // Agent.addEventListener('destroy', function(evt){ - // self.killAgent(evt.instance); - // }); - }, - - - // *** Agent Management *** // - - addUnit : function addUnit(unit, col,row){ - unit.game = this; - - if (col !== undefined) { - // Center unit in square - var sqX = (col || 0) * REF_SIZE - , sqY = (row || 0) * REF_SIZE - , x = sqX + (REF_SIZE-unit.width) /2 - , y = sqY + (REF_SIZE-unit.height)/2 ; - - unit.setLocation(x,y); - unit.render( this.level ); - } - - this.pathmap.addBlocker(unit); - - if ( !this.byId[unit.id] ) { - this.byId[unit.id] = unit; - this.units.push(unit); - } - - - return unit; - }, - - addAgent : function addAgent(agent){ - agent.game = this; - if (agent.id === undefined) return agent; - - this.pathmap.addBlocker(agent); - - if ( !this.byId[agent.id] ) { - this.byId[agent.id] = agent; - if (agent instanceof Ability) - this.abilities.push(agent); - else - this.units.push(agent); - } - - return agent; - }, - - killUnit : function killUnit(unit){ - delete this.byId[unit.id]; - this.units.remove(unit); - this.pathmap.removeBlocker(unit); - return unit; - }, - - moveAgentTo : function moveAgentTo(agent, x,y){ - this.pathmap.removeBlocker(agent); - agent.setLocation(x,y); - this.pathmap.addBlocker(agent); - return agent; - }, - - getUnitAt : function getUnitAt(x,y){ - return this.grid.get(x,y); - }, - - getUnitsAt : function getUnitsAt(x1,y1, x2,y2){ - return this.grid.get(x1,y1, x2,y2); - } - -}); - -Y(tanks.Game.prototype).extend({ - - resize : function resize(){ - var ratio = COLUMNS / ROWS - , el = this.el - , p = el.parent() - , pw = p.width(), ph = p.height() - , pRatio = pw / ph - ; - - if ( ratio > pRatio ) - CELL_SIZE = Math.floor((pw-GRID_OFFSET*2) / COLUMNS); - else - CELL_SIZE = Math.floor((ph-GRID_OFFSET*2) / ROWS); - - SCALE = CELL_SIZE/REF_SIZE; - - var w = COLUMNS*CELL_SIZE - , h = ROWS*CELL_SIZE - , canvas = this.canvas[0]; - - this.el.width(w).height(h); - this.canvas.width(w).height(h); - canvas.width = w; - canvas.height = h; - - this.el.offset({ - top : (ph - h) / 2, - left : (pw - w) / 2 - }); - - this.ctx.scale(SCALE,SCALE); - } - -}); diff --git a/src/tanks/game/game.js b/src/tanks/game/game.js index aefec21..a26567d 100644 --- a/src/tanks/game/game.js +++ b/src/tanks/game/game.js @@ -1,20 +1,39 @@ tanks.Game = new Y.Class('Game', { init : function init(viewport){ + Y.bindAll(this); // Seal all methods + + this.byId = {}; + this.active = new Y.YArray(); + this.units = new Y.YArray(); + this.bullets = new Y.YArray(); + this.viewport = $(viewport || GRID_ELEMENT); + this.loop = new EventLoop(this, FRAME_RATE); - // Seal all methods - // Y.bindAll(this); - this.resize = this.resize.bind(this); - this.tick = this.tick.bind(this); + // this.resize = this.resize.bind(this); + // this.tick = this.tick.bind(this); - this.viewport = $(viewport || GRID_ELEMENT); + this.root = + this.grid = + new Grid(COLUMNS,ROWS, CELL_SIZE) + .appendTo(this.viewport); + + this.level = + new Level(this, COLUMNS*REF_SIZE, ROWS*REF_SIZE) + .appendTo(this.root); - this.initMap(); + this.pathmap = this.level.pathmap; + + + Thing.addEventListener('create', this.addUnit); + Thing.addEventListener('destroy', this.killUnit); this.addEventListener('tick', this.tick); }, + + draw : function draw(){ this.root.draw(); this.pathmap.removeOverlay(this.viewport); @@ -34,12 +53,108 @@ tanks.Game = new Y.Class('Game', { SECONDTH = ELAPSED / 1000; SQUARETH = REF_SIZE * SECONDTH - this.units.invoke('act'); - // XXX: Collect the dead + this.active.invoke('act'); this.draw(); + }, + + + + + // *** Agent Management *** // + + addUnit : function addUnit(unit, col,row){ + if (unit instanceof Y.event.YEvent) + unit = unit.instance; + + unit.game = this; + + if (col !== undefined) { + // Center unit in square + var sqX = (col || 0) * REF_SIZE + , sqY = (row || 0) * REF_SIZE + , x = sqX + (REF_SIZE-unit.width) /2 + , y = sqY + (REF_SIZE-unit.height)/2 ; + + unit.setLocation(x,y); + unit.render( this.level ); + } + + this.pathmap.addBlocker(unit); + + if ( unit.active && !this.byId[unit.id] ) { + this.byId[unit.id] = unit; + this.active.push(unit); + + if (unit instanceof Bullet) + this.bullets.push(unit); + else + this.units.push(unit); + } + + return unit; + }, + + killUnit : function killUnit(unit){ + if (unit instanceof Y.event.YEvent) + unit = unit.instance; + + console.log('killUnit(', unit, ')'); + + delete this.byId[unit.id]; + this.active.remove(unit); + this.pathmap.removeBlocker(unit); + + if (unit instanceof Bullet) + this.bullets.remove(unit); + else + this.units.remove(unit); + + return unit; + }, + + moveAgentTo : function moveAgentTo(agent, x,y){ + this.pathmap.removeBlocker(agent); + agent.setLocation(x,y); + this.pathmap.addBlocker(agent); + return agent; + }, + + + + resize : function resize(){ + var ratio = COLUMNS / ROWS + , el = this.el + , p = el.parent() + , pw = p.width(), ph = p.height() + , pRatio = pw / ph + ; + + if ( ratio > pRatio ) + CELL_SIZE = Math.floor((pw-GRID_OFFSET*2) / COLUMNS); + else + CELL_SIZE = Math.floor((ph-GRID_OFFSET*2) / ROWS); + + SCALE = CELL_SIZE/REF_SIZE; + + var w = COLUMNS*CELL_SIZE + , h = ROWS*CELL_SIZE + , canvas = this.canvas[0]; + + this.el.width(w).height(h); + this.canvas.width(w).height(h); + canvas.width = w; + canvas.height = h; + + this.el.offset({ + top : (ph - h) / 2, + left : (pw - w) / 2 + }); + + this.ctx.scale(SCALE,SCALE); } + }); diff --git a/src/tanks/globals.js b/src/tanks/globals.js index ad7eeb4..0f80622 100644 --- a/src/tanks/globals.js +++ b/src/tanks/globals.js @@ -33,11 +33,11 @@ var undefined if (!window.console) { console = { - log : Y.op.noop, - info : Y.op.noop, - warn : Y.op.noop, - error : Y.op.noop, - dir : Y.op.noop, - debug : Y.op.noop + log : Y.op.nop, + info : Y.op.nop, + warn : Y.op.nop, + error : Y.op.nop, + dir : Y.op.nop, + debug : Y.op.nop }; } diff --git a/src/tanks/main.js b/src/tanks/main.js index 7e5e0b8..c8b3929 100644 --- a/src/tanks/main.js +++ b/src/tanks/main.js @@ -6,21 +6,12 @@ jQuery(main); function main(){ - var v = $('#viewport') - , sq = REF_SIZE - ; + var v = $('#viewport'); LBT = new tanks.Game(); ctx = LBT.level.ctx; - LBT.level.append( - new Wall(6*sq,1*sq, 1*sq,4*sq), - new Wall(6*sq,7*sq, 1*sq,2*sq), - new Wall(1*sq,7*sq, 2*sq,2*sq), - new Wall(3*sq,6*sq, 1*sq,1*sq) - ); - - T = LBT.addUnit(new Tank(0), 1,2); + T = LBT.addUnit(new Tank(1), 1,2); new Player(LBT, T); setupUI(); @@ -35,7 +26,7 @@ function main(){ function setupUI(){ LBT.loop.spark = new FpsSparkline(LBT.loop, '.fps-sparkline', 0,0); - btank = new Tank(0); + btank = new Tank(1); btank.act = function(){ return this; }; LBT.addUnit(btank, 0,0); LBT.pathmap.removeBlocker(btank); diff --git a/src/tanks/map/collision.js b/src/tanks/map/collision.js deleted file mode 100644 index ee4ea47..0000000 --- a/src/tanks/map/collision.js +++ /dev/null @@ -1,7 +0,0 @@ -Collision = new Y.Class('Collision', { - - init : function initCollision(line, to){ - - }, - -}); \ No newline at end of file diff --git a/src/tanks/map/level.js b/src/tanks/map/level.js index 608102e..ce7602b 100644 --- a/src/tanks/map/level.js +++ b/src/tanks/map/level.js @@ -1,41 +1,79 @@ -Level = new Y.Class('Level', Rect, { +Level = Rect.subclass('Level', { init : function init(game, w,h){ Rect.init.call(this, w,h); - this.game = game; + this.game = game; this.pathmap = new PathMap(this, 0,0, w, h, CAPACITY); + this.walls = []; + + this.setup(); + }, + + setup : function setup(){ + Y.map([ [6,1, 1,4], + [6,7, 1,2], + [1,7, 2,2], + [3,6, 1,1] ], + "this.addWall.apply(this, Y.map(_, '*50'))", + this ); }, - append : function append(children){ - Y(arguments).forEach(this.pathmap.addBlocker, this.pathmap); - return Rect.prototype.append.apply(this, arguments); + addWall : function addWall(x,y, w,h){ + var wall = new Level.Wall(x,y, w,h); + this.walls.push(wall); + this.pathmap.addBlocker(wall); + + return this.append( wall.render(this).shape ); }, drawShape : Y.op.nop }); -Wall = new Y.Class('Wall', Rect, { - fillStyle : 'rgba(255,255,255, 0.25)', +Level.Wall = Thing.subclass('Wall', { blocking : true, + active : false, - init : function init(x,y, w,h){ - Rect.init.call(this, w,h); - - var x1 = x, y1 = y - , x2 = x+w, y2 = y+h; - this.position(x,y); + stats : { + hp : Infinity, + move : 0, + power : 0, + speed : 0, + shots : 0 }, - appendTo : function appendTo(parent){ - if (parent instanceof Level) - parent.pathmap.addBlocker(this); - return Rect.prototype.appendTo.call(this, parent); + + init : function initWall(x,y, w,h){ + this.width = w; + this.height = h; + Thing.init.call(this); + this.setLocation(x,y); + }, + + // inactive + createCooldowns : Y.op.nop, + + // indestructable + dealDamage : Y.op.nop, + + + render : function render(parent){ + if (this.shape) this.shape.remove(); + + this.shape = + new Rect(this.width, this.height) + .position(this.loc.x, this.loc.y) + .fill('rgba(255,255,255, 0.25)') + .appendTo( parent ); + + return this; }, toString : function(){ - return this.className+'('+this.loc+', w='+this.layerWidth+', h='+this.layerHeight+')'; + var loc = this.loc + , x1 = loc.x, y1 = loc.y + , x2 = x1+this.width, y2 = y1 + this.height; + return this.className+'['+x1+','+y1+', '+x2+','+y2+'](w='+this.width+', h='+this.height+')'; } - }); diff --git a/src/tanks/map/pathmap.js b/src/tanks/map/pathmap.js index 44151fb..bce6aca 100644 --- a/src/tanks/map/pathmap.js +++ b/src/tanks/map/pathmap.js @@ -14,10 +14,10 @@ PathMap = new Y.Class('PathMap', QuadTree, { // }; this.walls = { - top : new Wall(0,0, w,1), - bottom : new Wall(0,h-1, w,1), - left : new Wall(0,0, 1,h), - right : new Wall(w-1,0, 1,h) + top : new Level.Wall(0,0, w,1), + bottom : new Level.Wall(0,h-1, w,1), + left : new Level.Wall(0,0, 1,h), + right : new Level.Wall(w-1,0, 1,h) }; Y(this.walls).forEach(this.addBlocker, this); }, diff --git a/src/tanks/map/trajectory.js b/src/tanks/map/trajectory.js index e99fca0..d5433ae 100644 --- a/src/tanks/map/trajectory.js +++ b/src/tanks/map/trajectory.js @@ -1,8 +1,9 @@ Trajectory = new Y.Class('Trajectory', math.Line, { - depth : 0, + halt : false, elapsed : 0, bounces : 0, + init : function initTrajectory(owner, x1,y1, x2,y2, tdist){ this.owner = owner; this.game = owner.game; @@ -33,100 +34,84 @@ Trajectory = new Y.Class('Trajectory', math.Line, { // Determine how much time can pass before we risk teleporting resetBound : function resetBound(){ - var abs = Math.abs + var BOUND_SIZE_RATIO = 0.75 + , abs = Math.abs , bb = this.owner.boundingBox; - // this.tBound = Math.max( (bb.width - this.x1) / this.pa, - // (bb.height - this.y1) / this.pb ); - this.tBound = Math.min( abs(bb.width / this.pa), - abs(bb.height / this.pb) ); + this.tBound = Math.min( abs(bb.width / this.pa) * BOUND_SIZE_RATIO, + abs(bb.height / this.pb) * BOUND_SIZE_RATIO ); return this; }, step : function step(dt){ - var _dt = dt = (dt === undefined ? ELAPSED : dt); + this.halt = false; - var o = this.owner, offX = o.offsetX, offY = o.offsetY, _bb - , bb = _bb = o.boundingBox, bw = bb.width, bh = bb.height - , loc = bb.p1 - ; + var _dt = dt = (dt === undefined ? ELAPSED : dt) + , _bb = bb = this.owner.boundingBox, bw = bb.width, bh = bb.height + , to, t, r; do { - var t, to, _to, ng - , test, blockers, side - ; - t = Math.min(this.tBound, dt); dt -= t; this.elapsed += t; - _to = to = this.parametric(this.elapsed); - test = this.pathmap.moveBlocked(o, this, to, bb); + to = this.stepTo(t, bb); + if (this.halt) break; + + bb = new Loc.Rect(to.x,to.y, to.x+bw,to.y+bh); + + } while (dt > 0); + + return to; + }, + + stepTo : function stepTo(t, bb){ + var ng, og = this.p2, owner = this.owner + , to = this.parametric(this.elapsed), _to = to + , test = this.pathmap.moveBlocked(owner, this, to, bb) + ; + + // Blocked! Reflect trajectory + if ( test ) { + to = test.to; + this.bounces++; + + var blocker = test.blockers[0]; + owner.fire('collide', blocker); + blocker.fire('collide', owner); + + // Potentially set by responders to collision event + if (this.halt) return to; - // Blocked! Reflect trajectory - if ( test ) { - this.bounces++; - to = test.to; - side = test.side; - blockers = test.blockers; - - if (!side) { - console.error('Null reflection line!', 'to:', to, 'blockers:', blockers); - continue; - } - - if ( blockers.length > 1 ) { - to = loc; - ng = this.p1; // XXX: recalculate? - console.log('corner!', this, 'to:', to, 'ng:', ng); - } else { - ng = math.reflect(this.p2, side); - } - - var x = to.x, y = to.y - , og = this.p2 - ; - this.reset(x,y, ng.x,ng.y); - - if (this.depth) { - console.log([ - '['+TICKS+':'+_dt+' ('+this.depth+')] '+this.owner+' reflected!', - ' wanted: '+_to+' x ('+(_to.x+bw)+','+(_to.y+bh)+')', - ' blocker: '+test.msg, - ' old:', - ' loc: '+bb.p1, - ' goal: '+og, - ' new:', - ' loc: '+to, - ' goal: '+ng, - ' --> trajectory: '+this - ].join('\n')); - } - - bb = new Loc.Rect(x,y, x+bw,y+bh); - ng = null; - - this.owner.render(this.game.level); - - if (this.depth++ < 5) { - // dt += t; // do this step over again - continue; - - } else { - // console.error('Reflection limit reached!'); break; - LBT.stop(); - throw new Error('Reflection limit reached!'); - } - - - // No collision: don't change trajectory + if (!test.side) { + console.error('Null reflection line!', 'to:', to, 'blockers:', test.blockers); + return to; + } + + if ( test.blockers.length > 1 ) { + to = bb.p1; + ng = this.p1; // XXX: recalculate? + console.log('corner!', this, 'to:',to, 'ng:',ng); } else { - this.depth = 0; + ng = math.reflect(this.p2, test.side); } - } while (dt > 0); + this.reset(to.x,to.y, ng.x,ng.y); + owner.render(this.game.level); + + false && console.log([ + '['+TICKS+' ('+this.depth+')] '+owner+' reflected!', + ' wanted: '+_to+' x ('+(_to.x+bb.width)+','+(_to.y+bb.height)+')', + ' blocker: '+test.msg, + ' old:', + ' loc: '+bb.p1, + ' goal: '+og, + ' new:', + ' loc: '+to, + ' goal: '+ng, + ' --> trajectory: '+this + ].join('\n')); + } - // console.log('['+TICKS+':'+_dt+' ('+this.depth+')]', loc, ' ~> ', to); - this.depth = 0; return to; }, diff --git a/src/tanks/thing/bullet.js b/src/tanks/thing/bullet.js index 83041c2..86e6d8a 100644 --- a/src/tanks/thing/bullet.js +++ b/src/tanks/thing/bullet.js @@ -8,18 +8,21 @@ Bullet = Thing.subclass('Bullet', { * @param {math.Line} trajectory */ init : function initBullet(owner, x2,y2){ + var self = this; this.owner = owner; this.game = owner.game; Thing.init.call(this, owner.align); + var loc = owner.getTurretLoc() , x1 = loc.x, y1 = loc.y; - this.setLocation(x1,y1); this.trajectory = new Trajectory(this, x1,y1, x2,y2, this.stats.move*REF_SIZE/1000); + + this.addEventListener('collide', self.onCollide.bind(this)); }, - elapsed : 0, - - blocking : true, + blocking : true, + bounces : 0, + bounceLimit : 1, offsetX : -3, offsetY : -3, @@ -27,12 +30,9 @@ Bullet = Thing.subclass('Bullet', { height : 6, stats : { - move : 2.0, // move speed (squares/sec) - range : 0.1 // attack range (squares) + move : 2.0 // move speed (squares/sec) }, - bounces : 0, - fillStats : function(){ this.stats = Y({}, Thing.fillStats(this.owner.stats), @@ -52,13 +52,10 @@ Bullet = Thing.subclass('Bullet', { var loc = this.loc , trj = this.trajectory; this.trajectory.reset(loc.x,loc.y, x,y); - // this.trajectory = new math.Line(loc.x,loc.y, x,y, trj.tdist); - // this.elapsed = 0; return this; }, act : function act(){ - // this.elapsed += ELAPSED; if (!this.dead) this.move(); return this; @@ -66,77 +63,28 @@ Bullet = Thing.subclass('Bullet', { move : function move(){ var to = this.trajectory.step( ELAPSED ); - this.bounces = this.trajectory.bounces; - if (this.bounces > 1) - this.destroy(); - else + + if (!this.dead) this.game.moveAgentTo(this, to.x, to.y); + return this; }, - _depth : 0, - _move : function _move(){ - var trj = this.trajectory, p2 = trj.p2, goal = p2, _ng - , to = trj.parametric(this.elapsed) - , test = this.game.pathmap.attemptMove(this, trj, to) - ; + onCollide : function onCollide(evt){ + var agent = evt.trigger + , trj = this.trajectory; - // Something obstructs us -- time to reflect! - if (!test.ok) { - - // Literal corner case :P - if (test.corner) { - var to = this.loc, ng = _ng = trj.p1; - // this.trajectory = new math.Line(loc.x,loc.y, p1.x,p1.y, trj.tdist); - - - // Normal reflection against one line - } else { - to = test.to; - var wall = test.wall - , bb = this.boundingBox - , goal = wall.isWithin(p2,bb) ? trj.near(p2.x+2*REF_SIZE, p2.y+2*REF_SIZE) : p2 - , ng = _ng = math.reflect(goal, wall) - , x1 = bb.x1, y1 = bb.y1 - , tx = to.x, ty = to.y - , cmp = Y.op.cmp - ; - - // Are we pointed the wrong direction? Rotate 180 degrees around the bullet - if ( cmp(tx,x1) !== cmp(goal.x,x1) || cmp(ty,y1) !== cmp(goal.y,y1) ) - ng = new math.Vec(tx - ng.x + tx, ty - ng.y + ty); - } - - this.trajectory = new math.Line(to.x,to.y, ng.x,ng.y, trj.tdist); - - if (this._depth) { - console.log('['+this._depth+' '+TICKS+'] '+this+' reflected by', (test.corner ? 'corner' : test.what)); - console.log(' to='+to+', goal='+goal+(p2.equals(goal) ? '' : ' <~ '+p2)); - console.log(' --> new goal='+ng+(ng.equals(_ng) ? '' : ' <~ '+_ng)); - console.log(' --> trajectory='+this.trajectory); - } - - this.render(this.game.level).draw(); - this.elapsed = 0; //ELAPSED; - - if (this._depth++ < 5) - return this; // this.move(); - - console.error('Reflection limit reached!'); - this._depth = 0; - return this; - } + if ( agent instanceof Level.Wall && trj.bounces <= this.bounceLimit ) + return; - // if (this._depth > 0) - // console.warn('['+this._depth+'@'+TICKS+'] ok!'); + agent.dealDamage(this.stats.power, this); - this._depth = 0; - this.game.moveAgentTo(this, to.x, to.y); - return this; + this.destroy(); + trj.halt = true; }, render : function render(parent){ - if (this.tline) this.tline.remove(); + if (this.tline) { this.tline.remove(); delete this.tline; } if (this.shape) this.shape.remove(); if (tanks.config.pathing.traceTrajectories) { @@ -154,7 +102,6 @@ Bullet = Thing.subclass('Bullet', { .appendTo( parent ); this.shape.layer.attr('title', ''+loc); - return this; }, diff --git a/src/tanks/thing/tank.js b/src/tanks/thing/tank.js index 2d6314b..8650a5c 100644 --- a/src/tanks/thing/tank.js +++ b/src/tanks/thing/tank.js @@ -1,5 +1,5 @@ -Tank = new Y.Class('Tank', Thing, { +Tank = Thing.subclass('Tank', { projectile : Bullet, blocking : true, @@ -9,12 +9,14 @@ Tank = new Y.Class('Tank', Thing, { // Attributes stats: { - move : 1.0, // move speed (squares/sec) - rotate : HALF_PI, // rotation speed (radians/sec) + hp : 1, // health - power : 1, // attack power - speed : 1.0, // attacks/sec - shots : 5 // max projectiles in the air at once + move : 1.0, // move speed (squares/sec) + rotate : HALF_PI, // rotation speed (radians/sec) + + power : 1, // attack power + speed : 0.5, // attack cool (sec) + shots : 5 // max projectiles in the air at once }, @@ -82,7 +84,7 @@ Tank = new Y.Class('Tank', Thing, { .appendTo( parent ); this.cannon = new Circle(r, true) - .position(w2-r+1, h2-r+1) + .position(w2-r, h2-r) .fill('#A72F5B') .appendTo( this.shape ) ; diff --git a/src/tanks/thing/thing.js b/src/tanks/thing/thing.js index 776d64d..c8482e0 100644 --- a/src/tanks/thing/thing.js +++ b/src/tanks/thing/thing.js @@ -9,12 +9,14 @@ Thing = new Evt.Class('Thing', { }, stats: { - move : 0.5, // move speed (squares/sec) - rotate : HALF_PI, // rotation speed (radians/sec) + hp : 1, // health - power : 1, // attack power - speed : 1.0, // attacks/sec - shots : 5 // max projectiles in the air at once + move : 1.0, // move speed (squares/sec) + rotate : HALF_PI, // rotation speed (radians/sec) + + power : 1, // attack power + speed : 0.5, // attack cool (sec) + shots : 5 // max projectiles in the air at once }, @@ -22,12 +24,14 @@ Thing = new Evt.Class('Thing', { // *** Bookkeeping *** // id : 0, - align : 0, + align : 0, // 0 reserved for neutral units dead : false, + blocking : true, // Whether the agent obstructs pathing + active : true, // Whether the agent takes actions + loc : null, boundingBox : null, - blocking : true, // Rotation (rads) rotation : 0, @@ -39,16 +43,12 @@ Thing = new Evt.Class('Thing', { height : REF_SIZE*0.6, - destroy : function destroy(){ - if (this.dead) return this; + dealDamage : function dealDamage(d, source){ + this.stats.hp -= d; + + if (this.stats.hp <= 0) + this.destroy(); - this.dead = true; - return this.remove().fire('destroy', this); - }, - - remove : function remove(){ - if (this.shape) this.shape.remove(); - if (this.game) this.game.killUnit(this); return this; }, @@ -76,28 +76,19 @@ Thing = new Evt.Class('Thing', { return this; }, - /** - * @protected - * Regenerates the bounding box of this object. - * Invoked with new values for position and/or size; unchanged values are undefined. - */ - createBoundingBox : function createBoundingBox(x,y, w,h){ - var loc = this.loc, tw = this.width, th = this.height - , dx = (x !== undefined), dy = (y !== undefined) - , dw = (w !== undefined), dh = (h !== undefined) - , x1 = (dx ? x : loc.x), y1 = (dy ? y : loc.y) - , x2 = (dw ? w : tw)+x2, y2 = (dh ? h : th)+y1 - ; - if (dx || dy || dw || dh) - this.boundingBox = new Loc.BoundingBox(x1,y1, x2,y2); - return this; + destroy : function destroy(){ + if (this.dead) return this; + this.dead = true; + return this.remove().fire('destroy', this); }, + remove : function remove(){ + if (this.shape) this.shape.remove(); + if (this.game) this.game.killUnit(this); + return this; + }, - - // *** Gameplay Methods *** // - - fillStats : function fillStats(){ + fillStats : function fillStats(){ this.stats = Thing.fillStats(this.stats); }, @@ -108,26 +99,19 @@ Thing = new Evt.Class('Thing', { }, /** + * Calculates the location of the bullet-spawning point on this Thing. + */ + getTurretLoc : function getTurretLoc(){ return this.loc; }, + + + + + // *** Gameplay Methods *** // + + /** * Determines what the creep should do -- move, attack, etc. */ act : function act(){ - if (this.dead) return this; - - this.cooldowns.invoke('update', NOW); - - var bb = this.boundingBox - , x1 = Calc.rangeX(this), x2 = bb.left.x - , y1 = bb.top.y, y2 = bb.bottom.y - , units = this.game - .getUnitsAt(x1,y1, x2,y2) - .filter(this.filterTarget, this) - , target = this.closest(units) - ; - if (target) - this.attack(target); - else - this.move(); - return this; }, @@ -137,9 +121,9 @@ Thing = new Evt.Class('Thing', { return this; }, - getTurretLoc : function getTurretLoc(){ - return this.loc; - }, + + + /// Rendering Methods /// @@ -173,9 +157,8 @@ Y(Thing).extend({ ; st.forEach(function(v, k){ - var k = Y(k) - , k_ = k.rtrim('_max') - ; + k = Y(k); + var k_ = k.rtrim('_max'); if ( k.endsWith('_max') ) { if ( stats[k_] === undefined ) diff --git a/src/tanks/ui/player.js b/src/tanks/ui/player.js index 7d011a7..aa1be67 100644 --- a/src/tanks/ui/player.js +++ b/src/tanks/ui/player.js @@ -60,7 +60,6 @@ Player = new Y.Class('Player', { this.tank.cooldowns.invoke('update', NOW); var action = this.queue.shift(); - if (action && action.type === 'fire') { // console.log('fire', [action.x, action.y], 'loc', this.tank.loc) this.tank.fireProjectile(action.x, action.y); diff --git a/tanks.php b/tanks.php index fbc90d6..1ae6e32 100644 --- a/tanks.php +++ b/tanks.php @@ -21,20 +21,19 @@ class Tanks { "src/tanks/config.js", "src/tanks/calc.js", + "src/tanks/thing/thing.js", + "src/tanks/thing/bullet.js", + "src/tanks/thing/tank.js", + "src/tanks/map/loc.js", "src/tanks/map/trajectory.js", "src/tanks/map/level.js", "src/tanks/map/pathmap.js", - "src/tanks/thing/thing.js", - "src/tanks/thing/bullet.js", - "src/tanks/thing/tank.js", - "src/tanks/ui/grid.js", "src/tanks/ui/player.js", - "src/tanks/game/game.js", - "src/tanks/game/game-map.js" + "src/tanks/game/game.js" ); static $libScripts = array(