<h3>config</h3>
<!--<div><label for="bullets">bullets</label> <input id="bullets" name="bullets" value="10" type="text"></div>-->
<div><label for="pathmap">overlay pathmap</label> <input id="pathmap" name="pathmap" value="1" type="checkbox"></div>
+ <div><label for="aipaths">overlay ai paths</label> <input id="aipaths" name="aipaths" value="1" type="checkbox"></div>
<div><label for="trajectories">trace trajectories</label> <input id="trajectories" name="trajectories" value="1" type="checkbox"></div>
</div>
return this;
},
- add : function add(b){
- return this.setXY(this.x+b.x, this.y+b.y);
+ add : function add(x,y){
+ if ( x instanceof Array ) {
+ y = x[1]; x = x[0];
+ }
+ return this.setXY(this.x+x, this.y+y);
},
- subtract : function subtract(b){
- return this.setXY(this.x-b.x, this.y-b.y);
+ subtract : function subtract(x,y){
+ if ( x instanceof Array ) {
+ y = x[1]; x = x[0];
+ }
+ return this.setXY(this.x-x, this.y-y);
},
scale : function scale(s){
--- /dev/null
+/* astar.js http://github.com/bgrins/javascript-astar
+ MIT License
+
+ Implements the astar search algorithm in javascript using a binary heap
+ **Requires graph.js**
+
+ Example Usage:
+ var graph = new Graph([
+ [0,0,0,0],
+ [1,0,0,1],
+ [1,1,0,0]
+ ]);
+ var start = graph.nodes[0][0];
+ var end = graph.nodes[1][2];
+ astar.search(graph.nodes, start, end);
+*/
+
+var astar = {
+
+ init: function initAStar(grid) {
+ // var copy = [];
+ for(var x = 0, xl = grid.length; x < xl; x++) {
+ var yl = grid[x].length;
+ // copy[x] = new Array(yl);
+ for(var y = 0; y < yl; y++) {
+ // var node = grid[x][y].clone();
+ var node = grid[x][y];
+ node.f = 0;
+ node.g = 0;
+ node.h = 0;
+ node.visited = false;
+ node.closed = false;
+ node.parent = null;
+ // copy[x][y] = node;
+ }
+ }
+ // return copy;
+ },
+
+ search: function search(grid, start, end, heuristic, ignore) {
+ astar.init(grid);
+ heuristic = heuristic || astar.manhattan;
+
+ ignore = ignore || [];
+ ignore.push(start.value);
+ ignore.push(end.value);
+
+ var open = new BinaryHeap('f');
+ open.push(start);
+
+ while( open.length > 0 ) {
+
+ // Grab the lowest f(x) to process next. Heap keeps this sorted for us.
+ var current = open.pop();
+
+ // End case -- result has been found, return the traced path
+ if(current === end) {
+ var path = []
+ , node = current;
+ while( node.parent ) {
+ path.push( node.clone() );
+ node = node.parent;
+ }
+ return path.reverse();
+ }
+
+ // Normal case -- move current from open to closed, process each of its neighbors
+ current.closed = true;
+
+ var neighbors = astar.neighbors(grid, current);
+ for(var i=0, il = neighbors.length; i < il; i++) {
+ var neighbor = neighbors[i];
+
+ // blocker
+ if( neighbor.closed || (neighbor.blocked && ignore.indexOf(neighbor.value) === -1) )
+ continue;
+
+ // g score is the shortest distance from start to current node, we need to check if
+ // the path we have arrived at this neighbor is the shortest one we have seen yet.
+ // We define the distance from a node to it's neighbor as 1, but this could be variable for weighted paths.
+ var g = current.g + 1;
+
+ if( !neighbor.visited || g < neighbor.g ) {
+
+ // Found an optimal (so far) path to this node. Take score for node to see how good it is.
+ neighbor.parent = current;
+ neighbor.h = neighbor.h || heuristic(neighbor, end);
+ neighbor.g = g;
+ neighbor.f = g + neighbor.h;
+
+ // New? Add to the set of nodes to process
+ if ( !neighbor.visited ) {
+ neighbor.visited = true;
+ open.push(neighbor);
+
+ // Seen, but since it has been rescored we need to reorder it in the heap
+ } else
+ open.rescore(neighbor);
+ }
+ }
+ }
+
+ // No result was found -- empty array signifies failure to find path
+ return [];
+ },
+
+ manhattan: function manhattan(p1, p2) {
+ // See list of heuristics: http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html
+ var d1 = Math.abs(p2.x - p1.x)
+ , d2 = Math.abs(p2.y - p1.y) ;
+ return d1 + d2;
+ },
+
+ manhattan4: function manhattan(x1,y1, x2,y2) {
+ // See list of heuristics: http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html
+ var d1 = Math.abs(x2 - x1)
+ , d2 = Math.abs(y2 - y1) ;
+ return d1 + d2;
+ },
+
+ neighbors: function neighbors(grid, node) {
+ var ret = [];
+ var x = node.x;
+ var y = node.y;
+
+ if(grid[x-1] && grid[x-1][y]) {
+ ret.push(grid[x-1][y]);
+ }
+ if(grid[x+1] && grid[x+1][y]) {
+ ret.push(grid[x+1][y]);
+ }
+ if(grid[x] && grid[x][y-1]) {
+ ret.push(grid[x][y-1]);
+ }
+ if(grid[x] && grid[x][y+1]) {
+ ret.push(grid[x][y+1]);
+ }
+ return ret;
+ }
+};
--- /dev/null
+(function(){
+
+function BinaryHeap(score){
+ if (typeof score === 'string')
+ this.key = score;
+ else
+ this.score = score;
+}
+
+var AP = Array.prototype
+, proto = BinaryHeap.prototype = []
+, methods = {
+
+ _push : AP.push,
+ _pop: AP.pop,
+
+ // Replaced by score function if supplied
+ score : function score(el){
+ return el[this.key];
+ },
+
+ push: function(el) {
+ // Add the new element to the end of the array.
+ this._push(el);
+
+ // Allow it to sink down.
+ this.heapDown(this.length-1);
+
+ return this;
+ },
+
+ pop: function() {
+ // Store the first element so we can return it later.
+ var result = this[0]
+
+ // Get the element at the end of the array.
+ , end = this._pop();
+
+ // If there are any elements left, put the end element at the
+ // start, and let it bubble up.
+ if ( this.length > 0 ) {
+ this[0] = end;
+ this.heapUp(0);
+ }
+ return result;
+ },
+
+ remove: function(node) {
+ var key = this.key
+ , i = this.indexOf(node)
+
+ // When it is found, the process seen in 'pop' is repeated
+ // to fill up the hole.
+ , end = this._pop();
+
+ if ( i !== this.length-1 ) {
+ this[i] = end;
+ var endScore = (key ? end[key] : this.score(end))
+ , nodeScore = (key ? node[key] : this.score(node)) ;
+ if ( endScore < nodeScore )
+ this.heapDown(i);
+ else
+ this.heapUp(i);
+ }
+ return this;
+ },
+
+ rescore: function(node) {
+ this.heapDown(this.indexOf(node));
+ return this;
+ },
+
+ heapDown: function(n) {
+ var key = this.key
+ , el = this[n]
+ , elScore = (key ? el[key] : this.score(el))
+ ;
+
+ // When at 0, an element can not sink any further.
+ while ( n > 0 ) {
+
+ // Compute the parent element's index and score
+ var parentN = ((n + 1) >> 1) - 1
+ , parent = this[parentN]
+ , parentScore = (key ? parent[key] : this.score(parent))
+ ;
+
+ // Swap the elements if the parent is greater
+ if ( elScore < parentScore ) {
+ this[parentN] = el;
+ this[n] = parent;
+
+ // Update 'n' to continue at the new position.
+ n = parentN;
+
+ // Found a parent that is less, no need to sink any further.
+ } else break;
+ }
+ },
+
+ heapUp: function(n) {
+ var key = this.key
+ , len = this.length
+ , el = this[n]
+ , elScore = this.score(el)
+ ;
+
+ while( true ) {
+ var child2N = (n+1) << 1 // Compute children
+ , child1N = child2N - 1
+ , swap = null // Swap index
+ ;
+
+ if ( child1N < len ) {
+ var child1 = this[child1N]
+ , child1Score = (key ? child1[key] : this.score(child1))
+ ;
+ if (child1Score < elScore)
+ swap = child1N;
+ }
+
+ if ( child2N < len ) {
+ var child2 = this[child2N]
+ , child2Score = (key ? child2[key] : this.score(child2))
+ ;
+ if (child2Score < (swap === null ? elScore : child1Score))
+ swap = child2N;
+ }
+
+ // Perform swap if necessary
+ if ( swap !== null ) {
+ this[n] = this[swap];
+ this[swap] = el;
+ n = swap;
+
+ } else break;
+ }
+ }
+};
+
+// Add methods to prototype
+for (var k in methods) proto[k] = methods[k];
+
+this.BinaryHeap = BinaryHeap;
+
+})();
\ No newline at end of file
--- /dev/null
+function Graph(grid) {
+ this.grid = grid;
+ this.nodes = [];
+
+ for (var x = 0; x < grid.length; x++) {
+ var row = grid[x];
+ this.nodes[x] = [];
+ for (var y = 0; y < row.length; y++) {
+ var n = new math.Vec(x,y);
+ n.blocked = !!row[y];
+ this.nodes[x].push(n);
+ }
+ }
+}
+
+
// Expects caller will have ordered x1 < x2, y1 < y2
overlaps : function overlaps(x1,y1, x2,y2){
- return !( x1 > this.x2 || y1 > this.y2
- || x2 < this.x1 || y2 < this.y1 );
+ return !( x1 > this.x2 || y1 > this.y2
+ || x2 <= this.x1 || y2 <= this.y1 );
},
toString : function toString(){
// Expects caller will have ordered x1 < x2, y1 < y2
overlaps : function overlaps(x1,y1, x2,y2){
- return !( x1 > this.x2 || y1 > this.y2
- || x2 < this.x1 || y2 < this.y1 );
+ return !( x1 > this.x2 || y1 > this.y2
+ || x2 <= this.x1 || y2 <= this.y1 );
},
tanks.config = {
pathing : {
overlayPathmap : false,
+ overlayAIPaths : true,
traceTrajectories : false
},
debug : {
draw : function draw(){
this.root.draw();
+
this.pathmap.removeOverlay(this.viewport);
if (tanks.config.pathing.overlayPathmap)
this.pathmap.overlay(this.viewport);
SECONDTH = ELAPSED / 1000;
SQUARETH = REF_SIZE * SECONDTH
+ if (!tanks.config.pathing.overlayAIPaths)
+ this.pathmap.clearPath();
+
this.active.invoke('updateCooldowns', NOW);
this.active.invoke('act');
LBT = new tanks.Game();
ctx = LBT.level.ctx;
- P = LBT.addUnit(new PlayerTank(1), 1,2);
- E = LBT.addUnit(new Tank(2), 5,6);
+ P = LBT.addUnit(new PlayerTank(1), 8,9);
+ E = LBT.addUnit(new Tank(2), 1,2);
setupUI();
// toggleGame();
updateInfo();
+
+ pm = LBT.pathmap;
}
var c = tanks.config, p = c.pathing;
$('#config [name=pathmap]').attr('checked', p.overlayPathmap);
+ $('#config [name=aipaths]').attr('checked', p.overlayAIPaths);
$('#config [name=trajectories]').attr('checked', p.traceTrajectories);
// $('#config [name=bullets]').val(c.debug.projectiles);
function updateConfig(evt){
var p = tanks.config.pathing;
- p.overlayPathmap = $('#config [name=pathmap]').attr('checked');
+ p.overlayPathmap = $('#config [name=pathmap]').attr('checked');
+ p.overlayAIPaths = $('#config [name=aipaths]').attr('checked');
p.traceTrajectories = $('#config [name=trajectories]').attr('checked');
}
// -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*-
+(function(){
+
PathMap = new Y.Class('PathMap', QuadTree, {
+ gridSquareSize : REF_SIZE,
+ gridSquareMidPt : new math.Vec(REF_SIZE/2, REF_SIZE/2),
+
+
- init : function init(game, x1,y1, x2,y2, capacity) {
+ init : function init(level, x1,y1, x2,y2, capacity) {
+ x1 -= 1; y1 -= 1; x2 += 1; y2 += 1;
QuadTree.init.call(this, x1,y1, x2,y2, capacity);
- this.game = game;
+ this.level = level;
+ this.game = level.game;
var w = this.width, h = this.height;
- // this.walls = {
- // top : new math.Line(0,0, w,0),
- // bottom : new math.Line(0,h, w,h),
- // left : new math.Line(0,0, 0,h),
- // right : new math.Line(w,0, w,h)
- // };
-
this.walls = {
- 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)
+ top : new Level.Wall(x1,y1, w,1),
+ bottom : new Level.Wall(x1,y2-1, w,1),
+ left : new Level.Wall(x1,y1, 1,h),
+ right : new Level.Wall(x2-1,y1, 1,h)
};
- Y(this.walls).forEach(this.addBlocker, this);
+ Y(this.walls).forEach(function(wall){
+ wall.isBoundary = true;
+ this.addBlocker(wall);
+ }, this);
},
addBlocker : function addBlocker(obj){
return obj;
},
+
+
moveBlocked : function moveBlocked(agent, trj, to, bb){
bb = bb || agent.boundingBox;
var blockers, blocker, msg
+ grid : function grid(){
+ if ( !this._grid ) {
+ var size = this.gridSquareSize
+ , floor = Math.floor, ceil = Math.ceil
+ , cols = ceil((this.width-2) /size)
+ , rows = ceil((this.height-2)/size)
+ , grid = new Array(cols);
+ ;
+
+ for (var x=0; x<cols; x++) {
+ var col = grid[x] = new Array(rows);
+ for (var y=0; y<rows; y++) {
+ var node = col[y] = new math.Vec(x,y);
+
+ node.clone = function cloneNode(){
+ var n = math.Vec.prototype.clone.call(this);
+ n.blocked = this.blocked;
+ n.value = this.value;
+ return n;
+ };
+ }
+ }
+
+ this._grid = this.reduce(function(grid, v, r){
+
+ // Ignore bullets and boundary-walls in pathing
+ if (v.isBoundary || v instanceof Bullet)
+ return grid;
+
+ var rowMin = floor( r.y1/size )
+ , colMin = floor( r.x1/size )
+ , rowMax = ceil( r.y2/size )
+ , colMax = ceil( r.x2/size )
+ ;
+
+ for (var x=colMin; x<colMax; x++) {
+ var col = grid[x];
+ for (var y=rowMin; y<rowMax; y++) {
+ var node = col[y];
+ node.blocked = true;
+ node.value = v;
+ }
+ }
+
+ return grid;
+ }, grid);
+ }
+ return this._grid;
+ },
+
+ vec2Square : function vec2Square(x,y){
+ if (x instanceof Array){
+ y = x.y;
+ x = x.x;
+ }
+ var floor = Math.floor, size = this.gridSquareSize;
+ return new math.Vec(floor(x/size), floor(y/size));
+ },
+
+ square2Vec : function square2Vec(x,y){
+ if (x instanceof Array){
+ y = x.y;
+ x = x.x;
+ }
+ var floor = Math.floor, size = this.gridSquareSize;
+ return new math.Vec(floor(x)*size, floor(y)*size);
+ },
+
+ manhattan: function manhattan(p1, p2) {
+ var d1 = Math.abs(p2.x - p1.x)
+ , d2 = Math.abs(p2.y - p1.y) ;
+ return d1 + d2;
+ },
+
+ manhattan4: function manhattan4(x1,y1, x2,y2) {
+ var d1 = Math.abs(x2 - x1)
+ , d2 = Math.abs(y2 - y1) ;
+ return d1 + d2;
+ },
+
+ path : function path(start, end){
+ var size = this.gridSquareSize, floor = Math.floor
+ , grid = this.grid()
+
+ , startX = floor(start.x/size)
+ , startY = floor(start.y/size)
+ , startN = grid[startX][startY]
+
+ , endX = floor(end.x/size)
+ , endY = floor(end.y/size)
+ , endN = grid[endX][endY]
+
+ , path = Y(astar.search(grid, startN, endN))
+ ;
+
+ if (tanks.config.pathing.overlayAIPaths)
+ this.drawPath(startN, path);
+
+ return path
+ .invoke('scale', size)
+ .invoke('add', this.gridSquareMidPt)
+ .end();
+ },
+
+ drawPath : function drawPath(start, path){
+ var size = this.gridSquareSize, off = size*0.15
+ , w = this.width-2, h = this.height-2
+ , el = this.game.viewport
+ , grid = this.grid()
+ , canvas = $('.path', el)[0]
+ ;
+
+ if (!canvas) {
+ canvas = $('<canvas class="path" style="position:absolute"/>').appendTo(el)[0];
+ $(canvas).width(w).height(h);
+ canvas.width = w;
+ canvas.height = h;
+ }
+
+ var ctx = canvas.getContext('2d');
+ ctx.lineWidth = 0;
+
+ // Clear the canvas
+ ctx.beginPath();
+ ctx.clearRect(this.x,this.y, w,h);
+ ctx.closePath();
+
+
+ // Draw blockers
+ // var s = size/2;
+ // ctx.fillStyle = 'rgba(255,0,0,0.2)';
+ //
+ // Y(grid).invoke('forEach', function(node){
+ // if ( !node.blocked ) return;
+ //
+ // var x = node.x*size + s
+ // , y = node.y*size + s ;
+ //
+ // ctx.beginPath();
+ // ctx.moveTo( x, y-s );
+ // ctx.lineTo( x+s, y );
+ // ctx.lineTo( x, y+s );
+ // ctx.lineTo( x-s, y );
+ // ctx.fill();
+ // ctx.closePath();
+ // });
+
+
+ // Draw path
+ var r = size/2 - off;
+
+ function drawStep(p, i){
+ var x = p.x*size + off + r
+ , y = p.y*size + off + r ;
+
+ ctx.beginPath();
+ ctx.arc(x,y, r, 0, Math.PI*2, false);
+ ctx.fill();
+ ctx.closePath();
+ }
+ this.drawStep = drawStep;
+
+ ctx.fillStyle = 'rgba(0,0,0,0.2)';
+ path.forEach(drawStep);
+
+
+ // Draw start
+ ctx.fillStyle = 'rgba(0,255,0,0.2)';
+ drawStep(start, 0);
+
+ // Draw finish
+ ctx.fillStyle = 'rgba(0,0,255,0.2)';
+ drawStep( path.last() );
+
+ $(canvas).show();
+ },
+
+ clearPath : function clearPath(){
+ $('.path', this.game.viewport).hide();
+ },
+
+
+
_overlayBG : $('<img src="img/pathmap-bg.png" />')[0],
overlay : function overlay(gridEl){
- var w = this.width *SCALE
- , h = this.height *SCALE
+ var w = this.width-2
+ , h = this.height-2
, canvas = $('.overlay', gridEl)[0];
if (!canvas) {
}
var ctx = canvas.getContext('2d');
- ctx.scale(SCALE, SCALE);
+ // ctx.scale(SCALE, SCALE);
// Clear the canvas
ctx.beginPath();
// Draw regions
- this.reduce(function(acc, value, r, tree){
- if ( acc[r.id] ) return acc;
+ this.reduce(function(acc, v, r, tree){
+ if ( acc[r.id] || v.isBoundary )
+ return acc;
acc[r.id] = r;
});
+var QT = QuadTree.prototype;
+
+'set remove removeAll clear'
+ .split(' ')
+ .forEach(function(name){
+ PathMap.prototype[name] = function(){
+ delete this._grid;
+ return QT[name].apply(this, arguments);
+ };
+ });
+
+
+})();
return this;
},
- attack : function attack(){
- var WIGGLE = 4;
-
- if ( !this.cooldowns.attack.ready )
- return;
-
- var barrel = this.barrel
- , bb = this.boundingBox
- , w2 = bb.width/2, h2 = bb.height/2
- , x0 = bb.x1+w2, y0 = bb.y1+h2
-
- , theta = barrel.transform.rotate
- , sin = Math.sin(theta), cos = Math.cos(theta)
-
- , x1 = x0 + w2*cos, y1 = y0 + h2*sin
- , sz = (barrel.boundingBox.width - w2)/2 + WIGGLE
- , blockers = this.game.pathmap.get(x1-sz,y1-sz, x1+sz,y1+sz).remove(this)
- ;
-
- if ( blockers.size() ) return; // console.log('squelch!', blockers);
-
+ attack : function attack(x,y){
this.queue.push({
type : 'fire',
- x : x0 + REF_SIZE*cos,
- y : y0 + REF_SIZE*sin
+ x : x,
+ y : y
});
},
init : function init(align){
Thing.init.call(this, align);
this.onBulletDeath = this.onBulletDeath.bind(this);
+
+ var self = this;
+ this.addEventListener('destroy', function(){
+ this.game.pathmap.clearPath();
+ });
},
cannonReady : function cannonReady(){
this.shoot(b.loc.x, b.loc.y);
return this;
}
-
}
// Nothing to shoot at? Move toward something
- var t = this.nearLike(10000, 'Y.is(Tank, _) && _.align !== '+this.align)
- .remove(this)
- .shift();
- if (t) {
- // console.log(this, 'moving toward', t);
- this.move(t.loc.x, t.loc.y);
- return this;
- }
+ this.continueMove();
+
+ return this;
+ },
+
+ continueMove : function continueMove(){
+ if (!this.currentMove)
+ this.recalculatePath();
+
+ var to = this.currentMove;
+ if (!to) return;
+
+ this.move(to.x, to.y);
+ if ( this.boundingBox.midpoint().equals(to) )
+ this.currentMove = null;
+ },
+
+ recalculatePath : function recalculatePath(){
+ var t = this.nearLike(10000, 'Y.is(Tank, _) && _.align !== '+this.align).shift()
+ , pm = this.game.pathmap
+ ;
+
+ if (!t) return;
+
+ pm.clearPath();
+
+ // console.log(this, 'moving toward', t);
+ var end = t.boundingBox.midpoint()
+ , bb = this.boundingBox
+ , mid = bb.midpoint()
+ , start = mid
+
+ , path = this.lastPath = pm.path(start, end)
+ , to = this.currentMove = path[0]
+ ;
+
+ // VVV We only really need this code if we're going to recalculate before we reach the currentMove
+
+ // we may need to move closer to start if we occupy multiple grid-squares
+ // var tosq = pm.vec2Square(to), manhattan = pm.manhattan
+ // , tl = pm.vec2Square(bb.x1,bb.y1), tr = pm.vec2Square(bb.x2,bb.y1)
+ // , bl = pm.vec2Square(bb.x1,bb.y2), br = pm.vec2Square(bb.x2,bb.y2)
+ // , straddles = [tl, tr, bl, br]
+ // ;
+ //
+ //
+ // if ( !tl.equals(br) ) {
+ // tl.to = pm.vec2Square(bb.x1,bb.y1); tr.to = pm.vec2Square(mid.x,bb.y1);
+ // bl.to = pm.vec2Square(bb.x1,mid.y); br.to = pm.vec2Square(mid.x,mid.y);
+ // straddles.sort(function(a,b){
+ // return Y.op.cmp(manhattan(a,tosq), manhattan(b,tosq));
+ // });
+ // to = pm.square2Vec(straddles[0].to).add(pm.gridSquareMidPt);
+ // path.unshift(to);
+ // }
+
+ // console.log('start:', start, 'straddles:', straddles.map(pm.square2Vec.bind(pm)), 'end:', end, 'next:', to);
},
nearLike : function nearLike(ticks, fn){
},
shoot : function shoot(x,y){
- this.rotateBarrel(x,y);
+ var WIGGLE = 4
+ , xydef = (x !== undefined && y !== undefined)
+ ;
+
+ if (xydef) this.rotateBarrel(x,y);
if ( this.nShots >= this.stats.shots || !this.cooldowns.attack.activate(NOW) )
return null;
+ var barrel = this.barrel
+ , bb = this.boundingBox
+ , w2 = bb.width/2, h2 = bb.height/2
+ , x0 = bb.x1+w2, y0 = bb.y1+h2
+
+ , theta = barrel.transform.rotate
+ , sin = Math.sin(theta), cos = Math.cos(theta)
+
+ , x1 = x0 + w2*cos, y1 = y0 + h2*sin
+ , sz = (barrel.boundingBox.width - w2)/2 + WIGGLE
+ , blockers = this.game.pathmap.get(x1-sz,y1-sz, x1+sz,y1+sz).remove(this)
+ ;
+
+ if ( blockers.size() ) return; // console.log('squelch!', blockers);
+
+ if (!xydef) {
+ x = x0 + REF_SIZE*cos;
+ y = y0 + REF_SIZE*sin;
+ }
+
var ProjectileType = this.projectile
, p = new ProjectileType(this, x,y);
return new math.Vec(x,y);
},
+ move : function move(x,y){
+ var bb = this.boundingBox
+ , w2 = bb.width/2, h2 = bb.height/2
+ ;
+ return this.moveByAngle( this.angleTo(x,y), x-w2,y-h2 );
+ },
+
+ moveByAngle : function moveByAngle(theta, targetX,targetY){
+ var abs = Math.abs
+ , bb = this.boundingBox, w2 = bb.width/2, h2 = bb.height/2
+ , loc = this.loc, mid = bb.midpoint()
+ , to = loc.moveByAngle(theta, this.stats.move * SQUARETH)
+ ;
+
+ // Don't overshoot the target
+ if ( targetX !== undefined && abs(loc.x-to.x) > abs(loc.x-targetX) )
+ to.setXY(targetX, to.y);
+
+ if ( targetY !== undefined && abs(loc.y-to.y) > abs(loc.y-targetY) )
+ to.setXY(to.x, targetY);
+
+ var nbb = bb.add(to.x,to.y)
+ , blockers = this.game.pathmap.get(nbb.x1,nbb.y1, nbb.x2,nbb.y2).remove(this)
+ ;
+
+ if ( !blockers.size() )
+ this.game.moveAgentTo(this, to.x, to.y);
+
+ return this;
+ },
+
+
/// Rendering Methods ///
return this;
},
- move : function move(x,y){
- return this.moveByAngle( this.angleTo(x,y) );
- },
-
- moveByAngle : function moveByAngle(theta){
- var to = this.loc.moveByAngle(theta, this.stats.move * SQUARETH)
- , bb = this.boundingBox.add(to.x,to.y)
- , blockers = this.game.pathmap.get(bb.x1,bb.y1, bb.x2,bb.y2).remove(this)
- ;
-
- if ( !blockers.size() )
- this.game.moveAgentTo(this, to.x, to.y);
-
- return this;
- },
-
"src/portal/shape/line.js",
"src/portal/shape/polygon.js",
+ "src/portal/util/binaryheap.js",
+ "src/portal/util/graph.js",
+ "src/portal/util/astar.js",
+
"src/portal/util/tree/quadtree.js",
"src/portal/loop/eventloop.js",