From 4231c73e7e5a252706d8d11f2fa36b247aedc6f8 Mon Sep 17 00:00:00 2001 From: dsc Date: Sat, 18 Dec 2010 09:20:48 -0800 Subject: [PATCH] Checkpoint on collision refactor to support zones. --- src/Y/index.cjs | 11 +- src/Y/modules/y.config.cjs | 4 +- src/Y/modules/y.event.cjs | 8 +- src/Y/modules/y.scaffold.cjs | 16 +++- src/Y/type.cjs | 50 ++++++---- src/Y/types/function.cjs | 2 +- src/Y/y.cjs | 3 +- src/evt.cjs | 72 +++++++++----- src/ezl/layer.cjs | 45 +++++--- src/ezl/loc/boundingbox.cjs | 24 ++++- src/ezl/loop/eventloop.cjs | 7 +- src/ezl/math/line.cjs | 24 ++++- src/ezl/shape/circle.cjs | 2 +- src/ezl/shape/line.cjs | 4 +- src/ezl/shape/polygon.cjs | 2 +- src/ezl/shape/rect.cjs | 3 +- src/ezl/util/configuration.cjs | 114 --------------------- src/ezl/widget/cooldown.cjs | 4 +- src/tanks/config.cjs | 26 ++++-- src/tanks/game.cjs | 48 ++++----- src/tanks/index.js | 2 +- src/tanks/map/level.cjs | 15 ++- src/tanks/map/pathmap.cjs | 213 ++++++--------------------------------- src/tanks/map/trajectory.cjs | 193 ++++++++++++++++++------------------ src/tanks/map/traversal.cjs | 95 ++++++++++++++++++ src/tanks/map/wall.cjs | 11 +- src/tanks/thing/bullet.cjs | 21 +++-- src/tanks/thing/index.cjs | 1 + src/tanks/thing/item.cjs | 58 +++++++++++ src/tanks/thing/player.cjs | 2 +- src/tanks/thing/tank.cjs | 34 +++--- src/tanks/thing/thing.cjs | 119 +++++++++++----------- src/tanks/ui/configui.cjs | 9 +- src/tanks/ui/grid.cjs | 8 +- src/tanks/ui/index.cjs | 3 + src/tanks/ui/main.cjs | 2 +- src/tanks/ui/pathmapui.cjs | 160 ++++++++++++++++++++++++++++++ www/deps.html | 6 +- 38 files changed, 793 insertions(+), 628 deletions(-) delete mode 100644 src/ezl/util/configuration.cjs create mode 100644 src/tanks/abilities/buff.cjs create mode 100644 src/tanks/abilities/index.cjs create mode 100644 src/tanks/map/traversal.cjs create mode 100644 src/tanks/thing/item.cjs create mode 100644 src/tanks/ui/pathmapui.cjs diff --git a/src/Y/index.cjs b/src/Y/index.cjs index dd9f2b5..cddbcd4 100644 --- a/src/Y/index.cjs +++ b/src/Y/index.cjs @@ -50,11 +50,12 @@ addNames('Class subclass instantiate fabricate YBase', require('Y/class')); /// Now start assembling the normal sub-modules /// -addNames('YCollection', require('Y/types/collection')); -addNames('YArray', require('Y/types/array')); -addNames('YObject deepcopy', require('Y/types/object')); -addNames('YString', require('Y/types/string')); -addNames('YNumber range', require('Y/types/number')); +addNames('YCollection', require('Y/types/collection')); +addNames('YArray', require('Y/types/array')); +addNames('YObject getNested setNested deepcopy', + require('Y/types/object')); +addNames('YString', require('Y/types/string')); +addNames('YNumber range', require('Y/types/number')); var utils = require('Y/utils'); Y['bindAll'] = utils.bindAll; diff --git a/src/Y/modules/y.config.cjs b/src/Y/modules/y.config.cjs index 91a694c..f812fde 100644 --- a/src/Y/modules/y.config.cjs +++ b/src/Y/modules/y.config.cjs @@ -1,5 +1,5 @@ var Y = require('Y').Y -, getNested = require('Y/types/object').getNested +, getNested = Y.getNested , Emitter = require('Y/modules/y.event').Emitter , @@ -142,7 +142,7 @@ Y.YObject.subclass('Config', function(Config){ */ updateOnChange : function updateOnChange(events, obj){ if ( !Y.isArray(events) ) - events = events.split(); + events = events.split(/\s+/); events = events.map(function events(key){ obj[key.split('.').pop()] = this.get(key); if ( !Y(key).startsWith('set:') ) diff --git a/src/Y/modules/y.event.cjs b/src/Y/modules/y.event.cjs index 7421fb8..64859d9 100644 --- a/src/Y/modules/y.event.cjs +++ b/src/Y/modules/y.event.cjs @@ -37,7 +37,7 @@ Y.YObject.subclass('Event', { addEventListener : function addEventListener(evts, fn){ fn = fn.toFunction(); if ( !Y.isArray(evts) ) - evts = evts.split(); + evts = evts.split(/\s+/); evts.forEach(function addEvent(evt){ this.getQueue(evt).push(fn); }, this); @@ -57,7 +57,11 @@ Y.YObject.subclass('Event', { return evt; }, - // XXX: does not handle degenerate or recursive event dispatch + /** + * Dispatches the given event to all listeners in the appropriate queue. + * Listeners are invoked with the event, and with context set to the target. + * XXX: does not handle degenerate or recursive event dispatch + */ dispatchEvent : function dispatchEvent(evt){ this.getQueue(evt.type).invoke('call', evt.target, evt); if (this.parent) this.parent.fire(evt.type, evt.trigger, evt.data); diff --git a/src/Y/modules/y.scaffold.cjs b/src/Y/modules/y.scaffold.cjs index 9a499df..717b7c9 100644 --- a/src/Y/modules/y.scaffold.cjs +++ b/src/Y/modules/y.scaffold.cjs @@ -37,6 +37,14 @@ type2el = { , +// addTypeSupport = +// exports['addTypeSupport'] = +// function addTypeSupport(type, parser, builder){ +// var T = Y.typeName(type); +// type2parser[T] = parser; +// } +// , + Field = exports['Field'] = @@ -52,7 +60,7 @@ Y.subclass('Field', { this.key = chain.split('.').pop(); this.label = camelToSpaces(this.key); - var T = Y(Y.type(val)).getName(); + var T = Y.typeName(def); this.cast = options.cast || type2parser[T]; this.type = options.type || type2el[T]; @@ -133,8 +141,12 @@ function create(config, el){ }, function createField(group, value, chain){ var def = config.getDefault(chain) - , field = new Field(chain, def, value); + , T = Y.typeName(def); + + if (!type2parser[T]) + return group; + var field = new Field(chain, def, value); fields[chain] = field; group.append(field.el); config.addEventListener('set:'+chain, function onConfigSet(evt){ diff --git a/src/Y/type.cjs b/src/Y/type.cjs index 18c7cb8..bed850b 100644 --- a/src/Y/type.cjs +++ b/src/Y/type.cjs @@ -28,46 +28,53 @@ var class2name = "Boolean Number String Function Array Date RegExp Object" .split(" ") .reduce(function(class2name, name) { - class2name[ "[object "+name+"]" ] = name.toLowerCase(); + class2name[ "[object "+name+"]" ] = name; return class2name; }, {}); -function type_of(obj){ - return obj == null ? - String( obj ) : - class2name[ toString.call(obj) ] || "object"; +function basicTypeName(o){ + return o == null ? + String(o) : + class2name[ toString.call(o) ] || "Object"; +} + +function typeName(o){ + if (o === undefined || o === null) + return String(o); + var T = type(o); + return T.className || (T !== Function ? T.name : basicTypeName(o)); } var arrayLike = isArray.types = []; -function isArray(obj) { return type_of(obj) === "array" || (arrayLike.indexOf(type(obj)) !== -1); } -function isFunction(obj) { return type_of(obj) === "function"; } -function isString(obj) { return type_of(obj) === "string"; } -function isNumber(obj) { return type_of(obj) === "number"; } -function isWindow(obj) { return obj && typeof obj === "object" && "setInterval" in obj; } +function isArray(o) { return basicTypeName(o) === "Array" || (arrayLike.indexOf(type(o)) !== -1); } +function isFunction(o) { return basicTypeName(o) === "Function"; } +function isString(o) { return basicTypeName(o) === "String"; } +function isNumber(o) { return basicTypeName(o) === "Number"; } +function isWindow(o) { return o && typeof o === "object" && "setInterval" in o; } -function isPlainObject( obj ){ +function isPlainObject(o){ // Must be an Object. // Because of IE, we also have to check the presence of the constructor property. // Make sure that DOM nodes and window objects don't pass through, as well - if ( !obj || type_of(obj) !== "object" || obj.nodeType || isWindow(obj) ) + if ( !o || basicTypeName(o) !== "Object" || o.nodeType || isWindow(o) ) return false; // Not own constructor property must be Object - if ( obj[FN] && - !hasOwn.call(obj, FN) && - !hasOwn.call(obj[FN][PT], "isPrototypeOf") ) + if ( o[FN] && + !hasOwn.call(o, FN) && + !hasOwn.call(o[FN][PT], "isPrototypeOf") ) return false; // Own properties are enumerated firstly, so to speed up, // if last one is own, then all properties are own. var key; - for ( key in obj ) {} + for ( key in o ) {} - return key === undefined || hasOwn.call( obj, key ); + return key === undefined || hasOwn.call( o, key ); } -function type( o ) { +function type(o) { if ( o === null || o === undefined ) return o; @@ -95,7 +102,7 @@ function type( o ) { } } -function is( A, B ){ +function is(A, B){ if ( isArray(B) ) return B.some( is.bind(this,A) ); else { @@ -106,7 +113,8 @@ function is( A, B ){ exports['is'] = is; exports['type'] = type; -exports['type_of'] = type_of; +exports['basicTypeName'] = basicTypeName; +exports['typeName'] = typeName; exports['isArray'] = isArray; exports['isFunction'] = isFunction; @@ -115,5 +123,5 @@ exports['isNumber'] = isNumber; exports['isWindow'] = isWindow; exports['isPlainObject'] = isPlainObject; -type['KNOWN_CLASSES'] = KNOWN_CLASSES; +type['KNOWN_CLASSES'] = KNOWN_CLASSES; diff --git a/src/Y/types/function.cjs b/src/Y/types/function.cjs index b332334..e11e31b 100644 --- a/src/Y/types/function.cjs +++ b/src/Y/types/function.cjs @@ -284,4 +284,4 @@ function getName( fn ){ // Export these last to avoid methodizing them exports['YFunction'] = YF(YF); -exports._ = _; +exports['_'] = _; diff --git a/src/Y/y.cjs b/src/Y/y.cjs index 57bb49d..5a22487 100644 --- a/src/Y/y.cjs +++ b/src/Y/y.cjs @@ -71,8 +71,7 @@ function Y(o){ } // Do we have a type-specific wrapper? - var name = type.type_of(o) - , yname = 'Y' + name.charAt(0).toUpperCase() + name.slice(1) + var yname = 'Y' + type.basicTypeName(o) , YType = Y[yname] ; diff --git a/src/evt.cjs b/src/evt.cjs index 7981385..61462cf 100644 --- a/src/evt.cjs +++ b/src/evt.cjs @@ -39,12 +39,19 @@ var Y = require('Y').Y , objToString = _Object[P].toString , classToString = function toString(){ return this.className+"()"; } -; +, + -// Private delegating constructor -- must be defined for every -// new class to prevent shared state. All construction is -// actually done in the init method. -exports['ConstructorTemplate'] = ConstructorTemplate; +/** + * @private + * Private delegating constructor. This function must be redefined for every + * new class to prevent shared state. All construction is actually done in + * the methods initialise and init. + * + * Fires `create` event prior to initialisation if not subclassing. + */ +ConstructorTemplate = +exports['ConstructorTemplate'] = function ConstructorTemplate() { var cls = arguments.callee , instance = this; @@ -64,7 +71,39 @@ function ConstructorTemplate() { return instance; } +, +/** + * Fires `init` event after calling init(). Note that events are fired + * for proper instances of the class and instances of subclasses (but + * not for the instance created when subclassing). + */ +createInitialise = +exports['createInitialise'] = +function createInitialise(NewClass){ + function initialise(){ + var instance = this + , init = NewClass.prototype.init + ; + + instance.__emitter__ = new Emitter(instance, NewClass); + + if (init) { + var result = init.apply(instance, arguments); + if (result) instance = result; + } + + instance.fire('init', instance, { + 'instance' : instance, + 'cls' : NewClass, + 'args' : Y(arguments) + }); + + return instance; + } + return Y(initialise); +} +; @@ -156,28 +195,7 @@ function Class(className, Parent, members){ NewClass.__super__ = SuperClass; // don't override NewClass.constructor -- it should be Function prototype.constructor = prototype.__class__ = NewClass; - NewClass.init = // XXX: This means subclasses will fire events. - prototype.initialise = - Y(function initialise(){ - var instance = this - , init = NewClass.prototype.init - ; - - instance.__emitter__ = new Emitter(instance, NewClass); - - if (init) { - var result = init.apply(instance, arguments); - if (result) instance = result; - } - - instance.fire('init', instance, { - 'instance' : instance, - 'cls' : NewClass, - 'args' : Y(arguments) - }); - - return instance; - }); + NewClass.init = prototype.initialise = createInitialise(NewClass); // Add class emitter var ParentEmitter = (Parent.__emitter__ ? Parent : ClassFactory) diff --git a/src/ezl/layer.cjs b/src/ezl/layer.cjs index 6857efb..21f150f 100644 --- a/src/ezl/layer.cjs +++ b/src/ezl/layer.cjs @@ -21,7 +21,6 @@ Y.subclass('Layer', { _cssClasses : 'ezl layer', canvas : null, - parent : null, children : null, @@ -43,10 +42,14 @@ Y.subclass('Layer', { // Transforms _origin : null, // rotational origin transform : null, // Object - useCanvasScaling : false, // default to CSS3 scaling /// Defaults /// + useCanvasScaling : false, // Default to CSS3 scaling + alwaysClearDrawing : true, // Whether to clear the canvas content before redraw + alwaysClearAttrs : false, // Whether to remove all canvas attributes (CONTEXT_ATTRS) + // and transforms (scale, rotate, translate) and reset defaults before redraw + originX : 0, originY : 0, @@ -129,7 +132,7 @@ Y.subclass('Layer', { */ empty : function empty(ctx){ this.children.invoke('remove'); - this.clear(); + this.clear(ctx); return this; }, @@ -469,48 +472,55 @@ Y.subclass('Layer', { * @param {Boolean} [force=false] Forces redraw. */ draw : function draw(ctx, force){ + var _ctx = ctx || this.ctx; + if ( this.dirty || force ){ - var _ctx = ctx || this.ctx; + this.dirty = false; this._openPath(_ctx); - this.drawShape(_ctx); + this.render(_ctx); this._closePath(_ctx); } - this.dirty = false; this.children.invoke('draw', ctx, force); return this; }, _openPath : function _openPath(ctx){ var self = this - , alwaysClear = this.alwaysClear , neg = this.negBleed , t = this.transform , w = this.canvasWidth, h = this.canvasHeight ; ctx.beginPath(); - ctx.setTransform(1,0,0,1,0,0); - ctx.clearRect(-w,-h, 2*w,2*h); + // TODO: only alwaysClearAttrs should reset transforms? + // if (this.alwaysClearAttrs) + ctx.setTransform(1,0,0,1,0,0); + + if (this.alwaysClearDrawing) + ctx.clearRect(-w,-h, 2*w,2*h); + + // if (this.alwaysClearAttrs) { if (this.useCanvasScaling && (t.scale.x !== 1 || t.scale.y !== 1)) ctx.scale(t.scale.x,t.scale.y); ctx.translate(neg.x, neg.y); ctx.translate(t.translate.x, t.translate.y); + // } // Set context attributes CONTEXT_ATTRS.forEach(function(name){ - if (self[name] !== undefined) - ctx[name] = self[name]; - else if (alwaysClear) + if (this[name] !== undefined) + ctx[name] = this[name]; + else if (this.alwaysClearAttrs) delete ctx[name]; - }); + }, this); return this; }, /** To be implemented by subclasses. */ - drawShape : function drawShape(ctx){ return this; }, + render : function render(ctx){ return this; }, _closePath : function _closePath(ctx){ ctx.closePath(); @@ -518,9 +528,9 @@ Y.subclass('Layer', { }, /** - * Clears this layer and all children. + * Clears this layer and optionally all children. */ - clear : function clear(ctx){ + clear : function clear(ctx, clearChildren){ var w = this.canvas.width() , h = this.canvas.height(); ctx = ctx || this.ctx; @@ -528,7 +538,8 @@ Y.subclass('Layer', { ctx.setTransform(1,0,0,1,0,0); ctx.clearRect(-w,-h, 2*w,2*h); ctx.closePath(); - this.children.invoke('clear'); + if (clearChildren) + this.children.invoke('clear'); return this; }, diff --git a/src/ezl/loc/boundingbox.cjs b/src/ezl/loc/boundingbox.cjs index 5de767d..973757f 100644 --- a/src/ezl/loc/boundingbox.cjs +++ b/src/ezl/loc/boundingbox.cjs @@ -41,6 +41,7 @@ Rect.subclass('BoundingBox', { get origin(){ return this._origin; }, relocate : function relocate(x,y){ + if (x instanceof Array) { y=x[1]; x=x[0]; } var _x1 = this[X1], _y1 = this[Y1] , _x2 = this[X2], _y2 = this[Y2] @@ -55,11 +56,11 @@ Rect.subclass('BoundingBox', { }, relocated : function relocated(x,y){ - var bb = this.clone(); - return bb.relocate(x,y); + return this.clone().relocate(x,y); }, resize : function resize(w,h){ + if (w instanceof Array) { h=w[1]; w=w[0]; } var x1 = this[X1], y1 = this[Y1] , x2 = this[X2], y2 = this[Y2] , wOld = x2-x1, hOld = y2-y1 @@ -87,13 +88,28 @@ Rect.subclass('BoundingBox', { }, resized : function resized(w,h){ - var bb = this.clone(); - return bb.resize(w,h); + return this.clone().resize(w,h); }, clone : function clone(){ var o = this._origin; return new BoundingBox(this[X1],this[Y1], this[X2],this[Y2], o[X1], o[Y1]); + }, + + + get topSide(){ return new Line(this[X1],this[Y1], this[X2],this[Y1]); }, + get bottomSide(){ return new Line(this[X1],this[Y2], this[X2],this[Y2]); }, + get leftSide(){ return new Line(this[X1],this[Y1], this[X1],this[Y2]); }, + get rightSide(){ return new Line(this[X2],this[Y1], this[X2],this[Y2]); }, + + side : function side(which){ + switch (which) { + case 'top' : return this.topSide; + case 'bottom' : return this.bottomSide; + case 'left' : return this.leftSide; + case 'right' : return this.rightSide; + default : throw new Error('Unknown side: '+which); + } } }); diff --git a/src/ezl/loop/eventloop.cjs b/src/ezl/loop/eventloop.cjs index d744531..c93e2e5 100644 --- a/src/ezl/loop/eventloop.cjs +++ b/src/ezl/loop/eventloop.cjs @@ -11,8 +11,8 @@ var Y = require('Y').Y EventLoop = exports['EventLoop'] = Emitter.subclass('EventLoop', { - samples : NUM_SAMPLES, // Number of frames to track for effective fps - dilation : 1.0, + samples : NUM_SAMPLES, // Number of frames to track for effective fps + timeDilation : 1.0, framerate : 0, // Target framerate frametime : 0, // 1000 / framerate @@ -46,7 +46,7 @@ Emitter.subclass('EventLoop', { if (samples !== undefined) this.samples = samples; if (dilation !== undefined) - this.dilation = dilation; + this.timeDilation = dilation; this.reset(); }, @@ -140,3 +140,4 @@ function decorate(delegate){ return delegate; } + diff --git a/src/ezl/math/line.cjs b/src/ezl/math/line.cjs index bdf54ee..1daa9c8 100644 --- a/src/ezl/math/line.cjs +++ b/src/ezl/math/line.cjs @@ -54,14 +54,34 @@ Vec.subclass('Line', { return this; }, + /** + * @return {Vec} Delta-(x,y) on this line after given elapsed parametric time. + */ + wouldMove : function wouldMove(t){ + return new Vec( t*this.pa, t*this.pb ); + }, + + /** + * @return {Float} Elapsed parametric time needed to move the given delta-(x,y). + */ + timeToMove : function timeToMove(dx,dy){ + // see note at iparametric + return (dx/this.pa + dy/this.pb) * 0.5; + }, + parametric : function parametric(t){ + // return this.wouldMove(t).add(this.p1); return new Vec( this.x1 + t*this.pa , this.y1 + t*this.pb ); }, iparametric : function iparametric(x,y){ - return new Vec( (x-this.x1)/this.pa , - (y-this.y1)/this.pa ); + var tx = (x - this.x1)/this.pa + , ty = (y - this.y1)/this.pb; + // tx and ty should be the same number provided (x,y) is on the line + // we average to provide a reasonable answer when that's not true, + // rather than failing outright or requiring the user to deal with a Vec. + return (tx + ty) * 0.5; }, pointAtX : function pointAtX(x){ return new Vec(x, this.calcY(x)); }, diff --git a/src/ezl/shape/circle.cjs b/src/ezl/shape/circle.cjs index e02eab7..733148a 100644 --- a/src/ezl/shape/circle.cjs +++ b/src/ezl/shape/circle.cjs @@ -25,7 +25,7 @@ Shape.subclass('Circle', { return this; }, - drawShape : function drawShape(ctx){ + render : function render(ctx){ var r = this._radius; ctx.arc(r,r, r, 0, Math.PI*2, false); ctx.fill(); diff --git a/src/ezl/shape/line.cjs b/src/ezl/shape/line.cjs index 34bc660..e007377 100644 --- a/src/ezl/shape/line.cjs +++ b/src/ezl/shape/line.cjs @@ -80,7 +80,7 @@ Shape.subclass('Line', { return this; }, - drawShape : function drawShape(ctx){ + render : function render(ctx){ this._fixSize(); var x1,y1, x2,y2 , line = this.line, p1 = line.p1, p2 = line.p2 @@ -112,7 +112,7 @@ Shape.subclass('Line', { ctx.closePath(); // } catch(e) { // if (window.console) { - // console.error(this+'.drawShape()'); + // console.error(this+'.render()'); // console.log(' points:', x1,y1, ' ', x2,y2); // console.log(' bounds:', minW,minH, ' ', maxW,maxH); // console.log(' ::', this, line); diff --git a/src/ezl/shape/polygon.cjs b/src/ezl/shape/polygon.cjs index b0308b8..8601c16 100644 --- a/src/ezl/shape/polygon.cjs +++ b/src/ezl/shape/polygon.cjs @@ -43,7 +43,7 @@ Shape.subclass('Polygon', { return values.map(function(v, i){ return (self[which+i] = v); }); }, - drawShape : function drawShape(ctx){ + render : function render(ctx){ this.points.forEach(function(loc, i){ ctx.lineTo(loc.x, loc.y); }); diff --git a/src/ezl/shape/rect.cjs b/src/ezl/shape/rect.cjs index 845688d..d88a45c 100644 --- a/src/ezl/shape/rect.cjs +++ b/src/ezl/shape/rect.cjs @@ -13,9 +13,10 @@ Shape.subclass('Rect', { this.size(w,h); }, - drawShape : function drawShape(ctx){ + render : function render(ctx){ ctx.rect(0,0, this.canvasWidth,this.canvasHeight); ctx.fill(); + ctx.stroke(); }, toString : function(){ diff --git a/src/ezl/util/configuration.cjs b/src/ezl/util/configuration.cjs deleted file mode 100644 index 539bd0a..0000000 --- a/src/ezl/util/configuration.cjs +++ /dev/null @@ -1,114 +0,0 @@ -var Y = require('Y').Y -, getNested = require('Y/types/object').getNested -, Emitter = require('Y/modules/y.event').Emitter -, - - -Config = -exports['Config'] = -Y.YObject.subclass('Config', function(Config){ - - Y.core.extend(this, { - - init : function initConfig(defaults){ - this._defaults = defaults || {}; - this._o = Y({}, this._defaults); - this.__emitter__ = new Emitter(this); - }, - - clone : function clone(){ - var c = new Config(); - c._defaults = this._defaults; - c._o = c._o.clone(); - return c; - }, - - set : function set(key, value, def){ - if ( key !== undefined ){ - var meta = this.ensure(key).getNested(key, def, true) - , old = meta.value ; - def = (def === undefined ? old : def); - value = (value === undefined ? def : value); - - meta.obj[meta.key] = value; - this._fireMutation('set', key, old, value, meta.obj); - } - return this; - }, - - remove : function remove(key){ - if ( key !== undefined ){ - var sentinel = {} - , meta = this.getNested(key, sentinel, true) - , old = (meta.value === sentinel ? undefined : old); - - if ( meta.obj ) { - delete meta.obj[meta.key]; - this._fireMutation('remove', key, old, undefined, meta.obj); - } - } - return this; - }, - - /** - * @private - */ - _fireMutation : function _fireMutation(evt, key, oldval, newval, obj){ - if (newval !== oldval){ - var data = { - 'key' : key, - 'oldval' : oldval, - 'newval' : newval, - 'obj' : obj, - 'root' : this - }; - this.fire(evt+':'+key, this, data); - this.fire(evt, this, data); - } - return this; - }, - - /** - * Recursively iterates the hierarchy of the Config, depth-first. - */ - reduce : function reduce(fn, acc, context){ - context = context || this; - - var ret = Y.reduce(this._o, this._reducer, { - 'acc' : acc, - 'path' : new Y.YArray(), - 'cxt' : context - }, this); - - return ret.acc; - }, - - /** - * @private - * XXX: Not going to call the iterator on root objects. ok? - * XXX: Always passing the Config object as obj to iterator. ok? - */ - _reducer : function _reducer(state, v, k, o){ - var chain = state.path.push(k).join('.'); - - // Nested object -- recurse - if ( Y.isPlainObject(v) ) - state = Y.reduce(v, this._reducer, state, this); - - // Normal value -- invoke iterator - else - state.acc = fn.call(state.cxt, state.acc, v, chain, this); - - state.path.pop(); - return state; - }, - - getDefault : function getDefault(k, def){ - return getNested(this._defaults, k, def); - } - - }); - - this['get'] = this.getNested; - -}); diff --git a/src/ezl/widget/cooldown.cjs b/src/ezl/widget/cooldown.cjs index dba3caa..c7f492a 100644 --- a/src/ezl/widget/cooldown.cjs +++ b/src/ezl/widget/cooldown.cjs @@ -36,8 +36,8 @@ Layer.subclass('CooldownGauge', function setupCooldownGauge(CooldownGauge){ return Layer.fn.draw.apply(this, arguments); }; - this['drawShape'] = - function drawShape(ctx){ + this['render'] = + function render(ctx){ var cool = this.cooldown; if (cool.ready) diff --git a/src/tanks/abilities/buff.cjs b/src/tanks/abilities/buff.cjs new file mode 100644 index 0000000..e69de29 diff --git a/src/tanks/abilities/index.cjs b/src/tanks/abilities/index.cjs new file mode 100644 index 0000000..e69de29 diff --git a/src/tanks/config.cjs b/src/tanks/config.cjs index 03a5308..06ec09f 100644 --- a/src/tanks/config.cjs +++ b/src/tanks/config.cjs @@ -1,6 +1,7 @@ // -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*- -var Y = require('Y').Y +var Y = require('Y').Y , Config = require('Y/modules/y.config').Config +, Vec = require('ezl/math').Vec , defaults = @@ -16,11 +17,22 @@ exports['defaults'] = { showAttackCooldown : false, showCountdown : (document.location.host.toString() !== 'tanks.woo') }, - pathing : { - overlayAiPaths : false, - overlayPathmap : false, - traceTrajectories : false + pathing : { + gridSquare : REF_SIZE, + gridSquareMid : new Vec(REF_SIZE/2, REF_SIZE/2), + overlayAiPaths : false, + overlayPathmap : false, + traceTrajectories : false } -}; +} +, + +config = +exports['config'] = new Config(defaults) +; + +config.addEventListener('set:pathing.gridSquare', function(evt){ + var sq = evt.data.newval; + config.set('pathing.gridSquareMid', new Vec(sq/2, sq/2)); +}); -exports['values'] = new Config(defaults); diff --git a/src/tanks/game.cjs b/src/tanks/game.cjs index 135f487..3c5ac9f 100644 --- a/src/tanks/game.cjs +++ b/src/tanks/game.cjs @@ -6,10 +6,11 @@ var Y = require('Y').Y , EventLoop = require('ezl/loop').EventLoop , Circle = require('ezl/shape').Circle -, config = require('tanks/config').values -, map = require('tanks/map') -, thing = require('tanks/thing') -, Grid = require('tanks/ui/grid').Grid +, config = require('tanks/config').config +, map = require('tanks/map') +, thing = require('tanks/thing') +, Grid = require('tanks/ui/grid').Grid +, PathMapUI = require('tanks/ui/pathmapui').PathMapUI , Level = map.Level , Thing = thing.Thing, Tank = thing.Tank, Bullet = thing.Bullet @@ -19,11 +20,12 @@ var Y = require('Y').Y Game = exports['Game'] = Y.subclass('Game', { - overlayPathmap : null, - overlayAiPaths : null, - gameoverDelay : null, + // Config + gameoverDelay : null, + + // Defaults + gameover : false, - gameover : false, init : function initGame(viewport){ @@ -45,10 +47,13 @@ Y.subclass('Game', { this.level = new Level(this, COLUMNS*REF_SIZE, ROWS*REF_SIZE) .appendTo(this.root); - this.pathmap = this.level.pathmap; - Thing.addEventListener('init', this.addUnit); - Thing.addEventListener('destroy', this.killUnit); + var pathmap = this.pathmap = this.level.pathmap; + pathmap.ui = new PathMapUI(this, pathmap); + this.level.append(pathmap.ui); + + Thing.addEventListener('init', this.addThing); + Thing.addEventListener('destroy', this.killThing); this.addEventListener('tick', this.tick); @@ -56,8 +61,8 @@ Y.subclass('Game', { }, destroy : function destroy(){ - Thing.removeEventListener('init', this.addUnit); - Thing.removeEventListener('destroy', this.killUnit); + Thing.removeEventListener('init', this.addThing); + Thing.removeEventListener('destroy', this.killThing); this.stop(); this.resetGlobals(); }, @@ -72,10 +77,6 @@ Y.subclass('Game', { draw : function draw(){ this.root.draw(); - - this.pathmap.removeOverlay(this.viewport); - if (this.overlayPathmap) - this.pathmap.overlay(this.viewport); }, /** @@ -91,9 +92,6 @@ Y.subclass('Game', { SECONDTH = ELAPSED / 1000; SQUARETH = REF_SIZE * SECONDTH - if (!this.overlayAiPaths) - this.pathmap.hidePaths(); - this.active.invoke('updateCooldowns', ELAPSED, NOW); this.active.invoke('act'); this.animations = this.animations.filter(this.tickAnimations, this); @@ -133,7 +131,7 @@ Y.subclass('Game', { // *** Agent Management *** // - addUnit : function addUnit(unit, col,row){ + addThing : function addThing(unit, col,row){ if (unit instanceof Event) unit = unit.trigger; @@ -165,7 +163,7 @@ Y.subclass('Game', { return unit; }, - killUnit : function killUnit(unit){ + killThing : function killThing(unit){ if (unit instanceof Event) unit = unit.trigger; @@ -181,7 +179,7 @@ Y.subclass('Game', { return unit; }, - moveUnitTo : function moveUnitTo(agent, x,y){ + moveThingTo : function moveThingTo(agent, x,y){ this.pathmap.removeBlocker(agent); agent.position(x,y); this.pathmap.addBlocker(agent); @@ -228,7 +226,5 @@ Y.subclass('Game', { }); -config.updateOnChange( - ['pathing.overlayPathmap', 'pathing.overlayAiPaths', 'game.gameoverDelay'], - Game.fn); +config.updateOnChange('game.gameoverDelay', Game.fn); diff --git a/src/tanks/index.js b/src/tanks/index.js index 1083c58..455172d 100644 --- a/src/tanks/index.js +++ b/src/tanks/index.js @@ -9,7 +9,7 @@ require('tanks/globals'); // exports.tanks = tanks = { - 'config' : require('tanks/config'), + 'config' : require('tanks/config').config, 'ui' : require('tanks/ui'), 'Game' : require('tanks/game').Game, diff --git a/src/tanks/map/level.cjs b/src/tanks/map/level.cjs index ac86997..a3cc320 100644 --- a/src/tanks/map/level.cjs +++ b/src/tanks/map/level.cjs @@ -2,6 +2,7 @@ var Y = require('Y').Y , Rect = require('ezl/shape').Rect , Thing = require('tanks/thing/thing').Thing , Tank = require('tanks/thing/tank').Tank +, Item = require('tanks/thing/item').Item , PlayerTank = require('tanks/thing/player').PlayerTank , PathMap = require('tanks/map/pathmap').PathMap , Wall = require('tanks/map/wall').Wall @@ -37,13 +38,15 @@ Rect.subclass('Level', { this ); P = - game.player = game.addUnit(new PlayerTank(1), 5,9); - // game.addUnit(new Tank(1).colors('#4596FF', '#182B53', '#F25522'), 3,9); + game.player = game.addThing(new PlayerTank(1), 5,9); + // game.addThing(new Tank(1).colors('#4596FF', '#182B53', '#F25522'), 3,9); E = - game.addUnit(new Tank(2), 0,1); - game.addUnit(new Tank(2), 1,0); - // game.addUnit(new Tank(2), 8,1); + game.addThing(new Tank(2), 0,1); + game.addThing(new Tank(2), 1,0); + // game.addThing(new Tank(2), 8,1); + + I = game.addThing(new Item(), 8,8); }, addWall : function addWall(x,y, w,h, isBoundary){ @@ -57,6 +60,6 @@ Rect.subclass('Level', { return wall; }, - drawShape : Y.op.nop + render : Y.op.nop }); \ No newline at end of file diff --git a/src/tanks/map/pathmap.cjs b/src/tanks/map/pathmap.cjs index 71abcfe..b1957a9 100644 --- a/src/tanks/map/pathmap.cjs +++ b/src/tanks/map/pathmap.cjs @@ -3,13 +3,14 @@ var Y = require('Y').Y , math = require('ezl/math') , QuadTree = require('ezl/util/tree/quadtree').QuadTree , astar = require('ezl/util/astar') +, config = require('tanks/config').config , Bullet = require('tanks/thing/bullet').Bullet -, config = require('tanks/config').values , Vec = math.Vec , Line = math.Line -, GRID_SQUARE_SIZE = REF_SIZE -, GRID_SQUARE_MID_PT = new Vec(REF_SIZE/2, REF_SIZE/2) +, PASSABLE = 1 // Does not obstruct other objects +, BLOCKING = 2 // Objstructs other blockers +, ZONE = 3 // Does not obstruct other objects, but still collides with them , @@ -18,8 +19,8 @@ PathMap = exports['PathMap'] = QuadTree.subclass('PathMap', { // Config - overlayAiPaths : null, - overlayPathmap : null, + gridSquare : null, + gridSquareMid : null, @@ -114,22 +115,22 @@ QuadTree.subclass('PathMap', { var B = blocker.boundingBox; if (bb.x2 <= B.x1 && x2 >= B.x1) { msg += 'left'; - side = new Line(B.x1,B.y1, B.x1,B.y2); + side = B.leftSide; to = trj.pointAtX(B.x1-offX-1); } else if (bb.x1 >= B.x2 && x1 <= B.x2) { msg += 'right'; - side = new Line(B.x2,B.y1, B.x2,B.y2); + side = B.rightSide; to = trj.pointAtX(B.x2+ro.x+1); } else if (bb.y2 <= B.y1 && y2 >= B.y1) { msg += 'top'; - side = new Line(B.x1,B.y1, B.x2,B.y1); + side = B.topSide; to = trj.pointAtY(B.y1-offY-1); } else if (bb.y1 >= B.y2 && y1 <= B.y2) { msg += 'bottom'; - side = new Line(B.x1,B.y2, B.x2,B.y2); + side = B.bottomSide; to = trj.pointAtY(B.y2+ro.y+1); } @@ -146,7 +147,7 @@ QuadTree.subclass('PathMap', { */ grid : function grid(){ if ( !this._grid ) { - var size = GRID_SQUARE_SIZE + var size = this.gridSquare , floor = Math.floor, ceil = Math.ceil , cols = ceil((this.width-2) /size) , rows = ceil((this.height-2)/size) @@ -195,25 +196,30 @@ QuadTree.subclass('PathMap', { }, vec2Square : function vec2Square(x,y){ - if (x instanceof Array){ - y = x.y; - x = x.x; - } - var floor = Math.floor, size = GRID_SQUARE_SIZE; + if (x instanceof Array){ y = x.y; x = x.x; } + var floor = Math.floor, size = this.gridSquare; return new 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 = GRID_SQUARE_SIZE; + if (x instanceof Array){ y = x.y; x = x.x; } + var floor = Math.floor, size = this.gridSquare; return new Vec(floor(x)*size, floor(y)*size); }, - path : function path(start, end, id){ - var size = GRID_SQUARE_SIZE, floor = Math.floor + path : function path(start, end, agent){ + return this.gridPath(start, end, agent) + .invoke('scale', this.gridSquare) + .invoke('add', this.gridSquareMid) + .end(); + }, + + /** + * @protected + * Generates grid-sized path from start to end. + */ + gridPath : function gridPath(start, end, agent){ + var size = this.gridSquare, floor = Math.floor , grid = this.grid() , startX = floor(start.x/size) @@ -223,169 +229,12 @@ QuadTree.subclass('PathMap', { , endX = floor(end.x/size) , endY = floor(end.y/size) , endN = grid[endX][endY] - - , path = Y(astar.search(grid, startN, endN)) ; - - if (this.overlayAiPaths) - this.drawPath(id, startN, path); - - return path - .invoke('scale', size) - .invoke('add', GRID_SQUARE_MID_PT) - .end(); - }, - - drawPath : function drawPath(id, start, path){ - var size = GRID_SQUARE_SIZE, off - , w = this.width-2, h = this.height-2 - , el = this.game.viewport - , grid = this.grid() - , canvas = $('#path'+id, el)[0] - ; - - if (!canvas) { - canvas = $('').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 - off = size*0.3; - - function drawStep(p){ - var r = size/2 - off - , 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.1)'; - drawStep( start ); - path.forEach(drawStep); - - - // Draw start - off = size*0.15; - - ctx.fillStyle = 'rgba(0,255,0,0.05)'; - drawStep( start ); - - // Draw finish - ctx.fillStyle = 'rgba(0,0,255,0.05)'; - drawStep( path.last() ); - - $(canvas).show(); - }, - - destroyPath : function destroyPath(id){ - $('#path'+id, this.game.viewport).remove(); - }, - - hidePath : function hidePath(id){ - $('#path'+id, this.game.viewport).hide(); - }, - - hidePaths : function hidePaths(){ - $('.path', this.game.viewport).hide(); - }, - - - - _overlayBG : $('')[0], - overlay : function overlay(gridEl){ - var w = this.width-2 - , h = this.height-2 - , canvas = $('.overlay', gridEl)[0] - ; - - if ( !canvas ) { - canvas = $('').prependTo(gridEl)[0]; - $(canvas).width(w).height(h); - canvas.width = w; - canvas.height = h; - } - - var ctx = canvas.getContext('2d'); - // ctx.scale(SCALE, SCALE); - - // Clear the canvas - ctx.beginPath(); - ctx.clearRect(this.x,this.y, w,h); - ctx.closePath(); - - // ctx.fillStyle = ctx.createPattern(this._overlayBG, 'repeat'); - ctx.fillStyle = 'rgba(255,255,255,0.1)'; - ctx.lineWidth = 1; - ctx.strokeStyle = 'rgba(255,255,255,0.2)'; - - - // Draw regions - this.reduce(function(acc, v, r, tree){ - if ( acc[r.id] || v.isBoundary ) - return acc; - - acc[r.id] = r; - - ctx.beginPath(); - // ctx.globalAlpha = 0.1; - ctx.rect(r.x1,r.y1, r.width,r.height); - ctx.fill(); - // ctx.globalAlpha = 1; - ctx.stroke(); - ctx.closePath(); - - return acc; - }, {}); - - $(canvas).show(); - }, - - removeOverlay : function removeOverlay(gridEl){ - $('.overlay', gridEl).hide(); + return Y(astar.search(grid, startN, endN)) } - }); -config.updateOnChange( - ['pathing.overlayPathmap', 'pathing.overlayAiPaths'], - PathMap.fn); 'set remove removeAll clear' .split(' ') @@ -396,3 +245,7 @@ config.updateOnChange( }; }); +config.updateOnChange( + ['pathing.gridSquare', 'pathing.gridSquareMid'], + PathMap.fn); + diff --git a/src/tanks/map/trajectory.cjs b/src/tanks/map/trajectory.cjs index edace1b..67d014c 100644 --- a/src/tanks/map/trajectory.cjs +++ b/src/tanks/map/trajectory.cjs @@ -1,12 +1,16 @@ var Y = require('Y').Y +, op = require('Y/op') , math = require('ezl/math') , Vec = math.Vec , Line = math.Line , Rect = math.Rect , BoundingBox = require('ezl/loc').BoundingBox , Thing = require('tanks/thing/thing').Thing + +, BOUND_SIZE_RATIO = 0.75 , + Trajectory = exports['Trajectory'] = Line.subclass('Trajectory', { @@ -35,13 +39,14 @@ Line.subclass('Trajectory', { // init with raw numbers to do calculations Line.init.call(this, x1,y1, x2,y2, tdist || this.tdist); + // Find appropriate edge in direction of line var pm = this.pathmap , ex = (x1 > x2 ? -REF_SIZE : REF_SIZE+pm.width ) , ey = (y1 > y2 ? -REF_SIZE : REF_SIZE+pm.height) , edge = this.near(ex,ey) ; - // Move goal point beyond far wall to avoid rotations + // Move goal point beyond far wall to avoid rotations post-reflection x2 = this.x2 = edge.x; y2 = this.y2 = edge.y; this.p2 = new Vec(x2,y2); @@ -55,14 +60,17 @@ Line.subclass('Trajectory', { // Determine how much time can pass before we risk teleporting // We'll need to reset this whenever the bounding box changes size resetBound : function resetBound(){ - var BOUND_SIZE_RATIO = 0.75 + var p = BOUND_SIZE_RATIO , abs = Math.abs , w = this.owner.width, h = this.owner.height; - this.tBound = Math.min( abs(w / this.pa) * BOUND_SIZE_RATIO, - abs(h / this.pb) * BOUND_SIZE_RATIO ); + this.tBound = Math.min(abs(w/this.pa) * p, abs(h/this.pb) * p); return this; }, + clone : function clone(){ + return new Trajectory(this.owner, this.x1,this.y1, this.x2,this.y2, this.tdist); + }, + intersects : function intersects(x,y){ var o = x; @@ -72,7 +80,7 @@ Line.subclass('Trajectory', { if (o instanceof Rect) return o.intersects(this); - return Line.prototype.intersects.call(this, x,y); + return Line.fn.intersects.call(this, x,y); }, @@ -83,34 +91,26 @@ Line.subclass('Trajectory', { * @return -1 if a closer b, 1 if a further b, 0 if a same as b */ compare : function compare(a, b){ - if (a instanceof Thing) a = a.midpoint; - if (b instanceof Thing) b = b.midpoint; + if (a instanceof Thing) a = a.loc; + if (b instanceof Thing) b = b.loc; var abs = Math.abs - // , cur = this.owner.midpoint - // , t = this.iparametric(cur.x, cur.y) , t = this.elapsed , xa = this.calcX(a.y), ya = this.calcY(a.x) - , ta = this.iparametric(xa,ya) - , da = (ta.x + ta.y)/2 - t - // , da = (ta.x - t.x + ta.y - t.y)/2 - // , dxa = ta.x - t.x, dya = ta.y - t.y + , ta = this.iparametric(xa,ya) - t , xb = this.calcX(b.y), yb = this.calcY(b.x) - , tb = this.iparametric(xb,yb) - , db = (tb.x + tb.y)/2 - t - // , db = (tb.x - t.x + tb.y - t.y)/2 - // , dxb = tb.x - t.x, dyb = tb.y - t.y + , tb = this.iparametric(xb,yb) - t ; // If one has passed, return the other - if ( da < 0 && db >= 0 ) + if ( ta < 0 && tb >= 0 ) return 1; - if ( db < 0 && da >= 0 ) + if ( tb < 0 && ta >= 0 ) return -1; - return Y.op.cmp(abs(da), abs(db)); + return op.cmp(abs(ta), abs(tb)); }, closer : function closer(o1, o2){ @@ -122,10 +122,10 @@ Line.subclass('Trajectory', { }, comesWithin : function comesWithin(pt, w,h){ - if ( !this.owner.midpoint ) + if ( !this.owner.loc ) return false; - if (pt instanceof Thing) pt = pt.midpoint; + if (pt instanceof Thing) pt = pt.loc; if ( w === undefined ){ w = 0; h = 0; @@ -133,14 +133,13 @@ Line.subclass('Trajectory', { h = w.height; w = w.width; } - var cur = this.owner.midpoint + var cur = this.owner.loc , fx = this.calcX(pt.y), fy = this.calcY(pt.x) , t = this.iparametric(cur.x, cur.y) , ft = this.iparametric(fx,fy) , dw = Math.abs(fx - pt.x), dh = Math.abs(fy - pt.y) ; - return ( t.x <= ft.x && t.y <= ft.y - && ( dw <= w || dh <= h ) ); + return ( t <= ft && (dw <= w || dh <= h) ); }, pathBlocked : function pathBlocked(obj, ignore){ @@ -158,78 +157,78 @@ Line.subclass('Trajectory', { }, - step : function step(dt){ - this.halt = false; - - var _dt = dt = (dt === undefined ? ELAPSED : dt) - , _bb = bb = this.owner.boundingBox, bw = bb.width, bh = bb.height - , to, t, r; - - do { - t = Math.min(this.tBound, dt); - dt -= t; - this.elapsed += t; - - to = this.stepTo(t, bb); - if (this.halt) break; - - bb = new BoundingBox(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; - - 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 { - ng = math.reflect(this.p2, test.side); - } - - this.reset(to.x,to.y, ng.x,ng.y); - owner.render(this.game.level); - - // 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')); - } - - return to; - }, + // step : function step(dt){ + // this.halt = false; + // + // var _dt = dt = (dt === undefined ? ELAPSED : dt) + // , _bb = bb = this.owner.boundingBox, bw = bb.width, bh = bb.height + // , to, t, r; + // + // do { + // t = Math.min(this.tBound, dt); + // dt -= t; + // this.elapsed += t; + // + // to = this.stepTo(t, bb); + // if (this.halt) break; + // + // bb = new BoundingBox(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; + // + // 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 { + // ng = math.reflect(this.p2, test.side); + // } + // + // this.reset(to.x,to.y, ng.x,ng.y); + // owner.render(this.game.level); + // + // // 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')); + // } + // + // return to; + // }, toString : function toString(){ return 'T['+this.p1+', '+this.p2+', slope='+this.slope.toFixed(3)+']'; diff --git a/src/tanks/map/traversal.cjs b/src/tanks/map/traversal.cjs new file mode 100644 index 0000000..bf95699 --- /dev/null +++ b/src/tanks/map/traversal.cjs @@ -0,0 +1,95 @@ +var Y = require('Y').Y +, + +Traversal = +exports['Traversal'] = +Y.subclass('Traversal', { + elapsed : 0, + halt : false, + + isBlocked : false, + + + + init : function initTraversal(thing, trajectory){ + this.thing = thing; + this.pathmap = thing.pathmap; + this.trajectory = trajectory || thing.trajectory; + }, + + step : function step(dt){ + this.halt = false; + + var traj = this.trajectory + , thing = this.thing + , bb = thing.boundingBox.clone() + , to, t; + + do { + t = Math.min(traj.tBound, dt); + dt -= t; + this.elapsed += t; + + to = this.stepTo(t, bb); + if (this.halt) break; + + bb = bb.relocate(to); + + } while (dt > 0); + + return to; + }, + + stepTo : function stepTo(t, bb){ + var ng, traj = this.trajectory, og = traj.p2, thing = this.thing + , to = traj.parametric(this.elapsed), _to = to + , test = this.pathmap.moveBlocked(thing, this, to, bb) + ; + + // Blocked! Reflect trajectory + if ( test ) { + to = test.to; + this.bounces++; + + var blocker = test.blockers[0]; + thing.fire('collide', blocker); + blocker.fire('collide', thing); + + // Potentially set by responders to collision event + if (this.halt) return to; + + 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 { + ng = math.reflect(this.p2, test.side); + } + + this.reset(to.x,to.y, ng.x,ng.y); + thing.render(this.game.level); + + // console.log([ + // '['+TICKS+' ('+this.depth+')] '+thing+' 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')); + } + + return to; + }, + + +}) diff --git a/src/tanks/map/wall.cjs b/src/tanks/map/wall.cjs index 8e3f3a3..03c5b3f 100644 --- a/src/tanks/map/wall.cjs +++ b/src/tanks/map/wall.cjs @@ -1,6 +1,7 @@ -var Y = require('Y').Y -, Rect = require('ezl/shape').Rect -, Thing = require('tanks/thing/thing').Thing +var Y = require('Y').Y +, op = require('Y/op') +, Rect = require('ezl/shape').Rect +, Thing = require('tanks/thing/thing').Thing , @@ -32,10 +33,10 @@ Thing.subclass('Wall', { }, // inactive - createCooldowns : Y.op.nop, + createCooldowns : op.nop, // indestructable - dealDamage : Y.op.nop, + dealDamage : op.nop, render : function render(parent){ diff --git a/src/tanks/thing/bullet.cjs b/src/tanks/thing/bullet.cjs index a6ed869..8377ef6 100644 --- a/src/tanks/thing/bullet.cjs +++ b/src/tanks/thing/bullet.cjs @@ -1,16 +1,21 @@ // -*- mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; -*- var Y = require('Y').Y +, op = require('Y/op') , math = require('ezl/math') , shape = require('ezl/shape') +, config = require('tanks/config').config +, thing = require('tanks/thing/thing') , Wall = require('tanks/map/wall').Wall -, Thing = require('tanks/thing/thing').Thing , Trajectory = require('tanks/map/trajectory').Trajectory , Explosion = require('tanks/fx/explosion').Explosion -, config = require('tanks/config').values + +, fillStats = thing.fillStats +, Thing = thing.Thing , Line = shape.Line , Circle = shape.Circle , + Bullet = exports['Bullet'] = Thing.subclass('Bullet', { @@ -57,10 +62,8 @@ Thing.subclass('Bullet', { move : 2.0 // move speed (squares/sec) }, - fillStats : function(){ - this.stats = Y({}, - Thing.fillStats(this.owner.stats), - Thing.fillStats(this.stats) ); + createStats : function createStats(){ + this.stats = Y({}, fillStats(this.owner.stats), fillStats(this.stats) ); }, remove : function remove(){ @@ -69,8 +72,8 @@ Thing.subclass('Bullet', { return this; }, - createCooldowns : Y.op.nop, - updateCooldowns : Y.op.nop, + createCooldowns : op.nop, + updateCooldowns : op.nop, setTarget : function setTarget(x,y){ var loc = this.loc @@ -88,7 +91,7 @@ Thing.subclass('Bullet', { // We test twice because the process of calculating the trajectory // could result in a collision, killing the bullet if (!this.dead) var to = this.trajectory.step( ELAPSED ); - if (!this.dead) this.game.moveUnitTo(this, to.x, to.y); + if (!this.dead) this.game.moveThingTo(this, to.x, to.y); return this; }, diff --git a/src/tanks/thing/index.cjs b/src/tanks/thing/index.cjs index 43a7d85..93d9be7 100644 --- a/src/tanks/thing/index.cjs +++ b/src/tanks/thing/index.cjs @@ -2,4 +2,5 @@ exports['Thing'] = require('tanks/thing/thing').Thing; exports['Bullet'] = require('tanks/thing/bullet').Bullet; exports['Tank'] = require('tanks/thing/tank').Tank; exports['PlayerTank'] = require('tanks/thing/player').PlayerTank; +exports['Item'] = require('tanks/thing/item').Item; // exports['CustomTank'] = require('tanks/thing/customtank').CustomTank; diff --git a/src/tanks/thing/item.cjs b/src/tanks/thing/item.cjs new file mode 100644 index 0000000..ea4b167 --- /dev/null +++ b/src/tanks/thing/item.cjs @@ -0,0 +1,58 @@ +var Y = require('Y').Y +, op = require('Y/op') +, Thing = require('tanks/thing/thing').Thing +, shape = require('ezl/shape') +, Rect = shape.Rect + +, SIZE = REF_SIZE * 0.5 +, + + +Item = +exports['Item'] = +Thing.subclass('Item', { + blocking : true, + active : false, + + width : SIZE, + height : SIZE, + + stats : { hp:1, move:0, power:0 }, + + // inactive + createCooldowns : op.nop, + updateCooldowns : op.nop, + act : op.nop, + + // indestructable + dealDamage : op.nop, + + + init : function initItem(){ + Thing.init.call(this, 0); + + this.addEventListener('collide', this.onCollide.bind(this)); + }, + + onCollide : function onCollide(evt){ + console.log('collide!', this); + this.destroy(); + }, + + render : function render(parent){ + this.remove(); + + var loc = this.loc; + this.shape = new Rect(this.width, this.height) + .origin('50%', '50%') + .position(loc.x, loc.y) + .fill('#83BB32') + .stroke('#1C625B', 2.0) + .appendTo( parent ); + this.shape.layer.attr('title', ''+loc); + + return this; + } + +}) +; diff --git a/src/tanks/thing/player.cjs b/src/tanks/thing/player.cjs index f7768ca..b50713d 100644 --- a/src/tanks/thing/player.cjs +++ b/src/tanks/thing/player.cjs @@ -106,7 +106,7 @@ Tank.subclass('PlayerTank', { // ; // // if ( !blockers.size() ) - // this.game.moveUnitTo(this, x,y); + // this.game.moveThingTo(this, x,y); // }, diff --git a/src/tanks/thing/tank.cjs b/src/tanks/thing/tank.cjs index 2ab69e1..1b33591 100644 --- a/src/tanks/thing/tank.cjs +++ b/src/tanks/thing/tank.cjs @@ -1,4 +1,5 @@ var Y = require('Y').Y +, op = require('Y/op') , Thing = require('tanks/thing/thing').Thing , Bullet = require('tanks/thing/bullet').Bullet , Trajectory = require('tanks/map/trajectory').Trajectory @@ -31,6 +32,7 @@ Thing.subclass('Tank', function(Tank){ width : REF_SIZE*0.55, height : REF_SIZE*0.55, + // Attributes stats : { hp : 1, // health @@ -49,6 +51,8 @@ Thing.subclass('Tank', function(Tank){ shootEnemy : 0.75 // shoot at enemy tank if in range }, + buffs : null, + nShots : 0, currentMove : null, forceCurrentMove : false, @@ -61,13 +65,8 @@ Thing.subclass('Tank', function(Tank){ Thing.init.call(this, align); this.coolgauge = new CooldownGauge(this.cooldowns.attack, this.width+1,this.height+1); this.onBulletDeath = this.onBulletDeath.bind(this); - this.addEventListener('destroy', destroyPath); }; - function destroyPath(){ - this.game.pathmap.destroyPath(this.id); - } - this['onBulletDeath'] = function onBulletDeath(evt){ this.nShots--; }; @@ -175,13 +174,13 @@ Thing.subclass('Tank', function(Tank){ }; this['findNearEnemies'] = - function findNearEnemies(ticks, needLineOfSight){ + function findNearEnemies(ticks, needLineOfSight){ // TODO: Split off LOS version return this.findNearLike(ticks, function nearEnemyFilter(agent){ - var am = agent.midpoint; + var aLoc = agent.loc; return ( agent.align !== this.align - && Y.is(Tank, agent) + && (agent instanceof Tank) && !(needLineOfSight - && new Trajectory(this,this.midpoint,am).pathBlocked(agent)) + && new Trajectory(this,this.loc,aLoc).pathBlocked(agent)) ); }); }; @@ -192,12 +191,13 @@ Thing.subclass('Tank', function(Tank){ return null; var manhattan = Vec.manhattan - , bb = this.boundingBox, mid = this.midpoint ; + , cmp = op.cmp + , loc = this.loc ; agents.sort(function(a,b){ - return Y.op.cmp( - manhattan(a.midpoint,mid), - manhattan(b.midpoint,mid) ); // FIXME: midpoint-to-midpoint is wrong -- should use pt on closest boundary-side of object + return cmp( + manhattan(a.midpoint,loc), + manhattan(b.midpoint,loc) ); }); return agents.attr(0); @@ -274,7 +274,7 @@ Thing.subclass('Tank', function(Tank){ this.nShots++; p.addEventListener('destroy', this.onBulletDeath); - this.game.addUnit(p).render(this.game.level); + this.game.addThing(p).render(this.game.level); return p; }; @@ -313,7 +313,7 @@ Thing.subclass('Tank', function(Tank){ , blockers = this.game.pathmap.get(nbb.x1,nbb.y1, nbb.x2,nbb.y2).remove(this); if ( !blockers.size() ) - this.game.moveUnitTo(this, x,y); + this.game.moveThingTo(this, x,y); return this; }; @@ -348,9 +348,9 @@ Thing.subclass('Tank', function(Tank){ // console.log(this, 'moving toward', t); var pm = this.game.pathmap - , start = this.midpoint + , start = this.loc - , path = this.lastPath = pm.path(start, end, this.id) + , path = this.lastPath = pm.path(start, end, this) , to = this.currentMove = path.shift() ; }; diff --git a/src/tanks/thing/thing.cjs b/src/tanks/thing/thing.cjs index 3931c44..4eba9dd 100644 --- a/src/tanks/thing/thing.cjs +++ b/src/tanks/thing/thing.cjs @@ -1,13 +1,33 @@ //#ensure "evt" - var Y = require('Y').Y +, op = require('Y/op') , evt = require('evt') - , Loc = require('ezl/loc/loc').Loc , BoundingBox = require('ezl/loc/boundingbox').BoundingBox , Cooldown = require('ezl/loop').Cooldown +, config = require('tanks/config').config + +, THING_ID = 0 +, -, config = require('tanks/config').values +fillStats = +exports['fillStats'] = +function fillStats(stats){ + return Y.reduce(stats, function(_stats, v, k){ + k = Y(k); + var k_ = k.rtrim('_max') + , k_max = k+'_max' + ; + if ( k.endsWith('_max') ) { + if ( _stats[k_] === undefined ) + _stats[k_] = v; + + } else if ( _stats[k_max] === undefined ) + _stats[k_max] = v; + + return _stats; + }, Y.extend({}, stats)); +} , @@ -21,7 +41,7 @@ new evt.Class('Thing', { stats: { hp : 1, // health move : 1.0, // move speed (squares/sec) - rotate : HALF_PI, // rotation speed (radians/sec) + rotate : HALF_PI, // rotation speed (radians/sec) [UNUSED] power : 1, // attack power speed : 0.5, // attack cool (sec) shots : 5 // max projectiles in the air at once @@ -36,6 +56,7 @@ new evt.Class('Thing', { align : 0, // 0 reserved for neutral units dead : false, + pathing : null, blocking : true, // Whether the agent obstructs pathing active : true, // Whether the agent takes actions @@ -52,18 +73,46 @@ new evt.Class('Thing', { width : REF_SIZE, height : REF_SIZE, + // Accessors + set : op.set.methodize(), + attr : op.attr.methodize(), + + init : function init(align){ - this.id = Thing.THING_ID++; + this.id = THING_ID++; this.align = align || 0; + this.createBoundingBox(); + this.createStats(); + this.createCooldowns(); + }, + + + createBoundingBox : function createBoundingBox(){ this.boundingBox = new BoundingBox(0,0, this.width,this.height, this.originX,this.originY); + }, + + createStats : function createStats(){ + this.stats = fillStats(this.stats); + }, + + createCooldowns : function createCooldowns(){ + this.cooldowns = { + 'attack': new Cooldown(1000 * this.stats.speed) + }; + this.ai = Y(this.ai).map(function(freq, k){ + return new Cooldown(1000 * freq); + }); - this.fillStats(); - this.createCooldowns(); + this._cooldowns = Y(this.cooldowns); + this._ai = Y(this.ai); }, - set : Y.op.set.methodize(), - attr : Y.op.attr.methodize(), + updateCooldowns : function updateCooldowns(elapsed, now){ + this._cooldowns.invoke('tick', elapsed, now); + this._ai.invoke('tick', elapsed, now); + return this; + }, position : function position(x,y){ @@ -73,7 +122,7 @@ new evt.Class('Thing', { var bb = this.boundingBox.relocate(x,y) , loc = this.loc = bb.absOrigin; this.midpoint = bb.midpoint; - if (this.shape) this.shape.position(loc.x,loc.y); + if (this.shape) this.shape.position(loc.x,loc.y); // XXX: eh? return this; }, @@ -88,38 +137,13 @@ new evt.Class('Thing', { return this; }, - fillStats : function fillStats(){ - this.stats = Thing.fillStats(this.stats); - }, - - createCooldowns : function createCooldowns(){ - this.cooldowns = { - 'attack': new Cooldown(1000 * this.stats.speed) - }; - this.ai = Y(this.ai).map(function(freq, k){ - return new Cooldown(1000 * freq); - }); - - this._cooldowns = Y(this.cooldowns); - this._ai = Y(this.ai); - }, - - updateCooldowns : function updateCooldowns(elapsed, now){ - this._cooldowns.invoke('tick', elapsed, now); - this._ai.invoke('tick', elapsed, now); - return this; - }, - dealDamage : function dealDamage(d, source){ this.stats.hp -= d; - if (this.stats.hp <= 0) this.destroy(); - return this; }, - /** * Calculates the location of the bullet-spawning point on this Thing. */ @@ -157,7 +181,7 @@ new evt.Class('Thing', { * Sets up unit appearance for minimal updates. Called once at start, * or when the world needs to be redrawn from scratch. */ - render : function render( parent ){ + render : function render(){ return this; }, @@ -174,29 +198,6 @@ new evt.Class('Thing', { }); -Y(Thing).extend({ - THING_ID : 0, - - fillStats : function fillStats(stats){ - var st = Y(stats) - , stats = st.clone().end() ; - - st.forEach(function(v, k){ - k = Y(k); - var k_ = k.rtrim('_max'); - - if ( k.endsWith('_max') ) { - if ( stats[k_] === undefined ) - stats[k_] = v; - - } else if ( stats[k+'_max'] === undefined ) - stats[k+'_max'] = v; - - }); - - return stats; - } -}); config.updateOnChange('ui.showAttackCooldown', Thing.fn); diff --git a/src/tanks/ui/configui.cjs b/src/tanks/ui/configui.cjs index 750d923..fcf7d57 100644 --- a/src/tanks/ui/configui.cjs +++ b/src/tanks/ui/configui.cjs @@ -3,7 +3,7 @@ var Y = require('Y').Y , cookies = require('Y/modules/y.cookies') , scaffold = require('Y/modules/y.scaffold') , EventLoop = require('ezl/loop/eventloop').EventLoop -, config = require('tanks/config').values +, config = require('tanks/config').config , configUI, fields ; @@ -17,11 +17,9 @@ function initConfigUi(){ }); configUI.append( jQuery('
') ); - config.addEventListener('set:game.timeDilation', function(evt){ - EventLoop.fn.dilation = evt.data.newval; - }); }; +// Persist config via cookies config.addEventListener('set', function(evt){ var d = evt.data; if (d.newval !== d.defval) @@ -30,6 +28,9 @@ config.addEventListener('set', function(evt){ cookies.remove(d.key); }); +config.updateOnChange('game.timeDilation', EventLoop.fn); + + // exports['init'] = // function initConfigUi(){ // $('#config [name=pathmap]').attr('checked', config.get('pathing.overlayPathmap')); diff --git a/src/tanks/ui/grid.cjs b/src/tanks/ui/grid.cjs index ed8424f..458a1e9 100644 --- a/src/tanks/ui/grid.cjs +++ b/src/tanks/ui/grid.cjs @@ -1,6 +1,6 @@ var Y = require('Y').Y , Rect = require('ezl/shape').Rect -, config = require('tanks/config').values +, config = require('tanks/config').config , Grid = @@ -10,8 +10,8 @@ Rect.subclass('Grid', { createGridTable : null, createGridCanvas : null, - // Defaults - _cssClasses : 'ezl layer shape rect grid', + // Shape Config + _cssClasses : 'grid rect shape layer ezl', strokeStyle : '#6E6E6E', lineWidth : 0.5, @@ -26,7 +26,7 @@ Rect.subclass('Grid', { Rect.init.call(this, cols*side, rows*side); }, - drawShape : function drawShape(ctx){ + render : function render(ctx){ if (this.table) this.table.remove(); // if (this.canvas) this.canvas.remove(); diff --git a/src/tanks/ui/index.cjs b/src/tanks/ui/index.cjs index d2a9874..2a41016 100644 --- a/src/tanks/ui/index.cjs +++ b/src/tanks/ui/index.cjs @@ -1,3 +1,6 @@ var Y = require('Y').Y; + Y.extend(exports, require('tanks/ui/configui')); + +exports['PathMapUI'] = require('tanks/ui/pathmapui').PathMapUI; exports['main'] = require('tanks/ui/main'); diff --git a/src/tanks/ui/main.cjs b/src/tanks/ui/main.cjs index 105a4e3..d0ac852 100644 --- a/src/tanks/ui/main.cjs +++ b/src/tanks/ui/main.cjs @@ -8,7 +8,7 @@ var Y = require('Y').Y , Game = require('tanks/game').Game , Tank = require('tanks/thing').Tank , FpsSparkline = require('ezl/loop').FpsSparkline -, config = require('tanks/config').values +, config = require('tanks/config').config , updateTimer = null ; diff --git a/src/tanks/ui/pathmapui.cjs b/src/tanks/ui/pathmapui.cjs new file mode 100644 index 0000000..73323ca --- /dev/null +++ b/src/tanks/ui/pathmapui.cjs @@ -0,0 +1,160 @@ +var Y = require('Y').Y +, Rect = require('ezl/shape').Rect +, astar = require('ezl/util/astar') +, PathMap = require('tanks/map/pathmap').PathMap +, config = require('tanks/config').config +, + + +PathMapUI = +exports['PathMapUI'] = +Rect.subclass('PathMapUI', { + // Config + gridSquare : null, + overlayPathmap : null, + overlayAiPaths : null, + + // Shape Config + _cssClasses : 'pathmap rect shape layer ezl', + + fillStyle : 'rgba(255,255,255,0.1)', + strokeStyle : 'rgba(255,255,255,0.2)', + lineWidth : 1, + + + + init : function initPathMapUI(game, pathmap){ + this.game = game; + this.pathmap = pathmap; + + Rect.init.call(this, pathmap.width-2, pathmap.height-2); + + pathmap.gridPath = this.gridPathWithUI.bind(this); + this.cleanUpAgent = this.cleanUpAgent.bind(this); + this._drawPathStep = this._drawPathStep.bind(this); + }, + + render : function render(ctx){ + if (this.overlayPathmap) + this.overlay(ctx); + // AI Paths automatically show up (and are cleaned up) + // as they are created (and destroyed). + this.dirty = true; + }, + + overlay : function overlay(ctx){ + this.pathmap.reduce(function(acc, v, r, tree){ + if ( acc[r.id] || v.isBoundary ) + return acc; + acc[r.id] = r; + ctx.beginPath(); + ctx.rect(r.x1,r.y1, r.width,r.height); + ctx.fill(); + ctx.stroke(); + ctx.closePath(); + return acc; + }, {}); + }, + + + /** + * Wraps PathMap.gridPath() to draw AI pathes. + */ + gridPathWithUI : function gridPathWithUI(start, end, agent){ + var size = this.gridSquare, floor = Math.floor + , grid = this.pathmap.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 (this.overlayAiPaths) + this.drawPath(agent, startN, path); + + return path; + }, + + monitorAgent : function monitorAgent(agent){ + if ( agent && !this.hasPath(agent.id) ) + agent.addEventListener('destroy', this.cleanUpAgent); + }, + + cleanUpAgent : function cleanUpAgent(evt){ this.destroyPath(evt.target.id); }, + + getPath : function getPath(id){ return $('.path.agent_'+id, this.layer); }, + hasPath : function hasPath(id){ return !!this.getPath(id).length; }, + showPaths : function showPaths(){ $('.path', this.layer).show(); }, + hidePaths : function hidePaths(){ $('.path', this.layer).hide(); }, + destroyPath : function destroyPath(id){ this.getPath(id).remove(); }, + + drawPath : function drawPath(agent, start, path){ + this.monitorAgent(agent); + + var id = agent.id + , w = this.layerWidth, h = this.layerHeight + , canvas = this.getPath(id) + ; + + if (!canvas.length) { + canvas = $('').appendTo(this.layer); + canvas.width(w).height(h); + canvas[0].width = w; + canvas[0].height = h; + } + + canvas.hide(); + + var ctx = canvas[0].getContext('2d'); + ctx.lineWidth = 0; + + // Clear the canvas + ctx.beginPath(); + ctx.clearRect(0,0, w,h); + ctx.closePath(); + + // Draw the path + ctx.fillStyle = 'rgba(0,0,0,0.1)'; + this._drawPathStep(ctx, start); + path.reduce(this._drawPathStep, ctx); + + // Draw start (concentric ring) + ctx.fillStyle = 'rgba(0,255,0,0.05)'; + this._drawPathStep(ctx, start, -1); + + // Draw finish + ctx.fillStyle = 'rgba(0,0,255,0.05)'; + this._drawPathStep(ctx, path.last()); + + canvas.show(); + }, + + _drawPathStep : function drawPathStep(ctx, p, idx){ + var size = this.gridSquare + , off = size*(idx === -1 ? 0.15 : 0.3) + , r = size/2 - off + , 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(); + + return ctx; + } + + +}); + + +config.updateOnChange( + ['pathing.gridSquare', 'pathing.overlayPathmap', 'pathing.overlayAiPaths'], + PathMapUI.fn); + diff --git a/www/deps.html b/www/deps.html index e6b0aaa..f00d42a 100644 --- a/www/deps.html +++ b/www/deps.html @@ -49,19 +49,21 @@ - + - + + + -- 1.7.0.4