// 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;
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
;
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;
},
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;
}
-
});
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;
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<len; v=bp[++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 inv = this.inventory
- , bp = inv.backpack
- , idx = ( (typeof idx == "number") ? idx : this.getEmptySlot() )
-
- , id = item.__id__
- , ui = item.ui
- ;
-
- // Backpack bounds check
- if ( idx < 0 || idx >= 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) )
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<len; v=bp[++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 inv = this.inventory
+ , bp = inv.backpack
+ , idx = ( (typeof idx == "number") ? idx : this._getEmptySlot() )
+
+ , id = item.__id__
+ , ui = item.ui
+ ;
+
+ // Backpack bounds check
+ if ( idx < 0 || idx >= 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;
+ },
});
, Thing = require('tanks/thing/thing').Thing
, ITEM_SIZE = REF_SIZE * 0.5
+, kNull = op.K(null)
,
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
},
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);
* Resets UI bookkeeping.
*/
clean : function clean(){
- this.ui = { bpIdx:null, equipSlot:null };
+ this.inv = Y.core.map(this.inv, kNull);
return this;
},
// TODO: unequip buffs
},
+ // TODO: Add to correct container
onCollide : function onCollide(evt){
var unit = evt.data.unit;
if (unit.hasInventory && unit.canAddItem())