Sweet. Items work.
authordsc <david.schoonover@gmail.com>
Mon, 3 Jan 2011 11:32:17 +0000 (03:32 -0800)
committerdsc <david.schoonover@gmail.com>
Mon, 3 Jan 2011 11:32:17 +0000 (03:32 -0800)
12 files changed:
src/Y/types/array.cjs
src/evt.cjs
src/tanks/effects/buff.cjs
src/tanks/mixins/configurable.cjs
src/tanks/mixins/index.cjs
src/tanks/mixins/inventoried.cjs [new file with mode: 0644]
src/tanks/mixins/meronomic.cjs
src/tanks/mixins/quantified.cjs
src/tanks/mixins/speciated.cjs
src/tanks/thing/item.cjs
src/tanks/thing/player.cjs
www/deps.html

index 90568f3..faca9e6 100644 (file)
@@ -20,7 +20,7 @@ YCollection.subclass('YArray', function(YArray){
     var newYArray = YArray.instantiate.bind(YArray);
     
     mixInto({ target:YArray, donor:_Array, context:'_o',
-        names:'indexOf lastIndexOf shift pop join'.split(' ') });
+        names:'indexOf lastIndexOf shift join'.split(' ') });
     mixInto({ target:YArray, donor:_Array, context:'_o', chain:true,
         names:'push unshift sort splice reverse'.split(' ') });
     mixInto({ target:YArray, donor:_Array, context:'_o', fnFirst:true, wrap:newYArray,
@@ -105,6 +105,17 @@ YCollection.subclass('YArray', function(YArray){
         return this;
     };
     
+    this['pop'] =
+    function pop(idx){
+        var r, A = this._o;
+        if (idx !== undefined) {
+            r = A[idx];
+            A.splice(idx, 1);
+            return r;
+        } else
+            return A.pop();
+    };
+    
     this['remove'] =
     function remove(v){
         var L = arguments.length;
index fbce38a..07392eb 100644 (file)
@@ -46,7 +46,7 @@ var Y       = require('Y').Y
             '__bases__', '__initialise__', '__class__', 'constructor', 'className',
             '__emitter__', '__static__', '__mixins__'
         ] //.concat(classStatics)
-,   mixinSkip = [ '__mixin_skip__', 'onMixin', 'onInit', 'init'
+,   mixinSkip = [ '__mixin_skip__', 'onMixin', 'onCreate', 'init'
         ].concat(classMagic)
 ,   GLOBAL_ID = 0
 ,
@@ -66,14 +66,15 @@ var Y       = require('Y').Y
  */
 ConstructorTemplate =
 exports['ConstructorTemplate'] =
-function ConstructorTemplate() {
-    var k, v
+function ConstructorTemplate(_args) {
+    var instance = this
     ,   cls = arguments.callee
-    ,   instance = this
+    ,   caller = unwrap(cls.caller)
+    ,   args = (caller === instantiate) ? _args : arguments
     ;
     
     // Not subclassing
-    if ( unwrap(cls.caller) !== Y.fabricate ) {
+    if ( caller !== Y.fabricate ) {
         instance.id = GLOBAL_ID++;
         
         // Perform actions that should only happen once in Constructor
@@ -82,12 +83,12 @@ function ConstructorTemplate() {
         cls.fire('create', instance, {
             'instance' : instance,
             'cls'      : cls,
-            'args'     : Y(arguments)
+            'args'     : Y(args)
         });
         
         var initialise = instance.__initialise__;
         if ( isFunction(initialise) )
-            return initialise.apply(instance, arguments);
+            return initialise.apply(instance, args);
     }
     
     return instance;
@@ -265,6 +266,8 @@ function Class(className, Parent, members){
                 setDesc(prototype, k, getDesc(members,k));
     }
     
+    // prototype.__bind__ = Y([]).concat(prototype.__bind__||[], SuperClass.fn.__bind__||[]).unique();
+    
     // Notify mixins to let them finish up any customization
     if (mixins.length)
         mixins.forEach(function(mxn){
@@ -301,10 +304,7 @@ Class.className = Class.fn.className = "Class";
  * Unlike the keyword `new`, instantiate can be applied.
  */
 function instantiate(cls){
-    var instance = cls.fabricate();
-    if ( instance.initialise )
-        instance.initialise.apply(instance, arguments);
-    return instance;
+    return new cls(Y(arguments,1)); // the magic is done by checking in the constructor for this specific caller
 }
 
 var CST = Class.__static__ = {};
@@ -341,10 +341,29 @@ new Class('Mixin', Class, {
     __mixin_skip__ : mixinSkip,
     
     __static__ : {
+        
+        /**
+         * Mixes this mixin into a class.
+         */
         mixInto : function mixIntoClass(cls){
             var mxn = this;
             return mixin(cls, mxn);
+        },
+        
+        /**
+         * @return The merged pairs at key taken from all bases.
+         */
+        aggregate : function aggregate(key){
+            return this.__bases__
+                .clone()
+                .unshift(this)
+                .reverse()
+                .reduce(function(acc, base){
+                    var proto = (base instanceof Function ? base.prototype : base);
+                    return Y.extend(acc, proto[key]);
+                }, {});
         }
+        
     }
     
 });
@@ -359,6 +378,9 @@ function mixinFilter(v, k){
  */
 function mixin(cls, _mxn){
     var proto = cls.fn
+    ,   bases   = cls.__bases__
+    ,   cbinds  = proto.__bind__
+    ,   cstatic = cls.__static__
     ,   mxns = (Y.isArray(_mxn) ? _mxn.slice(0) : Y(arguments, 1))
     ;
     mxns.reverse().forEach(function(mxn){
@@ -368,17 +390,13 @@ function mixin(cls, _mxn){
             throw new Error('Cannot mix in non-object! '+mxn);
         
         var mproto = (typeof mxn === "function") ? mxn.fn : mxn
-        ,   bases   = cls.__bases__
-        ,   cstatic = cls.__static__
         ,   statics = mxn.__static__
-        ,   onInit  = mproto.onInit
+        ,   onCreate  = mproto.onCreate
+        ,   firstMix = (bases && bases.indexOf(mxn) === -1)
         ;
         
         core.extend(proto, core.filter(mproto, mixinFilter, mproto));
         
-        if (bases && bases.indexOf(mxn) === -1)
-            bases.unshift(mxn);
-        
         // Add mixin statics to class constructor
         if (statics){
             for (var k in statics)
@@ -389,9 +407,16 @@ function mixin(cls, _mxn){
                 }
         }
         
-        // Register onInit to fire whenever a new instance is initialized
-        if ( hasOwn.call(mproto,'onInit') && isFunction(onInit) )
-            cls.addEventListener('init', onInit);
+        // Only perform these actions the first time we mix into this class
+        if (!firstMix)
+            return;
+        
+        // Add to bases
+        bases.unshift(mxn);
+        
+        // Register onCreate to fire whenever a new instance is initialized
+        if ( isFunction(cls.addEventListener) && hasOwn.call(mproto,'onCreate') && isFunction(onCreate) )
+            cls.addEventListener('create', onCreate);
         
         // Fire the mixin event on this Mixin only if we're done constructing the class
         if ( isFunction(mxn.fire) && mixin.caller !== Class )
index 99bc4bd..b29e874 100644 (file)
@@ -13,16 +13,14 @@ var Y = require('Y').Y
 //  - die.{expire,dismiss,dispel}
 Buff =
 exports['Buff'] =
-evt.subclass('Buff', {
-    __mixins__: [ Speciated, Meronomic, Quantified ], // Configurable, Usable
+new evt.Class('Buff', {
+    __mixins__: [ Speciated, Meronomic ], // Configurable, Usable
     
     
     /// Configurable ///
     priority  : 0, // Order of stat and effect application (lower is earlier) // TODO
-    stats : {
-        timeout : Infinity,
-        threat  : 1   // TODO
-    },
+    timeout : 5000,
+    threat  : 1, // TODO
     
     // TODO: functions
     // {stat : Number|Function} Modifications to stats
@@ -33,6 +31,7 @@ evt.subclass('Buff', {
     triggers  : {}, // {event : Effect} // maybe
     
     /// Instance ///
+    name    : 'Turrrrbo',
     owner   : null, // Agent which originated the buff
     target  : null, // Agent to which the buff applies
     game    : null,
@@ -40,24 +39,22 @@ evt.subclass('Buff', {
     
     
     init : function initBuff(target, owner){
-        this.owner = owner;
-        this.game = owner.game;
+        this.game = target.game;
         this.target = target;
-        
-        this.createStats()
-        // this.createCooldowns();
+        this.owner = owner || target;
         
         this.created = this.game.NOW;
-        this.expires = this.created + this.stats.timeout.val;
+        this.expires = this.created + this.timeout;
         
         this.applyStatMods();
-        this.target.fire('buff.live.acquire', this, { 'buff':this, 'unit':target, 'owner':owner });
+        
+        var data = { 'buff':this, 'unit':this.target, 'owner':this.owner };
+        this.fire('buff.conjure', this.target, data);
+        this.target.fire('buff.conjure', this, data);
     },
     
     tick : function tick(elapsed, now){
-        // this.updateCooldowns();
-        
-        if (now < this.expires) {
+        if (now >= this.expires) {
             // this.remove();
             this.die('expire');
         }
@@ -67,7 +64,11 @@ evt.subclass('Buff', {
     
     die : function die(reason){
         this.removeStatMods();
-        this.target.fire('buff.die.'+reason, this, { 'buff':this, 'unit':target, 'owner':owner });
+        // var evt = 'buff.die.'+reason // We'll add this when we add polymorphic dispatchers
+        var evt = 'buff.die'
+        ,   data = { 'buff':this, 'unit':this.target, 'owner':this.owner };
+        this.fire(evt, this.target, data);
+        this.target.fire(evt, this, data);
         return this;
     },
     
index 2d0f617..a57065f 100644 (file)
@@ -6,7 +6,7 @@ Configurable =
 exports['Configurable'] =
 Mixin.subclass('Configurable', {
     
-    onInit : function initConfigurable(evt){
+    onCreate : function initConfigurable(evt){
         
     }
     
index e7384ef..ea351cc 100644 (file)
@@ -1,6 +1,7 @@
 require('Y').Y.core
 .extend(exports, {
-    'Speciated'  : require('tanks/mixins/speciated').Speciated,
-    'Meronomic'  : require('tanks/mixins/meronomic').Meronomic,
-    'Quantified' : require('tanks/mixins/quantified').Quantified
+    'Speciated'   : require('tanks/mixins/speciated').Speciated,
+    'Meronomic'   : require('tanks/mixins/meronomic').Meronomic,
+    'Quantified'  : require('tanks/mixins/quantified').Quantified,
+    'Inventoried' : require('tanks/mixins/inventoried').Inventoried
 });
diff --git a/src/tanks/mixins/inventoried.cjs b/src/tanks/mixins/inventoried.cjs
new file mode 100644 (file)
index 0000000..307b089
--- /dev/null
@@ -0,0 +1,72 @@
+var Y = require('Y').Y
+,   Mixin = require('evt').Mixin
+,   Item = require('tanks/thing/item').Item
+,
+
+Inventoried =
+exports['Inventoried'] =
+Mixin.subclass('Inventoried', {
+    hasInventory : true,
+    inventory : [],
+    
+    
+    onCreate : function initInventoried(evt){
+        var self = evt.data.instance;
+        self.inventory = new Y.YArray();
+    },
+    
+    // TODO: update UI; UI to inform item on activation
+    addItem : function addItem(item){
+        if (!item) return this;
+        
+        this.inventory.push(item);
+        
+        var data = { 'unit':this, 'item':item };
+        this.fire('item.acquire', item, data);
+        item.fire('item.acquire', this, data);
+        
+        return this;
+    },
+    
+    getItem : function getItem(idx){
+        this.inventory.attr(idx);
+    },
+    
+    hasItem : function hasItem(item){
+        return this.inventory.has(item);
+    },
+    
+    removeItem : function removeItem(idx){
+        if (idx instanceof Item){
+            var item = idx;
+            this.inventory.remove(item);
+        } else
+            var item = this.inventory.pop(idx);
+        
+        if (item) {
+            var data = { 'unit':this, 'item':item };
+            this.fire('item.lose', item, data);
+            item.fire('item.lose', this, data);
+        }
+        
+        return item;
+    },
+    
+    dropItem : function dropItem(idx){
+        if (idx instanceof Item){
+            var item = idx;
+            this.inventory.remove(item);
+        } else
+            var item = this.inventory.pop(idx);
+        
+        if (item) {
+            var data = { 'unit':this, 'item':item };
+            this.fire('item.lose.drop', item, data);
+            item.fire('item.lose.drop', this, data);
+        }
+        
+        return item;
+    }
+    
+    
+});
index 0f21048..73dfbab 100644 (file)
@@ -6,7 +6,7 @@ Meronomic =
 exports['Meronomic'] =
 Mixin.subclass('Meronomic', {
     
-    onInit : function initMeronomic(evt){
+    onCreate : function initMeronomic(evt){
         
     }
     
index 26fdf98..05f2e4a 100644 (file)
@@ -9,11 +9,19 @@ Quantified =
 exports['Quantified'] =
 Mixin.subclass('Quantified', {
     
-    stats : {},
+    stats     : {},
     cooldowns : {},
-    ai    : {},
+    ai        : {},
+    buffs     : [],
     
     
+    onCreate : function initQuantified(evt){
+        var self = evt.data.instance;
+        self.buffs = Y([]);
+        self.addEventListener('buff.conjure', self.onBuffAcquired.bind(self));
+        self.addEventListener('buff.die',     self.onBuffLost.bind(self));
+    },
+    
     stat : function stat(k){
         var s = this.stats[k];
         return s ? s.val : s;
@@ -38,41 +46,32 @@ Mixin.subclass('Quantified', {
     updateCooldowns : function updateCooldowns(elapsed, now){
         this._cooldowns.invoke('tick', elapsed, now);
         this._ai.invoke('tick', elapsed, now);
+        this.buffs.invoke('tick', elapsed, now);
         return this;
     },
     
+    onBuffAcquired : function onBuffAcquired(evt){
+        var buff = evt.data.buff;
+        this.buffs.push(buff);
+        console.log('Buff '+buff+' acquired by '+this);
+        // TODO: update UI
+    },
     
-    // onInit : function initQuantified(evt){
-    //     var self = evt.data.instance;
-    //     self.createStats();
-    //     self.createCooldowns();
-    // },
+    onBuffLost : function onBuffLost(evt){
+        var buff = evt.data.buff;
+        this.buffs.remove(buff);
+        console.log('Buff '+buff+' lost by '+this);
+        // TODO: update UI
+    },
     
-    onMixin : function onMixin(evt){
+    onMixin : function mixQuantified(evt){
         var cls = evt.data.cls
         ,   mxn = evt.data.mixin
         ,   proto = cls.fn ;
-        console.log(mxn, '.onMixin()', cls);
+        // console.log(mxn, '.onMixin()', cls);
         proto.stats     = cls.aggregate('stats');
         proto.cooldowns = cls.aggregate('cooldowns');
         proto.ai        = cls.aggregate('ai');
-    },
-    
-    __static__ : {
-        
-        /**
-         * @return The merged pairs at key taken from all bases.
-         */
-        aggregate : function aggregate(key){
-            return this.__bases__
-                .clone()
-                .unshift(this)
-                .reverse()
-                .reduce(function(acc, base){
-                    var proto = (base instanceof Function ? base.prototype : base);
-                    return Y.extend(acc, proto[key]);
-                }, {});
-        }
     }
     
 });
index 95af71e..c7f489a 100644 (file)
@@ -51,7 +51,7 @@ Mixin.subclass('Speciated', {
         cls.__known__ = {};
     },
     
-    // onInit : function initSpeciated(evt){
+    // onCreate : function initSpeciated(evt){
     //     var d = evt.data
     //     ,   instance = d.instance
     //     ,   Species = d.cls
index 6055a4f..02fbcf9 100644 (file)
@@ -5,6 +5,7 @@ var Y     = require('Y').Y
 ,   Rect  = shape.Rect
 
 ,   map   = require('tanks/map/map')
+,   Buff  = require('tanks/effects/buff').Buff
 ,   Thing = require('tanks/thing/thing').Thing
 
 ,   ITEM_SIZE = REF_SIZE * 0.5
@@ -14,6 +15,9 @@ var Y     = require('Y').Y
 Item =
 exports['Item'] =
 Thing.subclass('Item', {
+    // __mixins__ : [  ],
+    __bind__ : [ 'onCollide', 'onAcquired', 'onLost' ],
+    
     align : 0, // 0 reserved for neutral units
     
     blocking : map.ZONE,
@@ -33,62 +37,76 @@ Thing.subclass('Item', {
     name     : 'Dummy',     // {String} Display name
     icon_inv : '',          // {String} URL to inventory icon file (TODO)
     icon_map : '',          // {String} URL to map icon file (TODO)
-    buffs    : Y([]),       // {Buff...} Passive effects (triggered on pickup)
-    effects  : Y([]),       // {Function...} Active effects (triggered on activation)
-    owner    : null,        // {Unit} Owner when picked up.
     
+    // {Buff...} Passive effects (triggered on pickup)
+    itemBuffs : Y([
+        Buff
+    ]),
+    
+    // {Function...} Active effects (triggered on activation)
+    effects : Y([]),
+    
+    // {Unit} Owner when picked up.
+    owner : null,
+    currentBuffs : null,
     
     
-    // XXX: Setting these per-instance only while testing
-    init : function initItem(name, buffs, effects){
+    init : function initItem(){
         Thing.init.call(this, 0);
-        if (name) this.name = name;
         
-        if (buffs)
-            this.buffs = Y(buffs);
-        else
-            this.buffs = this.buffs.clone();
+        this.itemBuffs = this.itemBuffs.clone();
+        this.effects = this.effects.clone();
         
-        if (effects)
-            this.effects = Y(effects);
-        else
-            this.effects = this.effects.clone();
-        
-        this.addEventListener('collide', this.onCollide.bind(this));
+        this.addEventListener('collide', this.onCollide);
+        this.addEventListener('item.acquire', this.onAcquired);
+        this.addEventListener('item.lose', this.onLost);
     },
     
     tick : function tick(elapsed, now){
-        
+        // Buffs will take care of themselves as part of the unit
     },
     
-    activate : function activate(){
-        if (!this.owner) return;
+    addToInventory : function addToInventory(unit){
+        if (!unit.hasInventory)
+            throw new Error('Unit has no inventory! '+unit);
+        unit.addItem(this);
+        return this;
     },
     
-    acquired : function acquired(unit){
-        this.owner = unit;
-        console.log(unit+' acquired '+this+'!');
-        // TODO: apply buffs
-        unit.fire('acquire', this, { 'unit':unit, 'item':this });
-        // TODO: unit to listen, add to inventory, update UI, inform unit on activation
+    removeFromInventory : function removeFromInventory(){
+        if (this.owner)
+            this.owner.removeItem(this);
+        return this;
     },
     
-    dropped : function dropped(){
+    activate : function activate(){
         if (!this.owner) return;
-        var unit = this.owner;
-        this.owner = null;
-        console.log(unit+' dropped '+this+'!');
-        unit.fire('drop', this, { 'unit':unit, 'item':this });
-        // TODO: game to listen, re-add to level at unit.loc
+    },
+    
+    onActivate : function onActivate(evt){
+        console.log(this+' activated!');
     },
     
     onCollide : function onCollide(evt){
         var unit = evt.data.unit;
-        
-        if (unit.hasInventory) {
-            this.destroy();
-            this.acquired(unit);
-        }
+        if (unit.hasInventory) unit.addItem(this);
+    },
+    
+    onAcquired : function onAcquired(evt){
+        this.owner = evt.data.unit;
+        console.log(this.owner+' acquired '+this+'!');
+        this.currentBuffs = this.itemBuffs.invoke('instantiate', this.owner);
+        this.destroy(); // removes map object
+    },
+    
+    onLost : function onLost(evt){
+        // if (!this.owner) return;
+        if ( this.currentBuffs )
+            this.currentBuffs.invoke('die', 'item.lost');
+        var unit = this.owner;
+        console.log(unit+' lost '+this+'!');
+        this.owner = null;
+        // TODO: game to listen, re-add to level at unit.loc
     },
     
     render : function render(parent){
index f81c604..3c7837f 100644 (file)
@@ -4,14 +4,16 @@
 var Y    = require('Y').Y
 ,   map  = require('tanks/map/map')
 ,   Tank = require('tanks/thing/tank').Tank
+,   Inventoried = require('tanks/mixins/inventoried').Inventoried
 ,
 
 
 PlayerTank =
 exports['PlayerTank'] =
 Tank.subclass('PlayerTank', {
+    __mixins__ : [ Inventoried ],
+    
     blocking : map.BLOCKING,
-    hasInventory : true,
     
     bodyColor   : '#E73075',
     turretColor : '#A72F5B',
index df785cc..10dba88 100644 (file)
@@ -45,8 +45,8 @@
 <script src="build/ezl/shape/circle.js" type="text/javascript"></script>
 <script src="build/ezl/shape.js" type="text/javascript"></script>
 <script src="build/ezl.js" type="text/javascript"></script>
-<script src="build/jquery.hotkeys.js" type="text/javascript"></script>
 <script src="build/tanks/globals.js" type="text/javascript"></script>
+<script src="build/jquery.hotkeys.js" type="text/javascript"></script>
 <script src="build/Y/modules/y.kv.js" type="text/javascript"></script>
 <script src="build/ezl/util/tree/binaryheap.js" type="text/javascript"></script>
 <script src="build/tanks/constants.js" type="text/javascript"></script>
 <script src="build/tanks/mixins/quantified.js" type="text/javascript"></script>
 <script src="build/tanks/thing/thing.js" type="text/javascript"></script>
 <script src="build/tanks/effects/buff.js" type="text/javascript"></script>
-<script src="build/tanks/mixins.js" type="text/javascript"></script>
 <script src="build/tanks/map/trajectory.js" type="text/javascript"></script>
 <script src="build/tanks/effects.js" type="text/javascript"></script>
+<script src="build/tanks/thing/item.js" type="text/javascript"></script>
 <script src="build/tanks/ui/pathmapui.js" type="text/javascript"></script>
 <script src="build/tanks/thing/wall.js" type="text/javascript"></script>
-<script src="build/tanks/thing/item.js" type="text/javascript"></script>
 <script src="build/tanks/ui/grid.js" type="text/javascript"></script>
 <script src="build/tanks/fx/explosion.js" type="text/javascript"></script>
 <script src="build/tanks/thing/bullet.js" type="text/javascript"></script>
+<script src="build/tanks/mixins/inventoried.js" type="text/javascript"></script>
 <script src="build/tanks/fx.js" type="text/javascript"></script>
 <script src="build/tanks/thing/tank.js" type="text/javascript"></script>
+<script src="build/tanks/mixins.js" type="text/javascript"></script>
 <script src="build/tanks/thing/player.js" type="text/javascript"></script>
 <script src="build/tanks/map/level.js" type="text/javascript"></script>
 <script src="build/tanks/thing.js" type="text/javascript"></script>