Checkpoint on new inventory architecture.
authordsc <david.schoonover@gmail.com>
Mon, 31 Jan 2011 19:54:09 +0000 (11:54 -0800)
committerdsc <david.schoonover@gmail.com>
Mon, 31 Jan 2011 19:54:09 +0000 (11:54 -0800)
14 files changed:
src/Y/utils.cjs
src/evt.cjs
src/ezl/layer/layer.cjs
src/tanks/effects/buff.cjs
src/tanks/inventory/bag.cjs [moved from src/tanks/item/bag.cjs with 100% similarity]
src/tanks/inventory/bagbag.cjs [moved from src/tanks/item/bagbag.cjs with 100% similarity]
src/tanks/inventory/belt.cjs [moved from src/tanks/item/belt.cjs with 100% similarity]
src/tanks/inventory/container.cjs [new file with mode: 0644]
src/tanks/inventory/inventory.cjs [moved from src/tanks/item/inventory.cjs with 100% similarity]
src/tanks/item/container.cjs [deleted file]
src/tanks/mixins/inventoried.cjs
src/tanks/mixins/meronomic.cjs
src/tanks/mixins/quantified.cjs
src/tanks/thing/item.cjs

index 85bbb16..649becd 100644 (file)
@@ -15,7 +15,7 @@ UE.prototype = new Error('Unimplemented!');
 
 function bindName(k){
     var v = this[k];
-    if ( isFunction(v) )
+    if ( typeof v == 'function' )
         this[k] = YFunction(v).bind(this);
 }
 
@@ -24,7 +24,7 @@ function bindAll(o, names){
         return o;
     if (arguments.length === 1)
         names = core.map(o, op.nth(1));
-    else if ( !(names instanceof Array) )
+    else if ( !isArray(names) )
         names = slice.call(arguments, 1);
     core.forEach(names, bindName, o);
     return o;
index bb45e16..83a0242 100644 (file)
@@ -86,7 +86,7 @@ function ConstructorTemplate() {
             'instance' : instance,
             'cls'      : cls,
             'args'     : Y(args)
-        });
+        }, instance);
         
         var initialise = instance.__initialise__;
         if ( isFunction(initialise) )
@@ -129,7 +129,7 @@ function createInitialise(cls){
             'instance' : instance,
             'cls'      : cls,
             'args'     : Y(arguments)
-        });
+        }, instance);
         
         return instance;
     }
@@ -233,6 +233,7 @@ function Class(className, Parent, members){
     // Record for metaprogramming
     KNOWN_CLASSES[className] = NewClass;
     
+    NewClass.__bind__ = prototype.__bind__;
     var mixins   = NewClass.__mixins__ = Y([]).concat(members.__mixins__||[], SuperClass.__mixins__||[]).unique()
     ,   statics  = NewClass.__static__ = {}
     ,   pstatics = SuperClass.__static__
@@ -269,7 +270,7 @@ function Class(className, Parent, members){
     }
     
     NewClass.instantiate = instantiate.partial(NewClass);
-    // prototype.__bind__ = Y([]).concat(prototype.__bind__||[], SuperClass.fn.__bind__||[]).unique();
+    NewClass.__bind__ = prototype.__bind__ = Y([]).concat(prototype.__bind__||[], SuperClass.__bind__||[]).unique();
     
     // Notify mixins to let them finish up any customization
     if (mixins.length)
@@ -286,7 +287,7 @@ function Class(className, Parent, members){
             'child'     : NewClass,
             'members'   : members,
             'prototype' : prototype
-        });
+        }, NewClass);
     
     return NewClass;
 }
@@ -298,6 +299,7 @@ Class.__emitter__ = new Emitter(Class);
 Class.fn = Class.prototype;
 Class.fn.__class__ = Class.fn.constructor = Class;
 Class.fn.__bases__ = Class.__bases__ = Y([ Object ]);
+Class.fn.__bind__ = Class.__bind__ = Y([]);
 Class.className = Class.fn.className = "Class";
 
 /* Class Methods */
@@ -380,8 +382,8 @@ function mixinFilter(v, k){
  */
 function mixin(cls, _mxn){
     var proto   = cls.fn
-    ,   bases   = cls.__bases__
-    ,   cbinds  = proto.__bind__
+    ,   bases   = Y(cls.__bases__ || [])
+    ,   cbinds  = Y(cls.__bind__  || [])
     ,   cstatic = cls.__static__
     ,   mxns    = (Y.isArray(_mxn) ? _mxn.slice(0) : Y(arguments, 1))
     ;
@@ -393,6 +395,7 @@ function mixin(cls, _mxn){
         
         var mproto   = (typeof mxn === "function") ? mxn.fn : mxn
         ,   statics  = mxn.__static__
+        ,   binds    = mxn.__bind__
         ,   onCreate = mproto.onCreate
         ,   firstMix = (bases && bases.indexOf(mxn) === -1)
         ;
@@ -409,13 +412,17 @@ function mixin(cls, _mxn){
                 }
         }
         
+        // Add/ensure the mixin is at the front of bases
+        bases.remove(mxn).unshift(mxn);
+        
+        // Collate binds from mixin
+        if (binds)
+            cls.__bind__ = proto.__bind__ = cbinds.concat(binds).unique();
+        
         // 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.on) && hasOwn.call(mproto,'onCreate') && isFunction(onCreate) )
             cls.on('create', onCreate);
index 32bd20a..bbfc418 100644 (file)
@@ -1,6 +1,9 @@
 //#ensure "jquery"
 var Y           = require('Y').Y
 ,   op          = require('Y/op')
+
+,   evt         = require('evt')
+
 ,   Vec         = require('ezl/math/vec').Vec
 ,   Loc         = require('ezl/loc/loc').Loc
 ,   BoundingBox = require('ezl/loc/boundingbox').BoundingBox
@@ -16,7 +19,9 @@ FAUX_ACCESSORS = Y('width height position stroke fill origin rotate scale transl
 
 Layer =
 exports['Layer'] =
-Y.subclass('Layer', {
+new evt.Class('Layer', {
+    __bind__ : [ 'tick' ],
+    
     /// Class Defaults ///
     
     // _layer* is applied to this.layer (the DOM Element)
@@ -109,7 +114,7 @@ Y.subclass('Layer', {
             this.canvas = jQuery();
         }
         
-        this.tick = this.tick.bind(this);
+        // this.tick = this.tick.bind(this);
     },
     
     /// Scene Graph Heirarchy ///
index 65be09b..0c1ea05 100644 (file)
@@ -58,13 +58,13 @@ new evt.Class('Buff', {
         return this;
     },
     
-    die : function die(reason){
+    die : function die(reason, trigger){
         this.removeStatMods();
         // 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.emit(evt, this.target, data);
-        this.target.emit(evt, this, data);
+        this.emit(evt, trigger||this.target, data, this);
+        this.target.emit(evt, this, data, this);
         return this;
     },
     
diff --git a/src/tanks/inventory/container.cjs b/src/tanks/inventory/container.cjs
new file mode 100644 (file)
index 0000000..9ab726d
--- /dev/null
@@ -0,0 +1,335 @@
+var Y        = require('Y').Y
+,   op       = require('Y/op')
+,   deepcopy = require('Y/types/object').deepcopy
+
+,   Mixin = require('evt').Mixin
+,   Item  = require('tanks/thing/item').Item
+
+,   kNull = op.K(null)
+,
+
+Container =
+exports['Container'] =
+Mixin.subclass('Container', {
+    __bind__   : [],
+    
+    
+    max   : 1,          // Container capacity
+    reqs  : null,       // Array of tag requirements to be stored here
+    equipsContents : false, // Whether held items are equipped
+    
+    owner : null,       // Owning unit
+    size  : 0,          // Number of items in container
+    items : null,       // item id -> Item
+    slots : null,       // Array of positions of items in container: container idx -> Item
+    children : null,    // Nested containers: container id -> Container
+    
+    
+    onCreate : function initContainer(evt, owner, items){
+        this.owner = owner;
+        this.items = {};
+        this.children = {};
+        this.slots = new Array(this.max);
+        
+        if ( typeof this.reqs == 'string' )
+            this.reqs = Y([ this.reqs ]);
+        
+        if (items) items.forEach(this.moveItem, this);
+    },
+    
+    
+    /**
+     * @param {Item} [item] If present, also check whether item matches this container's requirements.
+     * @return {Boolean} Whether this container will accept this item.
+     */
+    canAddItem : function canAddItem(item){
+        var reqs = this.reqs;
+        return this.size < this.max &&
+            ( !item || !reqs || reqs.intersect(item.tags).length === reqs.length );
+    },
+    
+    checkReqs : function checkReqs(item){
+        var reqs = this.reqs;
+        return !!item && ( !reqs || reqs.intersect(item.tags).length === reqs.length );
+    },
+    
+    getItem : function getItem(idx){
+        if ( typeof idx == 'number' ) {
+            return this.slots[ (idx < 0 ? this.max+idx : idx) ];
+        } else {
+            if (idx && !idx.inv)
+                idx.inv = new InventoryMeta();
+            return idx;
+        }
+    },
+    
+    /**
+     * @param {Number|Item} id The Item or Item ID for which to test.
+     * @return {Boolean} Whether the item is in this container.
+     */
+    hasItem : function hasItem(id){
+        if (id instanceof Item) id = id.__id__;
+        return !!this.items[id];
+    },
+    
+    /**
+     * Adds an item to this container.
+     * @param {Item} item
+     * @return {this}
+     */
+    addItem : function addItem(item){
+        if ( this.hasItem(item) )
+            return this;
+        else
+            return this.moveItem(item, this._getEmptySlot());
+    },
+    
+    /**
+     * Changes the position of the item in this container.
+     * @param {Item} item Item to move.
+     * @param {Number} idx Index in the container to which to move item.
+     * @return {this}
+     */
+    moveItem : function moveItem(item, idx){
+        item = this.getItem(item);
+        if (!item || typeof idx != "number")
+            return this;
+        
+        idx = this._getIdx(idx);
+        
+        // Note old slot info before we mutate anything
+        var inv = item.inv.clone()
+        ,   oldIdx = inv.idx
+        ,   oldBag = inv.bag
+        
+        // Note item we're displacing, if any
+        ,   dispItem      = this.slots[idx]
+        ,   didDisplace   = dispItem && dispItem !== item
+        ,   sameOwner     = this.owner && item.owner && this.owner === item.owner
+        ,   newPickup     = !(oldBag && sameOwner)
+        ,   inventoryMove = sameOwner && !this.hasItem(item)
+        ,   localMove     = sameOwner && !inventoryMove
+        
+        ,   hasRoom = !(didDisplace && newPickup && this.size >= this.max)
+        ;
+        
+        if ( !hasRoom )
+            return this;
+        
+        // Check requirements and then attempt transaction
+        // TODO: if displaced item fails req check, we should try to find it a new home rather than giving up
+        if ( !( this.checkReqs(item) && ( !didDisplace || oldBag.checkReqs(dispItem) )
+                && this._putItem(item, idx, didDisplace)) )
+            return this;
+        
+        // Updates meta + size
+        if ( inventoryMove )
+            oldBag._removeItem(item, oldIdx);
+        else if ( newPickup && oldBag )
+            oldBag.removeItem(item, { 'oldIdx':oldIdx });
+        
+        if ( didDisplace ) {
+            this._removeItem(dispItem, idx);
+            if ( oldBag && sameOwner )
+                oldBag._putItem(dispItem, oldIdx);
+            else
+                this._putItem(dispItem, this._getEmptySlot());
+        }
+        
+        var eq         = this.equipsContents
+        ,   oldEq      = oldBag.equipsContents
+        ,   isEquipped = sameOwner && oldEq
+        ,   evt        = 'item'+(newPickup ? '.acquire' : '.move')+(eq ? (!isEquipped ? '.equip' : '') : (isEquipped ? '.unequip' : ''))
+        ,   dispEvt    = 'item.move'+(!eq ? (oldEq ? '.equip' : '') : (!oldEq ? '.unequip' : ''))
+        ;
+        
+        // metadata clone preserves original values in case of loss (displacement by a new pickup)
+        this._emitItemChange(evt, item, { 'idx':idx, 'oldBag':inv.bag, 'oldIdx':inv.idx });
+        
+        if ( didDisplace ) {
+            oldBag._putItem(dispItem, oldIdx);
+            oldBag._emitItemChange('item.move', dispItem, { 'idx':oldIdx, 'oldBag':this, 'oldIdx':idx });
+        }
+        
+        if ( inventoryMove ) {
+            if ( didDisplace ) {
+                oldBag._putItem(dispItem, oldIdx);
+                oldBag._emitItemChange('item.move', dispItem, { 'idx':oldIdx, 'oldBag':this, 'oldIdx':idx });
+            }
+            this._emitItemChange('item.move', item, { 'idx':idx, 'oldBag':inv.bag, 'oldIdx':inv.idx });
+        } else {
+            evt     = 
+            dispEvt = 'item.'+(oldBag.equipsContents ? '.equip' : '');
+            this._emitItemChange(evt, item, { 'idx':idx, 'oldBag':inv.bag, 'oldIdx':inv.idx });
+        }
+        
+        return this;
+    },
+    
+    /**
+     * Removes the item from this container and fires the appropriate events.
+     * @param {Item|Number} item Item or container index to remove.
+     * @param {Object} [options] Options are:
+     *      * @option {Number} [oldIdx] In cases where `item.inv` has already been modified, the
+     *        old index of the item is required to be manually supplied to clean up correctly.
+     *      * @option {Boolean} [keepEquipped=false] If true, does not fire item.unequip event.
+     *      * @option {Boolean} [drop=false] If true, Item is dropped and not just removed from
+     *        this container. If Container `equipsContents`, this overrides `options.keepEquipped`.
+     * @return {Item} The removed item or null on failure.
+     */
+    removeItem : function removeItem(item, options){
+        options = options || {};
+        item = this.getItem(item);
+        if (!item)
+            return item;
+        
+        var idx = (options.oldIdx !== undefined ? options.oldIdx : item.inv.idx);
+        if ( !this._removeItem(item, options.oldIdx) )
+            return null;
+        
+        var unequip = this.equipsContents && (options.drop || !options.keepEquipped);
+        this._emitItemChange('item.lose'+(unequip ? '.unequip' : '')+(options.drop ? '.drop' : ''), item, { 'idx':idx });
+        
+        return item;
+    },
+    
+    
+    
+    /**
+     * @protected
+     * @return {Integer} Index of first empty slot in this container.
+     */
+    _getEmptySlot : function _getEmptySlot(){
+        var slots = this.slots
+        ,   max = this.max;
+        
+        if (this.size >= max)
+            return -1;
+        
+        for (var i=0, v=slots[i]; i<max; v=slots[++i])
+            if (v === undefined)
+                return i;
+        
+        return -1;
+    },
+    
+    /**
+     * @protected
+     * @param {Any} idx Index to test.
+     * @return {Number} The index, or the next empty index if non-number or -1.
+     */
+    _getIdx : function _getIdx(idx){
+        return (typeof idx == "number" && idx !== -1) ? idx : this._getEmptySlot();
+    },
+    
+    /**
+     * @protected
+     * @param {Number} [idx] Slot index to test.
+     * @return {Boolean} Ok if index is a number in the container bounds.
+     */
+    _idxOk : function _idxOk(idx){
+        return (typeof idx == "number" && 0 <= idx && idx < this.max);
+    },
+    
+    /**
+     * Inserts item into container at index, updating metadata.
+     * @protected
+     * @param {Item} item
+     * @param {Number} [idx] Container position at which to insert item. If missing, first open slot will be used.
+     * @return {Boolean}
+     */
+    _putItem : function _putItem(item, idx, didDisplace){
+        if (!item) return false;
+        
+        var idx = this._getIdx(idx)
+        ,   id  = item.__id__
+        ,   inv = item.inv
+        ;
+        
+        // Bag bounds check
+        if ( 0 <= idx && idx < this.max )
+            return false;
+        
+        // Already in bag? Remove from current slot
+        if ( this.items[id] )
+            delete this.slots[inv.idx];
+        else if (!didDisplace)
+            this.size++;                // Otherwise increment size
+        
+        this.items[id] = item;
+        this.slots[idx] = item;
+        inv.idx = idx;
+        inv.bag = this;
+        
+        return true;
+    },
+    
+    /**
+     * Removes item from container, updating metadata.
+     * @protected
+     * @param {Item} item
+     * @return {Boolean}
+     */
+    _removeItem : function removeItem(item, oldIdx){
+        if (!item) return false;
+        
+        var id  = item.__id__
+        ,   hasItem = !!this.items[id]
+        ,   itemMoved = oldIdx !== undefined
+        ,   inv = item.inv
+        ,   idx = itemMoved ? oldIdx : inv.idx
+        ;
+        
+        delete this.items[id];
+        
+        if ( hasItem ) {
+            this.size--;
+            if (this.slots[idx] === item)
+                delete this.slots[idx];
+            if (!itemMoved)
+                inv.idx = inv.bag = null;
+            return true;
+        }
+        
+        return false;
+    },
+    
+    /**
+     * @param {String} evt Event name.
+     * @param {Item} item Triggering item.
+     * @param {Object} [data] Additional data to add to the event.
+     * @return {this}
+     */
+    _emitItemChange : function _emitItemChange(evt, item, data){
+        data = Y.extend({ 'item':item, 'bag':this, 'unit':this.owner }, data || {});
+        this.emit(evt, item, data, item);
+        item.emit(evt, this, data, this);
+        // unit.emit(evt, item, data); // inventory should take care of this
+        return this;
+    }
+    
+    
+    
+});
+
+function InventoryMeta(bag, idx, links){
+    this.bag = bag;
+    this.idx = idx;
+    this.links = links || [];
+}
+Y.core.extend(InventoryMeta.prototype, {
+    clear : function clear(){
+        this.bag = this.idx = null;
+        this.links = [];
+        return this;
+    },
+    
+    clone : function clone(){
+        return new InventoryMeta(this.bag, this.idx, this.links); // Don't care about links
+    },
+    
+    toString : function(){
+        return "BagLoc(idx="+this.idx+", bag="+this.bag+")";
+    }
+    
+})
diff --git a/src/tanks/item/container.cjs b/src/tanks/item/container.cjs
deleted file mode 100644 (file)
index 436d5e1..0000000
+++ /dev/null
@@ -1,274 +0,0 @@
-var Y        = require('Y').Y
-,   op       = require('Y/op')
-,   deepcopy = require('Y/types/object').deepcopy
-
-,   Mixin = require('evt').Mixin
-,   Item  = require('tanks/thing/item').Item
-
-,   kNull = op.K(null)
-,
-
-Container =
-exports['Container'] =
-Mixin.subclass('Container', {
-    __bind__   : [],
-    
-    name  : null,   // Name of this container
-    max   : 1,      // Container capacity
-    reqs  : null,   // Array of tag requirements to be stored here
-    equipContents : false, // Whether held items are equipped
-    
-    size  : 0,      // Number of items in container
-    items : null,   // item id -> Item
-    slots : null,   // Array of positions of items in container: container idx -> Item
-    
-    
-    init : function initContainer(name, unit, items, options){
-        this.name = name;
-        this.unit = unit;
-        Y.core.extend(this, options);
-        
-        this.items = {};
-        this.slots = new Array(inv.max);
-        
-        if ( typeof this.reqs == 'string' )
-            this.reqs = Y([ this.reqs ]);
-        
-        if (items) items.forEach(this.moveItem, this);
-    },
-    
-    
-    /**
-     * @param {Item} [item] If present, also check whether item matches this container's requirements.
-     * @return {Boolean} Whether this container will accept this item.
-     */
-    canAddItem : function canAddItem(item){
-        var reqs = this.reqs;
-        return this.size < this.max && 
-            ( !item || !reqs || reqs.intersect(item.tags).length === reqs.length );
-    },
-    
-    getItem : function getItem(idx){
-        if (idx === undefined || idx === null || idx instanceof Item)
-            return idx;
-        else
-            return this.slots[idx];
-    },
-    
-    /**
-     * @param {Number|Item} id The Item or Item ID for which to test.
-     * @return {Boolean} Whether the item is in this container.
-     */
-    hasItem : function hasItem(id){
-        if (id instanceof Item) id = id.__id__;
-        return !!this.items[id];
-    },
-    
-    /**
-     * Adds an item to this container.
-     * @param {Item} item
-     * @return {this}
-     */
-    addItem : function addItem(item){
-        if ( this.hasItem(item) )
-            return this;
-        else
-            return this.moveItem(item, this._getEmptySlot());
-    },
-    
-    
-    /**
-     * Changes the position of the item in this container.
-     * @param {Item} item Item to move.
-     * @param {Number} idx Index in the container to which to move item.
-     * @return {this}
-     */
-    moveItem : function moveItem(item, idx){
-        item = this.getItem(item);
-        if (!item || typeof idx != "number")
-            return this;
-        
-        // Make note of item we're displacing, if any
-        var displacedItem = this.slots[idx]
-        ,   oldIdx = item.ui.bpIdx
-        ,   newItem = !this.hasItem(item)
-        ,   evtName = 'item.move'
-        ;
-        
-        if ( !this._putItem(item, idx) )
-            return this;
-        
-        if (newItem)
-            if (this.equipContents)
-                evtName = 'item.equip'
-            else
-                evtName = 'item.move'
-        
-        idx = item.ui.bpIdx;
-        this._emitItemChange(evtName, item, { 'idx':idx, 'oldIdx':oldIdx });
-        
-        if ( displacedItem && displacedItem !== item ) {
-            displacedItem.ui.bpIdx = null;
-            this._putItem(displacedItem, oldIdx);
-            this._emitItemChange('item.move', displacedItem, { 'idx':displacedItem.ui.bpIdx, 'oldIdx':idx });
-        }
-        
-        return this;
-    },
-    
-    /**
-     * Removes the item from this container.
-     * @param {Item|Number} idx Item or container index to remove.
-     * @param {Boolean} [drop=false] If true, Item is dropped and not just removed from this container.
-     * @return {Item} The removed item.
-     */
-    removeItem : function removeItem(idx, drop){
-        var item = this.getItem(idx);
-        idx = item ? item.ui.bpIdx : -1; // ensures we have the index if given an Item
-        
-        // TODO: UI feedback on failure
-        if ( !this._removeItem(item) )
-            return null;
-        
-        this._emitItemChange('item.lose'+(drop ? '.drop' : ''), item, { 'idx':idx });
-        return item;
-    },
-    
-    equipItem : function equipItem(slot, item){
-        item = this.getItem(item);
-        
-        var inv = this.inventory
-        ,   eqs = inv.equipment;
-        
-        if ( !(slot in eqs) )
-            throw new Error('Unit '+this+' does not have an equipment slot '+slot+'!');
-        
-        if ( !(item instanceof Item) )
-            throw new Error('Unit '+this+' cannot equip item '+item+'!');
-        
-        // TODO: Ensure item has right tags for slot
-        var oldIdx = item.backpack;
-        this._removeItem(item);
-        
-        // TODO: UI feedback
-        if ( eqs[slot] )
-            this.unequipSlot(slot, oldIdx);
-        
-        this.items[item.__id__] = item;
-        eqs[slot] = item;
-        item.slot = slot;
-        
-        return this._emitItemChange('item.equip', item, { 'slot':slot });
-    },
-    
-    unequipSlot : function unequipSlot(slot, idx){
-        var inv  = this.inventory
-        ,   eqs  = inv.equipment
-        ,   item = eqs[slot]
-        ;
-        if (item) {
-            // TODO: UI feedback on failure
-            if ( !this._putItem(item, idx) )
-                return this;
-            
-            eqs[slot] = null;
-            item.slot = null;
-            this._emitItemChange('item.unequip', item, { 'slot':slot, 'idx':idx });
-        }
-        return this;
-    },
-    
-    
-    
-    /**
-     * @return {Integer} Index of first empty slot in this unit's backpack.
-     */
-    _getEmptySlot : function _getEmptySlot(){
-        var slots = this.slots
-        ,   max = this.max;
-        
-        if (this.size >= max)
-            return -1;
-        
-        for (var i=0, v=slots[i]; i<max; v=slots[++i])
-            if (v === undefined)
-                return i;
-        
-        return -1;
-    },
-    
-    
-    /**
-     * Inserts item into backpack at index, updating metadata.
-     * @protected
-     * @param {Item} item
-     * @param {Number} [idx] Backpack position at which to insert item. If missing, first open slot will be used.
-     * @return {Boolean}
-     */
-    _putItem : function putItem(item, idx){
-        if (!item) return false;
-        
-        // item = this.getItem(item);
-        var idx = ( (typeof idx == "number") ? idx : this._getEmptySlot() )
-        ,   id = item.__id__
-        ,   ui = item.ui
-        ;
-        
-        // Backpack bounds check
-        if ( idx < 0 || idx >= this.max )
-            return false;
-        
-        if ( this.items[id] )
-            delete this.slots[ui.bpIdx]; // Already in backpack? Remove from current slot
-        else
-            this.size++;            // Otherwise increment size
-        
-        this.items[id] = item;
-        this.slots[idx] = item;
-        ui.bpIdx = idx;
-        ui.container = this;
-        
-        return true;
-    },
-    
-    
-    /**
-     * Removes item from backpack, updating metadata.
-     * @param {Item} item
-     * @return {Boolean}
-     */
-    _removeItem : function removeItem(item){
-        if (!item) return false;
-        
-        // item = this.getItem(item);
-        var id  = item.__id__
-        ,   ui  = item.ui
-        ,   idx = ui.bpIdx
-        ,   slot = ui.equipSlot
-        ,   hasItem = !!this.items[id]
-        ;
-        
-        delete this.items[id];
-        
-        if (hasItem) {
-            // TODO: unequip slot
-            item.clean();
-            delete this.slots[idx];
-            this.size--;
-            return true;
-        }
-        
-        return false;
-    },
-    
-    
-    _emitItemChange : function _emitItemChange(evt, item, data){
-        data = Y.extend({ 'unit':this, 'item':item }, data || {});
-        this.emit(evt, item, data);
-        item.emit(evt, this, data);
-        return this;
-    }
-    
-    
-    
-});
index ae2ef2f..0de4a80 100644 (file)
@@ -29,9 +29,8 @@ Mixin.subclass('Inventoried', {
     },
     
     
-    onCreate : function initInventoried(evt){
-        var self   = evt.data.instance
-        ,   clsInv = self.__class__.aggregate('inventory')
+    onCreate : function initInventoried(evt, self){
+        var clsInv = self.__class__.aggregate('inventory')
         ,   inv    = self.inventory = deepcopy(clsInv)
         ;
         inv.items     = {};
@@ -262,8 +261,8 @@ Mixin.subclass('Inventoried', {
     
     _emitItemChange : function _emitItemChange(evt, item, data){
         data = Y.extend({ 'unit':this, 'item':item }, data || {});
-        this.emit(evt, item, data);
-        item.emit(evt, this, data);
+        this.emit(evt, item, data, item);
+        item.emit(evt, this, data, this);
         return this;
     },
     
index 73dfbab..cfec61c 100644 (file)
@@ -6,7 +6,7 @@ Meronomic =
 exports['Meronomic'] =
 Mixin.subclass('Meronomic', {
     
-    onCreate : function initMeronomic(evt){
+    onCreate : function initMeronomic(evt, self){
         
     }
     
index 453f165..b21ac8b 100644 (file)
@@ -9,6 +9,7 @@ var Y = require('Y').Y
 Quantified =
 exports['Quantified'] =
 Mixin.subclass('Quantified', {
+    __bind__ : [ 'onBuffAcquired', 'onBuffLost' ],
     
     stats     : {},
     cooldowns : {},
@@ -16,11 +17,12 @@ Mixin.subclass('Quantified', {
     buffs     : [],
     
     
-    onCreate : function initQuantified(evt){
-        var self = evt.data.instance;
+    onCreate : function initQuantified(evt, self){
         self.buffs = Y([]);
-        self.on('buff.conjure', self.onBuffAcquired.bind(self));
-        self.on('buff.die',     self.onBuffLost.bind(self));
+        self.on('buff.conjure', self.onBuffAcquired);
+        self.on('buff.die',     self.onBuffLost);
+        // self.on('buff.conjure', self.onBuffAcquired.bind(self));
+        // self.on('buff.die',     self.onBuffLost.bind(self));
     },
     
     stat : function stat(k){
index c70295c..712a68f 100644 (file)
@@ -18,7 +18,7 @@ Item =
 exports['Item'] =
 Thing.subclass('Item', {
     // __mixins__ : [  ],
-    __bind__ : [ 'onCollide', 'onAcquired', 'onLost', 'onEquip', 'onUnequip' ],
+    __bind__ : [ 'onCollide', 'onAcquired', 'onLost', 'onEquip', 'onUnequip', 'onBuffDeath' ],
     
     align : 0, // 0 reserved for neutral units
     
@@ -47,39 +47,34 @@ Thing.subclass('Item', {
     
     owner        : null,    // {Unit} Owner when picked up.
     currentBuffs : null,    // {Buff...} Buffs applied to owner
+    isEquipped   : false,
     
-    // UI Bookkeeping
-    inv : {
-        idx     : null,   // {Integer} Index in owner's backpack
-        container : null    // {String} Slotname if equipped on owner
-    },
+    // Inventory Bookkeeping
+    // inv : {
+    //     links : [],         // {Array} Pointers to Belts that link to this item
+    //     idx : null,         // {Integer} Index in bag
+    //     bag : null          // {Container} Containing bag
+    // },
     
     
     
     init : function initItem(){
         Thing.init.call(this, 0);
-        this.clean();
         
         this.effects = this.effects.clone();
         this.passives = this.passives.clone();
         
-        this.activeBuffs = new Y.YArray();
-        this.passiveBuffs = new Y.YArray();
+        this.currentBuffs = new Y.YArray();
+        this.currentActive = new Y.YArray();
+        this.currentPassive = new Y.YArray();
         
         this.activateGauge = new CooldownGauge(this.cooldowns.activate, REF_SIZE+1,REF_SIZE+1);
         
-        this.on('collide', this.onCollide);
+        this.on('collide',      this.onCollide);
         this.on('item.acquire', this.onAcquired);
-        this.on('item.lose', this.onLost);
-        this.on('item.equip', this.onEquip);
-    },
-    
-    /**
-     * Resets UI bookkeeping.
-     */
-    clean : function clean(){
-        this.inv = Y.core.map(this.inv, kNull);
-        return this;
+        this.on('item.lose',    this.onLost);
+        this.on('item.equip',   this.onEquip);
+        this.on('item.unequip', this.onUnequip);
     },
     
     tick : function tick(elapsed, now, ticks){
@@ -103,23 +98,52 @@ Thing.subclass('Item', {
         console.log(this+' activated!');
         
         if ( this.owner && this.cooldowns.activate.activate() ) {
-            this.currentBuffs.extend( this.effects.invoke('instantiate', this.owner) );
+            var buffs = this.currentBuffs
+            ,   active = this.currentActive
+            ;
+            if ( active.length )
+                active.invoke('die', 'item.reactivated');
+            
+            var activated = this.effects.invoke('instantiate', this.owner);
+            activated.invoke('on', 'buff.die', this.onBuffDeath);
+            buffs.extend(activated);
+            active.extend(activated);
         }
         return this;
     },
     
-    onEquip : function onEquip(){
+    onBuffDeath : function onBuffDeath(evt, buff){
+        this.currentBuffs.remove(buff);
+        this.currentActive.remove(buff);
+        this.currentPassive.remove(buff);
+    },
+    
+    onEquip : function onEquip(evt){
         console.log(this+' equipped!');
         
         if ( this.owner ) {
-            // TODO: Note buffs for unequip
-            this.currentBuffs.extend( this.passives.invoke('instantiate', this.owner) );
+            this.isEquipped = true;
+            var buffs = this.currentBuffs
+            ,   passive = this.currentPassive
+            ;
+            if ( passive.length )
+                passive.invoke('die', 'item.reequipped');
+            
+            var equipped = this.passives.invoke('instantiate', this.owner);
+            equipped.invoke('on', 'buff.die', this.onBuffDeath);
+            buffs.extend(equipped);
+            passive.extend(equipped);
         }
+        return this;
     },
     
     onUnequip : function onUnequip(evt){
         console.log(this+' unequipped!');
-        // TODO: unequip buffs
+        this.isEquipped = false;
+        if ( this.owner ) {
+            this.currentPassive.invoke('die', 'item.unequipped');
+        }
+        return this;
     },
     
     // TODO: Add to correct container
@@ -129,22 +153,25 @@ Thing.subclass('Item', {
             unit.addItem(this);
     },
     
-    onAcquired : function onAcquired(evt){
+    onAcquired : function onAcquired(evt, container){
         this.owner = evt.data.unit;
         // console.log(this.owner+' acquired '+this+' ('+this.desc+')!');
-        // this.currentBuffs = this.passives.invoke('instantiate', this.owner);
         this.remove(); // removes map object
         this.game.map.removeBlocker(this); // remove map zone
     },
     
     onLost : function onLost(evt){
-        // if (!this.owner) return;
-        if ( this.currentBuffs )
-            this.currentBuffs.invoke('die', 'item.lost');
-        var unit = this.owner;
+        var current = this.currentBuffs
+        ,   unit = this.owner
+        ;
+        if ( current.length )
+            current.invoke('die', 'item.lost');
+        
         // console.log(unit+' lost '+this+'!');
         this.owner = null;
-        // TODO: game to listen, re-add to level at unit.loc
+        
+        if ( /\blost\.drop\b/.test(evt.type) )
+            unit.game.addUnit(this, unit.loc); // TODO: stop item from being instantly picked up.
     },
     
     render : function render(parent){