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;
+    }
+    
+    
+    
+    
+});
diff --git