Adds Buff & Stat class
authordsc <david.schoonover@gmail.com>
Sat, 1 Jan 2011 03:29:59 +0000 (19:29 -0800)
committerdsc <david.schoonover@gmail.com>
Sat, 1 Jan 2011 03:29:59 +0000 (19:29 -0800)
26 files changed:
doc/notes.md
src/Y/class.cjs
src/Y/types/array.cjs
src/Y/types/string.cjs
src/Y/utils.cjs
src/ezl/layer/layer.cjs
src/tanks/constants.cjs [new file with mode: 0644]
src/tanks/effects/buff.cjs
src/tanks/effects/stat.cjs [new file with mode: 0644]
src/tanks/game.cjs
src/tanks/globals.js
src/tanks/map/map.cjs
src/tanks/map/pathmap.cjs
src/tanks/map/trajectory.cjs
src/tanks/map/traversal.cjs
src/tanks/mixins/configurable.cjs [copied from src/tanks/thing/zone.cjs with 100% similarity]
src/tanks/mixins/informative.cjs [copied from src/tanks/thing/zone.cjs with 100% similarity]
src/tanks/mixins/meronomic.cjs [copied from src/tanks/thing/zone.cjs with 100% similarity]
src/tanks/mixins/speciated.cjs [copied from src/tanks/thing/zone.cjs with 100% similarity]
src/tanks/mixins/usable.cjs [moved from src/tanks/thing/zone.cjs with 100% similarity]
src/tanks/thing/bullet.cjs
src/tanks/thing/tank.cjs
src/tanks/thing/thing.cjs
src/tanks/thing/wall.cjs
www/header.html
www/test/shapes/test-shapes.js

index 2c03c50..fc55836 100644 (file)
@@ -6,7 +6,6 @@
 - Config-driven unit-types (name, stats, properties; pointers to behavior scripts, assets)
 - Game scoring
 - Support touch events (for iPad?)
-- Migrate A* code into PathMap
 
 
 # Code Notes
index 73015eb..6848b76 100644 (file)
@@ -195,10 +195,33 @@ Class.fn.subclass = YFunction(subclass);
     });
 
 
+var
+
+/**
+ * Everybody's favourite party metaclass!
+ */
+Mixin =
+exports['Mixin'] =
+Y.subclass('Mixin', {
+    __mixin_skip__ : [ 'mixIntoClass' ],
+    
+    
+    
+    /**
+     * Mixes this Mixin into another Class.
+     */
+    mixIntoClass : function mixIntoClass(cls){
+        
+    },
+    
+})
+,
+
+
 /**
  * Root-class for all Y-objects.
  */
-var YBase = new Class("YBase", {
+YBase = new Class("YBase", {
     __y__ : true
 });
 
index 8b6fc0a..0f2700c 100644 (file)
@@ -2,7 +2,7 @@ var Y           = require('Y/y').Y
 ,   del         = require('Y/delegate')
 ,   type        = require('Y/type')
 ,   op          = require('Y/op')
-,   mixin       = require('Y/utils').mixin
+,   mixInto     = require('Y/utils').mixInto
 ,   YCollection = require('Y/types/collection').YCollection
 
 ,   _Array        = Array
@@ -19,15 +19,15 @@ YCollection.subclass('YArray', function(YArray){
     
     var newYArray = YArray.instantiate.bind(YArray);
     
-    mixin({ target:YArray, donor:_Array, context:'_o',
+    mixInto({ target:YArray, donor:_Array, context:'_o',
         names:'indexOf lastIndexOf shift pop join'.split(' ') });
-    mixin({ target:YArray, donor:_Array, context:'_o', chain:true,
+    mixInto({ target:YArray, donor:_Array, context:'_o', chain:true,
         names:'push unshift sort splice reverse'.split(' ') });
-    mixin({ target:YArray, donor:_Array, context:'_o', fnFirst:true, wrap:newYArray,
+    mixInto({ target:YArray, donor:_Array, context:'_o', fnFirst:true, wrap:newYArray,
         names:'map forEach filter'.split(' ') });
-    mixin({ target:YArray, donor:_Array, context:'_o', fnFirst:true,
+    mixInto({ target:YArray, donor:_Array, context:'_o', fnFirst:true,
         names:['reduce', 'some', 'every'] });
-    mixin({ target:YArray, donor:_Array, context:'_o', wrap:newYArray,
+    mixInto({ target:YArray, donor:_Array, context:'_o', wrap:newYArray,
         names:['slice'] });
     
     
index 4339f9a..5a82ff5 100644 (file)
@@ -1,5 +1,5 @@
 var YCollection = require('Y/types/collection').YCollection
-,   mixin = require('Y/utils').mixin
+,   mixInto = require('Y/utils').mixInto
 ,   op    = require('Y/op')
 ,   core  = require('Y/core')
 ,   del   = require('Y/delegate')
@@ -10,9 +10,9 @@ exports['YString'] =
 YCollection.subclass('YString', function(YString){
     
     var newYString = YString.instantiate.bind(YString);
-    mixin({ target:YString, donor:String, context:'_o', wrap:newYString,
+    mixInto({ target:YString, donor:String, context:'_o', wrap:newYString,
         names:'slice substr substring concat replace toLowerCase toUpperCase'.split(' ') });
-    mixin({ target:YString, donor:String, context:'_o',
+    mixInto({ target:YString, donor:String, context:'_o',
         names:'split indexOf lastIndexOf charAt charCodeAt'.split(' ') });
     
     function trim(val){
index a27df65..0f01984 100644 (file)
@@ -32,8 +32,8 @@ function bindAll(o, names){
 
 
 var 
-This   = mixin['This']   = {},
-Target = mixin['Target'] = {},
+This   = mixInto['This']   = {},
+Target = mixInto['Target'] = {},
 defaults = {
     /**
      * {Object|Function} target Required. Target object onto which methods will be added. If this a Function, it's prototype will be used.
@@ -51,10 +51,10 @@ defaults = {
     'names' : null,
     
     /**
-     * {mixin.This|mixin.Target|String} [context=mixin.This]
+     * {mixInto.This|mixInto.Target|String} [context=mixInto.This]
      * Sets the call context for the function.
-     *  - {mixin.This}: (Default) Use runtime `this` as context.
-     *  - {mixin.Target}: Use `target` as context.
+     *  - {mixInto.This}: (Default) Use runtime `this` as context.
+     *  - {mixInto.Target}: Use `target` as context.
      *  - {String}: Names a property on `this` to use. If specified and property is false-y, `this` is used.
      */
     'context' : This,
@@ -81,7 +81,7 @@ defaults = {
      */
     'fnFirst' : false
 };
-function mixin(options){
+function mixInto(options){
     var o = core.extend({}, defaults, options)
     ,   target = o.target
     ,   donor  = o.donor
@@ -123,4 +123,4 @@ function mixin(options){
 }
 
 exports['bindAll'] = bindAll;
-exports['mixin'] = mixin;
+exports['mixInto'] = mixInto;
index 75d5408..beaa2ac 100644 (file)
@@ -64,8 +64,8 @@ Y.subclass('Layer', {
         this.negBleed = new Loc(0,0);
         this.posBleed = new Loc(0,0);
         
-        this.boundingBox = new BoundingBox(0,0, 0,0, this.originX,this.originY);
-        this._origin = this.boundingBox.origin;
+        this.bbox = new BoundingBox(0,0, 0,0, this.originX,this.originY);
+        this._origin = this.bbox.origin;
         
         this.transform = {
             rotate    : 0,
@@ -172,7 +172,7 @@ Y.subclass('Layer', {
         this.layerWidth  = w;
         this.layerHeight = h;
         
-        var bb = this.boundingBox.resize(w,h)
+        var bb = this.bbox.resize(w,h)
         ,   nb = this.negBleed, pb = this.posBleed
         
         // HTMLCanvas.{width,height} is a long
@@ -204,7 +204,7 @@ Y.subclass('Layer', {
         
         this.layerWidth = w;
         
-        var bb = this.boundingBox.resize(w, this.layerHeight)
+        var bb = this.bbox.resize(w, this.layerHeight)
         // ,   ro = bb.relOrigin
         ,   nb = this.negBleed
         ,   cw = this.canvasWidth = Math.ceil(w + nb.x + this.posBleed.x); // HTMLCanvas.width is a long
@@ -230,7 +230,7 @@ Y.subclass('Layer', {
         
         this.layerHeight = h;
         
-        var bb = this.boundingBox.resize(this.layerWidth, h)
+        var bb = this.bbox.resize(this.layerWidth, h)
         // ,   ro = bb.relOrigin
         ,   nb = this.negBleed
         ,   ch = this.canvasHeight = Math.ceil(h + nb.y + this.posBleed.y); // HTMLCanvas.height is a long
@@ -279,7 +279,7 @@ Y.subclass('Layer', {
             x = ('left' in x ? x.left : x.x);
         }
         
-        var bbox = this.boundingBox.relocate(x,y);
+        var bbox = this.bbox.relocate(x,y);
         this.css({
             'left' : bbox.x1,
             'top'  : bbox.y1
diff --git a/src/tanks/constants.cjs b/src/tanks/constants.cjs
new file mode 100644 (file)
index 0000000..d6eba26
--- /dev/null
@@ -0,0 +1,39 @@
+//#exports PathingType StatInvariant
+require('Y').Y.extend(exports, {
+    
+    /// Pathing Constants ///
+    /** How this object interacts with the world. */
+    PathingType : {
+        
+        /** Does not obstruct other objects. */
+        PASSABLE : 0,
+        
+        /** Does not obstruct other objects, but still collides with them. */
+        ZONE : 1,
+        
+        /** Obstructs other blockers with its BoundingBox. */
+        BLOCKING : 2,
+        
+        /** Potentially obstructs other objects, but requires a special test once a BoundingBox collision has been detected. */
+        IRREGULAR : 3
+        
+    },
+    
+    /** 
+     * Invariant to restore for the current value when modifying the base or max of a Stat.
+     * @see {tanks.effects.stat.Stat} for the default behavior of its methods.
+     */
+    StatInvariant : {
+        
+        /** Do not change current value. */
+        NONE : 1,
+        
+        /** Keep current value at the same ratio it was before the change. */
+        RATIO : 2,
+        
+        /** Apply the full change to the current value. */
+        FULL : 3
+        
+    }
+    
+});
\ No newline at end of file
index e69de29..84375bb 100644 (file)
@@ -0,0 +1,60 @@
+var Y = require('Y').Y
+,
+
+
+// Events:
+//  - live.{affect,stack}
+//  - die.{expire,dismiss,dispel}
+Buff =
+exports['Buff'] =
+Y.subclass('Buff', {
+    // __mixins__: [ Informative, Speciated, Meronomic, Usable ], // Configurable?
+    
+    
+    /// Configurable ///
+    priority  : 0, // Order of stat and effect application (lower is earlier)
+    stats : {
+        timeout : -1,
+        stacked : 1,
+        threat  : 1
+    },
+    stat_mods : {}, // {stat : Number|Function} Modifications to stats
+    // triggers  : {}, // {event : Effect} // maybe
+    // effects   : [], // {Function...} Effects to trigger on affect // not sure why i need this
+    
+    /// Instance ///
+    owner : null, // Agent which originated the buff
+    target : null, // Agent to which the buff applies
+    
+    
+    
+    init : function initBuff(owner, target){
+        this.owner = owner;
+        this.game = owner.game;
+        this.target = target;
+        this.created = this.game.NOW;
+        // XXX: Speciated mixin must clone objects
+    },
+    
+    applyStatMods : function applyStatMods(modifier){
+        modifier = modifier || 1;
+        Y(this.stat_mods).forEach(function(mod, name){
+            var keyparts = name.split('_')
+            ,   key = parts[0]
+            ,   part = parts[1] || 'val'
+            ;
+            this.target.stats[key].modifyPart(part, modifier * mod);
+        }, this);
+        return this;
+    },
+    
+    removeStatMods : function removeStatMods(){
+        return this.applyStatMods(-1);
+    }
+    
+    // XXX: toString implemented by Informative?
+    
+})
+
+
+
diff --git a/src/tanks/effects/stat.cjs b/src/tanks/effects/stat.cjs
new file mode 100644 (file)
index 0000000..3da9b06
--- /dev/null
@@ -0,0 +1,127 @@
+var Y = require('Y').Y
+
+,   StatInvariant = require('tanks/constants').StatInvariant
+
+,   NONE  = StatInvariant.NONE
+,   RATIO = StatInvariant.RATIO
+,   FULL  = StatInvariant.FULL
+,
+
+
+
+Stat =
+exports['Stat'] =
+Y.subclass('Stat', {
+    // __mixins__: [ Informative, Speciated, Meronomic, Usable ], // Configurable?
+    
+    integer : true, // if true, current value is rounded after a change
+    
+    base : 0,
+    val : 0,
+    max : 0,
+    
+    
+    init : function initStat(base, val, max, isFloat){
+        this.base = base;
+        this.val = val !== undefined ? val : base;
+        this.max = max !== undefined ? max : base;
+        this.integer = !isFloat;
+    },
+    
+    clone : function clone(){
+        return new Stat(this.base, this.val, this.max, !this.integer);
+    },
+    
+    /**
+     * Resets max value and current value to base value.
+     * @return {this}
+     */
+    reset : function reset(){
+        this.val = this.max = this.base;
+        return this;
+    },
+    
+    /**
+     * @param {Number} dv Amount to add to current value.
+     * @return {this}
+     */
+    modify : function modify(dv){
+        var v = Math.min(this.max, this.val+dv);
+        this.val = (this.integer ? Math.round(v) : v);
+        return this;
+    },
+    
+    /**
+     * @param {Number} dv Amount to add to base and max value.
+     * @param {StatInvariant} [inv=FULL] Invariant to restore for current value.
+     * @return {this}
+     */
+    modifyBase : function modifyBase(dv, inv){
+        this.base += dv;
+        return this.modifyMax(dv, inv || FULL);
+    },
+    
+    /**
+     * @param {Number} dv Amount to add to current value.
+     * @param {StatInvariant} [inv=FULL] Invariant to restore for current value.
+     * @return {this}
+     */
+    modifyMax : function modifyMax(dv, inv){
+        var v = this.val
+        ,   oldmax = this.max;
+        this.max += dv;
+        
+        switch ( inv ) {
+            case NONE: break;
+            
+            case RATIO:
+                v = (v / oldmax) * this.max;
+                break;
+            
+            case FULL:
+            default:
+                v += dv;
+                break;
+        }
+        this.val = (this.integer ? Math.round(v) : v);
+        
+        return this;
+    },
+    
+    modifyPart : function modifyPart(part, dv, inv){
+        var method =
+            (part === 'max'  ? 'modifyMax' : 
+            (part === 'base' ? 'modifyBase' : 'modify'));
+        return this[method](dv, inv);
+    },
+    
+    toString : function(){
+        return this.val+'/'+this.max+(this.max !== this.base ? ' ['+this.base+']' : '');
+    }
+    
+})
+;
+
+Stat.fn.modifyVal = Stat.fn.modify;
+
+
+Stat['from'] =
+function fromValue(v){
+    if ( v instanceof Stat )
+        return v.clone();
+    else if ( Y.isArray(v) )
+        return new Stat(v[0], v[1], v[2], v[3]);
+    else
+        return new Stat(v);
+};
+
+/**
+ * Accepts any number of objects which are combined to make the new map of names to stats.
+ */
+exports['createStats'] =
+function createStats(o){
+    var stats = Y({});
+    return stats
+        .extend.apply(stats, Y(arguments))
+        .map(Stat.from);
+};
index 3678225..b6d99ad 100644 (file)
@@ -86,11 +86,11 @@ Y.subclass('Game', {
         var d = evt.data
         ,   self = this;
         
-        NOW      = d.now;
-        ELAPSED  = d.elapsed;
-        TICKS    = d.ticks;
-        SECONDTH = ELAPSED / 1000;
-        SQUARETH = REF_SIZE * SECONDTH
+        NOW      = this.NOW      = d.now;
+        ELAPSED  = this.ELAPSED  = d.elapsed;
+        TICKS    = this.TICKS    = d.ticks;
+        SECONDTH = this.SECONDTH = ELAPSED / 1000;
+        SQUARETH = this.SQUARETH = REF_SIZE * SECONDTH
         
         this.active.invoke('updateCooldowns', ELAPSED, NOW);
         this.active.invoke('act', ELAPSED, NOW);
@@ -103,7 +103,7 @@ Y.subclass('Game', {
         
         if ( this.player.dead )
             this.gameover = 'lose';
-        else if ( !this.units.filter('_.align !== 1 && !_.dead'.lambda()).size() )
+        else if ( !this.units.filter('_.align !== 1 && !_.dead').size() )
             this.gameover = 'win';
         
         if ( this.gameover )
@@ -121,16 +121,16 @@ Y.subclass('Game', {
         return running;
     },
     
+    
+    
+    // *** Agent Management *** //
+    
     addAnimation : function addAnimation(animation){
         this.animations.push(animation);
         this.level.append(animation);
         return animation;
     },
     
-    
-    
-    // *** Agent Management *** //
-    
     addThing : function addThing(unit, col,row){
         if (unit instanceof Event)
             unit = unit.trigger;
index cb949e5..b3863ba 100644 (file)
@@ -19,6 +19,20 @@ var undefined
 ,   ELAPSED       = MS_PER_FRAME         // Time (ms) since previous tick
 ,   TICKS         = 0                    // Ticks since start of game
 
+/// Pathing Constants (from tanks/map/map) ///
+
+/** Does not obstruct other objects. */
+,   PASSABLE = 0
+
+/** Does not obstruct other objects, but still collides with them. */
+,   ZONE = 1
+
+/** Obstructs other blockers with its BoundingBox. */
+,   BLOCKING = 2
+
+/** Potentially obstructs other objects, but requires a special test once a BoundingBox collision has been detected. */
+,   IRREGULAR = 3
+
 
 /// Common Components of Computation ///
 
index 5860a7a..feec7a3 100644 (file)
@@ -23,8 +23,9 @@ QuadTree.subclass('Map', {
     
     addBlocker : function addBlocker(obj){
         this.removeBlocker(obj);
-        var bb = obj.boundingBox;
-        if (obj.blocking && bb)
+        var pt = obj.blocking
+        ,   bb = obj.bbox ;
+        if (pt !== undefined && bb)
             obj.region = this.set(bb.x1,bb.y1, bb.x2,bb.y2, obj);
         return obj;
     },
@@ -35,6 +36,20 @@ QuadTree.subclass('Map', {
             delete obj.region;
         }
         return obj;
+    },
+    
+    getBlockers : function getBlockers(x1,y1, x2,y2, ignore){
+        if (x1 && x1.x1 !== undefined) {
+            ignore = y1;
+            y2 = x1.y2;  x2 = x1.x2;
+            y1 = x1.y1;  x1 = x1.x1;
+        }
+        var bs = this.get(x1,y1, x2,y2)
+            .filter('_.blocking === BLOCKING');
+        if (ignore && bs.length)
+            return bs.remove.apply(bs, ignore || []);
+        else
+            return bs;
     }
     
     
index c82075b..ceac819 100644 (file)
@@ -40,15 +40,13 @@ Map.subclass('PathMap', {
     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._squares = {};
         
         this.level    = level;
         this.game     = level.game;
         this.walls    = level.walls;
         this.allWalls = level.allWalls;
         
-        this._squares = {};
-        
         this.game.addEventListener('ready', this.setup.bind(this, x1,y1, x2,y2));
     },
     
@@ -70,23 +68,9 @@ Map.subclass('PathMap', {
         this.boundaryWalls = Y.map(BWs, 'this.level.addWall.apply(this.level, _)', this);
     },
     
-    getBlockers : function getBlockers(x1,y1, x2,y2, ignore){
-        if (x1 && x1.x1 !== undefined) {
-            ignore = y1;
-            y2 = x1.y2;  x2 = x1.x2;
-            y1 = x1.y1;  x1 = x1.x1;
-        }
-        var bs = this.get(x1,y1, x2,y2)
-            .filter('_.blocking === 2'); // map.BLOCKING
-        if (ignore && bs.length)
-            return bs.remove.apply(bs, ignore || []);
-        else
-            return bs;
-    },
-    
     wallObstructs : function wallObstructs(line){
         return this.walls.some(function(wall){
-            return wall.boundingBox.intersects(line);
+            return wall.bbox.intersects(line);
         }, this);
     },
     
@@ -160,7 +144,7 @@ Map.subclass('PathMap', {
                 
                 // Ensure we don't get stuck on a corner on our first step
                 var first = path.first()
-                ,   bb = agent.boundingBox.relocated( vec.lerp(0.5, agent.loc,first) )
+                ,   bb = agent.bbox.relocated( vec.lerp(0.5, agent.loc,first) )
                 ,   blockers = this.getBlockers(bb, [agent]);
                 
                 if (blockers.length)
@@ -279,7 +263,7 @@ Y.subclass('Square', new Vec(0,0), {
     _blocked : function blocked(){
         var pm    = this.pathmap
         ,   agent = pm._agent
-        // ,   bb    = agent.boundingBox
+        // ,   bb    = agent.bbox
         
         // ,   origin = bb.relOrigin
         // ,   left = origin.x,  right  = bb.width - left
index 72b4f9a..dcd416a 100644 (file)
@@ -73,7 +73,7 @@ Line.subclass('Trajectory', {
     intersects : function intersects(x,y){
         var o = x;
         if (o instanceof Thing)
-            return o.boundingBox.intersects(this);
+            return o.bbox.intersects(this);
         
         if (o instanceof Rect)
             return o.intersects(this);
index c51d193..5f37d2a 100644 (file)
@@ -21,7 +21,7 @@ Y.subclass('Traversal', {
         this.thing   = thing;
         this.game    = thing.game;
         this.pathmap = thing.game.pathmap;
-        this.bbox    = thing.boundingBox.clone();
+        this.bbox    = thing.bbox.clone();
         
         this.trajectory = trajectory || thing.trajectory;
     },