From: dsc Date: Mon, 3 Jan 2011 11:32:17 +0000 (-0800) Subject: Sweet. Items work. X-Git-Url: http://git.less.ly:3516/?a=commitdiff_plain;h=80e232b5ffc104aff579c1d667bea46e536518aa;p=tanks.git Sweet. Items work. --- diff --git a/src/Y/types/array.cjs b/src/Y/types/array.cjs index 90568f3..faca9e6 100644 --- a/src/Y/types/array.cjs +++ b/src/Y/types/array.cjs @@ -20,7 +20,7 @@ YCollection.subclass('YArray', function(YArray){ var newYArray = YArray.instantiate.bind(YArray); mixInto({ target:YArray, donor:_Array, context:'_o', - names:'indexOf lastIndexOf shift pop join'.split(' ') }); + names:'indexOf lastIndexOf shift join'.split(' ') }); mixInto({ target:YArray, donor:_Array, context:'_o', chain:true, names:'push unshift sort splice reverse'.split(' ') }); mixInto({ target:YArray, donor:_Array, context:'_o', fnFirst:true, wrap:newYArray, @@ -105,6 +105,17 @@ YCollection.subclass('YArray', function(YArray){ return this; }; + this['pop'] = + function pop(idx){ + var r, A = this._o; + if (idx !== undefined) { + r = A[idx]; + A.splice(idx, 1); + return r; + } else + return A.pop(); + }; + this['remove'] = function remove(v){ var L = arguments.length; diff --git a/src/evt.cjs b/src/evt.cjs index fbce38a..07392eb 100644 --- a/src/evt.cjs +++ b/src/evt.cjs @@ -46,7 +46,7 @@ var Y = require('Y').Y '__bases__', '__initialise__', '__class__', 'constructor', 'className', '__emitter__', '__static__', '__mixins__' ] //.concat(classStatics) -, mixinSkip = [ '__mixin_skip__', 'onMixin', 'onInit', 'init' +, mixinSkip = [ '__mixin_skip__', 'onMixin', 'onCreate', 'init' ].concat(classMagic) , GLOBAL_ID = 0 , @@ -66,14 +66,15 @@ var Y = require('Y').Y */ ConstructorTemplate = exports['ConstructorTemplate'] = -function ConstructorTemplate() { - var k, v +function ConstructorTemplate(_args) { + var instance = this , cls = arguments.callee - , instance = this + , caller = unwrap(cls.caller) + , args = (caller === instantiate) ? _args : arguments ; // Not subclassing - if ( unwrap(cls.caller) !== Y.fabricate ) { + if ( caller !== Y.fabricate ) { instance.id = GLOBAL_ID++; // Perform actions that should only happen once in Constructor @@ -82,12 +83,12 @@ function ConstructorTemplate() { cls.fire('create', instance, { 'instance' : instance, 'cls' : cls, - 'args' : Y(arguments) + 'args' : Y(args) }); var initialise = instance.__initialise__; if ( isFunction(initialise) ) - return initialise.apply(instance, arguments); + return initialise.apply(instance, args); } return instance; @@ -265,6 +266,8 @@ function Class(className, Parent, members){ setDesc(prototype, k, getDesc(members,k)); } + // prototype.__bind__ = Y([]).concat(prototype.__bind__||[], SuperClass.fn.__bind__||[]).unique(); + // Notify mixins to let them finish up any customization if (mixins.length) mixins.forEach(function(mxn){ @@ -301,10 +304,7 @@ Class.className = Class.fn.className = "Class"; * Unlike the keyword `new`, instantiate can be applied. */ function instantiate(cls){ - var instance = cls.fabricate(); - if ( instance.initialise ) - instance.initialise.apply(instance, arguments); - return instance; + return new cls(Y(arguments,1)); // the magic is done by checking in the constructor for this specific caller } var CST = Class.__static__ = {}; @@ -341,10 +341,29 @@ new Class('Mixin', Class, { __mixin_skip__ : mixinSkip, __static__ : { + + /** + * Mixes this mixin into a class. + */ mixInto : function mixIntoClass(cls){ var mxn = this; return mixin(cls, mxn); + }, + + /** + * @return The merged pairs at key taken from all bases. + */ + aggregate : function aggregate(key){ + return this.__bases__ + .clone() + .unshift(this) + .reverse() + .reduce(function(acc, base){ + var proto = (base instanceof Function ? base.prototype : base); + return Y.extend(acc, proto[key]); + }, {}); } + } }); @@ -359,6 +378,9 @@ function mixinFilter(v, k){ */ function mixin(cls, _mxn){ var proto = cls.fn + , bases = cls.__bases__ + , cbinds = proto.__bind__ + , cstatic = cls.__static__ , mxns = (Y.isArray(_mxn) ? _mxn.slice(0) : Y(arguments, 1)) ; mxns.reverse().forEach(function(mxn){ @@ -368,17 +390,13 @@ function mixin(cls, _mxn){ throw new Error('Cannot mix in non-object! '+mxn); var mproto = (typeof mxn === "function") ? mxn.fn : mxn - , bases = cls.__bases__ - , cstatic = cls.__static__ , statics = mxn.__static__ - , onInit = mproto.onInit + , onCreate = mproto.onCreate + , firstMix = (bases && bases.indexOf(mxn) === -1) ; core.extend(proto, core.filter(mproto, mixinFilter, mproto)); - if (bases && bases.indexOf(mxn) === -1) - bases.unshift(mxn); - // Add mixin statics to class constructor if (statics){ for (var k in statics) @@ -389,9 +407,16 @@ function mixin(cls, _mxn){ } } - // Register onInit to fire whenever a new instance is initialized - if ( hasOwn.call(mproto,'onInit') && isFunction(onInit) ) - cls.addEventListener('init', onInit); + // Only perform these actions the first time we mix into this class + if (!firstMix) + return; + + // Add to bases + bases.unshift(mxn); + + // Register onCreate to fire whenever a new instance is initialized + if ( isFunction(cls.addEventListener) && hasOwn.call(mproto,'onCreate') && isFunction(onCreate) ) + cls.addEventListener('create', onCreate); // Fire the mixin event on this Mixin only if we're done constructing the class if ( isFunction(mxn.fire) && mixin.caller !== Class ) diff --git a/src/tanks/effects/buff.cjs b/src/tanks/effects/buff.cjs index 99bc4bd..b29e874 100644 --- a/src/tanks/effects/buff.cjs +++ b/src/tanks/effects/buff.cjs @@ -13,16 +13,14 @@ var Y = require('Y').Y // - die.{expire,dismiss,dispel} Buff = exports['Buff'] = -evt.subclass('Buff', { - __mixins__: [ Speciated, Meronomic, Quantified ], // Configurable, Usable +new evt.Class('Buff', { + __mixins__: [ Speciated, Meronomic ], // Configurable, Usable /// Configurable /// priority : 0, // Order of stat and effect application (lower is earlier) // TODO - stats : { - timeout : Infinity, - threat : 1 // TODO - }, + timeout : 5000, + threat : 1, // TODO // TODO: functions // {stat : Number|Function} Modifications to stats @@ -33,6 +31,7 @@ evt.subclass('Buff', { triggers : {}, // {event : Effect} // maybe /// Instance /// + name : 'Turrrrbo', owner : null, // Agent which originated the buff target : null, // Agent to which the buff applies game : null, @@ -40,24 +39,22 @@ evt.subclass('Buff', { init : function initBuff(target, owner){ - this.owner = owner; - this.game = owner.game; + this.game = target.game; this.target = target; - - this.createStats() - // this.createCooldowns(); + this.owner = owner || target; this.created = this.game.NOW; - this.expires = this.created + this.stats.timeout.val; + this.expires = this.created + this.timeout; this.applyStatMods(); - this.target.fire('buff.live.acquire', this, { 'buff':this, 'unit':target, 'owner':owner }); + + var data = { 'buff':this, 'unit':this.target, 'owner':this.owner }; + this.fire('buff.conjure', this.target, data); + this.target.fire('buff.conjure', this, data); }, tick : function tick(elapsed, now){ - // this.updateCooldowns(); - - if (now < this.expires) { + if (now >= this.expires) { // this.remove(); this.die('expire'); } @@ -67,7 +64,11 @@ evt.subclass('Buff', { die : function die(reason){ this.removeStatMods(); - this.target.fire('buff.die.'+reason, this, { 'buff':this, 'unit':target, 'owner':owner }); + // var evt = 'buff.die.'+reason // We'll add this when we add polymorphic dispatchers + var evt = 'buff.die' + , data = { 'buff':this, 'unit':this.target, 'owner':this.owner }; + this.fire(evt, this.target, data); + this.target.fire(evt, this, data); return this; }, diff --git a/src/tanks/mixins/configurable.cjs b/src/tanks/mixins/configurable.cjs index 2d0f617..a57065f 100644 --- a/src/tanks/mixins/configurable.cjs +++ b/src/tanks/mixins/configurable.cjs @@ -6,7 +6,7 @@ Configurable = exports['Configurable'] = Mixin.subclass('Configurable', { - onInit : function initConfigurable(evt){ + onCreate : function initConfigurable(evt){ } diff --git a/src/tanks/mixins/index.cjs b/src/tanks/mixins/index.cjs index e7384ef..ea351cc 100644 --- a/src/tanks/mixins/index.cjs +++ b/src/tanks/mixins/index.cjs @@ -1,6 +1,7 @@ require('Y').Y.core .extend(exports, { - 'Speciated' : require('tanks/mixins/speciated').Speciated, - 'Meronomic' : require('tanks/mixins/meronomic').Meronomic, - 'Quantified' : require('tanks/mixins/quantified').Quantified + 'Speciated' : require('tanks/mixins/speciated').Speciated, + 'Meronomic' : require('tanks/mixins/meronomic').Meronomic, + 'Quantified' : require('tanks/mixins/quantified').Quantified, + 'Inventoried' : require('tanks/mixins/inventoried').Inventoried }); diff --git a/src/tanks/mixins/inventoried.cjs b/src/tanks/mixins/inventoried.cjs new file mode 100644 index 0000000..307b089 --- /dev/null +++ b/src/tanks/mixins/inventoried.cjs @@ -0,0 +1,72 @@ +var Y = require('Y').Y +, Mixin = require('evt').Mixin +, Item = require('tanks/thing/item').Item +, + +Inventoried = +exports['Inventoried'] = +Mixin.subclass('Inventoried', { + hasInventory : true, + inventory : [], + + + onCreate : function initInventoried(evt){ + var self = evt.data.instance; + self.inventory = new Y.YArray(); + }, + + // TODO: update UI; UI to inform item on activation + addItem : function addItem(item){ + if (!item) return this; + + this.inventory.push(item); + + var data = { 'unit':this, 'item':item }; + this.fire('item.acquire', item, data); + item.fire('item.acquire', this, data); + + return this; + }, + + getItem : function getItem(idx){ + this.inventory.attr(idx); + }, + + hasItem : function hasItem(item){ + return this.inventory.has(item); + }, + + removeItem : function removeItem(idx){ + if (idx instanceof Item){ + var item = idx; + this.inventory.remove(item); + } else + var item = this.inventory.pop(idx); + + if (item) { + var data = { 'unit':this, 'item':item }; + this.fire('item.lose', item, data); + item.fire('item.lose', this, data); + } + + return item; + }, + + dropItem : function dropItem(idx){ + if (idx instanceof Item){ + var item = idx; + this.inventory.remove(item); + } else + var item = this.inventory.pop(idx); + + if (item) { + var data = { 'unit':this, 'item':item }; + this.fire('item.lose.drop', item, data); + item.fire('item.lose.drop', this, data); + } + + return item; + } + + +}); diff --git a/src/tanks/mixins/meronomic.cjs b/src/tanks/mixins/meronomic.cjs index 0f21048..73dfbab 100644 --- a/src/tanks/mixins/meronomic.cjs +++ b/src/tanks/mixins/meronomic.cjs @@ -6,7 +6,7 @@ Meronomic = exports['Meronomic'] = Mixin.subclass('Meronomic', { - onInit : function initMeronomic(evt){ + onCreate : function initMeronomic(evt){ } diff --git a/src/tanks/mixins/quantified.cjs b/src/tanks/mixins/quantified.cjs index 26fdf98..05f2e4a 100644 --- a/src/tanks/mixins/quantified.cjs +++ b/src/tanks/mixins/quantified.cjs @@ -9,11 +9,19 @@ Quantified = exports['Quantified'] = Mixin.subclass('Quantified', { - stats : {}, + stats : {}, cooldowns : {}, - ai : {}, + ai : {}, + buffs : [], + onCreate : function initQuantified(evt){ + var self = evt.data.instance; + self.buffs = Y([]); + self.addEventListener('buff.conjure', self.onBuffAcquired.bind(self)); + self.addEventListener('buff.die', self.onBuffLost.bind(self)); + }, + stat : function stat(k){ var s = this.stats[k]; return s ? s.val : s; @@ -38,41 +46,32 @@ Mixin.subclass('Quantified', { updateCooldowns : function updateCooldowns(elapsed, now){ this._cooldowns.invoke('tick', elapsed, now); this._ai.invoke('tick', elapsed, now); + this.buffs.invoke('tick', elapsed, now); return this; }, + onBuffAcquired : function onBuffAcquired(evt){ + var buff = evt.data.buff; + this.buffs.push(buff); + console.log('Buff '+buff+' acquired by '+this); + // TODO: update UI + }, - // onInit : function initQuantified(evt){ - // var self = evt.data.instance; - // self.createStats(); - // self.createCooldowns(); - // }, + onBuffLost : function onBuffLost(evt){ + var buff = evt.data.buff; + this.buffs.remove(buff); + console.log('Buff '+buff+' lost by '+this); + // TODO: update UI + }, - onMixin : function onMixin(evt){ + onMixin : function mixQuantified(evt){ var cls = evt.data.cls , mxn = evt.data.mixin , proto = cls.fn ; - console.log(mxn, '.onMixin()', cls); + // console.log(mxn, '.onMixin()', cls); proto.stats = cls.aggregate('stats'); proto.cooldowns = cls.aggregate('cooldowns'); proto.ai = cls.aggregate('ai'); - }, - - __static__ : { - - /** - * @return The merged pairs at key taken from all bases. - */ - aggregate : function aggregate(key){ - return this.__bases__ - .clone() - .unshift(this) - .reverse() - .reduce(function(acc, base){ - var proto = (base instanceof Function ? base.prototype : base); - return Y.extend(acc, proto[key]); - }, {}); - } } }); diff --git a/src/tanks/mixins/speciated.cjs b/src/tanks/mixins/speciated.cjs index 95af71e..c7f489a 100644 --- a/src/tanks/mixins/speciated.cjs +++ b/src/tanks/mixins/speciated.cjs @@ -51,7 +51,7 @@ Mixin.subclass('Speciated', { cls.__known__ = {}; }, - // onInit : function initSpeciated(evt){ + // onCreate : function initSpeciated(evt){ // var d = evt.data // , instance = d.instance // , Species = d.cls diff --git a/src/tanks/thing/item.cjs b/src/tanks/thing/item.cjs index 6055a4f..02fbcf9 100644 --- a/src/tanks/thing/item.cjs +++ b/src/tanks/thing/item.cjs @@ -5,6 +5,7 @@ var Y = require('Y').Y , Rect = shape.Rect , map = require('tanks/map/map') +, Buff = require('tanks/effects/buff').Buff , Thing = require('tanks/thing/thing').Thing , ITEM_SIZE = REF_SIZE * 0.5 @@ -14,6 +15,9 @@ var Y = require('Y').Y Item = exports['Item'] = Thing.subclass('Item', { + // __mixins__ : [ ], + __bind__ : [ 'onCollide', 'onAcquired', 'onLost' ], + align : 0, // 0 reserved for neutral units blocking : map.ZONE, @@ -33,62 +37,76 @@ Thing.subclass('Item', { name : 'Dummy', // {String} Display name icon_inv : '', // {String} URL to inventory icon file (TODO) icon_map : '', // {String} URL to map icon file (TODO) - buffs : Y([]), // {Buff...} Passive effects (triggered on pickup) - effects : Y([]), // {Function...} Active effects (triggered on activation) - owner : null, // {Unit} Owner when picked up. + // {Buff...} Passive effects (triggered on pickup) + itemBuffs : Y([ + Buff + ]), + + // {Function...} Active effects (triggered on activation) + effects : Y([]), + + // {Unit} Owner when picked up. + owner : null, + currentBuffs : null, - // XXX: Setting these per-instance only while testing - init : function initItem(name, buffs, effects){ + init : function initItem(){ Thing.init.call(this, 0); - if (name) this.name = name; - if (buffs) - this.buffs = Y(buffs); - else - this.buffs = this.buffs.clone(); + this.itemBuffs = this.itemBuffs.clone(); + this.effects = this.effects.clone(); - if (effects) - this.effects = Y(effects); - else - this.effects = this.effects.clone(); - - this.addEventListener('collide', this.onCollide.bind(this)); + this.addEventListener('collide', this.onCollide); + this.addEventListener('item.acquire', this.onAcquired); + this.addEventListener('item.lose', this.onLost); }, tick : function tick(elapsed, now){ - + // Buffs will take care of themselves as part of the unit }, - activate : function activate(){ - if (!this.owner) return; + addToInventory : function addToInventory(unit){ + if (!unit.hasInventory) + throw new Error('Unit has no inventory! '+unit); + unit.addItem(this); + return this; }, - acquired : function acquired(unit){ - this.owner = unit; - console.log(unit+' acquired '+this+'!'); - // TODO: apply buffs - unit.fire('acquire', this, { 'unit':unit, 'item':this }); - // TODO: unit to listen, add to inventory, update UI, inform unit on activation + removeFromInventory : function removeFromInventory(){ + if (this.owner) + this.owner.removeItem(this); + return this; }, - dropped : function dropped(){ + activate : function activate(){ if (!this.owner) return; - var unit = this.owner; - this.owner = null; - console.log(unit+' dropped '+this+'!'); - unit.fire('drop', this, { 'unit':unit, 'item':this }); - // TODO: game to listen, re-add to level at unit.loc + }, + + onActivate : function onActivate(evt){ + console.log(this+' activated!'); }, onCollide : function onCollide(evt){ var unit = evt.data.unit; - - if (unit.hasInventory) { - this.destroy(); - this.acquired(unit); - } + if (unit.hasInventory) unit.addItem(this); + }, + + onAcquired : function onAcquired(evt){ + this.owner = evt.data.unit; + console.log(this.owner+' acquired '+this+'!'); + this.currentBuffs = this.itemBuffs.invoke('instantiate', this.owner); + this.destroy(); // removes map object + }, + + onLost : function onLost(evt){ + // if (!this.owner) return; + if ( this.currentBuffs ) + this.currentBuffs.invoke('die', 'item.lost'); + var unit = this.owner; + console.log(unit+' lost '+this+'!'); + this.owner = null; + // TODO: game to listen, re-add to level at unit.loc }, render : function render(parent){ diff --git a/src/tanks/thing/player.cjs b/src/tanks/thing/player.cjs index f81c604..3c7837f 100644 --- a/src/tanks/thing/player.cjs +++ b/src/tanks/thing/player.cjs @@ -4,14 +4,16 @@ var Y = require('Y').Y , map = require('tanks/map/map') , Tank = require('tanks/thing/tank').Tank +, Inventoried = require('tanks/mixins/inventoried').Inventoried , PlayerTank = exports['PlayerTank'] = Tank.subclass('PlayerTank', { + __mixins__ : [ Inventoried ], + blocking : map.BLOCKING, - hasInventory : true, bodyColor : '#E73075', turretColor : '#A72F5B', diff --git a/www/deps.html b/www/deps.html index df785cc..10dba88 100644 --- a/www/deps.html +++ b/www/deps.html @@ -45,8 +45,8 @@ - + @@ -65,17 +65,18 @@ - + - + +