Checkpoint adding equippable items.
authordsc <david.schoonover@gmail.com>
Sun, 30 Jan 2011 09:20:13 +0000 (01:20 -0800)
committerdsc <david.schoonover@gmail.com>
Sun, 30 Jan 2011 09:20:13 +0000 (01:20 -0800)
23 files changed:
data/types/levels.yaml
src/Y/types/array.cjs
src/ezl/layer/html.cjs
src/ezl/layer/index.cjs
src/ezl/layer/layer.cjs
src/ezl/shape/circle.cjs
src/ezl/shape/line.cjs
src/ezl/shape/polygon.cjs
src/ezl/shape/rect.cjs
src/ezl/shape/shape.cjs
src/ezl/widget/cooldown.cjs
src/tanks/fx/explosion.cjs
src/tanks/item/container.cjs [new file with mode: 0644]
src/tanks/map/level.cjs
src/tanks/mixins/inventoried.cjs
src/tanks/ui/grid.cjs
src/tanks/ui/inventory/backpack.cjs
src/tanks/ui/inventory/container.cjs [new file with mode: 0644]
src/tanks/ui/inventory/equipslot.cjs
src/tanks/ui/inventory/inventory.cjs [deleted file]
src/tanks/ui/pathmapui.cjs
www/css/lttl.css
www/header.html

index 89bb7fc..48e348f 100644 (file)
@@ -71,7 +71,7 @@ types:
           - type: nitro
             loc: [325,475]
           - type: nitro
-            loc: [275,475]
+            loc: [325,25]
     
     three_test:
         name: Three Test
index 3f8877d..37a181e 100644 (file)
@@ -34,7 +34,10 @@ YCollection.subclass('YArray', function(YArray){
     
     this['init'] =
     function initYArray(o){
-        this._o = o || [];
+        if (o instanceof YArray)
+            this._o = o._o;
+        else
+            this._o = o || [];
     };
     
     this['clone'] =
@@ -90,10 +93,18 @@ YCollection.subclass('YArray', function(YArray){
                 return v;
     };
     
+    function _has(v){ return this._o.indexOf(v) !== -1; }
+    
+    this['intersect'] =
+    function intersect(a){
+        return Y(a).filter(_has, this);
+    };
+    
     this['clear'] =
     function clear(){
         var A = this._o;
-        while (A.length) A.pop();
+        // while (A.length) A.pop();
+        A.splice(0, A.length);
         return this;
     };
     
index ddfd032..8c2d0bf 100644 (file)
@@ -6,13 +6,7 @@ var Y = require('Y').Y
 HtmlLayer =
 exports['HtmlLayer'] =
 Layer.subclass('HtmlLayer', {
+    _layerClasses : 'ezl layer',
+    hasCanvas : false
     
-    init : function initHtmlLayer(){
-        Layer.init.call(this);
-    },
-    
-    treatAsChild : function treatAsChild(){
-        
-    },
-    
-})
+});
\ No newline at end of file
index e69de29..55b4392 100644 (file)
@@ -0,0 +1,14 @@
+var Y = require('Y').Y
+,   layer = require('ezl/layer/layer')
+,   html  = require('ezl/layer/html')
+;
+
+Y.core.extend(exports, {
+    'layer'     : layer,
+    'Layer'     : layer.Layer,
+    
+    'html'      : html,
+    'HtmlLayer' : html.HtmlLayer,
+    
+    'text'      : require('ezl/layer/text')
+});
index 48b71b6..32bd20a 100644 (file)
@@ -17,7 +17,26 @@ FAUX_ACCESSORS = Y('width height position stroke fill origin rotate scale transl
 Layer =
 exports['Layer'] =
 Y.subclass('Layer', {
-    _cssClasses : 'ezl layer',
+    /// Class Defaults ///
+    
+    // _layer* is applied to this.layer (the DOM Element)
+    _layerHtml  : '<div/>',     // HTML that will become the layer
+    _layerId    : null,         // HTML id attribute
+    _layerAttrs : null,         // HTML attributes
+    _layerClasses : 'ezl layer',// CSS classes
+    
+    originX : 0,
+    originY : 0,
+    
+    hasCanvas          : true,  // Whether to create a canvas
+    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
+    
+    
+    
+    /// State ///
     
     canvas     : null,
     parent     : null,
@@ -42,24 +61,17 @@ Y.subclass('Layer', {
     _origin : null, // rotational origin
     transform : null, // Object
     
-    /// Defaults ///
-    
-    hasCanvas          : true,  // Whether to create a canvas
-    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,
-    
-    
     
     /// Setup ///
     
     init : function init(props, attrs, html){
-        if (props)
-            Y.core.extend(this, props);
+        if (props !== undefined && props !== null) {
+            switch (typeof props) {
+                case 'boolean' : this.hasCanvas = props;        break;
+                case 'string'  : this._layerHtml = props;       break;
+                case 'object'  : Y.core.extend(this, props);    break;
+            }
+        }
         
         this.children   = new Y.YArray();
         this.animActive = new Y.YArray();
@@ -79,11 +91,14 @@ Y.subclass('Layer', {
             translate : new Loc(0,0) // translates canvas
         };
         
-        this.layer = jQuery(html || '<div/>')
-            .addClass(this._cssClasses)
+        this.layer = jQuery(html || this._layerHtml)
+            .addClass(this._layerClasses)
             .data('layer', this);
         this.layer[0].layer = this;
-        if (attrs) this.layer.attr(attrs);
+        
+        if (this._layerAttrs)   this.layer.attr(this._layerAttrs);
+        if (this._layerId)      this.layer.attr('id', this._layerId);
+        if (attrs)              this.layer.attr(attrs);
         
         if (this.hasCanvas) {
             this.canvas = jQuery('<canvas />')
index 909ee8d..aabdcaf 100644 (file)
@@ -4,7 +4,7 @@ var Shape = require('ezl/shape/shape').Shape
 Circle =
 exports['Circle'] =
 Shape.subclass('Circle', {
-    _cssClasses : 'ezl layer shape circle',
+    _layerClasses : 'ezl layer shape circle',
     originX : '50%',
     originY : '50%',
     
index 3d592ea..9a05999 100644 (file)
@@ -9,7 +9,7 @@ var Y     = require('Y').Y
 Line =
 exports['Line'] =
 Shape.subclass('Line', {
-    _cssClasses : 'ezl layer shape line',
+    _layerClasses : 'ezl layer shape line',
     
     useCanvasScaling : true,
     fillStyle   : 'transparent',
index 8601c16..67250bc 100644 (file)
@@ -8,7 +8,7 @@ var Y     = require('Y').Y
 Polygon =
 exports['Polygon'] =
 Shape.subclass('Polygon', {
-    _cssClasses : 'ezl layer shape polygon',
+    _layerClasses : 'ezl layer shape polygon',
     
     /**
      * Expects two arrays of coordinate-halfs, which could be zipped
@@ -56,7 +56,7 @@ Shape.subclass('Polygon', {
 Triangle =
 exports['Triangle'] =
 Polygon.subclass('Triangle', {
-    _cssClasses : 'ezl layer shape polygon triangle',
+    _layerClasses : 'ezl layer shape polygon triangle',
     
     init : function initTriangle(x1,y1, x2,y2){
         Polygon.init.call(this, [x1,x2], [y1,y2]);
@@ -82,7 +82,7 @@ Polygon.subclass('Triangle', {
 Quad =
 exports['Quad'] =
 Polygon.subclass('Quad', {
-    _cssClasses : 'ezl layer shape polygon quad',
+    _layerClasses : 'ezl layer shape polygon quad',
     
     init : function initQuad(x1,y1, x2,y2, x3,y3){
         Polygon.init.call(this, [x1,x2,x3], [y1,y2,y3]);
index d88a45c..2918a2b 100644 (file)
@@ -4,7 +4,7 @@ var Shape = require('ezl/shape/shape').Shape
 Rect =
 exports['Rect'] =
 Shape.subclass('Rect', {
-    _cssClasses : 'ezl layer shape rect',
+    _layerClasses : 'ezl layer shape rect',
     originX : 0,
     originY : 0,
     
index e7ffad7..d78907d 100644 (file)
@@ -4,7 +4,7 @@ var Layer = require('ezl/layer/layer').Layer
 Shape =
 exports['Shape'] =
 Layer.subclass('Shape', {
-    _cssClasses : 'ezl layer shape',
+    _layerClasses : 'ezl layer shape',
     
     fillStyle   : 'rgba(231,48,117, 1)',
     strokeStyle : 'transparent',
index 61e1c0b..0a84612 100644 (file)
@@ -14,7 +14,7 @@ CooldownGauge =
 exports['CooldownGauge'] =
 Layer.subclass('CooldownGauge', function setupCooldownGauge(CooldownGauge){
     Y.extend(this, {
-        _cssClasses : 'ezl layer cooldown',
+        _layerClasses : 'ezl layer cooldown',
         fillStyle   : '#000000',
         strokeStyle : 'transparent',
         lineWidth   : 0,
index d8069ea..9ee36d2 100644 (file)
@@ -5,7 +5,7 @@ var Y = require('Y').Y
 Explosion =
 exports['Explosion'] =
 Circle.subclass('Explosion', {
-    _cssClasses : 'ezl layer circle explosion',
+    _layerClasses : 'ezl layer circle explosion',
     fillStyle   : '#980011',
     
     ringSizes  : [ 0.6, 0.3 ],
diff --git a/src/tanks/item/container.cjs b/src/tanks/item/container.cjs
new file mode 100644 (file)
index 0000000..2e6b6d5
--- /dev/null
@@ -0,0 +1,269 @@
+var Y        = require('Y').Y
+,   op       = require('Y/op')
+,   deepcopy = require('Y/types/object').deepcopy
+
+,   evt = require('evt')
+,   Item  = require('tanks/thing/item').Item
+
+,   kNull = op.K(null)
+,
+
+Container =
+exports['Container'] =
+evt.subclass('Container', {
+    __bind__   : [],
+    
+    name  : null,   // Name of this container
+    max   : 1,      // Container capacity
+    reqs  : null,   // Array of tag requirements to be stored here
+    equips : 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);
+    },
+    
+    
+    /**
+     * @return {Boolean} Whether this container will accept this item.
+     */
+    canAddItem : function canAddItem(item){
+        return this.size < this.max && ( !item || !this.reqs || this.reqs.intersect(item.tags) );
+    },
+    
+    getItem : function getItem(idx){
+        if (idx === undefined || idx === null)
+            return idx;
+        if (idx instanceof Item)
+            return idx;
+        else
+            return this.slots[idx];
+    },
+    
+    hasItem : function hasItem(id){
+        if (id instanceof Item) id = id.__id__;
+        return !!this.items[id];
+    },
+    
+    
+    
+    /**
+     * @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 slots  = this.slots
+        ,   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 slots[ui.bpIdx]; // Already in backpack? Remove from current slot
+        else
+            this.size++;            // Otherwise increment size
+        
+        slots[idx] = item;
+        ui.bpIdx = idx;
+        this.items[id] = item;
+        
+        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;
+    },
+    
+    
+    _fireItemChange : function _fireItemChange(evt, item, data){
+        data = Y.extend({ 'unit':this, 'item':item }, data || {});
+        this.fire(evt, item, data);
+        item.fire(evt, this, data);
+        return this;
+    },
+    
+    // TODO: update UI; UI to inform item on activation
+    /**
+     * @param {Item} item
+     * @return {this}
+     */
+    addItem : function addItem(item){
+        if ( this.hasItem(item) )
+            return this;
+        
+        item = this.getItem(item);
+        var idx = this.getEmptySlot();
+        
+        // TODO: UI feedback on failure
+        if ( !this._putItem(item, idx) )
+            return this;
+        
+        return this._fireItemChange('item.acquire', item, { 'idx':idx });
+    },
+    
+    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
+        ;
+        
+        if ( !this._putItem(item, idx) )
+            return this;
+        
+        idx = item.ui.bpIdx;
+        this._fireItemChange('item.move', item, { 'idx':idx, 'oldIdx':oldIdx });
+        
+        if ( displacedItem && displacedItem !== item ) {
+            displacedItem.ui.bpIdx = null;
+            this._putItem(displacedItem, oldIdx);
+            this._fireItemChange('item.move', displacedItem, { 'idx':displacedItem.ui.bpIdx, 'oldIdx':idx });
+        }
+        
+        return true;
+    },
+    
+    removeItem : function removeItem(item){
+        item = this.getItem(item);
+        var idx = item && item.backpack;
+        
+        // TODO: UI feedback on failure
+        if ( this._removeItem(item) )
+            return this;
+        
+        this._fireItemChange('item.lose', item, { 'idx':idx });
+        return item;
+    },
+    
+    dropItem : function dropItem(idx){
+        item = this.getItem(item);
+        var idx = item ? item.ui.bpIdx : -1;
+        
+        // TODO: UI feedback on failure
+        if ( this._removeItem(item) )
+            return this;
+        
+        this._fireItemChange('item.lose.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._fireItemChange('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._fireItemChange('item.unequip', item, { 'slot':slot, 'idx':idx });
+        }
+        return this;
+    }
+    
+    
+    
+    
+});
index 1093e53..ad898b1 100644 (file)
@@ -22,14 +22,14 @@ Level =
 exports['Level'] =
 new evt.Class('Level', {
     __mixins__ : [ Speciated ],
-    _cssClasses : 'ezl layer level',
+    _layerClasses : 'ezl layer level',
     
     init : function init(game, capacity, buffer_size){
         this.game    = game;
         this.map = new Map(0,0, this.width, this.height, capacity, buffer_size);
         
         var shape = this.shape = new Rect(this.width,this.height).fill('transparent');
-        shape.layer.attr('class', this._cssClasses);
+        shape.layer.attr('class', this._layerClasses);
         this.bbox = shape.bbox;
     },
     
index f4e1c3e..2831a52 100644 (file)
@@ -13,15 +13,16 @@ exports['Inventoried'] =
 Mixin.subclass('Inventoried', {
     hasInventory : true,
     inventory : {
-        // Map of equipment slots to requirement tags
-        slots : {
-            'weapon' : 'weapon',
-            'armor'  : 'armor',
-            'item1'  : 'consumable',
-            'item2'  : 'consumable'
+        
+        
+        slots : {           // Map of equipment slots to requirement tags
+            'weapon'   : { max:1,  reqs:'weapon' },
+            'armor'    : { max:2,  reqs:'armor'  },
+            'backpack' : { max:12, reqs:null, defaultContainer:true }
         },
+        
         max       : 12,     // Backpack capacity
-        size      : 0,      // Number of items
+        size      : 0,      // Number of items in backpack
         items     : null,   // item id -> Item
         backpack  : null,   // Array of positions of items in backpack: backpack idx -> Item
         equipment : null    // slotName -> Item
index e2f4822..e1f0cc6 100644 (file)
@@ -11,7 +11,7 @@ Rect.subclass('Grid', {
     createGridCanvas : null,
     
     // Shape Config
-    _cssClasses : 'grid rect shape layer ezl',
+    _layerClasses : 'grid rect shape layer ezl',
     
     strokeStyle : '#6E6E6E',
     lineWidth   : 0.5,
index 704da1a..946678b 100644 (file)
@@ -8,7 +8,9 @@ var Y = require('Y').Y
 Backpack =
 exports['Backpack'] =
 Layer.subclass('Backpack', {
-    _cssClasses : 'backpack hud',
+    _layerClasses : 'backpack item-container hud',
+    _layerAttrs : { 'id':'backpack' },
+    _layerHtml  : '<div><h3>backpack</h3></div>',
     hasCanvas : false,
     
     
@@ -17,8 +19,6 @@ Layer.subclass('Backpack', {
         Layer.init.call(this);
         
         this.unit = unit;
-        this.layer.attr('id', 'backpack');
-        this.layer.append('<h3>backpack</h3>');
         var inv = this.inventory = unit.inventory
         ,   bp  = inv.backpack
         ;
@@ -27,7 +27,6 @@ Layer.subclass('Backpack', {
             return new BackpackSlot(this, idx).appendTo(this);
         }, this);
         
-        
         unit.addEventListener('item.acquire', this.onItemUpdated);
         unit.addEventListener('item.lose',    this.onItemUpdated);
         unit.addEventListener('item.move',    this.onItemUpdated);
@@ -59,9 +58,11 @@ Layer.subclass('Backpack', {
         var item = ui.draggable.data('item')
         ,   toIdx = $(evt.target).data('idx')
         ;
-        console.log(this+'.onDrop(item='+item+', toIdx='+toIdx+')', evt);
+        // console.log(this+'.onDrop(item='+item+', toIdx='+toIdx+')', evt);
         this.unit.moveItem(item, toIdx);
-        setTimeout(function(){ item.dragging = false; }, 25);
+        
+        // Clear flag preventing the drag from activing the item
+        setTimeout(function(){ item.dragging = false; }, 250);
     },
     
     
@@ -76,8 +77,9 @@ Layer.subclass('Backpack', {
 BackpackSlot =
 exports['BackpackSlot'] =
 Layer.subclass('BackpackSlot', {
-    _cssClasses : 'backpack hud slot',
+    _layerClasses : 'backpack hud slot',
     hasCanvas  : false,
+    
     artWidth   : 50,
     artHeight  : 50,
     
diff --git a/src/tanks/ui/inventory/container.cjs b/src/tanks/ui/inventory/container.cjs
new file mode 100644 (file)
index 0000000..faf1ce2
--- /dev/null
@@ -0,0 +1,206 @@
+//#ensure "jquery.tipsy"
+//#ensure "jquery-ui"
+var Y = require('Y').Y
+,   Layer = require('ezl/layer/layer').Layer
+,   HtmlLayer = require('ezl/layer/html').HtmlLayer
+,
+
+
+ItemContainerUI =
+exports['ItemContainerUI'] =
+HtmlLayer.subclass('ItemContainerUI', {
+    _layerClasses : 'item-container hud',
+    
+    name      : null,
+    title     : null,
+    showTitle : true,
+    defaultContainer : false,
+    
+    max       : 1,
+    reqs      : null,
+    unit      : null,
+    
+    
+    
+    init : function initItemContainerUI(name, unit, items, options){
+        Y.bindAll(this, 'onItemUpdated', 'onDrop');
+        if (options) Y.core.extend(this, options);
+        
+        this.name = name;
+        this.unit = unit;
+        this.items = items;
+        
+        this._layerId = this.name+'_slot';
+        HtmlLayer.init.call(this);
+        
+        if (this.showTitle)
+            this.layer.append( '<h3>'+(this.title || this.name.toLowerCase())+'</h3>' );
+        
+        this.slots = new Y(0, this.max).map(this._makeSlot, this);
+        
+        if (this.defaultContainer) {
+            unit.addEventListener('item.acquire', this.onItemUpdated);
+            unit.addEventListener('item.lose',    this.onItemUpdated);
+            unit.addEventListener('item.move',    this.onItemUpdated);
+        }
+    },
+    
+    _makeSlot : function _makeSlot(idx){
+        return new ItemContainerUISlot(this, idx).appendTo(this);
+    },
+    
+    refresh : function refresh(){
+        this.slots.invoke('refresh');
+        this.layer.find('.slot')
+            .droppable({
+                accept     : '.item',
+                hoverClass : 'drophover',
+                drop       : this.onDrop
+            });
+        
+        return this;
+    },
+    
+    getSlot : function getSlot(idx){
+        return this.slots[idx];
+    },
+    
+    onItemUpdated : function onItemUpdated(evt){
+        var d = evt.data;
+        this.slots[d.idx].refresh();
+        if ('oldIdx' in d) this.slots[d.oldIdx].refresh();
+    },
+    
+    onDrop : function onDrop(evt, ui){
+        var item = ui.draggable.data('item')
+        ,   toIdx = $(evt.target).data('idx')
+        ;
+        // console.log(this+'.onDrop(item='+item+', toIdx='+toIdx+')', evt);
+        this.unit.moveItem(item, toIdx);
+        
+        // Clear flag preventing the drag from activing the item
+        setTimeout(function(){ item.dragging = false; }, 250);
+    },
+    
+    
+    toString : function(){
+        return this.className+'(unit='+this.unit+')';
+    }
+    
+})
+,
+
+
+ItemContainerUISlot =
+exports['ItemContainerUISlot'] =
+Layer.subclass('ItemContainerUISlot', {
+    _layerClasses : 'item-container slot hud ',
+    hasCanvas  : false,
+    
+    artWidth   : 50,
+    artHeight  : 50,
+    
+    idx      : -1,
+    dragging : false,
+    backpack : null,
+    item     : null,
+    
+    
+    init : function initItemContainerUISlot(backpack, idx){
+        Y.bindAll(this, 'onActivate', 'onDragStart');
+        Layer.init.call(this);
+        this.backpack = backpack;
+        this.inventory = backpack.inventory;
+        this.idx = idx;
+        this.layer.addClass('slot'+idx);
+        this.inner =
+            new Layer({ hasCanvas:false })
+                .appendTo(this)
+                .position(8, 8);
+    },
+    
+    refresh : function refresh(){
+        this.layer.data({
+            'backpack-slot': this,
+            'idx': this.idx
+        });
+        var item = this.inventory.backpack[this.idx];
+        if (item)
+            this.setItem(item);
+        else
+            this.removeItem();
+    },
+    
+    setItem : function setItem(item){
+        if (!item) return this;
+        
+        this.removeItem();
+        this.item = item;
+        this.layer.addClass('occupied');
+        
+        var icon = item.art && item.art.inv_icon
+        ,   src = this.itemEl =
+            jQuery('<img />')
+                .width(this.artWidth)
+                .height(this.artHeight)
+                .appendTo(this.inner.layer)
+        ;
+        this.inner.layer
+            .addClass('item')
+            .data('item', item)
+            .draggable({
+                start  : this.onDragStart,
+                revert : 'invalid',
+                zIndex : 10
+            })
+            .attr('title', '<b>'+item.name+'</b><br/>'+item.desc)
+            .tipsy({ 'html':true, 'gravity':$.fn.tipsy.autoNS })
+        ;
+        
+        if (icon) src.attr('src', icon);
+        
+        this.inner.append(item.activateGauge);
+        this.inner.layer.bind('click', this.onActivate);
+        
+        return this;
+    },
+    
+    removeItem : function removeItem(){
+        if (this.item) {
+            this.layer
+                .removeClass('occupied');
+            
+            // Dismiss tooltip if present
+            this.inner.layer
+                .trigger('mouseleave');
+            
+            this.inner.remove();
+            this.inner =
+                new Layer({ hasCanvas:false })
+                    .appendTo(this)
+                    .position(8, 8);
+        }
+        
+        this.item = null;
+        this.itemEl = null;
+        return this;
+    },
+    
+    onDragStart : function onDragStart(evt, ui){
+        var item = this.item;
+        // console.log(this+'.dragStart(item='+item+', dragging='+(item || {}).dragging+')', evt, ui);
+        if (item) item.dragging = true;
+    },
+    
+    onActivate : function onActivate(evt){
+        var item = this.item;
+        // console.log(this+'.onActivate(item='+item+', dragging='+item.dragging+')');
+        if (item && !item.dragging) item.activate();
+    },
+    
+    toString : function(){
+        return this.className+'(idx='+this.idx+', item='+this.item+')';
+    }
+    
+})
+;
index 7813a6b..50e05ab 100644 (file)
+//#ensure "jquery.tipsy"
+//#ensure "jquery-ui"
 var Y = require('Y').Y
+,   Layer = require('ezl/layer/layer').Layer
+,   HtmlLayer = require('ezl/layer/html').HtmlLayer
 ,
 
+
 EquipSlot =
 exports['EquipSlot'] =
-Y.subclass('EquipSlot', {
+HtmlLayer.subclass('EquipSlot', {
+    _layerClasses : 'equip item-container hud',
+    
+    
+    init : function initEquipSlot(slotName, reqs, unit){
+        Y.bindAll(this, 'onItemUpdated', 'onDrop');
+        
+        this.slotName = slotName;
+        this.reqs = reqs;
+        this.unit = unit;
+        
+        HtmlLayer.init.call(this, null,
+            { 'id':slotName+'_slot' }, // layer attributes
+            '<div><h3>'+slotName.toLowerCase()+'</h3></div>'); // layer html
+        
+        var inv = this.inventory = unit.inventory
+        ,   bp  = inv.backpack
+        ;
+        this.slots = new Y(0, inv.max).map(function(idx){
+            var item = bp[idx];
+            return new BackpackSlot(this, idx).appendTo(this);
+        }, this);
+        
+        unit.addEventListener('item.acquire', this.onItemUpdated);
+        unit.addEventListener('item.lose',    this.onItemUpdated);
+        unit.addEventListener('item.move',    this.onItemUpdated);
+    },
+    
+    refresh : function refresh(){
+        this.slots.invoke('refresh');
+        this.layer.find('.slot')
+            .droppable({
+                accept     : '.item',
+                hoverClass : 'drophover',
+                drop       : this.onDrop
+            });
+        
+        return this;
+    },
+    
+    getSlot : function getSlot(idx){
+        return this.slots[idx];
+    },
+    
+    onItemUpdated : function onItemUpdated(evt){
+        var d = evt.data;
+        this.slots[d.idx].refresh();
+        if ('oldIdx' in d) this.slots[d.oldIdx].refresh();
+    },
+    
+    onDrop : function onDrop(evt, ui){
+        var item = ui.draggable.data('item')
+        ,   toIdx = $(evt.target).data('idx')
+        ;
+        // console.log(this+'.onDrop(item='+item+', toIdx='+toIdx+')', evt);
+        this.unit.moveItem(item, toIdx);
+        
+        // Clear flag preventing the drag from activing the item
+        setTimeout(function(){ item.dragging = false; }, 250);
+    },
+    
+    
+    toString : function(){
+        return this.className+'(unit='+this.unit+')';
+    }
+    
+})
+,
+
+
+BackpackSlot =
+exports['BackpackSlot'] =
+HtmlLayer.subclass('BackpackSlot', {
+    _layerClasses : 'backpack hud slot',
+    hasCanvas  : false,
+    
+    artWidth   : 50,
+    artHeight  : 50,
+    
+    idx      : -1,
+    dragging : false,
+    backpack : null,
+    item     : null,
+    
+    
+    init : function initBackpackSlot(backpack, idx){
+        Y.bindAll(this, 'onActivate', 'onDragStart');
+        Layer.init.call(this);
+        this.backpack = backpack;
+        this.inventory = backpack.inventory;
+        this.idx = idx;
+        this.layer.addClass('slot'+idx);
+        this.inner =
+            new Layer({ hasCanvas:false })
+                .appendTo(this)
+                .position(8, 8);
+    },
     
-    init : function initEquipSlot(){
-        this.layer = $('<div />')
+    refresh : function refresh(){
+        this.layer.data({
+            'backpack-slot': this,
+            'idx': this.idx
+        });
+        var item = this.inventory.backpack[this.idx];
+        if (item)
+            this.setItem(item);
+        else
+            this.removeItem();
     },
     
+    setItem : function setItem(item){
+        if (!item) return this;
+        
+        this.removeItem();
+        this.item = item;
+        this.layer.addClass('occupied');
+        
+        var icon = item.art && item.art.inv_icon
+        ,   src = this.itemEl =
+            jQuery('<img />')
+                .width(this.artWidth)
+                .height(this.artHeight)
+                .appendTo(this.inner.layer)
+        ;
+        this.inner.layer
+            .addClass('item')
+            .data('item', item)
+            .draggable({
+                start  : this.onDragStart,
+                revert : 'invalid',
+                zIndex : 10
+            })
+            .attr('title', '<b>'+item.name+'</b><br/>'+item.desc)
+            .tipsy({ 'html':true, 'gravity':$.fn.tipsy.autoNS })
+        ;
+        
+        if (icon) src.attr('src', icon);
+        
+        this.inner.append(item.activateGauge);
+        this.inner.layer.bind('click', this.onActivate);
+        
+        return this;
+    },
+    
+    removeItem : function removeItem(){
+        if (this.item) {
+            this.layer
+                .removeClass('occupied');
+            
+            // Dismiss tooltip if present
+            this.inner.layer
+                .trigger('mouseleave');
+            
+            this.inner.remove();
+            this.inner =
+                new Layer({ hasCanvas:false })
+                    .appendTo(this)
+                    .position(8, 8);
+        }
+        
+        this.item = null;
+        this.itemEl = null;
+        return this;
+    },
+    
+    onDragStart : function onDragStart(evt, ui){
+        var item = this.item;
+        // console.log(this+'.dragStart(item='+item+', dragging='+(item || {}).dragging+')', evt, ui);
+        if (item) item.dragging = true;
+    },
+    
+    onActivate : function onActivate(evt){
+        var item = this.item;
+        // console.log(this+'.onActivate(item='+item+', dragging='+item.dragging+')');
+        if (item && !item.dragging) item.activate();
+    },
     
+    toString : function(){
+        return this.className+'(idx='+this.idx+', item='+this.item+')';
+    }
     
-});
+})
+;
diff --git a/src/tanks/ui/inventory/inventory.cjs b/src/tanks/ui/inventory/inventory.cjs
deleted file mode 100644 (file)
index f8e855f..0000000
+++ /dev/null
@@ -1,17 +0,0 @@
-var Y = require('Y').Y
-,
-
-Inventory =
-exports['Inventory'] =
-Y.subclass('Inventory', {
-    unit : null,
-    inv : null,
-    layer : null,
-    
-    
-    init : function initInventory(unit){
-        this.unit = unit;
-        this.inv = unit.inventory;
-    }
-    
-});
index aca2005..435c5d7 100644 (file)
@@ -20,7 +20,7 @@ Rect.subclass('PathMapUI', {
     overlayAiPaths : null,
     
     // Shape Config
-    _cssClasses : 'map rect shape layer ezl',
+    _layerClasses : 'map rect shape layer ezl',
     
     
     // Blocking objects
index 967999a..689c605 100644 (file)
@@ -48,11 +48,12 @@ td { text-align:center; vertical-align:middle; }
 #game { position:absolute; width:100%; height:100%; margin:0; padding:0; }
 #viewport { position:relative; margin:1em auto; cursor:crosshair; width:auto; /* width:500px; height:500px;  overflow:hidden; top:50px; left:50px; */ }
     #viewport .layer.grid { outline:1px solid rgba(255,255,255,0.1); }
-#backpack { position:absolute; bottom:1em; right:1em; width:304px; height:254px; /* top:auto; left:auto; */ }
-    #backpack h3 { color:#fff; margin:0.25em; padding:0.5em 0; }
-    #backpack .slot { float:left; position:relative; width:50px; height:50px; margin:0.25em; border:1px solid transparent;
-        /* padding:0; top:auto; left:auto; */ }
-    #backpack .slot.drophover { border:1px solid #FFF6AE; }
+#backpack { position:absolute; bottom:1em; right:1em; width:304px; height:254px; }
+
+.item-container {}
+    .item-container h3 { color:#fff; margin:0.25em; padding:0.5em 0; }
+    .item-container .slot { float:left; position:relative; width:50px; height:50px; margin:0.25em; border:1px solid transparent; }
+    .item-container .slot.drophover { border:1px solid #FFF6AE; }
 
 
 
index b2b0b95..5b1a109 100644 (file)
@@ -1,11 +1,17 @@
 <!DOCTYPE html>
 <html>
 <head>
-<title>The Littlest Battletank</title>
 <link rel="shortcut icon" href="/favicon.ico">
 <link rel="stylesheet" href="css/reset.css" type="text/css" media="screen">
 <link rel="stylesheet" href="css/lttl.css"  type="text/css" media="screen">
 <link rel="stylesheet" href="css/tipsy.css" type="text/css" media="screen">
+<script>
+document.write(
+    '<title>'+
+        (window.location.hostname === 'tanks.woo' ? '[dev] ' : '')+
+        'The Littlest Battletank'+
+    '</title>');
+</script>
 </head>
 <body class="lttl tanks">