From d039736fc2a205f287096dbf7d05e3d421302041 Mon Sep 17 00:00:00 2001 From: dsc Date: Sun, 30 Jan 2011 22:39:35 -0800 Subject: [PATCH] Checkpoint on new inventory architecture and equipment UI. --- src/Y/class.cjs | 9 +- src/Y/modules/y.event.cjs | 3 +- src/Y/types/array.cjs | 36 ++++- src/Y/types/collection.cjs | 24 +++- src/evt.cjs | 21 ++-- src/tanks/item/container.cjs | 259 ++++++++++++++++---------------- src/tanks/mixins/inventoried.cjs | 196 ++++++++++++------------ src/tanks/thing/item.cjs | 17 ++- src/tanks/ui/inventory/container.cjs | 206 ------------------------- src/tanks/ui/inventory/containerui.cjs | 206 +++++++++++++++++++++++++ 10 files changed, 515 insertions(+), 462 deletions(-) create mode 100644 src/tanks/item/bag.cjs create mode 100644 src/tanks/item/bagbag.cjs create mode 100644 src/tanks/item/belt.cjs create mode 100644 src/tanks/item/inventory.cjs delete mode 100644 src/tanks/ui/inventory/container.cjs create mode 100644 src/tanks/ui/inventory/containerui.cjs diff --git a/src/Y/class.cjs b/src/Y/class.cjs index b8bf9e9..65b7d9d 100644 --- a/src/Y/class.cjs +++ b/src/Y/class.cjs @@ -99,12 +99,9 @@ function Class(className, Parent, members) { // Creates a new function with the appropriate name // based on the className. - var NewClass, - constructor = [ - 'var '+className, - (''+_Class).replace(_Class.name, className), - 'NewClass = '+className, '' - ].join(';\n'); + var NewClass, constructor = + 'var '+className+' = NewClass = '+ + (''+_Class).replace(_Class.name, className) + ';'; eval(constructor); // Copy Class statics diff --git a/src/Y/modules/y.event.cjs b/src/Y/modules/y.event.cjs index 64859d9..2e15d46 100644 --- a/src/Y/modules/y.event.cjs +++ b/src/Y/modules/y.event.cjs @@ -4,8 +4,7 @@ Y['event'] = exports; /** * A simple event. * TODO: Detect jQuery event objects. - * TODO: If DOM event, wrap with consistent API. - * TODO: use jQuery if present to wrap events. + * TODO: If DOM event, wrap with consistent API (use jQuery if present). */ var Event = exports['Event'] = diff --git a/src/Y/types/array.cjs b/src/Y/types/array.cjs index 37a181e..6b3a9eb 100644 --- a/src/Y/types/array.cjs +++ b/src/Y/types/array.cjs @@ -40,6 +40,17 @@ YCollection.subclass('YArray', function(YArray){ this._o = o || []; }; + this['get'] = + function get(i){ + if (arguments.length > 1) { + return this.slice.apply(this, arguments); + } else { + if (i < 0) + i += this._o.length; + return this._o[i]; + } + }; + this['clone'] = function clone(){ return new YArray(this._o.slice(0)); @@ -59,13 +70,14 @@ YCollection.subclass('YArray', function(YArray){ (function (j){ Object.defineProperty(self, j+'', { 'get' : function(){ return this._o[j]; }, - 'set' : function(v){ return (this._o[j] = v); }, + 'set' : function(v){ return (this._o[j] = v); } }); }).call(this, i); this['toString'] = function(){ - return "Y[" + arrayToString.call(this._o||[]) + "]"; + // return "Y[" + arrayToString.call(this._o||[]) + "]"; + return "YArray(["+this._o+"])"; }; /** @@ -93,11 +105,25 @@ YCollection.subclass('YArray', function(YArray){ return v; }; - function _has(v){ return this._o.indexOf(v) !== -1; } - + /** + * Intersects this YArray with another collection, returning a new YArray. + * The membership test uses Y(a).has(), so it is possible to intersect collections of different types. + * For YArray and YObject, .has() uses strict equality (===) via .indexOf(). + * + * @param {Array|Object|YCollection} a Comparison collection. + * @return {YArray} A new YArray of all elements in {this} found in the supplied collection. + * + * var foo = /foo/; + * var A = [foo, 'A', 1, 2, 3, 'C', /foo/]; + * var B = [foo, 'B', 3, 'A', 1, /foo/]; + * var I = Y(A).intersect(B); + * I.toString() === "YArray([/foo/,A,1,3])"; // true + * I.get(0) === foo; // true + */ this['intersect'] = function intersect(a){ - return Y(a).filter(_has, this); + var A = Y(a); + return this.filter(A.has, A); }; this['clear'] = diff --git a/src/Y/types/collection.cjs b/src/Y/types/collection.cjs index 7fbced3..46bbff5 100644 --- a/src/Y/types/collection.cjs +++ b/src/Y/types/collection.cjs @@ -1,4 +1,5 @@ -var YBase = require('Y/class').YBase +var Y = require('Y/y').Y +, YBase = require('Y/class').YBase , type = require('Y/type') , core = require('Y/core') , del = require('Y/delegate') @@ -154,8 +155,29 @@ YBase.subclass('YCollection', { // FIXME: this._o[name].apply 'apply' : function apply(name, args){ return this[name].apply(this, args); + }, + + /** + * Intersects this YArray with another collection, returning a new YArray. + * The membership test uses Y(a).has(), so it is possible to intersect collections of different types. + * For YArray and YObject, .has() uses strict equality (===) via .indexOf(). + * + * @param {Array|Object|YCollection} a Comparison collection. + * @return {YArray} A new YArray of all elements in {this} found in the supplied collection. + * + * var foo = /foo/; + * var A = [foo, 'A', 1, 2, 3, 'C', /foo/]; + * var B = [foo, 'B', 3, 'A', 1, /foo/]; + * var I = Y(A).intersect(B); + * I.toString() === "YArray([/foo/,A,1,3])"; // true + * I.get(0) === foo; // true + */ + 'intersect' : function intersect(a){ + var A = Y(a); + return this.filter(A.has, A); } + }); diff --git a/src/evt.cjs b/src/evt.cjs index b74b7d8..611ed6a 100644 --- a/src/evt.cjs +++ b/src/evt.cjs @@ -310,14 +310,14 @@ function instantiate(cls){ return new cls(Y(arguments,1)); // the magic is done by checking in the constructor for this specific caller } -var CST = Class.__static__ = {}; -CST.instantiate = Class.instantiate = Y(instantiate).methodize(); -CST.fabricate = Class.fabricate = Y.Class.fabricate; +var Cstatics = Class.__static__ = {}; +Cstatics.instantiate = Class.instantiate = Y(instantiate).methodize(); +Cstatics.fabricate = Class.fabricate = Y.Class.fabricate; /** * Class/Instance method of Classes, not to be confused with Evt.subclass, which is a static method. */ -CST.subclass = +Cstatics.subclass = Class.subclass = Class.fn.subclass = Y(function subclass(className, members){ @@ -329,12 +329,11 @@ function lookupClass(name){ return (typeof name === "function") ? name : KNOWN_CLASSES[name]; } -var /** * Everybody's favourite party metaclass! */ -Mixin = +var Mixin = exports['Mixin'] = new Class('Mixin', Class, { @@ -380,11 +379,11 @@ function mixinFilter(v, k){ * Mixes a Mixin into another Class. */ function mixin(cls, _mxn){ - var proto = cls.fn + var proto = cls.fn , bases = cls.__bases__ , cbinds = proto.__bind__ , cstatic = cls.__static__ - , mxns = (Y.isArray(_mxn) ? _mxn.slice(0) : Y(arguments, 1)) + , mxns = (Y.isArray(_mxn) ? _mxn.slice(0) : Y(arguments, 1)) ; mxns.reverse().forEach(function(mxn){ mxn = (typeof mxn === "string") ? lookupClass(mxn) : mxn; @@ -392,9 +391,9 @@ function mixin(cls, _mxn){ if ( !mxn ) throw new Error('Cannot mix in non-object! '+mxn); - var mproto = (typeof mxn === "function") ? mxn.fn : mxn - , statics = mxn.__static__ - , onCreate = mproto.onCreate + var mproto = (typeof mxn === "function") ? mxn.fn : mxn + , statics = mxn.__static__ + , onCreate = mproto.onCreate , firstMix = (bases && bases.indexOf(mxn) === -1) ; diff --git a/src/tanks/item/bag.cjs b/src/tanks/item/bag.cjs new file mode 100644 index 0000000..e69de29 diff --git a/src/tanks/item/bagbag.cjs b/src/tanks/item/bagbag.cjs new file mode 100644 index 0000000..e69de29 diff --git a/src/tanks/item/belt.cjs b/src/tanks/item/belt.cjs new file mode 100644 index 0000000..e69de29 diff --git a/src/tanks/item/container.cjs b/src/tanks/item/container.cjs index 2e6b6d5..b728b43 100644 --- a/src/tanks/item/container.cjs +++ b/src/tanks/item/container.cjs @@ -2,7 +2,7 @@ var Y = require('Y').Y , op = require('Y/op') , deepcopy = require('Y/types/object').deepcopy -, evt = require('evt') +, Mixin = require('evt').Mixin , Item = require('tanks/thing/item').Item , kNull = op.K(null) @@ -10,13 +10,13 @@ var Y = require('Y').Y Container = exports['Container'] = -evt.subclass('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 - equips : false, // Whether held items are equipped + equipContents : false, // Whether held items are equipped size : 0, // Number of items in container items : null, // item id -> Item @@ -39,32 +39,151 @@ evt.subclass('Container', { /** + * @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){ - return this.size < this.max && ( !item || !this.reqs || this.reqs.intersect(item.tags) ); + 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) - return idx; - if (idx instanceof Item) + 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._fireItemChange(evtName, 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 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._fireItemChange('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._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; + }, + /** * @return {Integer} Index of first empty slot in this unit's backpack. */ - getEmptySlot : function getEmptySlot(){ + _getEmptySlot : function _getEmptySlot(){ var slots = this.slots , max = this.max; @@ -90,9 +209,7 @@ evt.subclass('Container', { if (!item) return false; // item = this.getItem(item); - var slots = this.slots - , idx = ( (typeof idx == "number") ? idx : this.getEmptySlot() ) - + var idx = ( (typeof idx == "number") ? idx : this._getEmptySlot() ) , id = item.__id__ , ui = item.ui ; @@ -102,13 +219,14 @@ evt.subclass('Container', { return false; if ( this.items[id] ) - delete slots[ui.bpIdx]; // Already in backpack? Remove from current slot + delete this.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; + this.slots[idx] = item; + ui.bpIdx = idx; + ui.container = this; return true; }, @@ -149,121 +267,8 @@ evt.subclass('Container', { 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 a/src/tanks/item/inventory.cjs b/src/tanks/item/inventory.cjs new file mode 100644 index 0000000..e69de29 diff --git a/src/tanks/mixins/inventoried.cjs b/src/tanks/mixins/inventoried.cjs index 2831a52..6af5fa4 100644 --- a/src/tanks/mixins/inventoried.cjs +++ b/src/tanks/mixins/inventoried.cjs @@ -48,6 +48,11 @@ Mixin.subclass('Inventoried', { return inv.size < inv.max; }, + /** + * Looks up the Item at an index; returns undefined if that index is empty. + * @param {Number|Item} idx Index in backpack to retrieve. If an Item is passed, it is returned. + * @return {Item|undefined} + */ getItem : function getItem(idx){ if (idx instanceof Item) return idx; @@ -60,109 +65,13 @@ Mixin.subclass('Inventoried', { return !!this.inventory.items[id]; }, - - - /** - * @return {Integer} Index of first empty slot in this unit's backpack. - */ - getEmptySlot : function getEmptySlot(){ - var inv = this.inventory - , bp = inv.backpack - , len = inv.max; - - if (inv.size >= inv.max) - return -1; - - for (var i=0, v=bp[i]; i= bp.length ) - return false; - - if ( inv.items[id] ) - delete bp[ui.bpIdx]; // Already in backpack? Remove from current slot - else - inv.size++; // Otherwise increment size - - bp[idx] = item; - ui.bpIdx = idx; - inv.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 inv = this.inventory - , id = item.__id__ - , ui = item.ui - , idx = ui.bpIdx - , slot = ui.equipSlot - , hasItem = !!inv.items[id] - ; - - delete inv.items[id]; - - if (hasItem) { - // TODO: unequip slot - item.clean(); - delete inv.backpack[idx]; - inv.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){ item = this.getItem(item); - var idx = this.getEmptySlot(); + var idx = this._getEmptySlot(); // TODO: UI feedback on failure if ( !this._putItem(item, idx) ) @@ -262,9 +171,100 @@ Mixin.subclass('Inventoried', { this._fireItemChange('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 inv = this.inventory + , bp = inv.backpack + , len = inv.max; + + if (inv.size >= inv.max) + return -1; + + for (var i=0, v=bp[i]; i= bp.length ) + return false; + + if ( inv.items[id] ) + delete bp[ui.bpIdx]; // Already in backpack? Remove from current slot + else + inv.size++; // Otherwise increment size + + bp[idx] = item; + ui.bpIdx = idx; + inv.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 inv = this.inventory + , id = item.__id__ + , ui = item.ui + , idx = ui.bpIdx + , slot = ui.equipSlot + , hasItem = !!inv.items[id] + ; + + delete inv.items[id]; + + if (hasItem) { + // TODO: unequip slot + item.clean(); + delete inv.backpack[idx]; + inv.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; + }, }); diff --git a/src/tanks/thing/item.cjs b/src/tanks/thing/item.cjs index af2fa5c..946c2df 100644 --- a/src/tanks/thing/item.cjs +++ b/src/tanks/thing/item.cjs @@ -10,6 +10,7 @@ var Y = require('Y').Y , Thing = require('tanks/thing/thing').Thing , ITEM_SIZE = REF_SIZE * 0.5 +, kNull = op.K(null) , @@ -48,9 +49,9 @@ Thing.subclass('Item', { currentBuffs : null, // {Buff...} Buffs applied to owner // UI Bookkeeping - ui : { - bpIdx : null, // {Integer} Index in owner's backpack - equipSlot : null // {String} Slotname if equipped on owner + inv : { + idx : null, // {Integer} Index in owner's backpack + container : null // {String} Slotname if equipped on owner }, @@ -59,9 +60,12 @@ Thing.subclass('Item', { Thing.init.call(this, 0); this.clean(); - this.currentBuffs = new Y.YArray(); - this.passives = this.passives.clone(); this.effects = this.effects.clone(); + this.passives = this.passives.clone(); + + this.activeBuffs = new Y.YArray(); + this.passiveBuffs = new Y.YArray(); + this.activateGauge = new CooldownGauge(this.cooldowns.activate, REF_SIZE+1,REF_SIZE+1); this.addEventListener('collide', this.onCollide); @@ -74,7 +78,7 @@ Thing.subclass('Item', { * Resets UI bookkeeping. */ clean : function clean(){ - this.ui = { bpIdx:null, equipSlot:null }; + this.inv = Y.core.map(this.inv, kNull); return this; }, @@ -118,6 +122,7 @@ Thing.subclass('Item', { // TODO: unequip buffs }, + // TODO: Add to correct container onCollide : function onCollide(evt){ var unit = evt.data.unit; if (unit.hasInventory && unit.canAddItem()) diff --git a/src/tanks/ui/inventory/container.cjs b/src/tanks/ui/inventory/container.cjs deleted file mode 100644 index faf1ce2..0000000 --- a/src/tanks/ui/inventory/container.cjs +++ /dev/null @@ -1,206 +0,0 @@ -//#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( '

'+(this.title || this.name.toLowerCase())+'

' ); - - 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('') - .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', ''+item.name+'
'+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/containerui.cjs b/src/tanks/ui/inventory/containerui.cjs new file mode 100644 index 0000000..faf1ce2 --- /dev/null +++ b/src/tanks/ui/inventory/containerui.cjs @@ -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( '

'+(this.title || this.name.toLowerCase())+'

' ); + + 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('') + .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', ''+item.name+'
'+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+')'; + } + +}) +; -- 1.7.0.4