// 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
/**
* 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'] =
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));
(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+"])";
};
/**
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'] =
-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')
// 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);
}
+
});
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){
return (typeof name === "function") ? name : KNOWN_CLASSES[name];
}
-var
/**
* Everybody's favourite party metaclass!
*/
-Mixin =
+var Mixin =
exports['Mixin'] =
new Class('Mixin', Class, {
* 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;
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)
;
, 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)
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
/**
+ * @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;