From: dsc Date: Mon, 31 Jan 2011 06:39:35 +0000 (-0800) Subject: Checkpoint on new inventory architecture and equipment UI. X-Git-Url: http://git.less.ly:3516/?a=commitdiff_plain;h=d039736fc2a205f287096dbf7d05e3d421302041;p=tanks.git Checkpoint on new inventory architecture and equipment UI. --- 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/containerui.cjs similarity index 100% rename from src/tanks/ui/inventory/container.cjs rename to src/tanks/ui/inventory/containerui.cjs