Fixes issue where mixin event would fire prior to members being added to a new class.
authordsc <david.schoonover@gmail.com>
Mon, 3 Jan 2011 09:09:50 +0000 (01:09 -0800)
committerdsc <david.schoonover@gmail.com>
Mon, 3 Jan 2011 09:09:50 +0000 (01:09 -0800)
13 files changed:
src/Y/types/array.cjs
src/Y/types/collection.cjs
src/evt.cjs
src/tanks/effects/buff.cjs
src/tanks/effects/index.cjs
src/tanks/index.js
src/tanks/mixins/index.cjs
src/tanks/mixins/informative.cjs [deleted file]
src/tanks/mixins/quantified.cjs [new file with mode: 0644]
src/tanks/mixins/speciated.cjs
src/tanks/thing/tank.cjs
src/tanks/thing/thing.cjs
www/deps.html

index 9ffe267..90568f3 100644 (file)
@@ -48,20 +48,21 @@ YCollection.subclass('YArray', function(YArray){
         return this._o.length;
     };
     
+    var self = this;
     Object.defineProperty(this, 'length', { 'get':size });
     
     // Convenience getters for index 0-9
-    // var thisget   = op.thisget
-    // ,   thiskvset = op.thiskvset ;
-    // for (var i=0; i<10; i++)
-    //     Object.defineProperty(this, i, {
-    //         'get' : thisget.partial(i),
-    //         'set' : thiskvset.partial(i)
-    //     });
+    for (var i=0; i<10; i++)
+        (function (j){
+            Object.defineProperty(self, j+'', {
+                'get' : function(){  return this._o[j]; },
+                '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||[]) + "]";
     };
     
     /**
index fcb4875..7fbced3 100644 (file)
@@ -1,5 +1,5 @@
 var YBase  = require('Y/class').YBase
-// ,   type   = require('Y/type')
+,   type   = require('Y/type')
 ,   core   = require('Y/core')
 ,   del    = require('Y/delegate')
 ,   op     = require('Y/op')
@@ -144,11 +144,10 @@ YBase.subclass('YCollection', {
         });
     },
     
-    // FIXME: del.map
     'invoke' : function invoke(name){
         var args = slice.call(arguments,1);
         return del.map(this, function(o){
-            return o && o[name].apply(o, args);
+            return (o && type.isFunction(o[name])) ? o[name].apply(o, args) : null;
         });
     },
     
index f5e84ba..fbce38a 100644 (file)
@@ -46,11 +46,13 @@ var Y       = require('Y').Y
             '__bases__', '__initialise__', '__class__', 'constructor', 'className',
             '__emitter__', '__static__', '__mixins__'
         ] //.concat(classStatics)
-,   mixinSkip = [ '__mixin_skip__', '__mixin_skip', 'onMixin', 'onInit', 'init'
+,   mixinSkip = [ '__mixin_skip__', 'onMixin', 'onInit', 'init'
         ].concat(classMagic)
+,   GLOBAL_ID = 0
 ,
 
 
+
 /**
  * @private
  * Private delegating constructor. This function must be redefined for every
@@ -72,6 +74,7 @@ function ConstructorTemplate() {
     
     // Not subclassing
     if ( unwrap(cls.caller) !== Y.fabricate ) {
+        instance.id = GLOBAL_ID++;
         
         // Perform actions that should only happen once in Constructor
         instance.__emitter__ = new Emitter(instance, cls);
@@ -214,7 +217,7 @@ function Class(className, Parent, members){
     
     // Fix Constructors
     NewClass.__super__ = SuperClass; // don't override NewClass.constructor -- it should be Function
-    NewClass.__bases__ = NewClass.fn.__bases__ = [SuperClass].concat( SuperClass.__bases__ || [ Class, Object ] );
+    NewClass.__bases__ = NewClass.fn.__bases__ = Y([SuperClass]).concat( SuperClass.__bases__ || [ Class, Object ] );
     prototype.constructor = prototype.__class__ = NewClass;
     
     NewClass.init = prototype.__initialise__ = createInitialise(NewClass);
@@ -227,21 +230,6 @@ function Class(className, Parent, members){
     // Record for metaprogramming
     KNOWN_CLASSES[className] = NewClass;
     
-    // Either invoke body constructor...
-    if ( isFunction(members) ) {
-        members.call(prototype, NewClass);
-    
-    // Or add new instance methods
-    } else {
-        for (var k in members) {
-            if ( hasOwn.call(members,k) && classMagic.indexOf(k) === -1 )
-                setDesc(prototype, k, getDesc(members,k));
-        }
-        
-        // NewClass.__super__    = SuperClass;
-        // prototype.constructor = prototype.__class__ = NewClass;
-    }
-    
     var mixins   = NewClass.__mixins__ = Y([]).concat(members.__mixins__||[], SuperClass.__mixins__||[]).unique()
     ,   statics  = NewClass.__static__ = {}
     ,   pstatics = SuperClass.__static__
@@ -265,6 +253,25 @@ function Class(className, Parent, members){
             setDesc(statics, k, desc);
         }
     
+    // Either invoke body constructor...
+    if ( isFunction(members) ) {
+        // body fn is responsible for calling mixin, attaching statics, etc
+        members.call(prototype, NewClass);
+    
+    // Or add new instance methods
+    } else {
+        for (var k in members)
+            if ( hasOwn.call(members,k) && classMagic.indexOf(k) === -1 )
+                setDesc(prototype, k, getDesc(members,k));
+    }
+    
+    // Notify mixins to let them finish up any customization
+    if (mixins.length)
+        mixins.forEach(function(mxn){
+            if ( mxn && isFunction(mxn.fire) )
+                mxn.fire('mixin', NewClass, { 'mixin':mxn, 'cls':NewClass });
+        });
+    
     // Notify parent of the subclass
     ParentEmitter.fire('subclass',
         NewClass, {
@@ -284,7 +291,7 @@ Class.__super__ = Object;
 Class.__emitter__ = new Emitter(Class);
 Class.fn = Class.prototype;
 Class.fn.__class__ = Class.fn.constructor = Class;
-Class.fn.__bases__ = Class.__bases__ = [ Object ];
+Class.fn.__bases__ = Class.__bases__ = Y([ Object ]);
 Class.className = Class.fn.className = "Class";
 
 /* Class Methods */
@@ -333,11 +340,6 @@ new Class('Mixin', Class, {
      */
     __mixin_skip__ : mixinSkip,
     
-    __mixin_skip : function __mixin_skip(k){
-        var skip = this.__mixin_skip__;
-        return !hasOwn.call(this,k) || (skip && skip.indexOf(k) !== -1);
-    },
-    
     __static__ : {
         mixInto : function mixIntoClass(cls){
             var mxn = this;
@@ -347,6 +349,10 @@ new Class('Mixin', Class, {
     
 });
 
+function mixinFilter(v, k){
+    var skip = this.__mixin_skip__ || mixinSkip;
+    return hasOwn.call(this,k) && !(skip && skip.indexOf(k) !== -1);
+}
 
 /**
  * Mixes a Mixin into another Class.
@@ -368,7 +374,7 @@ function mixin(cls, _mxn){
         ,   onInit  = mproto.onInit
         ;
         
-        core.extend(proto, core.filter(mproto, mproto.__mixin_skip, mproto));
+        core.extend(proto, core.filter(mproto, mixinFilter, mproto));
         
         if (bases && bases.indexOf(mxn) === -1)
             bases.unshift(mxn);
@@ -387,8 +393,8 @@ function mixin(cls, _mxn){
         if ( hasOwn.call(mproto,'onInit') && isFunction(onInit) )
             cls.addEventListener('init', onInit);
         
-        // Fire the mixin event on this Mixin
-        if ( isFunction(mxn.fire) )
+        // Fire the mixin event on this Mixin only if we're done constructing the class
+        if ( isFunction(mxn.fire) && mixin.caller !== Class )
             mxn.fire('mixin', cls, { 'mixin':mxn, 'cls':cls });
     });
     return cls;
@@ -406,7 +412,7 @@ Mixin.addEventListener('subclass',
         if ( isFunction(onMixin) )
             mxn.addEventListener('mixin', onMixin);
         
-        console.log('Mixin.subclass()', mxn, '<', d.parent, 'onMixin:', onMixin);
+        // console.log('Mixin.subclass()', mxn, '<', d.parent, 'onMixin:', onMixin);
     });
 
 
index 8baf546..99bc4bd 100644 (file)
@@ -2,55 +2,85 @@ var Y = require('Y').Y
 ,   evt = require('evt')
 ,   mul = Y.op.curried.mul
 
-,   Speciated = require('tanks/mixins/speciated').Speciated
-,   Meronomic = require('tanks/mixins/meronomic').Meronomic
+,   Speciated  = require('tanks/mixins/speciated').Speciated
+,   Meronomic  = require('tanks/mixins/meronomic').Meronomic
+,   Quantified = require('tanks/mixins/quantified').Quantified
 ,
 
 
 // Events:
-//  - live.{affect,stack}
+//  - live.affect // stacking will be done with copies and collapsed in the UI
 //  - die.{expire,dismiss,dispel}
 Buff =
 exports['Buff'] =
 evt.subclass('Buff', {
-    __mixins__: [ Speciated, Meronomic ], // Configurable, Usable
+    __mixins__: [ Speciated, Meronomic, Quantified ], // Configurable, Usable
     
     
     /// Configurable ///
-    priority  : 0, // Order of stat and effect application (lower is earlier)
+    priority  : 0, // Order of stat and effect application (lower is earlier) // TODO
     stats : {
-        timeout : -1,
-        stacked : 1,
-        threat  : 1
+        timeout : Infinity,
+        threat  : 1   // TODO
     },
-    // {stat : Number|Function} Modifications to stats
+    
     // TODO: functions
+    // {stat : Number|Function} Modifications to stats
     stat_mods : {
         move : 2
     },
-    // effects   : [], // {Function...} Effects to trigger on affect // XXX: not sure why i need this...
-    // triggers  : {}, // {event : Effect} // maybe
+    effects   : [], // {Function...} Effects to trigger on affect // XXX: not sure why i need this...
+    triggers  : {}, // {event : Effect} // maybe
     
     /// Instance ///
-    owner : null, // Agent which originated the buff
-    target : null, // Agent to which the buff applies
+    owner   : null, // Agent which originated the buff
+    target  : null, // Agent to which the buff applies
+    game    : null,
+    created : 0,
     
     
-    
-    init : function initBuff(owner, target){
+    init : function initBuff(target, owner){
         this.owner = owner;
         this.game = owner.game;
         this.target = target;
+        
+        this.createStats()
+        // this.createCooldowns();
+        
         this.created = this.game.NOW;
-        // XXX: Speciated mixin must clone objects
+        this.expires = this.created + this.stats.timeout.val;
+        
+        this.applyStatMods();
+        this.target.fire('buff.live.acquire', this, { 'buff':this, 'unit':target, 'owner':owner });
     },
     
     tick : function tick(elapsed, now){
+        // this.updateCooldowns();
         
+        if (now < this.expires) {
+            // this.remove();
+            this.die('expire');
+        }
+        
+        return this;
+    },
+    
+    die : function die(reason){
+        this.removeStatMods();
+        this.target.fire('buff.die.'+reason, this, { 'buff':this, 'unit':target, 'owner':owner });
+        return this;
+    },
+    
+    dismiss : function dismiss(){
+        return this.die('dismiss');
+    },
+    
+    dispel : function dispel(){
+        return this.die('dispel');
     },
     
     applyStatMods : function applyStatMods(modifier){
-        modifier = modifier || 1;
+        modifier = (modifier || 1);
         Y(this.stat_mods).map(mul(modifier)).forEach(this._applyStatMod, this);
         return this;
     },
@@ -58,18 +88,14 @@ evt.subclass('Buff', {
     _applyStatMod : function _applyStatMod(mod, name){
         var keyparts = name.split('_')
         ,   key  = keyparts[0]
-        ,   part = keyparts[1] || 'max'
-        ;
+        ,   part = keyparts[1] || 'max' ;
         this.target.stats[key].modifyPart(part, mod);
-        return this;
     },
     
     removeStatMods : function removeStatMods(){
         return this.applyStatMods(-1);
     }
     
-    // XXX: toString implemented by Informative
-    
 })
 
 
index e69de29..f21e3c8 100644 (file)
@@ -0,0 +1,9 @@
+var stat = require('tanks/effects/stat');
+
+require('Y').Y.core
+.extend(exports, {
+    'Buff' : require('tanks/effects/buff').Buff,
+    
+    'Stat' : stat.Stat,
+    'createStats' : stat.createStats
+});
\ No newline at end of file
index b5e13a5..d36bad7 100644 (file)
@@ -9,12 +9,17 @@ require('tanks/globals');
 
 // exports.tanks =
 tanks = {
-    'config' : require('tanks/config').config,
-    'ui'     : require('tanks/ui'),
-    'mixins' : require('tanks/mixins'),
-    'Game'   : require('tanks/game').Game,
+    'config'    : require('tanks/config').config,
+    'constants' : require('tanks/constants'),
+    'effects'   : require('tanks/effects'),
+    'fx'        : require('tanks/fx'),
+    'mixins'    : require('tanks/mixins'),
+    'map'       : require('tanks/map'),
+    'thing'     : require('tanks/thing'),
+    'ui'        : require('tanks/ui'),
     
-    'game' : null,
+    'Game'      : require('tanks/game').Game,
+    'game'      : null
     
 };
 
index da70058..e7384ef 100644 (file)
@@ -1,4 +1,6 @@
-require('Y').Y.extend(exports, {
-    'Speciated' : require('tanks/mixins/speciated').Speciated,
-    'Meronomic' : require('tanks/mixins/meronomic').Meronomic
+require('Y').Y.core
+.extend(exports, {
+    'Speciated'  : require('tanks/mixins/speciated').Speciated,
+    'Meronomic'  : require('tanks/mixins/meronomic').Meronomic,
+    'Quantified' : require('tanks/mixins/quantified').Quantified
 });
diff --git a/src/tanks/mixins/informative.cjs b/src/tanks/mixins/informative.cjs
deleted file mode 100644 (file)
index d3e2268..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-var Y = require('Y').Y
-,   Mixin = require('evt').Mixin
-,
-
-Informative =
-exports['Informative'] =
-Mixin.subclass('Informative', {
-    
-    
-    onInit : function initInformative(evt){
-        var instance = evt.data.instance;
-        
-    },
-    
-    toString : function(){
-        return this.className+'()';
-    }
-    
-});
diff --git a/src/tanks/mixins/quantified.cjs b/src/tanks/mixins/quantified.cjs
new file mode 100644 (file)
index 0000000..26fdf98
--- /dev/null
@@ -0,0 +1,78 @@
+var Y = require('Y').Y
+,   Mixin    = require('evt').Mixin
+,   Cooldown = require('ezl/loop').Cooldown
+,   stat     = require('tanks/effects/stat')
+,
+
+
+Quantified =
+exports['Quantified'] =
+Mixin.subclass('Quantified', {
+    
+    stats : {},
+    cooldowns : {},
+    ai    : {},
+    
+    
+    stat : function stat(k){
+        var s = this.stats[k];
+        return s ? s.val : s;
+    },
+    
+    createStats : function createStats(){
+        this.stats = stat.createStats(this.stats);
+    },
+    
+    createCooldowns : function createCooldowns(){
+        this._cooldowns = Y({
+            'attack': new Cooldown(1000 * this.stats.speed.val)
+        });
+        this._ai = Y(this.ai).map(function(freq, k){
+            return new Cooldown(1000 * freq);
+        });
+        
+        this.cooldowns = this._cooldowns.end();
+        this.ai = this._ai.end();
+    },
+    
+    updateCooldowns : function updateCooldowns(elapsed, now){
+        this._cooldowns.invoke('tick', elapsed, now);
+        this._ai.invoke('tick', elapsed, now);
+        return this;
+    },
+    
+    
+    // onInit : function initQuantified(evt){
+    //     var self = evt.data.instance;
+    //     self.createStats();
+    //     self.createCooldowns();
+    // },
+    
+    onMixin : function onMixin(evt){
+        var cls = evt.data.cls
+        ,   mxn = evt.data.mixin
+        ,   proto = cls.fn ;
+        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]);
+                }, {});
+        }
+    }
+    
+});
index bb183cd..95af71e 100644 (file)
@@ -5,34 +5,42 @@ var Y = require('Y').Y
 Speciated =
 exports['Speciated'] =
 Mixin.subclass('Speciated', {
-    
-    lol : 1,
+    name : "",
+    desc : "",
+    tags : [],
     
     __static__ : {
-        __known__ : null,
+        id2name : function id2name(id){
+            var name = id+'';
+            // TODO: all YString methods to return YString :P But it'll proally break shit
+            name = Y(name).capitalize();
+            name = Y(name).snakeToCamel();
+            return name;
+        },
         
         speciate : function speciate(id, props){
+            if ( this.__known__[id] )
+                throw new Error('A species of '+this.className+' already exists with the id '+id+'!');
+            
             props = props || {};
             props.__species__ = id;
-            delete props.id;
+            delete props.id; // reserved for instance id
             
             var cls = this
-            ,   speciesName = Y(Y(id+'').capitalize()).snakeToCamel()
-            ,   Species = cls.__known__[id] = cls.subclass(speciesName, props)
+            ,   speciesName = this.id2name(id)
+            ,   Species = this.__known__[id] = cls.subclass(speciesName, props)
             ;
             Species.__species_props__ = props;
             return Species;
         },
         
         lookupSpecies : function lookupSpecies(id){
-            var cls = this;
-            return cls.__known__[id];
+            return this.__known__[id];
         },
         
         createSpeciesInstance : function createSpeciesInstance(id){
             var args = Y(arguments,1)
-            ,   cls = this
-            ,   Species = cls.__known__[id];
+            ,   Species = this.__known__[id];
             return Species.instantiate.apply(Species, args);
         }
         
@@ -41,7 +49,6 @@ Mixin.subclass('Speciated', {
     onMixin : function mixSpeciated(evt){
         var cls = evt.data.cls;
         cls.__known__ = {};
-        console.log('mixSpeciated()', 'cls:', cls, 'mxn:', evt.data.mixin);
     },
     
     // onInit : function initSpeciated(evt){
index bc9199d..9a6094c 100644 (file)
@@ -38,9 +38,8 @@ Thing.subclass('Tank', function(Tank){
         
         // Attributes
         stats : {
-            hp        : 2,          // health
+            hp        : 1,          // health
             move      : 0.75,       // move speed (squares/sec)
-            rotate    : HALF_PI,    // rotation speed (radians/sec)
             power     : 1,          // attack power
             speed     : 0.5,        // attack cool (sec)
             shots     : 4           // max projectiles in the air at once
index ad36be2..d49794d 100644 (file)
@@ -11,50 +11,33 @@ var Y           = require('Y').Y
 ,   config      = require('tanks/config').config
 ,   map         = require('tanks/map/map')
 ,   stat        = require('tanks/effects/stat')
-
-,   THING_ID = 0
+,   Quantified  = require('tanks/mixins/quantified').Quantified
 ,
 
 
-fillStats =
-exports['fillStats'] =
-function fillStats(stats){
-    return Y.reduce(stats, function(_stats, v, k){
-        _stats[k] = v;
-        
-        k = Y(k);
-        var k_     = k.rtrim('_max')
-        ,   k_max  = k_+'_max'
-        ,   k_base = k_+'_base'
-        ;
-        if ( _stats[k_] === undefined )
-            _stats[k_] = v;
-            
-        if ( _stats[k_max] === undefined )
-            _stats[k_max] = v;
-        
-        if ( _stats[k_base] === undefined )
-            _stats[k_base] = _stats[k_max];
-        
-        return _stats;
-    }, {});
-}
-,
-
 
 Thing =
-exports['Thing'] = new evt.Class('Thing', {
+exports['Thing'] =
+new evt.Class('Thing', {
+    __mixins__ : [ Quantified ],
+    
     // Config
     showAttackCooldown : null,
     
     // Attributes
     stats: {
+        // rotate    : HALF_PI,    // rotation speed (radians/sec) [UNUSED]
         hp        : 1,          // health
         move      : 1.0,        // move speed (squares/sec)
-        rotate    : HALF_PI,    // rotation speed (radians/sec) [UNUSED]
         power     : 1,          // attack power
         speed     : 0.5,        // attack cool (sec)
-        shots     : 5           // max projectiles in the air at once
+        shots     : 5,          // max projectiles in the air at once
+        sight     : 5,          // distance this unit can see (squares)
+        accuracy  : 1.0
+    },
+    
+    cooldowns : {
+        attack: 'stats.speed'
     },
     
     // AI "Cooldowns" (max frequency of each action per sec)
@@ -100,7 +83,6 @@ exports['Thing'] = new evt.Class('Thing', {
     
     
     init : function init(align){
-        this.id = THING_ID++;
         this.align = align || 0;
         this.createBoundingBox();
         this.createStats();
@@ -112,34 +94,6 @@ exports['Thing'] = new evt.Class('Thing', {
         this.bbox = new BoundingBox(0,0, this.width,this.height, this.originX,this.originY);
     },
     
-    stat : function stat(k){
-        var s = this.stats[k];
-        return s ? s.val : s;
-    },
-    
-    createStats : function createStats(){
-        this.stats = stat.createStats(this.stats);
-    },
-    
-    createCooldowns : function createCooldowns(){
-        this._cooldowns = Y({
-            'attack': new Cooldown(1000 * this.stats.speed.val)
-        });
-        this._ai = Y(this.ai).map(function(freq, k){
-            return new Cooldown(1000 * freq);
-        });
-        
-        this.cooldowns = this._cooldowns.end();
-        this.ai = this._ai.end();
-    },
-    
-    updateCooldowns : function updateCooldowns(elapsed, now){
-        this._cooldowns.invoke('tick', elapsed, now);
-        this._ai.invoke('tick', elapsed, now);
-        return this;
-    },
-    
-    
     position : function position(x,y){
         if (x === undefined && y === undefined)
             return this.loc;
index 6ebf6f0..df785cc 100644 (file)
 <script src="build/tanks/globals.js" type="text/javascript"></script>
 <script src="build/Y/modules/y.kv.js" type="text/javascript"></script>
 <script src="build/ezl/util/tree/binaryheap.js" type="text/javascript"></script>
-<script src="build/ezl/util/tree/quadtree.js" type="text/javascript"></script>
-<script src="build/tanks/effects/buff.js" type="text/javascript"></script>
 <script src="build/tanks/constants.js" type="text/javascript"></script>
+<script src="build/ezl/util/tree/quadtree.js" type="text/javascript"></script>
 <script src="build/Y/modules/y.scaffold.js" type="text/javascript"></script>
 <script src="build/Y/modules/y.config.js" type="text/javascript"></script>
-<script src="build/Y/modules/y.cookies.js" type="text/javascript"></script>
-<script src="build/tanks/map/map.js" type="text/javascript"></script>
 <script src="build/tanks/effects/stat.js" type="text/javascript"></script>
+<script src="build/tanks/map/map.js" type="text/javascript"></script>
+<script src="build/Y/modules/y.cookies.js" type="text/javascript"></script>
 <script src="build/tanks/mixins/speciated.js" type="text/javascript"></script>
 <script src="build/tanks/mixins/meronomic.js" type="text/javascript"></script>
 <script src="build/tanks/map/traversal.js" type="text/javascript"></script>
-<script src="build/tanks/mixins.js" type="text/javascript"></script>
 <script src="build/tanks/config.js" type="text/javascript"></script>
 <script src="build/tanks/map/pathmap.js" type="text/javascript"></script>
 <script src="build/tanks/ui/configui.js" type="text/javascript"></script>
+<script src="build/tanks/mixins/quantified.js" type="text/javascript"></script>
 <script src="build/tanks/thing/thing.js" type="text/javascript"></script>
+<script src="build/tanks/effects/buff.js" type="text/javascript"></script>
+<script src="build/tanks/mixins.js" type="text/javascript"></script>
 <script src="build/tanks/map/trajectory.js" type="text/javascript"></script>
+<script src="build/tanks/effects.js" type="text/javascript"></script>
 <script src="build/tanks/ui/pathmapui.js" type="text/javascript"></script>
 <script src="build/tanks/thing/wall.js" type="text/javascript"></script>
 <script src="build/tanks/thing/item.js" type="text/javascript"></script>
 <script src="build/tanks/ui/grid.js" type="text/javascript"></script>
 <script src="build/tanks/fx/explosion.js" type="text/javascript"></script>
 <script src="build/tanks/thing/bullet.js" type="text/javascript"></script>
+<script src="build/tanks/fx.js" type="text/javascript"></script>
 <script src="build/tanks/thing/tank.js" type="text/javascript"></script>
 <script src="build/tanks/thing/player.js" type="text/javascript"></script>
 <script src="build/tanks/map/level.js" type="text/javascript"></script>