=0)&&this.name.error('parameter name "'+e+'" is not allowed')}return Et(t,e),t.prototype.children=["name","value"],t.prototype.compileToFragments=function(e){return this.name.compileToFragments(e,N)},t.prototype.asReference=function(e){var t;return this.reference?this.reference:(t=this.name,t["this"]?(t=t.properties[0].name,t.value.reserved&&(t=new A(e.scope.freeVariable(t.value)))):t.isComplex()&&(t=new A(e.scope.freeVariable("arg"))),t=new G(t),this.splat&&(t=new W(t)),this.reference=t)},t.prototype.isComplex=function(){return this.name.isComplex()},t.prototype.eachName=function(e,t){var n,r,i,o,u,a;t==null&&(t=this.name),n=function(t){var n;n=t.properties[0].name;if(!n.value.reserved)return e(n.value,n)};if(t instanceof A)return e(t.value,t);if(t instanceof G)return n(t);a=t.objects;for(o=0,u=a.length;o=n.length)return[];if(n.length===1)return c=n[0],a=c.compileToFragments(e,N),r?a:[].concat(c.makeCode(""+vt("slice")+".call("),a,c.makeCode(")"));i=n.slice(l);for(f=h=0,p=i.length;h1?n.expressions.unshift(new w((new B(this.guard)).invert(),new A("continue"))):this.guard&&(n=u.wrap([new w(this.guard,n)]))),n=[].concat(this.makeCode("\n"),n.compileToFragments(e,L),this.makeCode("\n"+this.tab))),t=[].concat(this.makeCode(i+this.tab+"while ("),this.condition.compileToFragments(e,k),this.makeCode(") {"),n,this.makeCode("}")),this.returns&&t.push(this.makeCode("\n"+this.tab+"return "+r+";")),t},t}(o),t.Op=P=function(e){function t(e,t,r,i){if(e==="in")return new E(t,r);if(e==="do")return this.generateDo(t);if(e==="new"){if(t instanceof a&&!t["do"]&&!t.isNew)return t.newInstance();if(t instanceof c&&t.bound||t["do"])t=new B(t)}return this.operator=n[e]||e,this.first=t,this.second=r,this.flip=!!i,this}var n,r;return Et(t,e),n={"==":"===","!=":"!==",of:"in"},r={"!==":"===","===":"!=="},t.prototype.children=["first","second"],t.prototype.isSimpleNumber=_,t.prototype.isUnary=function(){return!this.second},t.prototype.isComplex=function(){var e;return!this.isUnary()||(e=this.operator)!=="+"&&e!=="-"||this.first.isComplex()},t.prototype.isChainable=function(){var e;return(e=this.operator)==="<"||e===">"||e===">="||e==="<="||e==="==="||e==="!=="},t.prototype.invert=function(){var e,n,i,s,o;if(this.isChainable()&&this.first.isChainable()){e=!0,n=this;while(n&&n.operator)e&&(e=n.operator in r),n=n.first;if(!e)return(new B(this)).invert();n=this;while(n&&n.operator)n.invert=!n.invert,n.operator=r[n.operator],n=n.first;return this}return(s=r[this.operator])?(this.operator=s,this.first.unwrap()instanceof t&&this.first.invert(),this):this.second?(new B(this)).invert():this.operator==="!"&&(i=this.first.unwrap())instanceof t&&((o=i.operator)==="!"||o==="in"||o==="instanceof")?i:new t("!",this)},t.prototype.unfoldSoak=function(e){var t;return((t=this.operator)==="++"||t==="--"||t==="delete")&&dt(e,this,"first")},t.prototype.generateDo=function(e){var t,n,r,i,o,u,f,l;i=[],n=e instanceof s&&(o=e.value.unwrap())instanceof c?o:e,l=n.params||[];for(u=0,f=l.length;u=0)&&this.error('cannot increment/decrement "'+this.first.unwrapAll().value+'"'),this.isUnary()?this.compileUnary(e):n?this.compileChain(e):this.operator==="?"?this.compileExistence(e):(t=[].concat(this.first.compileToFragments(e,C),this.makeCode(" "+this.operator+" "),this.second.compileToFragments(e,C)),e.level<=C?t:this.wrapInBraces(t))},t.prototype.compileChain=function(e){var t,n,r,i;return i=this.first.second.cache(e),this.first.second=i[0],r=i[1],n=this.first.compileToFragments(e,C),t=n.concat(this.makeCode(" "+(this.invert?"&&":"||")+" "),r.compileToFragments(e),this.makeCode(" "+this.operator+" "),this.second.compileToFragments(e,C)),this.wrapInBraces(t)},t.prototype.compileExistence=function(e){var t,n;return!e.isExistentialEquals&&this.first.isComplex()?(n=new A(e.scope.freeVariable("ref")),t=new B(new s(n,this.first))):(t=this.first,n=t),(new w(new d(t),n,{type:"if"})).addElse(this.second).compileToFragments(e)},t.prototype.compileUnary=function(e){var n,r,i;r=[],n=this.operator,r.push([this.makeCode(n)]);if(n==="!"&&this.first instanceof d)return this.first.negated=!this.first.negated,this.first.compileToFragments(e);if(e.level>=x)return(new B(this)).compileToFragments(e);i=n==="+"||n==="-",(n==="new"||n==="typeof"||n==="delete"||i&&this.first instanceof t&&this.first.operator===n)&&r.push([this.makeCode(" ")]);if(i&&this.first instanceof t||n==="new"&&this.first.isStatement(e))this.first=new B(this.first);return r.push(this.first.compileToFragments(e,C)),this.flip&&r.reverse(),this.joinFragmentArrays(r,"")},t.prototype.toString=function(e){return t.__super__.toString.call(this,e,this.constructor.name+" "+this.operator)},t}(o),t.In=E=function(e){function t(e,t){this.object=e,this.array=t}return Et(t,e),t.prototype.children=["object","array"],t.prototype.invert=M,t.prototype.compileNode=function(e){var t,n,r,i,s;if(this.array instanceof G&&this.array.isArray()){s=this.array.base.objects;for(r=0,i=s.length;r= 0"))),ot(r)===ot(n)?t:(t=r.concat(this.makeCode(", "),t),e.level= 0",this.step?(j?c&&(r=i,o=a):(r=""+F+" > 0 ? "+r+" : "+i,o="("+F+" > 0 ? ("+o+") : "+a+")"),v=""+y+" += "+F):v=""+(b!==y?"++"+y:""+y+"++"),h=[this.makeCode(""+o+"; "+r+"; "+E+v)])),this.returns&&(O=""+this.tab+_+" = [];\n",M="\n"+this.tab+"return "+_+";",t.makeReturn(_)),this.guard&&(t.expressions.length>1?t.expressions.unshift(new w((new B(this.guard)).invert(),new A("continue"))):this.guard&&(t=u.wrap([new w(this.guard,t)]))),this.pattern&&t.expressions.unshift(new s(this.name,new A(""+R+"["+b+"]"))),l=[].concat(this.makeCode(f),this.pluckDirectCall(e,t)),C&&(U="\n"+d+C+";"),this.object&&(h=[this.makeCode(""+b+" in "+R)],this.own&&(p="\n"+d+"if (!"+vt("hasProp")+".call("+R+", "+b+")) continue;")),n=t.compileToFragments(ft(e,{indent:d}),L),n&&n.length>0&&(n=[].concat(this.makeCode("\n"),n,this.makeCode("\n"))),[].concat(l,this.makeCode(""+(O||"")+this.tab+"for ("),h,this.makeCode(") {"+p+U),n,this.makeCode(""+this.tab+"}"+(M||"")))},t.prototype.pluckDirectCall=function(e,t){var n,r,i,o,u,f,l,h,p,d,v,m,g,y,b;r=[],d=t.expressions;for(u=h=0,p=d.length;h0&&(o=o.concat(n,this.makeCode("\n")));if(u===this.cases.length-1&&!this.otherwise)break;s=this.lastNonComment(t.expressions);if(s instanceof I||s instanceof A&&s.jumps()&&s.value!=="debugger")continue;o.push(r.makeCode(f+"break;\n"))}return this.otherwise&&this.otherwise.expressions.length&&o.push.apply(o,[this.makeCode(a+"default:\n")].concat(xt.call(this.otherwise.compileToFragments(e,L)),[this.makeCode("\n")])),o.push(this.makeCode(this.tab+"}")),o},t}(o),t.If=w=function(e){function t(e,t,n){this.body=t,n==null&&(n={}),this.condition=n.type==="unless"?e.invert():e,this.elseBody=null,this.isChain=!1,this.soak=n.soak}return Et(t,e),t.prototype.children=["condition","body","elseBody"],t.prototype.bodyNode=function(){var e;return(e=this.body)!=null?e.unwrap():void 0},t.prototype.elseBodyNode=function(){var e;return(e=this.elseBody)!=null?e.unwrap():void 0},t.prototype.addElse=function(e){return this.isChain?this.elseBodyNode().addElse(e):(this.isChain=e instanceof t,this.elseBody=this.ensureBlock(e),this.elseBody.updateLocationDataIfMissing(e.locationData)),this},t.prototype.isStatement=function(e){var t;return(e!=null?e.level:void 0)===L||this.bodyNode().isStatement(e)||((t=this.elseBodyNode())!=null?t.isStatement(e):void 0)},t.prototype.jumps=function(e){var t;return this.body.jumps(e)||((t=this.elseBody)!=null?t.jumps(e):void 0)},t.prototype.compileNode=function(e){return this.isStatement(e)?this.compileStatement(e):this.compileExpression(e)},t.prototype.makeReturn=function(e){return e&&(this.elseBody||(this.elseBody=new u([new A("void 0")]))),this.body&&(this.body=new u([this.body.makeReturn(e)])),this.elseBody&&(this.elseBody=new u([this.elseBody.makeReturn(e)])),this},t.prototype.ensureBlock=function(e){return e instanceof u?e:new u([e])},t.prototype.compileStatement=function(e){var n,r,i,s,o,u,a;return i=nt(e,"chainChild"),o=nt(e,"isExistentialEquals"),o?(new t(this.condition.invert(),this.elseBodyNode(),{type:"if"})).compileToFragments(e):(a=e.indent+V,s=this.condition.compileToFragments(e,k),r=this.ensureBlock(this.body).compileToFragments(ft(e,{indent:a})),u=[].concat(this.makeCode("if ("),s,this.makeCode(") {\n"),r,this.makeCode("\n"+this.tab+"}")),i||u.unshift(this.makeCode(this.tab)),this.elseBody?(n=u.concat(this.makeCode(" else ")),this.isChain?(e.chainChild=!0,n=n.concat(this.elseBody.unwrap().compileToFragments(e,L))):n=n.concat(this.makeCode("{\n"),this.elseBody.compileToFragments(ft(e,{indent:a}),L),this.makeCode("\n"+this.tab+"}")),n):u)},t.prototype.compileExpression=function(e){var t,n,r,i;return r=this.condition.compileToFragments(e,T),n=this.bodyNode().compileToFragments(e,N),t=this.elseBodyNode()?this.elseBodyNode().compileToFragments(e,N):[this.makeCode("void 0")],i=r.concat(this.makeCode(" ? "),n,this.makeCode(" : "),t),e.level>=T?this.wrapInBraces(i):i},t.prototype.unfoldSoak=function(){return this.soak&&this},t}(o),l={wrap:function(e,t,n){var i,s,o,f,l;if(e.jumps())return e;f=new c([],u.wrap([e])),i=[],s=e.contains(this.isLiteralArguments),s&&e.classBody&&s.error("Class bodies shouldn't reference arguments");if(s||e.contains(this.isLiteralThis))l=new A(s?"apply":"call"),i=[new A("this")],s&&i.push(new A("arguments")),f=new G(f,[new r(l)]);return f.noReturn=n,o=new a(f,i),t?u.wrap([o]):o},isLiteralArguments:function(e){return e instanceof A&&e.value==="arguments"&&!e.asKey},isLiteralThis:function(e){return e instanceof A&&e.value==="this"&&!e.asKey||e instanceof c&&e.bound||e instanceof a&&e.isSuper}},dt=function(e,t,n){var r;if(!(r=t[n].unfoldSoak(e)))return;return t[n]=r.body,r.body=new G(t),r},Q={"extends":function(){return"function(child, parent) { for (var key in parent) { if ("+vt("hasProp")+".call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }"},bind:function(){return"function(fn, me){ return function(){ return fn.apply(me, arguments); }; }"},indexOf:function(){return"[].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }"},hasProp:function(){return"{}.hasOwnProperty"},slice:function(){return"[].slice"}},L=1,k=2,N=3,T=4,C=5,x=6,V=" ",y="[$A-Za-z_\\x7f-\\uffff][$\\w\\x7f-\\uffff]*",g=RegExp("^"+y+"$"),q=/^[+-]?\d+$/,O=RegExp("^(?:("+y+")\\.prototype(?:\\.("+y+")|\\[(\"(?:[^\\\\\"\\r\\n]|\\\\.)*\"|'(?:[^\\\\'\\r\\n]|\\\\.)*')\\]|\\[(0x[\\da-fA-F]+|\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)\\]))|("+y+")$"),b=/^['"]/,vt=function(e){var t;return t="__"+e,U.root.assign(t,Q[e]()),t},lt=function(e,t){return e=e.replace(/\n/g,"$&"+t),e.replace(/\s+$/,"")}}),ace.define("ace/mode/coffee/scope",["require","exports","module","ace/mode/coffee/helpers"],function(e,t,n){var r,i,s,o;o=e("./helpers"),i=o.extend,s=o.last,t.Scope=r=function(){function e(t,n,r){this.parent=t,this.expressions=n,this.method=r,this.variables=[{name:"arguments",type:"arguments"}],this.positions={},this.parent||(e.root=this)}return e.root=null,e.prototype.add=function(e,t,n){return this.shared&&!n?this.parent.add(e,t,n):Object.prototype.hasOwnProperty.call(this.positions,e)?this.variables[this.positions[e]].type=t:this.positions[e]=this.variables.push({name:e,type:t})-1},e.prototype.namedMethod=function(){var e;return((e=this.method)!=null?e.name:void 0)||!this.parent?this.method:this.parent.namedMethod()},e.prototype.find=function(e){return this.check(e)?!0:(this.add(e,"var"),!1)},e.prototype.parameter=function(e){if(this.shared&&this.parent.check(e,!0))return;return this.add(e,"param")},e.prototype.check=function(e){var t;return!!(this.type(e)||((t=this.parent)!=null?t.check(e):void 0))},e.prototype.temporary=function(e,t){return e.length>1?"_"+e+(t>1?t-1:""):"_"+(t+parseInt(e,36)).toString(36).replace(/\d/g,"a")},e.prototype.type=function(e){var t,n,r,i;i=this.variables;for(n=0,r=i.length;n0||-1)*Math.floor(Math.abs(e))),e}function o(e){var t=typeof e;return e===null||t==="undefined"||t==="boolean"||t==="number"||t==="string"}function u(e){var t,n,r;if(o(e))return e;n=e.valueOf;if(typeof n=="function"){t=n.call(e);if(o(t))return t}r=e.toString;if(typeof r=="function"){t=r.call(e);if(o(t))return t}throw new TypeError}Function.prototype.bind||(Function.prototype.bind=function(e){var t=this;if(typeof t!="function")throw new TypeError("Function.prototype.bind called on incompatible "+t);var n=c.call(arguments,1),i=function(){if(this instanceof i){var r=t.apply(this,n.concat(c.call(arguments)));return Object(r)===r?r:this}return t.apply(e,n.concat(c.call(arguments)))};return t.prototype&&(r.prototype=t.prototype,i.prototype=new r,r.prototype=null),i});var a=Function.prototype.call,f=Array.prototype,l=Object.prototype,c=f.slice,h=a.bind(l.toString),p=a.bind(l.hasOwnProperty),d,v,m,g,y;if(y=p(l,"__defineGetter__"))d=a.bind(l.__defineGetter__),v=a.bind(l.__defineSetter__),m=a.bind(l.__lookupGetter__),g=a.bind(l.__lookupSetter__);if([1,2].splice(0).length!=2)if(!function(){function e(e){var t=new Array(e+2);return t[0]=t[1]=0,t}var t=[],n;t.splice.apply(t,e(20)),t.splice.apply(t,e(26)),n=t.length,t.splice(5,0,"XXX"),n+1==t.length;if(n+1==t.length)return!0}())Array.prototype.splice=function(e,t){var n=this.length;e>0?e>n&&(e=n):e==void 0?e=0:e<0&&(e=Math.max(n+e,0)),e+tu)for(h=f;h--;)this[a+h]=this[u+h];if(s&&e===l)this.length=l,this.push.apply(this,i);else{this.length=l+s;for(h=0;h>>0;if(h(e)!="[object Function]")throw new TypeError;while(++i>>0,i=Array(r),s=arguments[1];if(h(e)!="[object Function]")throw new TypeError(e+" is not a function");for(var o=0;o>>0,i=[],s,o=arguments[1];if(h(e)!="[object Function]")throw new TypeError(e+" is not a function");for(var u=0;u>>0,i=arguments[1];if(h(e)!="[object Function]")throw new TypeError(e+" is not a function");for(var s=0;s>>0,i=arguments[1];if(h(e)!="[object Function]")throw new TypeError(e+" is not a function");for(var s=0;s>>0;if(h(e)!="[object Function]")throw new TypeError(e+" is not a function");if(!r&&arguments.length==1)throw new TypeError("reduce of empty array with no initial value");var i=0,s;if(arguments.length>=2)s=arguments[1];else do{if(i in n){s=n[i++];break}if(++i>=r)throw new TypeError("reduce of empty array with no initial value")}while(!0);for(;i>>0;if(h(e)!="[object Function]")throw new TypeError(e+" is not a function");if(!r&&arguments.length==1)throw new TypeError("reduceRight of empty array with no initial value");var i,s=r-1;if(arguments.length>=2)i=arguments[1];else do{if(s in n){i=n[s--];break}if(--s<0)throw new TypeError("reduceRight of empty array with no initial value")}while(!0);do s in this&&(i=e.call(void 0,i,n[s],s,t));while(s--);return i});if(!Array.prototype.indexOf||[0,1].indexOf(1,2)!=-1)Array.prototype.indexOf=function(e){var t=E&&h(this)=="[object String]"?this.split(""):F(this),n=t.length>>>0;if(!n)return-1;var r=0;arguments.length>1&&(r=s(arguments[1])),r=r>=0?r:Math.max(0,n+r);for(;r>>0;if(!n)return-1;var r=n-1;arguments.length>1&&(r=Math.min(r,s(arguments[1]))),r=r>=0?r:n-Math.abs(r);for(;r>=0;r--)if(r in t&&e===t[r])return r;return-1};Object.getPrototypeOf||(Object.getPrototypeOf=function(e){return e.__proto__||(e.constructor?e.constructor.prototype:l)});if(!Object.getOwnPropertyDescriptor){var S="Object.getOwnPropertyDescriptor called on a non-object: ";Object.getOwnPropertyDescriptor=function(e,t){if(typeof e!="object"&&typeof e!="function"||e===null)throw new TypeError(S+e);if(!p(e,t))return;var n,r,i;n={enumerable:!0,configurable:!0};if(y){var s=e.__proto__;e.__proto__=l;var r=m(e,t),i=g(e,t);e.__proto__=s;if(r||i)return r&&(n.get=r),i&&(n.set=i),n}return n.value=e[t],n}}Object.getOwnPropertyNames||(Object.getOwnPropertyNames=function(e){return Object.keys(e)});if(!Object.create){var x;Object.prototype.__proto__===null?x=function(){return{__proto__:null}}:x=function(){var e={};for(var t in e)e[t]=null;return e.constructor=e.hasOwnProperty=e.propertyIsEnumerable=e.isPrototypeOf=e.toLocaleString=e.toString=e.valueOf=e.__proto__=null,e},Object.create=function(e,t){var n;if(e===null)n=x();else{if(typeof e!="object")throw new TypeError("typeof prototype["+typeof e+"] != 'object'");var r=function(){};r.prototype=e,n=new r,n.__proto__=e}return t!==void 0&&Object.defineProperties(n,t),n}}if(Object.defineProperty){var T=i({}),N=typeof document=="undefined"||i(document.createElement("div"));if(!T||!N)var C=Object.defineProperty}if(!Object.defineProperty||C){var k="Property description must be an object: ",L="Object.defineProperty called on non-object: ",A="getters & setters can not be defined on this javascript engine";Object.defineProperty=function(e,t,n){if(typeof e!="object"&&typeof e!="function"||e===null)throw new TypeError(L+e);if(typeof n!="object"&&typeof n!="function"||n===null)throw new TypeError(k+n);if(C)try{return C.call(Object,e,t,n)}catch(r){}if(p(n,"value"))if(y&&(m(e,t)||g(e,t))){var i=e.__proto__;e.__proto__=l,delete e[t],e[t]=n.value,e.__proto__=i}else e[t]=n.value;else{if(!y)throw new TypeError(A);p(n,"get")&&d(e,t,n.get),p(n,"set")&&v(e,t,n.set)}return e}}Object.defineProperties||(Object.defineProperties=function(e,t){for(var n in t)p(t,n)&&Object.defineProperty(e,n,t[n]);return e}),Object.seal||(Object.seal=function(e){return e}),Object.freeze||(Object.freeze=function(e){return e});try{Object.freeze(function(){})}catch(O){Object.freeze=function(e){return function(t){return typeof t=="function"?t:e(t)}}(Object.freeze)}Object.preventExtensions||(Object.preventExtensions=function(e){return e}),Object.isSealed||(Object.isSealed=function(e){return!1}),Object.isFrozen||(Object.isFrozen=function(e){return!1}),Object.isExtensible||(Object.isExtensible=function(e){if(Object(e)===e)throw new TypeError;var t="";while(p(e,t))t+="?";e[t]=!0;var n=p(e,t);return delete e[t],n});if(!Object.keys){var M=!0,_=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],D=_.length;for(var P in{toString:null})M=!1;Object.keys=function I(e){if(typeof e!="object"&&typeof e!="function"||e===null)throw new TypeError("Object.keys called on a non-object");var I=[];for(var t in e)p(e,t)&&I.push(t);if(M)for(var n=0,r=D;n=t?(e.row=Math.max(0,t-1),e.column=this.getLine(t-1).length):e.row<0&&(e.row=0),e},this.insert=function(e,t){if(!t||t.length===0)return e;e=this.$clipPosition(e),this.getLength()<=1&&this.$detectNewLine(t);var n=this.$split(t),r=n.splice(0,1)[0],i=n.length==0?null:n.splice(n.length-1,1)[0];return e=this.insertInLine(e,r),i!==null&&(e=this.insertNewLine(e),e=this._insertLines(e.row,n),e=this.insertInLine(e,i||"")),e},this.insertLines=function(e,t){return e>=this.getLength()?this.insert({row:e,column:0},"\n"+t.join("\n")):this._insertLines(Math.max(e,0),t)},this._insertLines=function(e,t){if(t.length==0)return{row:e,column:0};if(t.length>65535){var n=this._insertLines(e,t.slice(65535));t=t.slice(0,65535)}var r=[e,0];r.push.apply(r,t),this.$lines.splice.apply(this.$lines,r);var i=new s(e,0,e+t.length,0),o={action:"insertLines",range:i,lines:t};return this._emit("change",{data:o}),n||i.end},this.insertNewLine=function(e){e=this.$clipPosition(e);var t=this.$lines[e.row]||"";this.$lines[e.row]=t.substring(0,e.column),this.$lines.splice(e.row+1,0,t.substring(e.column,t.length));var n={row:e.row+1,column:0},r={action:"insertText",range:s.fromPoints(e,n),text:this.getNewLineCharacter()};return this._emit("change",{data:r}),n},this.insertInLine=function(e,t){if(t.length==0)return e;var n=this.$lines[e.row]||"";this.$lines[e.row]=n.substring(0,e.column)+t+n.substring(e.column);var r={row:e.row,column:e.column+t.length},i={action:"insertText",range:s.fromPoints(e,r),text:t};return this._emit("change",{data:i}),r},this.remove=function(e){!e instanceof s&&(e=s.fromPoints(e.start,e.end)),e.start=this.$clipPosition(e.start),e.end=this.$clipPosition(e.end);if(e.isEmpty())return e.start;var t=e.start.row,n=e.end.row;if(e.isMultiLine()){var r=e.start.column==0?t:t+1,i=n-1;e.end.column>0&&this.removeInLine(n,0,e.end.column),i>=r&&this._removeLines(r,i),r!=t&&(this.removeInLine(t,e.start.column,this.getLine(t).length),this.removeNewLine(e.start.row))}else this.removeInLine(t,e.start.column,e.end.column);return e.start},this.removeInLine=function(e,t,n){if(t==n)return;var r=new s(e,t,e,n),i=this.getLine(e),o=i.substring(t,n),u=i.substring(0,t)+i.substring(n,i.length);this.$lines.splice(e,1,u);var a={action:"removeText",range:r,text:o};return this._emit("change",{data:a}),r.start},this.removeLines=function(e,t){return e<0||t>=this.getLength()?this.remove(new s(e,0,t+1,0)):this._removeLines(e,t)},this._removeLines=function(e,t){var n=new s(e,0,t+1,0),r=this.$lines.splice(e,t-e+1),i={action:"removeLines",range:n,nl:this.getNewLineCharacter(),lines:r};return this._emit("change",{data:i}),r},this.removeNewLine=function(e){var t=this.getLine(e),n=this.getLine(e+1),r=new s(e,t.length,e+1,0),i=t+n;this.$lines.splice(e,2,i);var o={action:"removeText",range:r,text:this.getNewLineCharacter()};this._emit("change",{data:o})},this.replace=function(e,t){!e instanceof s&&(e=s.fromPoints(e.start,e.end));if(t.length==0&&e.isEmpty())return e.start;if(t==this.getTextRange(e))return e.end;this.remove(e);if(t)var n=this.insert(e.start,t);else n=e.start;return n},this.applyDeltas=function(e){for(var t=0;t=0;t--){var n=e[t],r=s.fromPoints(n.range.start,n.range.end);n.action=="insertLines"?this._removeLines(r.start.row,r.end.row-1):n.action=="insertText"?this.remove(r):n.action=="removeLines"?this._insertLines(r.start.row,n.lines):n.action=="removeText"&&this.insert(r.start,n.text)}},this.indexToPosition=function(e,t){var n=this.$lines||this.getAllLines(),r=this.getNewLineCharacter().length;for(var i=t||0,s=n.length;i ["+this.end.row+"/"+this.end.column+"]"},this.contains=function(e,t){return this.compare(e,t)==0},this.compareRange=function(e){var t,n=e.end,r=e.start;return t=this.compare(n.row,n.column),t==1?(t=this.compare(r.row,r.column),t==1?2:t==0?1:0):t==-1?-2:(t=this.compare(r.row,r.column),t==-1?-1:t==1?42:0)},this.comparePoint=function(e){return this.compare(e.row,e.column)},this.containsRange=function(e){return this.comparePoint(e.start)==0&&this.comparePoint(e.end)==0},this.intersects=function(e){var t=this.compareRange(e);return t==-1||t==0||t==1},this.isEnd=function(e,t){return this.end.row==e&&this.end.column==t},this.isStart=function(e,t){return this.start.row==e&&this.start.column==t},this.setStart=function(e,t){typeof e=="object"?(this.start.column=e.column,this.start.row=e.row):(this.start.row=e,this.start.column=t)},this.setEnd=function(e,t){typeof e=="object"?(this.end.column=e.column,this.end.row=e.row):(this.end.row=e,this.end.column=t)},this.inside=function(e,t){return this.compare(e,t)==0?this.isEnd(e,t)||this.isStart(e,t)?!1:!0:!1},this.insideStart=function(e,t){return this.compare(e,t)==0?this.isEnd(e,t)?!1:!0:!1},this.insideEnd=function(e,t){return this.compare(e,t)==0?this.isStart(e,t)?!1:!0:!1},this.compare=function(e,t){return!this.isMultiLine()&&e===this.start.row?tthis.end.column?1:0:ethis.end.row?1:this.start.row===e?t>=this.start.column?0:-1:this.end.row===e?t<=this.end.column?0:1:0},this.compareStart=function(e,t){return this.start.row==e&&this.start.column==t?-1:this.compare(e,t)},this.compareEnd=function(e,t){return this.end.row==e&&this.end.column==t?1:this.compare(e,t)},this.compareInside=function(e,t){return this.end.row==e&&this.end.column==t?1:this.start.row==e&&this.start.column==t?-1:this.compare(e,t)},this.clipRows=function(e,t){if(this.end.row>t)var n={row:t+1,column:0};else if(this.end.rowt)var r={row:t+1,column:0};else if(this.start.rowthis.row)return;if(n.start.row==this.row&&n.start.column>this.column)return;var r=this.row,i=this.column,s=n.start,o=n.end;if(t.action==="insertText")if(s.row===r&&s.column<=i){if(s.column!==i||!this.$insertRight)s.row===o.row?i+=o.column-s.column:(i-=s.column,r+=o.row-s.row)}else s.row!==o.row&&s.row=i?i=s.column:i=Math.max(0,i-(o.column-s.column)):s.row!==o.row&&s.row=this.document.getLength()?(n.row=Math.max(0,this.document.getLength()-1),n.column=this.document.getLine(n.row).length):e<0?(n.row=0,n.column=0):(n.row=e,n.column=Math.min(this.document.getLine(n.row).length,Math.max(0,t))),t<0&&(n.column=0),n}}).call(s.prototype)}),ace.define("ace/lib/lang",["require","exports","module"],function(e,t,n){t.stringReverse=function(e){return e.split("").reverse().join("")},t.stringRepeat=function(e,t){var n="";while(t>0){t&1&&(n+=e);if(t>>=1)e+=e}return n};var r=/^\s\s*/,i=/\s\s*$/;t.stringTrimLeft=function(e){return e.replace(r,"")},t.stringTrimRight=function(e){return e.replace(i,"")},t.copyObject=function(e){var t={};for(var n in e)t[n]=e[n];return t},t.copyArray=function(e){var t=[];for(var n=0,r=e.length;n2;e==null&&(e=[]);if(v&&e.reduce===v)return r&&(t=N.bind(t,r)),i?e.reduce(t,n):e.reduce(t);C(e,function(e,s,o){i?n=t.call(r,n,e,s,o):(n=e,i=!0)});if(!i)throw new TypeError(k);return n},N.reduceRight=N.foldr=function(e,t,n,r){var i=arguments.length>2;e==null&&(e=[]);if(m&&e.reduceRight===m)return r&&(t=N.bind(t,r)),i?e.reduceRight(t,n):e.reduceRight(t);var s=e.length;if(s!==+s){var o=N.keys(e);s=o.length}C(e,function(u,a,f){a=o?o[--s]:--s,i?n=t.call(r,n,e[a],a,f):(n=e[a],i=!0)});if(!i)throw new TypeError(k);return n},N.find=N.detect=function(e,t,n){var r;return L(e,function(e,i,s){if(t.call(n,e,i,s))return r=e,!0}),r},N.filter=N.select=function(e,t,n){var r=[];return e==null?r:g&&e.filter===g?e.filter(t,n):(C(e,function(e,i,s){t.call(n,e,i,s)&&(r[r.length]=e)}),r)},N.reject=function(e,t,n){return N.filter(e,function(e,r,i){return!t.call(n,e,r,i)},n)},N.every=N.all=function(e,t,n){t||(t=N.identity);var r=!0;return e==null?r:y&&e.every===y?e.every(t,n):(C(e,function(e,s,o){if(!(r=r&&t.call(n,e,s,o)))return i}),!!r)};var L=N.some=N.any=function(e,t,n){t||(t=N.identity);var r=!1;return e==null?r:b&&e.some===b?e.some(t,n):(C(e,function(e,s,o){if(r||(r=t.call(n,e,s,o)))return i}),!!r)};N.contains=N.include=function(e,t){return e==null?!1:w&&e.indexOf===w?e.indexOf(t)!=-1:L(e,function(e){return e===t})},N.invoke=function(e,t){var n=f.call(arguments,2),r=N.isFunction(t);return N.map(e,function(e){return(r?t:e[t]).apply(e,n)})},N.pluck=function(e,t){return N.map(e,function(e){return e[t]})},N.where=function(e,t,n){return N.isEmpty(t)?n?null:[]:N[n?"find":"filter"](e,function(e){for(var n in t)if(t[n]!==e[n])return!1;return!0})},N.findWhere=function(e,t){return N.where(e,t,!0)},N.max=function(e,t,n){if(!t&&N.isArray(e)&&e[0]===+e[0]&&e.length<65535)return Math.max.apply(Math,e);if(!t&&N.isEmpty(e))return-Infinity;var r={computed:-Infinity,value:-Infinity};return C(e,function(e,i,s){var o=t?t.call(n,e,i,s):e;o>=r.computed&&(r={value:e,computed:o})}),r.value},N.min=function(e,t,n){if(!t&&N.isArray(e)&&e[0]===+e[0]&&e.length<65535)return Math.min.apply(Math,e);if(!t&&N.isEmpty(e))return Infinity;var r={computed:Infinity,value:Infinity};return C(e,function(e,i,s){var o=t?t.call(n,e,i,s):e;or||n===void 0)return 1;if(n>>1;n.call(r,e[u])=0})})},N.difference=function(e){var t=l.apply(s,f.call(arguments,1));return N.filter(e,function(e){return!N.contains(t,e)})},N.zip=function(){var e=f.call(arguments),t=N.max(N.pluck(e,"length")),n=new Array(t);for(var r=0;r=0;n--)t=[e[n].apply(this,t)];return t[0]}},N.after=function(e,t){return e<=0?t():function(){if(--e<1)return t.apply(this,arguments)}},N.keys=x||function(e){if(e!==Object(e))throw new TypeError("Invalid object");var t=[];for(var n in e)N.has(e,n)&&(t[t.length]=n);return t},N.values=function(e){var t=[];for(var n in e)N.has(e,n)&&t.push(e[n]);return t},N.pairs=function(e){var t=[];for(var n in e)N.has(e,n)&&t.push([n,e[n]]);return t},N.invert=function(e){var t={};for(var n in e)N.has(e,n)&&(t[e[n]]=n);return t},N.functions=N.methods=function(e){var t=[];for(var n in e)N.isFunction(e[n])&&t.push(n);return t.sort()},N.extend=function(e){return C(f.call(arguments,1),function(t){if(t)for(var n in t)e[n]=t[n]}),e},N.pick=function(e){var t={},n=l.apply(s,f.call(arguments,1));return C(n,function(n){n in e&&(t[n]=e[n])}),t},N.omit=function(e){var t={},n=l.apply(s,f.call(arguments,1));for(var r in e)N.contains(n,r)||(t[r]=e[r]);return t},N.defaults=function(e){return C(f.call(arguments,1),function(t){if(t)for(var n in t)e[n]==null&&(e[n]=t[n])}),e},N.clone=function(e){return N.isObject(e)?N.isArray(e)?e.slice():N.extend({},e):e},N.tap=function(e,t){return t(e),e};var _=function(e,t,n,r){if(e===t)return e!==0||1/e==1/t;if(e==null||t==null)return e===t;e instanceof N&&(e=e._wrapped),t instanceof N&&(t=t._wrapped);var i=c.call(e);if(i!=c.call(t))return!1;switch(i){case"[object String]":return e==String(t);case"[object Number]":return e!=+e?t!=+t:e==0?1/e==1/t:e==+t;case"[object Date]":case"[object Boolean]":return+e==+t;case"[object RegExp]":return e.source==t.source&&e.global==t.global&&e.multiline==t.multiline&&e.ignoreCase==t.ignoreCase}if(typeof e!="object"||typeof t!="object")return!1;var s=n.length;while(s--)if(n[s]==e)return r[s]==t;n.push(e),r.push(t);var o=0,u=!0;if(i=="[object Array]"){o=e.length,u=o==t.length;if(u)while(o--)if(!(u=_(e[o],t[o],n,r)))break}else{var a=e.constructor,f=t.constructor;if(a!==f&&!(N.isFunction(a)&&a instanceof a&&N.isFunction(f)&&f instanceof f))return!1;for(var l in e)if(N.has(e,l)){o++;if(!(u=N.has(t,l)&&_(e[l],t[l],n,r)))break}if(u){for(l in t)if(N.has(t,l)&&!(o--))break;u=!o}}return n.pop(),r.pop(),u};N.isEqual=function(e,t){return _(e,t,[],[])},N.isEmpty=function(e){if(e==null)return!0;if(N.isArray(e)||N.isString(e))return e.length===0;for(var t in e)if(N.has(e,t))return!1;return!0},N.isElement=function(e){return!!e&&e.nodeType===1},N.isArray=S||function(e){return c.call(e)=="[object Array]"},N.isObject=function(e){return e===Object(e)},C(["Arguments","Function","String","Number","Date","RegExp"],function(e){N["is"+e]=function(t){return c.call(t)=="[object "+e+"]"}}),N.isArguments(arguments)||(N.isArguments=function(e){return!!e&&!!N.has(e,"callee")}),typeof /./!="function"&&(N.isFunction=function(e){return typeof e=="function"}),N.isFinite=function(e){return isFinite(e)&&!isNaN(parseFloat(e))},N.isNaN=function(e){return N.isNumber(e)&&e!=+e},N.isBoolean=function(e){return e===!0||e===!1||c.call(e)=="[object Boolean]"},N.isNull=function(e){return e===null},N.isUndefined=function(e){return e===void 0},N.has=function(e,t){return h.call(e,t)},N.noConflict=function(){return e._=r,this},N.identity=function(e){return e},N.times=function(e,t,n){var r=Array(e);for(var i=0;i":">",'"':""","'":"'","/":"/"}};D.unescape=N.invert(D.escape);var P={escape:new RegExp("["+N.keys(D.escape).join("")+"]","g"),unescape:new RegExp("("+N.keys(D.unescape).join("|")+")","g")};N.each(["escape","unescape"],function(e){N[e]=function(t){return t==null?"":(""+t).replace(P[e],function(t){return D[e][t]})}}),N.result=function(e,t){if(e==null)return null;var n=e[t];return N.isFunction(n)?n.call(e):n},N.mixin=function(e){C(N.functions(e),function(t){var n=N[t]=e[t];N.prototype[t]=function(){var e=[this._wrapped];return a.apply(e,arguments),I.call(this,n.apply(N,e))}})};var H=0;N.uniqueId=function(e){var t=++H+"";return e?e+t:t},N.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var B=/(.)^/,j={"'":"'","\\":"\\","\r":"r","\n":"n"," ":"t","\u2028":"u2028","\u2029":"u2029"},F=/\\|'|\r|\n|\t|\u2028|\u2029/g;N.template=function(e,t,n){var r;n=N.defaults({},n,N.templateSettings);var i=new RegExp([(n.escape||B).source,(n.interpolate||B).source,(n.evaluate||B).source].join("|")+"|$","g"),s=0,o="__p+='";e.replace(i,function(t,n,r,i,u){return o+=e.slice(s,u).replace(F,function(e){return"\\"+j[e]}),n&&(o+="'+\n((__t=("+n+"))==null?'':_.escape(__t))+\n'"),r&&(o+="'+\n((__t=("+r+"))==null?'':__t)+\n'"),i&&(o+="';\n"+i+"\n__p+='"),s=u+t.length,t}),o+="';\n",n.variable||(o="with(obj||{}){\n"+o+"}\n"),o="var __t,__p='',__j=Array.prototype.join,print=function(){__p+=__j.call(arguments,'');};\n"+o+"return __p;\n";try{r=new Function(n.variable||"obj","_",o)}catch(u){throw u.source=o,u}if(t)return r(t,N);var a=function(e){return r.call(this,e,N)};return a.source="function("+(n.variable||"obj")+"){\n"+o+"}",a},N.chain=function(e){return N(e).chain()};var I=function(e){return this._chain?N(e).chain():e};N.mixin(N),C(["pop","push","reverse","shift","sort","splice","unshift"],function(e){var t=s[e];N.prototype[e]=function(){var n=this._wrapped;return t.apply(n,arguments),(e=="shift"||e=="splice")&&n.length===0&&delete n[0],I.call(this,n)}}),C(["concat","join","slice"],function(e){var t=s[e];N.prototype[e]=function(){return I.call(this,t.apply(this._wrapped,arguments))}}),N.extend(N.prototype,{chain:function(){return this._chain=!0,this},value:function(){return this._wrapped}})}).call(this)},{}],2:[function(e,t,n){var r=e("underscore"),i={E001:"Bad option: '{a}'.",E002:"Bad option value.",E003:"Expected a JSON value.",E004:"Input is neither a string nor an array of strings.",E005:"Input is empty.",E006:"Unexpected early end of program.",E007:'Missing "use strict" statement.',E008:"Strict violation.",E009:"Option 'validthis' can't be used in a global scope.",E010:"'with' is not allowed in strict mode.",E011:"const '{a}' has already been declared.",E012:"const '{a}' is initialized to 'undefined'.",E013:"Attempting to override '{a}' which is a constant.",E014:"A regular expression literal can be confused with '/='.",E015:"Unclosed regular expression.",E016:"Invalid regular expression.",E017:"Unclosed comment.",E018:"Unbegun comment.",E019:"Unmatched '{a}'.",E020:"Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.",E021:"Expected '{a}' and instead saw '{b}'.",E022:"Line breaking error '{a}'.",E023:"Missing '{a}'.",E024:"Unexpected '{a}'.",E025:"Missing ':' on a case clause.",E026:"Missing '}' to match '{' from line {a}.",E027:"Missing ']' to match '[' form line {a}.",E028:"Illegal comma.",E029:"Unclosed string.",E030:"Expected an identifier and instead saw '{a}'.",E031:"Bad assignment.",E032:"Expected a small integer or 'false' and instead saw '{a}'.",E033:"Expected an operator and instead saw '{a}'.",E034:"get/set are ES5 features.",E035:"Missing property name.",E036:"Expected to see a statement and instead saw a block.",E037:null,E038:null,E039:"Function declarations are not invocable. Wrap the whole function invocation in parens.",E040:"Each value should have its own case label.",E041:"Unrecoverable syntax error.",E042:"Stopping.",E043:"Too many errors.",E044:"'{a}' is already defined and can't be redefined.",E045:"Invalid for each loop.",E046:"A yield statement shall be within a generator function (with syntax: `function*`)",E047:"A generator function shall contain a yield statement.",E048:"Let declaration not directly within block.",E049:"A {a} cannot be named '{b}'.",E050:"Mozilla requires the yield expression to be parenthesized here.",E051:"Regular parameters cannot come after default parameters."},s={W001:"'hasOwnProperty' is a really bad name.",W002:"Value of '{a}' may be overwritten in IE 8 and earlier.",W003:"'{a}' was used before it was defined.",W004:"'{a}' is already defined.",W005:"A dot following a number can be confused with a decimal point.",W006:"Confusing minuses.",W007:"Confusing pluses.",W008:"A leading decimal point can be confused with a dot: '{a}'.",W009:"The array literal notation [] is preferrable.",W010:"The object literal notation {} is preferrable.",W011:"Unexpected space after '{a}'.",W012:"Unexpected space before '{a}'.",W013:"Missing space after '{a}'.",W014:"Bad line breaking before '{a}'.",W015:"Expected '{a}' to have an indentation at {b} instead at {c}.",W016:"Unexpected use of '{a}'.",W017:"Bad operand.",W018:"Confusing use of '{a}'.",W019:"Use the isNaN function to compare with NaN.",W020:"Read only.",W021:"'{a}' is a function.",W022:"Do not assign to the exception parameter.",W023:"Expected an identifier in an assignment and instead saw a function invocation.",W024:"Expected an identifier and instead saw '{a}' (a reserved word).",W025:"Missing name in function declaration.",W026:"Inner functions should be listed at the top of the outer function.",W027:"Unreachable '{a}' after '{b}'.",W028:"Label '{a}' on {b} statement.",W030:"Expected an assignment or function call and instead saw an expression.",W031:"Do not use 'new' for side effects.",W032:"Unnecessary semicolon.",W033:"Missing semicolon.",W034:'Unnecessary directive "{a}".',W035:"Empty block.",W036:"Unexpected /*member '{a}'.",W037:"'{a}' is a statement label.",W038:"'{a}' used out of scope.",W039:"'{a}' is not allowed.",W040:"Possible strict violation.",W041:"Use '{a}' to compare with '{b}'.",W042:"Avoid EOL escaping.",W043:"Bad escaping of EOL. Use option multistr if needed.",W044:"Bad or unnecessary escaping.",W045:"Bad number '{a}'.",W046:"Don't use extra leading zeros '{a}'.",W047:"A trailing decimal point can be confused with a dot: '{a}'.",W048:"Unexpected control character in regular expression.",W049:"Unexpected escaped character '{a}' in regular expression.",W050:"JavaScript URL.",W051:"Variables should not be deleted.",W052:"Unexpected '{a}'.",W053:"Do not use {a} as a constructor.",W054:"The Function constructor is a form of eval.",W055:"A constructor name should start with an uppercase letter.",W056:"Bad constructor.",W057:"Weird construction. Is 'new' unnecessary?",W058:"Missing '()' invoking a constructor.",W059:"Avoid arguments.{a}.",W060:"document.write can be a form of eval.",W061:"eval can be harmful.",W062:"Wrap an immediate function invocation in parens to assist the reader in understanding that the expression is the result of a function, and not the function itself.",W063:"Math is not a function.",W064:"Missing 'new' prefix when invoking a constructor.",W065:"Missing radix parameter.",W066:"Implied eval. Consider passing a function instead of a string.",W067:"Bad invocation.",W068:"Wrapping non-IIFE function literals in parens is unnecessary.",W069:"['{a}'] is better written in dot notation.",W070:"Extra comma. (it breaks older versions of IE)",W071:"This function has too many statements. ({a})",W072:"This function has too many parameters. ({a})",W073:"Blocks are nested too deeply. ({a})",W074:"This function's cyclomatic complexity is too high. ({a})",W075:"Duplicate key '{a}'.",W076:"Unexpected parameter '{a}' in get {b} function.",W077:"Expected a single parameter in set {a} function.",W078:"Setter is defined without getter.",W079:"Redefinition of '{a}'.",W080:"It's not necessary to initialize '{a}' to 'undefined'.",W081:"Too many var statements.",W082:"Function declarations should not be placed in blocks. Use a function expression or move the statement to the top of the outer function.",W083:"Don't make functions within a loop.",W084:"Assignment in conditional expression",W085:"Don't use 'with'.",W086:"Expected a 'break' statement before '{a}'.",W087:"Forgotten 'debugger' statement?",W088:"Creating global 'for' variable. Should be 'for (var {a} ...'.",W089:"The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.",W090:"'{a}' is not a statement label.",W091:"'{a}' is out of scope.",W092:"Wrap the /regexp/ literal in parens to disambiguate the slash operator.",W093:"Did you mean to return a conditional instead of an assignment?",W094:"Unexpected comma.",W095:"Expected a string and instead saw {a}.",W096:"The '{a}' key may produce unexpected results.",W097:'Use the function form of "use strict".',W098:"'{a}' is defined but never used.",W099:"Mixed spaces and tabs.",W100:"This character may get silently deleted by one or more browsers.",W101:"Line is too long.",W102:"Trailing whitespace.",W103:"The '{a}' property is deprecated.",W104:"'{a}' is only available in JavaScript 1.7.",W105:"Unexpected {a} in '{b}'.",W106:"Identifier '{a}' is not in camel case.",W107:"Script URL.",W108:"Strings must use doublequote.",W109:"Strings must use singlequote.",W110:"Mixed double and single quotes.",W112:"Unclosed string.",W113:"Control character in string: {a}.",W114:"Avoid {a}.",W115:"Octal literals are not allowed in strict mode.",W116:"Expected '{a}' and instead saw '{b}'.",W117:"'{a}' is not defined.",W118:"'{a}' is only available in Mozilla JavaScript extensions (use moz option).",W119:"'{a}' is only available in ES6 (use esnext option).",W120:"You might be leaking a variable ({a}) here."},o={I001:"Comma warnings can be turned off with 'laxcomma'.",I002:"Reserved words as properties can be used under the 'es5' option.",I003:"ES5 option is now set per default"};n.errors={},n.warnings={},n.info={},r.each(i,function(e,t){n.errors[t]={code:t,desc:e}}),r.each(s,function(e,t){n.warnings[t]={code:t,desc:e}}),r.each(o,function(e,t){n.info[t]={code:t,desc:e}})},{underscore:1}],3:[function(e,t,n){n.reservedVars={arguments:!1,NaN:!1},n.ecmaIdentifiers={Array:!1,Boolean:!1,Date:!1,decodeURI:!1,decodeURIComponent:!1,encodeURI:!1,encodeURIComponent:!1,Error:!1,eval:!1,EvalError:!1,Function:!1,hasOwnProperty:!1,isFinite:!1,isNaN:!1,JSON:!1,Math:!1,Map:!1,Number:!1,Object:!1,parseInt:!1,parseFloat:!1,RangeError:!1,ReferenceError:!1,RegExp:!1,Set:!1,String:!1,SyntaxError:!1,TypeError:!1,URIError:!1,WeakMap:!1},n.browser={ArrayBuffer:!1,ArrayBufferView:!1,Audio:!1,Blob:!1,addEventListener:!1,applicationCache:!1,atob:!1,blur:!1,btoa:!1,clearInterval:!1,clearTimeout:!1,close:!1,closed:!1,CustomEvent:!1,DataView:!1,DOMParser:!1,defaultStatus:!1,document:!1,Element:!1,ElementTimeControl:!1,event:!1,FileReader:!1,Float32Array:!1,Float64Array:!1,FormData:!1,focus:!1,frames:!1,getComputedStyle:!1,HTMLElement:!1,HTMLAnchorElement:!1,HTMLBaseElement:!1,HTMLBlockquoteElement:!1,HTMLBodyElement:!1,HTMLBRElement:!1,HTMLButtonElement:!1,HTMLCanvasElement:!1,HTMLDirectoryElement:!1,HTMLDivElement:!1,HTMLDListElement:!1,HTMLFieldSetElement:!1,HTMLFontElement:!1,HTMLFormElement:!1,HTMLFrameElement:!1,HTMLFrameSetElement:!1,HTMLHeadElement:!1,HTMLHeadingElement:!1,HTMLHRElement:!1,HTMLHtmlElement:!1,HTMLIFrameElement:!1,HTMLImageElement:!1,HTMLInputElement:!1,HTMLIsIndexElement:!1,HTMLLabelElement:!1,HTMLLayerElement:!1,HTMLLegendElement:!1,HTMLLIElement:!1,HTMLLinkElement:!1,HTMLMapElement:!1,HTMLMenuElement:!1,HTMLMetaElement:!1,HTMLModElement:!1,HTMLObjectElement:!1,HTMLOListElement:!1,HTMLOptGroupElement:!1,HTMLOptionElement:!1,HTMLParagraphElement:!1,HTMLParamElement:!1,HTMLPreElement:!1,HTMLQuoteElement:!1,HTMLScriptElement:!1,HTMLSelectElement:!1,HTMLStyleElement:!1,HTMLTableCaptionElement:!1,HTMLTableCellElement:!1,HTMLTableColElement:!1,HTMLTableElement:!1,HTMLTableRowElement:!1,HTMLTableSectionElement:!1,HTMLTextAreaElement:!1,HTMLTitleElement:!1,HTMLUListElement:!1,HTMLVideoElement:!1,history:!1,Int16Array:!1,Int32Array:!1,Int8Array:!1,Image:!1,length:!1,localStorage:!1,location:!1,MessageChannel:!1,MessageEvent:!1,MessagePort:!1,MouseEvent:!1,moveBy:!1,moveTo:!1,MutationObserver:!1,name:!1,Node:!1,NodeFilter:!1,navigator:!1,onbeforeunload:!0,onblur:!0,onerror:!0,onfocus:!0,onload:!0,onresize:!0,onunload:!0,open:!1,openDatabase:!1,opener:!1,Option:!1,parent:!1,print:!1,removeEventListener:!1,resizeBy:!1,resizeTo:!1,screen:!1,scroll:!1,scrollBy:!1,scrollTo:!1,sessionStorage:!1,setInterval:!1,setTimeout:!1,SharedWorker:!1,status:!1,SVGAElement:!1,SVGAltGlyphDefElement:!1,SVGAltGlyphElement:!1,SVGAltGlyphItemElement:!1,SVGAngle:!1,SVGAnimateColorElement:!1,SVGAnimateElement:!1,SVGAnimateMotionElement:!1,SVGAnimateTransformElement:!1,SVGAnimatedAngle:!1,SVGAnimatedBoolean:!1,SVGAnimatedEnumeration:!1,SVGAnimatedInteger:!1,SVGAnimatedLength:!1,SVGAnimatedLengthList:!1,SVGAnimatedNumber:!1,SVGAnimatedNumberList:!1,SVGAnimatedPathData:!1,SVGAnimatedPoints:!1,SVGAnimatedPreserveAspectRatio:!1,SVGAnimatedRect:!1,SVGAnimatedString:!1,SVGAnimatedTransformList:!1,SVGAnimationElement:!1,SVGCSSRule:!1,SVGCircleElement:!1,SVGClipPathElement:!1,SVGColor:!1,SVGColorProfileElement:!1,SVGColorProfileRule:!1,SVGComponentTransferFunctionElement:!1,SVGCursorElement:!1,SVGDefsElement:!1,SVGDescElement:!1,SVGDocument:!1,SVGElement:!1,SVGElementInstance:!1,SVGElementInstanceList:!1,SVGEllipseElement:!1,SVGExternalResourcesRequired:!1,SVGFEBlendElement:!1,SVGFEColorMatrixElement:!1,SVGFEComponentTransferElement:!1,SVGFECompositeElement:!1,SVGFEConvolveMatrixElement:!1,SVGFEDiffuseLightingElement:!1,SVGFEDisplacementMapElement:!1,SVGFEDistantLightElement:!1,SVGFEFloodElement:!1,SVGFEFuncAElement:!1,SVGFEFuncBElement:!1,SVGFEFuncGElement:!1,SVGFEFuncRElement:!1,SVGFEGaussianBlurElement:!1,SVGFEImageElement:!1,SVGFEMergeElement:!1,SVGFEMergeNodeElement:!1,SVGFEMorphologyElement:!1,SVGFEOffsetElement:!1,SVGFEPointLightElement:!1,SVGFESpecularLightingElement:!1,SVGFESpotLightElement:!1,SVGFETileElement:!1,SVGFETurbulenceElement:!1,SVGFilterElement:!1,SVGFilterPrimitiveStandardAttributes:!1,SVGFitToViewBox:!1,SVGFontElement:!1,SVGFontFaceElement:!1,SVGFontFaceFormatElement:!1,SVGFontFaceNameElement:!1,SVGFontFaceSrcElement:!1,SVGFontFaceUriElement:!1,SVGForeignObjectElement:!1,SVGGElement:!1,SVGGlyphElement:!1,SVGGlyphRefElement:!1,SVGGradientElement:!1,SVGHKernElement:!1,SVGICCColor:!1,SVGImageElement:!1,SVGLangSpace:!1,SVGLength:!1,SVGLengthList:!1,SVGLineElement:!1,SVGLinearGradientElement:!1,SVGLocatable:!1,SVGMPathElement:!1,SVGMarkerElement:!1,SVGMaskElement:!1,SVGMatrix:!1,SVGMetadataElement:!1,SVGMissingGlyphElement:!1,SVGNumber:!1,SVGNumberList:!1,SVGPaint:!1,SVGPathElement:!1,SVGPathSeg:!1,SVGPathSegArcAbs:!1,SVGPathSegArcRel:!1,SVGPathSegClosePath:!1,SVGPathSegCurvetoCubicAbs:!1,SVGPathSegCurvetoCubicRel:!1,SVGPathSegCurvetoCubicSmoothAbs:!1,SVGPathSegCurvetoCubicSmoothRel:!1,SVGPathSegCurvetoQuadraticAbs:!1,SVGPathSegCurvetoQuadraticRel:!1,SVGPathSegCurvetoQuadraticSmoothAbs:!1,SVGPathSegCurvetoQuadraticSmoothRel:!1,SVGPathSegLinetoAbs:!1,SVGPathSegLinetoHorizontalAbs:!1,SVGPathSegLinetoHorizontalRel:!1,SVGPathSegLinetoRel:!1,SVGPathSegLinetoVerticalAbs:!1,SVGPathSegLinetoVerticalRel:!1,SVGPathSegList:!1,SVGPathSegMovetoAbs:!1,SVGPathSegMovetoRel:!1,SVGPatternElement:!1,SVGPoint:!1,SVGPointList:!1,SVGPolygonElement:!1,SVGPolylineElement:!1,SVGPreserveAspectRatio:!1,SVGRadialGradientElement:!1,SVGRect:!1,SVGRectElement:!1,SVGRenderingIntent:!1,SVGSVGElement:!1,SVGScriptElement:!1,SVGSetElement:!1,SVGStopElement:!1,SVGStringList:!1,SVGStylable:!1,SVGStyleElement:!1,SVGSwitchElement:!1,SVGSymbolElement:!1,SVGTRefElement:!1,SVGTSpanElement:!1,SVGTests:!1,SVGTextContentElement:!1,SVGTextElement:!1,SVGTextPathElement:!1,SVGTextPositioningElement:!1,SVGTitleElement:!1,SVGTransform:!1,SVGTransformList:!1,SVGTransformable:!1,SVGURIReference:!1,SVGUnitTypes:!1,SVGUseElement:!1,SVGVKernElement:!1,SVGViewElement:!1,SVGViewSpec:!1,SVGZoomAndPan:!1,TimeEvent:!1,top:!1,Uint16Array:!1,Uint32Array:!1,Uint8Array:!1,Uint8ClampedArray:!1,WebSocket:!1,window:!1,Worker:!1,XMLHttpRequest:!1,XMLSerializer:!1,XPathEvaluator:!1,XPathException:!1,XPathExpression:!1,XPathNamespace:!1,XPathNSResolver:!1,XPathResult:!1},n.devel={alert:!1,confirm:!1,console:!1,Debug:!1,opera:!1,prompt:!1},n.worker={importScripts:!0,postMessage:!0,self:!0},n.nonstandard={escape:!1,unescape:!1},n.couch={require:!1,respond:!1,getRow:!1,emit:!1,send:!1,start:!1,sum:!1,log:!1,exports:!1,module:!1,provides:!1},n.node={__filename:!1,__dirname:!1,Buffer:!1,DataView:!1,console:!1,exports:!0,GLOBAL:!1,global:!1,module:!1,process:!1,require:!1,setTimeout:!1,clearTimeout:!1,setInterval:!1,clearInterval:!1,setImmediate:!1,clearImmediate:!1},n.phantom={phantom:!0,require:!0,WebPage:!0},n.rhino={defineClass:!1,deserialize:!1,gc:!1,help:!1,importPackage:!1,java:!1,load:!1,loadClass:!1,print:!1,quit:!1,readFile:!1,readUrl:!1,runCommand:!1,seal:!1,serialize:!1,spawn:!1,sync:!1,toint32:!1,version:!1},n.shelljs={target:!1,echo:!1,exit:!1,cd:!1,pwd:!1,ls:!1,find:!1,cp:!1,rm:!1,mv:!1,mkdir:!1,test:!1,cat:!1,sed:!1,grep:!1,which:!1,dirs:!1,pushd:!1,popd:!1,env:!1,exec:!1,chmod:!1,config:!1,error:!1,tempdir:!1},n.wsh={ActiveXObject:!0,Enumerator:!0,GetObject:!0,ScriptEngine:!0,ScriptEngineBuildVersion:!0,ScriptEngineMajorVersion:!0,ScriptEngineMinorVersion:!0,VBArray:!0,WSH:!0,WScript:!0,XDomainRequest:!0},n.dojo={dojo:!1,dijit:!1,dojox:!1,define:!1,require:!1},n.jquery={$:!1,jQuery:!1},n.mootools={$:!1,$$:!1,Asset:!1,Browser:!1,Chain:!1,Class:!1,Color:!1,Cookie:!1,Core:!1,Document:!1,DomReady:!1,DOMEvent:!1,DOMReady:!1,Drag:!1,Element:!1,Elements:!1,Event:!1,Events:!1,Fx:!1,Group:!1,Hash:!1,HtmlTable:!1,Iframe:!1,IframeShim:!1,InputValidator:!1,instanceOf:!1,Keyboard:!1,Locale:!1,Mask:!1,MooTools:!1,Native:!1,Options:!1,OverText:!1,Request:!1,Scroller:!1,Slick:!1,Slider:!1,Sortables:!1,Spinner:!1,Swiff:!1,Tips:!1,Type:!1,typeOf:!1,URI:!1,Window:!1},n.prototypejs={$:!1,$$:!1,$A:!1,$F:!1,$H:!1,$R:!1,$break:!1,$continue:!1,$w:!1,Abstract:!1,Ajax:!1,Class:!1,Enumerable:!1,Element:!1,Event:!1,Field:!1,Form:!1,Hash:!1,Insertion:!1,ObjectRange:!1,PeriodicalExecuter:!1,Position:!1,Prototype:!1,Selector:!1,Template:!1,Toggle:!1,Try:!1,Autocompleter:!1,Builder:!1,Control:!1,Draggable:!1,Draggables:!1,Droppables:!1,Effect:!1,Sortable:!1,SortableObserver:!1,Sound:!1,Scriptaculous:!1},n.yui={YUI:!1,Y:!1,YUI_config:!1}},{}],n4bKNg:[function(e,t,n){var r=e("underscore"),i=e("events"),s=e("../shared/vars.js"),o=e("../shared/messages.js"),u=e("./lex.js").Lexer,a=e("./reg.js"),f=e("./state.js").state,l=e("./style.js"),c=e("console-browserify"),h=function(){function e(e,t){return e=e.trim(),/^[+-]W\d{3}$/g.test(e)?!0:Ot[e]===undefined&&At[e]===undefined&&t.type!=="jslint"?(E("E001",t,e),!1):!0}function t(e){return Object.prototype.toString.call(e)==="[object String]"}function n(e,t){return e?!e.identifier||e.value!==t?!1:!0:!1}function c(e){if(!e.reserved)return!1;var t=e.meta;if(t&&t.isFutureReservedWord&&f.option.inES5()){if(!t.es5)return!1;if(t.strictOnly&&!f.option.strict&&!f.directive["use strict"])return!1;if(e.isProperty)return!1}return!0}function p(e,t){return e.replace(/\{([^{}]*)\}/g,function(e,n){var r=t[n];return typeof r=="string"||typeof r=="number"?r:e})}function d(e,t){var n;for(n in t)r.has(t,n)&&!r.has(h.blacklist,n)&&(e[n]=t[n])}function v(){Object.keys(h.blacklist).forEach(function(e){delete $t[e]})}function m(){f.option.couch&&d($t,s.couch),f.option.rhino&&d($t,s.rhino),f.option.shelljs&&(d($t,s.shelljs),d($t,s.node)),f.option.phantom&&d($t,s.phantom),f.option.prototypejs&&d($t,s.prototypejs),f.option.node&&d($t,s.node),f.option.devel&&d($t,s.devel),f.option.dojo&&d($t,s.dojo),f.option.browser&&d($t,s.browser),f.option.nonstandard&&d($t,s.nonstandard),f.option.jquery&&d($t,s.jquery),f.option.mootools&&d($t,s.mootools),f.option.worker&&d($t,s.worker),f.option.wsh&&d($t,s.wsh),f.option.globalstrict&&f.option.strict!==!1&&(f.option.strict=!0),f.option.yui&&d($t,s.yui),f.option.inMoz=function(e){return f.option.moz},f.option.inESNext=function(e){return f.option.moz||f.option.esnext},f.option.inES5=function(){return!f.option.es3},f.option.inES3=function(e){return e?!f.option.moz&&!f.option.esnext&&f.option.es3:f.option.es3}}function g(e,t,n){var r=Math.floor(t/f.lines.length*100),i=o.errors[e].desc;throw{name:"JSHintError",line:t,character:n,message:i+" ("+r+"% scanned).",raw:i,code:e}}function y(e,t,n,r){return h.undefs.push([e,t,n,r])}function b(e,t,n,r,i,s){var u,a,l,c;if(/^W\d{3}$/.test(e)){if(f.ignored[e])return;c=o.warnings[e]}else/E\d{3}/.test(e)?c=o.errors[e]:/I\d{3}/.test(e)&&(c=o.info[e]);return t=t||f.tokens.next,t.id==="(end)"&&(t=f.tokens.curr),a=t.line||0,u=t.from||0,l={id:"(error)",raw:c.desc,code:c.code,evidence:f.lines[a-1]||"",line:a,character:u,scope:h.scope,a:n,b:r,c:i,d:s},l.reason=p(c.desc,l),h.errors.push(l),f.option.passfail&&g("E042",a,u),Yt+=1,Yt>=f.option.maxerr&&g("E043",a,u),l}function w(e,t,n,r,i,s,o){return b(e,{line:t,from:n},r,i,s,o)}function E(e,t,n,r,i,s){b(e,t,n,r,i,s)}function S(e,t,n,r,i,s,o){return E(e,{line:t,from:n},r,i,s,o)}function x(e,t){var n;return n={id:"(internal)",elem:e,value:t},h.internals.push(n),n}function T(e,t,n,i){t==="exception"&&r.has(Bt["(context)"],e)&&Bt[e]!==!0&&!f.option.node&&b("W002",f.tokens.next,e),r.has(Bt,e)&&!Bt["(global)"]&&(Bt[e]===!0?f.option.latedef&&(f.option.latedef===!0&&r.contains([Bt[e],t],"unction")||!r.contains([Bt[e],t],"unction"))&&b("W003",f.tokens.next,e):(!f.option.shadow&&t!=="exception"||Bt["(blockscope)"].getlabel(e))&&b("W004",f.tokens.next,e)),Bt["(blockscope)"]&&Bt["(blockscope)"].current.has(e)&&E("E044",f.tokens.next,e),i?Bt["(blockscope)"].current.add(e,t,f.tokens.curr):(Bt[e]=t,n&&(Bt["(tokens)"][e]=n),Bt["(global)"]?(Ft[e]=Bt,r.has(It,e)&&(f.option.latedef&&(f.option.latedef===!0&&r.contains([Bt[e],t],"unction")||!r.contains([Bt[e],t],"unction"))&&b("W003",f.tokens.next,e),delete It[e])):Jt[e]=Bt)}function N(){var t=f.tokens.next,n=t.body.match(/(-\s+)?[^\s,]+(?:\s*:\s*(-\s+)?[^\s,]+)?/g),i={};if(t.type==="globals"){n.forEach(function(e){e=e.split(":");var t=(e[0]||"").trim(),n=(e[1]||"").trim();t.charAt(0)==="-"?(t=t.slice(1),n=!1,h.blacklist[t]=t,v()):i[t]=n==="true"}),d($t,i);for(var s in i)r.has(i,s)&&(Dt[s]=t)}t.type==="exported"&&n.forEach(function(e){Pt[e]=!0}),t.type==="members"&&(Xt=Xt||{},n.forEach(function(e){var t=e.charAt(0),n=e.charAt(e.length-1);t===n&&(t==='"'||t==="'")&&(e=e.substr(1,e.length-2).replace("\\b","\b").replace("\\t"," ").replace("\\n","\n").replace("\\v","").replace("\\f","\f").replace("\\r","\r").replace("\\\\","\\").replace('\\"','"')),Xt[e]=!1}));var o=["maxstatements","maxparams","maxdepth","maxcomplexity","maxerr","maxlen","indent"];if(t.type==="jshint"||t.type==="jslint")n.forEach(function(n){n=n.split(":");var r=(n[0]||"").trim(),i=(n[1]||"").trim();if(!e(r,t))return;if(o.indexOf(r)>=0){if(i!=="false"){i=+i;if(typeof i!="number"||!isFinite(i)||i<=0||Math.floor(i)!==i){E("E032",t,n[1].trim());return}r==="indent"&&(f.option["(explicitIndent)"]=!0),f.option[r]=i}else r==="indent"?f.option["(explicitIndent)"]=!1:f.option[r]=!1;return}if(r==="validthis"){Bt["(global)"]?E("E009"):i==="true"||i==="false"?f.option.validthis=i==="true":E("E002",t);return}if(r==="quotmark"){switch(i){case"true":case"false":f.option.quotmark=i==="true";break;case"double":case"single":f.option.quotmark=i;break;default:E("E002",t)}return}if(r==="unused"){switch(i){case"true":f.option.unused=!0;break;case"false":f.option.unused=!1;break;case"vars":case"strict":f.option.unused=i;break;default:E("E002",t)}return}if(r==="latedef"){switch(i){case"true":f.option.latedef=!0;break;case"false":f.option.latedef=!1;break;case"nofunc":f.option.latedef="nofunc";break;default:E("E002",t)}return}var s=/^([+-])(W\d{3})$/g.exec(r);if(s){f.ignored[s[2]]=s[1]==="-";return}var u;if(i==="true"||i==="false"){t.type==="jslint"?(u=_t[r]||r,f.option[u]=i==="true",Mt[u]!==undefined&&(f.option[u]=!f.option[u])):f.option[r]=i==="true",r==="newcap"&&(f.option["(explicitNewcap)"]=!0);return}E("E002",t)}),m()}function C(e){var t=e||0,n=0,r;while(n<=t)r=Ut[n],r||(r=Ut[n]=zt.token()),n+=1;return r}function k(e,t){switch(f.tokens.curr.id){case"(number)":f.tokens.next.id==="."&&b("W005",f.tokens.curr);break;case"-":(f.tokens.next.id==="-"||f.tokens.next.id==="--")&&b("W006");break;case"+":(f.tokens.next.id==="+"||f.tokens.next.id==="++")&&b("W007")}if(f.tokens.curr.type==="(string)"||f.tokens.curr.identifier)Ct=f.tokens.curr.value;e&&f.tokens.next.id!==e&&(t?f.tokens.next.id==="(end)"?E("E019",t,t.id):E("E020",f.tokens.next,e,t.id,t.line,f.tokens.next.value):(f.tokens.next.type!=="(identifier)"||f.tokens.next.value!==e)&&b("W116",f.tokens.next,e,f.tokens.next.value)),f.tokens.prev=f.tokens.curr,f.tokens.curr=f.tokens.next;for(;;){f.tokens.next=Ut.shift()||zt.token(),f.tokens.next||g("E041",f.tokens.curr.line);if(f.tokens.next.id==="(end)"||f.tokens.next.id==="(error)")return;f.tokens.next.check&&f.tokens.next.check();if(f.tokens.next.isSpecial)N();else if(f.tokens.next.id!=="(endline)")break}}function L(e){return e.infix||!e.identifier&&!!e.led}function A(){var e=f.tokens.curr,t=f.tokens.next;return t.id===";"||t.id==="}"||t.id===":"?!0:L(t)===L(e)||e.id==="yield"&&f.option.inMoz(!0)?e.line!==t.line:!1}function O(e,t){var n,r=!1,i=!1,s=!1;!t&&f.tokens.next.value==="let"&&C(0).value==="("&&(f.option.inMoz(!0)||b("W118",f.tokens.next,"let expressions"),s=!0,Bt["(blockscope)"].stack(),k("let"),k("("),f.syntax.let.fud.call(f.syntax.let.fud,!1),k(")")),f.tokens.next.id==="(end)"&&E("E006",f.tokens.curr),k(),t&&(Ct="anonymous",Bt["(verb)"]=f.tokens.curr.value);if(t===!0&&f.tokens.curr.fud)n=f.tokens.curr.fud();else{f.tokens.curr.nud?n=f.tokens.curr.nud():E("E030",f.tokens.curr,f.tokens.curr.id);while(e="a"&&t<="z"||t>="A"&&t<="Z")e.identifier=e.reserved=!0;return e}function X(e,t){var n=q(e,150);return W(n),n.nud=typeof t=="function"?t:function(){this.right=O(150),this.arity="unary";if(this.id==="++"||this.id==="--")f.option.plusplus?b("W016",this,this.id):(!this.right.identifier||c(this.right))&&this.right.id!=="."&&this.right.id!=="["&&b("W017",this);return this},n}function V(e,t){var n=R(e);return n.type=e,n.nud=t,n}function $(e,t){var n=V(e,t);return n.identifier=!0,n.reserved=!0,n}function J(e,t){var n=V(e,t&&t.nud||function(){return this});return t=t||{},t.isFutureReservedWord=!0,n.value=e,n.identifier=!0,n.reserved=!0,n.meta=t,n}function K(e,t){return $(e,function(){return typeof t=="function"&&t(this),this})}function Q(e,t,n,r){var i=q(e,n);return W(i),i.infix=!0,i.led=function(i){return r||(H(f.tokens.prev,f.tokens.curr),P(f.tokens.curr,f.tokens.next)),e==="in"&&i.id==="!"&&b("W018",i,"!"),typeof t=="function"?t(i,this):(this.left=i,this.right=O(n),this)},i}function G(e){var t=q(e,42);return t.led=function(e){return f.option.inESNext()||b("W104",f.tokens.curr,"arrow function syntax (=>)"),H(f.tokens.prev,f.tokens.curr),P(f.tokens.curr,f.tokens.next),this.left=e,this.right=mt(undefined,undefined,!1,e),this},t}function Y(e,t){var r=q(e,100);return r.led=function(e){H(f.tokens.prev,f.tokens.curr),P(f.tokens.curr,f.tokens.next);var r=O(100);return n(e,"NaN")||n(r,"NaN")?b("W019",this):t&&t.apply(this,[e,r]),(!e||!r)&&g("E041",f.tokens.curr.line),e.id==="!"&&b("W018",e,"!"),r.id==="!"&&b("W018",r,"!"),this.left=e,this.right=r,this},r}function Z(e){return e&&(e.type==="(number)"&&+e.value===0||e.type==="(string)"&&e.value===""||e.type==="null"&&!f.option.eqnull||e.type==="true"||e.type==="false"||e.type==="undefined")}function et(e,t,n){var r=Q(e,typeof t=="function"?t:function(e,t){t.left=e;if(e){$t[e.value]===!1&&Jt[e.value]["(global)"]===!0?b("W020",e):e["function"]&&b("W021",e,e.value),Bt[e.value]==="const"&&E("E013",e,e.value);if(e.id===".")return e.left?e.left.value==="arguments"&&!f.directive["use strict"]&&b("E031",t):b("E031",t),t.right=O(10),t;if(e.id==="[")return f.tokens.curr.left.first?f.tokens.curr.left.first.forEach(function(e){Bt[e.value]==="const"&&E("E013",e,e.value)}):e.left?e.left.value==="arguments"&&!f.directive["use strict"]&&b("E031",t):b("E031",t),t.right=O(10),t;if(e.identifier&&!c(e))return Bt[e.value]==="exception"&&b("W022",e),t.right=O(10),t;e===f.syntax["function"]&&b("W023",f.tokens.curr)}E("E031",t)},n);return r.exps=!0,r.assign=!0,r}function tt(e,t,n){var r=q(e,n);return W(r),r.led=typeof t=="function"?t:function(e){return f.option.bitwise&&b("W016",this,this.id),this.left=e,this.right=O(n),this},r}function nt(e){return et(e,function(e,t){f.option.bitwise&&b("W016",t,t.id),P(f.tokens.prev,f.tokens.curr),P(f.tokens.curr,f.tokens.next);if(e)return e.id==="."||e.id==="["||e.identifier&&!c(e)?(O(10),t):(e===f.syntax["function"]&&b("W023",f.tokens.curr),t);E("E031",t)},20)}function rt(e){var t=q(e,150);return t.led=function(e){return f.option.plusplus?b("W016",this,this.id):(!e.identifier||c(e))&&e.id!=="."&&e.id!=="["&&b("W017",this),this.left=e,this},t}function it(e,t){if(!f.tokens.next.identifier)return;k();var n=f.tokens.curr,r=f.tokens.curr.value;return c(n)?t&&f.option.inES5()?r:e&&r==="undefined"?r:(t&&!kt.getCache("displayed:I002")&&(kt.setCache("displayed:I002",!0),b("I002")),b("W024",f.tokens.curr,f.tokens.curr.id),r):r}function st(e,t){var n=it(e,t);if(n)return n;f.tokens.curr.id==="function"&&f.tokens.next.id==="("?b("W025"):E("E030",f.tokens.next,f.tokens.next.value)}function ot(e){var t=0,n;if(f.tokens.next.id!==";"||Vt)return;for(;;){n=C(t);if(n.reach)return;if(n.id!=="(endline)"){if(n.id==="function"){if(!f.option.latedef)break;b("W026",n);break}b("W027",n,n.value,e);break}t+=1}}function ut(e){var t,n=Rt,i,s=Jt,o=f.tokens.next;if(o.id===";"){k(";");return}var u=c(o);u&&o.meta&&o.meta.isFutureReservedWord&&C().id===":"&&(b("W024",o,o.id),u=!1);if(r.has(["[","{"],o.value)&&on().isDestAssign){f.option.inESNext()||b("W104",f.tokens.curr,"destructuring expression"),t=wt(),t.forEach(function(e){y(Bt,"W117",e.token,e.id)}),k("="),Et(t,O(10,!0)),k(";");return}o.identifier&&!u&&C().id===":"&&(k(),k(":"),Jt=Object.create(s),T(o.value,"label"),!f.tokens.next.labelled&&f.tokens.next.value!=="{"&&b("W028",f.tokens.next,o.value,f.tokens.next.value),f.tokens.next.label=o.value,o=f.tokens.next);if(o.id==="{"){lt(!0,!0);return}return e||B(),i=O(0,!0),o.block||(!f.option.expr&&(!i||!i.exps)?b("W030",f.tokens.curr):f.option.nonew&&i&&i.left&&i.id==="("&&i.left.id==="new"&&b("W031",o),f.tokens.next.id!==";"?f.option.asi||(!f.option.lastsemic||f.tokens.next.id!=="}"||f.tokens.next.line!==f.tokens.curr.line)&&w("W033",f.tokens.curr.line,f.tokens.curr.character):(M(f.tokens.curr,f.tokens.next),k(";"),P(f.tokens.curr,f.tokens.next))),Rt=n,Jt=s,i}function at(e){var t=[],n;while(!f.tokens.next.reach&&f.tokens.next.id!=="(end)")f.tokens.next.id===";"?(n=C(),(!n||n.id!=="("&&n.id!=="[")&&b("W032"),k(";")):t.push(ut(e===f.tokens.next.line));return t}function ft(){var e,t,n;for(;;){if(f.tokens.next.id==="(string)"){t=C(0);if(t.id==="(endline)"){e=1;do n=C(e),e+=1;while(n.id==="(endline)");if(n.id!==";"){if(n.id!=="(string)"&&n.id!=="(number)"&&n.id!=="(regexp)"&&n.identifier!==!0&&n.id!=="}")break;b("W033",f.tokens.next)}else t=n}else if(t.id==="}")b("W033",t);else if(t.id!==";")break;B(),k(),f.directive[f.tokens.curr.value]&&b("W034",f.tokens.curr,f.tokens.curr.value),f.tokens.curr.value==="use strict"&&(f.option["(explicitNewcap)"]||(f.option.newcap=!0),f.option.undef=!0),f.directive[f.tokens.curr.value]=!0,t.id===";"&&k(";");continue}break}}function lt(e,t,n,i){var s,o=qt,u=Rt,a,l=Jt,c,h,p;qt=e;if(!e||!f.option.funcscope)Jt=Object.create(Jt);P(f.tokens.curr,f.tokens.next),c=f.tokens.next;var d=Bt["(metrics)"];d.nestedBlockDepth+=1,d.verifyMaxNestedBlockDepthPerFunction();if(f.tokens.next.id==="{"){k("{"),Bt["(blockscope)"].stack(),h=f.tokens.curr.line;if(f.tokens.next.id!=="}"){Rt+=f.option.indent;while(!e&&f.tokens.next.from>Rt)Rt+=f.option.indent;if(n){a={};for(p in f.directive)r.has(f.directive,p)&&(a[p]=f.directive[p]);ft(),f.option.strict&&Bt["(context)"]["(global)"]&&!a["use strict"]&&!f.directive["use strict"]&&b("E007")}s=at(h),d.statementCount+=s.length,n&&(f.directive=a),Rt-=f.option.indent,h!==f.tokens.next.line&&B()}else h!==f.tokens.next.line&&B();k("}",c),Bt["(blockscope)"].unstack(),Rt=u}else if(!e)if(n){a={},t&&!i&&!f.option.inMoz(!0)&&E("W118",f.tokens.curr,"function closure expressions");if(!t)for(p in f.directive)r.has(f.directive,p)&&(a[p]=f.directive[p]);O(10),f.option.strict&&Bt["(context)"]["(global)"]&&!a["use strict"]&&!f.directive["use strict"]&&b("E007")}else E("E021",f.tokens.next,"{",f.tokens.next.value);else Bt["(nolet)"]=!0,(!t||f.option.curly)&&b("W116",f.tokens.next,"{",f.tokens.next.value),Vt=!0,Rt+=f.option.indent,s=[ut(f.tokens.next.line===f.tokens.curr.line)],Rt-=f.option.indent,Vt=!1,delete Bt["(nolet)"];Bt["(verb)"]=null;if(!e||!f.option.funcscope)Jt=l;return qt=o,e&&f.option.noempty&&(!s||s.length===0)&&b("W035"),d.nestedBlockDepth-=1,s}function ct(e){Xt&&typeof Xt[e]!="boolean"&&b("W036",f.tokens.curr,e),typeof Wt[e]=="number"?Wt[e]+=1:Wt[e]=1}function ht(e){var t=e.value,n=e.line,r=It[t];typeof r=="function"&&(r=!1),r?r[r.length-1]!==n&&r.push(n):(r=[n],It[t]=r)}function pt(){var e={};return e.exps=!0,Bt["(comparray)"].stack(),e.right=O(10),k("for"),f.tokens.next.value==="each"&&(k("each"),f.option.inMoz(!0)||b("W118",f.tokens.curr,"for each")),k("("),Bt["(comparray)"].setState("define"),e.left=O(10),k(")"),f.tokens.next.value==="if"&&(k("if"),k("("),Bt["(comparray)"].setState("filter"),e.filter=O(10),k(")")),k("]"),Bt["(comparray)"].unstack(),e}function dt(){var e=it(!1,!0);return e||(f.tokens.next.id==="(string)"?(e=f.tokens.next.value,k()):f.tokens.next.id==="(number)"&&(e=f.tokens.next.value.toString(),k())),e==="hasOwnProperty"&&b("W001"),e}function vt(e){var t,n,i=[],s,o=[],u,a=!1;if(e){if(e instanceof Array){for(var l in e){t=e[l];if(r.contains(["{","["],t.id))for(u in t.left)u=o[u],u.id&&(i.push(u.id),T(u.id,"unused",u.token));else{if(t.value==="..."){f.option.inESNext()||b("W104",t,"spread/rest operator");continue}T(t.value,"unused",t)}}return i}if(e.identifier===!0)return T(e.value,"unused",e),[e]}n=f.tokens.next,k("("),D();if(f.tokens.next.id===")"){k(")");return}for(;;){if(r.contains(["{","["],f.tokens.next.id)){o=wt();for(u in o)u=o[u],u.id&&(i.push(u.id),T(u.id,"unused",u.token))}else f.tokens.next.value==="..."?(f.option.inESNext()||b("W104",f.tokens.next,"spread/rest operator"),k("..."),D(),s=st(!0),i.push(s),T(s,"unused",f.tokens.curr)):(s=st(!0),i.push(s),T(s,"unused",f.tokens.curr));a&&f.tokens.next.id!=="="&&E("E051",f.tokens.current),f.tokens.next.id==="="&&(f.option.inESNext()||b("W119",f.tokens.next,"default parameters"),k("="),a=!0,O(10));if(f.tokens.next.id!==",")return k(")",n),D(f.tokens.prev,f.tokens.curr),i;I()}}function mt(e,t,n,r){var i,s=f.option,o=f.ignored,u=Jt;return f.option=Object.create(f.option),f.ignored=Object.create(f.ignored),Jt=Object.create(Jt),Bt={"(name)":e||'"'+Ct+'"',"(line)":f.tokens.next.line,"(character)":f.tokens.next.character,"(context)":Bt,"(breakage)":0,"(loopage)":0,"(metrics)":gt(f.tokens.next),"(scope)":Jt,"(statement)":t,"(tokens)":{},"(blockscope)":Bt["(blockscope)"],"(comparray)":Bt["(comparray)"]},n&&(Bt["(generator)"]=!0),i=Bt,f.tokens.curr.funct=Bt,jt.push(Bt),e&&T(e,"function"),Bt["(params)"]=vt(r),Bt["(metrics)"].verifyMaxParametersPerFunction(Bt["(params)"]),lt(!1,!0,!0,r?!0:!1),n&&Bt["(generator)"]!=="yielded"&&E("E047",f.tokens.curr),Bt["(metrics)"].verifyMaxStatementsPerFunction(),Bt["(metrics)"].verifyMaxComplexityPerFunction(),Bt["(unusedOption)"]=f.option.unused,Jt=u,f.option=s,f.ignored=o,Bt["(last)"]=f.tokens.curr.line,Bt["(lastcharacter)"]=f.tokens.curr.character,Bt=Bt["(context)"],i}function gt(e){return{statementCount:0,nestedBlockDepth:-1,ComplexityCount:1,verifyMaxStatementsPerFunction:function(){f.option.maxstatements&&this.statementCount>f.option.maxstatements&&b("W071",e,this.statementCount)},verifyMaxParametersPerFunction:function(t){t=t||[],f.option.maxparams&&t.length>f.option.maxparams&&b("W072",e,t.length)},verifyMaxNestedBlockDepthPerFunction:function(){f.option.maxdepth&&this.nestedBlockDepth>0&&this.nestedBlockDepth===f.option.maxdepth+1&&b("W073",null,this.nestedBlockDepth)},verifyMaxComplexityPerFunction:function(){var t=f.option.maxcomplexity,n=this.ComplexityCount;t&&n>t&&b("W074",e,n)}}}function yt(){Bt["(metrics)"].ComplexityCount+=1}function bt(e){var t,n;e&&(t=e.id,n=e.paren,t===","&&(e=e.exprs[e.exprs.length-1])&&(t=e.id,n=n||e.paren));switch(t){case"=":case"+=":case"-=":case"*=":case"%=":case"&=":case"|=":case"^=":case"/=":!n&&!f.option.boss&&b("W084")}}function wt(){var e,t,n=[];f.option.inESNext()||b("W104",f.tokens.curr,"destructuring expression");var i=function(){var e;if(r.contains(["[","{"],f.tokens.next.value)){t=wt();for(var i in t)i=t[i],n.push({id:i.id,token:i.token})}else f.tokens.next.value===","?n.push({id:null,token:f.tokens.curr}):(e=st(),e&&n.push({id:e,token:f.tokens.curr}))};if(f.tokens.next.value==="["){k("["),i();while(f.tokens.next.value!=="]")k(","),i();k("]")}else if(f.tokens.next.value==="{"){k("{"),e=st(),f.tokens.next.value===":"?(k(":"),i()):n.push({id:e,token:f.tokens.curr});while(f.tokens.next.value!=="}")k(","),e=st(),f.tokens.next.value===":"?(k(":"),i()):n.push({id:e,token:f.tokens.curr});k("}")}return n}function Et(e,t){t.first&&r.zip(e,t.first).forEach(function(e){var t=e[0],n=e[1];t&&n?t.first=n:t&&t.first&&!n&&b("W080",t.first,t.first.value)})}function St(e){return f.option.inESNext()||b("W104",f.tokens.curr,"class"),e?(this.name=st(),T(this.name,"unused",f.tokens.curr)):f.tokens.next.identifier&&f.tokens.next.value!=="extends"&&(this.name=st()),xt(this),this}function xt(e){var t=f.directive["use strict"];f.tokens.next.value==="extends"&&(k("extends"),e.heritage=O(10)),f.directive["use strict"]=!0,k("{"),e.body=f.syntax["{"].nud(!0),f.directive["use strict"]=t}function Tt(){var e=on();e.notJson?(!f.option.inESNext()&&e.isDestAssign&&b("W104",f.tokens.curr,"destructuring assignment"),at()):(f.option.laxbreak=!0,f.jsonMode=!0,Nt())}function Nt(){function e(){var e={},t=f.tokens.next;k("{");if(f.tokens.next.id!=="}")for(;;){if(f.tokens.next.id==="(end)")E("E026",f.tokens.next,t.line);else{if(f.tokens.next.id==="}"){b("W094",f.tokens.curr);break}f.tokens.next.id===","?E("E028",f.tokens.next):f.tokens.next.id!=="(string)"&&b("W095",f.tokens.next,f.tokens.next.value)}e[f.tokens.next.value]===!0?b("W075",f.tokens.next,f.tokens.next.value):f.tokens.next.value==="__proto__"&&!f.option.proto||f.tokens.next.value==="__iterator__"&&!f.option.iterator?b("W096",f.tokens.next,f.tokens.next.value):e[f.tokens.next.value]=!0,k(),k(":"),Nt();if(f.tokens.next.id!==",")break;k(",")}k("}")}function t(){var e=f.tokens.next;k("[");if(f.tokens.next.id!=="]")for(;;){if(f.tokens.next.id==="(end)")E("E027",f.tokens.next,e.line);else{if(f.tokens.next.id==="]"){b("W094",f.tokens.curr);break}f.tokens.next.id===","&&E("E028",f.tokens.next)}Nt();if(f.tokens.next.id!==",")break;k(",")}k("]")}switch(f.tokens.next.id){case"{":e();break;case"[":t();break;case"true":case"false":case"null":case"(number)":case"(string)":k();break;case"-":k("-"),f.tokens.curr.character!==f.tokens.next.from&&b("W011",f.tokens.curr),M(f.tokens.curr,f.tokens.next),k("(number)");break;default:E("E003",f.tokens.next)}}var Ct,kt,Lt={"<":!0,"<=":!0,"==":!0,"===":!0,"!==":!0,"!=":!0,">":!0,">=":!0,"+":!0,"-":!0,"*":!0,"/":!0,"%":!0},At={asi:!0,bitwise:!0,boss:!0,browser:!0,camelcase:!0,couch:!0,curly:!0,debug:!0,devel:!0,dojo:!0,eqeqeq:!0,eqnull:!0,es3:!0,es5:!0,esnext:!0,moz:!0,evil:!0,expr:!0,forin:!0,funcscope:!0,gcl:!0,globalstrict:!0,immed:!0,iterator:!0,jquery:!0,lastsemic:!0,laxbreak:!0,laxcomma:!0,loopfunc:!0,mootools:!0,multistr:!0,newcap:!0,noarg:!0,node:!0,noempty:!0,nonew:!0,nonstandard:!0,nomen:!0,onevar:!0,passfail:!0,phantom:!0,plusplus:!0,proto:!0,prototypejs:!0,rhino:!0,shelljs:!0,undef:!0,scripturl:!0,shadow:!0,smarttabs:!0,strict:!0,sub:!0,supernew:!0,trailing:!0,validthis:!0,withstmt:!0,white:!0,worker:!0,wsh:!0,yui:!0,onecase:!0,regexp:!0,regexdash:!0},Ot={maxlen:!1,indent:!1,maxerr:!1,predef:!1,quotmark:!1,scope:!1,maxstatements:!1,maxdepth:!1,maxparams:!1,maxcomplexity:!1,unused:!0,latedef:!1},Mt={bitwise:!0,forin:!0,newcap:!0,nomen:!0,plusplus:!0,regexp:!0,undef:!0,white:!0,eqeqeq:!0,onevar:!0,strict:!0},_t={eqeq:"eqeqeq",vars:"onevar",windows:"wsh",sloppy:"strict"},Dt,Pt,Ht=["closure","exception","global","label","outer","unused","var"],Bt,jt,Ft,It,qt,Rt,Ut,zt,Wt,Xt,Vt,$t,Jt,Kt,Qt,Gt,Yt,Zt=[],en=new i.EventEmitter;V("(number)",function(){return this}),V("(string)",function(){return this}),f.syntax["(identifier)"]={type:"(identifier)",lbp:0,identifier:!0,nud:function(){var e=this.value,t=Jt[e],n;typeof t=="function"?t=undefined:typeof t=="boolean"&&(n=Bt,Bt=jt[0],T(e,"var"),t=Bt,Bt=n);var i;r.has(Bt,"(blockscope)")&&(i=Bt["(blockscope)"].getlabel(e));if(Bt===t||i)switch(i?i[e]["(type)"]:Bt[e]){case"unused":i?i[e]["(type)"]="var":Bt[e]="var";break;case"unction":i?i[e]["(type)"]="function":Bt[e]="function",this["function"]=!0;break;case"function":this["function"]=!0;break;case"label":b("W037",f.tokens.curr,e)}else if(Bt["(global)"])typeof $t[e]!="boolean"&&(Ct!=="typeof"&&Ct!=="delete"||f.tokens.next&&(f.tokens.next.value==="."||f.tokens.next.value==="["))&&(Bt["(comparray)"].check(e)||y(Bt,"W117",f.tokens.curr,e)),ht(f.tokens.curr);else switch(Bt[e]){case"closure":case"function":case"var":case"unused":b("W038",f.tokens.curr,e);break;case"label":b("W037",f.tokens.curr,e);break;case"outer":case"global":break;default:if(t===!0)Bt[e]=!0;else if(t===null)b("W039",f.tokens.curr,e),ht(f.tokens.curr);else if(typeof t!="object")(Ct!=="typeof"&&Ct!=="delete"||f.tokens.next&&(f.tokens.next.value==="."||f.tokens.next.value==="["))&&y(Bt,"W117",f.tokens.curr,e),Bt[e]=!0,ht(f.tokens.curr);else switch(t[e]){case"function":case"unction":this["function"]=!0,t[e]="closure",Bt[e]=t["(global)"]?"global":"outer";break;case"var":case"unused":t[e]="closure",Bt[e]=t["(global)"]?"global":"outer";break;case"closure":Bt[e]=t["(global)"]?"global":"outer";break;case"label":b("W037",f.tokens.curr,e)}}return this},led:function(){E("E033",f.tokens.next,f.tokens.next.value)}},V("(regexp)",function(){return this}),R("(endline)"),R("(begin)"),R("(end)").reach=!0,R("(error)").reach=!0,R("}").reach=!0,R(")"),R("]"),R('"').reach=!0,R("'").reach=!0,R(";"),R(":").reach=!0,R("#"),$("else"),$("case").reach=!0,$("catch"),$("default").reach=!0,$("finally"),K("arguments",function(e){f.directive["use strict"]&&Bt["(global)"]&&b("E008",e)}),K("eval"),K("false"),K("Infinity"),K("null"),K("this",function(e){f.directive["use strict"]&&!f.option.validthis&&(Bt["(statement)"]&&Bt["(name)"].charAt(0)>"Z"||Bt["(global)"])&&b("W040",e)}),K("true"),K("undefined"),et("=","assign",20),et("+=","assignadd",20),et("-=","assignsub",20),et("*=","assignmult",20),et("/=","assigndiv",20).nud=function(){E("E014")},et("%=","assignmod",20),nt("&=","assignbitand",20),nt("|=","assignbitor",20),nt("^=","assignbitxor",20),nt("<<=","assignshiftleft",20),nt(">>=","assignshiftright",20),nt(">>>=","assignshiftrightunsigned",20),Q(",",function(e,t){var n;t.exprs=[e];if(!I({peek:!0}))return t;for(;;){if(!(n=O(10)))break;t.exprs.push(n);if(f.tokens.next.value!==","||!I())break}return t},10,!0),Q("?",function(e,t){return yt(),t.left=e,t.right=O(10),k(":"),t["else"]=O(10),t},30);var tn=40;Q("||",function(e,t){return yt(),t.left=e,t.right=O(tn),t},tn),Q("&&","and",50),tt("|","bitor",70),tt("^","bitxor",80),tt("&","bitand",90),Y("==",function(e,t){var n=f.option.eqnull&&(e.value==="null"||t.value==="null");return!n&&f.option.eqeqeq?b("W116",this,"===","=="):Z(e)?b("W041",this,"===",e.value):Z(t)&&b("W041",this,"===",t.value),this}),Y("==="),Y("!=",function(e,t){var n=f.option.eqnull&&(e.value==="null"||t.value==="null");return!n&&f.option.eqeqeq?b("W116",this,"!==","!="):Z(e)?b("W041",this,"!==",e.value):Z(t)&&b("W041",this,"!==",t.value),this}),Y("!=="),Y("<"),Y(">"),Y("<="),Y(">="),tt("<<","shiftleft",120),tt(">>","shiftright",120),tt(">>>","shiftrightunsigned",120),Q("in","in",120),Q("instanceof","instanceof",120),Q("+",function(e,t){var n=O(130);return e&&n&&e.id==="(string)"&&n.id==="(string)"?(e.value+=n.value,e.character=n.character,!f.option.scripturl&&a.javascriptURL.test(e.value)&&b("W050",e),e):(t.left=e,t.right=n,t)},130),X("+","num"),X("+++",function(){return b("W007"),this.right=O(150),this.arity="unary",this}),Q("+++",function(e){return b("W007"),this.left=e,this.right=O(130),this},130),Q("-","sub",130),X("-","neg"),X("---",function(){return b("W006"),this.right=O(150),this.arity="unary",this}),Q("---",function(e){return b("W006"),this.left=e,this.right=O(130),this},130),Q("*","mult",140),Q("/","div",140),Q("%","mod",140),rt("++","postinc"),X("++","preinc"),f.syntax["++"].exps=!0,rt("--","postdec"),X("--","predec"),f.syntax["--"].exps=!0,X("delete",function(){var e=O(10);return(!e||e.id!=="."&&e.id!=="[")&&b("W051"),this.first=e,this}).exps=!0,X("~",function(){return f.option.bitwise&&b("W052",this,"~"),O(150),this}),X("...",function(){return f.option.inESNext()||b("W104",this,"spread/rest operator"),f.tokens.next.identifier||E("E030",f.tokens.next,f.tokens.next.value),O(150),this}),X("!",function(){return this.right=O(150),this.arity="unary",this.right||g("E041",this.line||0),Lt[this.right.id]===!0&&b("W018",this,"!"),this}),X("typeof","typeof"),X("new",function(){var e=O(155),t;if(e&&e.id!=="function")if(e.identifier){e["new"]=!0;switch(e.value){case"Number":case"String":case"Boolean":case"Math":case"JSON":b("W053",f.tokens.prev,e.value);break;case"Function":f.option.evil||b("W054");break;case"Date":case"RegExp":break;default:e.id!=="function"&&(t=e.value.substr(0,1),f.option.newcap&&(t<"A"||t>"Z")&&!r.has(Ft,e.value)&&b("W055",f.tokens.curr))}}else e.id!=="."&&e.id!=="["&&e.id!=="("&&b("W056",f.tokens.curr);else f.option.supernew||b("W057",this);return M(f.tokens.curr,f.tokens.next),f.tokens.next.id!=="("&&!f.option.supernew&&b("W058",f.tokens.curr,f.tokens.curr.value),this.first=e,this}),f.syntax["new"].exps=!0,X("void").exps=!0,Q(".",function(e,t){M(f.tokens.prev,f.tokens.curr),_();var n=st(!1,!0);return typeof n=="string"&&ct(n),t.left=e,t.right=n,n&&n==="hasOwnProperty"&&f.tokens.next.value==="="&&b("W001"),!e||e.value!=="arguments"||n!=="callee"&&n!=="caller"?!f.option.evil&&e&&e.value==="document"&&(n==="write"||n==="writeln")&&b("W060",e):f.option.noarg?b("W059",e,n):f.directive["use strict"]&&E("E008"),!f.option.evil&&(n==="eval"||n==="execScript")&&b("W061"),t},160,!0),Q("(",function(e,t){f.tokens.prev.id!=="}"&&f.tokens.prev.id!==")"&&_(f.tokens.prev,f.tokens.curr),D(),f.option.immed&&e&&!e.immed&&e.id==="function"&&b("W062");var n=0,r=[];e&&e.type==="(identifier)"&&e.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)&&"Number String Boolean Date Object".indexOf(e.value)===-1&&(e.value==="Math"?b("W063",e):f.option.newcap&&b("W064",e));if(f.tokens.next.id!==")")for(;;){r[r.length]=O(10),n+=1;if(f.tokens.next.id!==",")break;I()}return k(")"),D(f.tokens.prev,f.tokens.curr),typeof e=="object"&&(e.value==="parseInt"&&n===1&&b("W065",f.tokens.curr),f.option.evil||(e.value==="eval"||e.value==="Function"||e.value==="execScript"?(b("W061",e),r[0]&&[0].id==="(string)"&&x(e,r[0].value)):!r[0]||r[0].id!=="(string)"||e.value!=="setTimeout"&&e.value!=="setInterval"?r[0]&&r[0].id==="(string)"&&e.value==="."&&e.left.value==="window"&&(e.right==="setTimeout"||e.right==="setInterval")&&(b("W066",e),x(e,r[0].value)):(b("W066",e),x(e,r[0].value))),!e.identifier&&e.id!=="."&&e.id!=="["&&e.id!=="("&&e.id!=="&&"&&e.id!=="||"&&e.id!=="?"&&b("W067",e)),t.left=e,t},155,!0).exps=!0,X("(",function(){D();var e,t=[],n,r,i=0,s;do n=C(i),i+=1,r=C(i),i+=1;while(n.value!==")"&&r.value!=="=>"&&r.value!==";"&&r.type!=="(end)");f.tokens.next.id==="function"&&(f.tokens.next.immed=!0);var o=[];if(f.tokens.next.id!==")")for(;;){if(r.value==="=>"&&f.tokens.next.value==="{"){e=f.tokens.next,e.left=wt(),t.push(e);for(var u in e.left)o.push(e.left[u].token)}else o.push(O(10));if(f.tokens.next.id!==",")break;I()}k(")",this),D(f.tokens.prev,f.tokens.curr),f.option.immed&&o[0]&&o[0].id==="function"&&f.tokens.next.id!=="("&&(f.tokens.next.id!=="."||C().value!=="call"&&C().value!=="apply")&&b("W068",this);if(f.tokens.next.value==="=>")return o;if(!o.length)return;return o.length>1?(s=Object.create(f.syntax[","]),s.exprs=o):s=o[0],s&&(s.paren=!0),s}),G("=>"),Q("[",function(e,t){_(f.tokens.prev,f.tokens.curr),D();var n=O(10),r;return n&&n.type==="(string)"&&(!f.option.evil&&(n.value==="eval"||n.value==="execScript")&&b("W061",t),ct(n.value),!f.option.sub&&a.identifier.test(n.value)&&(r=f.syntax[n.value],(!r||!c(r))&&b("W069",f.tokens.prev,n.value))),k("]",t),n&&n.value==="hasOwnProperty"&&f.tokens.next.value==="="&&b("W001"),D(f.tokens.prev,f.tokens.curr),t.left=e,t.right=n,t},160,!0),X("[",function(){var e=on(!0);if(e.isCompArray)return f.option.inMoz(!0)||b("W118",f.tokens.curr,"array comprehension"),pt();e.isDestAssign&&!f.option.inESNext()&&b("W104",f.tokens.curr,"destructuring assignment");var t=f.tokens.curr.line!==f.tokens.next.line;this.first=[],t&&(Rt+=f.option.indent,f.tokens.next.from===Rt+f.option.indent&&(Rt+=f.option.indent));while(f.tokens.next.id!=="(end)"){while(f.tokens.next.id===",")f.option.inES5()||b("W070"),k(",");if(f.tokens.next.id==="]")break;t&&f.tokens.curr.line!==f.tokens.next.line&&B(),this.first.push(O(10));if(f.tokens.next.id!==",")break;I({allowTrailing:!0});if(f.tokens.next.id==="]"&&!f.option.inES5(!0)){b("W070",f.tokens.curr);break}}return t&&(Rt-=f.option.indent,B()),k("]",this),this},160),function(e){e.nud=function(e){function t(e,t){h[e]&&r.has(h,e)?b("W075",f.tokens.next,u):h[e]={},h[e].basic=!0,h[e].basictkn=t}function n(e,t){h[e]&&r.has(h,e)?(h[e].basic||h[e].setter)&&b("W075",f.tokens.next,u):h[e]={},h[e].setter=!0,h[e].setterToken=t}function i(e){h[e]&&r.has(h,e)?(h[e].basic||h[e].getter)&&b("W075",f.tokens.next,u):h[e]={},h[e].getter=!0,h[e].getterToken=f.tokens.curr}var s,o,u,a,l,c,h={},p="";s=f.tokens.curr.line!==f.tokens.next.line,s&&(Rt+=f.option.indent,f.tokens.next.from===Rt+f.option.indent&&(Rt+=f.option.indent));for(;;){if(f.tokens.next.id==="}")break;s&&B(),e&&f.tokens.next.value==="static"&&(k("static"),p="static ");if(f.tokens.next.value==="get"&&C().id!==":")k("get"),f.option.inES5(!e)||E("E034"),u=dt(),u||E("E035"),e&&u==="constructor"&&E("E049",f.tokens.next,"class getter method",u),i(p+u),l=f.tokens.next,M(f.tokens.curr,f.tokens.next),o=mt(),a=o["(params)"],a&&b("W076",l,a[0],u),M(f.tokens.curr,f.tokens.next);else if(f.tokens.next.value==="set"&&C().id!==":")k("set"),f.option.inES5(!e)||E("E034"),u=dt(),u||E("E035"),e&&u==="constructor"&&E("E049",f.tokens.next,"class setter method",u),n(p+u,f.tokens.next),l=f.tokens.next,M(f.tokens.curr,f.tokens.next),o=mt(),a=o["(params)"],(!a||a.length!==1)&&b("W077",l,u);else{c=!1,f.tokens.next.value==="*"&&f.tokens.next.type==="(punctuator)"&&(f.option.inESNext()||b("W104",f.tokens.next,"generator functions"),k("*"),c=!0),u=dt(),t(p+u,f.tokens.next);if(typeof u!="string")break;f.tokens.next.value==="("?(f.option.inESNext()||b("W104",f.tokens.curr,"concise methods"),mt(u,undefined,c)):e||(k(":"),P(f.tokens.curr,f.tokens.next),O(10))}e&&u==="prototype"&&E("E049",f.tokens.next,"class method",u),ct(u);if(e){p="";continue}if(f.tokens.next.id!==",")break;I({allowTrailing:!0,property:!0}),f.tokens.next.id===","?b("W070",f.tokens.curr):f.tokens.next.id==="}"&&!f.option.inES5(!0)&&b("W070",f.tokens.curr)}s&&(Rt-=f.option.indent,B()),k("}",this);if(f.option.inES5())for(var d in h)r.has(h,d)&&h[d].setter&&!h[d].getter&&b("W078",h[d].setterToken);return this},e.fud=function(){E("E036",f.tokens.curr)}}(R("{"));var nn=U("const",function(e){var t,n,i;f.option.inESNext()||b("W104",f.tokens.curr,"const"),this.first=[];for(;;){var s=[];P(f.tokens.curr,f.tokens.next),r.contains(["{","["],f.tokens.next.value)?(t=wt(),i=!1):(t=[{id:st(),token:f.tokens.curr}],i=!0);for(var o in t)o=t[o],Bt[o.id]==="const"&&b("E011",null,o.id),Bt["(global)"]&&$t[o.id]===!1&&b("W079",o.token,o.id),o.id&&(T(o.id,"const"),s.push(o.token));if(e)break;this.first=this.first.concat(s),f.tokens.next.id!=="="&&b("E012",f.tokens.curr,f.tokens.curr.value),f.tokens.next.id==="="&&(P(f.tokens.curr,f.tokens.next),k("="),P(f.tokens.curr,f.tokens.next),f.tokens.next.id==="undefined"&&b("W080",f.tokens.prev,f.tokens.prev.value),C(0).id==="="&&f.tokens.next.identifier&&b("W120",f.tokens.next,f.tokens.next.value),n=O(10),i?t[0].first=n:Et(s,n));if(f.tokens.next.id!==",")break;I()}return this});nn.exps=!0;var rn=U("var",function(e){var t,n,i;Bt["(onevar)"]&&f.option.onevar?b("W081"):Bt["(global)"]||(Bt["(onevar)"]=!0),this.first=[];for(;;){var s=[];P(f.tokens.curr,f.tokens.next),r.contains(["{","["],f.tokens.next.value)?(t=wt(),n=!1):(t=[{id:st(),token:f.tokens.curr}],n=!0);for(var o in t)o=t[o],f.option.inESNext()&&Bt[o.id]==="const"&&b("E011",null,o.id),Bt["(global)"]&&$t[o.id]===!1&&b("W079",o.token,o.id),o.id&&(T(o.id,"unused",o.token),s.push(o.token));if(e)break;this.first=this.first.concat(s),f.tokens.next.id==="="&&(P(f.tokens.curr,f.tokens.next),k("="),P(f.tokens.curr,f.tokens.next),f.tokens.next.id==="undefined"&&b("W080",f.tokens.prev,f.tokens.prev.value),C(0).id==="="&&f.tokens.next.identifier&&b("W120",f.tokens.next,f.tokens.next.value),i=O(10),n?t[0].first=i:Et(s,i));if(f.tokens.next.id!==",")break;I()}return this});rn.exps=!0;var sn=U("let",function(e){var t,n,i,s;f.option.inESNext()||b("W104",f.tokens.curr,"let"),f.tokens.next.value==="("?(f.option.inMoz(!0)||b("W118",f.tokens.next,"let block"),k("("),Bt["(blockscope)"].stack(),s=!0):Bt["(nolet)"]&&E("E048",f.tokens.curr),Bt["(onevar)"]&&f.option.onevar?b("W081"):Bt["(global)"]||(Bt["(onevar)"]=!0),this.first=[];for(;;){var o=[];P(f.tokens.curr,f.tokens.next),r.contains(["{","["],f.tokens.next.value)?(t=wt(),n=!1):(t=[{id:st(),token:f.tokens.curr.value}],n=!0);for(var u in t)u=t[u],f.option.inESNext()&&Bt[u.id]==="const"&&b("E011",null,u.id),Bt["(global)"]&&$t[u.id]===!1&&b("W079",u.token,u.id),u.id&&!Bt["(nolet)"]&&(T(u.id,"unused",u.token,!0),o.push(u.token));if(e)break;this.first=this.first.concat(o),f.tokens.next.id==="="&&(P(f.tokens.curr,f.tokens.next),k("="),P(f.tokens.curr,f.tokens.next),f.tokens.next.id==="undefined"&&b("W080",f.tokens.prev,f.tokens.prev.value),C(0).id==="="&&f.tokens.next.identifier&&b("W120",f.tokens.next,f.tokens.next.value),i=O(10),n?t[0].first=i:Et(o,i));if(f.tokens.next.id!==",")break;I()}return s&&(k(")"),lt(!0,!0),this.block=!0,Bt["(blockscope)"].unstack()),this});sn.exps=!0,z("class",function(){return St.call(this,!0)}),z("function",function(){var e=!1;f.tokens.next.value==="*"&&(k("*"),f.option.inESNext(!0)?e=!0:b("W119",f.tokens.curr,"function*")),qt&&b("W082",f.tokens.curr);var t=st();return Bt[t]==="const"&&b("E011",null,t),M(f.tokens.curr,f.tokens.next),T(t,"unction",f.tokens.curr),mt(t,{statement:!0},e),f.tokens.next.id==="("&&f.tokens.next.line===f.tokens.curr.line&&E("E039"),this}),X("function",function(){var e=!1;f.tokens.next.value==="*"&&(f.option.inESNext()||b("W119",f.tokens.curr,"function*"),k("*"),e=!0);var t=it();return t||f.option.gcl?M(f.tokens.curr,f.tokens.next):P(f.tokens.curr,f.tokens.next),mt(t,undefined,e),!f.option.loopfunc&&Bt["(loopage)"]&&b("W083"),this}),z("if",function(){var e=f.tokens.next;return yt(),f.condition=!0,k("("),P(this,e),D(),bt(O(0)),k(")",e),f.condition=!1,D(f.tokens.prev,f.tokens.curr),lt(!0,!0),f.tokens.next.id==="else"&&(P(f.tokens.curr,f.tokens.next),k("else"),f.tokens.next.id==="if"||f.tokens.next.id==="switch"?ut(!0):lt(!0,!0)),this}),z("try",function(){function e(){var e=Jt,t;k("catch"),P(f.tokens.curr,f.tokens.next),k("("),Jt=Object.create(e),t=f.tokens.next.value,f.tokens.next.type!=="(identifier)"&&(t=null,b("E030",f.tokens.next,t)),k(),Bt={"(name)":"(catch)","(line)":f.tokens.next.line,"(character)":f.tokens.next.character,"(context)":Bt,"(breakage)":Bt["(breakage)"],"(loopage)":Bt["(loopage)"],"(scope)":Jt,"(statement)":!1,"(metrics)":gt(f.tokens.next),"(catch)":!0,"(tokens)":{},"(blockscope)":Bt["(blockscope)"],"(comparray)":Bt["(comparray)"]},t&&T(t,"exception"),f.tokens.next.value==="if"&&(f.option.inMoz(!0)||b("W118",f.tokens.curr,"catch filter"),k("if"),O(0)),k(")"),f.tokens.curr.funct=Bt,jt.push(Bt),lt(!1),Jt=e,Bt["(last)"]=f.tokens.curr.line,Bt["(lastcharacter)"]=f.tokens.curr.character,Bt=Bt["(context)"]}var t;lt(!1);while(f.tokens.next.id==="catch")yt(),t&&!f.option.inMoz(!0)&&b("W118",f.tokens.next,"multiple catch blocks"),e(),t=!0;if(f.tokens.next.id==="finally"){k("finally"),lt(!1);return}return t||E("E021",f.tokens.next,"catch",f.tokens.next.value),this}),z("while",function(){var e=f.tokens.next;return Bt["(breakage)"]+=1,Bt["(loopage)"]+=1,yt(),k("("),P(this,e),D(),bt(O(0)),k(")",e),D(f.tokens.prev,f.tokens.curr),lt(!0,!0),Bt["(breakage)"]-=1,Bt["(loopage)"]-=1,this}).labelled=!0,z("with",function(){var e=f.tokens.next;return f.directive["use strict"]?E("E010",f.tokens.curr):f.option.withstmt||b("W085",f.tokens.curr),k("("),P(this,e),D(),O(0),k(")",e),D(f.tokens.prev,f.tokens.curr),lt(!0,!0),this}),z("switch",function(){var e=f.tokens.next,t=!1;Bt["(breakage)"]+=1,k("("),P(this,e),D(),bt(O(0)),k(")",e),D(f.tokens.prev,f.tokens.curr),P(f.tokens.curr,f.tokens.next),e=f.tokens.next,k("{"),P(f.tokens.curr,f.tokens.next),Rt+=f.option.indent,this.cases=[];for(;;)switch(f.tokens.next.id){case"case":switch(Bt["(verb)"]){case"yield":case"break":case"case":case"continue":case"return":case"switch":case"throw":break;default:a.fallsThrough.test(f.lines[f.tokens.next.line-2])||b("W086",f.tokens.curr,"case")}B(-f.option.indent),k("case"),this.cases.push(O(20)),yt(),t=!0,k(":"),Bt["(verb)"]="case";break;case"default":switch(Bt["(verb)"]){case"yield":case"break":case"continue":case"return":case"throw":break;default:this.cases.length&&(a.fallsThrough.test(f.lines[f.tokens.next.line-2])||b("W086",f.tokens.curr,"default"))}B(-f.option.indent),k("default"),t=!0,k(":");break;case"}":Rt-=f.option.indent,B(),k("}",e),Bt["(breakage)"]-=1,Bt["(verb)"]=undefined;return;case"(end)":E("E023",f.tokens.next,"}");return;default:if(t)switch(f.tokens.curr.id){case",":E("E040");return;case":":t=!1,at();break;default:E("E025",f.tokens.curr);return}else{if(f.tokens.curr.id!==":"){E("E021",f.tokens.next,"case",f.tokens.next.value);return}k(":"),E("E024",f.tokens.curr,":"),at()}}}).labelled=!0,U("debugger",function(){return f.option.debug||b("W087"),this}).exps=!0,function(){var e=U("do",function(){Bt["(breakage)"]+=1,Bt["(loopage)"]+=1,yt(),this.first=lt(!0,!0),k("while");var e=f.tokens.next;return P(f.tokens.curr,e),k("("),D(),bt(O(0)),k(")",e),D(f.tokens.prev,f.tokens.curr),Bt["(breakage)"]-=1,Bt["(loopage)"]-=1,this});e.labelled=!0,e.exps=!0}(),z("for",function(){var e,t=f.tokens.next,n=!1,i=null;t.value==="each"&&(i=t,k("each"),f.option.inMoz(!0)||b("W118",f.tokens.curr,"for each")),Bt["(breakage)"]+=1,Bt["(loopage)"]+=1,yt(),k("("),P(this,t),D();var s,o=0,u=["in","of"];do s=C(o),++o;while(!r.contains(u,s.value)&&s.value!==";"&&s.type!=="(end)");if(r.contains(u,s.value)){!f.option.inESNext()&&s.value==="of"&&E("W104",s,"for of");if(f.tokens.next.id==="var")k("var"),f.syntax["var"].fud.call(f.syntax["var"].fud,!0);else if(f.tokens.next.id==="let")k("let"),n=!0,Bt["(blockscope)"].stack(),f.syntax.let.fud.call(f.syntax.let.fud,!0);else{switch(Bt[f.tokens.next.value]){case"unused":Bt[f.tokens.next.value]="var";break;case"var":break;default:Bt["(blockscope)"].getlabel(f.tokens.next.value)||b("W088",f.tokens.next,f.tokens.next.value)}k()}k(s.value),O(20),k(")",t),e=lt(!0,!0),f.option.forin&&e&&(e.length>1||typeof e[0]!="object"||e[0].value!=="if")&&b("W089",this),Bt["(breakage)"]-=1,Bt["(loopage)"]-=1}else{i&&E("E045",i);if(f.tokens.next.id!==";")if(f.tokens.next.id==="var")k("var"),f.syntax["var"].fud.call(f.syntax["var"].fud);else if(f.tokens.next.id==="let")k("let"),n=!0,Bt["(blockscope)"].stack(),f.syntax.let.fud.call(f.syntax.let.fud);else for(;;){O(0,"for");if(f.tokens.next.id!==",")break;I()}j(f.tokens.curr),k(";"),f.tokens.next.id!==";"&&bt(O(0)),j(f.tokens.curr),k(";"),f.tokens.next.id===";"&&E("E021",f.tokens.next,")",";");if(f.tokens.next.id!==")")for(;;){O(0,"for");if(f.tokens.next.id!==",")break;I()}k(")",t),D(f.tokens.prev,f.tokens.curr),lt(!0,!0),Bt["(breakage)"]-=1,Bt["(loopage)"]-=1}return n&&Bt["(blockscope)"].unstack(),this}).labelled=!0,U("break",function(){var e=f.tokens.next.value;return Bt["(breakage)"]===0&&b("W052",f.tokens.next,this.value),f.option.asi||j(this),f.tokens.next.id!==";"&&!f.tokens.next.reach&&f.tokens.curr.line===f.tokens.next.line&&(Bt[e]!=="label"?b("W090",f.tokens.next,e):Jt[e]!==Bt&&b("W091",f.tokens.next,e),this.first=f.tokens.next,k()),ot("break"),this}).exps=!0,U("continue",function(){var e=f.tokens.next.value;return Bt["(breakage)"]===0&&b("W052",f.tokens.next,this.value),f.option.asi||j(this),f.tokens.next.id!==";"&&!f.tokens.next.reach?f.tokens.curr.line===f.tokens.next.line&&(Bt[e]!=="label"?b("W090",f.tokens.next,e):Jt[e]!==Bt&&b("W091",f.tokens.next,e),this.first=f.tokens.next,k()):Bt["(loopage)"]||b("W052",f.tokens.next,this.value),ot("continue"),this}).exps=!0,U("return",function(){return this.line===f.tokens.next.line?(f.tokens.next.id==="(regexp)"&&b("W092"),f.tokens.next.id!==";"&&!f.tokens.next.reach&&(P(f.tokens.curr,f.tokens.next),this.first=O(0),this.first&&this.first.type==="(punctuator)"&&this.first.value==="="&&!f.option.boss&&w("W093",this.first.line,this.first.character))):f.tokens.next.type==="(punctuator)"&&["[","{","+","-"].indexOf(f.tokens.next.value)>-1&&j(this),ot("return"),this}).exps=!0,function(e){e.exps=!0,e.lbp=25}(X("yield",function(){var e=f.tokens.prev;return f.option.inESNext(!0)&&!Bt["(generator)"]?E("E046",f.tokens.curr,"yield"):f.option.inESNext()||b("W104",f.tokens.curr,"yield"),Bt["(generator)"]="yielded",this.line===f.tokens.next.line||!f.option.inMoz(!0)?(f.tokens.next.id==="(regexp)"&&b("W092"),f.tokens.next.id!==";"&&!f.tokens.next.reach&&f.tokens.next.nud&&(H(f.tokens.curr,f.tokens.next),this.first=O(10),this.first.type==="(punctuator)"&&this.first.value==="="&&!f.option.boss&&w("W093",this.first.line,this.first.character)),f.option.inMoz(!0)&&f.tokens.next.id!==")"&&(e.lbp>30||!e.assign&&!A()||e.id==="yield")&&E("E050",this)):f.option.asi||j(this),this})),U("throw",function(){return j(this),P(f.tokens.curr,f.tokens.next),this.first=O(20),ot("throw"),this}).exps=!0,U("import",function(){f.option.inESNext()||b("W119",f.tokens.curr,"import");if(f.tokens.next.identifier)this.name=st(),T(this.name,"unused",f.tokens.curr);else{k("{");for(;;){var e;f.tokens.next.type==="default"?(e="default",k("default")):e=st(),f.tokens.next.value==="as"&&(k("as"),e=st()),T(e,"unused",f.tokens.curr);if(f.tokens.next.value!==","){if(f.tokens.next.value==="}"){k("}");break}E("E024",f.tokens.next,f.tokens.next.value);break}k(",")}}return k("from"),k("(string)"),this}).exps=!0,U("export",function(){f.option.inESNext()||b("W119",f.tokens.curr,"export");if(f.tokens.next.type==="default"){k("default");if(f.tokens.next.id==="function"||f.tokens.next.id==="class")this.block=!0;return this.exportee=O(10),this}if(f.tokens.next.value==="{"){k("{");for(;;){st();if(f.tokens.next.value!==","){if(f.tokens.next.value==="}"){k("}");break}E("E024",f.tokens.next,f.tokens.next.value);break}k(",")}return this}return f.tokens.next.id==="var"?(k("var"),f.syntax["var"].fud.call(f.syntax["var"].fud)):f.tokens.next.id==="let"?(k("let"),f.syntax.let.fud.call(f.syntax.let.fud)):f.tokens.next.id==="const"?(k("const"),f.syntax["const"].fud.call(f.syntax["const"].fud)):f.tokens.next.id==="function"?(this.block=!0,k("function"),f.syntax["function"].fud()):f.tokens.next.id==="class"?(this.block=!0,k("class"),f.syntax["class"].fud()):E("E024",f.tokens.next,f.tokens.next.value),this}).exps=!0,J("abstract"),J("boolean"),J("byte"),J("char"),J("class",{es5:!0,nud:St}),J("double"),J("enum",{es5:!0}),J("export",{es5:!0}),J("extends",{es5:!0}),J("final"),J("float"),J("goto"),J("implements",{es5:!0,strictOnly:!0}),J("import",{es5:!0}),J("int"),J("interface",{es5:!0,strictOnly:!0}),J("long"),J("native"),J("package",{es5:!0,strictOnly:!0}),J("private",{es5:!0,strictOnly:!0}),J("protected",{es5:!0,strictOnly:!0}),J("public",{es5:!0,strictOnly:!0}),J("short"),J("static",{es5:!0,strictOnly:!0}),J("super",{es5:!0}),J("synchronized"),J("throws"),J("transient"),J("volatile");var on=function(){var e,t,n=0,i=0,s={};r.contains(["[","{"],f.tokens.curr.value)&&(i+=1),r.contains(["[","{"],f.tokens.next.value)&&(i+=1),r.contains(["]","}"],f.tokens.next.value)&&(i-=1);do{e=C(n),t=C(n+1),n+=1,r.contains(["[","{"],e.value)?i+=1:r.contains(["]","}"],e.value)&&(i-=1);if(e.identifier&&e.value==="for"&&i===1){s.isCompArray=!0,s.notJson=!0;break}if(r.contains(["}","]"],e.value)&&t.value==="="){s.isDestAssign=!0,s.notJson=!0;break}e.value===";"&&(s.isBlock=!0,s.notJson=!0)}while(i>0&&e.id!=="(end)"&&n<15);return s},un=function(){function e(e){var t=s.variables.filter(function(t){if(t.value===e)return t.undef=!1,e}).length;return t!==0}function t(e){var t=s.variables.filter(function(t){if(t.value===e&&!t.undef)return t.unused===!0&&(t.unused=!1),e}).length;return t===0}var n=function(){this.mode="use",this.variables=[]},i=[],s;return{stack:function(){s=new n,i.push(s)},unstack:function(){s.variables.filter(function(e){e.unused&&b("W098",e.token,e.value),e.undef&&y(e.funct,"W117",e.token,e.value)}),i.splice(i[i.length-1],1),s=i[i.length-1]},setState:function(e){r.contains(["use","define","filter"],e)&&(s.mode=e)},check:function(n){return s&&s.mode==="use"?(s.variables.push({funct:Bt,token:f.tokens.curr,value:n,undef:!0,unused:!1}),!0):s&&s.mode==="define"?(e(n)||s.variables.push({funct:Bt,token:f.tokens.curr,value:n,undef:!1,unused:!0}),!0):s&&s.mode==="filter"?(t(n)&&y(Bt,"W117",f.tokens.curr,n),!0):!1}}},an=function(){function e(){for(var e in t)if(t[e]["(type)"]==="unused"&&f.option.unused){var n=t[e]["(token)"],r=n.line,i=n.character;w("W098",r,i,e)}}var t={},n=[t];return{stack:function(){t={},n.push(t)},unstack:function(){e(),n.splice(n.length-1,1),t=r.last(n)},getlabel:function(e){for(var t=n.length-1;t>=0;--t)if(r.has(n[t],e))return n[t]},current:{has:function(e){return r.has(t,e)},add:function(e,n,r){t[e]={"(type)":n,"(token)":r}}}}},fn=function(n,i,o){function a(e,t){if(!e)return;!Array.isArray(e)&&typeof e=="object"&&(e=Object.keys(e)),e.forEach(t)}var l,c,p,v,y={},E={};f.reset(),i&&i.scope?h.scope=i.scope:(h.errors=[],h.undefs=[],h.internals=[],h.blacklist={},h.scope="(main)"),$t=Object.create(null),d($t,s.ecmaIdentifiers),d($t,s.reservedVars),d($t,o||{}),Dt=Object.create(null),Pt=Object.create(null);if(i){a(i.predef||null,function(e){var t,n;e[0]==="-"?(t=e.slice(1),h.blacklist[t]=t):(n=Object.getOwnPropertyDescriptor(i.predef,e),$t[e]=n?n.value:!1)}),a(i.exported||null,function(e){Pt[e]=!0}),delete i.predef,delete i.exported,v=Object.keys(i);for(p=0;p0&&(e.implieds=t),Gt.length>0&&(e.urls=Gt),l=Object.keys(Jt),l.length>0&&(e.globals=l);for(o=1;o0&&(e.unused=Qt),n=[];for(a in Wt)if(typeof Wt[a]=="number"){e.member=Wt;break}return e},fn.jshint=fn,fn}();typeof n=="object"&&n&&(n.JSHINT=h)},{"../shared/messages.js":2,"../shared/vars.js":3,"./lex.js":5,"./reg.js":6,"./state.js":7,"./style.js":8,"console-browserify":9,events:10,underscore:1}],5:[function(e,t,n){function r(){var e=[];return{push:function(t){e.push(t)},check:function(){for(var t=0;t=65&&h<=90||h===95||h>=97&&h<=122;var p=[];for(var h=0;h<128;h++)p[h]=c[h]||h>=48&&h<=57;i.prototype={_lines:[],getLines:function(){return this._lines=a.lines,this._lines},setLines:function(e){this._lines=e,a.lines=this._lines},peek:function(e){return this.input.charAt(e||0)},skip:function(e){e=e||1,this.char+=e,this.input=this.input.slice(e)},on:function(e,t){e.split(" ").forEach(function(e){this.emitter.on(e,t)}.bind(this))},trigger:function(){this.emitter.emit.apply(this.emitter,Array.prototype.slice.call(arguments))},triggerAsync:function(e,t,n,r){n.push(function(){r()&&this.trigger(e,t)}.bind(this))},scanPunctuator:function(){var e=this.peek(),t,n,r;switch(e){case".":if(/^[0-9]$/.test(this.peek(1)))return null;if(this.peek(1)==="."&&this.peek(2)===".")return{type:f.Punctuator,value:"..."};case"(":case")":case";":case",":case"{":case"}":case"[":case"]":case":":case"~":case"?":return{type:f.Punctuator,value:e};case"#":return{type:f.Punctuator,value:e};case"":return null}return t=this.peek(1),n=this.peek(2),r=this.peek(3),e===">"&&t===">"&&n===">"&&r==="="?{type:f.Punctuator,value:">>>="}:e==="="&&t==="="&&n==="="?{type:f.Punctuator,value:"==="}:e==="!"&&t==="="&&n==="="?{type:f.Punctuator,value:"!=="}:e===">"&&t===">"&&n===">"?{type:f.Punctuator,value:">>>"}:e==="<"&&t==="<"&&n==="="?{type:f.Punctuator,value:"<<="}:e===">"&&t===">"&&n==="="?{type:f.Punctuator,value:">>="}:e==="="&&t===">"?{type:f.Punctuator,value:e+t}:e===t&&"+-<>&|".indexOf(e)>=0?{type:f.Punctuator,value:e+t}:"<>=!+-*%&|^".indexOf(e)>=0?t==="="?{type:f.Punctuator,value:e+t}:{type:f.Punctuator,value:e}:e==="/"?t==="="&&/\/=(?!(\S*\/[gim]?))/.test(this.input)?{type:f.Punctuator,value:"/="}:{type:f.Punctuator,value:"/"}:null},scanComments:function(){function e(e,t,n){var r=["jshint","jslint","members","member","globals","global","exported"],i=!1,s=e+t,o="plain";return n=n||{},n.isMultiline&&(s+="*/"),r.forEach(function(n){if(i)return;if(e==="//"&&n!=="jshint")return;t.substr(0,n.length)===n&&(i=!0,e+=n,t=t.substr(n.length)),!i&&t.charAt(0)===" "&&t.substr(1,n.length)===n&&(i=!0,e=e+" "+n,t=t.substr(n.length+1));if(!i)return;switch(n){case"member":o="members";break;case"global":o="globals";break;default:o=n}}),{type:f.Comment,commentType:o,value:s,body:t,isSpecial:i,isMultiline:n.isMultiline||!1,isMalformed:n.isMalformed||!1}}var t=this.peek(),n=this.peek(1),r=this.input.substr(2),i=this.line,s=this.char;if(t==="*"&&n==="/")return this.trigger("error",{code:"E018",line:i,character:s}),this.skip(2),null;if(t!=="/"||n!=="*"&&n!=="/")return null;if(n==="/")return this.skip(this.input.length),e("//",r);var o="";if(n==="*"){this.skip(2);while(this.peek()!=="*"||this.peek(1)!=="/")if(this.peek()===""){o+="\n";if(!this.nextLine())return this.trigger("error",{code:"E017",line:i,character:s}),e("/*",o,{isMultiline:!0,isMalformed:!0})}else o+=this.peek(),this.skip();return this.skip(2),e("/*",o,{isMultiline:!0})}},scanKeyword:function(){var e=/^[a-zA-Z_$][a-zA-Z0-9_$]*/.exec(this.input),t=["if","in","do","var","for","new","try","let","this","else","case","void","with","enum","while","break","catch","throw","const","yield","class","super","return","typeof","delete","switch","export","import","default","finally","extends","function","continue","debugger","instanceof"];return e&&t.indexOf(e[0])>=0?{type:f.Keyword,value:e[0]}:null},scanIdentifier:function(){function e(e){for(var t=0;t="a"&&e<="z"||e>="A"&&e<="Z"}var i=0,s="",o=this.input.length,u=this.peek(i),a;if(u!=="."&&!e(u))return null;if(u!=="."){s=this.peek(i),i+=1,u=this.peek(i);if(s==="0"){if(u==="x"||u==="X"){i+=1,s+=u;while(i"]});if(o==="\\"){this.skip(),o=this.peek();switch(o){case"'":this.triggerAsync("warning",{code:"W114",line:this.line,character:this.char,data:["\\'"]},e,function(){return a.jsonMode});break;case"b":o="\b";break;case"f":o="\f";break;case"n":o="\n";break;case"r":o="\r";break;case"t":o=" ";break;case"0":o="\0";var l=parseInt(this.peek(1),10);this.triggerAsync("warning",{code:"W115",line:this.line,character:this.char},e,function(){return l>=0&&l<=7&&a.directive["use strict"]});break;case"u":o=String.fromCharCode(parseInt(this.input.substr(1,4),16)),u=5;break;case"v":this.triggerAsync("warning",{code:"W114",line:this.line,character:this.char,data:["\\v"]},e,function(){return a.jsonMode}),o="";break;case"x":var c=parseInt(this.input.substr(1,2),16);this.triggerAsync("warning",{code:"W114",line:this.line,character:this.char,data:["\\x-"]},e,function(){return a.jsonMode}),o=String.fromCharCode(c),u=3;break;case"\\":case'"':case"/":break;case"":s=!0,o="";break;case"!":if(n.slice(n.length-2)==="<")break;default:this.trigger("warning",{code:"W044",line:this.line,character:this.char})}}n+=o,this.skip(u)}return this.skip(),{type:f.StringLiteral,value:n,isUnclosed:!1,quote:t}},scanRegExp:function(){var e=0,t=this.input.length,n=this.peek(),r=n,i="",s=[],o=!1,u=!1,a,l=function(){n<" "&&(o=!0,this.trigger("warning",{code:"W048",line:this.line,character:this.char})),n==="<"&&(o=!0,this.trigger("warning",{code:"W049",line:this.line,character:this.char,data:[n]}))}.bind(this);if(!this.prereg||n!=="/")return null;e+=1,a=!1;while(e=this.getLines().length?!1:(this.input=this.getLines()[this.line],this.line+=1,this.char=1,this.from=1,e=this.scanMixedSpacesAndTabs(),e>=0&&this.trigger("warning",{code:"W099",line:this.line,character:e+1}),this.input=this.input.replace(/\t/g,a.tab),e=this.scanUnsafeChars(),e>=0&&this.trigger("warning",{code:"W100",line:this.line,character:e}),a.option.maxlen&&a.option.maxlen-1&&!t.name.match(/^[A-Z0-9_]*$/)&&e.warn("W106",{line:t.line,"char":t.from,data:[t.name]})}),e.on("String",function(t){var n=e.getOption("quotmark"),r;if(!n)return;n==="single"&&t.quote!=="'"&&(r="W109"),n==="double"&&t.quote!=='"'&&(r="W108"),n===!0&&(e.getCache("quotmark")||e.setCache("quotmark",t.quote),e.getCache("quotmark")!==t.quote&&(r="W110")),r&&e.warn(r,{line:t.line,"char":t.char})}),e.on("Number",function(t){t.value.charAt(0)==="."&&e.warn("W008",{line:t.line,"char":t.char,data:[t.value]}),t.value.substr(t.value.length-1)==="."&&e.warn("W047",{line:t.line,"char":t.char,data:[t.value]}),/^00+/.test(t.value)&&e.warn("W046",{line:t.line,"char":t.char,data:[t.value]})}),e.on("String",function(t){var n=/^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i;if(e.getOption("scripturl"))return;n.test(t.value)&&e.warn("W107",{line:t.line,"char":t.char})})}},{}],9:[function(e,t,n){},{}],10:[function(e,t,n){function r(e,t){if(e.indexOf)return e.indexOf(t);for(var n=0;n0&&this._events[e].length>n&&(this._events[e].warned=!0,console.error("(node) warning: possible EventEmitter memory leak detected. %d listeners added. Use emitter.setMaxListeners() to increase limit.",this._events[e].length),console.trace())}this._events[e].push(t)}else this._events[e]=[this._events[e],t];return this},s.prototype.on=s.prototype.addListener,s.prototype.once=function(e,t){var n=this;return n.on(e,function r(){n.removeListener(e,r),t.apply(this,arguments)}),this},s.prototype.removeListener=function(e,t){if("function"!=typeof t)throw new Error("removeListener only takes instances of Function");if(!this._events||!this._events[e])return this;var n=this._events[e];if(o(n)){var i=r(n,t);if(i<0)return this;n.splice(i,1),n.length==0&&delete this._events[e]}else this._events[e]===t&&delete this._events[e];return this},s.prototype.removeAllListeners=function(e){return arguments.length===0?(this._events={},this):(e&&this._events&&this._events[e]&&(this._events[e]=null),this)},s.prototype.listeners=function(e){return this._events||(this._events={}),this._events[e]||(this._events[e]=[]),o(this._events[e])||(this._events[e]=[this._events[e]]),this._events[e]},s.listenerCount=function(e,t){var n;return!e._events||!e._events[t]?n=0:typeof e._events[t]=="function"?n=1:n=e._events[t].length,n}},{__browserify_process:11}],11:[function(e,t,n){var r=t.exports={};r.nextTick=function(){var e=typeof window!="undefined"&&window.setImmediate,t=typeof window!="undefined"&&window.postMessage&&window.addEventListener;if(e)return function(e){return window.setImmediate(e)};if(t){var n=[];return window.addEventListener("message",function(e){if(e.source===window&&e.data==="process-tick"){e.stopPropagation();if(n.length>0){var t=n.shift();t()}}},!0),function(e){n.push(e),window.postMessage("process-tick","*")}}return function(e){setTimeout(e,0)}}(),r.title="browser",r.browser=!0,r.env={},r.argv=[],r.binding=function(e){throw new Error("process.binding is not supported")},r.cwd=function(){return"/"},r.chdir=function(e){throw new Error("process.chdir is not supported")}},{}],jshint:[function(e,t,n){t.exports=e("n4bKNg")},{}]},{},["n4bKNg"]),n.exports=r("jshint")})
\ No newline at end of file
diff --git a/app/collections/DocumentFiles.coffee b/app/collections/DocumentFiles.coffee
new file mode 100644
index 000000000..d78c41bd9
--- /dev/null
+++ b/app/collections/DocumentFiles.coffee
@@ -0,0 +1,8 @@
+CocoCollection = require 'models/CocoCollection'
+
+module.exports = class ModelFiles extends CocoCollection
+ constructor: (model) ->
+ super()
+ url = model.constructor.prototype.urlRoot
+ url += "/#{model.get('original') or model.id}/files"
+ @url = url
\ No newline at end of file
diff --git a/app/initialize.coffee b/app/initialize.coffee
new file mode 100644
index 000000000..e076f2ec3
--- /dev/null
+++ b/app/initialize.coffee
@@ -0,0 +1,32 @@
+app = require 'application'
+
+$ ->
+ app.initialize()
+ Backbone.history.start({ pushState: true })
+ handleNormalUrls()
+
+ treemaExt = require 'treema-ext'
+ treemaExt.setup()
+ filepicker.setKey('AvlkNoldcTOU4PvKi2Xm7z')
+
+handleNormalUrls = ->
+ # http://artsy.github.com/blog/2012/06/25/replacing-hashbang-routes-with-pushstate/
+ $(document).on "click", "a[href^='/']", (event) ->
+
+ href = $(event.currentTarget).attr('href')
+
+ # chain 'or's for other black list routes
+ passThrough = href.indexOf('sign_out') >= 0
+
+ # Allow shift+click for new tabs, etc.
+ if !passThrough && !event.altKey && !event.ctrlKey && !event.metaKey && !event.shiftKey
+ event.preventDefault()
+
+ # Remove leading slashes and hash bangs (backward compatablility)
+ url = href.replace(/^\//,'').replace('\#\!\/','')
+
+ # Instruct Backbone to trigger routing events
+ app.router.navigate url, { trigger: true }
+
+ return false
+
diff --git a/app/lib/AudioPlayer.coffee b/app/lib/AudioPlayer.coffee
new file mode 100644
index 000000000..7f4e2a388
--- /dev/null
+++ b/app/lib/AudioPlayer.coffee
@@ -0,0 +1,132 @@
+CocoClass = require 'lib/CocoClass'
+cache = {}
+{me} = require('lib/auth')
+
+# Top 20 obscene words (plus 'fiddlesticks') will trigger swearing Simlish with *beeps*.
+# Didn't like leaving so much profanity lying around in the source, so rot13'd.
+rot13 = (s) -> s.replace /[A-z]/g, (c) -> String.fromCharCode c.charCodeAt(0) + (if c.toUpperCase() <= "M" then 13 else -13)
+swears = (rot13 s for s in ["nefrubyr", "nffubyr", "onfgneq", "ovgpu", "oybbql", "obyybpxf", "ohttre", "pbpx", "penc", "phag", "qnza", "qnea", "qvpx", "qbhpur", "snt", "shpx", "cvff", "chffl", "fuvg", "fyhg", "svqqyrfgvpxf"])
+
+createjs.Sound.registerPlugins([createjs.WebAudioPlugin, createjs.FlashPlugin, createjs.HTMLAudioPlugin])
+
+class Manifest
+ constructor: -> @storage = {}
+
+ add: (filename, group='misc') ->
+ name = name or filename
+ @storage[group] = [] unless @storage[group]?
+ return if filename in @storage[group]
+ @storage[group].push(filename)
+
+ addPrimarySound: (filename) -> @add(filename, 'primarySounds')
+ addSecondarySound: (filename) -> @add(filename, 'secondarySounds')
+ getData: -> return @storage
+
+class Media
+ constructor: (name) -> @name = name if name
+
+ loaded: false
+ data: null
+ progress: 0.0
+ error: null
+ name: ''
+
+class AudioPlayer extends CocoClass
+ subscriptions:
+ 'play-sound': (e) -> @playInterfaceSound e.trigger
+
+ constructor: () ->
+ super()
+ @ext = if createjs.Sound.getCapability('mp3') then '.mp3' else '.ogg'
+ @listenToSound()
+ @createNewManifest()
+ @soundsToPlayWhenLoaded = {}
+
+ createNewManifest: ->
+ @manifest = new Manifest()
+
+ listenToSound: ->
+ # I would like to go through PreloadJS to organize loading by queue, but
+ # when I try to set it up, I get an error with the Sound plugin.
+ # So for now, we'll just load through SoundJS instead.
+ createjs.Sound.on 'fileload', @onSoundLoaded
+
+ # PUBLIC LOADING METHODS
+
+ soundForDialogue: (message, soundTriggers) ->
+ if _.isArray message then message = message.join ' '
+ return message unless _.isString message
+ return null unless say = soundTriggers?.say
+ message = _.string.slugify message
+ return sound if sound = say[message]
+ defaults = say.defaultSimlish
+ if say.swearingSimlish?.length and _.find(swears, (s) -> message.search(s) isnt -1)
+ defaults = say.swearingSimlish
+ return null unless defaults?.length
+ return defaults[message.length % defaults.length]
+
+ preloadInterfaceSounds: (names) ->
+ for name in names
+ filename = "/file/interface/#{name}#{@ext}"
+ @preloadSound filename, name
+
+ playInterfaceSound: (name) ->
+ filename = "/file/interface/#{name}#{@ext}"
+ if filename of cache and createjs.Sound.loadComplete filename
+ @playSound name
+ createjs.Sound.play name
+ else
+ @preloadInterfaceSounds [name] unless filename of cache
+ @soundsToPlayWhenLoaded[name] = true
+
+ playSound: (name) ->
+ createjs.Sound.play name, {volume: me.get('volume')}
+
+# # TODO: load Interface sounds somehow, somewhere, somewhen
+
+ preloadSoundReference: (sound) ->
+ name = @nameForSoundReference sound
+ filename = '/file/' + name
+ @preloadSound filename, name
+ filename
+
+ nameForSoundReference: (sound) ->
+ sound[@ext.slice(1)] # mp3 or ogg
+
+ preloadSound: (filename, name) ->
+ return unless filename
+ return if filename of cache
+ name ?= filename
+ # SoundJS flips out if you try to register the same file twice
+ createjs.Sound.registerSound(filename, name, 1, true) # 1: 1 channel, true: should preload
+ cache[filename] = new Media(name)
+
+ # PROGRESS CALLBACKS
+
+ onSoundLoaded: (e) =>
+ media = cache[e.src]
+ return if not media
+ media.loaded = true
+ media.progress = 1.0
+ if @soundsToPlayWhenLoaded[media.name]
+ @playSound media.name
+ @soundsToPlayWhenLoaded[media.name] = false
+ @notifyProgressChanged()
+
+ onSoundLoadError: (e) =>
+ console.error 'Could not load sound', e
+
+ notifyProgressChanged: ->
+ Backbone.Mediator.publish('audio-player:loaded', {sender:@})
+
+ getStatus: (src) ->
+ return cache[src] or null
+
+
+module.exports = new AudioPlayer()
+
+average = (numbers) ->
+ return 0 if numbers.length is 0
+ sum = 0
+ sum += num for num in numbers
+ return sum / numbers.length
diff --git a/app/lib/Bus.coffee b/app/lib/Bus.coffee
new file mode 100644
index 000000000..63caba7f0
--- /dev/null
+++ b/app/lib/Bus.coffee
@@ -0,0 +1,134 @@
+CocoClass = require 'lib/CocoClass'
+
+{me} = require 'lib/auth'
+
+CHAT_SIZE_LIMIT = 500 # no more than 500 messages
+
+module.exports = Bus = class Bus extends CocoClass
+ joined: null
+ players: null
+
+ @get: (docName) -> return @getFromCache or new Bus docName
+ @getFromCache: (docName) -> return Bus.activeBuses[docName]
+ @activeBuses: {}
+ @fireHost: 'https://codecombat.firebaseio.com'
+
+ constructor: (@docName) ->
+ super()
+ @players = {}
+ Bus.activeBuses[@docName] = @
+
+ subscriptions:
+ 'level-bus-echo-states': 'onEchoStates'
+ 'me:synced': 'onMeSynced'
+
+ onEchoStates: ->
+ @notifyStateChanges()
+
+ connect: ->
+ Backbone.Mediator.publish 'bus:connecting', {bus:@}
+ Firebase.goOnline()
+ @fireRef = new Firebase(Bus.fireHost + "/" + @docName)
+ @fireRef.once 'value', @onFireOpen
+
+ onFireOpen: (snapshot) =>
+ if @destroyed
+ console.log("Leaving '#{@docName}' because class has been destroyed.")
+ return
+ @init()
+ Backbone.Mediator.publish 'bus:connected', {bus:@}
+
+ disconnect: ->
+ Firebase.goOffline()
+ @fireRef?.off()
+ @fireRef = null
+ @fireChatRef?.off()
+ @fireChatRef = null
+ @firePlayersRef?.off()
+ @firePlayersRef = null
+ @myConnection?.off()
+ @myConnection = null
+ @joined = false
+ Backbone.Mediator.publish 'bus:disconnected', {bus:@}
+
+ init: ->
+ """
+ Init happens when we're connected.
+ """
+ @fireChatRef = @fireRef.child('chat')
+ @firePlayersRef = @fireRef.child('players')
+ @join()
+ @listenForChanges()
+ @sendMessage("/me joined.", true)
+
+ join: ->
+ @joined = true
+ @myConnection = @firePlayersRef.child(me.id)
+ @myConnection.set({id: me.id, name: me.get('name'), connected: true})
+ @onDisconnect = @myConnection.child('connected').onDisconnect()
+ @onDisconnect.set(false)
+
+ listenForChanges: ->
+ @fireChatRef.limit(CHAT_SIZE_LIMIT).on 'child_added', @onChatAdded
+ @firePlayersRef.on 'child_added', @onPlayerJoined
+ @firePlayersRef.on 'child_removed', @onPlayerLeft
+ @firePlayersRef.on 'child_changed', @onPlayerChanged
+
+ onChatAdded: (snapshot) =>
+ Backbone.Mediator.publish('bus:new-message', {message:snapshot.val(), bus:@})
+
+ onPlayerJoined: (snapshot) =>
+ player = snapshot.val()
+ return unless player.connected
+ @players[player.id] = player
+ Backbone.Mediator.publish('bus:player-joined', {player:player, bus:@})
+
+ onPlayerLeft: (snapshot) =>
+ val = snapshot.val()
+ return unless val
+ player = @players[val.id]
+ return unless player
+ delete @players[player.id]
+ Backbone.Mediator.publish('bus:player-left', {player:player, bus:@})
+
+ onPlayerChanged: (snapshot) =>
+ player = snapshot.val()
+ wasConnected = @players[player.id]?.connected
+ @players[player.id] = player
+ @onPlayerLeft(snapshot) if wasConnected and not player.connected
+ @onPlayerJoined(snapshot) if player.connected and not wasConnected
+ Backbone.Mediator.publish('bus:player-states-changed', {states:@players, bus:@})
+
+ onMeSynced: =>
+ @myConnection?.child('name').set(me.get('name'))
+
+ countPlayers: -> _.size(@players)
+
+ onPoint: ->
+ # simple way to elect somone to do jobs that don't need to be done by each player
+ ids = _.keys(@players)
+ ids.sort()
+ return ids[0] is me.id
+
+ # MESSAGING
+
+ sendSystemMessage: (content) ->
+ @sendMessage(content, true)
+
+ sendMessage: (content, system=false) ->
+ MAX_MESSAGE_LENGTH = 400
+ message =
+ content:content[... MAX_MESSAGE_LENGTH]
+ authorName:me.displayName()
+ authorID:me.id
+ dateMade: new Date()
+ message.system = system if system
+ @fireChatRef.push(message)
+
+ # TEARDOWN
+
+ destroy: ->
+ @sendMessage("/me left.", true) if @joined
+ delete Bus.activeBuses[@docName] if @docName of Bus.activeBuses
+ @disconnect()
+ super()
diff --git a/app/lib/CocoClass.coffee b/app/lib/CocoClass.coffee
new file mode 100644
index 000000000..f06851d29
--- /dev/null
+++ b/app/lib/CocoClass.coffee
@@ -0,0 +1,60 @@
+# Template for classes with common functions, like hooking into the Mediator.
+utils = require './utils'
+classCount = 0
+makeScopeName = -> "class-scope-#{classCount++}"
+
+module.exports = class CocoClass
+ subscriptions: {}
+ shortcuts: {}
+
+ # setup/teardown
+
+ constructor: ->
+ @subscriptions = utils.combineAncestralObject(@, 'subscriptions')
+ @shortcuts = utils.combineAncestralObject(@, 'shortcuts')
+ @listenToSubscriptions()
+ @scope = makeScopeName()
+ @listenToShortcuts()
+ _.extend(@, Backbone.Events) if Backbone?
+
+ destroy: ->
+ # teardown subscriptions, prevent new ones
+ @destroyed = true
+ @stopListening?()
+ @unsubscribeAll()
+ @stopListeningToShortcuts()
+
+ # subscriptions
+
+ listenToSubscriptions: ->
+ # for initting subscriptions
+ return unless Backbone?.Mediator?
+ for channel, func of @subscriptions
+ func = utils.normalizeFunc(func, @)
+ Backbone.Mediator.subscribe(channel, func, @)
+
+ addNewSubscription: (channel, func) ->
+ # this is for adding subscriptions on the fly, rather than at init
+ return unless Backbone?.Mediator?
+ return if @destroyed
+ return unless @subscriptions[channel] is undefined
+ func = utils.normalizeFunc(func, @)
+ @subscriptions[channel] = func
+ Backbone.Mediator.subscribe(channel, func, @)
+
+ unsubscribeAll: ->
+ for channel, func of @subscriptions
+ func = utils.normalizeFunc(func, @)
+ Backbone.Mediator.unsubscribe(channel, func, @)
+
+ # keymaster shortcuts
+
+ listenToShortcuts: ->
+ return unless key?
+ for shortcut, func of @shortcuts
+ func = utils.normalizeFunc(func, @)
+ key(shortcut, @scope, _.bind(func, @))
+
+ stopListeningToShortcuts: ->
+ return unless key?
+ key.deleteScope(@scope)
\ No newline at end of file
diff --git a/app/lib/FacebookHandler.coffee b/app/lib/FacebookHandler.coffee
new file mode 100644
index 000000000..b1227c821
--- /dev/null
+++ b/app/lib/FacebookHandler.coffee
@@ -0,0 +1,63 @@
+CocoClass = require 'lib/CocoClass'
+{me, CURRENT_USER_KEY} = require 'lib/auth'
+{backboneFailure} = require 'lib/errors'
+{saveObjectToStorage} = require 'lib/storage'
+
+# facebook user object props to
+userPropsToSave =
+ 'first_name': 'firstName'
+ 'last_name': 'lastName'
+ 'gender': 'gender'
+ 'email': 'email'
+ 'id': 'facebookID'
+
+
+module.exports = FacebookHandler = class FacebookHandler extends CocoClass
+ constructor: ->
+ super()
+
+ subscriptions:
+ 'facebook-logged-in':'onFacebookLogin'
+ 'facebook-logged-out': 'onFacebookLogout'
+
+ onFacebookLogin: (e) =>
+ # user is logged in also when the page first loads, so check to see
+ # if we really need to do the lookup
+ return if not me
+
+ doIt = false
+ @authResponse = e.response.authResponse
+ for fbProp, userProp of userPropsToSave
+ unless me.get(userProp)
+ doIt = true
+ break
+ FB.api('/me', @onReceiveMeInfo) if doIt
+
+ onFacebookLogout: (e) =>
+ console.warn('On facebook logout not implemented.')
+
+ onReceiveMeInfo: (r) =>
+ unless r.email
+ console.error('could not get data, since no email provided')
+ return
+
+ oldEmail = me.get('email')
+ me.set('firstName', r.first_name) if r.first_name
+ me.set('lastName', r.last_name) if r.last_name
+ me.set('gender', r.gender) if r.gender
+ me.set('email', r.email) if r.email
+ me.set('facebookID', r.id) if r.id
+
+ Backbone.Mediator.publish('logging-in-with-facebook')
+ window.tracker?.trackEvent 'Facebook Login'
+ window.tracker?.identify()
+ me.save({}, {
+ error: backboneFailure,
+ url: "/db/user?facebookID=#{r.id}&facebookAccessToken=#{@authResponse.accessToken}"
+ success: (model) ->
+ saveObjectToStorage(CURRENT_USER_KEY, model.attributes)
+ window.location.reload() if model.get('email') isnt oldEmail
+ })
+
+ destroy: ->
+ super()
diff --git a/app/lib/GPlusHandler.coffee b/app/lib/GPlusHandler.coffee
new file mode 100644
index 000000000..da82ead4e
--- /dev/null
+++ b/app/lib/GPlusHandler.coffee
@@ -0,0 +1,76 @@
+CocoClass = require 'lib/CocoClass'
+{me, CURRENT_USER_KEY} = require 'lib/auth'
+{backboneFailure} = require 'lib/errors'
+{saveObjectToStorage} = require 'lib/storage'
+
+# gplus user object props to
+userPropsToSave =
+ 'name.givenName': 'firstName'
+ 'name.familyName': 'lastName'
+ 'gender': 'gender'
+ 'id': 'gplusID'
+
+fieldsToFetch = 'displayName,gender,image,name(familyName,givenName),id'
+plusURL = '/plus/v1/people/me?fields='+fieldsToFetch
+revokeUrl = 'https://accounts.google.com/o/oauth2/revoke?token='
+clientID = "800329290710-j9sivplv2gpcdgkrsis9rff3o417mlfa.apps.googleusercontent.com"
+
+module.exports = GPlusHandler = class GPlusHandler extends CocoClass
+ constructor: ->
+ super()
+
+ subscriptions:
+ 'gplus-logged-in':'onGPlusLogin'
+
+ onGPlusLogin: (e) =>
+ return if e._aa # this seems to show that it was auto generated on page load
+ return if not me
+ @accessToken = e.access_token
+
+ # email and profile data loaded separately
+ @responsesComplete = 0
+ gapi.client.request(path:plusURL, callback:@onPersonEntityReceived)
+ gapi.client.load('oauth2', 'v2', =>
+ gapi.client.oauth2.userinfo.get().execute(@onEmailReceived))
+
+ shouldSave: false
+ responsesComplete: 0
+
+ onPersonEntityReceived: (r) =>
+ for gpProp, userProp of userPropsToSave
+ keys = gpProp.split('.')
+ value = r
+ value = value[key] for key in keys
+ if value and not me.get(userProp)
+ @shouldSave = true
+ me.set(userProp, value)
+
+ @responsesComplete += 1
+ @saveIfAllDone()
+
+ onEmailReceived: (r) =>
+ newEmail = r.email and r.email isnt me.get('email')
+ return unless newEmail or me.get('anonymous')
+ me.set('email', r.email)
+ @shouldSave = true
+ @responsesComplete += 1
+ @saveIfAllDone()
+
+ saveIfAllDone: =>
+ return unless @responsesComplete is 2
+ return unless me.get('email') and me.get('gplusID')
+
+ Backbone.Mediator.publish('logging-in-with-gplus')
+ gplusID = me.get('gplusID')
+ window.tracker?.trackEvent 'Google Login'
+ window.tracker?.identify()
+ me.save({}, {
+ error: backboneFailure,
+ url: "/db/user?gplusID=#{gplusID}&gplusAccessToken=#{@accessToken}"
+ success: (model) ->
+ saveObjectToStorage(CURRENT_USER_KEY, model.attributes)
+ window.location.reload()
+ })
+
+ destroy: ->
+ super()
diff --git a/app/lib/God.coffee b/app/lib/God.coffee
new file mode 100644
index 000000000..f956b16fb
--- /dev/null
+++ b/app/lib/God.coffee
@@ -0,0 +1,224 @@
+{now} = require './world/world_utils'
+
+## Uncomment to imitate IE9 (and in world_utils.coffee)
+#window.Worker = null
+#window.Float32Array = null
+# (except that we won't have included vendor_with_box2d.js, so Collision won't run, things won't move, etc.)
+
+module.exports = class God
+ @ids: ['Athena', 'Baldr', 'Crom', 'Dagr', 'Eris', 'Freyja', 'Great Gish', 'Hades', 'Ishtar', 'Janus', 'Khronos', 'Loki', 'Marduk', 'Negafook', 'Odin', 'Poseidon', 'Quetzalcoatl', 'Ra', 'Shiva', 'Thor', 'Umvelinqangi', 'Týr', 'Vishnu', 'Wepwawet', 'Xipe Totec', 'Yahweh', 'Zeus', '上帝', 'Tiamat', '盘古', 'Phoebe', 'Artemis', 'Osiris', "嫦娥", 'Anhur', 'Teshub', 'Enlil', 'Perkele', 'Aether', 'Chaos', 'Hera', 'Iris', 'Theia', 'Uranus', 'Stribog', 'Sabazios', 'Izanagi', 'Ao', 'Tāwhirimātea', 'Tengri', 'Inmar', 'Torngarsuk', 'Centzonhuitznahua', 'Hunab Ku', 'Apollo', 'Helios', 'Thoth', 'Hyperion', 'Alectrona', 'Eos', 'Mitra', 'Saranyu', 'Freyr', 'Koyash', 'Atropos', 'Clotho', 'Lachesis', 'Tyche', 'Skuld', 'Urðr', 'Verðandi', 'Camaxtli', 'Huhetotl', 'Set', 'Anu', 'Allah', 'Anshar', 'Hermes', 'Lugh', 'Brigit', 'Manannan Mac Lir', 'Persephone', 'Mercury', 'Venus', 'Mars', 'Azrael', 'He-Man', 'Anansi', 'Issek', 'Mog', 'Kos', 'Amaterasu Omikami', 'Raijin', 'Susanowo', 'Blind Io', 'The Lady', 'Offler', 'Ptah', 'Anubis', 'Ereshkigal', 'Nergal', 'Thanatos', 'Macaria', 'Angelos', 'Erebus', 'Hecate', 'Hel', 'Orcus', 'Ishtar-Deela Nakh', 'Prometheus', 'Hephaestos', 'Sekhmet', 'Ares', 'Enyo', 'Otrera', 'Pele', 'Hadúr', 'Hachiman', 'Dayisun Tngri', 'Ullr', 'Lua', 'Minerva']
+ @nextID: ->
+ @lastID = (if @lastID? then @lastID + 1 else Math.floor(@ids.length * Math.random())) % @ids.length
+ @ids[@lastID]
+
+ maxAngels: 2 # how many concurrent web workers to use; if set past 8, make up more names
+ worldWaiting: false # whether we're waiting for a worker to free up and run the world
+ constructor: (@world, @level) ->
+ @id = God.nextID()
+ @angels = []
+ @firstWorld = true
+ Backbone.Mediator.subscribe 'tome:cast-spells', @onTomeCast, @
+
+ onTomeCast: (e) ->
+ return if @dead
+ @spells = e.spells
+ @createWorld()
+
+ getAngel: ->
+ for angel in @angels
+ return angel.enslave() unless angel.busy
+ maxedOut = @angels.length is @maxAngels
+ if not maxedOut
+ angel = new Angel @
+ @angels.push angel
+ return angel.enslave()
+ oldestAngel = {started: new Date(2099, 1, 1)}
+ for angel in @angels
+ oldestAngel = angel if angel.started < oldestAngel.started
+ oldestAngel.abort()
+ null
+
+ angelInfinitelyLooped: (angel) ->
+ return if @dead
+ problem = type: "runtime", level: "error", id: "runtime_InfiniteLoop", message: "Code never finished. It's either really slow or has an infinite loop."
+ Backbone.Mediator.publish 'god:user-code-problem', problem: problem
+ Backbone.Mediator.publish 'god:infinite-loop', firstWorld: @firstWorld
+
+ angelAborted: (angel) ->
+ return unless @worldWaiting and not @dead
+ @createWorld()
+
+ angelUserCodeProblem: (angel, problem) ->
+ return if @dead
+ #console.log "UserCodeProblem:", '"' + problem.message + '"', "for", problem.userInfo.thangID, "-", problem.userInfo.methodName, 'at line', problem.ranges?[0][0][0], 'column', problem.ranges?[0][0][1]
+ Backbone.Mediator.publish 'god:user-code-problem', problem: problem
+
+ createWorld: ->
+ #console.log @id + ': "Let there be light upon', @world.name + '!"'
+ unless Worker? # profiling world simulation is easier on main thread, or we are IE9
+ setTimeout @simulateWorld, 1
+ return
+
+ angel = @getAngel()
+ if angel
+ @worldWaiting = false
+ else
+ @worldWaiting = true
+ return
+ angel.worker.postMessage {func: 'runWorld', args: {
+ worldName: @world.name
+ userCodeMap: @getUserCodeMap()
+ level: @level
+ firstWorld: @firstWorld
+ goals: @goalManager?.getGoals()
+ }}
+
+ beholdWorld: (angel, serialized, goalStates) ->
+ worldCreation = angel.started
+ angel.free()
+ return if @latestWorldCreation? and worldCreation < @latestWorldCreation
+ @latestWorldCreation = worldCreation
+ @latestGoalStates = goalStates
+ window.BOX2D_ENABLED = false # Flip this off so that if we have box2d in the namespace, the Collides Components still don't try to create bodies for deserialized Thangs upon attachment
+ @world.constructor.deserialize serialized, @world.classMap, @lastSerializedWorldFrames, worldCreation, @finishBeholdingWorld
+ window.BOX2D_ENABLED = true
+ @lastSerializedWorldFrames = serialized.frames
+
+ finishBeholdingWorld: (newWorld) =>
+ newWorld.findFirstChangedFrame @world
+ @world = newWorld
+ errorCount = (t for t in @world.thangs when t.errorsOut).length
+ Backbone.Mediator.publish('god:new-world-created', world: @world, firstWorld: @firstWorld, errorCount: errorCount, goalStates: @latestGoalStates)
+ for scriptNote in @world.scriptNotes
+ Backbone.Mediator.publish scriptNote.channel, scriptNote.event
+ @firstWorld = false
+
+ getUserCodeMap: ->
+ userCodeMap = {}
+ for spellKey, spell of @spells
+ for thangID, spellThang of spell.thangs
+ (userCodeMap[thangID] ?= {})[spell.name] = spellThang.aether.serialize()
+ userCodeMap
+
+ destroy: ->
+ angel.destroy() for angel in @angels
+ @dead = true
+ Backbone.Mediator.unsubscribe('tome:cast-spells', @onTomeCast, @)
+ @goalManager = null
+
+ #### Bad code for running worlds on main thread (profiling / IE9) ####
+ simulateWorld: =>
+ if Worker?
+ console?.profile? "World Generation #{(Math.random() * 1000).toFixed(0)}"
+ @t0 = now()
+ @testWorld = new @world.constructor @world.name, @getUserCodeMap()
+ @testWorld.loadFromLevel @level
+ if @goalManager
+ @testGM = new @goalManager.constructor @testWorld
+ @testGM.setGoals @goalManager.getGoals()
+ @testGM.setCode @getUserCodeMap()
+ @testGM.worldGenerationWillBegin()
+ @testWorld.setGoalManager @testGM
+ @doSimulateWorld()
+ if Worker?
+ console?.profileEnd?()
+ console.log "Construction:", (@t1 - @t0).toFixed(0), "ms. Simulation:", (@t2 - @t1).toFixed(0), "ms --", ((@t2 - @t1) / @testWorld.frames.length).toFixed(3), "ms per frame, profiled."
+
+ # If performance was really a priority in IE9, we would rework things to be able to skip this step.
+ @latestGoalStates = @testGM?.getGoalStates()
+ serialized = @testWorld.serialize().serializedWorld
+ window.BOX2D_ENABLED = false
+ @testWorld.constructor.deserialize serialized, @world.classMap, @lastSerializedWorldFrames, @t0, @finishBeholdingWorld
+ window.BOX2D_ENABLED = true
+ @lastSerializedWorldFrames = serialized.frames
+
+ doSimulateWorld: ->
+ @t1 = now()
+ Math.random = @testWorld.rand.randf # so user code is predictable
+ i = 0
+ while i < @testWorld.totalFrames
+ frame = @testWorld.getFrame i++
+ @testWorld.ended = true
+ system.finish @testWorld.thangs for system in @testWorld.systems
+ @t2 = now()
+ #### End bad testing code ####
+
+
+class Angel
+ @ids: ['Archer', 'Lana', 'Cyril', 'Pam', 'Cheryl', 'Woodhouse', 'Ray', 'Krieger']
+ @nextID: ->
+ @lastID = (if @lastID? then @lastID + 1 else Math.floor(@ids.length * Math.random())) % @ids.length
+ @ids[@lastID]
+
+ infiniteLoopIntervalDuration: 5000 # check this often (must be more than the others added)
+ infiniteLoopTimeoutDuration: 1500 # wait this long when we check
+ abortTimeoutDuration: 500 # give in-process or dying workers this long to give up
+ constructor: (@god) ->
+ @id = Angel.nextID()
+ if (navigator.userAgent or navigator.vendor or window.opera).search("MSIE") isnt -1
+ @infiniteLoopIntervalDuration *= 20 # since it's so slow to serialize without transferable objects, we can't trust it
+ @infiniteLoopTimeoutDuration *= 20
+ @abortTimeoutDuration *= 10
+ @spawnWorker()
+
+ spawnWorker: ->
+ @worker = new Worker '/javascripts/workers/worker_world.js'
+ @listen()
+
+ enslave: ->
+ @busy = true
+ @started = new Date()
+ @purgatoryTimer = setInterval @testWorker, @infiniteLoopIntervalDuration
+ @
+
+ free: ->
+ @busy = false
+ @started = null
+ clearInterval @purgatoryTimer
+ @purgatoryTimer = null
+ @
+
+ abort: ->
+ @abortTimeout = _.delay @terminate, @abortTimeoutDuration
+ @worker.postMessage {func: 'abort'}
+
+ terminate: =>
+ @worker.terminate()
+ return if @dead
+ @spawnWorker()
+ @free()
+ @god.angelAborted @
+
+ destroy: ->
+ @dead = true
+ @abort()
+
+ testWorker: =>
+ @worker.postMessage {func: 'reportIn'}
+ @condemnTimeout = _.delay @condemnWorker, @infiniteLoopTimeoutDuration
+
+ condemnWorker: =>
+ @god.angelInfinitelyLooped @
+ @abort()
+
+ listen: ->
+ @worker.addEventListener 'message', (event) =>
+ switch event.data.type
+ when 'new-world'
+ @god.beholdWorld @, event.data.serialized, event.data.goalStates
+ when 'world-load-progress-changed'
+ Backbone.Mediator.publish 'god:world-load-progress-changed', event.data unless @dead
+ when 'console-log'
+ console.log "|" + @god.id + "'s " + @id + "|", event.data.args...
+ when 'user-code-problem'
+ @god.angelUserCodeProblem @, event.data.problem
+ when 'abort'
+ #console.log @id, "aborted."
+ clearTimeout @abortTimeout
+ @free()
+ @god.angelAborted @
+ @worker.terminate() if @god.dead
+ when 'reportIn'
+ clearTimeout @condemnTimeout
+ else
+ console.log "Unsupported message:", event.data
diff --git a/app/lib/LevelBus.coffee b/app/lib/LevelBus.coffee
new file mode 100644
index 000000000..73992b554
--- /dev/null
+++ b/app/lib/LevelBus.coffee
@@ -0,0 +1,219 @@
+Bus = require './Bus'
+{me} = require 'lib/auth'
+LevelSession = require 'models/LevelSession'
+
+module.exports = class LevelBus extends Bus
+
+ @get: (levelID, sessionID) ->
+ docName = "play/level/#{levelID}/#{sessionID}"
+ return Bus.getFromCache(docName) or new LevelBus docName
+
+ subscriptions:
+ 'self-wizard:target-changed': 'onSelfWizardTargetChanged'
+ 'tome:editing-began': 'onEditingBegan'
+ 'tome:editing-ended': 'onEditingEnded'
+ 'script:state-changed': 'onScriptStateChanged'
+ 'script:ended': 'onScriptEnded'
+ 'script:reset': 'onScriptReset'
+ 'surface:frame-changed': 'onFrameChanged'
+ 'surface:sprite-selected': 'onSpriteSelected'
+ 'level-set-playing': 'onSetPlaying'
+ 'thang-code-ran': 'onCodeRan'
+ 'level-show-victory': 'onVictory'
+ 'tome:spell-changed': 'onSpellChanged'
+
+ constructor: ->
+ super(arguments...)
+ @changedSessionProperties = {}
+ @saveSession = _.debounce(@saveSession, 1000, {maxWait: 5000})
+
+ init: ->
+ super()
+ @fireScriptsRef = @fireRef?.child('scripts')
+
+ setSession: (@session) ->
+ @session.on('change:multiplayer', @onMultiplayerChanged)
+
+ onPoint: ->
+ return true unless @session?.get('multiplayer')
+ super()
+
+ onSelfWizardTargetChanged: =>
+ wizardSprite = @getSelfWizard()
+ @wizardRef?.child('targetPos').set(wizardSprite?.targetPos or null)
+ @wizardRef?.child('targetSprite').set(wizardSprite?.targetSprite?.thang.id or null)
+
+ onMeSynced: =>
+ super()
+ @wizardRef?.child('wizardColor1').set(me.get('wizardColor1') or 0.0)
+
+ join: ->
+ super()
+ @wizardRef = @myConnection.child('wizard')
+ wizardSprite = @getSelfWizard()
+ @wizardRef?.child('targetPos').set(wizardSprite?.targetPos or null)
+ @wizardRef?.child('targetSprite').set(wizardSprite?.targetSprite?.thang.id or null)
+ @wizardRef?.child('wizardColor1').set(me.get('wizardColor1') or 0.0)
+
+ disconnect: ->
+ @wizardRef?.off()
+ @wizardRef = null
+ @fireScriptsRef?.off()
+ @fireScriptsRef = null
+ super()
+
+ removeFirebaseData: (callback) ->
+ return callback?() unless @myConnection
+ @myConnection.child('connected')
+ @fireRef.remove()
+ @onDisconnect.cancel(-> callback?())
+
+ getSelfWizard: ->
+ e = {}
+ Backbone.Mediator.publish('echo-self-wizard-sprite', e)
+ return e.payload
+
+ # UPDATING FIREBASE AND SESSION
+
+ onEditingBegan: -> @wizardRef?.child('editing').set(true)
+ onEditingEnded: -> @wizardRef?.child('editing').set(false)
+
+ # HACK: Backbone does not work with nested documents, but we want to
+ # patch only those props that have changed. Look into plugins to
+ # give Backbone support for nested docs and update the code here.
+
+ # TODO: The LevelBus doesn't need to be in charge of updating the
+ # LevelSession object. Either break this off into a separate class
+ # or have the LevelSession object listen for all these events itself.
+
+ onSpellChanged: (e) ->
+ return unless @onPoint()
+ code = @session.get('code')
+ code ?= {}
+ parts = e.spell.spellKey.split('/')
+ code[parts[0]] ?= {}
+ code[parts[0]][parts[1]] = e.spell.getSource()
+ @changedSessionProperties.code = true
+ @session.set({'code': code})
+ @saveSession()
+
+ onScriptStateChanged: (e) ->
+ return unless @onPoint()
+ @fireScriptsRef?.update(e)
+ state = @session.get('state')
+ scripts = state.scripts
+ scripts.currentScript = e.currentScript
+ scripts.currentScriptOffset = e.currentScriptOffset
+ @changedSessionProperties.state = true
+ @session.set('state', state)
+ @saveSession()
+
+ onScriptEnded: (e) ->
+ return unless @onPoint()
+ state = @session.get('state')
+ scripts = state.scripts
+ scripts.ended ?= {}
+ return if scripts.ended[e.scriptID]?
+ index = _.keys(scripts.ended).length + 1
+ @fireScriptsRef?.child('ended').child(e.scriptID).set(index)
+ scripts.ended[e.scriptID] = index
+ @session.set('state', state)
+ @changedSessionProperties.state = true
+ @saveSession()
+
+ onScriptReset: ->
+ return unless @onPoint()
+ @fireScriptsRef?.set({})
+ state = @session.get('state')
+ state.scripts = {}
+ state.complete = false
+ @session.set('state', state)
+ @changedSessionProperties.state = true
+ @saveSession()
+
+ onFrameChanged: (e) ->
+ return unless @onPoint()
+ state = @session.get('state')
+ state.frame = e.frame
+ @session.set('state', state)
+ @changedSessionProperties.state = true
+ @saveSession()
+
+ onSpriteSelected: (e) ->
+ return unless @onPoint()
+ state = @session.get('state')
+ state.selected = e.thang?.id or null
+ @session.set('state', state)
+ @changedSessionProperties.state = true
+ @saveSession()
+
+ onSetPlaying: (e) ->
+ return unless @onPoint()
+ state = @session.get('state')
+ state.playing = e.playing
+ @session.set('state', state)
+ @changedSessionProperties.state = true
+ @saveSession()
+
+ onCodeRan: (e) ->
+ return unless @onPoint()
+ state = @session.get('state')
+ state.thangs ?= {}
+ methods = _.cloneDeep(e.methods)
+ delete method.metrics.statements for methodName, method of methods
+ state.thangs[e.thangID] = { methods: methods }
+ @session.set('state', state)
+ @changedSessionProperties.state = true
+ @saveSession()
+
+ onVictory: ->
+ return unless @onPoint()
+ state = @session.get('state')
+ state.complete = true
+ @session.set('state', state)
+ @changedSessionProperties.state = true
+ @saveSession()
+
+ onPlayerJoined: (snapshot) =>
+ super(arguments...)
+ return unless @onPoint()
+ players = @session.get('players')
+ players ?= {}
+ player = snapshot.val()
+ return if players[player.id]?
+ players[player.id] = {}
+ @session.set('players', players)
+ @changedSessionProperties.players = true
+ @saveSession()
+
+ onChatAdded: (snapshot) =>
+ super(arguments...)
+ chat = @session.get('chat')
+ chat ?= []
+ message = snapshot.val()
+ return if message.system
+ chat.push(message)
+ chat = chat[chat.length-50...] if chat.length > 50
+ @session.set('chat', chat)
+ @changedSessionProperties.chat = true
+ @saveSession()
+
+ onMultiplayerChanged: =>
+ @changedSessionProperties.multiplayer = true
+ @session.updatePermissions()
+ @changedSessionProperties.permissions = true
+ @saveSession()
+
+ saveSession: ->
+ return if _.isEmpty @changedSessionProperties
+ # don't let peaking admins mess with the session accidentally
+ return unless @session.get('multiplayer') or @session.get('creator') is me.id
+ Backbone.Mediator.publish 'level:session-will-save', session: @session
+ patch = {}
+ patch[prop] = @session.get(prop) for prop of @changedSessionProperties
+ @changedSessionProperties = {}
+
+ # since updates are coming fast and loose for session objects
+ # don't let what the server returns overwrite changes since the save began
+ tempSession = new LevelSession _id:@session.id
+ tempSession.save(patch, {patch: true})
diff --git a/app/lib/LevelLoader.coffee b/app/lib/LevelLoader.coffee
new file mode 100644
index 000000000..c8894a3ba
--- /dev/null
+++ b/app/lib/LevelLoader.coffee
@@ -0,0 +1,164 @@
+Level = require 'models/Level'
+CocoClass = require 'lib/CocoClass'
+AudioPlayer = require 'lib/AudioPlayer'
+LevelSession = require 'models/LevelSession'
+ThangType = require 'models/ThangType'
+app = require 'application'
+
+# This is an initial stab at unifying loading and setup into a single place which can
+# monitor everything and keep a LoadingScreen visible overall progress.
+#
+# Would also like to incorporate into here:
+# * World Building
+# * Sprite map generation
+# * Connecting to Firebase
+
+module.exports = class LevelLoader extends CocoClass
+
+ spriteSheetsBuilt: 0
+ spriteSheetsToBuild: 0
+
+ subscriptions:
+ 'god:new-world-created': 'loadSoundsForWorld'
+
+ constructor: (@levelID, @supermodel, @sessionID) ->
+ super()
+ @loadSession()
+ @loadLevelModels()
+ @loadAudio()
+ @playJingle()
+ setTimeout (=> @update()), 1 # lets everything else resolve first
+
+ playJingle: ->
+ jingles = ["ident_1", "ident_2"]
+ AudioPlayer.playInterfaceSound jingles[Math.floor Math.random() * jingles.length]
+
+ # Session Loading
+
+ loadSession: ->
+ url = if @sessionID then "/db/level_session/#{@sessionID}" else "/db/level/#{@levelID}/session"
+ @session = new LevelSession()
+ @session.url = -> url
+ @session.fetch()
+ @session.once 'sync', @onSessionLoaded
+
+ onSessionLoaded: =>
+ # TODO: maybe have all non versioned models do this? Or make it work to PUT/PATCH to relative urls
+ @session.url = -> '/db/level.session/' + @id
+ @update()
+
+ # Supermodel (Level) Loading
+
+ loadLevelModels: ->
+ @supermodel.once 'loaded-all', @onSupermodelLoadedAll
+ @supermodel.on 'loaded-one', @onSupermodelLoadedOne
+ @supermodel.once 'error', @onSupermodelError
+ @level = @supermodel.getModel(Level, @levelID) or new Level _id: @levelID
+
+ @supermodel.shouldPopulate = (model) =>
+ # if left unchecked, the supermodel would load this level
+ # and every level next on the chain. This limits the population
+ handles = [model.id, model.get 'slug']
+ return model.constructor.className isnt "Level" or @levelID in handles
+
+ @supermodel.populateModel @level
+
+ onSupermodelError: =>
+ msg = $.i18n.t('play_level.level_load_error',
+ defaultValue: "Level could not be loaded.")
+ @$el.html('' + msg + '
')
+
+ onSupermodelLoadedOne: (e) =>
+ @notifyProgress()
+ if e.model.type() is 'ThangType'
+ thangType = e.model
+ building = thangType.buildSpriteSheet {async: true}
+ if building
+ @spriteSheetsToBuild += 1
+ thangType.on 'build-complete', =>
+ @spriteSheetsBuilt += 1
+ @notifyProgress()
+
+ onSupermodelLoadedAll: =>
+ @trigger 'loaded-supermodel'
+ @stopListening(@supermodel)
+ @update()
+
+ # Things to do when either the Session or Supermodel load
+
+ update: ->
+ @notifyProgress()
+
+ return if @updateCompleted
+ return unless @supermodel.finished() and @session.loaded
+ @denormalizeSession()
+ @loadLevelSounds()
+ app.tracker.updatePlayState(@level, @session)
+ @updateCompleted = true
+
+ denormalizeSession: ->
+ return if @session.get 'levelName'
+ patch =
+ 'levelName': @level.get('name')
+ 'levelID': @level.get('slug') or @level.id
+ if me.id is @session.get 'creator'
+ patch.creatorName = me.get('name')
+
+ @session.set key, value for key, value of patch
+ tempSession = new LevelSession _id: @session.id
+ tempSession.save(patch, {patch: true})
+ @sessionDenormalized = true
+
+ # Initial Sound Loading
+
+ loadAudio: ->
+ AudioPlayer.preloadInterfaceSounds ["victory"]
+
+ loadLevelSounds: ->
+ scripts = @level.get 'scripts'
+ return unless scripts
+
+ for script in scripts when script.noteChain
+ for noteGroup in script.noteChain when noteGroup.sprites
+ for sprite in noteGroup.sprites when sprite.say?.sound
+ AudioPlayer.preloadSoundReference(sprite.say.sound)
+
+ thangTypes = @supermodel.getModels(ThangType)
+ for thangType in thangTypes
+ for trigger, sounds of thangType.get('soundTriggers') or {} when trigger isnt 'say'
+ AudioPlayer.preloadSoundReference sound for sound in sounds
+
+ # Dynamic sound loading
+
+ loadSoundsForWorld: (e) ->
+ world = e.world
+ thangTypes = @supermodel.getModels(ThangType)
+ for [spriteName, message] in world.thangDialogueSounds()
+ continue unless thangType = _.find thangTypes, (m) -> m.get('name') is spriteName
+ continue unless sound = AudioPlayer.soundForDialogue message, thangType.get('soundTriggers')
+ filename = AudioPlayer.preloadSoundReference sound
+
+ # everything else sound wise is loaded as needed as worlds are generated
+
+ allDone: ->
+ @supermodel.finished() and @session.loaded and @spriteSheetsBuilt is @spriteSheetsToBuild
+
+ progress: ->
+ return 0 unless @level.loaded
+ overallProgress = 0
+ supermodelProgress = @supermodel.progress()
+ overallProgress += supermodelProgress * 0.7
+ overallProgress += 0.1 if @session.loaded
+ spriteMapProgress = if supermodelProgress is 1 then 0.2 else 0
+ spriteMapProgress *= @spriteSheetsBuilt / @spriteSheetsToBuild if @spriteSheetsToBuild
+ overallProgress += spriteMapProgress
+ return overallProgress
+
+ notifyProgress: ->
+ Backbone.Mediator.publish 'level-loader:progress-changed', progress: @progress()
+ @trigger 'ready-to-init-world' if @allDone()
+
+ destroy: ->
+ @supermodel.off 'loaded-one', @onSupermodelLoadedOne
+ super()
+
diff --git a/app/lib/LoadingScreen.coffee b/app/lib/LoadingScreen.coffee
new file mode 100644
index 000000000..b3993a435
--- /dev/null
+++ b/app/lib/LoadingScreen.coffee
@@ -0,0 +1,99 @@
+CocoClass = require 'lib/CocoClass'
+
+module.exports = class LoadingScreen extends CocoClass
+ progress: 0
+
+ constructor: (canvas) ->
+ super()
+ @width = canvas.width
+ @height = canvas.height
+ @stage = new createjs.Stage(canvas)
+
+ subscriptions:
+ 'level-loader:progress-changed': 'onLevelLoaderProgressChanged'
+
+ show: ->
+ @stage.removeChild(@screen) if @screen
+ @screen = @makeScreen()
+ @stage.addChild(@screen)
+ @updateProgressBar()
+
+ hide: ->
+ @stage.removeChild(@screen) if @screen
+ @screen = null
+
+ makeScreen: ->
+ c = new createjs.Container()
+ c.addChild(@makeLoadBackground())
+ c.addChild(@makeLoadText())
+ c.addChild(@makeProgressBar())
+ @makeLoadLogo(c)
+ c
+
+ makeLoadBackground: ->
+ g = new createjs.Graphics()
+ g.beginFill(createjs.Graphics.getRGB(30,30,60))
+ g.drawRoundRect(0, 0, @width, @height, 0.0)
+ s = new createjs.Shape(g)
+ s.y = 0
+ s.x = 0
+ s
+
+ makeLoadLogo: (container) ->
+ logoImage = new Image()
+ $(logoImage).load =>
+ @logo = new createjs.Bitmap logoImage
+ @logo.x = @width / 2 - logoImage.width / 2
+ @logo.y = 40
+ container.addChild @logo
+ logoImage.src = "/images/loading_image.png"
+
+ makeLoadText: ->
+ size = @height / 10
+ text = new createjs.Text("LOADING", "#{size}px Monospace", "#ff7700")
+ text.regX = text.getMeasuredWidth() / 2
+ text.regY = text.getMeasuredHeight() / 2
+ text.x = @width / 2
+ text.y = @height / 2
+ @text = text
+ return text
+
+ makeProgressBar: ->
+ BAR_PIXEL_HEIGHT = 20
+ BAR_PCT_WIDTH = .75
+ pixelWidth = parseInt(@width * BAR_PCT_WIDTH)
+ pixelMargin = (@width - (@width * BAR_PCT_WIDTH)) / 2
+ barY = 2 * (@height / 3)
+
+ c = new createjs.Container()
+ c.x = pixelMargin
+ c.y = barY
+
+ g = new createjs.Graphics()
+ g.beginFill(createjs.Graphics.getRGB(255,0,0))
+ g.drawRoundRect(0,0,pixelWidth, BAR_PIXEL_HEIGHT, 5)
+ @progressBar = new createjs.Shape(g)
+ c.addChild(@progressBar)
+
+ g = new createjs.Graphics()
+ g.setStrokeStyle(2)
+ g.beginStroke(createjs.Graphics.getRGB(230,230,230))
+ g.drawRoundRect(0,0,pixelWidth, BAR_PIXEL_HEIGHT, 5)
+ c.addChild(new createjs.Shape(g))
+ c
+
+ onLevelLoaderProgressChanged: (e) ->
+ @progress = e.progress
+ @updateProgressBar()
+
+ updateProgressBar: ->
+ newProg = parseInt((@progress or 0) * 100)
+ newProg = ' '+newProg while newProg.length < 4
+ @lastProg = newProg
+ @text.text = "BUILDING" if @progress is 1
+ @progressBar.scaleX = @progress
+ @stage.update()
+
+ destroy: ->
+ @stage.canvas = null
+ super()
\ No newline at end of file
diff --git a/app/lib/Router.coffee b/app/lib/Router.coffee
new file mode 100644
index 000000000..7395225c3
--- /dev/null
+++ b/app/lib/Router.coffee
@@ -0,0 +1,158 @@
+application = require 'application'
+{me} = require 'lib/auth'
+
+gplusClientID = "800329290710-j9sivplv2gpcdgkrsis9rff3o417mlfa.apps.googleusercontent.com"
+
+module.exports = class CocoRouter extends Backbone.Router
+ subscribe: ->
+ Backbone.Mediator.subscribe 'gapi-loaded', @onGPlusAPILoaded, @
+ Backbone.Mediator.subscribe 'router:navigate', @onNavigate, @
+
+ routes:
+ # every abnormal view gets listed here
+ '': 'home'
+ 'preview': 'home'
+ 'beta': 'home'
+
+ # editor views tend to have the same general structure
+ 'editor/:model(/:slug_or_id)(/:subview)': 'editorModelView'
+
+ # db and file urls call the server directly
+ 'db/*path': 'routeToServer'
+ 'file/*path': 'routeToServer'
+
+ # most go through here
+ '*name': 'general'
+
+ home: -> @openRoute('home')
+ general: (name) ->
+ console.log 'general', arguments
+ @openRoute(name)
+
+ editorModelView: (modelName, slugOrId, subview) ->
+ modulePrefix = "views/editor/#{modelName}/"
+ suffix = subview or (if slugOrId then 'edit' else 'home')
+ ViewClass = @tryToLoadModule(modulePrefix + suffix)
+ unless ViewClass
+ #console.log('could not hack it', modulePrefix + suffix)
+ args = (a for a in arguments when a)
+ args.splice(0, 0, 'editor')
+ return @openRoute(args.join('/'))
+ view = new ViewClass({}, slugOrId)
+ view.render()
+ if view then @openView(view) else @showNotFound()
+
+ cache: {}
+ openRoute: (route) ->
+ route = route.split('?')[0]
+ route = route.split('#')[0]
+ view = @getViewFromCache(route)
+ @openView(view)
+
+ openView: (view) ->
+ @closeCurrentView()
+ $('#page-container').empty().append view.el
+ window.currentView = view
+ @activateTab()
+ @renderLoginButtons()
+ window.scrollTo(0, view.scrollY) if view.scrollY?
+ view.afterInsert()
+ view.didReappear() if view.fromCache
+
+ onGPlusAPILoaded: =>
+ @renderLoginButtons()
+
+ renderLoginButtons: ->
+ $('.share-buttons').addClass('fade-in').delay(10000).removeClass('fade-in', 5000)
+ setTimeout(FB.XFBML.parse, 10) if FB? # Handles FB login and Like
+ twttr?.widgets?.load()
+
+ return unless gapi?.plusone?
+ gapi.plusone.go() # Handles +1 button
+ for gplusButton in $('.gplus-login-button')
+ params = {
+ callback:"signinCallback",
+ clientid:gplusClientID,
+ cookiepolicy:"single_host_origin",
+ scope:"https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/userinfo.email",
+ height:"short",
+ width:"standard",
+ }
+ if gapi.signin
+ gapi.signin.render(gplusButton, params)
+ else
+ console.warn "Didn't have gapi.signin to render G+ login button. (DoNotTrackMe extension?)"
+
+ getViewFromCache: (route) ->
+ if route of @cache
+ @cache[route].fromCache = true
+ return @cache[route]
+ view = @getView(route)
+ @cache[route] = view unless view and view.cache is false
+ return view
+
+ getView: (route, suffix='_view') ->
+ # iteratively breaks down the url pieces looking for the view
+ # passing the broken off pieces as args. This way views like "resource/14394893"
+ # will get passed to the resource view with arg '14394893'
+ pieces = _.string.words(route, '/')
+ split = Math.max(1, pieces.length-1)
+ while split > -1
+ sub_route = _.string.join('/', pieces[0..split]...)
+ path = "views/#{sub_route}#{suffix}"
+ ViewClass = @tryToLoadModule(path)
+ break if ViewClass
+ split -= 1
+
+ return @showNotFound() if not ViewClass
+ args = pieces[split+1..]
+ view = new ViewClass({}, args...) # options, then any path fragment args
+ view.render()
+
+ tryToLoadModule: (path) ->
+ try
+ return require(path)
+ catch error
+ if error.toString().search('Cannot find module "' + path + '" from') is -1
+ throw error
+
+ showNotFound: ->
+ NotFoundView = require('views/not_found')
+ view = new NotFoundView()
+ view.render()
+
+ closeCurrentView: ->
+ window.currentModal?.hide()
+ return unless window.currentView?
+ if window.currentView.cache
+ window.currentView.scrollY = window.scrollY
+ window.currentView.willDisappear()
+ else
+ window.currentView.destroy()
+
+ activateTab: ->
+ base = _.string.words(document.location.pathname[1..], '/')[0]
+ $("ul.nav li.#{base}").addClass('active')
+
+ initialize: ->
+ @cache = {}
+ # http://nerds.airbnb.com/how-to-add-google-analytics-page-tracking-to-57536
+ @bind 'route', @_trackPageView
+
+ _trackPageView: ->
+ window.tracker?.trackPageView()
+
+ onNavigate: (e) ->
+ manualView = e.view or e.viewClass
+ @navigate e.route, {trigger:not manualView}
+ return unless manualView
+ if e.viewClass
+ args = e.viewArgs or []
+ view = new e.viewClass(args...)
+ view.render()
+ @openView view
+ else
+ @openView e.view
+
+ routeToServer: (e) ->
+ window.location.href = window.location.href
diff --git a/app/lib/Tracker.coffee b/app/lib/Tracker.coffee
new file mode 100644
index 000000000..c3cd51efa
--- /dev/null
+++ b/app/lib/Tracker.coffee
@@ -0,0 +1,56 @@
+{me} = require 'lib/auth'
+
+module.exports = class Tracker
+ constructor: ->
+ if window.tracker
+ console.error "Overwrote our Tracker!", window.tracker
+ window.tracker = @
+ @isProduction = document.location.href.search("codecombat.com") isnt -1
+ @identify()
+ @updateOlark()
+
+ identify: (traits) ->
+ #console.log "Would identify", traits
+ return unless me and @isProduction and analytics?
+ # https://segment.io/docs/methods/identify
+ traits ?= {}
+ for userTrait in ['email', 'anonymous', 'dateCreated', 'name', 'wizardColor1', 'testGroupNumber', 'gender']
+ traits[userTrait] ?= me.get(userTrait)
+ analytics.identify me.id, traits
+
+ updateOlark: ->
+ return unless me and olark?
+ olark 'api.chat.updateVisitorStatus', snippet: ["User ID: #{me.id}"]
+ return if me.get("anonymous")
+ olark 'api.visitor.updateEmailAddress', emailAddress: me.get("email")
+ olark 'api.chat.updateVisitorNickname', snippet: me.displayName()
+
+ updatePlayState: (level, session) ->
+ link = "codecombat.com/play/level/#{level.get('slug') or level.id}?session=#{session.id}"
+ snippet = [
+ "#{link}"
+ "User ID: #{me.id}"
+ "Session ID: #{session.id}"
+ "Level: #{level.get('name')}"
+
+ ]
+ olark 'api.chat.updateVisitorStatus', snippet: snippet
+
+ trackPageView: ->
+ return unless @isProduction and analytics?
+ url = Backbone.history.getFragment()
+ #console.log "Going to track visit for", "/#{url}"
+ analytics.pageview "/#{url}"
+
+ trackEvent: (event, properties, includeProviders=null) =>
+ #console.log "Would track analytics event:", event, properties
+ return unless me and @isProduction and analytics?
+ #console.log "Going to track analytics event:", event, properties
+ properties = properties or {}
+ context = {}
+ if includeProviders
+ context.providers = {'All': false}
+ for provider in includeProviders
+ context.providers[provider] = true
+ event.label = properties.label if properties.label
+ analytics?.track event, properties, context
diff --git a/app/lib/auth.coffee b/app/lib/auth.coffee
new file mode 100644
index 000000000..8f939704b
--- /dev/null
+++ b/app/lib/auth.coffee
@@ -0,0 +1,81 @@
+{backboneFailure, genericFailure} = require 'lib/errors'
+User = require 'models/User'
+{saveObjectToStorage, loadObjectFromStorage} = require 'lib/storage'
+
+module.exports.CURRENT_USER_KEY = CURRENT_USER_KEY = 'whoami'
+BEEN_HERE_BEFORE_KEY = 'beenHereBefore'
+
+module.exports.createUser = (userObject, failure=backboneFailure) ->
+ user = new User(userObject)
+ user.save({}, {
+ error: failure,
+ success: (model) ->
+ saveObjectToStorage(CURRENT_USER_KEY, model)
+ window.location.reload()
+ })
+
+module.exports.loginUser = (userObject, failure=genericFailure) ->
+ jqxhr = $.post('/auth/login',
+ {
+ username:userObject.email,
+ password:userObject.password
+ },
+ (model) ->
+ saveObjectToStorage(CURRENT_USER_KEY, model)
+ window.location.reload()
+ )
+ jqxhr.fail(failure)
+
+module.exports.logoutUser = ->
+ FB.logout()
+ res = $.post('/auth/logout', {}, ->
+ saveObjectToStorage(CURRENT_USER_KEY, null)
+ window.location.reload()
+ )
+ res.fail(genericFailure)
+
+init = ->
+ # load the user from local storage, and refresh it from the server.
+ # If the server info doesn't match the local storage, refresh the page.
+ # Also refresh and cache the gravatar info.
+
+ loadedUser = loadObjectFromStorage(CURRENT_USER_KEY)
+ module.exports.me = window.me = if loadedUser then new User(loadedUser) else null
+ me.set('wizardColor1', Math.random()) if me and not me.get('wizardColor1')
+ $.get('/auth/whoami', (downloadedUser) ->
+ trackFirstArrival() # should happen after trackEvent has loaded, due to the callback
+ changedState = Boolean(downloadedUser) isnt Boolean(loadedUser)
+ switchedUser = downloadedUser and loadedUser and downloadedUser._id isnt loadedUser._id
+ if changedState or switchedUser
+ saveObjectToStorage(CURRENT_USER_KEY, downloadedUser)
+ window.location.reload()
+ if me and not me.get('testGroupNumber')?
+ # Assign testGroupNumber to returning visitors; new ones in server/handlers/user
+ me.set 'testGroupNumber', Math.floor(Math.random() * 256)
+ me.save()
+ saveObjectToStorage(CURRENT_USER_KEY, downloadedUser)
+ )
+ if module.exports.me
+ module.exports.me.loadGravatarProfile()
+ module.exports.me.on('sync', userSynced)
+
+userSynced = (user) ->
+ Backbone.Mediator.publish('me:synced', {me:user})
+ saveObjectToStorage(CURRENT_USER_KEY, user)
+
+init()
+
+onSetVolume = (e) ->
+ return if e.volume is me.get('volume')
+ me.set('volume', e.volume)
+ me.save()
+
+Backbone.Mediator.subscribe('level-set-volume', onSetVolume, module.exports)
+
+trackFirstArrival = ->
+ # will have to filter out users who log in with existing accounts separately
+ # but can at least not track logouts as first arrivals using local storage
+ beenHereBefore = loadObjectFromStorage(BEEN_HERE_BEFORE_KEY)
+ return if beenHereBefore
+ window.tracker?.trackEvent 'First Arrived' if not me
+ saveObjectToStorage(BEEN_HERE_BEFORE_KEY, true)
diff --git a/app/lib/contact.coffee b/app/lib/contact.coffee
new file mode 100644
index 000000000..fa38fbf69
--- /dev/null
+++ b/app/lib/contact.coffee
@@ -0,0 +1,16 @@
+
+
+module.exports.sendContactMessage = (contactMessageObject, modal) ->
+ modal.find('.sending-indicator').show()
+ jqxhr = $.post '/contact',
+ email: contactMessageObject.email
+ message: contactMessageObject.message
+ ,
+ (response) ->
+ console.log "Got contact response:", response
+ modal.find('.sending-indicator').hide()
+ modal.find('#contact-message').val("Thanks!")
+ _.delay ->
+ modal.find('#contact-message').val("")
+ modal.modal 'hide'
+ , 1000
diff --git a/app/lib/errors.coffee b/app/lib/errors.coffee
new file mode 100644
index 000000000..f979d1e35
--- /dev/null
+++ b/app/lib/errors.coffee
@@ -0,0 +1,48 @@
+errorModalTemplate = require('templates/modal/error')
+{applyErrorsToForm} = require('lib/forms')
+
+module.exports.parseServerError = (text) ->
+ try
+ error = JSON.parse(text) or {message:"Unknown error."}
+ catch SyntaxError
+ error = {message:text or "Unknown error."}
+ error = error[0] if _.isArray(error)
+ error
+
+module.exports.genericFailure = (jqxhr) ->
+ Backbone.Mediator.publish('server-error', {response:jqxhr})
+ return connectionFailure() if not jqxhr.status
+
+ error = module.exports.parseServerError(jqxhr.responseText)
+ message = error.message
+ message = error.property + ' ' + message if error.property
+ res = errorModalTemplate(
+ status:jqxhr.status
+ statusText:jqxhr.statusText
+ message: message
+ )
+ console.warn(jqxhr.status, jqxhr.statusText, error)
+ existingForm = $('.form-inline:visible:first')
+ if existingForm[0]
+ missingErrors = applyErrorsToForm(existingForm, [error])
+ for error in missingErrors
+ existingForm.append($('').text(error.message))
+ else
+ showErrorModal(res)
+
+module.exports.backboneFailure = (model, jqxhr, options) ->
+ module.exports.genericFailure(jqxhr)
+
+module.exports.connectionFailure = connectionFailure = ->
+ html = errorModalTemplate(
+ status: 0
+ statusText:'Connection Gone'
+ message: 'No response from the CoCo servers, captain.'
+ )
+ showErrorModal(html)
+
+showErrorModal = (html) ->
+ # TODO: make a views/modal/error_modal view for this to use so the template can reuse templates/modal/modal_base?
+ $('#modal-wrapper').html(html)
+ $('.modal:visible').modal('hide')
+ $('#modal-error').modal('show')
diff --git a/app/lib/forms.coffee b/app/lib/forms.coffee
new file mode 100644
index 000000000..4fa9e8b8d
--- /dev/null
+++ b/app/lib/forms.coffee
@@ -0,0 +1,38 @@
+module.exports.formToObject = (el) ->
+ obj = {}
+
+ inputs = $('input', el).add('textarea', el)
+ for input in inputs
+ input = $(input)
+ obj[input.attr('name')] = input.val()
+
+ obj
+
+module.exports.applyErrorsToForm = (el, errors) ->
+ errors = [errors] if not $.isArray(errors)
+ missingErrors = []
+ for error in errors
+ if error.dataPath
+ prop = error.dataPath[1..]
+ message = error.message
+
+ else
+ message = "#{error.property} #{error.message}."
+ message = message[0].toUpperCase() + message[1..]
+ message = error.message if error.formatted
+ prop = error.property
+
+ input = $("[name='#{prop}']", el)
+ if not input[0]
+ missingErrors.push(error)
+ continue
+ controls = input.closest('.controls')
+ controls.append($("#{message}"))
+ group = controls.closest('.control-group')
+ group.addClass('error')
+ return missingErrors
+
+module.exports.clearFormAlerts = (el) ->
+ $('.error', el).removeClass('error')
+ $('.error-inline', el).remove()
+ $('.alert', el).remove()
\ No newline at end of file
diff --git a/app/lib/image_filter.coffee b/app/lib/image_filter.coffee
new file mode 100644
index 000000000..4c1a5f93e
--- /dev/null
+++ b/app/lib/image_filter.coffee
@@ -0,0 +1,49 @@
+# Based on: http://www.html5rocks.com/en/tutorials/canvas/imagefilters/
+
+Filters = {}
+
+Filters.getPixels = (img) ->
+ c = @getCanvas(img.naturalWidth, img.naturalHeight)
+ ctx = c.getContext('2d')
+ ctx.drawImage(img, 0, 0)
+ return ctx.getImageData(0,0,c.width,c.height)
+
+Filters.getCanvas = (w,h) ->
+ c = document.createElement('canvas')
+ c.width = w
+ c.height = h
+ return c
+
+Filters.filterImage = (filter, image, args...) ->
+ args = [this.getPixels(image)].concat(args)
+ return filter(args...)
+
+Filters.brightness = (pixels, adjustment) ->
+ d = pixels.data
+ i = 0
+ while i < d.length
+ d[i] *= adjustment
+ d[i+1] *= adjustment
+ d[i+2] *= adjustment
+ i+=4
+ return pixels
+
+module.exports.darkenImage = darkenImage = (img, pct=0.5) ->
+ jqimg = $(img)
+ cachedValue = jqimg.data('darkened')
+ return img.src = cachedValue if cachedValue
+ jqimg.data('original', img.src) unless jqimg.data('original')
+ if not (img.naturalWidth > 0 and img.naturalHeight > 0)
+ console.warn "Tried to darken image", img, "but it has natural dimensions", img.naturalWidth, img.naturalHeight
+ return img
+ imageData = Filters.filterImage(Filters.brightness, img, pct)
+ c = Filters.getCanvas(img.naturalWidth, img.naturalHeight)
+ ctx = c.getContext('2d')
+ ctx.putImageData(imageData, 0, 0)
+ img.src = c.toDataURL()
+ jqimg.data('darkened', img.src)
+
+module.exports.revertImage = revertImage = (img) ->
+ jqimg = $(img)
+ return unless jqimg.data('original')
+ img.src = jqimg.data('original')
diff --git a/app/lib/scripts/DOMScriptModule.coffee b/app/lib/scripts/DOMScriptModule.coffee
new file mode 100644
index 000000000..f57a91b64
--- /dev/null
+++ b/app/lib/scripts/DOMScriptModule.coffee
@@ -0,0 +1,65 @@
+ScriptModule = require './ScriptModule'
+
+module.exports = class DOMScriptModule extends ScriptModule
+ @neededFor: (noteGroup) ->
+ return noteGroup.dom?
+
+ startNotes: ->
+ notes = []
+ notes.push(@highlightNote()) if @noteGroup.dom.highlight?
+ notes.push(@lockNote()) if @noteGroup.dom.lock?
+ notes.push(@focusNote()) if @noteGroup.dom.focus?
+ notes.push(@showVictoryNote()) if @noteGroup.dom.showVictory
+ notes.push(@letterboxNote()) if @noteGroup.dom.letterbox?
+ return notes
+
+ endNotes: ->
+ notes = []
+ notes.push({ 'channel': 'end-level-highlight-dom' }) if @noteGroup.dom.highlight?
+ notes.push({ 'channel': 'level-enable-controls' }) if @noteGroup.dom.lock?
+ return notes
+
+ skipNotes: ->
+ notes = []
+ notes.push(@showVictoryNote(false)) if @noteGroup.dom.showVictory?
+ notes.push(@letterboxNote()) if @noteGroup.dom.letterbox?
+ notes
+
+ highlightNote: ->
+ dom = @noteGroup.dom
+ note =
+ channel: 'level-highlight-dom'
+ event:
+ selector: dom.highlight.target
+ delay: dom.highlight.delay
+ sides: dom.highlight.sides
+ offset: dom.highlight.offset
+ rotation: dom.highlight.rotation
+ @maybeApplyDelayToNote note
+ note
+
+ focusNote: ->
+ note =
+ channel: 'level-focus-dom'
+ event:
+ selector: @noteGroup.dom.focus
+ note
+
+ showVictoryNote: (showModal) ->
+ e = {}
+ e.showModal = @noteGroup.dom.showVictory in [true, 'Done Button And Modal']
+ e.showModal = showModal if showModal?
+ note =
+ channel: 'level-show-victory'
+ event: e
+ note
+
+ lockNote: ->
+ event = {}
+ lock = @noteGroup.dom.lock
+ event.controls = lock if _.isArray lock # array: subset of controls
+ channel = if lock then 'level-disable-controls' else 'level-enable-controls'
+ return { channel: channel, event: event }
+
+ letterboxNote: ->
+ return { channel: 'level-set-letterbox', event: { on: @noteGroup.dom.letterbox } }
\ No newline at end of file
diff --git a/app/lib/scripts/GoalsScriptModule.coffee b/app/lib/scripts/GoalsScriptModule.coffee
new file mode 100644
index 000000000..b55673117
--- /dev/null
+++ b/app/lib/scripts/GoalsScriptModule.coffee
@@ -0,0 +1,35 @@
+ScriptModule = require './ScriptModule'
+
+module.exports = class GoalsScriptModule extends ScriptModule
+ @neededFor: (noteGroup) ->
+ return noteGroup.goals?
+
+ startNotes: ->
+ notes = []
+ notes.push(@addNote()) if @noteGroup.goals.add?
+ notes.push(@removeNote()) if @noteGroup.goals.remove?
+ return notes
+
+ endNotes: ->
+ return []
+
+ skipNotes: ->
+ return @startNotes()
+
+ addNote: ->
+ note =
+ channel: 'level-add-goals'
+ event:
+ goals: @noteGroup.goals.add
+ worldName: @view.world.name
+ return note
+
+ removeNote: ->
+ note =
+ channel: 'level-remove-goals'
+ event:
+ goals: @noteGroup.goals.remove
+ worldName: @view.world.name
+ return note
+
+
\ No newline at end of file
diff --git a/app/lib/scripts/PlaybackScriptModule.coffee b/app/lib/scripts/PlaybackScriptModule.coffee
new file mode 100644
index 000000000..40487be16
--- /dev/null
+++ b/app/lib/scripts/PlaybackScriptModule.coffee
@@ -0,0 +1,42 @@
+ScriptModule = require './ScriptModule'
+
+module.exports = class PlaybackScriptModule extends ScriptModule
+ @neededFor: (noteGroup) ->
+ return noteGroup.playback?
+
+ startNotes: ->
+ notes = []
+ notes.push(@playingNote()) if @noteGroup.playback.playing?
+ notes.push(@scrubNote()) if @noteGroup.playback.scrub?
+ return notes
+
+ endNotes: ->
+ notes = []
+ # TODO: Want scripts to end where the scrub should go, but this doesn't work
+ # when scripts go somewhere then do something else. Figure out a different technique?
+# notes.push(@scrubNote(true)) if @noteGroup.playback.scrub?
+ return notes
+
+ skipNotes: ->
+ notes = []
+ notes.push(@playingNote()) if @noteGroup.playback.playing?
+ notes.push(@scrubNote(true)) if @noteGroup.playback.scrub?
+ return notes
+
+ playingNote: ->
+ note =
+ channel: 'level-set-playing'
+ event: {playing: @noteGroup.playback.playing}
+ return note
+
+ scrubNote: (instant=false) ->
+ scrub = @noteGroup.playback.scrub
+ note =
+ channel: 'level-set-time'
+ event:
+ frameOffset: scrub.frameOffset or 2
+ scrubDuration: if instant then 0 else scrub.duration
+ note.event.time = scrub.toTime if scrub.toTime?
+ note.event.ratio = scrub.toRatio if scrub.toRatio?
+ return note
+
diff --git a/app/lib/scripts/ScriptManager.coffee b/app/lib/scripts/ScriptManager.coffee
new file mode 100644
index 000000000..576d61320
--- /dev/null
+++ b/app/lib/scripts/ScriptManager.coffee
@@ -0,0 +1,321 @@
+# * search for how various places handle or call 'end-current-script' event
+
+
+CocoClass = require 'lib/CocoClass'
+{scriptMatchesEventPrereqs} = require './../world/script_event_prereqs'
+
+allScriptModules = []
+allScriptModules.push(require './SpriteScriptModule')
+allScriptModules.push(require './DOMScriptModule')
+allScriptModules.push(require './SurfaceScriptModule')
+allScriptModules.push(require './PlaybackScriptModule')
+GoalScriptsModule = require './GoalsScriptModule'
+allScriptModules.push(GoalScriptsModule)
+allScriptModules.push(require './SoundScriptModule')
+
+commonScripts = require './commonScripts'
+
+DEFAULT_BOT_MOVE_DURATION = 500
+DEFAULT_SCRUB_DURATION = 1000
+
+module.exports = ScriptManager = class ScriptManager extends CocoClass
+ scriptInProgress: false
+ currentNoteGroup: null
+ currentTimeouts: []
+ worldLoading: true
+ ignoreEvents: false
+ quiet: false
+
+ triggered: []
+ ended: []
+ noteGroupQueue: []
+ originalScripts: [] # use these later when you want to revert to an original state
+
+ subscriptions:
+ 'end-current-script': 'onEndNoteGroup'
+ 'end-all-scripts': 'onEndAll'
+ 'level:started': -> @setWorldLoading(false)
+ 'level:restarted': 'onLevelRestarted'
+ 'level:shift-space-pressed': 'onEndNoteGroup'
+ 'level:escape-pressed': 'onEndAll'
+
+ shortcuts:
+ '⇧+space, space, enter': -> Backbone.Mediator.publish 'level:shift-space-pressed'
+ 'escape': -> Backbone.Mediator.publish 'level:escape-pressed'
+
+ # SETUP / TEARDOWN
+
+ constructor: (options) ->
+ super(options)
+ @originalScripts = options.scripts
+ @view = options.view
+ @session = options.session
+ @initProperties()
+ @addScriptSubscriptions()
+
+ setScripts: (@originalScripts) ->
+ @quiet = true
+ @initProperties()
+ @loadFromSession()
+ @quiet = false
+ @run()
+
+ initProperties: ->
+ @endAll({force:true}) if @scriptInProgress
+ @triggered = []
+ @ended = []
+ @noteGroupQueue = []
+ @scripts = _.cloneDeep(@originalScripts)
+ @scripts = @scripts.concat(_.cloneDeep(commonScripts))
+
+ addScriptSubscriptions: ->
+ idNum = 0
+ makeCallback = (channel) => (event) => @onNote(channel, event)
+ for script in @scripts
+ script.id = (idNum++).toString() unless script.id
+ callback = makeCallback(script.channel) # curry in the channel argument
+ @addNewSubscription(script.channel, callback)
+
+ loadFromSession: ->
+ # load the queue with note groups to skip through
+ @addEndedScriptsFromSession()
+ @addPartiallyEndedScriptFromSession()
+ @fireGoalNotesEarly()
+
+ addPartiallyEndedScriptFromSession: ->
+ scripts = @session.get('state').scripts
+ return unless scripts?.currentScript
+ script = _.find @scripts, {id: scripts.currentScript}
+ return unless script
+ @triggered.push(script.id)
+ noteChain = @processScript(script)
+ if scripts.currentScriptOffset
+ noteGroup.skipMe = true for noteGroup in noteChain[..scripts.currentScriptOffset-1]
+ @addNoteChain(noteChain, false)
+
+ addEndedScriptsFromSession: ->
+ scripts = @session.get('state').scripts
+ return unless scripts
+ endedObj = scripts['ended'] or {}
+ sortedPairs = _.sortBy(_.pairs(endedObj), (pair) -> pair[1])
+ scriptsToSkip = (p[0] for p in sortedPairs)
+ for scriptID in scriptsToSkip
+ script = _.find @scripts, {id: scriptID}
+ unless script
+ console.warn "Couldn't find script for", scriptID, "from scripts", @scripts, "when restoring session scripts."
+ continue
+ @triggered.push(scriptID)
+ @ended.push(scriptID)
+ noteChain = @processScript(script)
+ noteGroup.skipMe = true for noteGroup in noteChain
+ @addNoteChain(noteChain, false)
+
+ fireGoalNotesEarly: ->
+ for noteGroup in @noteGroupQueue
+ @processNoteGroup(noteGroup)
+ for module in noteGroup.modules
+ if module instanceof GoalScriptsModule
+ notes = module.skipNotes()
+ @processNote(note, noteGroup) for note in notes
+
+ setWorldLoading: (@worldLoading) ->
+ @run() unless @worldLoading
+
+ destroy: ->
+ super()
+ @onEndAll()
+
+ # TRIGGERERING NOTES
+
+ onNote: (channel, event) ->
+ return if @ignoreEvents
+ for script in @scripts
+ alreadyTriggered = script.id in @triggered
+ continue unless script.channel is channel
+ continue if alreadyTriggered and not script.repeats
+ continue if script.lastTriggered? and new Date().getTime() - script.lastTriggered < 1
+ continue unless @scriptPrereqsSatisfied(script)
+ continue unless scriptMatchesEventPrereqs(script, event)
+ # everything passed!
+ console.log "SCRIPT: Running script '#{script.id}'"
+ script.lastTriggered = new Date().getTime()
+ @triggered.push(script.id) unless alreadyTriggered
+ noteChain = @processScript(script)
+ @addNoteChain(noteChain)
+ @run()
+
+ scriptPrereqsSatisfied: (script) ->
+ _.every(script.scriptPrereqs or [], (prereq) => prereq in @triggered)
+
+ processScript: (script) ->
+ noteChain = script.noteChain
+ noteGroup.scriptID = script.id for noteGroup in noteChain
+ if noteChain.length
+ lastNoteGroup = noteChain[noteChain.length - 1]
+ lastNoteGroup.isLast = true
+ return noteChain
+
+ addNoteChain: (noteChain, clearYields=true) ->
+ @processNoteGroup(noteGroup) for noteGroup in noteChain
+ noteGroup.index = i for noteGroup, i in noteChain
+ if clearYields
+ noteGroup.skipMe = true for noteGroup in @noteGroupQueue when noteGroup.script.yields
+ @noteGroupQueue.push noteGroup for noteGroup in noteChain
+ @endYieldingNote()
+
+ processNoteGroup: (noteGroup) ->
+ return if noteGroup.modules?
+ if noteGroup.playback?.scrub?
+ noteGroup.playback.scrub.duration ?= DEFAULT_SCRUB_DURATION
+ noteGroup.sprites ?= []
+ for sprite in noteGroup.sprites
+ if sprite.move?
+ sprite.move.duration ?= DEFAULT_BOT_MOVE_DURATION
+ sprite.id ?= 'Captain Anya'
+ noteGroup.script ?= {}
+ noteGroup.script.yields ?= true
+ noteGroup.script.skippable ?= true
+ noteGroup.modules = (new Module(noteGroup, @view) for Module in allScriptModules when Module.neededFor(noteGroup))
+
+ endYieldingNote: ->
+ if @scriptInProgress and @currentNoteGroup?.script.yields
+ @endNoteGroup()
+ return true
+
+ # STARTING NOTES
+
+ run: =>
+ # catch all for analyzing the current state and doing whatever needs to happen next
+ return if @scriptInProgress
+ @skipAhead()
+ return unless @noteGroupQueue.length
+ nextNoteGroup = @noteGroupQueue[0]
+ return if @worldLoading and nextNoteGroup.skipMe
+ return if @worldLoading and not nextNoteGroup.script?.beforeLoad
+ @noteGroupQueue = @noteGroupQueue[1..]
+ @currentNoteGroup = nextNoteGroup
+ @notifyScriptStateChanged()
+ @scriptInProgress = true
+ @currentTimeouts = []
+ console.log "SCRIPT: Starting note group '#{nextNoteGroup.name}'"
+ for module in nextNoteGroup.modules
+ @processNote(note, nextNoteGroup) for note in module.startNotes()
+ if nextNoteGroup.script.duration
+ f = => @onNoteGroupTimeout nextNoteGroup
+ setTimeout(f, nextNoteGroup.script.duration)
+ Backbone.Mediator.publish('note-group-started')
+
+ skipAhead: ->
+ return if @worldLoading
+ return unless @noteGroupQueue[0]?.skipMe
+ @ignoreEvents = true
+ for noteGroup, i in @noteGroupQueue
+ break unless noteGroup.skipMe
+ console.log "SCRIPT: Skipping note group '#{noteGroup.name}'"
+ @processNoteGroup(noteGroup)
+ for module in noteGroup.modules
+ notes = module.skipNotes()
+ @processNote(note, noteGroup) for note in notes
+ @trackScriptCompletions(noteGroup)
+ @noteGroupQueue = @noteGroupQueue[i..]
+ @ignoreEvents = false
+
+ processNote: (note, noteGroup) ->
+ note.event ?= {}
+ if note.delay
+ f = => @sendDelayedNote noteGroup, note
+ @currentTimeouts.push setTimeout(f, note.delay)
+ else
+ @publishNote(note)
+
+ sendDelayedNote: (noteGroup, note) ->
+ # some events should only happen after the bot has moved into position
+ return unless noteGroup is @currentNoteGroup
+ @publishNote(note)
+
+ publishNote: (note) ->
+ Backbone.Mediator.publish(note.channel, note.event)
+
+ # ENDING NOTES
+
+ onLevelRestarted: ->
+ @quiet = true
+ @endAll({force:true})
+ @initProperties()
+ @resetThings()
+ Backbone.Mediator.publish 'script:reset'
+ @quiet = false
+ @run()
+
+ onEndNoteGroup: (e) ->
+ e?.preventDefault()
+ # press enter
+ return unless @currentNoteGroup?.script.skippable
+ @endNoteGroup()
+ @run()
+
+ endNoteGroup: ->
+ return if @ending # kill infinite loops right here
+ @ending = true
+ return unless @currentNoteGroup?
+ console.log "SCRIPT: Ending note group '#{@currentNoteGroup.name}'"
+ clearTimeout(timeout) for timeout in @currentTimeouts
+ for module in @currentNoteGroup.modules
+ @processNote(note, @currentNoteGroup) for note in module.endNotes()
+ Backbone.Mediator.publish 'note-group-ended' unless @quiet
+ @scriptInProgress = false
+ @ended.push(@currentNoteGroup.scriptID) if @currentNoteGroup.isLast
+ @trackScriptCompletions(@currentNoteGroup)
+ @currentNoteGroup = null
+ unless @noteGroupQueue.length
+ @notifyScriptStateChanged()
+ @resetThings()
+ @ending = false
+
+ onEndAll: ->
+ # press escape
+ @endAll()
+
+ endAll: (options) ->
+ options ?= {}
+ if @scriptInProgress
+ return if (not @currentNoteGroup.script.skippable) and (not options.force)
+ @endNoteGroup()
+
+ for noteGroup, i in @noteGroupQueue
+ if ((noteGroup.script?.skippable) is false) and not options.force
+ @noteGroupQueue = @noteGroupQueue[i..]
+ @run()
+ return
+
+ @processNoteGroup(noteGroup)
+ for module in noteGroup.modules
+ notes = module.skipNotes()
+ @processNote(note, noteGroup) for note in notes unless @quiet
+ @trackScriptCompletions(noteGroup) unless @quiet
+
+ @noteGroupQueue = []
+
+ @resetThings()
+
+ onNoteGroupTimeout: (noteGroup) ->
+ return unless noteGroup is @currentNoteGroup
+ @endNoteGroup()
+ @run()
+
+ resetThings: ->
+ Backbone.Mediator.publish 'level-enable-controls', {}
+ Backbone.Mediator.publish 'level-set-letterbox', { on: false }
+
+ trackScriptCompletions: (noteGroup) ->
+ return if @quiet
+ return unless noteGroup.isLast
+ @ended.push(noteGroup.scriptID) unless noteGroup.scriptID in @ended
+ Backbone.Mediator.publish 'script:ended', {scriptID: noteGroup.scriptID}
+
+ notifyScriptStateChanged: ->
+ return if @quiet
+ event =
+ currentScript: @currentNoteGroup?.scriptID or null
+ currentScriptOffset: @currentNoteGroup?.index or 0
+ Backbone.Mediator.publish 'script:state-changed', event
diff --git a/app/lib/scripts/ScriptModule.coffee b/app/lib/scripts/ScriptModule.coffee
new file mode 100644
index 000000000..daa706990
--- /dev/null
+++ b/app/lib/scripts/ScriptModule.coffee
@@ -0,0 +1,39 @@
+CocoClass = require 'lib/CocoClass'
+
+module.exports = class ScriptModule extends CocoClass
+
+ scrubbingTime = 0
+ movementTime = 0
+
+ constructor: (@noteGroup, @view) ->
+ super()
+ if not @noteGroup.prepared
+ @analyzeNoteGroup(noteGroup)
+ @noteGroup.notes ?= []
+ @noteGroup.prepared = true
+
+ # subclass should overwrite these
+
+ @neededFor: -> false
+ startNotes: -> []
+ endNotes: -> []
+ skipNotes: -> @endNotes()
+
+ # common logic
+
+ analyzeNoteGroup: ->
+ # some notes need to happen after others. Calculate the delays
+ @movementTime = @calculateMovementMax(@noteGroup)
+ @scrubbingTime = @noteGroup.playback?.scrub?.duration or 0
+
+ calculateMovementMax: ->
+ sums = {}
+ for sprite in @noteGroup.sprites
+ continue unless sprite.move?
+ sums[sprite.id] ?= 0
+ sums[sprite.id] += sprite.move.duration
+ sums = (sums[k] for k of sums)
+ Math.max(0, sums...)
+
+ maybeApplyDelayToNote: (note) ->
+ note.delay = @scrubbingTime + @movementTime
\ No newline at end of file
diff --git a/app/lib/scripts/SoundScriptModule.coffee b/app/lib/scripts/SoundScriptModule.coffee
new file mode 100644
index 000000000..4c9282c2a
--- /dev/null
+++ b/app/lib/scripts/SoundScriptModule.coffee
@@ -0,0 +1,34 @@
+ScriptModule = require './ScriptModule'
+
+currentMusic = null
+standingBy = null
+
+{me} = require('lib/auth')
+
+module.exports = class SoundScriptModule extends ScriptModule
+ @neededFor: (noteGroup) ->
+ return noteGroup.sound?
+
+ startNotes: ->
+ notes = []
+ notes.push(@addSuppressSelectionSoundsNote()) if @noteGroup.sound.suppressSelectionSounds?
+ notes.push(@addMusicNote()) if @noteGroup.sound.music?
+ return notes
+
+ endNotes: ->
+ return []
+
+ skipNotes: ->
+ return @startNotes()
+
+ addSuppressSelectionSoundsNote: ->
+ note =
+ channel: 'level-suppress-selection-sounds'
+ event: {suppress: @noteGroup.sound.suppressSelectionSounds}
+ return note
+
+ addMusicNote: ->
+ note =
+ channel: 'level-play-music'
+ event: @noteGroup.sound.music
+ return note
diff --git a/app/lib/scripts/SpriteScriptModule.coffee b/app/lib/scripts/SpriteScriptModule.coffee
new file mode 100644
index 000000000..8bd2af6dd
--- /dev/null
+++ b/app/lib/scripts/SpriteScriptModule.coffee
@@ -0,0 +1,71 @@
+ScriptModule = require './ScriptModule'
+{me} = require 'lib/auth'
+
+module.exports = class SpritesScriptModule extends ScriptModule
+ @neededFor: (noteGroup) ->
+ return noteGroup.sprites?.length
+
+ startNotes: ->
+ notes = []
+ @moveSums = {}
+ @speakingSprites = {}
+ for sprite in @noteGroup.sprites or []
+ notes.push(@spriteMoveNote sprite) if sprite.move?
+ for sprite in @noteGroup.sprites or []
+ notes.push(@spriteSayNote(sprite, @noteGroup.script)) if sprite.say?
+ notes.push(@spriteSelectNote sprite) if sprite.select?
+ return (n for n in notes when n)
+
+ spriteMoveNote: (sprite, instant=false) ->
+ duration = if instant then 0 else sprite.move.duration
+ note =
+ channel: 'level-sprite-move'
+ event:
+ pos: sprite.move.target
+ duration: duration
+ spriteID: sprite.id
+ if duration
+ @moveSums[sprite.id] ?= 0
+ note.delay = @scrubbingTime + @moveSums[sprite.id]
+ @moveSums[sprite.id] += sprite.move.duration
+ return note
+
+ spriteSayNote: (sprite, script) ->
+ return if @speakingSprites[sprite.id]
+ responses = sprite.say.responses
+ responses = [] unless script.skippable
+ for response in responses ? []
+ response.text = response.i18n?[me.lang()]?.text ? response.text
+ text = sprite.say.i18n?[me.lang()]?.text or sprite.say.text
+ blurb = sprite.say.i18n?[me.lang()]?.blurb or sprite.say.blurb
+ sound = sprite.say.sound?[me.lang()]?.sound or sprite.say.sound
+ note =
+ channel: 'level-sprite-dialogue'
+ event:
+ message: text
+ blurb: blurb
+ mood: sprite.say.mood or "explain"
+ responses: responses
+ spriteID: sprite.id
+ sound: sound
+ @maybeApplyDelayToNote note
+ return note
+
+ spriteSelectNote: (sprite) ->
+ note =
+ channel: 'level-select-sprite'
+ event:
+ thangID: if sprite.select then sprite.id else null
+ return note
+
+ endNotes: ->
+ notes = {}
+ for sprite in @noteGroup.sprites or []
+ notes[sprite.id] ?= {}
+ notes[sprite.id]['move'] = (@spriteMoveNote sprite, true) if sprite.move?
+ notes[sprite.id]['say'] = { channel: 'level-sprite-clear-dialogue' } if sprite.say?
+ noteArray = []
+ for spriteID of notes
+ for type of notes[spriteID]
+ noteArray.push(notes[spriteID][type])
+ noteArray
diff --git a/app/lib/scripts/SurfaceScriptModule.coffee b/app/lib/scripts/SurfaceScriptModule.coffee
new file mode 100644
index 000000000..28424130f
--- /dev/null
+++ b/app/lib/scripts/SurfaceScriptModule.coffee
@@ -0,0 +1,49 @@
+ScriptModule = require './ScriptModule'
+
+module.exports = class SurfaceScriptModule extends ScriptModule
+ @neededFor: (noteGroup) ->
+ return noteGroup.surface?
+
+ startNotes: ->
+ notes = []
+ notes.push(@surfaceCameraNote()) if @noteGroup.surface.focus?
+ notes.push(@surfaceHighlightNote()) if @noteGroup.surface.highlight?
+ notes.push(@surfaceLockSelectNote()) if @noteGroup.surface.lockSelect?
+ return notes
+
+ endNotes: ->
+ notes = []
+ notes.push({channel:'level-highlight-sprites', event: {thangIDs: []}}) if @noteGroup.surface.highlight?
+ notes.push(@surfaceCameraNote(true)) if @noteGroup.surface.focus?
+ notes.push(@surfaceLockSelectNote()) if @noteGroup.surface.lockSelect?
+ return notes
+
+ skipNotes: ->
+ notes = []
+ notes.push(@surfaceCameraNote(true)) if @noteGroup.surface.focus?
+ notes.push(@surfaceLockSelectNote()) if @noteGroup.surface.lockSelect?
+ return notes
+
+ surfaceCameraNote: (instant=false) ->
+ focus = @noteGroup.surface.focus
+ e = {}
+ e.pos = focus.target if _.isPlainObject focus.target
+ e.thangID = focus.target if _.isString focus.target
+ e.zoom = focus.zoom or 2.0
+ e.duration = if focus.duration? then focus.duration else 1500
+ e.duration = 0 if instant
+ e.bounds = focus.bounds if focus.bounds?
+ return { channel: 'level-set-surface-camera', event: e }
+
+ surfaceHighlightNote: ->
+ highlight = @noteGroup.surface.highlight
+ note =
+ channel: 'level-highlight-sprites'
+ event:
+ thangIDs: highlight.targets
+ delay: highlight.delay
+ @maybeApplyDelayToNote note, @noteGroup
+ return note
+
+ surfaceLockSelectNote: ->
+ return { channel: 'level-lock-select', event: {lock: @noteGroup.surface.lockSelect} }
diff --git a/app/lib/scripts/commonScripts.coffee b/app/lib/scripts/commonScripts.coffee
new file mode 100644
index 000000000..0b80415a6
--- /dev/null
+++ b/app/lib/scripts/commonScripts.coffee
@@ -0,0 +1,85 @@
+module.exports = [
+ {
+ "channel": "surface:sprite-selected",
+ "noteChain": [
+ {
+ "sprites": [
+ {
+ "id": "Captain Anya",
+ "say": {
+ "text": "What do you need help with, boss?",
+ "responses": [
+ {
+ "text": "What do I do next?",
+ "channel": "help-next"
+ },
+ {
+ "text": "I'm lost",
+ "channel": "help-overview"
+ },
+ {
+ "text": "Never mind.",
+ "channel": "end-current-script"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ],
+ "eventPrereqs": [
+ {
+ "eventProps": [
+ "sprite",
+ "thang",
+ "id"
+ ],
+ "equalTo": "Captain Anya"
+ }
+ ],
+ "id": "Anya Clicked Guide"
+ "repeats": true
+ },
+ {
+ "channel": "help-next",
+ "noteChain": [
+ {
+ "sprites": [
+ {
+ "id": "Captain Anya",
+ "say": {
+ "text": "Look for a list of objectives on the upper left. They tell you what to focus on."
+ }
+ }
+ ],
+ "dom": {
+ "highlight": {
+ "target": "#primary-goals-list"
+ }
+ }
+ }
+ ],
+ "id": "Clicked Help Next"
+ },
+ {
+ "channel": "help-overview",
+ "noteChain": [
+ {
+ "sprites": [
+ {
+ "id": "Captain Anya",
+ "say": {
+ "text": "Click the 'Guide' button on the upper right for an overview of the level and some hints."
+ }
+ }
+ ],
+ "dom": {
+ "highlight": {
+ "target": "#docs-button"
+ }
+ }
+ }
+ ],
+ "id": "Clicked I'm Lost"
+ }
+]
diff --git a/app/lib/scripts/defaultScripts.coffee b/app/lib/scripts/defaultScripts.coffee
new file mode 100644
index 000000000..25c851e7b
--- /dev/null
+++ b/app/lib/scripts/defaultScripts.coffee
@@ -0,0 +1,36 @@
+module.exports = [
+ {
+ "id": "Add Default Goals",
+ "channel": "god:new-world-created",
+ "noteChain": [
+ {
+ "goals": {
+ "add": [
+ {
+ "name": "Humans Survive",
+ "id": "humans-survive",
+ "saveThangs": [
+ "humans"
+ ],
+ "worldEndsAfter": 3,
+ "howMany": 1,
+ "hiddenGoal": true
+ },
+ {
+ "name": "Ogres Die",
+ "id": "ogres-die",
+ "killThangs": [
+ "ogres"
+ ],
+ "worldEndsAfter": 3,
+ "hiddenGoal": true
+ }
+ ]
+ }
+ }
+ ]
+ }
+]
+
+
+# Could add other default scripts, like not having to redo Victory Playback sequence from scratch every time.
diff --git a/app/lib/sprites/SpriteBuilder.coffee b/app/lib/sprites/SpriteBuilder.coffee
new file mode 100644
index 000000000..154e90cc0
--- /dev/null
+++ b/app/lib/sprites/SpriteBuilder.coffee
@@ -0,0 +1,120 @@
+module.exports = class SpriteBuilder
+ constructor: (@thangType, @options) ->
+ raw = _.cloneDeep(@thangType.get('raw'))
+ @shapeStore = raw.shapes
+ @containerStore = raw.containers
+ @animationStore = raw.animations
+
+ setOptions: (@options) ->
+
+ buildMovieClip: (animationName, movieClipArgs...) ->
+ animData = @animationStore[animationName]
+ console.log "couldn't find animData from", @animationStore, "for", animationName unless animData
+ locals = {}
+ _.extend locals, @buildMovieClipShapes(animData.shapes)
+ _.extend locals, @buildMovieClipContainers(animData.containers)
+ _.extend locals, @buildMovieClipAnimations(animData.animations)
+ _.extend locals, @buildMovieClipGraphics(animData.graphics)
+ anim = new createjs.MovieClip()
+ movieClipArgs ?= []
+ labels = {}
+ labels[animationName] = 0
+ anim.initialize(
+ movieClipArgs[0] ? createjs.MovieClip.INDEPENDENT, # mode
+ movieClipArgs[1] ? 0, # start position
+ movieClipArgs[2] ? true, # loops
+ labels)
+ for tweenData in animData.tweens
+ tween = createjs.Tween
+ for func in tweenData
+ args = _.cloneDeep(func.a)
+ @dereferenceArgs(args, locals)
+ tween = tween[func.n](args...)
+ anim.timeline.addTween(tween)
+
+ anim.nominalBounds = new createjs.Rectangle(animData.bounds...)
+ if animData.frameBounds
+ anim.frameBounds = (new createjs.Rectangle(bounds...) for bounds in animData.frameBounds)
+ anim
+
+ dereferenceArgs: (args, locals) ->
+ for key, val of args
+ if locals[val]
+ args[key] = locals[val]
+ else if val is null
+ args[key] = {}
+ else if _.isString(val) and val.indexOf('createjs.') is 0
+ args[key] = eval(val) # TODO: Security risk
+ else if _.isObject(val) or _.isArray(val)
+ @dereferenceArgs(val, locals)
+ args
+
+ buildMovieClipShapes: (localShapes) ->
+ map = {}
+ for localShape in localShapes
+ if localShape.im
+ shape = new createjs.Shape()
+ shape._off = true
+ else
+ shape = @buildShapeFromStore(localShape.gn)
+ if localShape.m
+ shape.mask = map[localShape.m]
+ map[localShape.bn] = shape
+ map
+
+ buildMovieClipContainers: (localContainers) ->
+ map = {}
+ for localContainer in localContainers
+ container = @buildContainerFromStore(localContainer.gn)
+ container.setTransform(localContainer.t...)
+ container._off = localContainer.o if localContainer.o?
+ container.alpha = localContainer.al if localContainer.al?
+ map[localContainer.bn] = container
+ map
+
+ buildMovieClipAnimations: (localAnimations) ->
+ map = {}
+ for localAnimation in localAnimations
+ animation = @buildMovieClip(localAnimation.gn, localAnimation.a)
+ animation.setTransform(localAnimation.t...)
+ map[localAnimation.bn] = animation
+ map
+
+ buildMovieClipGraphics: (localGraphics) ->
+ map = {}
+ for localGraphic in localGraphics
+ graphic = new createjs.Graphics().p(localGraphic.p)
+ map[localGraphic.bn] = graphic
+ map
+
+ buildShapeFromStore: (shapeKey, debug=false) ->
+ shapeData = @shapeStore[shapeKey]
+ shape = new createjs.Shape()
+ if shapeData.lf?
+ shape.graphics.lf shapeData.lf...
+ else if shapeData.fc?
+ shape.graphics.f shapeData.fc
+ if shapeData.ls?
+ shape.graphics.ls shapeData.ls...
+ else if shapeData.sc?
+ shape.graphics.s shapeData.sc
+ shape.graphics.ss shapeData.ss... if shapeData.ss?
+ shape.graphics.de shapeData.de... if shapeData.de?
+ shape.graphics.p shapeData.p if shapeData.p?
+ shape.setTransform shapeData.t...
+ shape
+
+ buildContainerFromStore: (containerKey) ->
+ console.error "Yo we don't have no", containerKey unless containerKey
+ contData = @containerStore[containerKey]
+ cont = new createjs.Container()
+ cont.initialize()
+ for childData in contData.c
+ if _.isString(childData)
+ child = @buildShapeFromStore(childData)
+ else
+ child = @buildContainerFromStore(childData.gn)
+ child.setTransform(childData.t...)
+ cont.addChild(child)
+ cont.bounds = new createjs.Rectangle(contData.b...)
+ cont
diff --git a/app/lib/sprites/SpriteParser.coffee b/app/lib/sprites/SpriteParser.coffee
new file mode 100644
index 000000000..1fb1598f5
--- /dev/null
+++ b/app/lib/sprites/SpriteParser.coffee
@@ -0,0 +1,486 @@
+module.exports = class SpriteParser
+ constructor: (@thangTypeModel) ->
+ # Create a new ThangType, or work with one we've been building
+ @thangType = _.cloneDeep(@thangTypeModel.attributes.raw)
+ @thangType ?= {shapes: {}, containers: {}, animations: {}}
+
+ # Internal parser state
+ @shapeLongKeys = {}
+ @containerLongKeys = {}
+ @containerRenamings = {}
+ @animationLongKeys = {}
+ @animationRenamings = {}
+ @populateLongKeys()
+
+ populateLongKeys: ->
+ for shortKey, shape of @thangType.shapes
+ longKey = JSON.stringify(_.values(shape))
+ @shapeLongKeys[longKey] = shortKey
+ for shortKey, container of @thangType.containers
+ longKey = JSON.stringify(_.values(container))
+ @containerLongKeys[longKey] = shortKey
+ for shortKey, animation of @thangType.animations
+ longKey = JSON.stringify(_.values(animation))
+ @animationLongKeys[longKey] = shortKey
+
+ parse: (source) ->
+ options = {loc: false, range: true}
+ ast = esprima.parse source, options
+ blocks = @findBlocks ast, source
+ containers = _.filter blocks, {kind: 'Container'}
+ movieClips = _.filter blocks, {kind: 'MovieClip'}
+ if movieClips.length
+ # First movie clip is root, so do it last
+ movieClips = movieClips[1 ... movieClips.length].concat([movieClips[0]])
+ else if containers.length
+ # First container is root, so do it last
+ containers = containers[1 ... containers.length].concat([containers[0]])
+ mainClip = _.last(movieClips) ? _.last(containers)
+ @animationName = mainClip.name
+ for container in containers
+ [shapeKeys, localShapes] = @getShapesFromBlock container, source
+ localContainers = @getContainersFromMovieClip container, source
+ addChildArgs = @getAddChildCallArguments container, source
+ instructions = []
+ for bn in addChildArgs
+ gotIt = false
+ for shape in localShapes
+ if shape.bn is bn
+ instructions.push shape.gn
+ gotIt = true
+ break
+ continue if gotIt
+ for c in localContainers
+ if c.bn is bn
+ instructions.push { t: c.t, gn: c.gn }
+ break
+ @addContainer {c: instructions, b: container.bounds}, container.name
+ for movieClip in movieClips
+ localGraphics = @getGraphicsFromBlock(movieClip, source)
+ [shapeKeys, localShapes] = @getShapesFromBlock movieClip, source
+ localContainers = @getContainersFromMovieClip movieClip, source, true
+ localAnimations = @getAnimationsFromMovieClip movieClip, source, true
+ localTweens = @getTweensFromMovieClip movieClip, source, localShapes, localContainers, localAnimations
+ @addAnimation {
+ shapes: localShapes
+ containers: localContainers
+ animations: localAnimations
+ tweens: localTweens
+ graphics: localGraphics
+ bounds: movieClip.bounds
+ frameBounds: movieClip.frameBounds
+ }, movieClip.name
+
+ @saveToModel()
+ return movieClips[0]?.name
+
+ saveToModel: ->
+ @thangTypeModel.set('raw', @thangType)
+
+ addShape: (shape) ->
+ longKey = JSON.stringify(_.values(shape))
+ shortKey = @shapeLongKeys[longKey]
+ unless shortKey?
+ shortKey = '' + _.size @thangType.shapes
+ @thangType.shapes[shortKey] = shape
+ @shapeLongKeys[longKey] = shortKey
+ return shortKey
+
+ addContainer: (container, name) ->
+ longKey = JSON.stringify(_.values(container))
+ shortKey = @containerLongKeys[longKey]
+ if not shortKey?
+ shortKey = name
+ if @thangType.containers[shortKey]?
+ shortKey = @animationName + ":" + name
+ @thangType.containers[shortKey] = container
+ @containerLongKeys[longKey] = shortKey
+ @containerRenamings[name] = shortKey
+ return shortKey
+
+ addAnimation: (animation, name) ->
+ longKey = JSON.stringify(_.values(animation))
+ shortKey = @animationLongKeys[longKey]
+ if shortKey?
+ @animationRenamings[shortKey] = name
+ else
+ shortKey = name
+ @thangType.animations[shortKey] = animation
+ @animationLongKeys[longKey] = shortKey
+ @animationRenamings[name] = name
+ return shortKey
+
+ walk: (node, parent, fn) ->
+ node.parent = parent
+ for key, child of node
+ continue if key is 'parent'
+ if _.isArray child
+ for grandchild in child
+ @walk grandchild, node, fn if _.isString grandchild?.type
+ else if _.isString child?.type
+ node.parent = parent
+ @walk child, node, fn
+ fn node
+
+ orphanify: (node) ->
+ delete node.parent if node.parent
+ for key, child of node
+ continue if key is 'parent'
+ if _.isArray child
+ for grandchild in child
+ @orphanify grandchild if _.isString grandchild?.type
+ else if _.isString child?.type
+ delete node.parent if node.parent
+ @orphanify child
+
+ subSourceFromRange: (range, source) ->
+ source[range[0] ... range[1]]
+
+ grabFunctionArguments: (source, literal=false) ->
+ # Replace first and last parens with brackets to turn args into array
+ args = source.replace(/.*?\(/, '[').replace(/\)[^)]*?$/, ']')
+ if literal then eval(args) else args
+
+ findBlocks: (ast, source) ->
+ functionExpressions = []
+ rectangles = []
+ gatherFunctionExpressions = (node) =>
+ if node.type is 'FunctionExpression'
+ name = node.parent?.left?.property?.name
+ if name
+ expression = node.parent.parent
+ kind = expression.parent.right.right.callee.property.name
+ statement = node.parent.parent.parent.parent
+ statementIndex = _.indexOf statement.parent.body, statement
+ nominalBoundsStatement = statement.parent.body[statementIndex + 1]
+ nominalBoundsRange = nominalBoundsStatement.expression.right.range
+ nominalBoundsSource = @subSourceFromRange nominalBoundsRange, source
+ nominalBounds = @grabFunctionArguments nominalBoundsSource, true
+
+ frameBoundsStatement = statement.parent.body[statementIndex + 2]
+ if frameBoundsStatement
+ frameBoundsRange = frameBoundsStatement.expression.right.range
+ frameBoundsSource = @subSourceFromRange frameBoundsRange, source
+ if frameBoundsSource.search(/\[rect/) is -1 # some other statement; we don't have multiframe bounds
+ console.log "Didn't have multiframe bounds for this movie clip."
+ frameBounds = [nominalBounds]
+ else
+ lastRect = nominalBounds
+ frameBounds = []
+ for arg, i in frameBoundsStatement.expression.right.elements
+ bounds = null
+ argSource = @subSourceFromRange arg.range, source
+ if arg.type is 'Identifier'
+ bounds = lastRect
+ else if arg.type is 'NewExpression'
+ bounds = @grabFunctionArguments argSource, true
+ else if arg.type is 'AssignmentExpression'
+ bounds = @grabFunctionArguments argSource.replace('rect=', ''), true
+ lastRect = bounds
+ frameBounds.push bounds
+ else
+ console.log "Didn't have multiframe bounds for this movie clip!"
+ frameBounds = [nominalBounds]
+
+ functionExpressions.push {name: name, bounds: nominalBounds, frameBounds: frameBounds, expression: node.parent.parent, kind: kind}
+ @walk ast, null, gatherFunctionExpressions
+ functionExpressions
+
+ ###
+ this.shape_1.graphics.f("#605E4A").s().p("AAOD/IgOgaIAEhkIgmgdIgMgBIgPgFIgVgJQA1h9g8jXQAQAHAOASQAQAUAKAeQARAuAJBJQAHA/gBA5IAAADIACAfIAFARIACAGIAEAHIAHAHQAVAXAQAUQAUAaANAUIABACIgsgdIgggXIAAAnIABAwIgBgBg");
+ this.shape_1.sett(23.2,30.1);
+
+ this.shape.graphics.f().s("#000000").ss(0.1,1,1).p("AAAAAQAAAAAAAA");
+ this.shape.sett(3.8,22.4);
+ ###
+
+ getGraphicsFromBlock: (block, source) ->
+ block = block.expression.object.right.body
+ localGraphics = []
+ gatherShapeDefinitions = (node) =>
+ return unless node.type is 'NewExpression' and node.callee.property.name is 'Graphics'
+ blockName = node.parent.parent.parent.id.name
+ graphicsString = node.parent.parent.arguments[0].value
+ localGraphics.push({p:graphicsString, bn:blockName})
+
+ @walk block, null, gatherShapeDefinitions
+ return localGraphics
+
+ getShapesFromBlock: (block, source) ->
+ block = block.expression.object.right.body
+ shapeKeys = []
+ localShapes = []
+ gatherShapeDefinitions = (node) =>
+ return unless node.type is 'MemberExpression'
+ name = node.object?.object?.property?.name
+ if not name
+ name = node.parent?.parent?.id?.name
+ return unless name and name.indexOf('mask') is 0 and node.property?.name is 'Shape'
+ shape = { bn: name, im: true }
+ localShapes.push shape
+ return
+ return unless name.search('shape') is 0 and node.object.property?.name is 'graphics'
+ fillCall = node.parent
+ if fillCall.callee.property.name is 'lf'
+ linearGradientFillSource = @subSourceFromRange fillCall.parent.range, source
+ linearGradientFill = @grabFunctionArguments linearGradientFillSource.replace(/.*?lf\(/, 'lf('), true
+ else
+ fillColor = fillCall.arguments[0]?.value ? null
+ console.error "What is this?! Not a fill!" unless fillCall.callee.property.name is 'f'
+ strokeCall = node.parent.parent.parent.parent
+ if strokeCall.object.callee.property.name is 'ls'
+ linearGradientStrokeSource = @subSourceFromRange strokeCall.parent.range, source
+ linearGradientStroke = @grabFunctionArguments linearGradientStrokeSource.replace(/.*?ls\(/, 'ls(').replace(/\).ss\(.*/, ')'), true
+ else
+ strokeColor = strokeCall.object.arguments?[0]?.value ? null
+ console.error "What is this?! Not a stroke!" unless strokeCall.object.callee.property.name is 's'
+ strokeStyle = null
+ graphicsStatement = strokeCall.parent
+ if strokeColor or linearGradientStroke
+ # There might now be an extra node, ss, for stroke style
+ strokeStyleSource = @subSourceFromRange strokeCall.parent.range, source
+ if strokeStyleSource.search(/ss\(/) isnt -1
+ strokeStyle = @grabFunctionArguments strokeStyleSource.replace(/.*?ss\(/, 'ss('), true
+ graphicsStatement = strokeCall.parent.parent.parent
+ if graphicsStatement.callee.property.name is 'de'
+ drawEllipseSource = @subSourceFromRange graphicsStatement.parent.range, source
+ drawEllipse = @grabFunctionArguments drawEllipseSource.replace(/.*?de\(/, 'de('), true
+ else
+ path = graphicsStatement.arguments?[0]?.value ? null
+ console.error "What is this?! Not a path!" unless graphicsStatement.callee.property.name is 'p'
+ body = graphicsStatement.parent.parent.body
+ graphicsStatementIndex = _.indexOf body, graphicsStatement.parent
+ t = body[graphicsStatementIndex + 1].expression
+ tSource = @subSourceFromRange t.range, source
+ if tSource.search('setTransform') is -1
+ t = [0, 0]
+ else
+ t = @grabFunctionArguments tSource, true
+
+ for statement in body.slice(graphicsStatementIndex + 2)
+ # Handle things like
+ # this.shape.mask = this.shape_1.mask = this.shape_2.mask = this.shape_3.mask = mask;
+ continue unless statement.expression?.left?.property?.name is 'mask'
+ exp = statement.expression
+ matchedName = false
+ while exp
+ matchedName = matchedName or exp.left?.object?.property?.name is name
+ mask = exp.name
+ exp = exp.right
+ continue unless matchedName
+ break
+
+ shape = {t: t}
+ shape.p = path if path
+ shape.de = drawEllipse if drawEllipse
+ shape.sc = strokeColor if strokeColor
+ shape.ss = strokeStyle if strokeStyle
+ shape.fc = fillColor if fillColor
+ shape.lf = linearGradientFill if linearGradientFill
+ shape.ls = linearGradientStroke if linearGradientStroke
+ if name.search('shape') isnt -1 and shape.fc is "rgba(0,0,0,0.451)" and not shape.ss and not shape.sc
+ console.log "Skipping a shadow", name, shape, "because we're doing shadows separately now."
+ return
+ shapeKeys.push shapeKey = @addShape shape
+ localShape = {bn: name, gn: shapeKey}
+ localShape.m = mask if mask
+ localShapes.push localShape
+
+ @walk block, null, gatherShapeDefinitions
+ return [shapeKeys, localShapes]
+
+ getContainersFromMovieClip: (movieClip, source, possibleAnimations=false) ->
+ block = movieClip.expression.object.right.body
+ localContainers = []
+ gatherContainerDefinitions = (node) =>
+ return unless node.type is 'Identifier' and node.name is 'lib'
+ args = node.parent.parent.arguments
+ libName = node.parent.property.name
+ return if args.length and not possibleAnimations # might be animation, not container
+ gn = @containerRenamings[libName]
+ return if possibleAnimations and not gn # not a container we know about
+ bn = node.parent.parent.parent.left.property.name
+ expressionStatement = node.parent.parent.parent.parent
+ body = expressionStatement.parent.body
+ expressionStatementIndex = _.indexOf body, expressionStatement
+ t = body[expressionStatementIndex + 1].expression
+ tSource = @subSourceFromRange t.range, source
+ t = @grabFunctionArguments tSource, true
+ o = body[expressionStatementIndex + 2].expression
+ localContainer = {bn: bn, t: t, gn: gn}
+ if o and o.left?.object?.property?.name is bn and o.left.property?.name is '_off'
+ localContainer.o = o.right.value
+ else if o and o.left?.property?.name is 'alpha'
+ localContainer.al = o.right.value
+ localContainers.push localContainer
+
+ @walk block, null, gatherContainerDefinitions
+ return localContainers
+
+ getAnimationsFromMovieClip: (movieClip, source, possibleContainers=false) ->
+ block = movieClip.expression.object.right.body
+ localAnimations = []
+ gatherAnimationDefinitions = (node) =>
+ return unless node.type is 'Identifier' and node.name is 'lib'
+ args = node.parent.parent.arguments
+ libName = node.parent.property.name
+ return unless args.length or possibleContainers # might be container, not animation
+ return if @containerRenamings[libName] and not @animationRenamings[libName] # we have it as a container
+ args = @grabFunctionArguments @subSourceFromRange(node.parent.parent.range, source), true
+ bn = node.parent.parent.parent.left.property.name
+ expressionStatement = node.parent.parent.parent.parent
+ body = expressionStatement.parent.body
+ expressionStatementIndex = _.indexOf body, expressionStatement
+ t = body[expressionStatementIndex + 1].expression
+ tSource = @subSourceFromRange t.range, source
+ t = @grabFunctionArguments tSource, true
+ gn = @animationRenamings[libName] ? libName
+ localAnimation = {bn: bn, t: t, gn: gn, a: args}
+ localAnimations.push localAnimation
+
+ @walk block, null, gatherAnimationDefinitions
+ return localAnimations
+
+ getTweensFromMovieClip: (movieClip, source, localShapes, localContainers, localAnimations) ->
+ block = movieClip.expression.object.right.body
+ localTweens = []
+ gatherTweens = (node) =>
+ return unless node.property?.name is 'addTween'
+ callExpressions = []
+ tweenNode = node
+ gatherCallExpressions = (node) =>
+ return unless node.type is 'CallExpression'
+ name = node.callee.property?.name
+ return unless name in ['get', 'to', 'wait']
+ return if name is 'get' and callExpressions.length # avoid Ease calls in the tweens
+ flattenedRanges = _.flatten [a.range for a in node.arguments]
+ range = [_.min(flattenedRanges), _.max(flattenedRanges)]
+ # Replace "this." references with just the "name"
+ argsSource = @subSourceFromRange(range, source)
+ argsSource = argsSource.replace(/mask/g, 'this.mask') # so the mask thing will be handled correctly as a blockName in the next line
+ argsSource = argsSource.replace(/this\.([a-z_0-9]+)/ig, '"$1"') # turns this.shape literal to "shape" string
+ argsSource = argsSource.replace(/cjs(.+)\)/, '"createjs$1)"') # turns cjs.Ease.get(0.5)
+
+ args = eval "[#{argsSource}]"
+ if args[0]?.state?[0]?.t?.search?("shape") is 0 and not _.find(localShapes, bn: args[0].state[0].t)
+ console.log "Skipping tween", name, argsSource, args, "from localShapes", localShapes, "presumably because it's a shadow we skipped."
+ return
+ callExpressions.push {n: name, a: args}
+ @walk node.parent.parent, null, gatherCallExpressions
+ localTweens.push callExpressions
+
+ @walk block, null, gatherTweens
+ return localTweens
+
+ getAddChildCallArguments: (block, source) ->
+ block = block.expression.object.right.body
+ localArgs = []
+ gatherAddChildCalls = (node) =>
+ return unless node.type is "Identifier" and node.name is "addChild"
+ args = node.parent.parent.arguments
+ args = (arg.property.name for arg in args)
+ localArgs.push arg for arg in args
+ return
+
+ @walk block, null, gatherAddChildCalls
+ return localArgs
+###
+
+ this.timeline.addTween(cjs.Tween.get(this.instance).to({scaleX:0.82,scaleY:0.79,rotation:-10.8,x:98.4,y:-86.5},4).to({scaleY:0.7,rotation:9.3,x:95.6,y:-48.8},1).to({scaleX:0.82,scaleY:0.61,rotation:29.4,x:92.8,y:-11},1).to({regX:7.3,scaleX:0.82,scaleY:0.53,rotation:49.7,x:90.1,y:26.6},1).to({regX:7.2,regY:29.8,scaleY:0.66,rotation:19.3,x:101.2,y:-27.8},2).to({regY:29.9,scaleY:0.79,rotation:-10.8,x:98.4,y:-86.5},2).to({scaleX:0.84,scaleY:0.83,rotation:-30.7,x:68.4,y:-110},2).to({regX:7.3,scaleX:0.84,scaleY:0.84,rotation:-33.9,x:63.5,y:-114},1).wait(1));
+
+###
+
+###
+simpleSample = """
+(function (lib, img, cjs) {
+
+var p; // shortcut to reference prototypes
+
+// stage content:
+(lib.enemy_flying_move_side = function(mode,startPosition,loop) {
+ this.initialize(mode,startPosition,loop,{});
+
+ // D_Head
+ this.instance = new lib.Dragon_Head();
+ this.instance.setTransform(227,200.5,1,1,0,0,0,51.9,42.5);
+
+ this.timeline.addTween(cjs.Tween.get(this.instance).to({y:182.9},7).to({y:200.5},7).wait(1));
+
+ // Layer 7
+ this.shape = new cjs.Shape();
+ this.shape.graphics.f("#4F6877").s().p("AgsAxQgSgVgB");
+ this.shape.setTransform(283.1,146.1);
+
+ // Layer 7 2
+ this.shape_1 = new cjs.Shape();
+ this.shape_1.graphics.f("rgba(255,255,255,0.4)").s().p("ArTs0QSMB7EbVGQhsBhiGBHQjg1IvVkhg");
+ this.shape_1.setTransform(400.2,185.5);
+
+ this.timeline.addTween(cjs.Tween.get({}).to({state:[]}).to({state:[{t:this.shape}]},7).to({state:[]},2).wait(6));
+
+ // Wing
+ this.instance_9 = new lib.Wing_Animation("synched",0);
+ this.instance_9.setTransform(313.9,145.6,1,1,0,0,0,49,-83.5);
+
+ this.timeline.addTween(cjs.Tween.get(this.instance_9).to({y:128,startPosition:7},7).wait(1));
+
+ // Example hard one with two shapes
+ this.timeline.addTween(cjs.Tween.get({}).to({state:[]}).to({state:[{t:this.shape}]},7).to({state:[{t:this.shape_1}]},1).to({state:[]},1).wait(7));
+
+
+}).prototype = p = new cjs.MovieClip();
+p.nominalBounds = new cjs.Rectangle(7.1,48.9,528.7,431.1);
+
+(lib.Dragon_Head = function() {
+ this.initialize();
+
+ // Isolation Mode
+ this.shape = new cjs.Shape();
+ this.shape.graphics.f("#1D2226").s().p("AgVAwQgUgdgN");
+ this.shape.setTransform(75,25.8);
+
+ this.shape_1 = new cjs.Shape();
+ this.shape_1.graphics.f("#1D2226").s().p("AgnBXQACABAF");
+ this.shape_1.setTransform(80.8,22);
+
+ this.addChild(this.shape_1,this.shape);
+}).prototype = p = new cjs.Container();
+p.nominalBounds = new cjs.Rectangle(5.8,0,87.9,85);
+
+(lib.WingPart_01 = function() {
+ this.initialize();
+
+ // Layer 1
+ this.shape = new cjs.Shape();
+ this.shape.graphics.f("#DBDDBC").s().p("Ag3BeQgCgRA");
+ this.shape.setTransform(10.6,19.7,1.081,1.081);
+
+ this.shape_1 = new cjs.Shape();
+ this.shape_1.graphics.f("#1D2226").s().p("AB4CDQgGg");
+ this.shape_1.setTransform(19.9,17.6,1.081,1.081);
+
+ this.shape_2 = new cjs.Shape();
+ this.shape_2.graphics.f("#605E4A").s().p("AiECbQgRg");
+ this.shape_2.setTransform(19.5,18.4,1.081,1.081);
+
+ this.addChild(this.shape_2,this.shape_1,this.shape);
+}).prototype = p = new cjs.Container();
+p.nominalBounds = new cjs.Rectangle(0,-3.1,40,41.6);
+
+
+(lib.Wing_Animation = function(mode,startPosition,loop) {
+ this.initialize(mode,startPosition,loop,{});
+
+ // WP_02
+ this.instance = new lib.WingPart_01();
+ this.instance.setTransform(53.6,-121.9,0.854,0.854,-40.9,0,0,7.2,29.9);
+
+ this.timeline.addTween(cjs.Tween.get(this.instance).to({scaleY:0.7,rotation:9.3,x:95.6,y:-48.8},1).wait(1));
+
+}).prototype = p = new cjs.MovieClip();
+p.nominalBounds = new cjs.Rectangle(-27.7,-161.6,153.4,156.2);
+
+})(lib = lib||{}, images = images||{}, createjs = createjs||{});
+var lib, images, createjs;
+"""
+###
diff --git a/app/lib/storage.coffee b/app/lib/storage.coffee
new file mode 100644
index 000000000..d6c1b45a3
--- /dev/null
+++ b/app/lib/storage.coffee
@@ -0,0 +1,13 @@
+module.exports.loadObjectFromStorage = (key) ->
+ s = localStorage.getItem(key)
+ return null unless s
+ try
+ value = JSON.parse(s)
+ return value
+ catch SyntaxError
+ console.warning('error loading from storage', key)
+ return null
+
+module.exports.saveObjectToStorage = (key, value) ->
+ s = JSON.stringify(value)
+ localStorage.setItem(key, s)
\ No newline at end of file
diff --git a/app/lib/surface/Camera.coffee b/app/lib/surface/Camera.coffee
new file mode 100644
index 000000000..2f81d8a06
--- /dev/null
+++ b/app/lib/surface/Camera.coffee
@@ -0,0 +1,261 @@
+CocoClass = require 'lib/CocoClass'
+
+# If I were the kind of math major who remembered his math, this would all be done with matrix transforms.
+
+r2d = (radians) -> radians * 180 / Math.PI
+d2r = (degrees) -> degrees / 180 * Math.PI
+
+MAX_ZOOM = 8
+MIN_ZOOM = 0.1
+DEFAULT_ZOOM = 2.0
+DEFAULT_TARGET = {x:0, y:0}
+DEFAULT_TIME = 1000
+
+# You can't mutate any of the constructor parameters after construction.
+# You can only call zoomTo to change the zoom target and zoom level.
+module.exports = class Camera extends CocoClass
+ @PPM: 10 # pixels per meter
+ @MPP: 0.1 # meters per pixel; should match @PPM
+
+ bounds: null # list of two surface points defining the viewable rectangle in the world
+ # or null if there are no bounds
+
+ # what the camera is pointed at right now
+ target: DEFAULT_TARGET
+ zoom: DEFAULT_ZOOM
+
+ # properties for tracking going between targets
+ oldZoom: null
+ newZoom: null
+ oldTarget: null
+ newTarget: null
+ tweenProgress: 0.0
+
+ instant: false
+
+ # INIT
+
+ subscriptions:
+ 'camera-zoom-in': 'onZoomIn'
+ 'camera-zoom-out': 'onZoomOut'
+ 'surface:mouse-scrolled': 'onMouseScrolled'
+ 'level:restarted': 'onLevelRestarted'
+
+ # TODO: Fix tests to not use mainLayer
+ constructor: (@canvasWidth, @canvasHeight, angle=Math.asin(0.75), hFOV=d2r(30)) ->
+ super()
+ @calculateViewingAngle angle
+ @calculateFieldOfView hFOV
+ @calculateAxisConversionFactors()
+ @updateViewports()
+ @calculateMinZoom()
+
+ calculateViewingAngle: (angle) ->
+ # Operate on open interval between 0 - 90 degrees to make the math easier
+ epsilon = 0.000001 # Too small and numerical instability will get us.
+ @angle = Math.max(Math.min(Math.PI / 2 - epsilon, angle), epsilon)
+ if @angle isnt angle and angle isnt 0 and angle isnt Math.PI / 2
+ console.log "Restricted given camera angle of #{r2d(angle)} to #{r2d(@angle)}."
+
+ calculateFieldOfView: (hFOV) ->
+ # http://en.wikipedia.org/wiki/Field_of_view_in_video_games
+ epsilon = 0.000001 # Too small and numerical instability will get us.
+ @hFOV = Math.max(Math.min(Math.PI - epsilon, hFOV), epsilon)
+ if @hFOV isnt hFOV and hFOV isnt 0 and hFOV isnt Math.PI
+ console.log "Restricted given horizontal field of view to #{r2d(hFOV)} to #{r2d(@hFOV)}."
+ @vFOV = 2 * Math.atan(Math.tan(@hFOV / 2) * @canvasHeight / @canvasWidth)
+ if @vFOV > Math.PI
+ console.log "Vertical field of view problem: expected canvas not to be taller than it is wide with high field of view."
+ @vFOV = Math.PI - epsilon
+
+ calculateAxisConversionFactors: ->
+ @y2x = Math.sin @angle # 1 unit along y is equivalent to y2x units along x
+ @z2x = Math.cos @angle # 1 unit along z is equivalent to z2x units along x
+ @z2y = @z2x / @y2x # 1 unit along z is equivalent to z2y units along y
+ @x2y = 1 / @y2x # 1 unit along x is equivalent to x2y units along y
+ @x2z = 1 / @z2x # 1 unit along x is equivalent to x2z units along z
+ @y2z = 1 / @z2y # 1 unit along y is equivalent to y2z units along z
+
+ # CONVERSIONS AND CALCULATIONS
+
+ worldToSurface: (pos) ->
+ x = pos.x * Camera.PPM
+ y = -pos.y * @y2x * Camera.PPM
+ if pos.z
+ y -= @z2y * @y2x * pos.z * Camera.PPM
+ {x: x, y: y}
+
+ surfaceToCanvas: (pos) ->
+ {x: (pos.x - @surfaceViewport.x) * @zoom, y: (pos.y - @surfaceViewport.y) * @zoom}
+
+ # TODO: do we even need separate screen coordinates?
+ # We would need some other properties for the actual ratio of screen size to canvas size.
+ canvasToScreen: (pos) ->
+ #{x: pos.x * @someCanvasToScreenXScaleFactor, y: pos.y * @someCanvasToScreenYScaleFactor}
+ {x: pos.x, y: pos.y}
+
+ screenToCanvas: (pos) ->
+ #{x: pos.x / @someCanvasToScreenXScaleFactor, y: pos.y / @someCanvasToScreenYScaleFactor}
+ {x: pos.x, y: pos.y}
+
+ canvasToSurface: (pos) ->
+ {x: pos.x / @zoom + @surfaceViewport.x, y: pos.y / @zoom + @surfaceViewport.y}
+
+ surfaceToWorld: (pos) ->
+ {x: pos.x * Camera.MPP, y: -pos.y * Camera.MPP * @x2y, z: 0}
+
+ canvasToWorld: (pos) -> @surfaceToWorld @canvasToSurface pos
+ worldToCanvas: (pos) -> @surfaceToCanvas @worldToSurface pos
+ worldToScreen: (pos) -> @canvasToScreen @worldToCanvas pos
+ surfaceToScreen: (pos) -> @canvasToScreen @surfaceToCanvas pos
+ screenToSurface: (pos) -> @canvasToSurface @screenToCanvas pos
+ screenToWorld: (pos) -> @surfaceToWorld @screenToSurface pos
+
+ cameraWorldPos: ->
+ # I tried to figure out the math for how much of @vFOV is below the midpoint (botFOV) and how much is above (topFOV), but I failed.
+ # So I'm just making something up. This would give botFOV 20deg, topFOV 10deg at @vFOV 30deg and @angle 45deg, or an even 15/15 at @angle 90deg.
+ botFOV = @x2y * @vFOV / (@y2x + @x2y)
+ topFOV = @y2x * @vFOV / (@y2x + @x2y)
+ botDist = @worldViewport.height / 2 * Math.sin(@angle) / Math.sin(botFOV)
+ z = botDist * Math.sin(@angle + botFOV)
+ x: @worldViewport.cx, y: @worldViewport.cy - z * @z2y, z: z
+
+ distanceTo: (pos) ->
+ # Get the physical distance in meters from the camera to the given world pos.
+ cpos = @cameraWorldPos()
+ dx = pos.x - cpos.x
+ dy = pos.y - cpos.y
+ dz = (pos.z or 0) - cpos.z
+ Math.sqrt dx * dx + dy * dy + dz * dz
+
+ distanceRatioTo: (pos) ->
+ # Get the ratio of the distance to the given world pos over the distance to the center of the camera view.
+ cpos = @cameraWorldPos()
+ dy = @worldViewport.cy - cpos.y
+ camDist = Math.sqrt(dy * dy + cpos.z * cpos.z)
+ return @distanceTo(pos) / camDist
+
+ # Old method for flying things below; could re-integrate this
+ ## Because none of our maps are designed to get smaller with distance along the y-axis, we'll only use z, as if we were looking straight down, until we get high enough. Based on worldPos.z, we gradually shift over to the more-realistic scale. This is pretty hacky.
+ #ratioWithoutY = dz * dz / (cPos.z * cPos.z)
+ #zv = Math.min(Math.max(0, worldPos.z - 5), cPos.z - 5) / (cPos.z - 5)
+ #zv * ratioWithY + (1 - zv) * ratioWithoutY
+
+ # SUBSCRIPTIONS
+
+ onZoomIn: (e) -> @zoomTo @target, @zoom * 1.15, 300
+ onZoomOut: (e) -> @zoomTo @target, @zoom / 1.15, 300
+ onMouseScrolled: (e) ->
+ ratio = 1 + 0.05 * Math.sqrt(Math.abs(e.deltaY))
+ ratio = 1 / ratio if e.deltaY > 0
+ @zoomTo @target, @zoom * ratio, 0
+ onLevelRestarted: ->
+ @setBounds(@firstBounds)
+
+ # COMMANDS
+
+ setBounds: (worldBounds) ->
+ # receives an array of two world points. Normalize and apply them
+ @firstBounds = worldBounds unless @firstBounds
+ @bounds = @normalizeBounds(worldBounds)
+ @calculateMinZoom()
+ @updateZoom true
+ @target = @currentTarget unless @target.name
+
+ normalizeBounds: (worldBounds) ->
+ return null unless worldBounds
+ top = Math.max(worldBounds[0].y, worldBounds[1].y)
+ left = Math.min(worldBounds[0].x, worldBounds[1].x)
+ bottom = Math.min(worldBounds[0].y, worldBounds[1].y)
+ right = Math.max(worldBounds[0].x, worldBounds[1].x)
+ bottom -= 1 if top is bottom
+ right += 1 if left is right
+ p1 = @worldToSurface({x:left, y:top})
+ p2 = @worldToSurface({x:right, y:bottom})
+ {x:p1.x, y:p1.y, width:p2.x-p1.x, height:p2.y-p1.y}
+
+ calculateMinZoom: ->
+ # Zoom targets are always done in Surface coordinates.
+ if not @bounds
+ @minZoom = 0.5
+ return
+ @minZoom = Math.max @canvasWidth / @bounds.width, @canvasHeight / @bounds.height
+ @zoom = Math.max(@minZoom, @zoom) if @zoom
+
+ zoomTo: (newTarget=null, newZoom=1.0, time=1500) ->
+ # Target is either just a {x, y} pos or a display object with {x, y} that might change; surface coordinates.
+ time = 0 if @instant
+ newTarget ?= {x:0, y:0}
+ newZoom = Math.min((Math.max @minZoom, newZoom), MAX_ZOOM)
+ return if @zoom is newZoom and newTarget is newTarget.x and newTarget.y is newTarget.y
+
+ @finishTween(true)
+ if time
+ @newTarget = newTarget
+ @oldTarget = @boundTarget(@target, @zoom)
+ @oldZoom = @zoom
+ @newZoom = newZoom
+ @tweenProgress = 0.01
+ createjs.Tween.get(@)
+ .to({tweenProgress: 1.0}, time, createjs.Ease.getPowInOut(3))
+ .call @onTweenEnd
+
+ else
+ @target = newTarget
+ @zoom = newZoom
+ @updateZoom true
+
+ onTweenEnd: => @finishTween()
+
+ finishTween: (abort=false) =>
+ createjs.Tween.removeTweens(@)
+ return unless @newTarget
+ unless abort
+ @target = @newTarget
+ @zoom = @newZoom
+ @newZoom = @oldZoom = @newTarget = @newTarget = @tweenProgress = null
+ @updateZoom true
+
+ updateZoom: (force=false) ->
+ # Update when we're focusing on a Thang, tweening, or forcing it, unless we're locked
+ return if @locked or (not force and not @newTarget and not @target?.name)
+ if @newTarget
+ t = @tweenProgress
+ @zoom = @oldZoom + t * (@newZoom - @oldZoom)
+ [p1, p2] = [@oldTarget, @boundTarget(@newTarget, @newZoom)]
+ target = @target = x: p1.x + t * (p2.x - p1.x), y: p1.y + t * (p2.y - p1.y)
+ else
+ target = @boundTarget @target, @zoom
+ return if not force and _.isEqual target, @currentTarget
+ @currentTarget = target
+ @updateViewports target
+ Backbone.Mediator.publish 'camera:zoom-updated', camera: @, zoom: @zoom, surfaceViewport: @surfaceViewport
+
+ boundTarget: (pos, zoom) ->
+ # Given an {x, y} in Surface coordinates, return one that will keep our viewport on the Surface.
+ return pos unless @bounds
+ marginX = (@canvasWidth / zoom / 2)
+ marginY = (@canvasHeight / zoom / 2)
+ x = Math.min(Math.max(marginX + @bounds.x, pos.x), @bounds.x + @bounds.width - marginX)
+ y = Math.min(Math.max(marginY + @bounds.y, pos.y), @bounds.y + @bounds.height - marginY)
+ {x: x, y: y}
+
+ updateViewports: (target) ->
+ target ?= @target
+ sv = width: @canvasWidth / @zoom, height: @canvasHeight / @zoom, cx: target.x, cy: target.y
+ sv.x = sv.cx - sv.width / 2
+ sv.y = sv.cy - sv.height / 2
+ @surfaceViewport = sv
+
+ wv = @surfaceToWorld sv # get x and y
+ wv.width = sv.width * Camera.MPP
+ wv.height = sv.height * Camera.MPP * @x2y
+ wv.cx = wv.x + wv.width / 2
+ wv.cy = wv.y + wv.height / 2
+ @worldViewport = wv
+
+ lock: ->
+ @target = @currentTarget
+ @locked = true
+ unlock: ->
+ @locked = false
diff --git a/app/lib/surface/CameraBorder.coffee b/app/lib/surface/CameraBorder.coffee
new file mode 100644
index 000000000..e315a25d8
--- /dev/null
+++ b/app/lib/surface/CameraBorder.coffee
@@ -0,0 +1,30 @@
+module.exports = class CameraBorder extends createjs.Container
+ layerPriority: 100
+
+ subscriptions: {}
+
+ constructor: (options) ->
+ super()
+ @initialize()
+ @mouseEnabled = @mouseChildren = false
+ @updateBounds options.bounds
+ Backbone.Mediator.subscribe(channel, @[func], @) for channel, func of @subscriptions
+
+ destroy: ->
+ Backbone.Mediator.unsubscribe(channel, @[func], @) for channel, func of @subscriptions
+
+ updateBounds: (bounds) ->
+ return if _.isEqual bounds, @bounds
+ @bounds = bounds
+ if @border
+ @removeChild @border
+ @border = null
+ return unless @bounds
+ @addChild @border = new createjs.Shape()
+ width = 20
+ i = width
+ while i
+ opacity = 3 * (1 - (i/width)) / width
+ @border.graphics.setStrokeStyle(i,"round").beginStroke("rgba(0,0,0,#{opacity})").drawRect(bounds.x, bounds.y, bounds.width, bounds.height)
+ i -= 1
+ @border.cache bounds.x, bounds.y, bounds.width, bounds.height
diff --git a/app/lib/surface/CocoSprite.coffee b/app/lib/surface/CocoSprite.coffee
new file mode 100644
index 000000000..53e19d575
--- /dev/null
+++ b/app/lib/surface/CocoSprite.coffee
@@ -0,0 +1,425 @@
+CocoClass = require 'lib/CocoClass'
+SpriteBuilder = require 'lib/sprites/SpriteBuilder'
+{createProgressBar} = require './sprite_utils'
+Camera = require './Camera'
+Mark = require './Mark'
+Label = require './Label'
+AudioPlayer = require 'lib/AudioPlayer'
+
+# We'll get rid of this once level's teams actually have colors
+healthColors =
+ ogres: [64, 128, 212]
+ humans: [255, 0, 0]
+ neutral: [64, 212, 128]
+
+# Sprite: EaselJS-based view/controller for Thang model
+module.exports = CocoSprite = class CocoSprite extends CocoClass
+ thangType: null # ThangType instance
+
+ displayObject: null
+ imageObject: null
+
+ healthBar: null
+ marks: null
+ labels: null
+
+ options:
+ resolutionFactor: 4
+ groundLayer: null
+ textLayer: null
+ floatingLayer: null
+ frameRateFactor: 1 # TODO: use or lose?
+ thang: null
+ camera: null
+ spriteSheetCache: null
+ showInvisible: false
+
+ flipped: false
+ flippedCount: 0
+ originalScaleX: null
+ originalScaleY: null
+ actionQueue: null
+ actions: null
+ rotation: 0
+
+ # ACTION STATE
+ # Actions have relations. If you say 'move', 'move_side' may play because of a direction
+ # relationship, and if you say 'cast', 'cast_begin' may happen first, or 'cast_end' after.
+ currentRootAction: null # action that, in general, is playing or will play
+ currentAction: null # related action that is right now playing
+
+ subscriptions:
+ 'level-sprite-dialogue': 'onDialogue'
+ 'level-sprite-clear-dialogue': 'onClearDialogue'
+ 'level-set-letterbox': 'onSetLetterbox'
+
+ constructor: (@thangType, options) ->
+ super()
+ @options = _.extend(_.cloneDeep(@options), options)
+ @setThang @options.thang
+ console.error @toString(), "has no ThangType!" unless @thangType
+ @actionQueue = []
+ @marks = {}
+ @labels = {}
+ @actions = @thangType.getActions()
+ @buildFromSpriteSheet @buildSpriteSheet()
+
+ destroy: ->
+ super()
+ mark.destroy() for name, mark of @marks
+ label.destroy() for name, label of @labels
+
+ toString: -> ""
+
+ spriteSheetKey: ->
+ "#{@thangType.get('name')} - #{@options.resolutionFactor}"
+
+ buildSpriteSheet: -> @thangType.getSpriteSheet @options
+
+ buildFromSpriteSheet: (spriteSheet) ->
+ if spriteSheet
+ sprite = new createjs.Sprite(spriteSheet)
+ else
+ sprite = new createjs.Shape()
+ sprite.scaleX = sprite.scaleY = 1 / @options.resolutionFactor
+ if @thangType.get('name') in ['Dungeon Floor', 'Grass', 'Goal Trigger', 'Obstacle'] # temp, until these are re-exported with perspective
+ sprite.scaleY *= @options.camera.y2x
+ @displayObject = new createjs.Container()
+ @imageObject = sprite
+ @displayObject.addChild(sprite)
+ @addHealthBar()
+ @configureMouse()
+ # TODO: generalize this later?
+ @originalScaleX = sprite.scaleX
+ @originalScaleY = sprite.scaleY
+ @displayObject.sprite = @
+ @displayObject.layerPriority = @thangType.get 'layerPriority'
+ @displayObject.name = @thang?.spriteName or @thangType.get 'name'
+ @imageObject.on 'animationend', @onActionEnd
+
+ ##################################################
+ # QUEUEING AND PLAYING ACTIONS
+
+ queueAction: (action) ->
+ # The normal way to have an action play
+ action = @actions[action] if _.isString(action)
+ action ?= @actions.idle
+ @actionQueue = []
+ @actionQueue.push @currentRootAction.relatedActions.end if @currentRootAction?.relatedActions?.end
+ @actionQueue.push action.relatedActions.begin if action.relatedActions?.begin
+ @actionQueue.push action
+ @currentRootAction = action
+ @playNextAction()
+
+ onActionEnd: (e) => @playNextAction()
+
+ playNextAction: ->
+ @playAction(@actionQueue.splice(0,1)[0]) if @actionQueue.length
+
+ playAction: (action) ->
+ @currentAction = action
+ return @updateActionDirection() unless action.animation or action.container
+ m = if action.container then "gotoAndStop" else "gotoAndPlay"
+ @imageObject[m] action.name
+ @imageObject.framerate = action.framerate or 20
+ reg = @getOffset 'registration'
+ @imageObject.regX = -reg.x
+ @imageObject.regY = -reg.y
+ if @currentRootAction.name is 'move'
+ start = Math.floor(Math.random() * action.frames.length)
+ @imageObject.currentAnimationFrame = start
+
+ update: ->
+ # Gets the sprite to reflect what the current state of the thangs and surface are
+ @updatePosition()
+ @updateScale()
+ @updateAlpha()
+ @updateRotation()
+ @updateAction()
+ @updateStats()
+ @updateMarks()
+ @updateLabels()
+
+ cache: ->
+ bounds = @imageObject.getBounds()
+ @displayObject.cache 0, 0, bounds.width, bounds.height
+ #console.log "just cached", @thang.id, "which was at", @imageObject.x, @imageObject.y, bounds.width, bounds.height, "with scale", Math.max(@imageObject.scaleX, @imageObject.scaleY)
+
+ updatePosition: ->
+ return unless @thang?.pos and @options.camera?
+ [p0, p1] = [@lastPos, @thang.pos]
+ return if p0 and p0.x is p1.x and p0.y is p1.y and p0.z is p1.z and not @options.camera.tweeningZoomTo
+ wop = x: p1.x, y: p1.y, z: if @thang.isLand then 0 else p1.z - @thang.depth / 2
+ sup = @options.camera.worldToSurface wop
+ [@displayObject.x, @displayObject.y] = [sup.x, sup.y]
+ @lastPos = _.clone(p1)
+ @hasMoved = true
+
+ updateScale: ->
+ if @thangType.get('matchWorldDimensions') and @thang
+ if @thang.width isnt @lastThangWidth or @thang.height isnt @lastThangHeight
+ [@lastThangWidth, @lastThangHeight] = [@thang.width, @thang.height]
+ bounds = @imageObject.getBounds()
+ @imageObject.scaleX = @thang.width * Camera.PPM / bounds.width * @thangType.get('scale') ? 1
+ @imageObject.scaleY = @thang.height * Camera.PPM * @options.camera.y2x / bounds.height * @thangType.get('scale') ? 1
+ return
+ scaleX = if @getActionProp 'flipX' then -1 else 1
+ scaleY = if @getActionProp 'flipY' then -1 else 1
+ scaleFactor = @thang.scaleFactor ? 1
+ @imageObject.scaleX = @originalScaleX * scaleX * scaleFactor
+ @imageObject.scaleY = @originalScaleY * scaleY * scaleFactor
+
+ updateAlpha: ->
+ return unless @thang?.alpha?
+ @imageObject.alpha = @thang.alpha
+ if @options.showInvisible
+ @imageObject.alpha = Math.max 0.5, @imageObject.alpha
+
+ updateRotation: (imageObject) ->
+ rotationType = @thangType.get('rotationType')
+ return if rotationType is 'fixed'
+ rotation = @getRotation()
+ imageObject ?= @imageObject
+ return imageObject.rotation = rotation if not rotationType
+ @updateIsometricRotation(rotation, imageObject)
+
+ getRotation: ->
+ return @rotation if not @thang?.rotation
+ rotation = @thang?.rotation
+ rotation = (360 - (rotation * 180 / Math.PI) % 360) % 360
+ rotation -= 360 if rotation > 180
+ rotation
+
+ updateIsometricRotation: (rotation, imageObject) ->
+ action = @currentRootAction
+ return unless action
+# @flipOccasionally() if action.name is 'idle'
+ imageObject ?= @imageObject
+ imageObject.scaleX *= -1 if imageObject.scaleX < 0 # normalize to point right
+ imageObject.scaleX *= -1 if Math.abs(rotation) >= 135
+# imageObject.scaleX *= -1 if @flipped and action.name is 'idle'
+
+ flipOccasionally: ->
+ @flippedCount += 1
+ return unless _.random(0,1000) <= 15 and @flippedCount > 30
+ @flipped = not @flipped
+ @flippedCount = 0
+
+ ##################################################
+ updateAction: ->
+ action = @determineAction()
+ isDifferent = action isnt @currentRootAction
+ console.error "action is", action, "for", @thang?.id, "from", @currentRootAction, @thang.action, @thang.getActionName?() if not action and @thang?.actionActivated and @thang.id is 'Artillery'
+ @queueAction(action) if isDifferent or (@thang?.actionActivated and action.name isnt 'move')
+ @updateActionDirection()
+
+ determineAction: ->
+ action = null
+ action = @thang.getActionName() if @thang?.acts
+ action ?= @currentRootAction.name if @currentRootAction?
+ action ?= 'idle'
+ action = null unless @actions[action]?
+ return null unless action
+ action = 'break' if @actions.break? and @thang?.erroredOut
+ action = 'die' if @actions.die? and @thang?.health? and @thang.health <= 0
+ @actions[action]
+
+ updateActionDirection: (@wallGrid=null) ->
+ # wallGrid is only needed for wall grid face updates; should refactor if this works
+ return unless action = @getActionDirection()
+ @playAction(action) if action isnt @currentAction
+
+ getActionDirection: (rootAction=null) ->
+ rootAction ?= @currentRootAction
+ return null unless relatedActions = rootAction?.relatedActions ? {}
+ rotation = @getRotation()
+ if relatedActions["111111111111"] # has grid-surrounding-wall-based actions
+ if @wallGrid
+ action = ''
+ tileSize = 4
+ [gx, gy] = [@thang.pos.x, @thang.pos.y]
+ for y in [gy + tileSize, gy, gy - tileSize, gy - tileSize * 2]
+ for x in [gx - tileSize, gx, gx + tileSize]
+ if x >= 0 and y >= 0 and x < @wallGrid.width and y < @wallGrid.height
+ wallThangs = @wallGrid.contents x, y
+ else
+ wallThangs = ['outside of the map yo']
+ if wallThangs.length is 0
+ if y is gy and x is gx
+ action += "1" # the center wall we're placing
+ else
+ action += "0"
+ else if wallThangs.length is 1
+ action += "1"
+ else
+ console.error "Overlapping walls at", x, y, "...", wallThangs
+ action += "1"
+ matchedAction = '111111111111'
+ for relatedAction of relatedActions
+ if action.match(relatedAction.replace(/\?/g, '.'))
+ matchedAction = relatedAction
+ break
+ #console.log "returning", matchedAction, "for", @thang.id, "at", gx, gy
+ return relatedActions[matchedAction]
+ else
+ keys = _.keys relatedActions
+ index = Math.max 0, Math.floor((179 + rotation) / 360 * keys.length)
+ #console.log "Showing", relatedActions[keys[index]]
+ return relatedActions[keys[index]]
+ value = Math.abs(rotation)
+ direction = null
+ direction = 'side' if value <= 45 or value >= 135
+ direction = 'fore' if 135 > rotation > 45
+ direction = 'back' if -135 < rotation < -45
+ relatedActions[direction]
+
+ updateStats: ->
+ if bar = @healthBar
+ return if @thang.health is @lastHealth
+ @lastHealth = @thang.health
+ healthPct = Math.max(@thang.health / @thang.maxHealth, 0)
+ bar.scaleX = healthPct
+ healthOffset = @getOffset 'aboveHead'
+ [bar.x, bar.y] = [healthOffset.x - bar.width / 2, healthOffset.y]
+
+ configureMouse: ->
+ @displayObject.cursor = 'pointer' if @thang?.isSelectable
+ @displayObject.mouseEnabled = @displayObject.mouseChildren = false unless @thang?.isSelectable or @thang?.isLand
+ if @displayObject.mouseEnabled
+ @displayObject.on 'mousedown', @onMouseEvent, @, false, 'sprite:mouse-down'
+ @displayObject.on 'click', @onMouseEvent, @, false, 'sprite:clicked'
+ @displayObject.on 'dblclick', @onMouseEvent, @, false, 'sprite:double-clicked'
+ @displayObject.on 'pressmove', @onMouseEvent, @, false, 'sprite:dragged'
+ @displayObject.on 'pressup', @onMouseEvent, @, false, 'sprite:mouse-up'
+
+ onSetLetterbox: (e) ->
+ @letterboxOn = e.on
+
+ onMouseEvent: (e, ourEventName) ->
+ return if @letterboxOn
+ Backbone.Mediator.publish ourEventName, sprite: @, thang: @thang, originalEvent: e
+
+ addHealthBar: ->
+ @displayObject.removeChild @healthBar if @healthBar?.parent
+ return unless @thang?.health? and "health" in (@thang?.hudProperties ? [])
+ healthColor = healthColors[@thang?.team] ? healthColors["neutral"]
+ healthOffset = @getOffset 'aboveHead'
+ bar = @healthBar = createProgressBar(healthColor, healthOffset.y)
+ bar.x = healthOffset.x - bar.width / 2
+ bar.name = 'health bar'
+ bar.cache 0, -bar.height / 2, bar.width, bar.height
+ @displayObject.addChild bar
+
+ getActionProp: (prop, subProp, def=null) ->
+ # Get a property or sub-property from an action, falling back to ThangType
+ for val in [@currentAction?[prop], @thangType.get(prop)]
+ val = val[subProp] if val? and subProp
+ return val if val?
+ def
+
+ getOffset: (prop) ->
+ # Get the proper offset from either the current action or the ThangType
+ def = x: 0, y: {registration: 0, torso: -50, mouth: -60, aboveHead: -100}[prop]
+ pos = @getActionProp 'positions', prop, def
+ pos = x: pos.x, y: pos.y
+ scale = @getActionProp 'scale', null, 1
+ scale *= @options.resolutionFactor if prop is 'registration'
+ pos.x *= scale
+ pos.y *= scale
+ pos
+
+ updateMarks: ->
+ return unless @options.camera
+ @addMark 'repair', null, @options.markThangTypes.repair if @thang?.errorsOut
+ @marks.repair?.toggle @thang?.errorsOut
+ @addMark('bounds').toggle true if @thang?.drawsBounds
+ @addMark('shadow').toggle true unless @thangType.get('shadow') is 0
+ mark.update() for name, mark of @marks
+
+ setHighlight: (to, delay) ->
+ @addMark 'highlight', @options.floatingLayer, @options.markThangTypes.highlight if to
+ @marks.highlight?.highlightDelay = delay
+ @marks.highlight?.toggle to and not @dimmed
+
+ setDimmed: (@dimmed) ->
+ @marks.highlight?.toggle @marks.highlight.on and not @dimmed
+
+ setThang: (@thang) ->
+ @options.thang = @thang
+
+ setDebug: (debug) ->
+ return unless @thang?.collides and @options.camera?
+ @addMark 'debug', @options.floatingLayer if debug
+ @marks.debug?.toggle debug
+
+ getAverageDimension: ->
+ bounds = @imageObject.getBounds()
+ averageDimension = (bounds.height + bounds.width) / 2
+ Math.min(80, averageDimension)
+
+ addLabel: (name, style) ->
+ @labels[name] ?= new Label sprite: @, camera: @options.camera, layer: @options.textLayer, style: style
+ @labels[name]
+
+ addMark: (name, layer, thangType=null) ->
+ @marks[name] ?= new Mark name: name, sprite: @, camera: @options.camera, layer: layer ? @options.groundLayer, thangType: thangType
+ @marks[name]
+
+ notifySpeechUpdated: (e) ->
+ e = _.clone(e)
+ e.sprite = @
+ e.blurb ?= '...'
+ Backbone.Mediator.publish 'sprite:speech-updated', e
+
+ isTalking: ->
+ Boolean @labels.dialogue?.text or @labels.say?.text
+
+ onDialogue: (e) ->
+ return unless @thang?.id is e.spriteID
+ label = @addLabel 'dialogue', Label.STYLE_DIALOGUE
+ label.setText e.blurb or '...'
+ sound = e.sound ? AudioPlayer.soundForDialogue e.message, @thangType.get 'soundTriggers'
+ @instance?.stop()
+ if @instance = @playSound sound, false
+ @instance.addEventListener "complete", => Backbone.Mediator.publish 'dialogue-sound-completed'
+ @notifySpeechUpdated e
+
+ onClearDialogue: (e) ->
+ @labels.dialogue?.setText null
+ @instance?.stop()
+ @notifySpeechUpdated {}
+
+ setNameLabel: (name) ->
+ label = @addLabel 'name', Label.STYLE_NAME
+ label.setText name
+
+ updateLabels: ->
+ return unless @thang
+ blurb = if @thang.health <= 0 then null else @thang.sayMessage # Dead men tell no tales
+ @addLabel 'say', Label.STYLE_SAY if blurb
+ if @labels.say?.setText blurb
+ @notifySpeechUpdated blurb: blurb
+ label.update() for name, label of @labels
+
+ playSounds: (withDelay=true, volume=1.0) ->
+ for event in @thang.currentEvents ? []
+ @playSound event, withDelay, volume
+ if @thang.actionActivated and (action = @thang.getActionName()) isnt 'say'
+ @playSound action, withDelay, volume
+ if @thang.sayMessage and withDelay # don't play sayMessages while scrubbing, annoying
+ offsetFrames = Math.abs(@thang.sayStartTime - @thang.world.age) / @thang.world.dt
+ if offsetFrames <= 2 # or (not withDelay and offsetFrames < 30)
+ sound = AudioPlayer.soundForDialogue @thang.sayMessage, @thangType.get 'soundTriggers'
+ @playSound sound, false, volume
+
+ playSound: (sound, withDelay=true, volume=1.0) ->
+ if _.isString sound
+ sound = @thangType.get('soundTriggers')?[sound]
+ if _.isArray sound
+ sound = sound[Math.floor Math.random() * sound.length]
+ return null unless sound
+ delay = if withDelay and sound.delay then 1000 * sound.delay / createjs.Ticker.getFPS() else 0
+ name = AudioPlayer.nameForSoundReference sound
+ instance = createjs.Sound.play name, "none", delay, 0, 0, volume
+ #console.log @thang?.id, "played sound", name, "with delay", delay, "volume", volume, "and got sound instance", instance
+ instance
diff --git a/app/lib/surface/CoordinateDisplay.coffee b/app/lib/surface/CoordinateDisplay.coffee
new file mode 100644
index 000000000..9d2029a43
--- /dev/null
+++ b/app/lib/surface/CoordinateDisplay.coffee
@@ -0,0 +1,59 @@
+module.exports = class CoordinateDisplay extends createjs.Container
+ layerPriority: -10
+ subscriptions:
+ 'surface:mouse-moved': 'onMouseMove'
+ 'surface:mouse-out': 'onMouseOut'
+ 'surface:mouse-over': 'onMouseOver'
+ 'camera:zoom-updated': 'onZoomUpdated'
+
+ constructor: (options) ->
+ super()
+ @initialize()
+ @camera = options.camera
+ console.error "CoordinateDisplay needs camera." unless @camera
+ @build()
+ @show = _.debounce @show, 250
+ Backbone.Mediator.subscribe(channel, @[func], @) for channel, func of @subscriptions
+
+ destroy: ->
+ Backbone.Mediator.unsubscribe(channel, @[func], @) for channel, func of @subscriptions
+
+ build: ->
+ @mouseEnabled = @mouseChildren = false
+ @addChild @label = new createjs.Text("", "20px Arial", "#003300")
+ @label.name = 'position text'
+ @label.shadow = new createjs.Shadow("#FFFFFF", 1, 1, 0)
+
+ onMouseOver: (e) -> @mouseInBounds = true
+ onMouseOut: (e) -> @mouseInBounds = false
+
+ onMouseMove: (e) ->
+ wop = @camera.canvasToWorld x: e.x, y: e.y
+ wop.x = Math.round(wop.x)
+ wop.y = Math.round(wop.y)
+ return if wop.x is @lastPos?.x and wop.y is @lastPos?.y
+ @lastPos = wop
+ @hide()
+ @show() # debounced
+
+ onZoomUpdated: (e) ->
+ @hide()
+ @show()
+
+ hide: ->
+ return unless @label.parent
+ @removeChild @label
+ @uncache()
+
+ show: =>
+ return unless @mouseInBounds and @lastPos
+ @label.text = "(#{@lastPos.x}, #{@lastPos.y})"
+ [width, height] = [@label.getMeasuredWidth(), @label.getMeasuredHeight()]
+ @label.regX = width / 2
+ @label.regY = height / 2
+ sup = @camera.worldToSurface @lastPos
+ @x = sup.x
+ @y = sup.y
+ @addChild @label
+ @cache -width / 2, -height / 2, width, height
+ Backbone.Mediator.publish 'surface:coordinates-shown', {}
diff --git a/app/lib/surface/DebugDisplay.coffee b/app/lib/surface/DebugDisplay.coffee
new file mode 100644
index 000000000..a53059bf4
--- /dev/null
+++ b/app/lib/surface/DebugDisplay.coffee
@@ -0,0 +1,45 @@
+module.exports = class DebugDisplay extends createjs.Container
+ layerPriority: 20
+ subscriptions:
+ 'level-set-debug': 'onSetDebug'
+
+ constructor: (options) ->
+ super()
+ @initialize()
+ @canvasWidth = options.canvasWidth
+ @canvasHeight = options.canvasHeight
+ console.error "DebugDisplay needs canvasWidth/Height." unless @canvasWidth and @canvasHeight
+ @build()
+ @onSetDebug debug: true
+ Backbone.Mediator.subscribe(channel, @[func], @) for channel, func of @subscriptions
+
+ destroy: ->
+ Backbone.Mediator.unsubscribe(channel, @[func], @) for channel, func of @subscriptions
+
+ onSetDebug: (e) ->
+ return if e.debug is @on
+ @visible = @on = e.debug
+ @fps = null
+ @framesRenderedThisSecond = 0
+ @lastFrameSecondStart = Date.now()
+
+ build: ->
+ @mouseEnabled = @mouseChildren = false
+ @addChild @frameText = new createjs.Text "...", "20px Arial", "#FFF"
+ @frameText.name = 'frame text'
+ @frameText.x = @canvasWidth - 50
+ @frameText.y = @canvasHeight - 25
+ @frameText.alpha = 0.5
+
+ updateFrame: (currentFrame) ->
+ return unless @on
+ ++@framesRenderedThisSecond
+ time = Date.now()
+ diff = (time - @lastFrameSecondStart) / 1000
+ if diff > 1
+ @fps = Math.round @framesRenderedThisSecond / diff
+ @lastFrameSecondStart = time
+ @framesRenderedThisSecond = 0
+
+ @frameText.text = Math.round(currentFrame) + (if @fps? then " - " + @fps + ' fps' else '')
+ @frameText.x = @canvasWidth - @frameText.getMeasuredWidth() - 10
diff --git a/app/lib/surface/Dimmer.coffee b/app/lib/surface/Dimmer.coffee
new file mode 100644
index 000000000..aaa91a040
--- /dev/null
+++ b/app/lib/surface/Dimmer.coffee
@@ -0,0 +1,78 @@
+CocoClass = require 'lib/CocoClass'
+
+module.exports = class Dimmer extends CocoClass
+ subscriptions:
+ 'level-disable-controls': 'onDisableControls'
+ 'level-enable-controls': 'onEnableControls'
+ 'level-highlight-sprites': 'onHighlightSprites'
+ 'sprite:speech-updated': 'onSpriteSpeechUpdated'
+ 'surface:frame-changed': 'onFrameChanged'
+ 'camera:zoom-updated': 'onZoomUpdated'
+
+ constructor: (options) ->
+ super()
+ options ?= {}
+ @camera = options.camera
+ @layer = options.layer
+ console.error @toString(), "needs a camera." unless @camera
+ console.error @toString(), "needs a layer." unless @layer
+ @build()
+ @updateDimMask = _.throttle @updateDimMask, 10
+ @highlightedThangIDs = []
+ @sprites = {}
+
+ destroy: ->
+ super()
+
+ toString: -> ""
+
+ build: ->
+ @dimLayer = new createjs.Container()
+ @dimLayer.mouseEnabled = @dimLayer.mouseChildren = false
+ @dimLayer.layerIndex = -10
+ @dimLayer.addChild @dimScreen = new createjs.Shape()
+ @dimLayer.addChild @dimMask = new createjs.Shape()
+ @dimScreen.graphics.beginFill("rgba(0,0,0,0.5)").rect 0, 0, @camera.canvasWidth, @camera.canvasHeight
+ @dimMask.compositeOperation = 'destination-out'
+ @dimLayer.cache 0, 0, @camera.canvasWidth, @camera.canvasHeight
+
+ onDisableControls: (e) ->
+ return if @on or (e.controls and not ('surface' in e.controls))
+ @dim()
+
+ onEnableControls: (e) ->
+ return if not @on or (e.controls and not ('surface' in e.controls))
+ @undim()
+
+ onSpriteSpeechUpdated: (e) -> @updateDimMask() if @on
+ onFrameChanged: (e) -> @updateDimMask() if @on
+ onZoomUpdated: (e) -> @updateDimMask() if @on
+ onHighlightSprites: (e) ->
+ @highlightedThangIDs = e.thangIDs ? []
+ @updateDimMask() if @on
+
+ setSprites: (@sprites) ->
+ console.log
+
+ dim: ->
+ @on = true
+ @layer.addChild @dimLayer
+ @layer.updateLayerOrder()
+ sprite.setDimmed true for thangID, sprite of @sprites
+ @updateDimMask()
+
+ undim: ->
+ @on = false
+ @layer.removeChild @dimLayer
+ sprite.setDimmed false for thangID, sprite of @sprites
+
+ updateDimMask: =>
+ @dimMask.graphics.clear()
+ for thangID, sprite of @sprites
+ continue unless (thangID in @highlightedThangIDs) or sprite.isTalking?() or sprite.thang?.id is 'My Wizard'
+ sup = x: sprite.displayObject.x, y: sprite.displayObject.y
+ cap = @camera.surfaceToCanvas sup
+ r = 50 * @camera.zoom # TODO: find better way to get the radius based on the sprite's size
+ @dimMask.graphics.beginRadialGradientFill(["rgba(0,0,0,1)", "rgba(0,0,0,0)"], [0.5, 1], cap.x, cap.y, 0, cap.x, cap.y, r).drawCircle(cap.x, cap.y, r)
+
+ @dimLayer.updateCache 0, 0, @camera.canvasWidth, @camera.canvasHeight
diff --git a/app/lib/surface/Dropper.coffee b/app/lib/surface/Dropper.coffee
new file mode 100644
index 000000000..3dae5b7db
--- /dev/null
+++ b/app/lib/surface/Dropper.coffee
@@ -0,0 +1,29 @@
+Dropper = class Dropper
+ lost_frames: 0.0
+ drop_counter: 0
+
+ constructor: ->
+ @listener = (e) => @tick(e)
+
+ tick: ->
+ unless @tickedOnce
+ @tickedOnce = true # Can't get measured FPS on the 0th frame
+ return
+
+ # decrement drop counter
+ @drop_counter -= 1 if @drop_counter > 0
+
+ # track number of frames we've lost since the last tick
+ fps = createjs.Ticker.getFPS()
+ actual = createjs.Ticker.getMeasuredFPS(1)
+ @lost_frames += (fps - actual) / fps
+
+ # if lost_frames > 1, drop that number for the next tick
+ @drop_counter += parseInt(@lost_frames)
+ @lost_frames = @lost_frames % 1
+
+ drop: ->
+ return @drop_counter > 0
+
+
+module.exports = new Dropper()
diff --git a/app/lib/surface/IndieSprite.coffee b/app/lib/surface/IndieSprite.coffee
new file mode 100644
index 000000000..5681350a7
--- /dev/null
+++ b/app/lib/surface/IndieSprite.coffee
@@ -0,0 +1,89 @@
+{me} = require('lib/auth')
+Thang = require 'lib/world/thang'
+Vector = require 'lib/world/vector'
+CocoSprite = require 'lib/surface/CocoSprite'
+Camera = require './Camera'
+
+module.exports = IndieSprite = class IndieSprite extends CocoSprite
+ notOfThisWorld: true
+ subscriptions:
+ 'level-sprite-move': 'onMove'
+ 'note-group-started': 'onNoteGroupStarted'
+ 'note-group-ended': 'onNoteGroupEnded'
+
+ constructor: (thangType, options) ->
+ options.thang = @makeIndieThang thangType, options.thangID, options.pos
+ super thangType, options
+
+ makeIndieThang: (thangType, thangID, pos) ->
+ @thang = thang = new Thang null, thangType.get('name'), thangID
+ # Build needed results of what used to be Exists, Physical, Acts, and Selectable Components
+ thang.exists = true
+ thang.width = thang.height = thang.depth = 4
+ thang.pos = pos ? @defaultPos()
+ thang.pos.z ?= @defaultPos().z
+ thang.rotation = 0
+ thang.action = 'idle'
+ thang.setAction = (action) -> thang.action = action
+ thang.getActionName = -> thang.action
+ thang.acts = true
+ thang.isSelectable = true
+ thang
+
+ onNoteGroupStarted: => @scriptRunning = true
+ onNoteGroupEnded: => @scriptRunning = false
+ onMouseEvent: (e, ourEventName) -> super e, ourEventName unless @scriptRunning
+ defaultPos: -> x: -20, y: 20, z: @thang.depth / 2
+
+ onMove: (e) ->
+ return unless e.spriteID is @thang.id
+ pos = e.pos
+ if _.isArray pos
+ pos = new Vector pos...
+ else if _.isString pos
+ return console.warn "Couldn't find target sprite", pos, "from", @options.sprites unless pos of @options.sprites
+ target = @options.sprites[pos].thang
+ heading = Vector.subtract(target.pos, @thang.pos).normalize()
+ distance = @thang.pos.distance target.pos
+ offset = Math.max(target.width, target.height, 2) / 2 + 3
+ pos = Vector.add(@thang.pos, heading.multiply(distance - offset))
+ Backbone.Mediator.publish 'level-sprite-clear-dialogue', {}
+ @onClearDialogue()
+ args = [pos]
+ args.push(e.duration) if e.duration?
+ @move(args...)
+
+ move: (pos, duration=2000, endAnimation='idle') =>
+ if not duration
+ createjs.Tween.removeTweens(@thang.pos) if @lastTween
+ @lastTween = null
+ z = @thang.pos.z
+ @thang.pos = pos
+ @thang.pos.z = z
+ @imageObject.gotoAndPlay(endAnimation)
+ return
+
+ @thang.action = 'move'
+ @thang.actionActivated = true
+ @pointToward(pos)
+
+ ease = createjs.Ease.getPowInOut(2.2)
+ if @lastTween
+ ease = createjs.Ease.getPowOut(1.2)
+ createjs.Tween.removeTweens(@thang.pos)
+
+ endFunc = =>
+ @lastTween = null
+ @imageObject.gotoAndPlay(endAnimation)
+ @thang.action = 'idle'
+ window.myself = @
+
+ @lastTween = createjs.Tween
+ .get(@thang.pos)
+ .to({x:pos.x, y:pos.y}, duration, ease)
+ .call(endFunc)
+
+ pointToward: (pos) ->
+ @thang.rotation = Math.atan2(pos.y - @thang.pos.y, pos.x - @thang.pos.x)
+ if (@thang.rotation * 180 / Math.PI) % 90 is 0
+ @thang.rotation += 0.01
diff --git a/app/lib/surface/Label.coffee b/app/lib/surface/Label.coffee
new file mode 100644
index 000000000..d7caa0c47
--- /dev/null
+++ b/app/lib/surface/Label.coffee
@@ -0,0 +1,184 @@
+CocoClass = require 'lib/CocoClass'
+
+module.exports = class Label extends CocoClass
+ @STYLE_DIALOGUE = "dialogue" # A speech bubble from a script
+ @STYLE_SAY = "say" # A piece of text generated from the world
+ @STYLE_NAME = "name" # A name like Scott set up for the Wizard
+ # We might want to combine 'say' and 'name'; they're very similar
+ # Nick designed 'say' based off of Scott's 'name' back when they were using two systems
+
+ subscriptions: {}
+
+ constructor: (options) ->
+ super()
+ options ?= {}
+ @sprite = options.sprite
+ @camera = options.camera
+ @layer = options.layer
+ @style = options.style ? Label.STYLE_SAY
+ console.error @toString(), "needs a sprite." unless @sprite
+ console.error @toString(), "needs a camera." unless @camera
+ console.error @toString(), "needs a layer." unless @layer
+ @setText options.text if options.text
+
+ destroy: ->
+ @setText null
+ super()
+
+ toString: -> " | ')
+ content = message.content
+ content = _.string.escapeHTML(content)
+ content = content.replace(/\n/g, "
")
+ content = content.replace(RegExp(' ', 'g'), " ") # coffeescript can't compile "/ /g"
+ if _.string.startsWith(content, '/me')
+ content = message.authorName + content.slice(3)
+
+ if message.system
+ td.append($('').html(content))
+
+ else if _.string.startsWith(content, '/me')
+ td.append($('').html(content))
+
+ else
+ td.append($('').text(message.authorName+': '))
+ td.append($('').html(content))
+
+ tr = $('
')
+ tr.addClass('me') if message.authorID is me.id
+ tr.append(td)
+
+ addOne: (message) ->
+ return if message.system and message.authorID is me.id
+ if @open
+ openPanel = $('.open-chat-area', @$el)
+ height = openPanel.outerHeight()
+ distanceFromBottom = openPanel[0].scrollHeight - height - openPanel[0].scrollTop
+ doScroll = distanceFromBottom < 10
+ tr = @messageObjectToJQuery(message)
+ tr.data('added', new Date().getTime())
+ @chatTables.append(tr)
+ @scrollDown() if doScroll
+
+ trimClosedPanel: ->
+ closedPanel = $('.closed-chat-area', @$el)
+ limit = 5
+ rows = $('tr', closedPanel)
+ for row, i in rows
+ break if rows.length - i <= limit
+ row.remove()
+
+ onChatKeydown: (e) =>
+ if key.isPressed('enter')
+ message = _.string.strip($(e.target).val())
+ return false unless message
+ @bus.sendMessage(message)
+ $(e.target).val('')
+ return false
+
+ onIconClick: =>
+ openPanel = $('.open-chat-area', @$el)
+ closedPanel = $('.closed-chat-area', @$el)
+ @open = not @open
+ if @open
+ closedPanel.addClass('hide')
+ openPanel.removeClass('hide')
+ @scrollDown()
+ else
+ openPanel.addClass('hide')
+ closedPanel.removeClass('hide')
+ if window.getSelection?
+ sel = window.getSelection()
+ sel.empty?()
+ sel.removeAllRanges?()
+ else
+ document.selection.empty()
+
+ scrollDown: ->
+ openPanel = $('.open-chat-area', @$el)[0]
+ openPanel.scrollTop = openPanel.scrollHeight or 1000000
+
+ destroy: ->
+ console.log('DESTROY CHAT', @levelID)
+ super()
+ key.deleteScope('level')
diff --git a/app/views/play/level/modal/docs_modal.coffee b/app/views/play/level/modal/docs_modal.coffee
new file mode 100644
index 000000000..fc975b84a
--- /dev/null
+++ b/app/views/play/level/modal/docs_modal.coffee
@@ -0,0 +1,50 @@
+View = require 'views/kinds/ModalView'
+template = require 'templates/play/level/modal/docs'
+Article = require 'models/Article'
+
+# let's implement this once we have the docs database schema set up
+
+module.exports = class DocsModal extends View
+ template: template
+
+ shortcuts:
+ 'enter': 'hide'
+
+ constructor: (options) ->
+ @docs = options?.docs
+ general = @docs.generalArticles or []
+ specific = @docs.specificArticles or []
+
+ articles = options.supermodel.getModels(Article)
+ articleMap = {}
+ articleMap[article.get('original')] = article for article in articles
+ general = (articleMap[ref.original] for ref in general)
+ general = (article.attributes for article in general when article)
+
+ @docs = specific.concat(general)
+ marked.setOptions {gfm: true, sanitize: false, smartLists: true, breaks: false}
+ @docs = _.cloneDeep(@docs)
+ doc.html = marked(doc.body) for doc in @docs
+ doc.slug = _.string.slugify(doc.name) for doc in @docs
+ super()
+
+ getRenderData: ->
+ c = super()
+ c.docs = @docs
+ c
+
+ afterRender: ->
+ super()
+ if @docs.length is 1
+ @$el.find('.modal-body').html(@docs[0].html)
+ else
+ # incredible hackiness. Getting bootstrap tabs to work shouldn't be this complex
+ @$el.find('.nav-tabs li:first').addClass('active')
+ @$el.find('.tab-content .tab-pane:first').addClass('active')
+ @$el.find('.nav-tabs a').click(@clickTab)
+
+ clickTab: (e) =>
+ @$el.find('li.active').removeClass('active')
+
+ onHidden: ->
+ Backbone.Mediator.publish 'level:docs-hidden'
\ No newline at end of file
diff --git a/app/views/play/level/modal/infinite_loop_modal.coffee b/app/views/play/level/modal/infinite_loop_modal.coffee
new file mode 100644
index 000000000..f26185103
--- /dev/null
+++ b/app/views/play/level/modal/infinite_loop_modal.coffee
@@ -0,0 +1,10 @@
+View = require 'views/kinds/ModalView'
+template = require 'templates/play/level/modal/infinite_loop'
+
+module.exports = class InfiniteLoopModal extends View
+ id: '#infinite-loop-modal'
+ template: template
+
+ events:
+ 'click #restart-level-infinite-loop-retry-button': -> Backbone.Mediator.publish 'tome:cast-spell'
+ 'click #restart-level-infinite-loop-confirm-button': -> Backbone.Mediator.publish 'restart-level'
diff --git a/app/views/play/level/modal/multiplayer_modal.coffee b/app/views/play/level/modal/multiplayer_modal.coffee
new file mode 100644
index 000000000..8f2300f68
--- /dev/null
+++ b/app/views/play/level/modal/multiplayer_modal.coffee
@@ -0,0 +1,39 @@
+View = require 'views/kinds/ModalView'
+template = require 'templates/play/level/modal/multiplayer'
+
+module.exports = class MultiplayerModal extends View
+ id: 'level-multiplayer-modal'
+ template: template
+
+ events:
+ 'click textarea': 'onClickLink'
+ 'change #multiplayer': 'updateLinkSection'
+
+ constructor: (options) ->
+ super(options)
+ @session = options.session
+ @session.on 'change:multiplayer', @updateLinkSection
+
+ getRenderData: ->
+ c = super()
+ c.joinLink = (document.location.href.replace(/\?.*/, '').replace('#', '') +
+ '?session=' +
+ @session.id)
+ c.multiplayer = @session.get('multiplayer')
+ c
+
+ afterRender: ->
+ super()
+ @updateLinkSection()
+
+ onClickLink: (e) =>
+ e.target.select()
+
+ updateLinkSection: =>
+ multiplayer = @$el.find('#multiplayer').attr('checked')
+ la = @$el.find('#link-area')
+ if multiplayer then la.show() else la.hide()
+
+ onHidden: ->
+ multiplayer = Boolean(@$el.find('#multiplayer').attr('checked'))
+ @session.set('multiplayer', multiplayer)
\ No newline at end of file
diff --git a/app/views/play/level/modal/reload_modal.coffee b/app/views/play/level/modal/reload_modal.coffee
new file mode 100644
index 000000000..f8b454f1c
--- /dev/null
+++ b/app/views/play/level/modal/reload_modal.coffee
@@ -0,0 +1,11 @@
+View = require 'views/kinds/ModalView'
+template = require 'templates/play/level/modal/reload'
+
+# let's implement this once we have the docs database schema set up
+
+module.exports = class ReloadModal extends View
+ id: '#reload-code-modal'
+ template: template
+
+ events:
+ 'click #restart-level-confirm-button': -> Backbone.Mediator.publish 'restart-level'
diff --git a/app/views/play/level/modal/victory_modal.coffee b/app/views/play/level/modal/victory_modal.coffee
new file mode 100644
index 000000000..4e5013c57
--- /dev/null
+++ b/app/views/play/level/modal/victory_modal.coffee
@@ -0,0 +1,126 @@
+View = require 'views/kinds/ModalView'
+template = require 'templates/play/level/modal/victory'
+{me} = require 'lib/auth'
+LevelFeedback = require 'models/LevelFeedback'
+
+# let's implement this once we have the docs database schema set up
+
+module.exports = class VictoryModal extends View
+ id: 'level-victory-modal'
+ template: template
+
+ events:
+ 'click .next-level-button': 'onPlayNextLevel'
+
+ # review events
+ 'mouseover .rating i': (e) -> @showStars(@starNum($(e.target)))
+ 'mouseout .rating i': -> @showStars()
+ 'click .rating i': (e) ->
+ @setStars(@starNum($(e.target)))
+ @$el.find('.review').show()
+ 'keypress .review textarea': -> @saveReviewEventually()
+
+ shortcuts:
+ 'enter': -> 'onPlayNextLevel'
+
+ constructor: (options) ->
+ victory = options.level.get('victory')
+ body = victory?.i18n?[me.lang()]?.body or victory.body or 'Sorry, this level has no victory message yet.'
+ @body = marked(body)
+ @level = options.level
+ @session = options.session
+ @saveReviewEventually = _.debounce(@saveReviewEventually, 2000)
+ @loadExistingFeedback()
+ super options
+
+ loadExistingFeedback: ->
+ url = "/db/level/#{@level.id}/feedback"
+ @feedback = new LevelFeedback()
+ @feedback.url = -> url
+ @feedback.fetch()
+ @feedback.once 'sync', => @onFeedbackLoaded()
+ @feedback.once 'error', => @onFeedbackNotFound()
+
+ onFeedbackLoaded: ->
+ @feedback.url = -> '/db/level.feedback/' + @id
+ @$el.find('.review textarea').val(@feedback.get('review'))
+ @$el.find('.review').show()
+ @showStars()
+
+ onFeedbackNotFound: ->
+ @feedback = new LevelFeedback()
+ @feedback.set('levelID', @level.get('slug') or @level.id)
+ @feedback.set('levelName', @level.get('name') or '')
+ @feedback.set('level', {majorVersion: @level.get('version').major, original:@level.get('original')})
+ @showStars()
+
+ onPlayNextLevel: ->
+ @saveReview() if @$el.find('.review textarea').val()
+ Backbone.Mediator.publish('play-next-level')
+
+ getRenderData: ->
+ c = super()
+ c.body = @body
+ c.me = me
+ c.hasNextLevel = _.isObject(@level.get('nextLevel')) and (@level.get('name') isnt "Mobile Artillery")
+ c.levelName = @level.get('i18n')?[me.lang()]?.name ? @level.get('name')
+ if me.get 'hourOfCode'
+ # Show the Hour of Code "I'm Done" tracking pixel after they played for 30 minutes
+ elapsed = (new Date() - new Date(me.get('dateCreated')))
+ enough = not c.hasNextLevel or elapsed >= 30 * 60 * 1000
+ if enough and not me.get('hourOfCodeComplete')
+ $('body').append($(""))
+ me.set 'hourOfCodeComplete', true
+ me.save()
+ window.tracker?.trackEvent 'Hour of Code Finish', {}
+ # Show the "I'm done" button if they get to the end, unless it's been over two hours
+ tooMuch = elapsed >= 120 * 60 * 1000
+ c.showHourOfCodeDoneButton = not c.hasNextLevel and not tooMuch
+
+ if c.hasNextLevel and me.lang().split('-')[0] is 'en'
+ # A/B test "Unlock Next Level" vs. "Play Next Level"
+ unlock = Boolean(me.get('testGroupNumber') & 2) # 2, 3, 6, 7, 10, 11, ...
+ text = if unlock then "Unlock Next Level" else "Play Next Level"
+ window.tracker?.trackEvent 'Next Level Text', text: text
+ window.tracker?.identify {nextLevelText: text}
+ c.nextLevelText = text
+ c
+
+ afterRender: ->
+ super()
+
+ afterInsert: ->
+ super()
+ Backbone.Mediator.publish 'play-sound', trigger: "victory"
+ gapi?.plusone.go @$el[0]
+ FB?.XFBML.parse @$el[0]
+ twttr?.widgets?.load()
+
+ onHidden: ->
+ Backbone.Mediator.publish 'level:victory-hidden'
+
+ destroy: ->
+ super()
+ @saveReview() if @$el.find('.review textarea').val()
+
+ # rating, review
+
+ starNum: (starEl) -> starEl.prevAll('i').length + 1
+
+ showStars: (num) ->
+ @$el.find('.rating').show()
+ num ?= @feedback?.get('rating') or 0
+ stars = @$el.find('.rating i')
+ stars.removeClass('icon-star').addClass('icon-star-empty')
+ stars.slice(0, num).removeClass('icon-star-empty').addClass('icon-star')
+
+ setStars: (num) ->
+ @feedback.set('rating', num)
+ @feedback.save()
+
+ saveReviewEventually: ->
+ @saveReview()
+
+ saveReview: ->
+ @feedback.set('review', @$el.find('.review textarea').val())
+ @feedback.save()
diff --git a/app/views/play/level/playback_view.coffee b/app/views/play/level/playback_view.coffee
new file mode 100644
index 000000000..3d1f3ebb7
--- /dev/null
+++ b/app/views/play/level/playback_view.coffee
@@ -0,0 +1,242 @@
+View = require 'views/kinds/CocoView'
+template = require 'templates/play/level/playback'
+{me} = require 'lib/auth'
+
+module.exports = class PlaybackView extends View
+ id: "playback-view"
+ template: template
+
+ subscriptions:
+ 'level-disable-controls': 'onDisableControls'
+ 'level-enable-controls': 'onEnableControls'
+ 'level-set-playing': 'onSetPlaying'
+ 'level-toggle-playing': 'onTogglePlay'
+ 'level-scrub-forward': 'onScrubForward'
+ 'level-scrub-back': 'onScrubBack'
+ 'level-set-volume': 'onSetVolume'
+ 'level-set-debug': 'onSetDebug'
+ 'level-set-grid': 'onSetGrid'
+ 'level-toggle-grid': 'onToggleGrid'
+ 'surface:frame-changed': 'onFrameChanged'
+ 'god:new-world-created': 'onNewWorld'
+ 'level-set-letterbox': 'onSetLetterbox'
+
+ events:
+ 'click #debug-toggle': 'onToggleDebug'
+ 'click #grid-toggle': 'onToggleGrid'
+ 'click #edit-wizard-settings': 'onEditWizardSettings'
+ 'click #music-button': ->
+ me.set('music', not me.get('music'))
+ me.save()
+ 'click #zoom-in-button': -> Backbone.Mediator.publish('camera-zoom-in') unless @disabled
+ 'click #zoom-out-button': -> Backbone.Mediator.publish('camera-zoom-out') unless @disabled
+ 'click #volume-button': 'onToggleVolume'
+ 'click #play-button': 'onTogglePlay'
+ 'click': -> Backbone.Mediator.publish 'focus-editor'
+
+ shortcuts:
+ '⌘+p, p, ctrl+p': 'onTogglePlay'
+ '[': 'onScrubBack'
+ ']': 'onScrubForward'
+
+ constructor: ->
+ super(arguments...)
+ me.on('change:music', @updateMusicButton, @)
+
+ afterRender: =>
+ super()
+ @hookUpScrubber()
+ @updateMusicButton()
+ $(window).on('resize', @onWindowResize)
+
+ # callbacks
+
+ updateMusicButton: ->
+ @$el.find('#music-button').toggleClass('music-on', me.get('music'))
+
+ onSetLetterbox: (e) ->
+ button = @$el.find '#play-button, .scrubber-handle'
+ if e.on then button.css('visibility', 'hidden') else button.css('visibility', 'visible')
+ @disabled = e.on
+
+ onWindowResize: (s...) =>
+ @barWidth = $('.progress', @$el).width()
+
+ onNewWorld: (e) =>
+ pct = parseInt(100 * e.world.totalFrames / e.world.maxTotalFrames) + '%'
+ @barWidth = $('.progress', @$el).css('width', pct).removeClass('hide').width()
+
+ onToggleDebug: ->
+ return if @shouldIgnore()
+ flag = $('#debug-toggle i.icon-ok')
+ Backbone.Mediator.publish('level-set-debug', {debug: flag.hasClass('hide')})
+
+ onToggleGrid: ->
+ return if @shouldIgnore()
+ flag = $('#grid-toggle i.icon-ok')
+ Backbone.Mediator.publish('level-set-grid', {grid: flag.hasClass('hide')})
+
+ onEditWizardSettings: ->
+ Backbone.Mediator.publish 'edit-wizard-settings'
+
+ onDisableControls: (e) =>
+ if not e.controls or 'playback' in e.controls
+ @disabled = true
+ $('button', @$el).addClass('disabled')
+ try
+ $('.scrubber .progress', @$el).slider('disable', true)
+ catch e
+ #console.warn('error disabling scrubber')
+ if not e.controls or 'playback-hover' in e.controls
+ @hoverDisabled = true
+ $('#volume-button', @$el).removeClass('disabled')
+
+ onEnableControls: (e) =>
+ if not e.controls or 'playback' in e.controls
+ @disabled = false
+ $('button', @$el).removeClass('disabled')
+ try
+ $('.scrubber .progress', @$el).slider('enable', true)
+ catch e
+ #console.warn('error enabling scrubber')
+ if not e.controls or 'playback-hover' in e.controls
+ @hoverDisabled = false
+
+ onSetPlaying: (e) =>
+ @playing = (e ? {}).playing ? true
+ button = @$el.find '#play-button'
+ ended = button.hasClass 'ended'
+ button.toggleClass('playing', @playing and not ended).toggleClass('paused', not @playing and not ended)
+ return # don't stripe the bar
+ bar = @$el.find '.scrubber .progress'
+ bar.toggleClass('progress-striped', @playing and not ended).toggleClass('active', @playing and not ended)
+
+ onSetVolume: (e) ->
+ classes = ['vol-off', 'vol-down', 'vol-up']
+ button = $('#volume-button', @$el)
+ button.removeClass(c) for c in classes
+ button.addClass(classes[0]) if e.volume <= 0.0
+ button.addClass(classes[1]) if e.volume > 0.0 and e.volume < 1.0
+ button.addClass(classes[2]) if e.volume >= 1.0
+
+ onScrubForward: (e) ->
+ e?.preventDefault()
+ Backbone.Mediator.publish('level-set-time', ratioOffset: 0.05, scrubDuration: 500)
+
+ onScrubBack: (e) ->
+ e?.preventDefault()
+ Backbone.Mediator.publish('level-set-time', ratioOffset: -0.05, scrubDuration: 500)
+
+ onFrameChanged: (e) ->
+ if e.progress isnt @lastProgress
+ @updateProgress(e.progress)
+ @updatePlayButton(e.progress)
+ @lastProgress = e.progress
+
+ updateProgress: (progress) ->
+ $('.scrubber .bar', @$el).css('width', "#{progress*100}%")
+
+ updatePlayButton: (progress) ->
+ if progress >= 1.0 and @lastProgress < 1.0
+ $('#play-button').removeClass('playing').removeClass('paused').addClass('ended')
+ if progress < 1.0 and @lastProgress >= 1.0
+ b = $('#play-button').removeClass('ended')
+ if @playing then b.addClass('playing') else b.addClass('paused')
+
+ onSetDebug: (e) ->
+ flag = $('#debug-toggle i.icon-ok')
+ flag.removeClass('hide')
+ flag.addClass('hide') unless e.debug
+
+ onSetGrid: (e) ->
+ flag = $('#grid-toggle i.icon-ok')
+ flag.removeClass('hide')
+ flag.addClass('hide') unless e.grid
+
+ # to refactor
+
+ hookUpScrubber: ->
+ @sliderIncrements = 500 # max slider width before we skip pixels
+ @sliderHoverDelay = 500 # wait this long before scrubbing on slider hover
+ @clickingSlider = false # whether the mouse has been pressed down without moving
+ @hoverTimeout = null
+ $('.scrubber .progress', @$el).slider(
+ max: @sliderIncrements
+ animate: "slow"
+ slide: (event, ui) => @scrubTo ui.value / @sliderIncrements
+ start: (event, ui) => @clickingSlider = true
+ stop: (event, ui) =>
+ @actualProgress = ui.value / @sliderIncrements
+ Backbone.Mediator.publish 'playback:manually-scrubbed', ratio: @actualProgress
+ if @clickingSlider
+ @clickingSlider = false
+ @wasPlaying = false
+ @onSetPlaying {playing: false}
+ @$el.find('.scrubber-handle').effect('bounce', {times: 2})
+ # Wait a while before we start scrubbing on mousemove again
+ @hoverTimeout = _.delay @onProgressMouseOver, 5 * @sliderHoverDelay, null
+ )
+ $('.scrubber').mouseover((e) =>
+ return if @clickingSlider or @disabled or @hoverDisabled or @hoverTimeout
+ @hoverTimeout = _.delay @onProgressMouseOver, @sliderHoverDelay, e
+ ).mouseleave(@onProgressMouseLeave).mousemove(@onProgressMouseMove)
+
+ onProgressMouseOver: (e) =>
+ @hoverTimeout = null
+ return if @clickingSlider or @disabled or @hoverDisabled
+ @wasPlaying = @playing
+ Backbone.Mediator.publish 'level-set-playing', playing: false
+ @onProgressMouseMove e if e
+
+ onProgressMouseLeave: (e) =>
+ return if @clickingSlider or @disabled or @hoverDisabled
+ if @hoverTimeout
+ clearTimeout @hoverTimeout
+ @hoverTimeout = null
+ if @wasPlaying? and @playing isnt @wasPlaying
+ Backbone.Mediator.publish 'level-set-playing', playing: @wasPlaying
+ @wasPlaying = null
+
+ onProgressMouseMove: (e) =>
+ return if @disabled or @hoverDisabled or @hoverTimeout
+ @clickingSlider = false
+ posX = e.pageX - $(e.target).offset().left
+ @actualProgress = posX / @barWidth
+ @scrubTo @actualProgress
+
+ getScrubRatio: ->
+ bar = $('.scrubber .progress', @$el)
+ $('.bar', bar).width() / bar.width()
+
+ scrubTo: (ratio, duration=0) ->
+ return if @disabled
+ Backbone.Mediator.publish 'level-set-time', ratio: ratio, scrubDuration: duration
+
+ shouldIgnore: ->
+ return true if @disabled
+ return false
+
+ onTogglePlay: (e) ->
+ e?.preventDefault()
+ return if @shouldIgnore()
+ button = $('#play-button')
+ willPlay = button.hasClass('paused') or button.hasClass('ended')
+ Backbone.Mediator.publish 'level-set-playing', playing: willPlay
+ $(document.activeElement).blur()
+
+ onToggleVolume: (e) ->
+ button = $(e.target).closest('#volume-button')
+ classes = ['vol-off', 'vol-down', 'vol-up']
+ volumes = [0, 0.4, 1.0]
+ for oldClass, i in classes
+ if button.hasClass oldClass
+ newI = (i + 1) % classes.length
+ break
+ else if i is classes.length - 1 # no oldClass
+ newI = 2
+ Backbone.Mediator.publish 'level-set-volume', volume: volumes[newI]
+ $(document.activeElement).blur()
+
+ destroy: ->
+ super()
+ $(window).off('resize', @onWindowResize)
diff --git a/app/views/play/level/thang_avatar_view.coffee b/app/views/play/level/thang_avatar_view.coffee
new file mode 100644
index 000000000..dd6b13a37
--- /dev/null
+++ b/app/views/play/level/thang_avatar_view.coffee
@@ -0,0 +1,49 @@
+View = require 'views/kinds/CocoView'
+template = require 'templates/play/level/thang_avatar'
+{createAvatarURL} = require 'lib/surface/sprite_utils'
+
+module.exports = class ThangAvatarView extends View
+ className: 'thang-avatar-view'
+ template: template
+
+ subscriptions:
+ 'tome:problems-updated': "onProblemsUpdated"
+
+ constructor: (options) ->
+ super options
+ @thang = options.thang
+ @includeName = options.includeName
+
+ getRenderData: (context={}) =>
+ context = super context
+ context.thang = @thang
+ context.avatarURL = createAvatarURL @thang.spriteName
+ context.includeName = @includeName
+ context
+
+ setProblems: (problemCount, level) ->
+ badge = @$el.find('.badge.problems').text(if problemCount then 'x' else '')
+ badge.removeClass('error warning info')
+ badge.addClass level if level
+
+ setSharedThangs: (sharedThangCount) ->
+ badge = @$el.find('.badge.shared-thangs').text(if sharedThangCount > 1 then sharedThangCount else '')
+ # TODO: change the alert color based on whether any of those things that aren't us have problems
+ #badge.removeClass('error warning info')
+ #badge.addClass level if level
+
+ setSelected: (selected) ->
+ @$el.toggleClass 'selected', Boolean(selected)
+
+ onProblemsUpdated: (e) ->
+ return unless @thang.id of e.spell.thangs
+ myProblems = []
+ for thangID, spellThang of e.spell.thangs when thangID is @thang.id
+ #aether = if e.isCast and spellThang.castAether then spellThang.castAether else spellThang.aether
+ aether = spellThang.castAether # try only paying attention to the actually cast ones
+ myProblems = myProblems.concat aether.getAllProblems() if aether
+ worstLevel = null
+ for level in ['error', 'warning', 'info'] when _.some myProblems, {level: level}
+ worstLevel = level
+ break
+ @setProblems myProblems.length, worstLevel
diff --git a/app/views/play/level/tome/cast_button_view.coffee b/app/views/play/level/tome/cast_button_view.coffee
new file mode 100644
index 000000000..f400375af
--- /dev/null
+++ b/app/views/play/level/tome/cast_button_view.coffee
@@ -0,0 +1,88 @@
+View = require 'views/kinds/CocoView'
+template = require 'templates/play/level/tome/cast_button'
+{me} = require 'lib/auth'
+
+module.exports = class CastButtonView extends View
+ id: 'cast-button-view'
+ template: template
+
+ subscriptions:
+ 'tome:spell-changed': "onSpellChanged"
+ 'tome:cast-spells': 'onCastSpells'
+ 'god:world-load-progress-changed': 'onWorldLoadProgressChanged'
+ 'god:new-world-created': 'onNewWorld'
+
+ constructor: (options) ->
+ super options
+ @spells = options.spells
+ isMac = navigator.platform.toUpperCase().indexOf('MAC') isnt -1
+ @castShortcut = "⇧↩"
+ @castShortcutVerbose = "Shift+Enter"
+
+ getRenderData: (context={}) =>
+ context = super context
+ context.castShortcutVerbose = @castShortcutVerbose
+ context
+
+
+ afterRender: ->
+ super()
+ # TODO: use a User setting instead of localStorage
+ @hookUpButtons()
+ delay = localStorage.getItem 'autocastDelay'
+ delay ?= 5000
+ @setAutocastDelay delay
+
+ hookUpButtons: ->
+ # hook up cast button callbacks
+ @castButton = $('.cast-button', @$el)
+ @castButtonGroup = $('.cast-button-group', @$el)
+ @castOptions = $('.autocast-delays', @$el)
+
+ @castButton.click (e) =>
+ Backbone.Mediator.publish 'tome:manual-cast', {}
+ @castOptions.find('a').click (e) =>
+ Backbone.Mediator.publish 'focus-editor'
+ @castButtonGroup.removeClass 'open'
+ @setAutocastDelay $(e.target).attr 'data-delay'
+ false
+
+ onSpellChanged: (e) ->
+ @updateCastButton()
+
+ onCastSpells: (e) ->
+ @casting = true
+ @updateCastButton()
+
+ onWorldLoadProgressChanged: (e) ->
+ overlay = @castButtonGroup.find '.button-progress-overlay'
+ overlay.css 'width', e.progress * @castButtonGroup.width()
+
+ onNewWorld: (e) ->
+ @casting = false
+ @updateCastButton()
+
+ updateCastButton: ->
+ return if _.some @spells, (spell) => not spell.loaded
+ castable = _.some @spells, (spell) => spell.hasChangedSignificantly spell.getSource()
+ @castButtonGroup.toggleClass('castable', castable).toggleClass('casting', @casting)
+ if @casting
+ s = $.i18n.t("play_level.tome_cast_button_casting", defaultValue: "Casting")
+ else if castable
+ s = $.i18n.t("play_level.tome_cast_button_castable", defaultValue: "Cast") + " " + @castShortcut
+ else
+ s = $.i18n.t("play_level.tome_cast_button_cast", defaultValue: "Spell Cast")
+ @castButton.text s
+ @castButton.attr 'disabled', not castable
+
+ setAutocastDelay: (delay) ->
+ #console.log "Set autocast delay to", delay
+ return unless delay
+ @autocastDelay = delay = parseInt delay
+ localStorage.setItem 'autocastDelay', delay
+ spell.view.setAutocastDelay delay for spellKey, spell of @spells
+ @castOptions.find('a').each ->
+ $(@).toggleClass('selected', parseInt($(@).attr('data-delay')) is delay)
+
+ destroy: ->
+ super()
diff --git a/app/views/play/level/tome/problem.coffee b/app/views/play/level/tome/problem.coffee
new file mode 100644
index 000000000..aeb011ad4
--- /dev/null
+++ b/app/views/play/level/tome/problem.coffee
@@ -0,0 +1,47 @@
+ProblemAlertView = require './problem_alert_view'
+Range = ace.require("ace/range").Range
+
+module.exports = class Problem
+ annotation: null
+ alertView: null
+ markerRange: null
+ constructor: (@aether, @aetherProblem, @ace, withAlert=false, withRange=false) ->
+ @buildAnnotation()
+ @buildAlertView() if withAlert
+ @buildMarkerRange() if withRange
+
+ destroy: ->
+ @alertView?.$el.remove()
+ @alertView?.destroy()
+ @removeMarkerRange()
+
+ buildAnnotation: ->
+ return unless @aetherProblem.ranges
+ text = @aetherProblem.message.replace /^Line \d+: /, ''
+ start = @aetherProblem.ranges[0][0]
+ @annotation =
+ row: start[0],
+ column: start[1],
+ raw: text,
+ text: text,
+ type: @aetherProblem.level ? "error"
+
+ buildAlertView: ->
+ @alertView = new ProblemAlertView problem: @
+ @alertView.render()
+ $(@ace.container).append @alertView.el
+
+ buildMarkerRange: ->
+ return unless @aetherProblem.ranges
+ [start, end] = @aetherProblem.ranges[0]
+ clazz = "problem-marker-#{@aetherProblem.level}"
+ @markerRange = new Range(start[0], start[1], end[0], end[1])
+ @markerRange.start = @ace.getSession().getDocument().createAnchor @markerRange.start
+ @markerRange.end = @ace.getSession().getDocument().createAnchor @markerRange.end
+ @markerRange.id = @ace.getSession().addMarker @markerRange, clazz, "text"
+
+ removeMarkerRange: ->
+ return unless @markerRange
+ @ace.getSession().removeMarker @markerRange.id
+ @markerRange.start.detach()
+ @markerRange.end.detach()
diff --git a/app/views/play/level/tome/problem_alert_view.coffee b/app/views/play/level/tome/problem_alert_view.coffee
new file mode 100644
index 000000000..7559fb6e8
--- /dev/null
+++ b/app/views/play/level/tome/problem_alert_view.coffee
@@ -0,0 +1,30 @@
+View = require 'views/kinds/CocoView'
+template = require 'templates/play/level/tome/problem_alert'
+{me} = require 'lib/auth'
+
+module.exports = class ProblemAlertView extends View
+ className: 'problem-alert'
+ template: template
+
+ subscriptions: {}
+
+ events:
+ "click .close": "onRemoveClicked"
+
+ constructor: (options) ->
+ super options
+ @problem = options.problem
+
+ getRenderData: (context={}) =>
+ context = super context
+ context.message = @problem.aetherProblem.message.replace("\n", "
")
+ context
+
+ afterRender: ->
+ super()
+ @$el.addClass('alert').addClass("alert-#{@problem.aetherProblem.level}")
+
+ onRemoveClicked: ->
+ @$el.remove()
+ @destroy()
+ #@problem.destroy() # let's try leaving the annotations / marker ranges alone
diff --git a/app/views/play/level/tome/spell.coffee b/app/views/play/level/tome/spell.coffee
new file mode 100644
index 000000000..93f1c36bc
--- /dev/null
+++ b/app/views/play/level/tome/spell.coffee
@@ -0,0 +1,74 @@
+SpellView = require './spell_view'
+SpellListTabEntryView = require './spell_list_tab_entry_view'
+{me} = require 'lib/auth'
+
+module.exports = class Spell
+ loaded: false
+ view: null
+ entryView: null
+
+ constructor: (programmableMethod, @spellKey, @pathComponents, @session) ->
+ p = programmableMethod
+ @name = p.name
+ @source = @session.getSourceFor(@spellKey) ? p.source
+ @originalSource = p.source
+ @parameters = p.parameters
+ @permissions = read: p.permissions?.read ? [], readwrite: p.permissions?.readwrite ? [] # teams
+ @thangs = {}
+ @view = new SpellView {spell: @, session: @session}
+ @view.render() # Get it ready and code loaded in advance
+ @tabView = new SpellListTabEntryView spell: @
+ @tabView.render()
+
+ addThang: (thang) ->
+ @thangs[thang.id] = {thang: thang, aether: @createAether(thang), castAether: null}
+
+ canRead: (team) ->
+ (team ? me.team) in @permissions.read or (team ? me.team) in @permissions.readwrite
+
+ canWrite: (team) ->
+ (team ? me.team) in @permissions.readwrite
+
+ getSource: ->
+ @view.getSource()
+
+ transpile: (source) ->
+ if source
+ @source = source
+ else
+ source = @getSource()
+ spellThang.aether.transpile source for thangID, spellThang of @thangs
+ #for thangID, spellThang of @thangs
+ # console.log "aether transpiled", source, "to", spellThang.aether.pure
+ # break
+
+ hasChanged: (newSource=null, currentSource=null) ->
+ (newSource ? @originalSource) isnt (currentSource ? @source)
+
+ hasChangedSignificantly: (newSource=null, currentSource=null) ->
+ for thangID, spellThang of @thangs
+ aether = spellThang.aether
+ break
+ unless aether
+ console.error @toString(), "couldn't find a spellThang with aether of", @thangs
+ return false
+ aether.hasChangedSignificantly (newSource ? @originalSource), (currentSource ? @source), true, true
+
+ createAether: (thang) ->
+ aetherOptions =
+ thisValue: thang.createUserContext()
+ problems:
+ jshint_W040: {level: "ignore"}
+ aether_MissingThis: {level: (if thang.requiresThis then 'error' else 'warning')}
+ functionName: @name
+ functionParameters: @parameters
+ yieldConditionally: thang.plan?
+ requiresThis: thang.requiresThis
+ if @name is 'chooseAction' or not (me.team in @permissions.readwrite) or thang.id is 'Thoktar' # Gridmancer can't handle it
+ #console.log "Turning off includeFlow for", @spellKey
+ aetherOptions.includeFlow = false
+ aether = new Aether aetherOptions
+ aether
+
+ toString: ->
+ ""
diff --git a/app/views/play/level/tome/spell_list_entry_thangs_view.coffee b/app/views/play/level/tome/spell_list_entry_thangs_view.coffee
new file mode 100644
index 000000000..6403b05e5
--- /dev/null
+++ b/app/views/play/level/tome/spell_list_entry_thangs_view.coffee
@@ -0,0 +1,32 @@
+View = require 'views/kinds/CocoView'
+ThangAvatarView = require 'views/play/level/thang_avatar_view'
+template = require 'templates/play/level/tome/spell_list_entry_thangs'
+
+module.exports = class SpellListEntryThangsView extends View
+ className: 'spell-list-entry-thangs-view'
+ template: template
+
+ constructor: (options) ->
+ super options
+ @thangs = options.thangs
+ @thang = options.thang
+ @spell = options.spell
+
+ getRenderData: (context={}) =>
+ context = super context
+ context.thangs = @thangs
+ context.spell = @spell
+ context
+
+ afterRender: ->
+ super()
+ @avatars = []
+ spellName = @spell.name
+ for thang in @thangs
+ avatar = new ThangAvatarView thang: thang, includeName: true
+ @$el.append avatar.el
+ avatar.render()
+ avatar.setSelected thang is @thang
+ avatar.$el.data('thang-id', thang.id).click (e) ->
+ Backbone.Mediator.publish "level-select-sprite", thangID: $(@).data('thang-id'), spellName: spellName
+ avatar.onProblemsUpdated spell: @spell
diff --git a/app/views/play/level/tome/spell_list_entry_view.coffee b/app/views/play/level/tome/spell_list_entry_view.coffee
new file mode 100644
index 000000000..5399195e8
--- /dev/null
+++ b/app/views/play/level/tome/spell_list_entry_view.coffee
@@ -0,0 +1,98 @@
+# TODO: This still needs a way to send problem states to its Thang
+
+View = require 'views/kinds/CocoView'
+ThangAvatarView = require 'views/play/level/thang_avatar_view'
+SpellListEntryThangsView = require 'views/play/level/tome/spell_list_entry_thangs_view'
+template = require 'templates/play/level/tome/spell_list_entry'
+
+module.exports = class SpellListEntryView extends View
+ tagName: 'div' #'li'
+ className: 'spell-list-entry-view'
+ template: template
+ controlsEnabled: true
+
+ subscriptions:
+ 'tome:problems-updated': "onProblemsUpdated"
+ 'level-disable-controls': 'onDisableControls'
+ 'level-enable-controls': 'onEnableControls'
+
+ events:
+ 'click': 'onClick'
+ 'mouseenter .thang-avatar-view': 'onMouseEnterAvatar'
+ 'mouseleave .thang-avatar-view': 'onMouseLeaveAvatar'
+
+ constructor: (options) ->
+ super options
+ @spell = options.spell
+ @showTopDivider = options.showTopDivider
+
+ getRenderData: (context={}) =>
+ context = super context
+ context.spell = @spell
+ context.parameters = (@spell.parameters or []).join ', '
+ context.thangNames = (thangID for thangID of @spell.thangs).join(', ') # + ", Marcus, Robert, Phoebe, Will Smith, Zap Brannigan, You, Gandaaaaalf"
+ context.showTopDivider = @showTopDivider
+ context
+
+ getPrimarySpellThang: ->
+ if @lastSelectedThang
+ spellThang = _.find @spell.thangs, (spellThang) => spellThang.thang.id is @lastSelectedThang.id
+ return spellThang if spellThang
+ for thangID, spellThang of @spell.thangs
+ return spellThang # Just do the first one else
+
+ afterRender: ->
+ super()
+ return unless @options.showTopDivider # Don't repeat Thang avatars when not changed from previous entry
+ return unless spellThang = @getPrimarySpellThang()
+ @avatar = new ThangAvatarView thang: spellThang.thang, includeName: false
+ @$el.prepend @avatar.el # Before rendering, so render can use parent for popover
+ @avatar.render()
+ @avatar.setSharedThangs _.size @spell.thangs
+ @$el.addClass 'shows-top-divider' if @options.showTopDivider
+
+ setSelected: (selected, @lastSelectedThang) ->
+ @avatar?.setSelected selected
+
+ onClick: (e) ->
+ spellThang = @getPrimarySpellThang()
+ Backbone.Mediator.publish "level-select-sprite", thangID: spellThang.thang.id, spellName: @spell.name
+
+ onMouseEnterAvatar: (e) ->
+ return unless @controlsEnabled and _.size(@spell.thangs) > 1
+ @showThangs()
+
+ onMouseLeaveAvatar: (e) ->
+ return unless @controlsEnabled and _.size(@spell.thangs) > 1
+ @hideThangsTimeout = _.delay @hideThangs, 100
+
+ showThangs: =>
+ clearTimeout @hideThangsTimeout if @hideThangsTimeout
+ return if @thangsView
+ @thangsView = new SpellListEntryThangsView thangs: (spellThang.thang for thangID, spellThang of @spell.thangs), thang: @getPrimarySpellThang().thang, spell: @spell
+ @thangsView.render()
+ @$el.append @thangsView.el
+ @thangsView.$el.mouseenter (e) => @onMouseEnterAvatar()
+ @thangsView.$el.mouseleave (e) => @onMouseLeaveAvatar()
+
+ hideThangs: =>
+ return unless @thangsView
+ @thangsView.off 'mouseenter mouseleave'
+ @thangsView.$el.remove()
+ @thangsView.destroy()
+ @thangsView = null
+
+ onProblemsUpdated: (e) ->
+ return unless e.spell is @spell
+ @$el.toggleClass "user-code-problem", e.problems.length
+
+ onDisableControls: (e) -> @toggleControls e, false
+ onEnableControls: (e) -> @toggleControls e, true
+ toggleControls: (e, enabled) ->
+ return if e.controls and not ('editor' in e.controls)
+ return if enabled is @controlsEnabled
+ @controlsEnabled = enabled
+ disabled = not enabled
+ # Should refactor the disabling list so we can target the spell list separately?
+ # Should not call it 'editor' any more?
+ @$el.toggleClass('disabled', disabled).find('*').attr('disabled', disabled)
diff --git a/app/views/play/level/tome/spell_list_tab_entry_view.coffee b/app/views/play/level/tome/spell_list_tab_entry_view.coffee
new file mode 100644
index 000000000..2d4285ea7
--- /dev/null
+++ b/app/views/play/level/tome/spell_list_tab_entry_view.coffee
@@ -0,0 +1,86 @@
+SpellListEntryView = require './spell_list_entry_view'
+ThangAvatarView = require 'views/play/level/thang_avatar_view'
+template = require 'templates/play/level/tome/spell_list_tab_entry'
+{createAvatarURL} = require 'lib/surface/sprite_utils'
+Docs = require 'lib/world/docs'
+
+module.exports = class SpellListTabEntryView extends SpellListEntryView
+ template: template
+ id: 'spell-list-tab-entry-view'
+
+ subscriptions:
+ 'tome:spell-loaded': "onSpellLoaded"
+ 'tome:spell-changed': "onSpellChanged"
+
+ events:
+ 'click .spell-list-button': 'onDropdownClick'
+ 'click .reload-code': 'onCodeReload'
+
+ constructor: (options) ->
+ super options
+
+ getRenderData: (context={}) =>
+ context = super context
+ context
+
+ afterRender: ->
+ super()
+ @$el.addClass 'spell-tab'
+
+ setThang: (thang) ->
+ return if thang.id is @thang?.id
+ @thang = thang
+ @spellThang = @spell.thangs[@thang.id]
+ @buildAvatar()
+ @buildDocs() unless @docsBuilt
+
+ buildAvatar: ->
+ avatar = new ThangAvatarView thang: @thang, includeName: false
+ if @avatar
+ @avatar.$el.replaceWith avatar.$el
+ @avatar.destroy()
+ else
+ @$el.find('.thang-avatar-placeholder').replaceWith avatar.$el
+ @avatar = avatar
+ @avatar.render()
+
+ buildDocs: ->
+ doc = Docs.getDocsFor(@thang, [@spell.name])[0]
+ @$el.find('code').attr('title', doc.title()).popover(
+ animation: true
+ html: true
+ placement: 'bottom'
+ trigger: 'hover'
+ content: doc.html()
+ container: @$el.parent()
+ )
+ @docsBuilt = true
+
+ onMouseEnterAvatar: (e) -> # Don't call super
+ onMouseLeaveAvatar: (e) -> # Don't call super
+ onClick: (e) -> # Don't call super
+
+ onDropdownClick: (e) ->
+ Backbone.Mediator.publish 'tome:toggle-spell-list'
+
+ onCodeReload: ->
+ Backbone.Mediator.publish "tome:reload-code", spell: @spell
+
+ updateReloadButton: ->
+ changed = @spell.hasChanged null, @spell.getSource()
+ @$el.find('.reload-code').css('display', if changed then 'inline-block' else 'none')
+
+ onSpellLoaded: (e) ->
+ return unless e.spell is @spell
+ @updateReloadButton()
+
+ onSpellChanged: (e) ->
+ return unless e.spell is @spell
+ @updateReloadButton()
+
+ toggleControls: (e, enabled) ->
+ # Don't call super; do it differently
+ return if e.controls and not ('editor' in e.controls)
+ return if enabled is @controlsEnabled
+ @controlsEnabled = enabled
+ @$el.toggleClass 'read-only', not enabled
diff --git a/app/views/play/level/tome/spell_list_view.coffee b/app/views/play/level/tome/spell_list_view.coffee
new file mode 100644
index 000000000..38de566d8
--- /dev/null
+++ b/app/views/play/level/tome/spell_list_view.coffee
@@ -0,0 +1,71 @@
+# The SpellListView has SpellListEntryViews, which have ThangAvatarViews.
+# The SpellListView serves as a dropdown triggered from a SpellListTabEntryView, which actually isn't in a list, just had a lot of similar parts.
+# There is only one SpellListView, and it belongs to the TomeView.
+
+# TODO: showTopDivider should change when we reorder
+
+View = require 'views/kinds/CocoView'
+template = require 'templates/play/level/tome/spell_list'
+{me} = require 'lib/auth'
+SpellListEntryView = require './spell_list_entry_view'
+
+module.exports = class SpellListView extends View
+ className: 'spell-list-view'
+ id: 'spell-list-view'
+ template: template
+
+ subscriptions: {}
+
+ constructor: (options) ->
+ super options
+ @spells = options.spells
+ @sortSpells()
+
+ sortSpells: ->
+ # Keep only spells for which we have permissions
+ spells = _.filter @spells, (s) -> s.canRead()
+ @spells = _.sortBy spells, @sortScoreForSpell
+ #console.log "Kept sorted spells", @spells
+
+ sortScoreForSpell: (s) =>
+ # Sort by most spells per fewest Thangs
+ # Lower comes first
+ score = 0
+ # Selected spell at the top
+ score -= 9001900190019001 if s is @spell
+ # Spells for selected thang at the top
+ score -= 900190019001 if @thang and @thang.id of s.thangs
+ # Read-only spells at the bottom
+ score += 90019001 unless s.canWrite()
+ # The more Thangs sharing a spell, the lower
+ score += 9001 * _.size(s.thangs)
+ # The more spells per Thang, the higher
+ score -= _.filter(@spells, (s2) -> thangID of s2.thangs).length for thangID of s.thangs
+ score
+
+ sortEntries: ->
+ # Call sortSpells before this
+ @entries = _.sortBy @entries, (entry) => _.indexOf @spells, entry.spell
+ @$el.append entry.$el for entry in @entries
+
+ afterRender: ->
+ super()
+ @addSpellListEntries()
+
+ addSpellListEntries: ->
+ @entries = []
+ lastThangs = null
+ for spell, index in @spells
+ theseThangs = _.keys(spell.thangs)
+ changedThangs = not lastThangs or not _.isEqual theseThangs, lastThangs
+ lastThangs = theseThangs
+ @entries.push entry = new SpellListEntryView spell: spell, showTopDivider: changedThangs
+ for entry in @entries
+ @$el.append entry.el
+ entry.render() # Render after appending so that we can access parent container for popover
+
+ setThangAndSpell: (@thang, @spell) ->
+ @entries[0]?.setSelected false
+ @sortSpells()
+ @sortEntries()
+ @entries[0].setSelected true, @thang
diff --git a/app/views/play/level/tome/spell_palette_entry_view.coffee b/app/views/play/level/tome/spell_palette_entry_view.coffee
new file mode 100644
index 000000000..37904ff54
--- /dev/null
+++ b/app/views/play/level/tome/spell_palette_entry_view.coffee
@@ -0,0 +1,56 @@
+View = require 'views/kinds/CocoView'
+template = require 'templates/play/level/tome/spell_palette_entry'
+{me} = require 'lib/auth'
+filters = require 'lib/image_filter'
+Docs = require 'lib/world/docs'
+
+module.exports = class SpellPaletteEntryView extends View
+ tagName: 'div' # Could also try instead of , but would need to adjust colors
+ className: 'spell-palette-entry-view'
+ template: template
+
+ subscriptions:
+ 'surface:frame-changed': "onFrameChanged"
+
+ events:
+ 'mouseover': 'onMouseOver'
+ 'click': 'onClick'
+
+ constructor: (options) ->
+ super options
+ @thang = options.thang
+ @doc = options.doc
+ @shortenize = options.shortenize
+
+ afterRender: ->
+ super()
+ text = if @shortenize then @doc.shorterName else @doc.shortName
+ @$el.text(text).addClass(@doc.type)
+ @$el.attr('title', @doc.title()).popover(
+ animation: true
+ html: true
+ placement: 'top'
+ trigger: 'hover'
+ content: @doc.html()
+ container: @$el.parent().parent().parent()
+ )
+ @$el.on 'show', =>
+ # New, good event
+ Backbone.Mediator.publish 'tome:palette-hovered', thang: @thang, prop: @doc.prop
+ # Bad, old one for old scripts (TODO)
+ Backbone.Mediator.publish 'editor:palette-hovered', thang: @thang, prop: @doc.prop
+
+ onMouseOver: (e) ->
+ # Make sure the doc has the updated Thang so it can regenerate its prop value
+ @doc.thang = @thang
+ @$el.data('popover').options.content = @doc.html()
+ @$el.popover('setContent')
+
+ onClick: (e) ->
+ Backbone.Mediator.publish 'tome:palette-clicked', thang: @thang, prop: @doc.prop
+
+ onFrameChanged: (e) ->
+ return unless e.selectedThang?.id is @thang.id
+ @thang = e.selectedThang # Update our thang to the current version
+ @doc = Docs.getDocsFor(@thang, [@doc.prop])[0]
+ @$el.find("code.current-value").text(@doc.formatValue()) # Don't call any functions. (?? What does this mean?)
diff --git a/app/views/play/level/tome/spell_palette_view.coffee b/app/views/play/level/tome/spell_palette_view.coffee
new file mode 100644
index 000000000..ee080950e
--- /dev/null
+++ b/app/views/play/level/tome/spell_palette_view.coffee
@@ -0,0 +1,62 @@
+View = require 'views/kinds/CocoView'
+template = require 'templates/play/level/tome/spell_palette'
+{me} = require 'lib/auth'
+filters = require 'lib/image_filter'
+Docs = require 'lib/world/docs'
+SpellPaletteEntryView = require './spell_palette_entry_view'
+
+module.exports = class SpellPaletteView extends View
+ id: 'spell-palette-view'
+ template: template
+ controlsEnabled: true
+
+ subscriptions:
+ 'level-disable-controls': 'onDisableControls'
+ 'level-enable-controls': 'onEnableControls'
+ 'surface:frame-changed': "onFrameChanged"
+
+ constructor: (options) ->
+ super options
+ @thang = options.thang
+
+ afterRender: ->
+ super()
+ @createPalette()
+
+ createPalette: ->
+ docs = Docs.getDocsFor @thang, @thang.programmableProperties
+ docs = docs.concat Docs.getDocsFor(@thang, @thang.programmableSnippets, true)
+ shortenize = docs.length > 6
+ @entries = (@addEntry doc, shortenize for doc in docs)
+
+ addEntry: (doc, shortenize) ->
+ entry = new SpellPaletteEntryView doc: doc, thang: @thang, shortenize: shortenize
+ @$el.find('.properties').append entry.el
+ entry.render() # Render after appending so that we can access parent container for popover
+ entry
+
+ onDisableControls: (e) -> @toggleControls e, false
+ onEnableControls: (e) -> @toggleControls e, true
+ toggleControls: (e, enabled) ->
+ return if e.controls and not ('palette' in e.controls)
+ return if enabled is @controlsEnabled
+ @controlsEnabled = enabled
+ @$el.find('*').attr('disabled', not enabled)
+ @toggleBackground()
+
+ toggleBackground: =>
+ # TODO: make the palette background an actual background and do the CSS trick
+ # used in spell_list_entry.sass for disabling
+ background = @$el.find('.code-palette-background')[0]
+ if background.naturalWidth is 0 # not loaded yet
+ return _.delay @toggleBackground, 100
+ filters.revertImage background if @controlsEnabled
+ filters.darkenImage background, 0.8 unless @controlsEnabled
+
+ onFrameChanged: (e) ->
+ return unless e.selectedThang?.id is @thang.id
+ @thang = e.selectedThang # Update our thang to the current version
+
+ destroy: ->
+ super()
+ entry.destroy() for entry in @entries
diff --git a/app/views/play/level/tome/spell_view.coffee b/app/views/play/level/tome/spell_view.coffee
new file mode 100644
index 000000000..ff73bfef3
--- /dev/null
+++ b/app/views/play/level/tome/spell_view.coffee
@@ -0,0 +1,439 @@
+View = require 'views/kinds/CocoView'
+template = require 'templates/play/level/tome/spell'
+{me} = require 'lib/auth'
+filters = require 'lib/image_filter'
+Range = ace.require("ace/range").Range
+Problem = require './problem'
+
+module.exports = class SpellView extends View
+ id: 'spell-view'
+ className: 'shown'
+ template: template
+ controlsEnabled: true
+ eventsSuppressed: true
+ writable: true
+
+ subscriptions:
+ 'level-disable-controls': 'onDisableControls'
+ 'level-enable-controls': 'onEnableControls'
+ 'surface:frame-changed': 'onFrameChanged'
+ 'god:new-world-created': 'onNewWorld'
+ 'god:user-code-problem': 'onUserCodeProblem'
+ 'tome:manual-cast': 'onManualCast'
+ 'tome:reload-code': 'onCodeReload'
+ 'modal-closed': 'focus'
+ 'focus-editor': 'focus'
+
+ constructor: (options) ->
+ super options
+ @session = options.session
+ @session.on 'change:multiplayer', @onMultiplayerChanged
+ @spell = options.spell
+ @problems = {}
+ @writable = false unless me.team in @spell.permissions.readwrite # TODO: make this do anything
+ @highlightCurrentLine = _.throttle @highlightCurrentLine, 100
+
+ afterRender: ->
+ super()
+ @createACE()
+ @createACEShortcuts()
+ @fillACE()
+ if @session.get 'multiplayer'
+ @createFirepad()
+ else
+ # needs to happen after the code generating this view is complete
+ setTimeout @onLoaded, 1
+
+ createACE: ->
+ # Test themes and settings here: http://ace.ajax.org/build/kitchen-sink.html
+ @ace = ace.edit @$el.find('.ace')[0]
+ @aceSession = @ace.getSession()
+ @aceDoc = @aceSession.getDocument()
+ @aceSession.setUseWorker false
+ @aceSession.setMode 'ace/mode/javascript'
+ @aceSession.setWrapLimitRange null
+ @aceSession.setUseWrapMode true
+ @ace.setTheme 'ace/theme/textmate'
+ @ace.setDisplayIndentGuides false
+ @ace.setShowPrintMargin false
+ @ace.setShowInvisibles false
+ @ace.setBehavioursEnabled false
+ @toggleControls null, @writable
+ @aceSession.selection.on 'changeCursor', @onCursorActivity
+ $(@ace.container).find('.ace_gutter').on 'click', '.ace_error, .ace_warning, .ace_info', @onAnnotationClick
+
+ createACEShortcuts: ->
+ @ace.commands.addCommand
+ name: 'run-code'
+ bindKey: {win: 'Shift-Enter|Ctrl-Enter|Ctrl-S', mac: 'Shift-Enter|Command-Enter|Ctrl-Enter|Command-S|Ctrl-S'}
+ exec: (e) => @recompile()
+ @ace.commands.addCommand
+ name: 'toggle-playing'
+ bindKey: {win: 'Ctrl-P', mac: 'Command-P|Ctrl-P'}
+ exec: -> Backbone.Mediator.publish 'level-toggle-playing'
+ @ace.commands.addCommand
+ name: 'end-current-script'
+ bindKey: {win: 'Shift-Space', mac: 'Shift-Space'}
+ exec: -> Backbone.Mediator.publish 'level:shift-space-pressed'
+ @ace.commands.addCommand
+ name: 'end-all-scripts'
+ bindKey: {win: 'Escape', mac: 'Escape'}
+ exec: -> Backbone.Mediator.publish 'level:escape-pressed'
+
+ # TODO: These don't work on, for example, Danish keyboards. Figure out a more universal solution.
+# @ace.commands.addCommand
+# name: 'toggle-grid'
+# bindKey: {win: 'Alt-G', mac: 'Alt-G'}
+# exec: -> Backbone.Mediator.publish 'level-toggle-grid'
+# @ace.commands.addCommand
+# name: 'toggle-debug'
+# bindKey: {win: 'Alt-\\', mac: 'Alt-\\'}
+# exec: -> Backbone.Mediator.publish 'level-toggle-debug'
+# @ace.commands.addCommand
+# name: 'level-scrub-forward'
+# bindKey: {win: 'Alt-]', mac: 'Alt-]'}
+# exec: -> Backbone.Mediator.publish 'level-scrub-forward'
+# @ace.commands.addCommand
+# name: 'level-scrub-back'
+# bindKey: {win: 'Alt-[', mac: 'Alt-['}
+# exec: -> Backbone.Mediator.publish 'level-scrub-back'
+
+ fillACE: ->
+ @ace.setValue @spell.source
+ @ace.clearSelection()
+
+ onMultiplayerChanged: =>
+ if @session.get 'multiplayer'
+ @createFirepad()
+ else
+ @firepad?.dispose()
+
+ createFirepad: ->
+ # load from firebase or the original source if there's nothing there
+ return if @firepadLoading
+ @eventsSuppressed = true
+ @loaded = false
+ @previousSource = @ace.getValue()
+ @ace.setValue('')
+ fireURL = 'https://codecombat.firebaseio.com/' + @spell.pathComponents.join('/')
+ @fireRef = new Firebase fireURL
+ firepadOptions = userId: me.id
+ @firepad = Firepad.fromACE @fireRef, @ace, firepadOptions
+ @firepad.on 'ready', @onFirepadLoaded
+ @firepadLoading = true
+
+ onFirepadLoaded: =>
+ @firepadLoading = false
+ firepadSource = @ace.getValue()
+ if firepadSource
+ @spell.source = firepadSource
+ else
+ @ace.setValue @previousSource
+ @ace.clearSelection()
+ @onLoaded()
+
+ onLoaded: =>
+ @spell.transpile @spell.source
+ @spell.loaded = true
+ Backbone.Mediator.publish 'tome:spell-loaded', spell: @spell
+ @eventsSuppressed = false # Now that the initial change is in, we can start running any changed code
+
+ getSource: ->
+ @ace.getValue() # could also do @firepad.getText()
+
+ setThang: (thang) ->
+ @focus()
+ return if thang.id is @thang?.id
+ @thang = thang
+ @spellThang = @spell.thangs[@thang.id]
+ @updateAether false, true
+ @highlightCurrentLine()
+
+ cast: ->
+ Backbone.Mediator.publish 'tome:cast-spell', spell: @spell, thang: @thang
+
+ notifySpellChanged: =>
+ Backbone.Mediator.publish 'tome:spell-changed', spell: @spell
+
+ notifyEditingEnded: =>
+ return if @aceDoc.undergoingFirepadOperation # from my Firepad ACE adapter
+ Backbone.Mediator.publish('tome:editing-ended')
+
+ notifyEditingBegan: =>
+ return if @aceDoc.undergoingFirepadOperation # from my Firepad ACE adapter
+ Backbone.Mediator.publish('tome:editing-began')
+
+ onManualCast: (e) ->
+ cast = @$el.parent().length
+ @recompile cast
+ @focus() if cast
+
+ onCodeReload: (e) ->
+ return unless e.spell is @spell
+ @reloadCode true
+
+ reloadCode: (cast=true) ->
+ @updateACEText @spell.originalSource
+ @recompile cast
+
+ recompile: (cast=true) =>
+ @setRecompileNeeded false
+ return if @spell.source is @getSource()
+ @spell.transpile @getSource()
+ @updateAether true, false
+ @cast() if cast
+ @notifySpellChanged()
+
+ updateACEText: (source) ->
+ @eventsSuppressed = true
+ if @firepad
+ @firepad.setText source
+ else
+ @ace.setValue source
+ @eventsSuppressed = false
+ @ace.resize true # hack: @ace may not have updated its text properly, so we force it to refresh
+
+ # Called from CastButtonView initially and whenever the delay is changed
+ setAutocastDelay: (@autocastDelay) ->
+ @createOnCodeChangeHandlers()
+
+ createOnCodeChangeHandlers: ->
+ @aceDoc.removeListener 'change', @onCodeChangeMetaHandler if @onCodeChangeMetaHandler
+ autocastDelay = @autocastDelay ? 3000
+ onSignificantChange = [
+ _.debounce @setRecompileNeeded, autocastDelay - 100
+ @currentAutocastHandler = _.debounce (=> @recompile() if @recompileNeeded), autocastDelay
+ ]
+ onAnyChange = [
+ _.debounce @updateAether, 500
+ _.debounce @notifyEditingEnded, 1000
+ _.throttle @notifyEditingBegan, 250
+ _.throttle @notifySpellChanged, 300
+ ]
+ @onCodeChangeMetaHandler = =>
+ return if @eventsSuppressed
+ if @spell.hasChangedSignificantly @getSource(), @spellThang.aether.raw
+ callback() for callback in onSignificantChange # Do these first
+ callback() for callback in onAnyChange # Then these
+ @aceDoc.on 'change', @onCodeChangeMetaHandler
+
+ setRecompileNeeded: (needed=true) =>
+ if needed
+ @recompileNeeded = needed # and @recompileValid # todo, remove if not caring about validity
+ else
+ @recompileNeeded = false
+
+ onCursorActivity: =>
+ @currentAutocastHandler?()
+
+ # Design for a simpler system?
+ # * Turn off ACE's JSHint worker
+ # * Keep Aether linting, debounced, on any significant change
+ # - Don't send runtime errors from in-progress worlds
+ # - All problems just vanish when you make any change to the code
+ # * You wouldn't accept any Aether updates/runtime information/errors unless its code was current when you got it
+ # * Store the last run Aether in each spellThang and use it whenever its code actually is current
+ # This suffers from the problem that any whitespace/comment changes will lose your info, but what else
+ # could you do other than somehow maintain a mapping from current to original code locations?
+ # I guess you could use dynamic markers for problem ranges and keep annotations/alerts in when insignificant
+ # changes happen, but always treat any change in the (trimmed) number of lines as a significant change.
+ # Ooh, that's pretty nice. Gets you most of the way there and is simple.
+ # - All problems have a master representation as a Problem, and we can easily generate all Problems from
+ # any Aether instance. Then when we switch contexts in any way, we clear, recreate, and reapply the Problems.
+ # * Problem alerts will have their own templated ProblemAlertViews
+ # * We'll only show the first problem alert, and it will always be at the bottom.
+ # Annotations and problem ranges can show all, I guess.
+ # * The editor will reserve space for one annotation as a codeless area.
+ # - Problem alerts and ranges will only show on fully cast worlds. Annotations will show continually.
+
+ updateAether: (force=false, fromCodeChange=true) =>
+ # Depending on whether we have any code changes, significant code changes, or have switched
+ # to a new spellThang, we may want to refresh our Aether display.
+ return unless aether = @spellThang?.aether
+ source = @getSource()
+ codeHasChangedSignificantly = force or @spell.hasChangedSignificantly source, aether.raw
+ return unless codeHasChangedSignificantly or @spellThang isnt @lastUpdatedAetherSpellThang
+ castAether = @spellThang.castAether
+ codeIsAsCast = castAether and not @spell.hasChangedSignificantly source, castAether.raw
+ aether = castAether if codeIsAsCast
+
+ # Now that that's figured out, perform the update.
+ @clearAetherDisplay()
+ aether.transpile source if codeHasChangedSignificantly and not codeIsAsCast
+ @displayAether aether
+ @lastUpdatedAetherSpellThang = @spellThang
+ @guessWhetherFinished aether if fromCodeChange
+
+ clearAetherDisplay: ->
+ problem.destroy() for problem in @problems
+ @problems = []
+ @aceSession.setAnnotations []
+ @highlightCurrentLine {} # This'll remove all highlights
+
+ displayAether: (aether) ->
+ isCast = not _.isEmpty(aether.metrics) or _.some aether.problems.errors, {type: 'runtime'}
+ @problems = []
+ annotations = []
+ for aetherProblem, problemIndex in aether.getAllProblems()
+ @problems.push problem = new Problem aether, aetherProblem, @ace, isCast and problemIndex is 0, isCast
+ annotations.push problem.annotation if problem.annotation
+ @aceSession.setAnnotations annotations
+ @highlightCurrentLine aether.flow unless _.isEmpty aether.flow
+ #console.log ' and we could do the metrics', aether.metrics unless _.isEmpty aether.metrics
+ #console.log ' and we could do the style', aether.style unless _.isEmpty aether.style
+ #console.log ' and we could do the visualization', aether.visualization unless _.isEmpty aether.visualization
+ # Could use the user-code-problem style... or we could leave that to other places.
+ @ace[if @problems.length then 'setStyle' else 'unsetStyle'] 'user-code-problem'
+ Backbone.Mediator.publish 'tome:problems-updated', spell: @spell, problems: @problems, isCast: isCast
+
+ # Autocast:
+ # Goes immediately if the code is a) changed and b) complete/valid and c) the cursor is at beginning or end of a line
+ # We originall thought it would:
+ # - Go after specified delay if a) and b) but not c)
+ # - Go only when manually cast or deselecting a Thang when there are errors
+ # But the error message display was delayed, so now trying:
+ # - Go after specified delay if a) and not b) or c)
+ guessWhetherFinished: (aether) ->
+ return if @autocastDelay > 60000
+ #@recompileValid = not aether.getAllProblems().length
+ valid = not aether.getAllProblems().length
+ cursorPosition = @ace.getCursorPosition()
+ currentLine = @aceDoc.$lines[cursorPosition.row].replace(/[ \t]*\/\/[^"']*/g, '').trimRight() # trim // unless inside "
+ endOfLine = cursorPosition.column >= currentLine.length # just typed a semicolon or brace, for example
+ beginningOfLine = not currentLine.substr(0, cursorPosition.column).trim().length # uncommenting code, for example
+ #console.log "finished?", valid, endOfLine, beginningOfLine, cursorPosition, currentLine.length, aether, new Date() - 0, currentLine
+ if valid and endOfLine or beginningOfLine
+ @recompile()
+ #console.log "recompile now!"
+ #else if not valid
+ # # if this works, we can get rid of all @recompileValid logic
+ # console.log "not valid, but so we'll wait to do it in", @autocastDelay + "ms"
+ #else
+ # console.log "valid but not at end of line; recompile in", @autocastDelay + "ms"
+
+ onUserCodeProblem: (e) ->
+ return @onInfiniteLoop e if e.problem.id is "runtime_InfiniteLoop"
+ return unless e.problem.userInfo.methodName is @spell.name
+ return unless spellThang = _.find @spell.thangs, (spellThang, thangID) -> thangID is e.problem.userInfo.thangID
+ return if @spell.hasChangedSignificantly @getSource() # don't show this error if we've since edited the code
+ spellThang.aether.addProblem e.problem
+ @lastUpdatedAetherSpellThang = null # force a refresh without a re-transpile
+ @updateAether false, false
+
+ onInfiniteLoop: (e) ->
+ return unless @spellThang
+ @spellThang.aether.addProblem e.problem
+ @lastUpdatedAetherSpellThang = null # force a refresh without a re-transpile
+ @updateAether false, false
+
+ onNewWorld: (e) ->
+ for thangID, spellThang of @spell.thangs
+ aether = e.world.userCodeMap[thangID][@spell.name]
+ #console.log thangID, "got new castAether with raw", aether.raw, "problems", aether.problems
+ spellThang.castAether = aether
+ spellThang.aether = @spell.createAether e.world.getThangByID(thangID)
+ #console.log thangID, @spell.spellKey, "ran", aether.metrics.callsExecuted, "times over", aether.metrics.statementsExecuted, "statements, with max recursion depth", aether.metrics.maxDepth, "and full flow/metrics", aether.metrics, aether.flow
+ @spell.transpile()
+ @updateAether false, false
+
+ # --------------------------------------------------------------------------------------------------
+
+ focus: ->
+ # TODO: it's a hack checking if a modal is visible; the events should be removed somehow
+ # but this view is not part of the normal subview destroying because of how it's swapped
+ return unless @controlsEnabled and @writable and $('.modal:visible').length is 0
+ return if @ace.isFocused()
+ @ace.focus()
+ @ace.clearSelection()
+
+ onFrameChanged: (e) ->
+ return unless e.selectedThang?.id is @thang?.id
+ @thang = e.selectedThang # update our thang to the current version
+ @highlightCurrentLine()
+
+ highlightCurrentLine: (flow) =>
+ flow ?= @spellThang?.castAether?.flow
+ return unless flow
+ executed = []
+ matched = false
+ for callState, callNumber in flow.states or []
+ if matched
+ executed.pop()
+ break
+ executed.push []
+ for state, statementNumber in callState
+ if state.userInfo?.time > @thang.world.age
+ matched = true
+ break
+ _.last(executed).push state
+ #state.executing = true if state.userInfo?.time is @thang.world.age # no work
+
+ # TODO: don't redo the markers if they haven't actually changed
+ text = @aceDoc.getValue()
+ offsetToPos = (offset) ->
+ # TODO: use the nice conversion utils David put into Aether
+ rows = text.substr(0, offset).split '\n'
+ {row: rows.length - 1, column: _.last(rows).length}
+
+ for markerRange in (@markerRanges ?= [])
+ markerRange.start.detach()
+ markerRange.end.detach()
+ @aceSession.removeMarker markerRange.id
+ @markerRanges = []
+ @aceSession.removeGutterDecoration row, 'executing' for row in [0 ... @aceSession.getLength()]
+ $(@ace.container).find('.ace_gutter-cell.executing').removeClass('executing')
+ return unless executed.length
+ lastExecuted = _.last executed
+ marked = {}
+ for state, i in lastExecuted
+ #clazz = if state.executing then 'executing' else 'executed' # doesn't work
+ clazz = if i is lastExecuted.length - 1 then 'executing' else 'executed'
+ if clazz is 'executed'
+ key = state.range[0] + '_' + state.range[1]
+ continue if marked[key] > 2 # don't allow more than three of the same marker
+ marked[key] ?= 0
+ ++marked[key]
+ [start, end] = [offsetToPos(state.range[0]), offsetToPos(state.range[1])]
+ markerRange = new Range(start.row, start.column, end.row, end.column)
+ markerRange.start = @aceDoc.createAnchor markerRange.start
+ markerRange.end = @aceDoc.createAnchor markerRange.end
+ markerRange.id = @aceSession.addMarker markerRange, clazz, "text"
+ @markerRanges.push markerRange
+ @aceSession.addGutterDecoration start.row, clazz if clazz is 'executing'
+
+ onAnnotationClick: ->
+ alertBox = $("
#{msg}
")
+ offset = $(@).offset()
+ offset.left -= 162 # default width of the Bootstrap alert here
+ alertBox.css(offset).css('z-index', 500).css('position', 'absolute')
+ $('body').append(alertBox.alert())
+ _.delay (-> alertBox.alert('close')), 2500
+
+ onDisableControls: (e) -> @toggleControls e, false
+ onEnableControls: (e) -> @toggleControls e, @writable
+ toggleControls: (e, enabled) ->
+ return if e?.controls and not ('editor' in e.controls)
+ return if enabled is @controlsEnabled
+ @controlsEnabled = enabled and @writable
+ disabled = not enabled
+ $('body').focus() if disabled and $(document.activeElement).is('.ace_text-input')
+ @ace.setReadOnly disabled
+ @ace[if disabled then "setStyle" else "unsetStyle"] "disabled"
+ @toggleBackground()
+
+ toggleBackground: =>
+ # TODO: make the background an actual background and do the CSS trick
+ # used in spell_list_entry.sass for disabling
+ background = @$el.find('.code-background')[0]
+ if background.naturalWidth is 0 # not loaded yet
+ return _.delay @toggleBackground, 100
+ filters.revertImage background if @controlsEnabled
+ filters.darkenImage background, 0.8 unless @controlsEnabled
+
+ dismiss: ->
+ @recompile() if @spell.hasChangedSignificantly @getSource()
+
+ destroy: ->
+ super()
+ @firepad?.dispose()
+ @ace.destroy()
diff --git a/app/views/play/level/tome/thang_list_entry_view.coffee b/app/views/play/level/tome/thang_list_entry_view.coffee
new file mode 100644
index 000000000..beb9ebb0d
--- /dev/null
+++ b/app/views/play/level/tome/thang_list_entry_view.coffee
@@ -0,0 +1,135 @@
+# TODO: be useful to add error indicator states to the spellsPopoverTemplate
+# TODO: reordering based on errors isn't working yet
+
+View = require 'views/kinds/CocoView'
+ThangAvatarView = require 'views/play/level/thang_avatar_view'
+template = require 'templates/play/level/tome/thang_list_entry'
+spellsPopoverTemplate = require 'templates/play/level/tome/thang_list_entry_spells'
+{me} = require 'lib/auth'
+
+module.exports = class ThangListEntryView extends View
+ tagName: 'div' #'li'
+ className: 'thang-list-entry-view'
+ template: template
+ controlsEnabled: true
+ reasonsToBeDisabled: {}
+
+ subscriptions:
+ 'tome:problems-updated': "onProblemsUpdated"
+ 'level-disable-controls': 'onDisableControls'
+ 'level-enable-controls': 'onEnableControls'
+ 'surface:frame-changed': "onFrameChanged"
+ 'level-set-letterbox': 'onSetLetterbox'
+
+ events:
+ 'click': 'onClick'
+ 'mouseenter': 'onMouseEnter'
+ 'mouseleave': 'onMouseLeave'
+
+ constructor: (options) ->
+ super options
+ @thang = options.thang
+ @spells = options.spells
+ @permission = options.permission
+ @reasonsToBeDisabled = {}
+ @sortSpells()
+
+ getRenderData: (context={}) =>
+ context = super context
+ context.thang = @thang
+ context.spell = @spells
+ context
+
+ afterRender: ->
+ super()
+ @avatar = new ThangAvatarView thang: @thang, includeName: true
+ @$el.append @avatar.el # Before rendering, so render can use parent for popover
+ @avatar.render()
+ @avatar.setSharedThangs @spells.length # A bit weird to call it sharedThangs; could refactor if we like this
+ @$el.popover(
+ animation: false
+ html: true
+ placement: 'bottom'
+ trigger: 'manual'
+ content: @getSpellListHTML()
+ container: @$el.parent().parent().parent()
+ )
+
+ sortSpells: ->
+ return if @sorted
+ # Keep only spells for which we have permissions
+ spells = _.filter @spells, (s) => @options.permission and me.team in s.permissions[@options.permission]
+ @spells = _.sortBy spells, @sortScoreForSpell
+ @sorted = true
+
+ sortScoreForSpell: (s) =>
+ # Sort by errored-out spells first, then spells shared with fewest other Thangs
+ # Lower comes first
+ score = 0
+ # My errors are highest priority
+ score -= 9001900190019001 * (s.thangs[@thang.id].aether?.getAllProblems().length or 0)
+ # Other shared Thangs errors are also high priority
+ score -= _.reduce s.thangs, (spellThang, num) -> 900190019001 * (spellThang.aether?.getAllProblems().length or 0)
+ # Read-only spells at the bottom
+ score += 90019001 unless s.canWrite()
+ # The more Thangs sharing a spell, the lower
+ score += 9001 * _.size(s.thangs)
+ score
+
+ onClick: (e) ->
+ return unless @controlsEnabled
+ @sortSpells()
+ Backbone.Mediator.publish "level-select-sprite", thangID: @thang.id, spellName: @spells[0]?.name
+
+ onMouseEnter: (e) ->
+ return unless @controlsEnabled and @spells.length
+ @showSpells()
+
+ onMouseLeave: (e) ->
+ return unless @controlsEnabled and @spells.length
+ @hideSpellsTimeout = _.delay @hideSpells, 100
+
+ showSpells: =>
+ @sortSpells()
+ @$el.data('popover').options.content = @getSpellListHTML()
+ @$el.popover('setContent').popover('show')
+ @$el.parent().parent().parent().i18n()
+ clearTimeout @hideSpellsTimeout if @hideSpellsTimeout
+ popover = @$el.parent().parent().parent().find('.popover')
+ popover.off 'mouseenter mouseleave'
+ popover.mouseenter (e) => @onMouseEnter()
+ popover.mouseleave (e) => @onMouseLeave()
+ thangID = @thang.id
+ popover.find('code').click (e) ->
+ Backbone.Mediator.publish "level-select-sprite", thangID: thangID, spellName: $(@).data 'spell-name'
+
+ hideSpells: =>
+ @$el.popover('hide')
+
+ getSpellListHTML: ->
+ spellsPopoverTemplate {spells: @spells}
+
+ onProblemsUpdated: (e) ->
+ return unless e.spell in @spells
+ @sorted = false
+
+ onSetLetterbox: (e) ->
+ if e.on then @reasonsToBeDisabled.letterbox = true else delete @reasonsToBeDisabled.letterbox
+ @updateControls()
+ onDisableControls: (e) ->
+ return if e.controls and not ('surface' in e.controls) # disable selection?
+ @reasonsToBeDisabled.controls = true
+ @updateControls()
+ onEnableControls: (e) ->
+ delete @reasonsToBeDisabled.controls
+ @updateControls()
+ updateControls: ->
+ enabled = _.keys(@reasonsToBeDisabled).length is 0
+ return if enabled is @controlsEnabled
+ @controlsEnabled = enabled
+ @$el.toggleClass('disabled', not enabled)
+
+ onFrameChanged: (e) ->
+ return unless currentThang = e.world.thangMap[@thang.id]
+ @$el.toggle Boolean(currentThang.exists)
+ @$el.toggleClass 'dead', currentThang.health <= 0 if currentThang.exists
diff --git a/app/views/play/level/tome/thang_list_view.coffee b/app/views/play/level/tome/thang_list_view.coffee
new file mode 100644
index 000000000..d7a847628
--- /dev/null
+++ b/app/views/play/level/tome/thang_list_view.coffee
@@ -0,0 +1,73 @@
+# The ThangListView lives in the code area behind the SpellView, so that when you don't have a spell, you can select any Thang.
+# It just ha a bunch of ThangListEntryViews (which are mostly ThangAvatarViews) in a few sections.
+
+View = require 'views/kinds/CocoView'
+template = require 'templates/play/level/tome/thang_list'
+{me} = require 'lib/auth'
+ThangListEntryView = require './thang_list_entry_view'
+
+module.exports = class ThangListView extends View
+ className: 'thang-list-view'
+ id: 'thang-list-view'
+ template: template
+
+ subscriptions: {}
+
+ constructor: (options) ->
+ super options
+ @spells = options.spells
+ @thangs = _.filter options.thangs, 'isSelectable'
+ @sortThangs()
+
+ sortThangs: ->
+ @readwriteThangs = _.sortBy _.filter(@thangs, (thang) =>
+ return true for spellKey, spell of @spells when thang.id of spell.thangs and spell.canWrite()
+ false
+ ), @sortScoreForThang
+ @readThangs = _.sortBy _.filter(@thangs, (thang) =>
+ return true for spellKey, spell of @spells when thang.id of spell.thangs and spell.canRead() and not spell.canWrite()
+ false
+ ), @sortScoreForThang
+ @muggleThangs = _.sortBy _.without(@thangs, @readwriteThangs..., @readThangs...), @sortScoreForThang
+
+ sortScoreForThang: (t) =>
+ # Sort by my team, then most spells and fewest shared Thangs per spell,
+ # then by thang.spriteName alpha, then by thang.id alpha.
+ # Lower comes first
+ score = 0
+ # Thangs on my team are highest priority
+ score -= 9001900190019001 if t.team is me.team
+ # The more spells per Thang, the lower
+ score -= 900190019001 for spellKey, spell of @spells when t.id of spell.thangs and spell.canRead()
+ # The more Thangs per spell, the higher
+ score += 90019001 for t2 of spell.thangs for spellKey, spell of @spells when t.id of spell.thangs
+ alpha = (s) -> _.reduce [0 ... s.length], ((acc, i) -> acc + s.charCodeAt(i) / Math.pow(100, i)), 0
+ # Alpha by spriteName
+ score += 9001 * alpha t.spriteName
+ # Alpha by id
+ score += alpha t.id
+ score
+
+ afterRender: ->
+ super()
+ @addThangListEntries()
+
+ addThangListEntries: ->
+ @entries = []
+ for [thangs, section, permission] in [
+ [@readwriteThangs, "#readwrite-thangs", "readwrite"] # Your Minions
+ [@readThangs, "#read-thangs", "read"] # Read-Only
+ [@muggleThangs, "#muggle-thangs", null] # Non-Castable
+ ]
+ section = @$el.find(section).toggle thangs.length > 0
+ for thang in thangs
+ spells = _.filter @spells, (s) -> thang.id of s.thangs
+ entry = new ThangListEntryView thang: thang, spells: spells, permission: permission
+ section.find('.thang-list').append entry.el # Render after appending so that we can access parent container for popover
+ entry.render()
+ @entries.push entry
+
+ topSpellForThang: (thang) ->
+ for entry in @entries when entry.thang.id is thang.id
+ return entry.spells[0]
+ null
diff --git a/app/views/play/level/tome/tome_view.coffee b/app/views/play/level/tome/tome_view.coffee
new file mode 100644
index 000000000..34ef4608a
--- /dev/null
+++ b/app/views/play/level/tome/tome_view.coffee
@@ -0,0 +1,153 @@
+# There's one TomeView per Level. It has:
+# - a CastButtonView, which has
+# - a cast button
+# - an autocast settings options button
+# - for each spell (programmableMethod):
+# - a Spell, which has
+# - a list of Thangs that share that Spell, with one aether per Thang per Spell
+# - a SpellView, which has
+# - tons of stuff; the meat
+# - a SpellListView, which has
+# - for each spell:
+# - a SpellListEntryView, which has
+# - icons for each Thang
+# - the spell name
+# - a reload button
+# - documentation for that method (in a popover)
+# - a SpellPaletteView, which has
+# - for each programmableProperty:
+# - a SpellPaletteEntryView
+#
+# The CastButtonView and SpellListView always show.
+# The SpellPaletteView shows the entries for the currently selected Programmable Thang.
+# The SpellView shows the code and runtime state for the currently selected Spell and, specifically, Thang.
+# The SpellView obscures most of the SpellListView when present. We might mess with this.
+# You can switch a SpellView to showing the runtime state of another Thang sharing that Spell.
+# SpellPaletteViews are destroyed and recreated whenever you switch Thangs.
+# The SpellListView shows spells to which your team has read or readwrite access.
+# It doubles as a Thang selector, since it's there when nothing is selected.
+
+View = require 'views/kinds/CocoView'
+template = require 'templates/play/level/tome/tome'
+{me} = require 'lib/auth'
+Spell = require './spell'
+SpellListView = require './spell_list_view'
+ThangListView = require './thang_list_view'
+SpellPaletteView = require './spell_palette_view'
+CastButtonView = require './cast_button_view'
+
+module.exports = class TomeView extends View
+ id: 'tome-view'
+ template: template
+ controlsEnabled: true
+ cache: false
+
+ subscriptions:
+ 'tome:spell-loaded': "onSpellLoaded"
+ 'tome:cast-spell': "onCastSpell"
+ 'tome:toggle-spell-list': 'onToggleSpellList'
+ 'surface:sprite-selected': 'onSpriteSelected'
+
+ events:
+ 'click #spell-view': 'onSpellViewClick'
+ 'click': -> Backbone.Mediator.publish 'focus-editor'
+
+ constructor: (options) ->
+ super options
+
+ afterRender: ->
+ super()
+ programmableThangs = _.filter @options.thangs, 'isProgrammable'
+ @createSpells programmableThangs # Do before spellList, thangList, and castButton
+ @spellList = @insertSubView new SpellListView spells: @spells
+ @thangList = @insertSubView new ThangListView spells: @spells, thangs: @options.thangs
+ @castButton = @insertSubView new CastButtonView spells: @spells
+
+ createSpells: (programmableThangs) ->
+ # If needed, we could make this able to update when programmableThangs changes.
+ # We haven't done that yet, so call it just once on init.
+ pathPrefixComponents = ['play', 'level', @options.levelID, @options.session.id, 'code']
+ @spells = {}
+ @thangSpells = {}
+ for thang in programmableThangs
+ world = thang.world
+ @thangSpells[thang.id] = []
+ for methodName, method of thang.programmableMethods
+ pathComponents = [thang.id, methodName]
+ if method.cloneOf
+ pathComponents[0] = method.cloneOf # referencing another Thang's method
+ pathComponents[0] = _.string.slugify pathComponents[0]
+ spellKey = pathComponents.join '/'
+ @thangSpells[thang.id].push spellKey
+ unless method.cloneOf
+ spell = @spells[spellKey] = new Spell method, spellKey, pathPrefixComponents.concat(pathComponents), @options.session
+ for thangID, spellKeys of @thangSpells
+ thang = world.getThangByID(thangID)
+ @spells[spellKey].addThang thang for spellKey in spellKeys
+ null
+
+ onSpellLoaded: (e) ->
+ for spellID, spell of @spells
+ return unless spell.loaded
+ @cast()
+
+ onCastSpell: (e) ->
+ # A single spell is cast.
+ # Hmm; do we need to make sure other spells are all cast here?
+ @cast()
+
+ cast: ->
+ Backbone.Mediator.publish 'tome:cast-spells', spells: @spells
+
+ onToggleSpellList: (e) ->
+ @spellList.$el.toggle()
+
+ onSpellViewClick: (e) ->
+ @spellList.$el.hide()
+
+ clearSpellView: ->
+ @spellView?.dismiss()
+ @spellView?.$el.after('
').detach()
+ @spellView = null
+ @spellTabView?.$el.after('
').detach()
+ @spellTabView = null
+ @removeSubView @spellPaletteView if @spellPaletteView
+ @thangList.$el.show()
+
+ onSpriteSelected: (e) ->
+ thang = e.thang
+ spellName = e.spellName
+ @spellList.$el.hide()
+ return @clearSpellView() unless thang?.isProgrammable
+ selectedThangSpells = (@spells[spellKey] for spellKey in @thangSpells[thang.id])
+ if spellName
+ spell = _.find selectedThangSpells, {name: spellName}
+ else
+ spell = @thangList.topSpellForThang thang
+ #spell = selectedThangSpells[0] # TODO: remember last selected spell for this thang
+ return @clearSpellView() unless spell?.canRead()
+ @spellList.setThangAndSpell thang, spell
+ return if spell.view is @spellView
+ @clearSpellView()
+ @spellView = spell.view
+ @spellTabView = spell.tabView
+ @$el.find('#' + @spellView.id).after(@spellView.el).remove()
+ @$el.find('#' + @spellTabView.id).after(@spellTabView.el).remove()
+ @spellView.setThang thang
+ @spellTabView.setThang thang
+ @thangList.$el.hide()
+ @spellPaletteView = @insertSubView new SpellPaletteView thang: thang
+ @spellPaletteView.toggleControls {}, @spellView.controlsEnabled # TODO: know when palette should have been disabled but didn't exist
+ # New, good event
+ Backbone.Mediator.publish 'tome:spell-shown', thang: thang, spell: spell
+ # Bad, old one for old scripts (TODO)
+ Backbone.Mediator.publish 'editor:tab-shown', thang: thang, methodName: spell.name
+
+ reloadAllCode: ->
+ spell.view.reloadCode false for spellKey, spell of @spells
+ Backbone.Mediator.publish 'tome:cast-spells', spells: @spells
+
+ destroy: ->
+ super()
+ for spellKey, spell of @spells
+ spell.view.destroy()
diff --git a/app/views/play/level_view.coffee b/app/views/play/level_view.coffee
new file mode 100644
index 000000000..fd625eaa7
--- /dev/null
+++ b/app/views/play/level_view.coffee
@@ -0,0 +1,385 @@
+View = require 'views/kinds/RootView'
+template = require 'templates/play/level'
+{me} = require('lib/auth')
+application = require('application')
+ThangType = require 'models/ThangType'
+
+# temp hard coded data
+World = require 'lib/world/world'
+docs = require 'lib/world/docs'
+
+# tools
+Surface = require 'lib/surface/Surface'
+God = require 'lib/God'
+GoalManager = require 'lib/world/GoalManager'
+ScriptManager = require 'lib/scripts/ScriptManager'
+LevelBus = require('lib/LevelBus')
+LevelLoader = require 'lib/LevelLoader'
+LevelSession = require 'models/LevelSession'
+Level = require 'models/Level'
+LevelComponent = require 'models/LevelComponent'
+Camera = require 'lib/surface/Camera'
+
+# subviews
+TomeView = require './level/tome/tome_view'
+ChatView = require './level/level_chat_view'
+HUDView = require './level/hud_view'
+ControlBarView = require './level/control_bar_view'
+PlaybackView = require './level/playback_view'
+GoalsView = require './level/goals_view'
+VictoryModal = require './level/modal/victory_modal'
+InfiniteLoopModal = require './level/modal/infinite_loop_modal'
+
+LoadingScreen = require 'lib/LoadingScreen'
+
+PROFILE_ME = false
+
+module.exports = class PlayLevelView extends View
+ id: 'level-view'
+ template: template
+ cache: false
+ shortcutsEnabled: true
+ startsLoading: true
+ isEditorPreview: false
+
+ subscriptions:
+ 'level-set-volume': (e) -> createjs.Sound.setVolume(e.volume)
+ 'level-show-victory': 'onShowVictory'
+ 'restart-level': 'onRestartLevel'
+ 'level-highlight-dom': 'onHighlightDom'
+ 'end-level-highlight-dom': 'onEndHighlight'
+ 'level-focus-dom': 'onFocusDom'
+ 'level-disable-controls': 'onDisableControls'
+ 'level-enable-controls': 'onEnableControls'
+ 'god:infinite-loop': 'onInfiniteLoop'
+ 'bus:connected': 'onBusConnected'
+ 'level-reload-from-data': 'onLevelReloadFromData'
+ 'play-next-level': 'onPlayNextLevel'
+ 'edit-wizard-settings': 'showWizardSettingsModal'
+ 'surface:world-set-up': 'onSurfaceSetUpNewWorld'
+ 'level:session-will-save': 'onSessionWillSave'
+
+ events:
+ 'click #level-done-button': 'onDonePressed'
+
+ constructor: (options, @levelID) ->
+ console.profile?() if PROFILE_ME
+ super options
+ if not me.get('hourOfCode') and @getQueryVariable "hour_of_code"
+ me.set 'hourOfCode', true
+ me.save()
+ $('body').append($("
"))
+ window.tracker?.trackEvent 'Hour of Code Begin', {}
+
+ @isEditorPreview = @getQueryVariable "dev"
+ sessionID = @getQueryVariable "session"
+ @levelLoader = new LevelLoader(@levelID, @supermodel, sessionID)
+ @levelLoader.once 'ready-to-init-world', @onReadyToInitWorld
+
+ $(window).on('resize', @onWindowResize)
+ @supermodel.once 'error', =>
+ msg = $.i18n.t('play_level.level_load_error', defaultValue: "Level could not be loaded.")
+ @$el.html('
' + msg + '
')
+ @saveScreenshot = _.throttle @saveScreenshot, 30000
+
+ setLevel: (@level, @supermodel) ->
+ @god?.level = @level.serialize @supermodel
+ @initWorld()
+
+ getRenderData: ->
+ c = super()
+ c.world = @world
+ c
+
+ afterRender: ->
+ window.onPlayLevelViewLoaded? @ # still a hack
+ @loadingScreen = new LoadingScreen(@$el.find('canvas')[0])
+ @loadingScreen.show()
+ super()
+
+ onReadyToInitWorld: =>
+ @session = @levelLoader.session
+ @level = @levelLoader.level
+ @loadingScreen.destroy()
+ @initWorld()
+ @initSurface()
+ @initGod()
+ @initGoalManager()
+ @initScriptManager()
+ @insertSubviews()
+ @initVolume()
+ @session.on 'change:multiplayer', @onMultiplayerChanged
+ @originalSessionState = _.cloneDeep(@session.get('state'))
+ @register()
+ @controlBar.setBus(@bus)
+ @surface.showLevel()
+
+ onSupermodelLoadedOne: =>
+ @modelsLoaded ?= 0
+ @modelsLoaded += 1
+ @updateInitString()
+
+ updateInitString: ->
+ return if @surface
+ @modelsLoaded ?= 0
+ canvas = @$el.find('#surface')[0]
+ ctx = canvas.getContext('2d')
+ ctx.font="20px Georgia"
+ ctx.clearRect(0, 0, canvas.width, canvas.height)
+ ctx.fillText("Loaded #{@modelsLoaded} thingies",50,50)
+
+ insertSubviews: ->
+ @insertSubView @tome = new TomeView levelID: @levelID, session: @session, thangs: @world.thangs
+ @insertSubView new PlaybackView {}
+ @insertSubView new GoalsView {}
+ @insertSubView new HUDView {}
+ @insertSubView new ChatView levelID: @levelID, sessionID: @session.id, session: @session
+ worldName = @level.get('i18n')?[me.lang()]?.name ? @level.get('name')
+ @controlBar = @insertSubView new ControlBarView {worldName: worldName, session: @session, level: @level}
+ #Backbone.Mediator.publish('level-set-debug', debug: true) if me.displayName() is 'Nick!'
+
+ afterInsert: ->
+ super()
+# @showWizardSettingsModal() if not me.get('name')
+
+ # callbacks
+
+ onLevelReloadFromData: (e) ->
+ isReload = Boolean @world
+ @setLevel e.level, e.supermodel
+ if isReload
+ @scriptManager.setScripts(e.level.get('scripts'))
+ Backbone.Mediator.publish 'tome:cast-spell' # a bit hacky
+
+ onWindowResize: (s...) ->
+ $('#pointer').css('opacity', 0.0)
+
+ onDisableControls: (e) =>
+ return if e.controls and not ('level' in e.controls)
+ @shortcutsEnabled = false
+ @wasFocusedOn = document.activeElement
+ $('body').focus()
+
+ onEnableControls: (e) =>
+ return if e.controls? and not ('level' in e.controls)
+ @shortcutsEnabled = true
+ $(@wasFocusedOn).focus() if @wasFocusedOn
+ @wasFocusedOn = null
+
+ onDonePressed: => @showVictory()
+
+ onShowVictory: (e) =>
+ console.log 'show vict', e
+ $('#level-done-button').show()
+ @showVictory() if e.showModal
+ setTimeout(@preloadNextLevel, 3000)
+
+ showVictory: ->
+ options = {level: @level, supermodel: @supermodel, session:@session}
+ docs = new VictoryModal(options)
+ @openModalView(docs)
+ window.tracker?.trackEvent 'Saw Victory', level: @world.name, label: @world.name
+
+ onRestartLevel: ->
+ @tome.reloadAllCode()
+ Backbone.Mediator.publish 'level:restarted'
+ $('#level-done-button', @$el).hide()
+ window.tracker?.trackEvent 'Confirmed Restart', level: @world.name, label: @world.name
+
+ onInfiniteLoop: (e) ->
+ return unless e.firstWorld
+ @openModalView new InfiniteLoopModal()
+ window.tracker?.trackEvent 'Saw Initial Infinite Loop', level: @world.name, label: @world.name
+
+ onPlayNextLevel: =>
+ nextLevel = @getNextLevel()
+ nextLevelID = nextLevel.get('slug') or nextLevel.id
+ url = "/play/level/#{nextLevelID}"
+ if @level.get('name') is 'It\'s a Trap!'
+ # A/B test Break the Prison vs. skipping it and going to Taunt
+ skip = Boolean(me.get('testGroupNumber') & 1) # odds
+ window.tracker?.trackEvent 'Skip Break the Prison', skip: skip
+ window.tracker?.identify {skipBreakThePrison: skip}
+ url = '/play/level/taunt' if skip
+
+# application.router.navigate url, trigger: true
+ Backbone.Mediator.publish 'router:navigate', {
+ route: url,
+ viewClass: PlayLevelView,
+ viewArgs: [{supermodel:@supermodel}, nextLevelID]}
+
+ getNextLevel: ->
+ nextLevelOriginal = @level.get('nextLevel')?.original
+ levels = @supermodel.getModels(Level)
+ return l for l in levels when l.get('original') is nextLevelOriginal
+
+ onHighlightDom: (e) =>
+ if e.delay
+ delay = e.delay
+ delete e.delay
+ @pointerInterval = _.delay((=> @onHighlightDom e), delay)
+ return
+ @addPointer()
+ selector = e.selector + ':visible'
+ dom = $(selector)
+ return if parseFloat(dom.css('opacity')) is 0.0
+ offset = dom.offset()
+ return if not offset
+ target_left = offset.left + dom.outerWidth() * 0.5
+ target_top = offset.top + dom.outerHeight() * 0.5
+ body = $('#level-view')
+
+ if e.sides
+ if 'left' in e.sides then target_left = offset.left
+ if 'right' in e.sides then target_left = offset.left + dom.outerWidth()
+ if 'top' in e.sides then target_top = offset.top
+ if 'bottom' in e.sides then target_top = offset.top + dom.outerHeight()
+ else
+ # aim to hit the side if the target is entirely on one side of the screen
+ if offset.left > body.outerWidth()*0.5
+ target_left = offset.left
+ else if offset.left + dom.outerWidth() < body.outerWidth()*0.5
+ target_left = offset.left + dom.outerWidth()
+
+ # aim to hit the bottom or top if the target is entirely on the top or bottom of the screen
+ if offset.top > body.outerWidth()*0.5
+ target_top = offset.top
+ else if offset.top + dom.outerHeight() < body.outerHeight()*0.5
+ target_top = offset.top + dom.outerHeight()
+
+ if e.offset
+ target_left += e.offset.x
+ target_top += e.offset.y
+
+ @pointerRadialDistance = -47 # - Math.sqrt(Math.pow(dom.outerHeight()*0.5, 2), Math.pow(dom.outerWidth()*0.5))
+ @pointerRotation = e.rotation ? Math.atan2(body.outerWidth()*0.5 - target_left, target_top - body.outerHeight()*0.5)
+ pointer = $('#pointer')
+ pointer
+ .css('opacity', 1.0)
+ .css('transition', 'none')
+ .css('transform', "rotate(#{@pointerRotation}rad) translate(-3px, #{@pointerRadialDistance}px)")
+ .css('top', target_top - 50)
+ .css('left', target_left - 50)
+ setTimeout((=>
+ @animatePointer()
+ clearInterval(@pointerInterval)
+ @pointerInterval = setInterval(@animatePointer, 1200)
+ ), 1)
+
+
+ animatePointer: =>
+ pointer = $('#pointer')
+ pointer.css('transition', 'all 0.6s ease-out')
+ pointer.css('transform', "rotate(#{@pointerRotation}rad) translate(-3px, #{@pointerRadialDistance-50}px)")
+ setTimeout((=>
+ pointer.css('transform', "rotate(#{@pointerRotation}rad) translate(-3px, #{@pointerRadialDistance}px)").css('transition', 'all 0.4s ease-in')), 800)
+
+ onFocusDom: (e) => $(e.selector).focus()
+
+ onEndHighlight: =>
+ $('#pointer').css('opacity', 0.0)
+ clearInterval(@pointerInterval)
+
+ onMultiplayerChanged: (e) =>
+ if @session.get('multiplayer')
+ @bus.connect()
+ else
+ @bus.removeFirebaseData =>
+ @bus.disconnect()
+
+ # initialization
+
+ addPointer: ->
+ p = $('#pointer')
+ return if p.length
+ @$el.append($('
'))
+
+ initWorld: ->
+ @world ?= new World @level.get('name')
+ serializedLevel = @level.serialize(@supermodel)
+ @world.loadFromLevel serializedLevel, false
+ @setTeam @world.teamForPlayer 1 # We don't know which player we are; this will go away--temp TODO
+
+ initSurface: ->
+ surfaceCanvas = $('canvas#surface', @$el)
+ @surface = new Surface(@world, surfaceCanvas, thangTypes: @supermodel.getModels(ThangType), playJingle: not @isEditorPreview)
+ worldBounds = @world.getBounds()
+ bounds = [{x:worldBounds.left, y:worldBounds.top}, {x:worldBounds.right, y:worldBounds.bottom}]
+ @surface.camera.setBounds(bounds)
+ @surface.camera.zoomTo({x:0, y:0}, 0.1, 0)
+
+ initGod: ->
+ @god = new God @world, @level.serialize @supermodel
+
+ initGoalManager: ->
+ @goalManager = new GoalManager(@world)
+ @god.goalManager = @goalManager
+
+ initScriptManager: ->
+ @scriptManager = new ScriptManager({scripts: @world.scripts or [], view:@, session: @session})
+ @scriptManager.loadFromSession()
+
+ initVolume: ->
+ volume = me.get('volume')
+ volume = 1.0 unless volume?
+ Backbone.Mediator.publish 'level-set-volume', volume: volume
+
+ onSurfaceSetUpNewWorld: ->
+ return if @alreadyLoadedState
+ @alreadyLoadedState = true
+ state = @originalSessionState
+ if state.frame
+ Backbone.Mediator.publish 'level-set-time', { time: 0, frameOffset: state.frame }
+ if state.selected
+ # TODO: Should also restore selected spell here by saving spellName
+ Backbone.Mediator.publish 'level-select-sprite', { thangID: state.selected, spellName: null }
+ if state.playing?
+ Backbone.Mediator.publish 'level-set-playing', { playing: state.playing }
+
+ preloadNextLevel: =>
+ # TODO: Loading models in the middle of gameplay causes stuttering. Most of the improvement in loading time is simply from passing the supermodel from this level to the next, but if we can find a way to populate the level early without it being noticeable, that would be even better.
+# return if @destroyed
+# return if @preloaded
+# nextLevel = @getNextLevel()
+# @supermodel.populateModel nextLevel
+# @preloaded = true
+
+ register: ->
+ @bus = LevelBus.get(@levelID, @session.id)
+ @bus.setSession(@session)
+ @bus.connect() if @session.get('multiplayer')
+
+ onBusConnected: ->
+ # Need to set this team stuff before the Tome loads... let's think about this with Scott.
+ #@setTeam @world.teamForPlayer(@bus.countPlayers()) unless me.team
+ #$('#multiplayer-team-selection').toggle(@world.playableTeams.length > 1)
+ # .find('input').attr('checked', -> $(@).val() is me.team)
+ # .bind('change', @setTeam)
+
+ onSessionWillSave: (e) ->
+ # Something interesting has happened, so (at a lower frequency), we'll save a screenshot.
+ @saveScreenshot e.session
+
+ # Throttled
+ saveScreenshot: (session) =>
+ return unless screenshot = @surface?.screenshot()
+ session.save {screenshot: screenshot}, {patch: true}
+
+ setTeam: (team) ->
+ team = $(@).val() unless _.isString team
+ me.team = team
+ Backbone.Mediator.publish 'level:team-set', team: team
+
+ destroy: ->
+ super()
+ @levelLoader.destroy()
+ @surface?.destroy()
+ @god?.destroy()
+ @goalManager?.destroy()
+ @scriptManager?.destroy()
+ $(window).off('resize', @onWindowResize)
+
+ clearInterval(@pointerInterval)
+ @bus?.destroy()
+ #@instance.save() unless @instance.loading
+ console.profileEnd?() if PROFILE_ME
diff --git a/app/views/play_view.coffee b/app/views/play_view.coffee
new file mode 100644
index 000000000..4dfbcaee3
--- /dev/null
+++ b/app/views/play_view.coffee
@@ -0,0 +1,179 @@
+View = require 'views/kinds/RootView'
+template = require 'templates/play'
+
+module.exports = class PlayView extends View
+ id: "play-view"
+ template: template
+
+ getRenderData: (context={}) =>
+ context = super(context)
+ context.home = true
+
+ tutorials = [
+ {
+ name: 'Rescue Mission'
+ id: 'rescue-mission'
+ description: "Tharin has been captured!"
+ image: '/file/db/level/52740644904ac0411700067c/rescue_mission_icon.png'
+ difficulty: 1
+ }
+ {
+ name: 'Grab the Mushroom'
+ difficulty: 1
+ image: '/file/db/level/529662dfe0df8f0000000007/grab_the_mushroom_icon.png'
+ id: 'grab-the-mushroom'
+ description: "Grab a powerup and smash a big ogre."
+ }
+ {
+ name: 'Drink Me'
+ difficulty: 1
+ image: '/file/db/level/525dc5589a0765e496000006/drink_me_icon.png'
+ id: 'drink-me'
+ description: "Drink up and slay two munchkins."
+ }
+ {
+ name: 'Taunt the Guards'
+ difficulty: 1
+ image: '/file/db/level/5276c9bdcf83207a2801ff8f/taunt_icon.png'
+ id: 'taunt-the-guards'
+ description: "Tharin, if clever, can escape with Phoebe."
+ }
+ {
+ name: "It's a Trap"
+ difficulty: 1
+ image: '/file/db/level/528aea2d7f37fc4e0700016b/its_a_trap_icon.png'
+ id: 'its-a-trap'
+ description: "Organize a dungeon ambush with archers."
+ }
+ {
+ name: 'Break the Prison'
+ difficulty: 1
+ image: '/file/db/level/5275272c69abdcb12401216e/break_the_prison_icon.png'
+ id: 'break-the-prison'
+ description: "More comrades are imprisoned!"
+ }
+ {
+ name: 'Taunt'
+ difficulty: 1
+ image: '/file/db/level/525f150306e1ab0962000018/taunt_icon.png'
+ id: 'taunt'
+ description: "Taunt the ogre to claim victory."
+ }
+ {
+ name: 'Cowardly Taunt'
+ difficulty: 1
+ image: '/file/db/level/525abfd9b12777d78e000009/cowardly_taunt_icon.png'
+ id: 'cowardly-taunt'
+ description: "Lure infuriated ogres to their doom."
+ }
+ {
+ name: 'Commanding Followers'
+ difficulty: 1
+ image: '/file/db/level/525ef8ef06e1ab0962000003/commanding_followers_icon.png'
+ id: 'commanding-followers'
+ description: "Lead allied soldiers into battle."
+ }
+ {
+ name: 'Mobile Artillery'
+ difficulty: 1
+ image: '/file/db/level/525085419851b83f4b000001/mobile_artillery_icon.png'
+ id: 'mobile-artillery'
+ description: "Blow ogres up!"
+ }
+ ]
+
+ experienced = [
+ {
+ name: 'Hunter Triplets'
+ id: 'hunter-triplets'
+ description: "Three soldiers go ogre hunting."
+ image: '/file/db/level/526711d9add4f8965f000002/hunter_triplets_icon.png'
+ difficulty: 2
+ }
+ {
+ name: 'Emphasis on Aim'
+ difficulty: 2
+ image: '/file/db/level/525f384d96cd77000000000f/munchkin_masher_icon.png'
+ id: 'emphasis-on-aim'
+ description: "Chose your targets carefully."
+ }
+ {
+ name: 'Zone of Danger'
+ id: 'zone-of-danger'
+ description: "Target the ogres swarming into arrow range."
+ image: '/file/db/level/526ae95c1e5cd30000000008/zone_of_danger_icon.png'
+ difficulty: 3
+ }
+ {
+ name: 'Molotov Medic'
+ difficulty: 2
+ image: '/file/db/level/52602ecb026e8481e7000001/generic_1.png'
+ id: 'molotov-medic'
+ description: "Tharin must play support in this dungeon battle."
+ }
+# {
+# name: 'The Herd'
+# id: 'the-herd'
+# description: "Stop an ogre stampede with deadly artillery."
+# image: '/images/generic-icon.png'
+# difficulty: 3
+# disabled: true
+# }
+ {
+ name: 'Gridmancer'
+ id: 'gridmancer'
+ description: "Challenge! Beat this level, get a job!"
+ image: '/file/db/level/52ae2460ef42c52f13000008/gridmancer_icon.png'
+ difficulty: 5
+ }
+ ]
+
+# arenas = [
+# {
+# name: 'Forest Arena'
+# difficulty: 3
+# id: 'forest-arena'
+# image: '/images/levels/forest_arena_icon.png'
+# description: "Play head-to-head against friends!"
+# disabled: true
+# }
+# ]
+
+ playerCreated = [
+ {
+ name: 'Extra Extrapolation'
+ difficulty: 2
+ id: 'extra-extrapolation'
+ image: '/file/db/level/526bda3fe79aefde2a003e36/mobile_artillery_icon.png'
+ description: "Predict your target's position for deadly aim. - by Sootn"
+ }
+ {
+ name: 'The Right Route'
+ difficulty: 1
+ id: 'the-right-route'
+ image: '/file/db/level/526fd3043c637ece50001bb2/the_herd_icon.png'
+ description: "Strike at the weak point in an array of enemies. - by Aftermath"
+ }
+ {
+ name: 'Enemy Artillery'
+ difficulty: 1
+ id: 'enemy-artillery'
+ image: '/file/db/level/526dba94a188322044000a40/mobile_artillery_icon.png'
+ description: "Take cover while shells fly, then strike! - by mcdavid1991"
+ disabled: true
+ }
+ ]
+
+ context.campaigns = [
+ {id: "beginner", name: "Beginner Campaign", description: "... in which you learn the wizardry of programming.", levels: tutorials}
+ {id: "dev", name: "Random Harder Levels", description: "... in which you learn the interface while doing something a little harder.", levels: experienced}
+# {id: "multiplayer", name: "Multiplayer Arenas", description: "... in which you code head-to-head against other players.", levels: arenas}
+ {id: "player_created", name: "Player-Created", description: "... in which you battle against the creativity of your fellow
Artisan Wizards.", levels: playerCreated}
+ ]
+
+ context
+
+ afterRender: ->
+ super()
+ @$el.find('.modal').on 'shown', ->
+ $('input:visible:first', @).focus()
diff --git a/app/views/sprite_parser_test_view.coffee b/app/views/sprite_parser_test_view.coffee
new file mode 100644
index 000000000..a4fb5fbf1
--- /dev/null
+++ b/app/views/sprite_parser_test_view.coffee
@@ -0,0 +1,22 @@
+View = require 'views/kinds/RootView'
+template = require 'templates/editor/thang/sprite_parser_test'
+SpriteParser = require 'lib/sprites/SpriteParser'
+mixed_samples = require 'lib/sprites/parser_samples'
+samples = require 'lib/sprites/parser_samples_artillery'
+ThangType = require 'models/ThangType'
+
+module.exports = class SpriteParserTestView extends View
+ id: "sprite-parser-test-view"
+ template: template
+
+ afterRender: ->
+ @parse samples
+
+ parse: (samples) ->
+ thangType = new ThangType()
+ for sample in _.shuffle samples
+ parser = new SpriteParser(thangType)
+ parser.parse(sample)
+ console.log "thang type is now", thangType
+ console.log JSON.stringify(thangType).length
+# console.log JSON.stringify(thangType.attributes.raw.animations.tharin_defend.tweens)
\ No newline at end of file
diff --git a/app/views/teachers_view.coffee b/app/views/teachers_view.coffee
new file mode 100644
index 000000000..b3fbc77ba
--- /dev/null
+++ b/app/views/teachers_view.coffee
@@ -0,0 +1,6 @@
+View = require 'views/kinds/RootView'
+template = require 'templates/teachers'
+
+module.exports = class TeachersView extends View
+ id: "teachers-view"
+ template: template
diff --git a/bin/coco-brunch b/bin/coco-brunch
new file mode 100755
index 000000000..f26edd21f
--- /dev/null
+++ b/bin/coco-brunch
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+ulimit -n 10000
+cd ~/Desktop/coco
+until brunch w --config brunch.coffee; do
+ echo "Brunch Crashed. Recompiling entire project. Press Ctrl-C quickly to quit." >&2
+ sleep 1
+done
diff --git a/bin/coco-client-test-runner b/bin/coco-client-test-runner
new file mode 100755
index 000000000..fa88b51b6
--- /dev/null
+++ b/bin/coco-client-test-runner
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+sleep 5
+cd ~/Desktop/coco
+until node_modules/karma/bin/karma start; do
+ echo "Karma crashed with exit code $?. Respawning.." >&2
+ sleep 1
+done
diff --git a/bin/coco-dev-server b/bin/coco-dev-server
new file mode 100755
index 000000000..edd4aaac1
--- /dev/null
+++ b/bin/coco-dev-server
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+cd ~/Desktop/coco
+node_modules/.bin/nodemon . --ext ".coffee|.js" --watch server --watch app.js --watch server_config.js
+# https://github.com/remy/nodemon/issues/145 prevents us from going to 0.7.6+ until we understand what is going on
diff --git a/bin/coco-mongodb b/bin/coco-mongodb
new file mode 100755
index 000000000..dd82e5d28
--- /dev/null
+++ b/bin/coco-mongodb
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+mkdir -p ~/Desktop/coco/mongo
+mongod --setParameter textSearchEnabled=true --dbpath ~/Desktop/coco/mongo
diff --git a/bin/coco-server-test-runner b/bin/coco-server-test-runner
new file mode 100755
index 000000000..703db7f94
--- /dev/null
+++ b/bin/coco-server-test-runner
@@ -0,0 +1,5 @@
+#!/bin/bash
+
+#sleep 5
+cd ~/Desktop/coco
+node_modules/jasmine-node/bin/jasmine-node test/server/ --coffee --autotest --captureExceptions
diff --git a/bin/coco-test-server b/bin/coco-test-server
new file mode 100755
index 000000000..371a76312
--- /dev/null
+++ b/bin/coco-test-server
@@ -0,0 +1,4 @@
+#!/bin/bash
+
+cd ~/Desktop/coco
+node_modules/.bin/nodemon . --ext ".coffee|.js" --watch server --watch server.coffee --unittest
diff --git a/bin/coco-update-createjs b/bin/coco-update-createjs
new file mode 100755
index 000000000..5cd3f9140
--- /dev/null
+++ b/bin/coco-update-createjs
@@ -0,0 +1,61 @@
+# automatically builds the latest versions of the CreateJS suite
+# and puts them into vendor/scripts.
+
+# for this to work:
+# 1. create the CreateJS directory in your Desktop
+# 2. git clone all four suites into that directory (ex. https://github.com/CreateJS/PreloadJS.git)
+# 3. npm install grunt-cli for each, or add the -g options to run globally once
+
+# then you can run this script to build the latest version whenever
+# see the build/README file in any of these libraries on GitHub for more info
+# https://github.com/CreateJS/EaselJS/blob/master/build/README.md
+
+cd ~/Desktop/CreateJS/EaselJS/
+echo updating Easel
+git checkout .
+git pull
+sudo npm update
+cd build
+sudo npm update
+echo building Easel
+grunt combine
+
+cd ~/Desktop/CreateJS/PreloadJS/
+echo updating Preload
+git checkout .
+git pull
+sudo npm update
+cd build
+sudo npm install
+sudo npm update
+echo building Preload
+grunt combine
+
+cd ~/Desktop/CreateJS/SoundJS/
+echo updating Sound
+git checkout .
+git pull
+sudo npm update
+cd build
+sudo npm install
+sudo npm update
+echo building Sound
+grunt combine
+
+cd ~/Desktop/CreateJS/TweenJS/
+echo updating Tween
+git checkout .
+git pull
+sudo npm update
+cd build
+sudo npm install
+sudo npm update
+echo building Tween
+grunt combine
+
+echo moving to CoCo
+cp ~/Desktop/CreateJS/EaselJS/build/output/easeljs-NEXT.combined.js ~/Desktop/coco/vendor/scripts
+cp ~/Desktop/CreateJS/EaselJS/build/output/movieclip-NEXT.min.js ~/Desktop/coco/vendor/scripts
+cp ~/Desktop/CreateJS/SoundJS/build/output/soundjs-NEXT.combined.js ~/Desktop/coco/vendor/scripts
+cp ~/Desktop/CreateJS/PreloadJS/build/output/preloadjs-NEXT.combined.js ~/Desktop/coco/vendor/scripts
+cp ~/Desktop/CreateJS/TweenJS/build/output/tweenjs-NEXT.combined.js ~/Desktop/coco/vendor/scripts
diff --git a/bin/coco-update-treema b/bin/coco-update-treema
new file mode 100755
index 000000000..163d3e8e9
--- /dev/null
+++ b/bin/coco-update-treema
@@ -0,0 +1,3 @@
+cd ~/Desktop/coco
+cp ~/Desktop/treema/treema.js ./vendor/scripts/
+cp ~/Desktop/treema/treema.css ./vendor/styles/
diff --git a/bower.json b/bower.json
new file mode 100644
index 000000000..065e831a5
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,52 @@
+{
+ "name": "codecombat",
+ "main": "index.js",
+ "version": "0.0.0",
+ "homepage": "https://github.com/codecombat/codecombat",
+ "authors": [
+ "CodeCombat
"
+ ],
+ "description": "A multiplayer programming game for learning how to code.",
+ "keywords": [
+ "learning",
+ "live",
+ "coding",
+ "game",
+ "multiplayer"
+ ],
+ "license": "MIT",
+ "private": true,
+ "ignore": [
+ "**/.*",
+ "node_modules",
+ "bower_components",
+ "vendor",
+ "test"
+ ],
+ "dependencies": {
+ "jquery": "~2.0.3",
+ "lodash": "~2.4.1",
+ "backbone": "~1.1.0",
+ "jquery-mousewheel": "~3.1.9",
+ "i18next": "~1.7.1",
+ "firepad": "~0.1.2",
+ "marked": "~0.3.0",
+ "moment": "~2.5.0",
+ "aether": "~0.0.1",
+ "underscore.string": "~2.3.3"
+ },
+ "overrides": {
+ "backbone": {
+ "main": "backbone.js"
+ },
+ "lodash": {
+ "main": "dist/lodash.js"
+ },
+ "marked": {
+ "main": "lib/marked.js"
+ },
+ "underscore.string": {
+ "main": "lib/underscore.string.js"
+ }
+ }
+}
diff --git a/brunch.coffee b/brunch.coffee
new file mode 100644
index 000000000..ba9f12ba5
--- /dev/null
+++ b/brunch.coffee
@@ -0,0 +1,81 @@
+sysPath = require 'path'
+startsWith = (string, substring) ->
+ string.lastIndexOf(substring, 0) is 0
+
+exports.config =
+ server:
+ path: 'server.coffee'
+ paths:
+ 'public': 'public'
+ conventions:
+ ignored: (path) -> startsWith(sysPath.basename(path), '_')
+ workers:
+ enabled: false # turned out to be much, much slower than without workers
+ files:
+ javascripts:
+ defaultExtension: 'coffee'
+ joinTo:
+ 'javascripts/world.js': /^((app(\/|\\)lib(\/|\\)world(?!(\/|\\)test))|(app(\/|\\)lib(\/|\\)CocoClass.coffee)|(app(\/|\\)lib(\/|\\)utils.coffee)|(vendor(\/|\\)scripts(\/|\\)Box2dWeb-2.1.a.3)|(bower_components(\/|\\)lodash(\/|\\)dist(\/|\\)lodash.js)|(vendor(\/|\\)scripts(\/|\\)string_score.js)|(bower_components(\/|\\)aether(\/|\\)build(\/|\\)aether.js))/
+ 'javascripts/app.js': /^app/
+ 'javascripts/vendor.js': /^(vendor(\/|\\)(?!(scripts\/Box2d|scripts\/box2d))|bower_components)/
+ 'javascripts/vendor_with_box2d.js': /^(vendor(\/|\\)(?!scripts\/box2d)|bower_components)/ # include box2dweb for profiling (and for IE9...)
+ 'test/javascripts/test.js': /^test(\/|\\)(?!vendor)/
+ 'test/javascripts/test-vendor.js': /^test(\/|\\)(?=vendor)/
+ order:
+ before: [
+ 'bower_components/jquery/jquery.js'
+ 'bower_components/lodash/dist/lodash.js'
+ 'bower_components/backbone/backbone.js'
+ # Twitter Bootstrap jquery plugins
+ 'vendor/scripts/bootstrap/bootstrap-transition.js'
+ 'vendor/scripts/bootstrap/bootstrap-affix.js'
+ 'vendor/scripts/bootstrap/bootstrap-alert.js'
+ 'vendor/scripts/bootstrap/bootstrap-button.js'
+ 'vendor/scripts/bootstrap/bootstrap-carousel.js'
+ 'vendor/scripts/bootstrap/bootstrap-collapse.js'
+ 'vendor/scripts/bootstrap/bootstrap-dropdown.js'
+ 'vendor/scripts/bootstrap/bootstrap-modal.js'
+ 'vendor/scripts/bootstrap/bootstrap-scrollspy.js'
+ 'vendor/scripts/bootstrap/bootstrap-tab.js'
+ 'vendor/scripts/bootstrap/bootstrap-tooltip.js'
+ 'vendor/scripts/bootstrap/bootstrap-popover.js'
+ 'vendor/scripts/bootstrap/bootstrap-typeahead.js'
+ # CreateJS dependencies
+ 'vendor/scripts/easeljs-NEXT.combined.js'
+ 'vendor/scripts/preloadjs-NEXT.combined.js'
+ 'vendor/scripts/soundjs-NEXT.combined.js'
+ 'vendor/scripts/tweenjs-NEXT.combined.js'
+ 'vendor/scripts/movieclip-NEXT.min.js'
+
+ # Aether before box2d for some strange Object.defineProperty thing
+ 'bower_components/aether/build/aether.js'
+ ]
+ stylesheets:
+ defaultExtension: 'sass'
+ joinTo:
+ 'stylesheets/app.css': /^(app|vendor)/
+ order:
+ before: ['app/styles/bootstrap.scss']
+ templates:
+ defaultExtension: 'jade'
+ joinTo: 'javascripts/app.js'
+ framework: 'backbone'
+
+ plugins:
+ coffeelint:
+ pattern: /^app\/.*\.coffee$/
+ options:
+ line_endings:
+ value: "unix"
+ level: "error"
+ max_line_length:
+ level: "ignore"
+ no_trailing_whitespace:
+ level: "ignore" # PyCharm can't just autostrip for .coffee, needed for .jade
+ no_unnecessary_fat_arrows:
+ level: "ignore"
+ # https://gist.github.com/toolmantim/4958966
+ uglify:
+ output:
+ beautify: true
+ indent_level: 0
diff --git a/index.js b/index.js
new file mode 100644
index 000000000..315ac98f8
--- /dev/null
+++ b/index.js
@@ -0,0 +1,3 @@
+require('coffee-script');
+var app = require('./server');
+app.startServer();
diff --git a/karma.conf.js b/karma.conf.js
new file mode 100644
index 000000000..7b8df7609
--- /dev/null
+++ b/karma.conf.js
@@ -0,0 +1,83 @@
+// Testacular configuration
+// Generated on Fri Feb 15 2013 18:38:33 GMT-0500 (EST)
+
+
+// base path, that will be used to resolve files and exclude
+basePath = '.';
+
+
+// list of files / patterns to load in the browser
+files = [
+ JASMINE,
+ JASMINE_ADAPTER,
+
+ 'public/javascripts/vendor.js',
+ 'public/lib/ace/ace.js',
+ 'public/javascripts/app.js',
+
+ 'test/app/**/*.coffee'
+];
+
+
+// list of files to exclude
+exclude = [
+
+];
+
+
+// test results reporter to use
+// possible values: 'dots', 'progress', 'junit'
+reporters = ['progress', 'coverage'];
+//reporters = ['progress'];
+
+
+// web server port
+port = 9050;
+
+
+// cli runner port
+runnerPort = 9051;
+
+
+// enable / disable colors in the output (reporters and logs)
+colors = true;
+
+
+// level of logging
+// possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
+logLevel = LOG_INFO;
+
+
+// enable / disable watching file and executing tests whenever any file changes
+autoWatch = true;
+
+
+// Start these browsers, currently available:
+// - Chrome
+// - ChromeCanary
+// - Firefox
+// - Opera
+// - Safari (only Mac)
+// - PhantomJS
+// - IE (only Windows)
+browsers = ['Chrome'];
+
+
+// If browser does not capture in given timeout [ms], kill it
+captureTimeout = 5000;
+
+
+// Continuous Integration mode
+// if true, it capture browsers, run tests and exit
+singleRun = false;
+
+
+preprocessors = {
+ '**/*.coffee': 'coffee',
+ '**/javascripts/app.js': 'coverage'
+};
+
+coverageReporter = {
+ type : 'html',
+ dir : 'coverage/'
+};
diff --git a/package.json b/package.json
new file mode 100644
index 000000000..1527dfc5a
--- /dev/null
+++ b/package.json
@@ -0,0 +1,87 @@
+{
+ "name": "codecombat",
+ "description": "A multiplayer programming game for learning how to code.",
+ "author": "Nick Winter ",
+ "homepage": "https://github.com/codecombat/codecombat",
+ "domains": [
+ "codecombat.com"
+ ],
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/codecombat/codecombat"
+ },
+ "contributors": [
+ {
+ "name": "Nick Winter",
+ "email": "nick@codecombat.com"
+ },
+ {
+ "name": "Scott Erickson",
+ "email": "scott@codecombat.com"
+ },
+ {
+ "name": "George Saines",
+ "email": "george@codecombat.com"
+ }
+ ],
+ "scripts": {
+ "start": "node ./index.js",
+ "test": "brunch test",
+ "predeploy": "echo Starting deployment--hold onto your butts.; echo Skipping brunch build --production",
+ "postdeploy": "echo Deployed. Unclench."
+ },
+ "main": "index.js",
+ "keywords": [
+ "learning",
+ "live coding",
+ "game",
+ "multiplayer"
+ ],
+ "dependencies": {
+ "express": "~3.0.6",
+ "winston": "0.6.x",
+ "passport": "0.1.x",
+ "passport-local": "0.1.x",
+ "mongodb": "1.2.x",
+ "mongoose": "3.6.x",
+ "mongoose-text-search": "~0.0.2",
+ "request": "2.12.x",
+ "tv4": "",
+ "lodash": "~2.0.0",
+ "underscore.string": "2.3.x",
+ "async": "0.2.x",
+ "connect": "2.7.x",
+ "nodemailer": "0.4.x",
+ "coffee-script": "",
+ "graceful-fs": "~2.0.1",
+ "node-force-domain": "~0.1.0",
+ "mailchimp-api": "",
+ "express-useragent": "~0.0.9",
+ "gridfs-stream": ""
+ },
+ "devDependencies": {
+ "jade": "0.33.x",
+ "javascript-brunch": "> 1.0 < 1.8",
+ "coffee-script-brunch": "> 1.0 < 1.8",
+ "coffeelint-brunch": "> 1.0 < 1.8",
+ "sass-brunch": "> 1.0 < 1.8",
+ "css-brunch": "> 1.0 < 1.8",
+ "jade-brunch": "> 1.0 < 1.8",
+ "uglify-js-brunch": "~1.7.4",
+ "clean-css-brunch": "> 1.0 < 1.8",
+ "auto-reload-brunch": "> 1.0 < 1.8",
+ "brunch": "~1.7.4",
+ "karma": "~0.8.0",
+ "jasmine-node": "",
+ "nodemon": "0.7.5",
+ "marked": "0.2.x",
+ "telepath-brunch": "https://github.com/nwinter/telepath-brunch/tarball/master",
+ "bower": "~1.2.8",
+ "bless-brunch": "~1.6.1"
+ },
+ "license": "Copyright © 2014 CodeCombat",
+ "private": true,
+ "engines": {
+ "node": "0.10.x"
+ }
+}
diff --git a/scripts/devSetup/.gitignore b/scripts/devSetup/.gitignore
new file mode 100644
index 000000000..4a298ae2c
--- /dev/null
+++ b/scripts/devSetup/.gitignore
@@ -0,0 +1 @@
+codecombat/
diff --git a/scripts/devSetup/bin/startApp.py b/scripts/devSetup/bin/startApp.py
new file mode 100644
index 000000000..6cd56523a
--- /dev/null
+++ b/scripts/devSetup/bin/startApp.py
@@ -0,0 +1,12 @@
+__author__ = u'schmatz'
+from subprocess import call
+import os
+import sys
+#TODO: Upgrade this so it works on windows
+#These scripts will be placed in coco/bin
+current_directory = os.path.dirname(os.path.realpath(sys.argv[0]))
+coco_path = os.getenv("COCO_DIR",os.path.join(current_directory,os.pardir))
+nodemon_path = coco_path + os.sep + "node_modules" + os.sep + ".bin" + os.sep + "nodemon"
+
+call(nodemon_path + " . --ext \".coffee|.js\" --watch server --watch app.js --watch server_config.js",shell=True,cwd=coco_path)
+
diff --git a/scripts/devSetup/bin/startBrunch.py b/scripts/devSetup/bin/startBrunch.py
new file mode 100644
index 000000000..dfd894ccd
--- /dev/null
+++ b/scripts/devSetup/bin/startBrunch.py
@@ -0,0 +1,16 @@
+__author__ = u'schmatz'
+from subprocess import call
+import subprocess
+import os
+import time
+import sys
+#TODO: Upgrade this so it works on windows
+#These scripts will be placed in coco/bin
+current_directory = os.path.dirname(os.path.realpath(sys.argv[0]))
+coco_path = os.getenv("COCO_DIR",os.path.join(current_directory,os.pardir))
+brunch_path = coco_path + os.sep + "node_modules" + os.sep + ".bin" + os.sep + "brunch"
+subprocess.Popen("ulimit -n 10000",shell=True)
+while True:
+ call(brunch_path + " w --config " + coco_path + os.sep + "brunch.coffee",shell=True,cwd=coco_path)
+ print("Brunch crashed. Press control+C within 1 second to quit.")
+ time.sleep(1)
\ No newline at end of file
diff --git a/scripts/devSetup/bin/startDatabase.py b/scripts/devSetup/bin/startDatabase.py
new file mode 100644
index 000000000..6552f5fd3
--- /dev/null
+++ b/scripts/devSetup/bin/startDatabase.py
@@ -0,0 +1,84 @@
+__author__ = u'schmatz'
+from subprocess import call
+import os
+import sys
+import subprocess
+#copied straight from the python 3 standard library
+def which(cmd, mode=os.F_OK | os.X_OK, path=None):
+ """Given a command, mode, and a PATH string, return the path which
+ conforms to the given mode on the PATH, or None if there is no such
+ file.
+
+ `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
+ of os.environ.get("PATH"), or can be overridden with a custom search
+ path.
+
+ """
+ # Check that a given file can be accessed with the correct mode.
+ # Additionally check that `file` is not a directory, as on Windows
+ # directories pass the os.access check.
+ def _access_check(fn, mode):
+ return (os.path.exists(fn) and os.access(fn, mode)
+ and not os.path.isdir(fn))
+
+ # If we're given a path with a directory part, look it up directly rather
+ # than referring to PATH directories. This includes checking relative to the
+ # current directory, e.g. ./script
+ if os.path.dirname(cmd):
+ if _access_check(cmd, mode):
+ return cmd
+ return None
+
+ if path is None:
+ path = os.environ.get("PATH", os.defpath)
+ if not path:
+ return None
+ path = path.split(os.pathsep)
+
+ if sys.platform == "win32":
+ # The current directory takes precedence on Windows.
+ if not os.curdir in path:
+ path.insert(0, os.curdir)
+
+ # PATHEXT is necessary to check on Windows.
+ pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
+ # See if the given file matches any of the expected path extensions.
+ # This will allow us to short circuit when given "python.exe".
+ # If it does match, only test that one, otherwise we have to try
+ # others.
+ if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
+ files = [cmd]
+ else:
+ files = [cmd + ext for ext in pathext]
+ else:
+ # On other platforms you don't have things like PATHEXT to tell you
+ # what file suffixes are executable, so just pass on cmd as-is.
+ files = [cmd]
+
+ seen = set()
+ for dir in path:
+ normdir = os.path.normcase(dir)
+ if not normdir in seen:
+ seen.add(normdir)
+ for thefile in files:
+ name = os.path.join(dir, thefile)
+ if _access_check(name, mode):
+ return name
+ return None
+
+#TODO: Upgrade this so it works on windows
+#These scripts will be placed in coco/bin
+current_directory = os.path.dirname(os.path.realpath(sys.argv[0]))
+if which("mongod") and "v2.5.4" in subprocess.check_output("mongod --version",shell=True):
+ mongo_executable = "mongod"
+else:
+ print("Mongod 2.5.4 wasn't found. Searching in bin directory...")
+
+mongo_directory = current_directory + os.sep + u"mongo"
+mongo_executable = os.environ.get("COCO_MONGOD_PATH",mongo_directory + os.sep + u"mongod")
+mongo_db_path = os.path.abspath(os.path.join(current_directory,os.pardir)) + os.sep + u"mongo"
+if not os.path.exists(mongo_db_path):
+ os.mkdir(mongo_db_path)
+mongo_arguments = [mongo_executable + u" --setParameter textSearchEnabled=true --dbpath=" + mongo_db_path]
+call(mongo_arguments,shell=True)
+
diff --git a/scripts/devSetup/bootstrap.sh b/scripts/devSetup/bootstrap.sh
new file mode 100644
index 000000000..0461f0981
--- /dev/null
+++ b/scripts/devSetup/bootstrap.sh
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+repositoryUrl=https://github.com/nwinter/codecombat.git
+deps=( git python )
+function checkDependencies { #usage: checkDependencies [name of dependency array] [name of error checking function]
+ declare -a dependencyArray=("${!1}")
+ for i in "${dependencyArray[@]}"
+ do
+ command -v $i >/dev/null 2>&1 || { $2 "$i" >&2; exit 1; }
+ done
+}
+
+function basicDependenciesErrorHandling {
+ case "$1" in
+ "python")
+ echo "Python isn't installed. Please install it to continue."
+ read -p "Press enter to open download link..."
+ open http://www.python.org/getit/
+ exit 1
+ ;;
+ "git")
+ echo "Please install Git.(If you're running mac, this is included in the XCode command line tools."
+ esac
+ }
+
+function checkIsRoot {
+ if [[ $EUID -ne 0 ]]; then
+ echo "This script must be run as root (run 'sudo ./$me $installDirectory')" 1>&2
+ exit 1
+ fi
+}
+#checkIsRoot
+checkDependencies deps[@] basicDependenciesErrorHandling
+#install git repository
+git clone https://github.com/schmatz/cocopractice.git coco
+echo "CHANGE LOCATION OF GIT REPO BEFORE RELEASE OR IT WILL NOT WORK!"
+#python ./coco/scripts/devSetup/setup.py
+echo "Now copy and paste 'sudo python ./coco/scripts/devSetup/setup.py' into the terminal!"
\ No newline at end of file
diff --git a/scripts/devSetup/configuration.py b/scripts/devSetup/configuration.py
new file mode 100644
index 000000000..2eb0be39c
--- /dev/null
+++ b/scripts/devSetup/configuration.py
@@ -0,0 +1,19 @@
+__author__ = u'schmatz'
+from systemConfiguration import SystemConfiguration
+import os
+import directoryController
+import errors
+class Configuration(object):
+ def __init__(self):
+ self.system = SystemConfiguration()
+ assert isinstance(self.system,SystemConfiguration)
+ self.directory = directoryController.DirectoryController(self)
+ self.directory.create_base_directories()
+ #self.repository_url = u"https://github.com/nwinter/codecombat.git"
+ self.repository_url = "https://github.com/schmatz/cocopractice.git"
+ @property
+ def mem_width(self):
+ return self.system.virtual_memory_address_width
+
+
+
diff --git a/scripts/devSetup/dependency.py b/scripts/devSetup/dependency.py
new file mode 100644
index 000000000..2cc726e86
--- /dev/null
+++ b/scripts/devSetup/dependency.py
@@ -0,0 +1,13 @@
+__author__ = u'schmatz'
+
+from configuration import Configuration
+
+class Dependency(object):
+ def __init__(self,configuration):
+ assert isinstance(configuration,Configuration)
+ self.config = configuration
+ def download_dependencies(self):
+ raise NotImplementedError
+ def install_dependencies(self):
+ raise NotImplementedError
+
diff --git a/scripts/devSetup/directoryController.py b/scripts/devSetup/directoryController.py
new file mode 100644
index 000000000..b6cd7fd8d
--- /dev/null
+++ b/scripts/devSetup/directoryController.py
@@ -0,0 +1,50 @@
+__author__ = u'schmatz'
+import configuration
+import os
+import sys
+import errors
+import shutil
+class DirectoryController(object):
+ def __init__(self,config):
+ assert isinstance(config,configuration.Configuration)
+ self.config = config
+ self.root_dir = self.config.system.get_current_working_directory()
+
+ @property
+ def root_install_directory(self):
+ #return self.root_dir + os.sep + u"codecombat"
+ return self.root_dir + os.sep + "coco" + os.sep + "bin"
+ @property
+ def tmp_directory(self):
+ return self.root_install_directory + os.sep + u"tmp"
+ @property
+ def bin_directory(self):
+ return self.root_install_directory
+
+ def create_directory_in_tmp(self,subdirectory):
+ os.mkdir(self.generate_path_for_directory_in_tmp(subdirectory))
+
+ def generate_path_for_directory_in_tmp(self,subdirectory):
+ return self.tmp_directory + os.sep + subdirectory
+ def create_directory_in_bin(self,subdirectory):
+ full_path = self.bin_directory + os.sep + subdirectory
+ os.mkdir(full_path)
+
+ def create_base_directories(self):
+ #first create the directory for the development environment to be installed in
+ try:
+ #os.mkdir(self.root_install_directory)
+ #then the tmp directory for file downloads and the like
+ os.mkdir(self.tmp_directory)
+ #then the bin directory for binaries(also includes binaries for dependencies?
+ #os.mkdir(self.bin_directory)
+ except:
+ #cleanup whatever we created
+ #self.remove_directories()
+ raise errors.CoCoError(u"There was an error creating the directory structure, do you have correct permissions? Please remove all and start over.")
+
+
+ def remove_directories(self):
+ print u"Removed directories created!"
+ shutil.rmtree(self.tmp_directory)
+ #shutil.rmtree(self.root_install_directory)
diff --git a/scripts/devSetup/downloader.py b/scripts/devSetup/downloader.py
new file mode 100644
index 000000000..9c016f883
--- /dev/null
+++ b/scripts/devSetup/downloader.py
@@ -0,0 +1,36 @@
+__author__ = 'schmatz'
+from configuration import Configuration
+import urllib
+from dependency import Dependency
+class Downloader:
+ def __init__(self,dependency):
+ assert isinstance(dependency, Dependency)
+ self.dependency = dependency
+ @property
+ def download_directory(self):
+ raise NotImplementedError
+ def download(self):
+ raise NotImplementedError
+ def download_file(self,url,filePath):
+ urllib.urlretrieve(url,filePath,self.__progress_bar_reporthook)
+ def decompress(self):
+ raise NotImplementedError
+ def check_download(self):
+ raise NotImplementedError
+
+ def __progress_bar_reporthook(self,blocknum,blocksize,totalsize):
+ #http://stackoverflow.com/a/13895723/1928667
+ #http://stackoverflow.com/a/3173331/1928667
+ bars_to_display = 70
+ amount_of_data_downloaded_so_far = blocknum * blocksize
+ if totalsize > 0:
+ progress_fraction = float(amount_of_data_downloaded_so_far) / float(totalsize)
+ progress_percentage = progress_fraction * 1e2
+ stringToDisplay = '\r[{0}] {1:.1f}%'.format('#'*int(bars_to_display*progress_fraction),progress_percentage)
+ print stringToDisplay,
+ if amount_of_data_downloaded_so_far >= totalsize:
+ print "\n",
+ else:
+ stringToDisplay = '\r File size unknown. Read {0} bytes.'.format(amount_of_data_downloaded_so_far)
+ print stringToDisplay,
+
diff --git a/scripts/devSetup/errors.py b/scripts/devSetup/errors.py
new file mode 100644
index 000000000..448eb6748
--- /dev/null
+++ b/scripts/devSetup/errors.py
@@ -0,0 +1,19 @@
+__author__ = u'schmatz'
+#TODO: Clean these up
+class CoCoError(Exception):
+ def __init__(self,details):
+ self.details = details
+ def __str__(self):
+ return repr(self.details + u"\n Please contact CodeCombat support, and include this error in your message.")
+
+class NotSupportedError(CoCoError):
+ def __init__(self,details):
+ self.details = details
+ def __str__(self):
+ return repr(self.details + u"\n Please contact CodeCombat support, and include this error in your message.")
+
+class DownloadCorruptionError(CoCoError):
+ def __init__(self,details):
+ self.details = details
+ def __str__(self):
+ return repr(self.details + u"\n Please contact CodeCombat support, and include this error in your message.")
diff --git a/scripts/devSetup/factories.py b/scripts/devSetup/factories.py
new file mode 100644
index 000000000..860f06ce6
--- /dev/null
+++ b/scripts/devSetup/factories.py
@@ -0,0 +1,85 @@
+__author__ = u'schmatz'
+
+import errors
+import configuration
+import mongo
+import node
+import repositoryInstaller
+import ruby
+import shutil
+import os
+import glob
+import subprocess
+def constructSetup():
+ config = configuration.Configuration()
+ if config.system.operating_system == u"mac":
+ print("Mac detected, architecture: " + str(config.system.get_virtual_memory_address_width()) + " bit")
+ return MacSetup(config)
+ elif config.system.operating_system == u"win":
+ print("Windows detected, architecture: " + str(config.system.get_virtual_memory_address_width())+ " bit")
+ raise NotImplementedError("Windows is not supported at this time.")
+ #return WinSetup(config)
+ elif config.system.operating_system == u"linux":
+ print("Linux detected, architecture: " + str(config.system.get_virtual_memory_address_width())+ " bit")
+ return LinuxSetup(config)
+
+class SetupFactory(object):
+ def __init__(self,config):
+ self.config = config
+ self.mongo = mongo.MongoDB(self.config)
+ self.node = node.Node(self.config)
+ self.repoCloner = repositoryInstaller.RepositoryInstaller(self.config)
+ self.ruby = ruby.Ruby(self.config)
+ def setup(self):
+ mongo_version_string = ""
+ try:
+ mongo_version_string = subprocess.check_output("mongod --version",shell=True)
+ except:
+ print("Mongod not found.")
+ if "v2.5.4" not in mongo_version_string:
+ print("MongoDB 2.5.4 not found, so installing...")
+ self.mongo.download_dependencies()
+ self.mongo.install_dependencies()
+ self.node.download_dependencies()
+ self.node.install_dependencies()
+ #self.repoCloner.cloneRepository()
+ self.repoCloner.install_node_packages()
+ self.ruby.install_gems()
+
+ print ("Doing initial bower install...")
+ bower_path = self.config.directory.root_dir + os.sep + "coco" + os.sep + "node_modules" + os.sep + ".bin" + os.sep + "bower"
+ subprocess.call(bower_path + " --allow-root install",shell=True,cwd=self.config.directory.root_dir + os.sep + "coco")
+ print("Copying bin scripts...")
+
+ script_location =self.config.directory.root_dir + os.sep + "coco" + os.sep + "scripts" + os.sep + "devSetup" + os.sep + "bin"
+ #print("Script location: " + script_location)
+ #print("Destination: "+ self.config.directory.root_install_directory)
+ for filename in glob.glob(os.path.join(script_location, '*.*')):
+ shutil.copy(filename, self.config.directory.root_install_directory)
+ print("Removing temporary directories")
+ self.config.directory.remove_directories()
+ print("Changing permissions of files...")
+ #TODO: Make this more robust and portable(doesn't pose security risk though)
+ subprocess.call("chmod -R 0777 " + self.config.directory.root_dir + os.sep + "coco" + os.sep + "bin",shell=True)
+
+ print("Done! If you want to start the server, head into /coco/bin and run ")
+ print("1. sudo python startDatabase.py")
+ print("2. python startBrunch.py")
+ print("3. python startApp.py")
+ print("Once brunch is done, visit http://localhost:3000!")
+ #print self.mongo.bashrc_string()
+ #print self.node.bashrc_string()
+ #print "COCO_DIR="+ self.config.directory.root_dir + os.sep + "coco"
+ def cleanup(self):
+ self.config.directory.remove_directories()
+
+
+class MacSetup(SetupFactory):
+ def setup(self):
+ super(self.__class__, self).setup()
+class WinSetup(SetupFactory):
+ def setup(self):
+ super(self.__class__, self).setup()
+class LinuxSetup(SetupFactory):
+ def setup(self):
+ super(self.__class__, self).setup()
\ No newline at end of file
diff --git a/scripts/devSetup/mongo.py b/scripts/devSetup/mongo.py
new file mode 100644
index 000000000..b6917967f
--- /dev/null
+++ b/scripts/devSetup/mongo.py
@@ -0,0 +1,99 @@
+__author__ = u'schmatz'
+from downloader import Downloader
+import tarfile
+from errors import DownloadCorruptionError
+import warnings
+import os
+from configuration import Configuration
+from dependency import Dependency
+
+class MongoDB(Dependency):
+ def __init__(self,configuration):
+ super(self.__class__, self).__init__(configuration)
+ operating_system = configuration.system.operating_system
+ self.config.directory.create_directory_in_tmp(u"mongo")
+ #self.config.directory.create_directory_in_bin("mongo")
+ if operating_system == u"mac":
+ self.downloader = MacMongoDBDownloader(self)
+ elif operating_system == u"win":
+ self.downloader = WindowsMongoDBDownloader(self)
+ elif operating_system == u"linux":
+ self.downloader = LinuxMongoDBDownloader(self)
+ @property
+ def tmp_directory(self):
+ return self.config.directory.tmp_directory
+ @property
+ def bin_directory(self):
+ return self.config.directory.bin_directory
+
+ def bashrc_string(self):
+ return "COCO_MONGOD_PATH=" + self.config.directory.bin_directory + os.sep + u"mongo" + os.sep +"bin" + os.sep + "mongod"
+
+ def download_dependencies(self):
+ self.downloader.download()
+ self.downloader.decompress()
+ def install_dependencies(self):
+ install_directory = self.config.directory.bin_directory + os.sep + u"mongo"
+ import shutil
+ shutil.copytree(self.findUnzippedMongoBinPath(),install_directory)
+
+ def findUnzippedMongoBinPath(self):
+ return self.downloader.download_directory + os.sep + \
+ (os.walk(self.downloader.download_directory).next()[1])[0] + os.sep + u"bin"
+
+
+
+
+class MongoDBDownloader(Downloader):
+ @property
+ def download_url(self):
+ raise NotImplementedError
+ @property
+ def download_directory(self):
+ return self.dependency.tmp_directory + os.sep + u"mongo"
+ @property
+ def downloaded_file_path(self):
+ return self.download_directory + os.sep + u"mongodb.tgz"
+ def download(self):
+ print u"Downloading MongoDB from URL " + self.download_url
+ self.download_file(self.download_url,self.downloaded_file_path)
+ self.check_download()
+ def decompress(self):
+ print u"Decompressing MongoDB..."
+ tfile = tarfile.open(self.downloaded_file_path)
+ #TODO: make directory handler class
+ tfile.extractall(self.download_directory)
+ print u"Decompressed MongoDB into " + self.download_directory
+
+ def check_download(self):
+ isFileValid = tarfile.is_tarfile(self.downloaded_file_path)
+ if not isFileValid:
+ raise DownloadCorruptionError(u"MongoDB download was corrupted.")
+
+
+
+class LinuxMongoDBDownloader(MongoDBDownloader):
+ @property
+ def download_url(self):
+ if self.dependency.config.mem_width == 64:
+ return u"http://fastdl.mongodb.org/linux/mongodb-linux-x86_64-2.5.4.tgz"
+ else:
+ warnings.warn(u"MongoDB *really* doesn't run well on 32 bit systems. You have been warned.")
+ return u"http://fastdl.mongodb.org/linux/mongodb-linux-i686-2.5.4.tgz"
+
+class WindowsMongoDBDownloader(MongoDBDownloader):
+ @property
+ def download_url(self):
+ #TODO: Implement Windows Vista detection
+ warnings.warn(u"If you have a version of Windows older than 7, MongoDB may not function properly!")
+ if self.dependency.config.mem_width == 64:
+ return u"http://fastdl.mongodb.org/win32/mongodb-win32-x86_64-2008plus-2.5.4.zip"
+ else:
+ return u"http://fastdl.mongodb.org/win32/mongodb-win32-i386-2.5.4.zip"
+
+class MacMongoDBDownloader(MongoDBDownloader):
+ @property
+ def download_url(self):
+ return u"http://fastdl.mongodb.org/osx/mongodb-osx-x86_64-2.5.4.tgz"
+
+
diff --git a/scripts/devSetup/node.py b/scripts/devSetup/node.py
new file mode 100644
index 000000000..0d1ac7d5f
--- /dev/null
+++ b/scripts/devSetup/node.py
@@ -0,0 +1,142 @@
+__author__ = u'schmatz'
+from downloader import Downloader
+import tarfile
+import errors
+from errors import DownloadCorruptionError
+import warnings
+import os
+from configuration import Configuration
+from dependency import Dependency
+import shutil
+from which import which
+import subprocess
+class Node(Dependency):
+ def __init__(self,configuration):
+ super(self.__class__, self).__init__(configuration)
+ operating_system = configuration.system.operating_system
+ self.config.directory.create_directory_in_tmp(u"node")
+ if operating_system == u"mac":
+ self.downloader = MacNodeDownloader(self)
+ elif operating_system == u"win":
+ self.downloader = WindowsNodeDownloader(self)
+ self.config.directory.create_directory_in_bin(u"node") #TODO: Fix windows node installation
+ elif operating_system == u"linux":
+ self.downloader = LinuxNodeDownloader(self)
+ @property
+ def tmp_directory(self):
+ return self.config.directory.tmp_directory
+ @property
+ def bin_directory(self):
+ return self.config.directory.bin_directory
+
+ def download_dependencies(self):
+ self.downloader.download()
+ self.downloader.decompress()
+ def bashrc_string(self):
+ return "COCO_NODE_PATH=" + self.config.directory.bin_directory + os.sep + u"node" + os.sep + "bin" + os.sep +"node"
+ def install_dependencies(self):
+ install_directory = self.config.directory.bin_directory + os.sep + u"node"
+ unzipped_node_path = self.findUnzippedNodePath()
+ if self.config.system.operating_system in ["mac","linux"] and not which("node"):
+ print "Copying node into /usr/local/bin/..."
+ shutil.copy(unzipped_node_path + os.sep + "bin" + os.sep + "node","/usr/local/bin/")
+ os.chmod("/usr/local/bin/node",0777)
+ shutil.copytree(self.findUnzippedNodePath(),install_directory)
+ wants_to_upgrade = True
+ if self.check_if_executable_installed(u"npm"):
+ warning_string = u"A previous version of npm has been found. \nYou may experience problems if you have a version of npm that's too old.Would you like to upgrade?(y/n) "
+ from distutils.util import strtobool
+ print warning_string
+ #for bash script, you have to somehow redirect stdin to raw_input()
+ user_input = raw_input()
+ while True:
+ try:
+ wants_to_upgrade = strtobool(user_input)
+ except:
+ print u"Please enter y or n. "
+ continue
+ break
+ if wants_to_upgrade:
+ import urllib2, urllib
+ print u"Retrieving npm update script..."
+ npm_install_script_path = install_directory + os.sep + u"install.sh"
+ urllib.urlretrieve(u"https://npmjs.org/install.sh",filename=npm_install_script_path)
+ print u"Retrieved npm install script. Executing..."
+ subprocess.call([u"sh", npm_install_script_path])
+ print u"Updated npm version installed"
+
+
+
+ def findUnzippedNodePath(self):
+ return self.downloader.download_directory + os.sep + \
+ (os.walk(self.downloader.download_directory).next()[1])[0]
+ def check_if_executable_installed(self,name):
+ executable_path = which(name)
+ if executable_path:
+ return True
+ else:
+ return False
+
+ def check_node_version(self):
+ version = subprocess.check_output(u"node -v")
+ return version
+ def check_npm_version(self):
+ version = subprocess.check_output(u"npm -v")
+ return version
+
+
+class NodeDownloader(Downloader):
+ @property
+ def download_url(self):
+ raise NotImplementedError
+ @property
+ def download_directory(self):
+ return self.dependency.tmp_directory + os.sep + u"node"
+ @property
+ def downloaded_file_path(self):
+ return self.download_directory + os.sep + u"node.tgz"
+ def download(self):
+ print u"Downloading Node from URL " + self.download_url
+ self.download_file(self.download_url,self.downloaded_file_path)
+ self.check_download()
+ def decompress(self):
+ print u"Decompressing Node..."
+ tfile = tarfile.open(self.downloaded_file_path)
+ #TODO: make directory handler class
+ tfile.extractall(self.download_directory)
+ print u"Decompressed Node into " + self.download_directory
+
+ def check_download(self):
+ isFileValid = tarfile.is_tarfile(self.downloaded_file_path)
+ if not isFileValid:
+ raise DownloadCorruptionError(u"Node download was corrupted.")
+
+
+
+class LinuxNodeDownloader(NodeDownloader):
+ @property
+ def download_url(self):
+ if self.dependency.config.mem_width == 64:
+ return u"http://nodejs.org/dist/v0.10.24/node-v0.10.24-linux-x64.tar.gz"
+ else:
+ return u"http://nodejs.org/dist/v0.10.24/node-v0.10.24-linux-x86.tar.gz"
+
+class WindowsNodeDownloader(NodeDownloader):
+ @property
+ def download_url(self):
+ raise NotImplementedError(u"Needs MSI to be executed to install npm")
+ #"http://nodejs.org/dist/v0.10.24/x64/node-v0.10.24-x64.msi"
+ if self.dependency.config.mem_width == 64:
+ return u"http://nodejs.org/dist/v0.10.24/x64/node.exe"
+ else:
+ return u"http://nodejs.org/dist/v0.10.24/node.exe"
+
+class MacNodeDownloader(NodeDownloader):
+ @property
+ def download_url(self):
+ if self.dependency.config.mem_width == 64:
+ return u"http://nodejs.org/dist/v0.10.24/node-v0.10.24-darwin-x64.tar.gz"
+ else:
+ return u"http://nodejs.org/dist/v0.10.24/node-v0.10.24-darwin-x86.tar.gz"
+
+
diff --git a/scripts/devSetup/repositoryInstaller.py b/scripts/devSetup/repositoryInstaller.py
new file mode 100644
index 000000000..d1336a07d
--- /dev/null
+++ b/scripts/devSetup/repositoryInstaller.py
@@ -0,0 +1,70 @@
+__author__ = u'schmatz'
+import configuration
+import errors
+import subprocess
+import os
+from which import which
+#git clone https://github.com/nwinter/codecombat.git coco
+class RepositoryInstaller():
+ def __init__(self,config):
+ self.config = config
+ assert isinstance(self.config,configuration.Configuration)
+ if not self.checkIfGitExecutableExists():
+ if self.config.system.operating_system == "linux":
+ raise errors.CoCoError("Git is missing. Please install it(with apt, type 'sudo apt-get install git'")
+ elif self.config.system.operating_system == "mac":
+ raise errors.CoCoError("Git is missing. Please install the Xcode command line tools.")
+ raise errors.CoCoError(u"Git is missing. Please install git.")
+ #http://stackoverflow.com/questions/9329243/xcode-4-4-and-later-install-command-line-tools
+ if not self.checkIfCurlExecutableExists():
+ if self.config.system.operating_system == "linux":
+ raise errors.CoCoError("Curl is missing. Please install it(with apt, type 'sudo apt-get install curl'")
+ elif self.config.system.operating_system == "mac":
+ raise errors.CoCoError("Curl is missing. Please install the Xcode command line tools.")
+ raise errors.CoCoError(u"Git is missing. Please install git.")
+ def checkIfGitExecutableExists(self):
+ gitPath = which(u"git")
+ if gitPath:
+ return True
+ else:
+ return False
+ #TODO: Refactor this into a more appropriate file
+ def checkIfCurlExecutableExists(self):
+ curlPath = which("curl")
+ if curlPath:
+ return True
+ else:
+ return False
+ def cloneRepository(self):
+ print u"Cloning repository..."
+ #TODO: CHANGE THIS BEFORE LAUNCH
+ return_code = True
+ git_folder = self.config.directory.root_install_directory + os.sep + "coco"
+ print "Installing into " + git_folder
+ return_code = subprocess.call("git clone " + self.config.repository_url +" coco",cwd=self.config.directory.root_install_directory,shell=True)
+ #TODO: remove this on windos
+ subprocess.call("chown -R " +git_folder + " 0777",shell=True)
+ if return_code and self.config.system.operating_system != u"windows":
+ #raise errors.CoCoError("Failed to clone git repository")
+ import shutil
+ #import sys
+ #sys.stdout.flush()
+ raw_input(u"Copy it now")
+ #shutil.copytree(u"/Users/schmatz/coco",self.config.directory.root_install_directory + os.sep + u"coco")
+ print u"Copied tree just for you"
+ #print("FAILED TO CLONE GIT REPOSITORY")
+ #input("Clone the repository and click any button to continue")
+ elif self.config.system.operating_system == u"windows":
+ raise errors.CoCoError(u"Windows doesn't support automated installations of npm at this point.")
+ else:
+ print u"Cloned git repository"
+ def install_node_packages(self):
+ print u"Installing node packages..."
+ #TODO: "Replace npm with more robust package
+ #npm_location = self.config.directory.bin_directory + os.sep + "node" + os.sep + "bin" + os.sep + "npm"
+ npm_location = u"npm"
+ return_code = subprocess.call([npm_location,u"install"],cwd=self.config.directory.root_dir + os.sep + u"coco")
+ if return_code:
+ raise errors.CoCoError(u"Failed to install node packages")
+ else:
+ print u"Installed node packages!"
diff --git a/scripts/devSetup/ruby.py b/scripts/devSetup/ruby.py
new file mode 100644
index 000000000..f7c0f373c
--- /dev/null
+++ b/scripts/devSetup/ruby.py
@@ -0,0 +1,41 @@
+__author__ = u'root'
+
+import dependency
+import configuration
+import shutil
+import errors
+import subprocess
+from which import which
+class Ruby(dependency.Dependency):
+ def __init__(self,config):
+ self.config = config
+ assert isinstance(config,configuration.Configuration)
+ is_ruby_installed = self.check_if_ruby_exists()
+ is_gem_installed = self.check_if_gem_exists()
+ if is_ruby_installed and not is_gem_installed:
+ #this means their ruby is so old that RubyGems isn't packaged with it
+ raise errors.CoCoError(u"You have an extremely old version of Ruby. Please upgrade it to get RubyGems.")
+ elif not is_ruby_installed:
+ self.install_ruby()
+ elif is_ruby_installed and is_gem_installed:
+ print u"Ruby found."
+ def check_if_ruby_exists(self):
+ ruby_path = which(u"ruby")
+ return bool(ruby_path)
+ def check_if_gem_exists(self):
+ gem_path = which(u"gem")
+ return bool(gem_path)
+ def install_ruby(self):
+ operating_system = self.config.system.operating_system
+ #Ruby is on every recent Mac, most Linux distros
+ if operating_system == u"windows":
+ self.install_ruby_on_windows()
+ elif operating_system == u"mac":
+ raise errors.CoCoError(u"Ruby should be installed with Mac OSX machines. Please install Ruby.")
+ elif operating_system == u"linux":
+ raise errors.CoCoError(u"Please install Ruby on your Linux distribution(try 'sudo apt-get install ruby'.")
+ def install_ruby_on_windows(self):
+ raise NotImplementedError
+
+ def install_gems(self):
+ gem_install_status = subprocess.call([u"gem",u"install",u"sass"])
\ No newline at end of file
diff --git a/scripts/devSetup/setup.py b/scripts/devSetup/setup.py
new file mode 100644
index 000000000..5c4165bdc
--- /dev/null
+++ b/scripts/devSetup/setup.py
@@ -0,0 +1,48 @@
+#setup.py
+#A setup script for the CodeCombat development environment
+
+# The MIT License (MIT)
+#
+# Copyright (c) 2013 CodeCombat Inc.
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+import factories
+import os
+import errors
+import ctypes
+def check_if_root():
+ is_admin = False
+ try:
+ uid = os.getuid()
+ if uid == 0:
+ is_admin = True
+ except:
+ is_admin = True
+ #is_admin = ctypes.windll.shell32.IsUserAnAdmin()
+ if not is_admin:
+ raise errors.CoCoError(u"You need to be root. Run as sudo.")
+
+if __name__ == u"__main__":
+ print("Code Combat Development Environment Setup Script")
+ check_if_root()
+ setup = factories.constructSetup()
+ setup.setup()
+
+
diff --git a/scripts/devSetup/systemConfiguration.py b/scripts/devSetup/systemConfiguration.py
new file mode 100644
index 000000000..b16e2f2ed
--- /dev/null
+++ b/scripts/devSetup/systemConfiguration.py
@@ -0,0 +1,36 @@
+from __future__ import division
+__author__ = u'schmatz'
+
+import sys
+import os
+from errors import NotSupportedError
+class SystemConfiguration(object):
+
+ def __init__(self):
+ self.operating_system = self.get_operating_system()
+ self.virtual_memory_address_width = self.get_virtual_memory_address_width()
+
+ def get_operating_system(self):
+ platform = sys.platform
+ if platform.startswith(u'linux'):
+ return u"linux"
+ elif platform.startswith(u'darwin'):
+ return u"mac"
+ elif platform.startswith(u'win'):
+ return u"windows"
+ else:
+ raise NotSupportedError(u"Your platform," + sys.platform + u",isn't supported.")
+
+ def get_current_working_directory(self):
+ return os.getcwdu()
+
+ def get_virtual_memory_address_width(self):
+ is64Bit = sys.maxsize/3 > 2**32
+ if is64Bit:
+ return 64
+ else:
+ if self.operating_system != u"linux" or self.operating_system !=u"windows":
+ raise NotSupportedError(u"Your processor is determined to have a maxSize of" + sys.maxsize +
+ u",\n which doesn't correspond with a 64-bit architecture.")
+ return 32
+
diff --git a/scripts/devSetup/which.py b/scripts/devSetup/which.py
new file mode 100644
index 000000000..d8ec70bb6
--- /dev/null
+++ b/scripts/devSetup/which.py
@@ -0,0 +1,66 @@
+__author__ = 'root'
+#copied from python3
+import os
+import sys
+
+def which(cmd, mode=os.F_OK | os.X_OK, path=None):
+ """Given a command, mode, and a PATH string, return the path which
+ conforms to the given mode on the PATH, or None if there is no such
+ file.
+
+ `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result
+ of os.environ.get("PATH"), or can be overridden with a custom search
+ path.
+
+ """
+ # Check that a given file can be accessed with the correct mode.
+ # Additionally check that `file` is not a directory, as on Windows
+ # directories pass the os.access check.
+ def _access_check(fn, mode):
+ return (os.path.exists(fn) and os.access(fn, mode)
+ and not os.path.isdir(fn))
+
+ # If we're given a path with a directory part, look it up directly rather
+ # than referring to PATH directories. This includes checking relative to the
+ # current directory, e.g. ./script
+ if os.path.dirname(cmd):
+ if _access_check(cmd, mode):
+ return cmd
+ return None
+
+ if path is None:
+ path = os.environ.get("PATH", os.defpath)
+ if not path:
+ return None
+ path = path.split(os.pathsep)
+
+ if sys.platform == "win32":
+ # The current directory takes precedence on Windows.
+ if not os.curdir in path:
+ path.insert(0, os.curdir)
+
+ # PATHEXT is necessary to check on Windows.
+ pathext = os.environ.get("PATHEXT", "").split(os.pathsep)
+ # See if the given file matches any of the expected path extensions.
+ # This will allow us to short circuit when given "python.exe".
+ # If it does match, only test that one, otherwise we have to try
+ # others.
+ if any(cmd.lower().endswith(ext.lower()) for ext in pathext):
+ files = [cmd]
+ else:
+ files = [cmd + ext for ext in pathext]
+ else:
+ # On other platforms you don't have things like PATHEXT to tell you
+ # what file suffixes are executable, so just pass on cmd as-is.
+ files = [cmd]
+
+ seen = set()
+ for dir in path:
+ normdir = os.path.normcase(dir)
+ if not normdir in seen:
+ seen.add(normdir)
+ for thefile in files:
+ name = os.path.join(dir, thefile)
+ if _access_check(name, mode):
+ return name
+ return None
\ No newline at end of file
diff --git a/scripts/migrate-from-firebase.js b/scripts/migrate-from-firebase.js
new file mode 100644
index 000000000..4371a8fcd
--- /dev/null
+++ b/scripts/migrate-from-firebase.js
@@ -0,0 +1,132 @@
+// dump me into the console of a codecombat page or the dev server
+// and I will migrate various playing data from firebase to mongodb.
+
+var d = {};
+d.sessionsToDo = [];
+LevelSession = require('models/LevelSession');
+d.codeLoading = {};
+d.tasks = 0;
+
+function go() {
+ if(d.sessionsToDo.length) {
+ doOne();
+ }
+ else {
+ fetchMore();
+ }
+}
+
+function doOne() {
+ var session = d.sessionsToDo.pop();
+ var host = 'https://codecombat.firebaseio.com/play/level/';
+ host += session.levelID + '/' + session._id;
+// console.log('DOING:', session.levelID + '/' + session._id);
+ var fireRef = new Firebase(host);
+ fireRef.once('value', function(snapshot) {
+ window.snapshot = snapshot;
+ d.patch = {};
+ patchIt(session._id, snapshot);
+ });
+}
+
+function patchIt(sessionID, snapshot) {
+ var val = snapshot.val();
+ if(!val) {
+ console.log('miss');
+ task = {
+ ref: snapshot.ref(),
+ sessionID: sessionID,
+ patch: { code: { 0:0 }}
+ }
+ d.missed = true;
+ return finishTask(task);
+ }
+ window.val = val;
+ d.patch = {};
+ if(val.chat) {
+ d.patch.chat = [];
+ for(var i in val.chat) { d.patch.chat.push(val.chat[i]); }
+ delete val.chat;
+ }
+ if(val.players) {
+ d.patch.players = val.players;
+ delete val.players;
+ }
+ if(val.scripts) {
+ delete val.scripts;
+ }
+ for(var thangName in val) {
+ var thang = val[thangName];
+ for(var spellName in thang) {
+// console.log('Load code from', snapshot.ref().toString(), 'for', thangName, spellName);
+ var aceEl = $('');
+ $('body').append(aceEl);
+ var aceEditor = ace.edit(aceEl[0]);
+ firepad = Firepad.fromACE(snapshot.ref().child(thangName+'/'+spellName), aceEditor);
+ var task = {
+ aceEl: aceEl,
+ aceEditor: aceEditor,
+ spellName: spellName,
+ thangName: thangName,
+ firepad: firepad,
+ patch: d.patch,
+ sessionID: sessionID,
+ ref: snapshot.ref()
+ };
+ d.tasks += 1;
+ firepad.on('ready', (function(task) { return function() { codeLoaded(task); } })(task));
+ }
+ }
+// console.log('Made', d.tasks, 'tasks');
+ if (!d.tasks) {
+ task = {
+ patch: d.patch,
+ sessionID: sessionID,
+ ref: snapshot.ref()
+ };
+ finishTask(task);
+ }
+// d.patch.code = val;
+}
+
+function codeLoaded(task) {
+// console.log('------------------\nLoaded code:\n', task.aceEditor.getValue());
+ if(!task.patch.code) task.patch.code = {};
+ if(!task.patch.code[task.thangName]) task.patch.code[task.thangName] = {};
+ task.patch.code[task.thangName][task.spellName] = task.aceEditor.getValue();
+
+ // cleanup this task
+ task.aceEditor.destroy();
+ task.firepad.dispose();
+ task.aceEl.remove();
+
+ d.tasks -= 1;
+ if(d.tasks === 0) {
+ finishTask(task);
+// console.log('We got it all', task.patch);
+ }
+}
+
+function finishTask(task) {
+ var session = new LevelSession({_id:task.sessionID});
+ window.session = session;
+
+ task.ref.remove();
+ session.save(task.patch, {patch:true});
+// session.once('sync', function() { console.log('saved'); } );
+ session.once('sync', function() { setTimeout(go, 1); } )
+}
+
+
+function fetchMore() {
+ console.log('\n\nFETCHING MORE TO DO');
+ $.ajax({
+ url:'/admin/sessions_to_do?level=munchkin-masher',
+ success: function(res) {
+ d.sessionsToDo = res;
+ setTimeout(go, 1);
+ }
+ });
+}
+
+go();
diff --git a/server.coffee b/server.coffee
new file mode 100644
index 000000000..30b3101ae
--- /dev/null
+++ b/server.coffee
@@ -0,0 +1,121 @@
+# Put lodash and underscore.string into the global namespace
+GLOBAL._ = require('lodash')
+_.str = require('underscore.string')
+_.mixin(_.str.exports())
+
+express = require('express')
+path = require('path')
+winston = require('winston')
+passport = require('passport')
+useragent = require 'express-useragent'
+
+auth = require './server/auth'
+db = require './server/db'
+file = require './server/file'
+folder = require './server/folder'
+user = require './server/handlers/user'
+logging = require './server/logging'
+sprites = require './server/sprites'
+contact = require './server/contact'
+languages = require './server/languages'
+
+https = require('https')
+http = require('http')
+fs = require('graceful-fs')
+
+config = require('./server_config')
+
+logging.setup()
+db.connectDatabase()
+
+# MailChimp setup
+mcapi = require 'mailchimp-api'
+mc = new mcapi.Mailchimp(config.mail.mailchimpAPIKey)
+GLOBAL.mc = mc
+
+# Express server setup
+app = express()
+
+active_responses = []
+
+oldBrowser = (req, res, next) ->
+ return next() if req.query['try-old-browser-anyway'] or not isOldBrowser(req)
+ res.sendfile(path.join(__dirname, 'public', 'index_old_browser.html'))
+
+# determines order of middleware and request handling
+app.configure(->
+ app.use (req, res, next) ->
+ req.setTimeout 15000, ->
+ console.log 'timed out!'
+ req.abort()
+ self.emit('pass',message)
+ next()
+
+ app.use(express.logger('dev'))
+ app.use(express.static(path.join(__dirname, 'public')))
+ app.use(useragent.express())
+ app.use '/play/', oldBrowser # When they go directly to play a level, they won't see our browser warning, so give it to 'em.
+ app.set('port', config.port)
+ app.set('views', __dirname + '/app/views')
+ app.set('view engine', 'jade')
+ app.set('view options', { layout: false })
+ app.use(express.favicon())
+ app.use(express.cookieParser(config.cookie_secret))
+ app.use(express.bodyParser())
+ app.use(express.methodOverride())
+ app.use(express.cookieSession({secret:'defenestrate'}))
+ app.use(passport.initialize())
+ app.use(passport.session())
+ if(config.slow_down)
+ app.use((req, res, next) -> setTimeout((-> next()), 1000))
+ user.setupMiddleware(app)
+
+ app.use(app.router)
+)
+
+app.configure('development', -> app.use(express.errorHandler()))
+
+auth.setupRoutes(app)
+db.setupRoutes(app)
+sprites.setupRoutes(app)
+contact.setupRoutes(app)
+file.setupRoutes(app)
+folder.setupRoutes(app)
+languages.setupRoutes(app)
+
+# Some sort of cross-domain communication hack facebook requires
+app.get('/channel.html', (req, res) ->
+ res.sendfile(path.join(__dirname, 'public', 'channel.html'))
+)
+
+# blitz.io (load tester) auth
+app.get '/mu-a2a0832f-10763ae9-170d6c87-70a62423', (req, res) ->
+ res.send('42')
+
+# Anything that isn't handled at this point gets index.html
+app.get('*', (req, res) ->
+ res.sendfile(path.join(__dirname, 'public', 'index.html'))
+)
+
+#ssl_options =
+# key: fs.readFileSync('ssl/key.pem')
+# cert: fs.readFileSync('ssl/cert.pem')
+
+module.exports.startServer = ->
+ http.createServer(app).listen(app.get('port'))
+ winston.info("Express SSL server listening on port " + app.get('port'))
+# https.createServer(ssl_options, app).listen(config['ssl_port']);
+ return app
+
+isOldBrowser = (req) ->
+ # https://github.com/biggora/express-useragent/blob/master/lib/express-useragent.js
+ return false unless ua = req.useragent
+ return true if ua.isiPad or ua.isiPod or ua.isiPhone or ua.isOpera
+ return false unless ua and ua.Browser in ["Chrome", "Safari", "Firefox", "IE"] and ua.Version
+ b = ua.Browser
+ v = parseInt ua.Version.split('.')[0], 10
+ return true if b is 'Chrome' and v < 17
+ return true if b is 'Safari' and v < 6
+ return true if b is 'Firefox' and v < 21
+ return true if b is 'IE' and v < 10
+ false
diff --git a/server/auth.coffee b/server/auth.coffee
new file mode 100644
index 000000000..428f5db38
--- /dev/null
+++ b/server/auth.coffee
@@ -0,0 +1,107 @@
+passport = require('passport')
+winston = require('winston')
+LocalStrategy = require('passport-local').Strategy
+User = require('./models/User')
+UserHandler = require('./handlers/user')
+config = require '../server_config'
+nodemailer = require 'nodemailer'
+
+module.exports.setupRoutes = (app) ->
+ passport.serializeUser((user, done) -> done(null, user._id))
+ passport.deserializeUser((id, done) ->
+ User.findById(id, (err, user) -> done(err, user)))
+
+ passport.use(new LocalStrategy(
+ (username, password, done) ->
+ User.findOne({emailLower:username.toLowerCase()}).exec((err, user) ->
+ return done(err) if err
+ return done(null, false, {message:'not found', property:'email'}) if not user
+ passwordReset = (user.get('passwordReset') or '').toLowerCase()
+ if passwordReset and password.toLowerCase() is passwordReset
+ User.update {_id: user.get('_id')}, {passwordReset: ''}, {}, ->
+ return done(null, user)
+
+ hash = User.hashPassword(password)
+ unless user.get('passwordHash') is hash
+ return done(null, false, {message:'is wrong, wrong, wrong', property:'password'})
+ return done(null, user)
+ )
+ ))
+
+ app.post('/auth/login', (req, res, next) ->
+ passport.authenticate('local', (err, user, info) ->
+ return next(err) if err
+ if not user
+ res.status(401)
+ res.send([{message:info.message, property:info.property}])
+ return res.end()
+
+ req.logIn(user, (err) ->
+ return next(err) if (err)
+ res.send(UserHandler.formatEntity(req, req.user))
+ return res.end()
+ )
+ )(req, res, next)
+ )
+
+ app.get('/auth/whoami', (req, res) ->
+ res.setHeader('Content-Type', 'text/json');
+ res.send(UserHandler.formatEntity(req, req.user))
+ res.end()
+ )
+
+ app.post('/auth/logout', (req, res) ->
+ req.logout()
+ res.end()
+ )
+
+ app.post('/auth/reset', (req, res) ->
+ unless req.body.email
+ res.status(422)
+ res.send([{message:'Need an email specified.', property:email}])
+ return res.end()
+ User.findOne({emailLower:req.body.email.toLowerCase()}).exec((err, user) ->
+ if not user
+ res.status(404)
+ res.send([{message:'not found.', property:'email'}])
+ return res.end()
+
+ user.set('passwordReset', Math.random().toString(36).slice(2,7).toUpperCase())
+ user.save (err) =>
+ return returnServerError(res) if err
+ if config.isProduction or true
+ transport = createSMTPTransport()
+ options = createMailOptions req.body.email, user.get('passwordReset')
+ transport.sendMail options, (error, response) ->
+ if error
+ console.error "Error sending mail: #{error.message or error}"
+ return returnServerError(res) if err
+ else
+ return res.end()
+ )
+ )
+
+createMailOptions = (receiver, password) ->
+ # TODO: use email templates here
+ options =
+ from: config.mail.username
+ to: receiver
+ replyTo: config.mail.username
+ subject: "[CodeCombat] Password Reset"
+ text: "You can log into your account with: #{password}"
+#html: message.replace '\n', '
\n'
+
+createSMTPTransport = ->
+ return smtpTransport if smtpTransport
+ smtpTransport = nodemailer.createTransport "SMTP",
+ service: config.mail.service
+ user: config.mail.username
+ pass: config.mail.password
+ authMethod: "LOGIN"
+ smtpTransport
+
+returnServerError = (res) ->
+ res.status(500)
+ res.send('Server error.')
+ res.end()
+
diff --git a/server/contact.coffee b/server/contact.coffee
new file mode 100644
index 000000000..9ac1d8a87
--- /dev/null
+++ b/server/contact.coffee
@@ -0,0 +1,37 @@
+config = require '../server_config'
+winston = require 'winston'
+nodemailer = require 'nodemailer'
+
+module.exports.setupRoutes = (app) ->
+ app.post '/contact', (req, res) ->
+ winston.info "Sending mail from #{req.body.email} saying #{req.body.message}"
+ if config.isProduction or true
+ transport = createSMTPTransport()
+ options = createMailOptions req.body.email, req.body.message, req.user
+ transport.sendMail options, (error, response) ->
+ if error
+ winston.error "Error sending mail: #{error.message or error}"
+ else
+ winston.info "Mail sent successfully. Response: #{response.message}"
+ return res.end()
+
+createMailOptions = (sender, message, user) ->
+ # TODO: use email templates here
+ console.log 'text is now', "#{message}\n\n#{user.get('name')}\nID: #{user._id}"
+ options =
+ from: config.mail.username
+ to: config.mail.username
+ replyTo: sender
+ subject: "[CodeCombat] Feedback - #{sender}"
+ text: "#{message}\n\nUsername: #{user.get('name') or 'Anonymous'}\nID: #{user._id}"
+ #html: message.replace '\n', '
\n'
+
+smtpTransport = null
+createSMTPTransport = ->
+ return smtpTransport if smtpTransport
+ smtpTransport = nodemailer.createTransport "SMTP",
+ service: config.mail.service
+ user: config.mail.username
+ pass: config.mail.password
+ authMethod: "LOGIN"
+ smtpTransport
\ No newline at end of file
diff --git a/server/db.coffee b/server/db.coffee
new file mode 100644
index 000000000..56c8b716b
--- /dev/null
+++ b/server/db.coffee
@@ -0,0 +1,60 @@
+config = require '../server_config'
+winston = require 'winston'
+mongoose = require 'mongoose'
+Grid = require 'gridfs-stream'
+async = require 'async'
+
+testing = '--unittest' in process.argv
+
+module.exports.connectDatabase = () ->
+ dbName = config.mongo.db
+ dbName += '_unittest' if testing
+ address = config.mongo.host + ":" + config.mongo.port
+ if config.mongo.username and config.mongo.password
+ address = config.mongo.username + ":" + config.mongo.password + "@" + address
+# address = config.mongo.username + "@" + address # if connecting to production server
+ address = "mongodb://#{address}/#{dbName}"
+ console.log "got address:", address
+ mongoose.connect address
+ mongoose.connection.once 'open', ->
+ Grid.gfs = Grid(mongoose.connection.db, mongoose.mongo)
+
+module.exports.setupRoutes = (app) ->
+ app.all '/db/*', (req, res) ->
+ res.setHeader('Content-Type', 'application/json')
+ module = req.path[4..]
+
+ parts = module.split('/')
+ module = parts[0]
+ return getSchema(req, res, module) if parts[1] is 'schema'
+
+ try
+ name = "./handlers/#{module.replace '.', '_'}"
+ module = require(name)
+ return module.getLatestVersion(req, res, parts[1], parts[3]) if parts[2] is 'version'
+ return module.versions(req, res, parts[1]) if parts[2] is 'versions'
+ return module.files(req, res, parts[1]) if parts[2] is 'files'
+ return module.search(req, res) if req.route.method is 'get' and parts[1] is 'search'
+ return module.getByRelationship(req, res, parts[1..]...) if parts.length > 2
+ return module.getById(req, res, parts[1]) if req.route.method is 'get' and parts[1]?
+ return module.patch(req, res, parts[1]) if req.route.method is 'patch' and parts[1]?
+ module[req.route.method](req, res)
+ catch error
+ winston.error("Error trying db method #{req.route.method} route #{parts} from #{name}: #{error}")
+ winston.error(error)
+ res.status(404)
+ res.write("Route #{req.path} not found.")
+ res.end()
+
+getSchema = (req, res, moduleName) ->
+ try
+ name = "./schemas/#{moduleName.replace '.', '_'}"
+ schema = require(name)
+ res.send(schema)
+ res.end()
+
+ catch error
+ winston.error("Error trying to grab schema from #{name}: #{error}")
+ res.status(404)
+ res.write("Schema #{moduleName} not found.")
+ res.end()
diff --git a/server/errors.coffee b/server/errors.coffee
new file mode 100644
index 000000000..1ea03b1a5
--- /dev/null
+++ b/server/errors.coffee
@@ -0,0 +1,31 @@
+
+module.exports.notFound = (req, res, message) ->
+ res.status(404)
+ message = "Route #{req.path} not found." unless message
+ res.write(message)
+ res.end()
+
+module.exports.badMethod = (res) ->
+ res.status(405)
+ res.send('Method not allowed.')
+ res.end()
+
+module.exports.badInput = (res) ->
+ res.status(422)
+ res.send('Bad post input.')
+ res.end()
+
+module.exports.conflict = (res) ->
+ res.status(409)
+ res.send('File exists.')
+ res.end()
+
+module.exports.serverError = (res) ->
+ res.status(500)
+ res.send('Server error.')
+ res.end()
+
+module.exports.unauthorized = (res) ->
+ res.status(403)
+ res.send('Unauthorized.')
+ res.end()
\ No newline at end of file
diff --git a/server/file.coffee b/server/file.coffee
new file mode 100644
index 000000000..bc35e2b4f
--- /dev/null
+++ b/server/file.coffee
@@ -0,0 +1,178 @@
+winston = require 'winston'
+Grid = require 'gridfs-stream'
+fs = require 'fs'
+request = require 'request'
+mongoose = require('mongoose')
+
+module.exports.setupRoutes = (app) ->
+ app.all '/file*', (req, res) ->
+ return fileGet(req, res) if req.route.method is 'get'
+ return filePost(req, res) if req.route.method is 'post'
+ return returnBadMethod(res)
+
+
+fileGet = (req, res) ->
+ path = req.path[6..]
+ isFolder = false
+ try
+ objectId = mongoose.Types.ObjectId(path)
+ query = objectId
+ catch e
+ path = path.split('/')
+ filename = path[path.length-1]
+ path = path[...path.length-1].join('/')
+ query =
+ 'metadata.path': path
+ if filename then query.filename = filename else isFolder = true
+
+ if isFolder
+ Grid.gfs.collection('media').find query, (err, cursor) ->
+ return returnServerError(res) if err
+ results = cursor.toArray (err, results) ->
+ return returnServerError(res) if err
+ res.setHeader('Content-Type', 'text/json')
+ res.send(results)
+ res.end()
+
+ else
+ Grid.gfs.collection('media').findOne query, (err, filedata) =>
+ return returnNotFound(req, res) if not filedata
+ readstream = Grid.gfs.createReadStream({_id: filedata._id, root:'media'})
+ if req.headers['if-modified-since'] is filedata.uploadDate
+ res.status(304)
+ return res.end()
+
+ res.setHeader('Content-Type', filedata.contentType)
+ res.setHeader('Last-Modified', filedata.uploadDate)
+ res.setHeader('Cache-Control', 'public')
+ readstream.pipe(res)
+ handleStreamEnd(res, res)
+
+postFileSchema =
+ type: 'object'
+ properties:
+ # source
+ url: { type: 'string', description: 'The url to download the file from.' }
+ postName: { type: 'string', description: 'The input field this file was sent on.' }
+
+ # options
+ force: { type: 'string', 'default': '', description: 'Whether to overwrite existing files (as opposed to throwing an error).' }
+
+ # metadata
+ filename: { type: 'string', description: 'What the file will be named in the system.' }
+ mimetype: { type: 'string' }
+ name: { type: 'string', description: 'Human readable and searchable string.' }
+ description: { type: 'string' }
+ path: { type: 'string', description: 'What "folder" this file goes into.' }
+
+ required: ['filename', 'mimetype', 'path']
+
+filePost = (req, res) ->
+ return returnNotAllowed(req, res) unless req.user.isAdmin()
+ options = req.body
+ tv4 = require('tv4').tv4
+ valid = tv4.validate(options, postFileSchema)
+ hasSource = options.url or options.postName
+ return returnBadInput(res) if (not valid) or (not hasSource)
+ return saveURL(req, res) if options.url
+ return saveFile(req, res) if options.postName
+
+saveURL = (req, res) ->
+ options = createPostOptions(req)
+ checkExistence options, res, req.body.force, (err) ->
+ return if err
+ writestream = Grid.gfs.createWriteStream(options)
+ request(req.body.url).pipe(writestream)
+ handleStreamEnd(res, writestream)
+
+saveFile = (req, res) ->
+ options = createPostOptions(req)
+ checkExistence options, res, req.body.force, (err) ->
+ return if err
+ writestream = Grid.gfs.createWriteStream(options)
+ f = req.files[req.body.postName]
+ fileStream = fs.createReadStream(f.path)
+ fileStream.pipe(writestream)
+ handleStreamEnd(res, writestream)
+
+checkExistence = (options, res, force, done) ->
+ q = {
+ filename: options.filename
+ 'metadata.path': options.metadata.path
+ }
+ Grid.gfs.collection('media').find(q).toArray (err, files) ->
+ if files.length and not force
+ returnConflict(res)
+ done(true)
+ else if files.length
+ q.root = 'media'
+ Grid.gfs.remove q, (err) ->
+ return returnServerError(res) if err
+ done()
+ else
+ done()
+
+handleStreamEnd = (res, stream) ->
+ stream.on 'close', (f) ->
+ res.send(f)
+ res.end()
+
+ stream.on 'error', ->
+ return returnServerError(res)
+
+CHUNK_SIZE = 1024*256
+
+createPostOptions = (req) ->
+ unless req.body.name
+ name = req.body.filename.split('.')[0]
+ req.body.name = _.str.humanize(name)
+
+ path = req.body.path or ''
+ path = path[1...] if path and path[0] is '/'
+ path = path[...path.length-2] if path and path[path.length-1] is '/'
+
+ options =
+ mode: 'w'
+ filename: req.body.filename
+ chunk_size: CHUNK_SIZE
+ root: 'media'
+ content_type: req.body.mimetype
+ metadata:
+ name: req.body.name
+ path: path
+ creator: ''+req.user._id
+ options.metadata.description = req.body.description if req.body.description?
+
+ options
+
+returnNotAllowed = (req, res, message) ->
+ res.status(403)
+ message = "Can't do that, Dave." unless message
+ res.write(message)
+ res.end()
+
+returnNotFound = (req, res, message) ->
+ res.status(404)
+ message = "Route #{req.path} not found." unless message
+ res.write(message)
+ res.end()
+
+returnBadMethod = (res) ->
+ res.status(405)
+ res.send('Method not allowed.')
+ res.end()
+
+returnBadInput = (res) ->
+ res.status(422)
+ res.send('Bad post input.')
+ res.end()
+
+returnConflict = (res) ->
+ res.status(409)
+ res.send('File exists.')
+ res.end()
+
+returnServerError = (res) ->
+ res.status(500)
+ res.send('Server error.')
+ res.end()
\ No newline at end of file
diff --git a/server/folder.coffee b/server/folder.coffee
new file mode 100644
index 000000000..b48cd5f17
--- /dev/null
+++ b/server/folder.coffee
@@ -0,0 +1,20 @@
+fs = require 'fs'
+request = require 'request'
+mongoose = require('mongoose')
+errors = require './errors'
+
+module.exports.setupRoutes = (app) ->
+ app.all '/folder*', (req, res) ->
+ return folderGet(req, res) if req.route.method is 'get'
+ return errors.badMethod(res)
+
+folderGet = (req, res) ->
+ folder = req.path[7..]
+ userfolder = "/user-#{req.user.id}/"
+ folder = userfolder if folder is '/me/'
+ return errors.unauthorized(res) unless (folder is userfolder) or (req.user.isAdmin())
+
+ mongoose.connection.db.collection 'media.files', (errors, collection) ->
+ collection.find({'metadata.path': folder}).toArray (err, results) ->
+ res.send(results)
+ res.end()
\ No newline at end of file
diff --git a/server/handlers/Handler.coffee b/server/handlers/Handler.coffee
new file mode 100644
index 000000000..c34cc3fe9
--- /dev/null
+++ b/server/handlers/Handler.coffee
@@ -0,0 +1,291 @@
+async = require 'async'
+mongoose = require('mongoose')
+Grid = require 'gridfs-stream'
+
+module.exports = class Handler
+ # subclasses should override these properties
+ modelClass: null
+ editableProperties: []
+ postEditableProperties: []
+ jsonSchema: {}
+ waterfallFunctions: []
+
+ # subclasses should override these methods
+ hasAccess: (req) -> true
+ hasAccessToDocument: (req, document, method=null) ->
+ return true if req.user.isAdmin()
+ if @modelClass.schema.uses_coco_permissions
+ return document.hasPermissionsForMethod(req.user, method or req.method)
+ return true
+
+ formatEntity: (req, document) -> document?.toObject()
+ getEditableProperties: (req, document) ->
+ props = @editableProperties.slice()
+ isBrandNew = req.method is 'POST' and not req.body.original
+ if isBrandNew
+ props = props.concat @postEditableProperties
+
+ if @modelClass.schema.uses_coco_permissions
+ # can only edit permissions if this is a brand new property,
+ # or you are an owner of the old one
+ isOwner = document.getAccessForUserObjectId(req.user._id) is 'owner'
+ if isBrandNew or isOwner or req.user.isAdmin()
+ props.push 'permissions'
+
+ if @modelClass.schema.uses_coco_versions
+ props.push 'commitMessage'
+
+ props
+
+ # sending functions
+ sendUnauthorizedError: (res) -> @sendError(res, 403, "Unauthorized.")
+ sendNotFoundError: (res) -> @sendError(res, 404, 'Resource not found.')
+ sendMethodNotAllowed: (res) -> @sendError(res, 405, 'Method not allowed.')
+ sendBadInputError: (res, message) -> @sendError(res, 422, message)
+ sendDatabaseError: (res, err) -> @sendError(res, 500, 'Database error.')
+
+ sendError: (res, code, message) ->
+ console.warn "Sending an error code", code, message
+ res.status(code)
+ res.send(message)
+ res.end()
+
+ sendSuccess: (res, message) ->
+ res.send(message)
+ res.end()
+
+ # generic handlers
+ get: (req, res) ->
+ # by default, ordinary users never get unfettered access to the database
+ return @sendUnauthorizedError(res) unless req.user.isAdmin()
+
+ # admins can send any sort of query down the wire, though
+ conditions = JSON.parse(req.query.conditions || '[]')
+ query = @modelClass.find()
+
+ try
+ for condition in conditions
+ name = condition[0]
+ f = query[name]
+ args = condition[1..]
+ query = query[name](args...)
+ catch e
+ return @sendError(res, 422, 'Badly formed conditions.')
+
+ query.exec (err, documents) =>
+ return @sendDatabaseError(res, err) if err
+ documents = (@formatEntity(req, doc) for doc in documents)
+ @sendSuccess(res, documents)
+
+ getById: (req, res, id) ->
+ return @sendUnauthorizedError(res) unless @hasAccess(req)
+
+ @getDocumentForIdOrSlug id, (err, document) =>
+ return @sendDatabaseError(res, err) if err
+ return @sendNotFoundError(res) unless document?
+ return @sendUnauthorizedError(res) unless @hasAccessToDocument(req, document)
+ @sendSuccess(res, @formatEntity(req, document))
+
+ getByRelationship: (req, res, args...) ->
+ # this handler should be overwritten by subclasses
+ return @sendNotFoundError(res)
+
+ search: (req, res) ->
+ unless @modelClass.schema.uses_coco_search
+ return @sendNotFoundError(res)
+
+ term = req.query.term
+ matchedObjects = []
+ filters = [{filter: {index: true}}]
+ if @modelClass.schema.uses_coco_permissions
+ filters.push {filter: {index: req.user.get('id')}}
+ for filter in filters
+ callback = (err, results) =>
+ return @sendDatabaseError(res, err) if err
+ for r in results.results ? results
+ obj = r.obj ? r
+ continue if obj in matchedObjects # TODO: probably need a better equality check
+ matchedObjects.push obj
+ filters.pop() # doesn't matter which one
+ unless filters.length
+ res.send matchedObjects
+ res.end()
+ if term
+ @modelClass.textSearch term, filter, callback
+ else
+ @modelClass.find(filter.filter).limit(100).exec callback
+
+ versions: (req, res, id) ->
+ # TODO: a flexible system for doing GAE-like cursors for these sort of paginating queries
+ # Keeping it simple for now and just allowing access to the first 100 results.
+ query = {'original': mongoose.Types.ObjectId(id)}
+ sort = {'created': -1}
+ @modelClass.find(query).limit(100).sort(sort).exec (err, results) =>
+ for doc in results
+ return @sendUnauthorizedError(res) unless @hasAccessToDocument(req, doc)
+ res.send(results)
+ res.end()
+
+ files: (req, res, id) ->
+ module = req.path[4..].split('/')[0]
+ query = {'metadata.path': "db/#{module}/#{id}"}
+ Grid.gfs.collection('media').find query, (err, cursor) ->
+ return @sendDatabaseError(res, err) if err
+ results = cursor.toArray (err, results) ->
+ return @sendDatabaseError(res, err) if err
+ res.send(results)
+ res.end()
+
+ getLatestVersion: (req, res, original, version) ->
+ # can get latest overall version, latest of a major version, or a specific version
+ query = { 'original': mongoose.Types.ObjectId(original) }
+ if version?
+ version = version.split('.')
+ majorVersion = parseInt(version[0])
+ minorVersion = parseInt(version[1])
+ query['version.major'] = majorVersion unless _.isNaN(majorVersion)
+ query['version.minor'] = minorVersion unless _.isNaN(minorVersion)
+ sort = { 'version.major': -1, 'version.minor': -1 }
+ @modelClass.findOne(query).sort(sort).exec (err, doc) =>
+ return @sendNotFoundError(res) unless doc?
+ return @sendUnauthorizedError(res) unless @hasAccessToDocument(req, doc)
+ res.send(doc)
+ res.end()
+
+ patch: ->
+ @put(arguments...)
+
+ put: (req, res, id) ->
+ return @postNewVersion(req, res) if @modelClass.schema.uses_coco_versions
+ return @sendBadInputError(res, 'No input.') if _.isEmpty(req.body)
+ return @sendUnauthorizedError(res) unless @hasAccess(req)
+ @getDocumentForIdOrSlug req.body._id or id, (err, document) =>
+ return @sendBadInputError(res, 'Bad id.') if err and err.name is 'CastError'
+ return @sendDatabaseError(res, err) if err
+ return @sendNotFoundError(res) unless document?
+ return @sendUnauthorizedError(res) unless @hasAccessToDocument(req, document)
+ @doWaterfallChecks req, document, (err, document) =>
+ return @sendError(res, err.code, err.res) if err
+ @saveChangesToDocument req, document, (err) =>
+ return @sendBadInputError(res, err.errors) if err?.valid is false
+ return @sendDatabaseError(res, err) if err
+ @sendSuccess(res, @formatEntity(req, document))
+
+ post: (req, res) ->
+ if @modelClass.schema.uses_coco_versions
+ if req.body.original
+ return @postNewVersion(req, res)
+ else
+ return @postFirstVersion(req, res)
+
+ return @sendBadInputError(res, 'No input.') if _.isEmpty(req.body)
+ return @sendBadInputError(res, 'id should not be included.') if req.body._id
+ return @sendUnauthorizedError(res) unless @hasAccess(req)
+ validation = @validateDocumentInput(req.body)
+ return @sendBadInputError(res, validation.errors) unless validation.valid
+ document = @makeNewInstance(req)
+ @saveChangesToDocument req, document, (err) =>
+ return @sendDatabaseError(res, err) if err
+ @sendSuccess(res, @formatEntity(req, document))
+
+ # TODO: think about pulling some common stuff out of postFirstVersion/postNewVersion into a postVersion if we can figure out the breakpoints?
+ # ..... actually, probably better would be to do the returns with throws instead and have a handler which turns them into status codes and messages
+ postFirstVersion: (req, res) ->
+ return @sendBadInputError(res, 'No input.') if _.isEmpty(req.body)
+ return @sendBadInputError(res, 'id should not be included.') if req.body._id
+ return @sendUnauthorizedError(res) unless @hasAccess(req)
+ validation = @validateDocumentInput(req.body)
+ return @sendBadInputError(res, validation.errors) unless validation.valid
+ document = @makeNewInstance(req)
+ document.set('original', document._id)
+ document.set('creator', req.user._id)
+ @saveChangesToDocument req, document, (err) =>
+ return @sendBadInputError(res, err.response) if err?.response
+ return @sendDatabaseError(res, err) if err
+ @sendSuccess(res, @formatEntity(req, document))
+
+ postNewVersion: (req, res) ->
+ """
+ To the client, posting new versions look like this:
+
+ POST /db/modelname
+
+ With the input being just the altered structure of the old version,
+ leaving the _id property intact even.
+ No version object means it's a new major version.
+ A version object with a major value means a new minor version.
+ All other properties in version are ignored.
+ """
+ return @sendBadInputError(res, 'This entity is not versioned') unless @modelClass.schema.uses_coco_versions
+ return @sendBadInputError(res, 'No input.') if _.isEmpty(req.body)
+ return @sendUnauthorizedError(res) unless @hasAccess(req)
+ validation = @validateDocumentInput(req.body)
+ return @sendBadInputError(res, validation.errors) unless validation.valid
+ @getDocumentForIdOrSlug req.body._id, (err, parentDocument) =>
+ return @sendBadInputError(res, 'Bad id.') if err and err.name is 'CastError'
+ return @sendDatabaseError(res, err) if err
+ return @sendNotFoundError(res) unless parentDocument?
+ return @sendUnauthorizedError(res) unless @hasAccessToDocument(req, parentDocument)
+ updatedObject = parentDocument.toObject()
+ changes = _.pick req.body, @getEditableProperties(req, parentDocument)
+ _.extend updatedObject, changes
+ delete updatedObject._id
+ major = req.body.version?.major
+
+ done = (err, newDocument) =>
+ return @sendDatabaseError(res, err) if err
+ newDocument.set('creator', req.user._id)
+ newDocument.save (err) =>
+ return @sendDatabaseError(res, err) if err
+ @sendSuccess(res, @formatEntity(req, newDocument))
+
+ if major?
+ parentDocument.makeNewMinorVersion(updatedObject, major, done)
+
+ else
+ parentDocument.makeNewMajorVersion(updatedObject, done)
+
+ makeNewInstance: (req) ->
+ new @modelClass({})
+
+ validateDocumentInput: (input) ->
+ tv4 = require('tv4').tv4
+ res = tv4.validateMultiple(input, @jsonSchema)
+ res
+
+ getDocumentForIdOrSlug: (idOrSlug, done) ->
+ idOrSlug = idOrSlug+''
+ try
+ mongoose.Types.ObjectId.createFromHexString(idOrSlug) # throw error if not a valid ID (probably a slug)
+ @modelClass.findById(idOrSlug).exec (err, document) ->
+ done(err, document)
+ catch e
+ @modelClass.findOne {slug: idOrSlug}, (err, document) =>
+ done(err, document)
+
+
+ doWaterfallChecks: (req, document, done) ->
+ return done(null, document) unless @waterfallFunctions.length
+
+ # waterfall doesn't let you pass an initial argument
+ # so wrap the first waterfall function to pass in the document
+ funcs = (f for f in @waterfallFunctions)
+ firstFunc = funcs[0]
+ wrapped = (func, r, doc) -> (callback) -> func(r, doc, callback)
+ funcs[0] = wrapped(firstFunc, req, document)
+ async.waterfall funcs, (err, rrr, document) ->
+ done(err, document)
+
+ saveChangesToDocument: (req, document, done) ->
+ for prop in @getEditableProperties(req, document)
+ document.set(prop, req.body[prop]) if req.body[prop]?
+ obj = document.toObject()
+
+ # Hack to get saving of Users to work. Probably should replace these props with strings
+ # so that validation doesn't get hung up on Date objects in the documents.
+ delete obj.dateCreated
+
+ validation = @validateDocumentInput(obj)
+ return done(validation) unless validation.valid
+
+ document.save (err) -> done(err)
diff --git a/server/handlers/article.coffee b/server/handlers/article.coffee
new file mode 100644
index 000000000..49e856d10
--- /dev/null
+++ b/server/handlers/article.coffee
@@ -0,0 +1,13 @@
+winston = require('winston')
+request = require('request')
+Article = require('../models/Article')
+Handler = require('./Handler')
+
+ArticleHandler = class ArticleHandler extends Handler
+ modelClass: Article
+ editableProperties: ['body', 'name']
+
+ hasAccess: (req) ->
+ req.method is 'GET' or req.user?.isAdmin()
+
+module.exports = new ArticleHandler()
diff --git a/server/handlers/campaign.coffee b/server/handlers/campaign.coffee
new file mode 100644
index 000000000..17f668542
--- /dev/null
+++ b/server/handlers/campaign.coffee
@@ -0,0 +1,10 @@
+winston = require('winston')
+request = require('request')
+Campaign = require('../models/Campaign')
+Handler = require('./Handler')
+
+CampaignHandler = class CampaignHandler extends Handler
+ modelClass: Campaign
+ editableProperties: ['name', 'description', 'levels']
+
+module.exports = new CampaignHandler()
\ No newline at end of file
diff --git a/server/handlers/campaign_status.coffee b/server/handlers/campaign_status.coffee
new file mode 100644
index 000000000..ce5d293c0
--- /dev/null
+++ b/server/handlers/campaign_status.coffee
@@ -0,0 +1,15 @@
+winston = require('winston')
+request = require('request')
+CampaignStatus = require('../models/CampaignStatus')
+Handler = require('./Handler')
+
+CampaignStatusHandler = class CampaignStatusHandler extends Handler
+ modelClass: CampaignStatus
+ editableProperties: ['levelStatuses']
+ postEditableProperties: ['campaign', 'user']
+
+ post: (req, res) ->
+ req.body.user = req.user._id
+ super(req, res)
+
+module.exports = new CampaignStatusHandler()
diff --git a/server/handlers/file.coffee b/server/handlers/file.coffee
new file mode 100644
index 000000000..4123925e0
--- /dev/null
+++ b/server/handlers/file.coffee
@@ -0,0 +1,13 @@
+File = require('../models/File')
+Handler = require('./Handler')
+
+FileHandler = class FileHandler extends Handler
+ modelClass: File
+ editableProperties: ['metadata']
+
+ hasAccess: (req) ->
+ req.method is 'GET'
+
+# TODO: once we're building the clients, need special GET handler, search handler
+
+module.exports = new FileHandler()
diff --git a/server/handlers/level.coffee b/server/handlers/level.coffee
new file mode 100644
index 000000000..22e43e5f7
--- /dev/null
+++ b/server/handlers/level.coffee
@@ -0,0 +1,80 @@
+winston = require('winston')
+request = require('request')
+Level = require('../models/Level')
+Session = require('../models/LevelSession')
+SessionHandler = require('./level_session')
+Feedback = require('../models/LevelFeedback')
+Handler = require('./Handler')
+mongoose = require('mongoose')
+
+LevelHandler = class LevelHandler extends Handler
+ modelClass: Level
+ editableProperties: [
+ 'description'
+ 'documentation'
+ 'background'
+ 'nextLevel'
+ 'scripts'
+ 'thangs'
+ 'systems'
+ 'victory'
+ 'name'
+ 'i18n'
+ 'icon'
+ ]
+
+ getByRelationship: (req, res, args...) ->
+ return @getSession(req, res, args[0]) if args[1] is 'session'
+ return @getFeedback(req, res, args[0]) if args[1] is 'feedback'
+ return @sendNotFoundError(res)
+
+ getSession: (req, res, id) ->
+ @getDocumentForIdOrSlug id, (err, level) =>
+ return @sendDatabaseError(res, err) if err
+ return @sendNotFoundError(res) unless level?
+ return @sendUnauthorizedError(res) unless @hasAccessToDocument(req, level)
+
+ sessionQuery = {
+ level: {original: level.original.toString(), majorVersion: level.version.major}
+ creator: req.user.id
+ }
+ Session.findOne(sessionQuery).exec (err, doc) =>
+ return @sendDatabaseError(res, err) if err
+ if doc
+ res.send(doc)
+ res.end()
+ return
+
+ initVals = sessionQuery
+ initVals.state = {complete:false, scripts:{currentScript:null}} # will not save empty objects
+ initVals.permissions = [{target:req.user.id, access:'owner'}, {target:'public', access:'write'}]
+ session = new Session(initVals)
+ session.save (err) =>
+ return @sendDatabaseError(res, err) if err
+ @sendSuccess(res, @formatEntity(req, session))
+ # TODO: tying things like @formatEntity and saveChangesToDocument don't make sense
+ # associated with the handler, because the handler might return a different type
+ # of model, like in this case. Refactor to move that logic to the model instead.
+
+ getFeedback: (req, res, id) ->
+ @getDocumentForIdOrSlug id, (err, level) =>
+ return @sendDatabaseError(res, err) if err
+ return @sendNotFoundError(res) unless level?
+ return @sendUnauthorizedError(res) unless @hasAccessToDocument(req, level, 'get')
+
+ feedbackQuery = {
+ creator: mongoose.Types.ObjectId(req.user.id.toString())
+ 'level.original': level.original.toString()
+ 'level.majorVersion': level.version.major
+ }
+
+ Feedback.findOne(feedbackQuery).exec (err, doc) =>
+ return @sendDatabaseError(res, err) if err
+ return @sendNotFoundError(res) unless doc?
+ res.send(doc)
+ res.end()
+ return
+
+ postEditableProperties: ['name']
+
+module.exports = new LevelHandler()
diff --git a/server/handlers/level_component.coffee b/server/handlers/level_component.coffee
new file mode 100644
index 000000000..70ade0f3e
--- /dev/null
+++ b/server/handlers/level_component.coffee
@@ -0,0 +1,29 @@
+winston = require('winston')
+request = require('request')
+LevelComponent = require('../models/LevelComponent')
+Handler = require('./Handler')
+
+LevelComponentHandler = class LevelComponentHandler extends Handler
+ modelClass: LevelComponent
+ editableProperties: [
+ 'system'
+ 'description'
+ 'code'
+ 'js'
+ 'language'
+ 'dependencies'
+ 'propertyDocumentation'
+ 'configSchema'
+ ]
+ postEditableProperties: ['name']
+
+ getEditableProperties: (req, document) ->
+ props = super(req, document)
+ props.push('official') if req.user?.isAdmin()
+ props
+
+ hasAccess: (req) ->
+ req.method is 'GET' or req.user?.isAdmin()
+
+
+module.exports = new LevelComponentHandler()
\ No newline at end of file
diff --git a/server/handlers/level_draft.coffee b/server/handlers/level_draft.coffee
new file mode 100644
index 000000000..4900c766e
--- /dev/null
+++ b/server/handlers/level_draft.coffee
@@ -0,0 +1,15 @@
+winston = require('winston')
+request = require('request')
+LevelDraft = require('../models/LevelDraft')
+Handler = require('./Handler')
+
+LevelDraftHandler = class LevelDraftHandler extends Handler
+ modelClass: LevelDraft
+ editableProperties: ['level']
+ postEditableProperties: ['user']
+
+ post: (req, res) ->
+ req.body.user = req.user._id
+ super(req, res)
+
+module.exports = new LevelDraftHandler()
\ No newline at end of file
diff --git a/server/handlers/level_feedback.coffee b/server/handlers/level_feedback.coffee
new file mode 100644
index 000000000..d0b5292a2
--- /dev/null
+++ b/server/handlers/level_feedback.coffee
@@ -0,0 +1,14 @@
+LevelFeedback = require('../models/LevelFeedback')
+Handler = require('./Handler')
+
+class LevelFeedbackHandler extends Handler
+ modelClass: LevelFeedback
+ editableProperties: ['rating', 'review', 'level', 'levelID', 'levelName']
+
+ makeNewInstance: (req) ->
+ feedback = super(req)
+ feedback.set('creator', req.user._id)
+ feedback.set('creatorName', req.user.get('name') or '')
+ feedback
+
+module.exports = new LevelFeedbackHandler()
diff --git a/server/handlers/level_session.coffee b/server/handlers/level_session.coffee
new file mode 100644
index 000000000..198ebe337
--- /dev/null
+++ b/server/handlers/level_session.coffee
@@ -0,0 +1,22 @@
+LevelSession = require('../models/LevelSession')
+Handler = require('./Handler')
+
+TIMEOUT = 1000 * 30 # no activity for 30 seconds means it's not active
+
+class LevelSessionHandler extends Handler
+ modelClass: LevelSession
+ editableProperties: ['multiplayer', 'players', 'code', 'completed', 'state',
+ 'levelName', 'creatorName', 'levelID', 'screenshot',
+ 'chat']
+
+ getByRelationship: (req, res, args...) ->
+ return @sendNotFoundError(res) unless args.length is 2 and args[1] is 'active'
+ start = new Date()
+ start = new Date(start.getTime() - TIMEOUT)
+ query = @modelClass.find({'changed': {$gt: start}})
+ query.exec (err, documents) =>
+ return @sendDatabaseError(res, err) if err
+ documents = (@formatEntity(req, doc) for doc in documents)
+ @sendSuccess(res, documents)
+
+module.exports = new LevelSessionHandler()
diff --git a/server/handlers/level_system.coffee b/server/handlers/level_system.coffee
new file mode 100644
index 000000000..5c237843d
--- /dev/null
+++ b/server/handlers/level_system.coffee
@@ -0,0 +1,28 @@
+winston = require('winston')
+request = require('request')
+LevelSystem = require('../models/LevelSystem')
+Handler = require('./Handler')
+
+LevelSystemHandler = class LevelSystemHandler extends Handler
+ modelClass: LevelSystem
+ editableProperties: [
+ 'description'
+ 'code'
+ 'js'
+ 'language'
+ 'dependencies'
+ 'propertyDocumentation'
+ 'configSchema'
+ ]
+ postEditableProperties: ['name']
+
+ getEditableProperties: (req, document) ->
+ props = super(req, document)
+ props.push('official') if req.user?.isAdmin()
+ props
+
+ hasAccess: (req) ->
+ req.method is 'GET' or req.user?.isAdmin()
+
+
+module.exports = new LevelSystemHandler()
diff --git a/server/handlers/thang_type.coffee b/server/handlers/thang_type.coffee
new file mode 100644
index 000000000..d80b70e48
--- /dev/null
+++ b/server/handlers/thang_type.coffee
@@ -0,0 +1,27 @@
+winston = require('winston')
+request = require('request')
+ThangType = require('../models/ThangType')
+Handler = require('./Handler')
+
+ThangTypeHandler = class ThangTypeHandler extends Handler
+ modelClass: ThangType
+ editableProperties: [
+ 'name',
+ 'raw',
+ 'actions',
+ 'soundTriggers',
+ 'rotationType',
+ 'matchWorldDimensions',
+ 'shadow',
+ 'layerPriority',
+ 'staticImage',
+ 'scale',
+ 'positions',
+ 'snap',
+ 'components'
+ ]
+
+ hasAccess: (req) ->
+ req.method is 'GET' or req.user?.isAdmin()
+
+module.exports = new ThangTypeHandler()
diff --git a/server/handlers/user.coffee b/server/handlers/user.coffee
new file mode 100644
index 000000000..2e41af302
--- /dev/null
+++ b/server/handlers/user.coffee
@@ -0,0 +1,167 @@
+winston = require('winston')
+schema = require('../schemas/user')
+crypto = require('crypto')
+request = require('request')
+User = require('../models/User')
+Handler = require('./Handler')
+languages = require '../languages'
+mongoose = require 'mongoose'
+
+serverProperties = ['passwordHash', 'emailLower', 'nameLower', 'passwordReset']
+privateProperties = ['permissions', 'email', 'firstName', 'lastName', 'gender', 'facebookID', 'music', 'volume']
+
+UserHandler = class UserHandler extends Handler
+ modelClass: User
+
+ editableProperties: [
+ 'name', 'photoURL', 'password', 'anonymous', 'wizardColor1', 'volume',
+ 'firstName', 'lastName', 'gender', 'facebookID', 'emailSubscriptions',
+ 'testGroupNumber', 'music', 'hourOfCode', 'hourOfCodeComplete', 'preferredLanguage'
+ ]
+
+ jsonSchema: schema
+
+ formatEntity: (req, document) ->
+ return null unless document?
+ obj = document.toObject()
+ delete obj[prop] for prop in serverProperties
+ includePrivates = req.user and (req.user.isAdmin() or req.user._id.equals(document._id))
+ delete obj[prop] for prop in privateProperties unless includePrivates
+
+ # emailHash is used by gravatar
+ hash = crypto.createHash('md5')
+ if document.get('email')
+ hash.update(_.trim(document.get('email')).toLowerCase())
+ else
+ hash.update(@_id+'')
+ obj.emailHash = hash.digest('hex')
+
+ return obj
+
+ waterfallFunctions: [
+ # FB access token checking
+ # Check the email is the same as FB reports
+ (req, user, callback) ->
+ fbID = req.query.facebookID
+ fbAT = req.query.facebookAccessToken
+ return callback(null, req, user) unless fbID and fbAT
+ url = "https://graph.facebook.com/me?access_token=#{fbAT}"
+ request(url, (error, response, body) =>
+ body = JSON.parse(body)
+ emailsMatch = req.body.email is body.email
+ return callback(res:'Invalid Facebook Access Token.', code:422) unless emailsMatch
+ callback(null, req, user)
+ )
+
+ # GPlus access token checking
+ (req, user, callback) ->
+ gpID = req.query.gplusID
+ gpAT = req.query.gplusAccessToken
+ return callback(null, req, user) unless gpID and gpAT
+ url = "https://www.googleapis.com/oauth2/v2/userinfo?access_token=#{gpAT}"
+ request(url, (error, response, body) =>
+ body = JSON.parse(body)
+ emailsMatch = req.body.email is body.email
+ return callback(res:'Invalid G+ Access Token.', code:422) unless emailsMatch
+ callback(null, req, user)
+ )
+
+ # Email setting
+ (req, user, callback) ->
+ return callback(null, req, user) unless req.body.email?
+ emailLower = req.body.email.toLowerCase()
+ return callback(null, req, user) if emailLower is user.get('emailLower')
+ User.findOne({emailLower:emailLower}).exec (err, otherUser) ->
+ return callback(res:'Database error.', code:500) if err
+
+ if (req.query.gplusID or req.query.facebookID) and otherUser
+ # special case, log in as that user
+ return req.logIn(otherUser, (err) ->
+ return callback(res:'Facebook user login error.', code:500) if err
+ return callback(null, req, otherUser)
+ )
+ r = {message:'is already used by another account', property:'email'}
+ return callback({res:r, code:409}) if otherUser
+ user.set('email', req.body.email)
+ callback(null, req, user)
+
+ # Name setting
+ (req, user, callback) ->
+ return callback(null, req, user) unless req.body.name
+ nameLower = req.body.name?.toLowerCase()
+ return callback(null, req, user) if nameLower is user.get('nameLower')
+ User.findOne({nameLower:nameLower}).exec (err, otherUser) ->
+ return callback(res:'Database error.', code:500) if err
+ r = {message:'is already used by another account', property:'name'}
+ return callback({res:r, code:409}) if otherUser
+ user.set('name', req.body.name)
+ callback(null, req, user)
+ ]
+
+ getById: (req, res, id) ->
+ if req.user and req.user._id.equals(id)
+ return @sendSuccess(res, @formatEntity(req, req.user))
+ super(req, res, id)
+
+ post: (req, res) ->
+ return @sendBadInputError(res, 'No input.') if _.isEmpty(req.body)
+ return @sendBadInputError(res, 'Existing users cannot create new ones.') unless req.user.get('anonymous')
+ req.body._id = req.user._id if req.user.get('anonymous')
+ @put(req, res)
+
+ hasAccessToDocument: (req, document) ->
+ if req.route.method in ['put', 'post', 'patch']
+ return true if req.user.isAdmin()
+ return req.user._id.equals(document._id)
+ return true
+
+ getByRelationship: (req, res, args...) ->
+ return @agreeToCLA(req, res) if args[1] is 'agreeToCLA'
+ return @sendNotFoundError(res)
+
+ agreeToCLA: (req, res) ->
+ doc =
+ user: req.user._id+''
+ email: req.user.get 'email'
+ name: req.user.get 'name'
+ githubUsername: req.body.githubUsername
+ created: new Date()
+ collection = mongoose.connection.db.collection 'cla.submissions', (err, collection) ->
+ return @sendDatabaseError(res, err) if err
+ collection.insert doc, (err) ->
+ return @sendDatabaseError(res, err) if err
+ req.user.set('signedCLA', doc.created)
+ req.user.save (err) ->
+ return @sendDatabaseError(res, err) if err
+ res.send({result:'success'})
+ res.end()
+
+module.exports = new UserHandler()
+
+module.exports.setupMiddleware = (app) ->
+ app.use (req, res, next) ->
+ if req.user
+ next()
+ else
+ user = new User({anonymous:true})
+ user.set 'testGroupNumber', Math.floor(Math.random() * 256) # also in app/lib/auth
+ user.set 'preferredLanguage', languages.languageCodeFromAcceptedLanguages req.acceptedLanguages
+ loginUser(req, res, user, false, next)
+
+loginUser = (req, res, user, send=true, next=null) ->
+ user.save((err) ->
+ if err
+ res.status(500)
+ return res.end()
+
+ req.logIn(user, (err) ->
+ if err
+ res.status(500)
+ return res.end()
+
+ if send
+ res.send(user)
+ return res.end()
+ next() if next
+ )
+ )
diff --git a/server/languages.coffee b/server/languages.coffee
new file mode 100644
index 000000000..70c1b717f
--- /dev/null
+++ b/server/languages.coffee
@@ -0,0 +1,49 @@
+errors = require './errors'
+winston = require 'winston'
+locale = require '../app/locale/locale' # requiring from app; will break if we stop serving from where app lives
+
+module.exports.setupRoutes = (app) ->
+ app.all '/languages/add/:lang/:namespace', (req, res) ->
+ # Should probably store these somewhere
+ winston.info "#{req.params.lang}.#{req.params.namespace} missing an i18n key:", req.body
+ res.send('')
+ res.end()
+
+ app.all '/languages', (req, res) ->
+ # Now that these are in the client, not sure when we would use this, but hey
+ return errors.badMethod(res) if req.route.method isnt 'get'
+ res.send(languages)
+ return res.end()
+
+languages = []
+for code, localeInfo of locale
+ languages.push code: code, nativeDescription: localeInfo.nativeDescription, englishDescription: localeInfo.englishDescription
+
+module.exports.languages = languages
+module.exports.languageCodes = languageCodes = (language.code for language in languages)
+module.exports.languageCodesLower = languageCodesLower = (code.toLowerCase() for code in languageCodes)
+
+# Keep keys lower-case for matching and values with second subtag uppercase like i18next expects
+languageAliases =
+ 'en': 'en-US'
+
+ 'zh-cn': 'zh-HANS'
+ 'zh-hans-cn': 'zh-HANS'
+ 'zh-sg': 'zh-HANS'
+ 'zh-hans-sg': 'zh-HANS'
+
+ 'zh-tw': 'zh-HANT'
+ 'zh-hant-tw': 'zh-HANT'
+ 'zh-hk': 'zh-HANT'
+ 'zh-hant-hk': 'zh-HANT'
+ 'zh-mo': 'zh-HANT'
+ 'zh-hant-mo': 'zh-HANT'
+
+module.exports.languageCodeFromAcceptedLanguages = languageCodeFromAcceptedLanguages = (acceptedLanguages) ->
+ for lang in acceptedLanguages ? []
+ code = languageAliases[lang.toLowerCase()]
+ return code if code
+ codeIndex = _.indexOf languageCodesLower, lang
+ if codeIndex isnt -1
+ return languageCodes[codeIndex]
+ return 'en-US'
diff --git a/server/logging.coffee b/server/logging.coffee
new file mode 100644
index 000000000..1a8e4d7ed
--- /dev/null
+++ b/server/logging.coffee
@@ -0,0 +1,8 @@
+winston = require('winston')
+
+module.exports.setup = ->
+ winston.remove(winston.transports.Console)
+ winston.add(winston.transports.Console,
+ colorize: true,
+ timestamp: true
+ )
\ No newline at end of file
diff --git a/server/models/Article.coffee b/server/models/Article.coffee
new file mode 100644
index 000000000..d4d4571ee
--- /dev/null
+++ b/server/models/Article.coffee
@@ -0,0 +1,12 @@
+mongoose = require('mongoose')
+plugins = require('./plugins')
+
+ArticleSchema = new mongoose.Schema(
+ body: String,
+)
+
+ArticleSchema.plugin(plugins.NamedPlugin)
+ArticleSchema.plugin(plugins.VersionedPlugin)
+ArticleSchema.plugin(plugins.SearchablePlugin, {searchable: ['body', 'name']})
+
+module.exports = mongoose.model('article', ArticleSchema)
diff --git a/server/models/Campaign.coffee b/server/models/Campaign.coffee
new file mode 100644
index 000000000..37ad7a3ab
--- /dev/null
+++ b/server/models/Campaign.coffee
@@ -0,0 +1,22 @@
+mongoose = require('mongoose')
+plugins = require('./plugins')
+
+NestedLevelSchema = new mongoose.Schema(
+ name: String
+ description: String
+ thumbnail: Buffer
+ original: {type: mongoose.Schema.ObjectId, ref: 'level'}
+ majorVersion: Number
+)
+
+CampaignSchema = new mongoose.Schema(
+ description: String
+ levels: [NestedLevelSchema]
+)
+
+CampaignSchema.plugin(plugins.NamedPlugin)
+CampaignSchema.plugin(plugins.PermissionsPlugin)
+CampaignSchema.plugin(plugins.SearchablePlugin, {searchable: ['name', 'description']})
+
+module.exports = Campaign = mongoose.model('campaign', CampaignSchema)
+
diff --git a/server/models/CampaignStatus.coffee b/server/models/CampaignStatus.coffee
new file mode 100644
index 000000000..77058902a
--- /dev/null
+++ b/server/models/CampaignStatus.coffee
@@ -0,0 +1,17 @@
+mongoose = require('mongoose')
+
+LevelStatusSchema = new mongoose.Schema(
+ lastPlayed: Date
+ victorious: Boolean
+ original: {type: mongoose.Schema.ObjectId, ref: 'level'}
+ majorVersion: Number
+)
+
+CampaignStatusSchema = new mongoose.Schema(
+ user: {type: mongoose.Schema.ObjectId, ref: 'User'}
+ campaign: {type: mongoose.Schema.ObjectId, ref: 'campaign'}
+ levelStatuses: [LevelStatusSchema]
+)
+CampaignStatusSchema.index {user: 1, campaign: 1}, {unique: true}
+
+module.exports = CampaignStatus = mongoose.model('campaign.status', CampaignStatusSchema)
\ No newline at end of file
diff --git a/server/models/File.coffee b/server/models/File.coffee
new file mode 100644
index 000000000..915744498
--- /dev/null
+++ b/server/models/File.coffee
@@ -0,0 +1,8 @@
+mongoose = require('mongoose')
+plugins = require('./plugins')
+
+FileSchema = new mongoose.Schema()
+
+FileSchema.plugin(plugins.SearchablePlugin, {searchable: ['metadata.description', 'metadata.name']})
+
+module.exports = mongoose.model('media.files', FileSchema)
diff --git a/server/models/Level.coffee b/server/models/Level.coffee
new file mode 100644
index 000000000..31e8871ec
--- /dev/null
+++ b/server/models/Level.coffee
@@ -0,0 +1,24 @@
+mongoose = require('mongoose')
+plugins = require('./plugins')
+jsonschema = require('../schemas/level')
+
+LevelSchema = new mongoose.Schema({
+ description: String
+}, {strict: false})
+
+LevelSchema.plugin(plugins.NamedPlugin)
+LevelSchema.plugin(plugins.PermissionsPlugin)
+LevelSchema.plugin(plugins.VersionedPlugin)
+LevelSchema.plugin(plugins.SearchablePlugin, {searchable: ['name', 'description']})
+
+LevelSchema.pre 'init', (next) ->
+ return next() unless jsonschema.properties?
+ for prop, sch of jsonschema.properties
+ @set(prop, _.cloneDeep(sch.default)) if sch.default?
+ next()
+
+LevelSchema.post 'init', (doc) ->
+ if _.isString(doc.get('nextLevel'))
+ doc.set('nextLevel', undefined)
+
+module.exports = Level = mongoose.model('level', LevelSchema)
diff --git a/server/models/LevelComponent.coffee b/server/models/LevelComponent.coffee
new file mode 100644
index 000000000..27789748f
--- /dev/null
+++ b/server/models/LevelComponent.coffee
@@ -0,0 +1,21 @@
+mongoose = require('mongoose')
+plugins = require('./plugins')
+jsonschema = require('../schemas/level_component')
+
+LevelComponentSchema = new mongoose.Schema {
+ description: String
+ system: String
+}, {strict: false}
+
+LevelComponentSchema.plugin(plugins.NamedPlugin)
+LevelComponentSchema.plugin(plugins.PermissionsPlugin)
+LevelComponentSchema.plugin(plugins.VersionedPlugin)
+LevelComponentSchema.plugin(plugins.SearchablePlugin, {searchable: ['name', 'description', 'system']})
+
+LevelComponentSchema.pre 'init', (next) ->
+ return next() unless jsonschema.properties?
+ for prop, sch of jsonschema.properties
+ @set(prop, _.cloneDeep sch.default) if sch.default?
+ next()
+
+module.exports = LevelComponent = mongoose.model('level.component', LevelComponentSchema)
diff --git a/server/models/LevelDraft.coffee b/server/models/LevelDraft.coffee
new file mode 100644
index 000000000..3b82a3129
--- /dev/null
+++ b/server/models/LevelDraft.coffee
@@ -0,0 +1,9 @@
+mongoose = require('mongoose')
+Level = require('./Level')
+
+LevelDraftSchema = new mongoose.Schema(
+ user: {type: mongoose.Schema.ObjectId, ref: 'User'}
+ level: {}
+)
+
+module.exports = LevelDraft = mongoose.model('level.draft', LevelDraftSchema)
\ No newline at end of file
diff --git a/server/models/LevelFeedback.coffee b/server/models/LevelFeedback.coffee
new file mode 100644
index 000000000..9561ec4fb
--- /dev/null
+++ b/server/models/LevelFeedback.coffee
@@ -0,0 +1,13 @@
+# TODO: not updated since rename from level_instance, or since we redid how all models are done; probably busted
+
+mongoose = require('mongoose')
+plugins = require('./plugins')
+jsonschema = require('../schemas/level_feedback')
+
+LevelFeedbackSchema = new mongoose.Schema({
+ created:
+ type: Date
+ 'default': Date.now
+}, {strict: false})
+
+module.exports = LevelFeedback = mongoose.model('level.feedback', LevelFeedbackSchema)
\ No newline at end of file
diff --git a/server/models/LevelSession.coffee b/server/models/LevelSession.coffee
new file mode 100644
index 000000000..c96bf7e3d
--- /dev/null
+++ b/server/models/LevelSession.coffee
@@ -0,0 +1,25 @@
+# TODO: not updated since rename from level_instance, or since we redid how all models are done; probably busted
+
+mongoose = require('mongoose')
+plugins = require('./plugins')
+jsonschema = require('../schemas/level_session')
+
+LevelSessionSchema = new mongoose.Schema({
+ created:
+ type: Date
+ 'default': Date.now
+}, {strict: false})
+LevelSessionSchema.plugin(plugins.PermissionsPlugin)
+
+LevelSessionSchema.pre 'init', (next) ->
+ # TODO: refactor this into a set of common plugins for all models?
+ return next() unless jsonschema.properties?
+ for prop, sch of jsonschema.properties
+ @set(prop, _.cloneDeep(sch.default)) if sch.default?
+ next()
+
+LevelSessionSchema.pre 'save', (next) ->
+ @set('changed', new Date())
+ next()
+
+module.exports = LevelSession = mongoose.model('level.session', LevelSessionSchema)
\ No newline at end of file
diff --git a/server/models/LevelSystem.coffee b/server/models/LevelSystem.coffee
new file mode 100644
index 000000000..1be4a1ad9
--- /dev/null
+++ b/server/models/LevelSystem.coffee
@@ -0,0 +1,20 @@
+mongoose = require('mongoose')
+plugins = require('./plugins')
+jsonschema = require('../schemas/level_system')
+
+LevelSystemSchema = new mongoose.Schema {
+ description: String
+}, {strict: false}
+
+LevelSystemSchema.plugin(plugins.NamedPlugin)
+LevelSystemSchema.plugin(plugins.PermissionsPlugin)
+LevelSystemSchema.plugin(plugins.VersionedPlugin)
+LevelSystemSchema.plugin(plugins.SearchablePlugin, {searchable: ['name', 'description']})
+
+LevelSystemSchema.pre 'init', (next) ->
+ return next() unless jsonschema.properties?
+ for prop, sch of jsonschema.properties
+ @set(prop, _.cloneDeep sch.default) if sch.default?
+ next()
+
+module.exports = LevelSystem = mongoose.model('level.system', LevelSystemSchema)
diff --git a/server/models/LevelThangType.coffee b/server/models/LevelThangType.coffee
new file mode 100644
index 000000000..9eb626dcb
--- /dev/null
+++ b/server/models/LevelThangType.coffee
@@ -0,0 +1,64 @@
+mongoose = require('mongoose')
+plugins = require('./plugins')
+
+LevelComponentSchema = new mongoose.Schema(
+ original: {type: mongoose.Schema.ObjectId, ref: 'level.session'}
+ majorVersion: Number
+)
+
+ThangSoundSchema = new mongoose.Schema(
+ events: [String]
+ actions: [String]
+ files: [String]
+ delay: Number
+)
+
+SpriteHueLayer = new mongoose.Schema(
+ name: String
+ hueRange:
+ min: Number
+ max: Number
+ saturationRange:
+ min: Number
+ max: Number
+ brightnessRange:
+ min: Number
+ max: Number
+)
+
+LevelThangTypeSchema = new mongoose.Schema()
+
+LevelThangTypeSchema.add(
+ components: [LevelComponentSchema]
+ description: String
+ media:
+ sounds: [ThangSoundSchema]
+ display:
+ alpha: Number
+ children: [LevelThangTypeSchema]
+ offset: [Number]
+ hueLayers: [SpriteHueLayer]
+ rotation: String
+ scale:
+ both: Number
+ toWorld: Boolean
+ shadow: String
+ width: Number
+ height: Number
+ sheetFromFile:
+ images: [String]
+ frames: [Array]
+ animations: mongoose.Schema.Types.Mixed
+ customSheet:
+ images: [String]
+ frames: [Array]
+ animations: mongoose.Schema.Types.Mixed
+ z: Number
+ image: String
+)
+
+LevelThangTypeSchema.plugin(plugins.PermissionsPlugin)
+LevelThangTypeSchema.plugin(plugins.NamedPlugin)
+LevelThangTypeSchema.plugin(plugins.SearchablePlugin, {searchable: ['name', 'description']})
+
+module.exports = LevelThangType = mongoose.model('level.thang_type', LevelThangTypeSchema)
diff --git a/server/models/ThangType.coffee b/server/models/ThangType.coffee
new file mode 100644
index 000000000..72e291a79
--- /dev/null
+++ b/server/models/ThangType.coffee
@@ -0,0 +1,12 @@
+mongoose = require('mongoose')
+plugins = require('./plugins')
+
+ThangTypeSchema = new mongoose.Schema({
+ body: String,
+}, {strict: false})
+
+ThangTypeSchema.plugin(plugins.NamedPlugin)
+ThangTypeSchema.plugin(plugins.VersionedPlugin)
+ThangTypeSchema.plugin(plugins.SearchablePlugin, {searchable: ['name']})
+
+module.exports = mongoose.model('thang.type', ThangTypeSchema)
diff --git a/server/models/User.coffee b/server/models/User.coffee
new file mode 100644
index 000000000..69cf22c18
--- /dev/null
+++ b/server/models/User.coffee
@@ -0,0 +1,92 @@
+mongoose = require('mongoose')
+jsonschema = require('../schemas/user')
+crypto = require('crypto')
+{salt} = require('../../server_config')
+
+UserSchema = new mongoose.Schema({
+ dateCreated:
+ type: Date
+ 'default': Date.now
+}, {strict: false})
+
+UserSchema.pre('init', (next) ->
+ return next() unless jsonschema.properties?
+ for prop, sch of jsonschema.properties
+ @set(prop, sch.default) if sch.default?
+ next()
+)
+
+UserSchema.post('init', ->
+ @set('anonymous', false) if @get('email')
+ @currentSubscriptions = JSON.stringify(@get('emailSubscriptions'))
+)
+
+UserSchema.methods.isAdmin = ->
+ p = @get('permissions')
+ return p and 'admin' in p
+
+UserSchema.statics.updateMailChimp = (doc, callback) ->
+ return callback?() if doc.updatedMailChimp
+ return callback?() unless doc.get('email')
+ existingProps = doc.get('mailChimp')
+ emailChanged = (not existingProps) or existingProps?.email isnt doc.get('email')
+ emailSubs = doc.get('emailSubscriptions')
+ newGroups = (groupingMap[name] for name in emailSubs)
+ if (not existingProps) and newGroups.length is 0
+ return callback?() # don't add totally unsubscribed people to the list
+ subsChanged = doc.currentSubscriptions isnt JSON.stringify(emailSubs)
+ return callback?() unless emailChanged or subsChanged
+
+ params = {}
+ params.id = MAILCHIMP_LIST_ID
+ params.email = if existingProps then {leid:existingProps.leid} else {email:doc.get('email')}
+ params.merge_vars = { groupings: [ {id: MAILCHIMP_GROUP_ID, groups: newGroups} ] }
+ params.update_existing = true
+
+ onSuccess = (data) ->
+ doc.set('mailChimp', data)
+ doc.updatedMailChimp = true
+ doc.save()
+ callback?()
+
+ onFailure = (error) ->
+ console.error 'failed to subscribe', error, callback?
+ doc.updatedMailChimp = true
+ callback?()
+
+ mc.lists.subscribe params, onSuccess, onFailure
+
+
+UserSchema.pre('save', (next) ->
+ @set('emailLower', @get('email')?.toLowerCase())
+ @set('nameLower', @get('name')?.toLowerCase())
+ pwd = @get('password')
+ if @get('password')
+ @set('passwordHash', User.hashPassword(pwd))
+ @set('password', undefined)
+ @set('anonymous', false) if @get('email')
+ next()
+)
+
+MAILCHIMP_LIST_ID = 'e9851239eb'
+MAILCHIMP_GROUP_ID = '4529'
+
+groupingMap =
+ announcement: 'Announcements'
+ tester: 'Adventurers'
+ level_creator: 'Artisans'
+ developer: 'Archmages'
+ article_editor: 'Scribes'
+ translator: 'Diplomats'
+ support: 'Ambassadors'
+
+UserSchema.post 'save', (doc) ->
+ UserSchema.statics.updateMailChimp(doc)
+
+UserSchema.statics.hashPassword = (password) ->
+ password = password.toLowerCase()
+ shasum = crypto.createHash('sha512')
+ shasum.update(salt + password)
+ shasum.digest('hex')
+
+module.exports = User = mongoose.model('User', UserSchema)
\ No newline at end of file
diff --git a/server/models/plugins.coffee b/server/models/plugins.coffee
new file mode 100644
index 000000000..93da928f6
--- /dev/null
+++ b/server/models/plugins.coffee
@@ -0,0 +1,264 @@
+mongoose = require('mongoose')
+User = require('./User')
+textSearch = require('mongoose-text-search')
+
+module.exports.NamedPlugin = (schema) ->
+ schema.add({name: String, slug: String})
+ schema.index({'slug': 1}, {unique: true, sparse: true, name: 'slug index'})
+
+ schema.pre('save', (next) ->
+ if schema.uses_coco_versions
+ v = @get('version')
+ return next() unless v.isLatestMajor and v.isLatestMinor
+
+ newSlug = _.str.slugify(@get('name'))
+ if newSlug isnt @get('slug')
+ @set('slug', newSlug)
+ @checkSlugConflicts(next)
+ else
+ next()
+ )
+
+ schema.methods.checkSlugConflicts = (done) ->
+ slug = @get('slug')
+
+ try
+ id = mongoose.Types.ObjectId.createFromHexString(slug)
+ err = new Error('Bad name.')
+ err.response = {message:'cannot be like a MondoDB id, Mr Hacker.', property:'name'}
+ err.code = 422
+ done(err)
+ catch e
+
+ query = { slug:slug }
+
+ if @get('original')
+ query.original = {'$ne':@original}
+ else if @_id
+ query._id = {'$ne':@_id}
+
+ @model(@constructor.modelName).count query, (err, count) ->
+ if count
+ err = new Error('Slug conflict.')
+ err.response = {message:'is already in use', property:'name'}
+ err.code = 409
+ done(err)
+ done()
+
+
+
+module.exports.PermissionsPlugin = (schema) ->
+ schema.uses_coco_permissions = true
+
+ PermissionSchema = new mongoose.Schema
+ target: mongoose.Schema.Types.Mixed
+ access: {type: String, 'enum':['read', 'write', 'owner']}
+ , {id: false, _id: false}
+
+ schema.add(permissions: [PermissionSchema])
+
+ schema.pre 'save', (next) ->
+ return next() if @getOwner()
+ err = new Error('Permissions needs an owner.')
+ err.response = {message:'needs an owner.', property:'permissions'}
+ err.code = 409
+ next(err)
+
+ schema.methods.hasPermissionsForMethod = (actor, method) ->
+ method = method.toLowerCase()
+ # method is 'get', 'put', 'patch', 'post', or 'delete'
+ # actor is a User object
+
+ allowed =
+ get: ['read', 'write', 'owner']
+ put: ['write', 'owner']
+ patch: ['write', 'owner']
+ post: ['write', 'owner'] # used to post new versions of something
+ delete: [] # nothing may go!
+
+ allowed = allowed[method] or []
+
+ for permission in @permissions
+ if permission.target is 'public' or actor._id.equals(permission.target)
+ return true if permission.access in allowed
+
+ return false
+
+ schema.methods.getOwner = ->
+ for permission in @permissions
+ if permission.access is 'owner'
+ return permission.target
+
+ schema.methods.getPublicAccess = ->
+ for permission in @permissions
+ if permission.target is 'public'
+ return permission.access
+
+ schema.methods.getAccessForUserObjectId = (objectId) ->
+ public_access = null
+ for permission in @permissions
+ if permission.target is 'public'
+ public_access = permission.access
+ continue
+ if objectId.equals(permission.target)
+ return permission.access
+ return public_access
+
+module.exports.VersionedPlugin = (schema) ->
+ schema.uses_coco_versions = true
+
+ schema.add(
+ version:
+ major: {type: Number, 'default': 0}
+ minor: {type: Number, 'default': 0}
+ isLatestMajor: {type: Boolean, 'default': true}
+ isLatestMinor: {type: Boolean, 'default': true}
+ original: {type: mongoose.Schema.ObjectId, ref: @modelName}
+ parent: {type: mongoose.Schema.ObjectId, ref: @modelName}
+ creator: {type: mongoose.Schema.ObjectId, ref: 'User'}
+ created: { type: Date, 'default': Date.now }
+ commitMessage: {type: String}
+ )
+
+ # Prevent multiple documents with the same version
+ # Also used for looking up latest version, or specific versions.
+ schema.index({'original': 1, 'version.major': -1, 'version.minor': -1}, {unique: true, name: 'version index'})
+
+ schema.statics.getLatestMajorVersion = (original, options, done) ->
+ options = options or {}
+ query = @findOne({original:original, 'version.isLatestMajor':true})
+ query.select(options.select) if options.select
+ query.exec((err, latest) =>
+ return done(err) if err
+ return done(null, latest) if latest
+
+ # handle the case where no version is marked as the latest
+ q = @find({original:original})
+ q.sort({'version.major':-1, 'version.minor':-1})
+ q.select(options.select) if options.select
+ q.limit(1)
+ q.exec((err, latest) =>
+ return done(err) if err
+ return done(null, null) if latest.length is 0
+ latest = latest[0]
+
+ # don't fix missing versions by default. In all likelihood, it's about to change anyway
+ if options.autofix
+ latest.version.isLatestMajor = true
+ latest.version.isLatestMinor = true
+ latestObject = latest.toObject()
+ @update({_id: latest._id}, {version: latestObject.version})
+ done(null, latest)
+ )
+ )
+
+ schema.statics.getLatestMinorVersion = (original, majorVersion, options, done) ->
+ options = options or {}
+ query = @findOne({original:original, 'version.isLatestMinor':true, 'version.major':majorVersion})
+ query.select(options.select) if options.select
+ query.exec((err, latest) =>
+ return done(err) if err
+ return done(null, latest) if latest
+ q = @find({original:original, 'version.major':majorVersion})
+ q.sort({'version.minor':-1})
+ q.select(options.select) if options.select
+ q.limit(1)
+ q.exec((err, latest) ->
+ return done(err) if err
+ return done(null, null) if latest.length is 0
+ latest = latest[0]
+
+ if options.autofix
+ latestObject = latest.toObject()
+ latestObject.version.isLatestMajor = true
+ latestObject.version.isLatestMinor = true
+ @update({_id: latest._id}, {version: latestObject.version})
+ done(null, latest)
+ )
+ )
+
+ schema.methods.makeNewMajorVersion = (newObject, done) ->
+ Model = @model(@constructor.modelName)
+
+ latest = Model.getLatestMajorVersion(@original, {select:'version'}, (err, latest) =>
+ return done(err) if err
+
+ updatedObject = _.cloneDeep latestObject
+ # unmark the current latest major version in the database
+ latestObject = latest.toObject()
+ latestObject.version.isLatestMajor = false
+ Model.update({_id: latest._id}, {version: latestObject.version, $unset: {index:1, slug: 1} }, {}, (err) =>
+ return done(err) if err
+
+ newObject['version'] = { major: latest.version.major + 1 }
+ newObject.index = true
+ newObject.parent = @_id
+ delete newObject['_id']
+ delete newObject['created']
+ done(null, new Model(newObject))
+ )
+ )
+
+ schema.methods.makeNewMinorVersion = (newObject, majorVersion, done) ->
+ Model = @model(@constructor.modelName)
+
+ latest = Model.getLatestMinorVersion(@original, majorVersion, {select:'version'}, (err, latest) =>
+ return done(err) if err
+
+ # unmark the current latest major version in the database
+ latestObject = latest.toObject()
+ wasLatestMajor = latestObject.version.isLatestMajor
+ latestObject.version.isLatestMajor = false
+ latestObject.version.isLatestMinor = false
+ Model.update({_id: latest._id}, {version: latestObject.version, $unset: {index:1, slug: 1}}, {}, (err) =>
+ return done(err) if err
+
+ newObject['version'] =
+ major: latest.version.major
+ minor: latest.version.minor + 1
+ isLatestMajor: wasLatestMajor
+ if wasLatestMajor
+ newObject.index = true
+ else
+ delete newObject.index if newObject.index?
+ delete newObject.slug if newObject.slug?
+ newObject.parent = @_id
+ delete newObject['_id']
+ delete newObject['created']
+ done(null, new Model(newObject))
+ )
+ )
+
+
+module.exports.SearchablePlugin = (schema, options) ->
+ # this plugin must be added only after the others (specifically Versioned and Permissions)
+ # have been added, as how it builds the text search index depends on which of those are used.
+
+ searchable = options.searchable
+ unless searchable
+ throw Error('SearchablePlugin options must include list of searchable properties.')
+
+ index = {}
+
+ schema.uses_coco_search = true
+ if schema.uses_coco_versions or schema.uses_coco_permissions
+ index['index'] = 1
+ schema.add(index: mongoose.Schema.Types.Mixed)
+
+ index[prop] = 'text' for prop in searchable
+
+ # should now have something like {'index': 1, name:'text', body:'text'}
+ schema.plugin(textSearch)
+ schema.index(index, { sparse: true, name: 'search index', language_override: 'searchLanguage' })
+
+ schema.pre 'save', (next) ->
+ # never index old versions, index plugin handles un-indexing old versions
+ if schema.uses_coco_versions and ((not @version.isLatestMajor) or (not @version.isLatestMinor))
+ return next()
+
+ @index = true
+ if schema.uses_coco_permissions
+ access = @getPublicAccess()
+ @index = @getOwner() unless access
+
+ next()
diff --git a/server/schemas/article.coffee b/server/schemas/article.coffee
new file mode 100644
index 000000000..cf6c579ec
--- /dev/null
+++ b/server/schemas/article.coffee
@@ -0,0 +1,12 @@
+c = require './common'
+
+ArticleSchema = c.object()
+c.extendNamedProperties ArticleSchema # name first
+
+ArticleSchema.properties.body = { type: 'string', title: 'Content', format: 'markdown' }
+
+c.extendBasicProperties(ArticleSchema, 'article')
+c.extendSearchableProperties(ArticleSchema)
+c.extendVersionedProperties(ArticleSchema, 'article')
+
+module.exports = ArticleSchema
\ No newline at end of file
diff --git a/server/schemas/common.coffee b/server/schemas/common.coffee
new file mode 100644
index 000000000..8b091c48a
--- /dev/null
+++ b/server/schemas/common.coffee
@@ -0,0 +1,139 @@
+#language imports
+Language = require '../languages'
+# schema helper methods
+
+me = module.exports
+
+combine = (base, ext) ->
+ return base unless ext?
+ return _.extend(base, ext)
+
+# Common schema properties
+me.object = (ext, props) -> combine {type: 'object', additionalProperties: false, properties: props or {}}, ext
+me.array = (ext, items) -> combine {type: 'array', items: items or {}}, ext
+me.shortString = (ext) -> combine({type: 'string', maxLength: 100}, ext)
+me.pct = (ext) -> combine({type: 'number', maximum: 1.0, minimum: 0.0}, ext)
+me.date = (ext) -> combine({type: 'string', format: 'date-time'}, ext)
+me.objectId = (ext) -> schema = combine({type: ['object', 'string'] }, ext) # should just be string (Mongo ID), but sometimes mongoose turns them into objects representing those, so we are lenient
+
+PointSchema = me.object {title: "Point", description: "An {x, y} coordinate point.", format: "point2d", required: ["x", "y"]},
+ x: {title: "x", description: "The x coordinate.", type: "number", "default": 15}
+ y: {title: "y", description: "The y coordinate.", type: "number", "default": 20}
+
+me.point2d = (ext) -> combine(_.cloneDeep(PointSchema), ext)
+
+SoundSchema = me.object { format: 'sound' },
+ mp3: { type: 'string', format: 'sound-file' }
+ ogg: { type: 'string', format: 'sound-file' }
+
+me.sound = (props) ->
+ obj = _.cloneDeep(SoundSchema)
+ for prop of props
+ obj.properties[prop] = props[prop]
+ obj
+
+# BASICS
+
+basicProps = (linkFragment) ->
+ _id: me.objectId(links: [{rel: 'self', href: "/db/#{linkFragment}/{($)}"}], format: 'hidden')
+ __v: { title: 'Mongoose Version', format: 'hidden' }
+
+me.extendBasicProperties = (schema, linkFragment) ->
+ schema.properties = {} unless schema.properties?
+ _.extend(schema.properties, basicProps(linkFragment))
+
+
+# NAMED
+
+namedProps = ->
+ name: me.shortString({title: 'Name'})
+ slug: me.shortString({title: 'Slug', format: 'hidden'})
+
+me.extendNamedProperties = (schema) ->
+ schema.properties = {} unless schema.properties?
+ _.extend(schema.properties, namedProps())
+
+
+# VERSIONED
+
+versionedProps = (linkFragment) ->
+ version:
+ 'default': { minor: 0, major: 0, isLatestMajor: true, isLatestMinor: true }
+ format: 'version'
+ title: 'Version'
+ type: 'object'
+ readOnly: true
+ additionalProperties: false
+ properties:
+ major: { type: 'number', minimum: 0 }
+ minor: { type: 'number', minimum: 0 }
+ isLatestMajor: { type: 'boolean' }
+ isLatestMinor: { type: 'boolean' }
+ original: me.objectId(links: [{rel: 'extra', href: "/db/#{linkFragment}/{($)}"}], format: 'hidden') # TODO: figure out useful 'rel' values here
+ parent: me.objectId(links: [{rel: 'extra', href: "/db/#{linkFragment}/{($)}"}], format: 'hidden')
+ creator: me.objectId(links: [{rel: 'extra', href: "/db/user/{($)}"}], format: 'hidden')
+ created: me.date( { title: 'Created', readOnly: true })
+ commitMessage: { type: 'string', maxLength: 500, title: 'Commit Message', readOnly: true }
+
+me.extendVersionedProperties = (schema, linkFragment) ->
+ schema.properties = {} unless schema.properties?
+ _.extend(schema.properties, versionedProps(linkFragment))
+
+
+# SEARCHABLE
+
+searchableProps = ->
+ index: { format: 'hidden' }
+
+me.extendSearchableProperties = (schema) ->
+ schema.properties = {} unless schema.properties?
+ _.extend(schema.properties, searchableProps())
+
+
+# PERMISSIONED
+
+permissionsProps = ->
+ permissions:
+ type: 'array'
+ items:
+ type: 'object'
+ additionalProperties: false
+ properties:
+ target: {}
+ access: {type: 'string', 'enum': ['read', 'write', 'owner']}
+ format: "hidden"
+
+me.extendPermissionsProperties = (schema) ->
+ schema.properties = {} unless schema.properties?
+ _.extend(schema.properties, permissionsProps())
+
+# TRANSLATABLE
+
+me.generateLanguageCodeArrayRegex = -> "^(" + Language.languageCodes.join("|") + ")$"
+
+me.getLanguageCodeArray = ->
+ return Language.languageCodes
+
+me.getLanguagesObject = -> return Language
+
+# OTHER
+
+me.classNamePattern = "^[A-Z][A-Za-z0-9]*$" # starts with capital letter; just letters and numbers
+me.identifierPattern = "^[a-z][A-Za-z0-9]*$" # starts with lowercase letter; just letters and numbers
+
+me.FunctionArgumentSchema = me.object {
+ title: "Function Argument",
+ description: "Documentation entry for a function argument."
+ "default":
+ name: "target"
+ type: "Thang"
+ example: "this.getNearestEnemy()"
+ description: "The target of this function."
+ "default": null
+ required: ['name', 'type', 'example', 'description', 'default']
+},
+ name: {type: 'string', pattern: me.identifierPattern, title: "Name", description: "Name of the function argument."}
+ type: me.shortString(title: "Type", description: "Intended type of the argument.") # not actual JS types, just whatever they describe...
+ example: me.shortString(title: "Example", description: "Example value for the argument.")
+ description: {type: 'string', description: "Description of the argument.", maxLength: 1000}
+ "default": {title: "Default", description: "Default value of the argument. (Your code should set this.)", "default": null}
diff --git a/server/schemas/file.coffee b/server/schemas/file.coffee
new file mode 100644
index 000000000..0ea9cd52d
--- /dev/null
+++ b/server/schemas/file.coffee
@@ -0,0 +1,25 @@
+c = require './common'
+
+FileSchema = c.baseSchema()
+
+_.extend(FileSchema.properties, {
+ filename: c.shortStringProp()
+ contentType: c.shortStringProp()
+ length: { type: 'number' }
+ chunkSize: { type: 'number', format: 'hidden' }
+ uploadDate: { type: 'string' }
+ aliases: {}
+ metadata:
+ type: 'object'
+ additionalProperties: false
+ name: c.shortStringArrayProp()
+ description: { type: 'string' }
+ createdFor: { type: 'array', items: {}}
+ path: { type: 'string' }
+ creator: { type: 'string' }
+})
+
+c.extendSearchableProperties(FileSchema.properties.metadata)
+FileSchema.format = 'file'
+
+module.exports = FileSchema
\ No newline at end of file
diff --git a/server/schemas/i18n.coffee b/server/schemas/i18n.coffee
new file mode 100644
index 000000000..994b5b9b5
--- /dev/null
+++ b/server/schemas/i18n.coffee
@@ -0,0 +1,48 @@
+#this file will hold the experimental JSON schema for i18n
+c = require './common'
+
+languageCodeArrayRegex = c.generateLanguageCodeArrayRegex()
+
+
+ExampleSchema = {
+ title: "Example Schema",
+ description:"An example schema",
+ type: "object",
+ properties: {
+ text: {
+ title: "Text",
+ description: "A short message to display in the dialogue area. Markdown okay.",
+ type: "string",
+ maxLength: 400
+ },
+ i18n: {"$ref": "#/definitions/i18n"}
+ },
+
+ definitions: {
+ i18n: {
+ title: "i18n",
+ description: "The internationalization object",
+ type: "object",
+ patternProperties: {
+ languageCodeArrayRegex: {
+ additionalProperties: false,
+ properties: {
+ #put the translatable properties here
+ #if it is possible to not include i18n with a reference
+ # to #/properties, you could just do
+ properties: {"$ref":"#/properties"}
+ # text: {"$ref": "#/properties/text"}
+ }
+ default: {
+ title: "LanguageCode",
+ description: "LanguageDescription"
+ }
+ }
+ }
+ }
+ },
+
+}
+
+#define a i18n object type for each schema, then have the i18n have it's oneOf check against
+#translatable schemas of that object
\ No newline at end of file
diff --git a/server/schemas/level.coffee b/server/schemas/level.coffee
new file mode 100644
index 000000000..3d4cc6f67
--- /dev/null
+++ b/server/schemas/level.coffee
@@ -0,0 +1,231 @@
+c = require './common'
+ThangComponentSchema = require './thang_component'
+
+SpecificArticleSchema = c.object()
+c.extendNamedProperties SpecificArticleSchema # name first
+SpecificArticleSchema.properties.body = { type: 'string', title: 'Content', description: "The body content of the article, in Markdown.", format: 'markdown' }
+SpecificArticleSchema.displayProperty = 'name'
+
+side = {title: "Side", description: "A side.", type: 'string', 'enum': ['left', 'right', 'top', 'bottom']}
+thang = {title: "Thang", description: "The name of a Thang.", type: 'string', maxLength: 30, format:'thang'}
+
+eventPrereqValueTypes = ["boolean", "integer", "number", "null", "string"] # not "object" or "array"
+EventPrereqSchema = c.object {title: "Event Prerequisite", format: 'event-prereq', description: "Script requires that the value of some property on the event triggering it to meet some prerequisite.", "default": {eventProps: []}, required: ["eventProps"]},
+ eventProps: c.array {'default': ["thang"], format:'event-value-chain', maxItems: 10, title: "Event Property", description: 'A chain of keys in the event, like "thang.pos.x" to access event.thang.pos.x.'}, c.shortString(title: "Property", description: "A key in the event property key chain.")
+ equalTo: c.object {type: eventPrereqValueTypes, title: "==", description: "Script requires the event's property chain value to be equal to this value."}
+ notEqualTo: c.object {type: eventPrereqValueTypes, title: "!=", description: "Script requires the event's property chain value to *not* be equal to this value."}
+ greaterThan: {type: 'number', title: ">", description: "Script requires the event's property chain value to be greater than this value."}
+ greaterThanOrEqualTo: {type: 'number', title: ">=", description: "Script requires the event's property chain value to be greater or equal to this value."}
+ lessThan: {type: 'number', title: "<", description: "Script requires the event's property chain value to be less than this value."}
+ lessThanOrEqualTo: {type: 'number', title: "<=", description: "Script requires the event's property chain value to be less than or equal to this value."}
+ containingString: c.shortString(title: "Contains", description: "Script requires the event's property chain value to be a string containing this string.")
+ notContainingString: c.shortString(title: "Does not contain", description: "Script requires the event's property chain value to *not* be a string containing this string.")
+
+GoalSchema = c.object {title: "Goal", description: "A goal that the player can accomplish.", required: ["name", "id"]},
+ name: c.shortString(title: "Name", description: "Name of the goal that the player will see, like \"Defeat eighteen dragons\".")
+ i18n: {type: "object", format: 'i18n', props: ['name'], description: "Help translate this goal"}
+ id: c.shortString(title: "ID", description: "Unique identifier for this goal, like \"defeat-dragons\".") # unique somehow?
+ worldEndsAfter: {title: 'World Ends After', description: "When included, ends the world this many seconds after this goal succeeds or fails.", type: 'number', minimum: 0, exclusiveMinimum: true, maximum: 300, default: 3}
+ howMany: {title: "How Many", description: "When included, require only this many of the listed goal targets instead of all of them.", type: 'integer', minimum: 1}
+ hiddenGoal: {title: "Hidden", description: "Hidden goals don't show up in the goals area for the player until they're failed. (Usually they're obvious, like 'don't die'.)", 'type': 'boolean', default: false}
+ killThangs: c.array {title: "Kill Thangs", description: "A list of Thang IDs the player should kill, or team names.", uniqueItems: true, minItems: 1, "default": ["ogres"]}, thang
+ saveThangs: c.array {title: "Save Thangs", description: "A list of Thang IDs the player should save, or team names", uniqueItems: true, minItems: 1, "default": ["humans"]}, thang
+ getToLocations: c.object {title: "Get To Locations", description: "TODO: explain", required: ["who", "targets"]},
+ who: c.array {title: "Who", description: "The Thangs who must get to the target locations.", minItems: 1}, thang
+ targets: c.array {title: "Targets", description: "The target locations to which the Thangs must get.", minItems: 1}, thang
+ keepFromLocations: c.object {title: "Keep From Locations", description: "TODO: explain", required: ["who", "targets"]},
+ who: c.array {title: "Who", description: "The Thangs who must not get to the target locations.", minItems: 1}, thang
+ targets: c.array {title: "Targets", description: "The target locations to which the Thangs must not get.", minItems: 1}, thang
+ leaveOffSides: c.object {title: "Leave Off Sides", description: "Sides of the level to get some Thangs to leave across.", required: ["who", "sides"]},
+ who: c.array {title: "Who", description: "The Thangs which must leave off the sides of the level.", minItems: 1}, thang
+ sides: c.array {title: "Sides", description: "The sides off which the Thangs must leave.", minItems: 1}, side
+ keepFromLeavingOffSides: c.object {title: "Keep From Leaving Off Sides", description: "Sides of the level to keep some Thangs from leaving across.", required: ["who", "sides"]},
+ who: c.array {title: "Who", description: "The Thangs which must not leave off the sides of the level.", minItems: 1}, thang
+ sides: side, {title: "Sides", description: "The sides off which the Thangs must not leave.", minItems: 1}, side
+ collectThangs: c.object {title: "Collect", description: "Thangs that other Thangs must collect.", required: ["who", "targets"]},
+ who: c.array {title: "Who", description: "The Thangs which must collect the target items.", minItems: 1}, thang
+ targets: c.array {title: "Targets", description: "The target items which the Thangs must collect.", minItems: 1}, thang
+ keepFromCollectingThangs: c.object {title: "Keep From Collecting", description: "Thangs that the player must prevent other Thangs from collecting.", required: ["who", "targets"]},
+ who: c.array {title: "Who", description: "The Thangs which must not collect the target items.", minItems: 1}, thang
+ targets: c.array {title: "Targets", description: "The target items which the Thangs must not collect.", minItems: 1}, thang
+
+ResponseSchema = c.object {title: "Dialogue Button", description: "A button to be shown to the user with the dialogue.", required: ["text"]},
+ text: {title: "Title", description: "The text that will be on the button", "default": "Okay", type: 'string', maxLength: 30}
+ channel: c.shortString(title: "Channel", format: 'event-channel', description: 'Channel that this event will be broadcast over, like "level-set-playing".')
+ event: {type: 'object', title: "Event", description: "Event that will be broadcast when this button is pressed, like {playing: true}."}
+ buttonClass: c.shortString(title: "Button Class", description: 'CSS class that will be added to the button, like "btn-primary".')
+ i18n: {type: "object", format: 'i18n', props: ['text'], description: "Help translate this button"}
+
+PointSchema = c.object {title: "Point", description: "An {x, y} coordinate point.", format: "point2d", required: ["x", "y"]},
+ x: {title: "x", description: "The x coordinate.", type: "number", "default": 15}
+ y: {title: "y", description: "The y coordinate.", type: "number", "default": 20}
+
+SpriteCommandSchema = c.object {title: "Thang Command", description: "Make a target Thang move or say something, or select/deselect it.", required: ["id"], default: {id: "Captain Anya"}},
+ id: thang
+ select: {title: "Select", description: "Select or deselect this Thang.", type: 'boolean'}
+ say: c.object {title: "Say", description: "Make this Thang say a message.", required: ["text"]},
+ blurb: c.shortString(title: "Blurb", description: "A very short message to display above this Thang's head. Plain text.", maxLength: 50)
+ mood: c.shortString(title: "Mood", description: "The mood with which the Thang speaks.", "enum": ["explain", "debrief", "congrats", "attack", "joke", "tip", "alarm"], "default": "explain")
+ text: {title: "Text", description: "A short message to display in the dialogue area. Markdown okay.", type: "string", maxLength: 400}
+ sound: c.object {title: "Sound", description: "A dialogue sound file to accompany the message.", required: ["mp3", "ogg"]},
+ mp3: c.shortString(title: "MP3", format: 'sound-file')
+ ogg: c.shortString(title: "OGG", format: 'sound-file')
+ preload: {title: "Preload", description: "Whether to load this sound file before the level can begin (typically for the first dialogue of a level).", type: 'boolean', "default": false}
+ responses: c.array {title: "Buttons", description: "An array of buttons to include with the dialogue, with which the user can respond."}, ResponseSchema
+ i18n: {type: "object", format: 'i18n', props: ['blurb', 'text'], description: "Help translate this message"}
+ move: c.object {title: "Move", description: "Tell the Thang to move.", required: ['target'], default: {target: {x: 20, y: 20}, duration: 500}},
+ target: _.extend _.cloneDeep(PointSchema), {title: 'Target', description: 'Target point to which the Thang will move.'}
+ duration: {title: "Duration", description: "Number of milliseconds over which to move, or 0 for an instant move.", type: 'integer', minimum: 0, default: 500, format: 'milliseconds'}
+
+NoteGroupSchema = c.object {title: "Note Group", description: "A group of notes that should be sent out as a result of this script triggering.", displayProperty: "name"},
+ name: {title: "Name", description: "Short name describing the script, like \"Anya greets the player\", for your convenience.", type: "string"}
+ dom: c.object {title: "DOM", description: "Manipulate things in the play area DOM, outside of the level area canvas."},
+ focus: c.shortString(title: "Focus", description: "Set the window focus to this DOM selector string.")
+ showVictory: {
+ title: "Show Victory",
+ description: "Show the done button and maybe also the victory modal.",
+ enum: [true, 'Done Button', 'Done Button And Modal'] # deprecate true, same as 'done_button_and_modal'
+ }
+ highlight: c.object {title: "Highlight", description: "Highlight the target DOM selector string with a big arrow."},
+ target: c.shortString(title: "Target", description: "Target highlight element DOM selector string.")
+ delay: {type: 'integer', minimum: 0, title: "Delay", description: "Show the highlight after this many milliseconds. Doesn't affect the dim shade cutout highlight method."}
+ offset: _.extend _.cloneDeep(PointSchema), {title: 'Offset', description: 'Pointing arrow tip offset in pixels from the default target.', format: null}
+ rotation: {type: 'number', minimum: 0, title: "Rotation", description: "Rotation of the pointing arrow, in radians. PI / 2 points left, PI points up, etc."}
+ sides: c.array {title: "Sides", description: "Which sides of the target element to point at."}, {type: 'string', 'enum': ['left', 'right', 'top', 'bottom'], title: "Side", description: "A side of the target element to point at."}
+ lock: {title: "Lock", description: "Whether the interface should be locked so that the player's focus is on the script, or specific areas to lock.", type: ['boolean', 'array'], items: {type: 'string', enum: ['surface', 'editor', 'palette', 'hud', 'playback', 'playback-hover', 'level', ]}}
+ letterbox: {type: 'boolean', title: 'Letterbox', description:'Turn letterbox mode on or off. Disables surface and playback controls.'}
+
+ goals: c.object {title: "Goals", description: "Add or remove goals for the player to complete in the level."},
+ add: c.array {title: "Add", description: "Add these goals."}, GoalSchema
+ remove: c.array {title: "Remove", description: "Remove these goals."}, GoalSchema
+
+ playback: c.object {title: "Playback", description: "Control the playback of the level."},
+ playing: {type: 'boolean', title: "Set Playing", description: "Set whether playback is playing or paused."}
+ scrub: c.object {title: "Scrub", description: "Scrub the level playback time to a certain point.", default: {offset: 2, duration: 1000, toRatio: 0.5}},
+ offset: {type: 'integer', title: "Offset", description: "Number of frames by which to adjust the scrub target time.", default: 2}
+ duration: {type: 'integer', title: "Duration", description: "Number of milliseconds over which to scrub time.", minimum: 0, format: 'milliseconds'}
+ toRatio: {type: 'number', title: "To Progress Ratio", description: "Set playback time to a target playback progress ratio.", minimum: 0, maximum: 1}
+ toTime: {type: 'number', title: "To Time", description: "Set playback time to a target playback point, in seconds.", minimum: 0}
+ toGoal: c.shortString(title: "To Goal", description: "Set playback time to when this goal was achieved. (TODO: not implemented.)")
+
+ script: c.object {title: "Script", description: "Extra configuration for this action group."},
+ duration: {type: 'integer', minimum: 0, title: "Duration", description: "How long this script should last in milliseconds. 0 for indefinite.", format: 'milliseconds'}
+ skippable: {type: 'boolean', title: "Skippable", description: "Whether this script shouldn't bother firing when the player skips past all current scripts."}
+ beforeLoad: {type: 'boolean', title: "Before Load", description: "Whether this script should fire before the level is finished loading."}
+
+ sprites: c.array {title: "Sprites", description: "Commands to issue to Sprites on the Surface."}, SpriteCommandSchema
+
+ surface: c.object {title: "Surface", description: "Commands to issue to the Surface itself."},
+ focus: c.object {title: "Camera", description: "Focus the camera on a specific point on the Surface.", format:'viewport'},
+ target: {anyOf: [PointSchema, thang, {type: 'null'}], title: "Target", description: "Where to center the camera view."}
+ zoom: {type: 'number', minimum: 0, exclusiveMinimum: true, maximum: 64, title: "Zoom", description: "What zoom level to use."}
+ duration: {type:'number', minimum: 0, title: "Duration", description: "in ms"}
+ bounds: c.array {title:'Boundary', maxItems: 2, minItems: 2, default:[{x:0,y:0}, {x:46, y:39}], format: 'bounds'}, PointSchema
+ isNewDefault: {type:'boolean', format: 'hidden', title: "New Default", description: 'Set this as new default zoom once scripts end.'} # deprecated
+ highlight: c.object {title: "Highlight", description: "Highlight specific Sprites on the Surface."},
+ targets: c.array {title: "Targets", description: "Thang IDs of target Sprites to highlight."}, thang
+ delay: {type: 'integer', minimum: 0, title: "Delay", description: "Delay in milliseconds before the highlight appears."}
+ lockSelect: {type: 'boolean', title: "Lock Select", description: "Whether to lock Sprite selection so that the player can't select/deselect anything."}
+
+ sound: c.object {title: "Sound", description: "Commands to control sound playback."},
+ suppressSelectionSounds: {type: "boolean", title: "Suppress Selection Sounds", description: "Whether to suppress selection sounds made from clicking on Thangs."}
+ music: c.object { title: "Music", description: "Control music playing"},
+ play: { title: "Play", type: "boolean" }
+ file: c.shortString(title: "File", enum:['/music/music_level_1','/music/music_level_2','/music/music_level_3','/music/music_level_4','/music/music_level_5'])
+
+ScriptSchema = c.object {
+ title: "Script"
+ description: 'A script fires off a chain of notes to interact with the game when a certain event triggers it.'
+ required: ["channel"]
+ 'default': {channel: "world:won", noteChain: []}
+},
+ id: c.shortString(title: "ID", description: "A unique ID that other scripts can rely on in their Happens After prereqs, for sequencing.") # uniqueness?
+ channel: c.shortString(title: "Event", format: 'event-channel', description: 'Event channel this script might trigger for, like "world:won".')
+ eventPrereqs: c.array {title: "Event Checks", description: "Logical checks on the event for this script to trigger.", format:'event-prereqs'}, EventPrereqSchema
+ repeats: {title: "Repeats", description: "Whether this script can trigger more than once during a level.", type: 'boolean', "default": false}
+ scriptPrereqs: c.array {title: "Happens After", description: "Scripts that need to fire first."},
+ c.shortString(title: "ID", description: "A unique ID of a script that must have triggered before the parent script can trigger.")
+ noteChain: c.array {title: "Actions", description: "A list of things that happen when this script triggers."}, NoteGroupSchema
+
+LevelThangSchema = c.object {
+ title: "Thang",
+ description: "Thangs are any units, doodads, or abstract things that you use to build the level. (\"Thing\" was too confusing to say.)",
+ format: "thang"
+ required: ["id", "thangType", "components"]
+ 'default':
+ id: "Boris"
+ thangType: "Soldier"
+ components: []
+},
+ id: thang # TODO: figure out if we can make this unique and how to set dynamic defaults
+ # TODO: split thangType into "original" and "majorVersion" like the rest for consistency
+ thangType: c.objectId(links: [{rel: "db", href: "/db/thang_type/{($)}/version"}], title: "Thang Type", description: "A reference to the original Thang template being configured.", format: 'thang-type')
+ components: c.array {title: "Components", description: "Thangs are configured by changing the Components attached to them.", uniqueItems: true, format: 'thang-components-array'}, ThangComponentSchema # TODO: uniqueness should be based on "original", not whole thing
+
+LevelSystemSchema = c.object {
+ title: "System"
+ description: "Configuration for a System that this Level uses."
+ format: 'level-system'
+ required: ['original', 'majorVersion']
+ 'default':
+ majorVersion: 0
+ config: {}
+ links: [{rel: "db", href: "/db/level.system/{(original)}/version/{(majorVersion)}"}]
+},
+ original: c.objectId(title: "Original", description: "A reference to the original System being configured.", format: "hidden")
+ config: c.object {title: "Configuration", description: "System-specific configuration properties.", additionalProperties: true, format: 'level-system-configuration'}
+ majorVersion: {title: "Major Version", description: "Which major version of the System is being used.", type: 'integer', minimum: 0, default: 0, format: "hidden"}
+
+GeneralArticleSchema = c.object {
+ title: "Article"
+ description: "Reference to a general documentation article."
+ required: ['original']
+ format: 'latest-version-reference'
+ 'default':
+ original: null
+ majorVersion: 0
+ links: [{rel: "db", href: "/db/article/{(original)}/version/{(majorVersion)}"}]
+},
+ original: c.objectId(title: "Original", description: "A reference to the original Article.")#, format: "hidden") # hidden?
+ majorVersion: {title: "Major Version", description: "Which major version of the Article is being used.", type: 'integer', minimum: 0}#, format: "hidden"} # hidden?
+
+LevelSchema = c.object {
+ title: "Level"
+ description: "A spectacular level which will delight and educate its stalwart players with the sorcery of coding."
+ required: ["name", "description", "scripts", "thangs", "documentation"]
+ 'default':
+ name: "Ineffable Wizardry"
+ description: "This level is indescribably flarmy."
+ documentation: {specificArticles: [], generalArticles: []}
+ scripts: []
+ thangs: []
+}
+c.extendNamedProperties LevelSchema # let's have the name be the first property
+_.extend LevelSchema.properties,
+ description: {title: "Description", description: "A short explanation of what this level is about.", type: "string", maxLength: 65536, "default": "This level is indescribably flarmy!", format: 'markdown'}
+ documentation: c.object {title: "Documentation", description: "Documentation articles relating to this level.", required: ["specificArticles", "generalArticles"], 'default': {specificArticles: [], generalArticles: []}},
+ specificArticles: c.array {title: "Specific Articles", description: "Specific documentation articles that live only in this level.", uniqueItems: true, "default": []}, SpecificArticleSchema
+ generalArticles: c.array {title: "General Articles", description: "General documentation articles that can be linked from multiple levels.", uniqueItems: true, "default": []}, GeneralArticleSchema
+ background: c.objectId({format: 'hidden'})
+ nextLevel: c.objectId(links: [{rel: "extra", href: "/db/level/{($)}"}, {rel:'db', href: "/db/level/{(original)}/version/{(majorVersion)}"}], format: 'latest-version-reference', title: "Next Level", description: "Reference to the next level players will player after beating this one.")
+ scripts: c.array {title: "Scripts", description: "An array of scripts that trigger based on what the player does and affect things outside of the core level simulation.", "default": []}, ScriptSchema
+ thangs: c.array {title: "Thangs", description: "An array of Thangs that make up the level.", "default": []}, LevelThangSchema
+ systems: c.array {title: "Systems", description: "Levels are configured by changing the Systems attached to them.", uniqueItems: true, default: []}, LevelSystemSchema # TODO: uniqueness should be based on "original", not whole thing
+ victory: c.object {title: "Victory Screen", default: {}, properties: {'body': {type: 'string', format: 'markdown', title: 'Body Text', description: 'Inserted into the Victory Modal once this level is complete. Tell the player they did a good job and what they accomplished!'}, i18n: {type: "object", format: 'i18n', props: ['body'], description: "Help translate this victory message"}}}
+ i18n: {type: "object", format: 'i18n', props: ['name', 'description'], description: "Help translate this level"}
+ icon: { type: 'string', format: 'image-file', title: 'Icon' }
+
+
+c.extendBasicProperties LevelSchema, 'level'
+c.extendSearchableProperties LevelSchema
+c.extendVersionedProperties LevelSchema, 'level'
+c.extendPermissionsProperties LevelSchema, 'level'
+
+module.exports = LevelSchema
+
+# To test:
+# 1: Copy the schema from http://localhost:3000/db/level/schema
+# 2. Open up the Treema demo page http://localhost:9090/demo.html
+# 3. tv4.addSchema(metaschema.id, metaschema)
+# 4. S =
+# 5. tv4.validateMultiple(S, metaschema) and look for errors
diff --git a/server/schemas/level_component.coffee b/server/schemas/level_component.coffee
new file mode 100644
index 000000000..02e593351
--- /dev/null
+++ b/server/schemas/level_component.coffee
@@ -0,0 +1,70 @@
+c = require './common'
+metaschema = require './metaschema'
+
+attackSelfCode = """
+class AttacksSelf extends Component
+ @className: "AttacksSelf"
+ chooseAction: ->
+ @attack @
+"""
+systems = ["action", "ai", "alliance", "collision", "combat", "display", "event", "existence", "hearing", "inventory", "movement", "programming", "targeting", "ui", "vision", "misc", "physics"]
+
+PropertyDocumentationSchema = c.object {
+ title: "Property Documentation"
+ description: "Documentation entry for a property this Component will add to its Thang which other Components might want to also use."
+ "default":
+ name: "foo"
+ type: "object"
+ description: "This Component provides a 'foo' property to satisfy all one's foobar needs. Use it wisely."
+ required: ['name', 'type', 'description']
+},
+ name: {type: 'string', pattern: c.identifierPattern, title: "Name", description: "Name of the property."}
+ type: c.shortString(title: "Type", description: "Intended type of the property.") # not actual JS types, just whatever they describe...
+ description: {type: 'string', description: "Description of the property.", maxLength: 1000}
+ args: c.array {title: "Arguments", description: "If this property has type 'function', then provide documentation for any function arguments."}, c.FunctionArgumentSchema
+
+DependencySchema = c.object {
+ title: "Component Dependency"
+ description: "A Component upon which this Component depends."
+ "default":
+ #original: ?
+ majorVersion: 0
+ required: ["original", "majorVersion"]
+ format: 'latest-version-reference'
+ links: [{rel: "db", href: "/db/level.component/{(original)}/version/{(majorVersion)}"}]
+},
+ original: c.objectId(title: "Original", description: "A reference to another Component upon which this Component depends.")
+ majorVersion: {title: "Major Version", description: "Which major version of the Component this Component needs.", type: 'integer', minimum: 0}
+
+LevelComponentSchema = c.object {
+ title: "Component"
+ description: "A Component which can affect Thang behavior."
+ required: ["system", "name", "description", "code", "dependencies", "propertyDocumentation", "language"]
+ "default":
+ system: "ai"
+ name: "AttacksSelf"
+ description: "This Component makes the Thang attack itself."
+ code: attackSelfCode
+ language: "coffeescript"
+ dependencies: [] # TODO: should depend on something by default
+ propertyDocumentation: []
+}
+c.extendNamedProperties LevelComponentSchema # let's have the name be the first property
+LevelComponentSchema.properties.name.pattern = c.classNamePattern
+_.extend LevelComponentSchema.properties,
+ system: {title: "System", description: "The short name of the System this Component belongs to, like \"ai\".", type: "string", "enum": systems, "default": "ai"}
+ description: {title: "Description", description: "A short explanation of what this Component does.", type: "string", maxLength: 2000, "default": "This Component makes the Thang attack itself."}
+ language: {type: "string", title: "Language", description: "Which programming language this Component is written in.", "enum": ["coffeescript"]}
+ code: {title: "Code", description: "The code for this Component, as a CoffeeScript class. TODO: add link to documentation for how to write these.", "default": attackSelfCode, type: "string", format: "coffee"}
+ js: {title: "JavaScript", description: "The transpiled JavaScript code for this Component", type: "string", format: "hidden"}
+ dependencies: c.array {title: "Dependencies", description: "An array of Components upon which this Component depends.", "default": [], uniqueItems: true}, DependencySchema
+ propertyDocumentation: c.array {title: "Property Documentation", description: "An array of documentation entries for each notable property this Component will add to its Thang which other Components might want to also use.", "default": []}, PropertyDocumentationSchema
+ configSchema: _.extend metaschema, {title: "Configuration Schema", description: "A schema for validating the arguments that can be passed to this Component as configuration.", default: {type: 'object', additionalProperties: false}}
+ official: {type: "boolean", title: "Official", description: "Whether this is an official CodeCombat Component.", "default": false}
+
+c.extendBasicProperties LevelComponentSchema, 'level.component'
+c.extendSearchableProperties LevelComponentSchema
+c.extendVersionedProperties LevelComponentSchema, 'level.component'
+c.extendPermissionsProperties LevelComponentSchema, 'level.component'
+
+module.exports = LevelComponentSchema
diff --git a/server/schemas/level_feedback.coffee b/server/schemas/level_feedback.coffee
new file mode 100644
index 000000000..f10c22ad1
--- /dev/null
+++ b/server/schemas/level_feedback.coffee
@@ -0,0 +1,27 @@
+c = require './common'
+
+LevelFeedbackLevelSchema = c.object {required: ['original', 'majorVersion']}, {
+ original: c.objectId({})
+ majorVersion: {type: 'integer', minimum: 0, default: 0}}
+
+LevelFeedbackSchema = c.object {
+ title: "Feedback"
+ description: "Feedback on a level."
+}
+
+_.extend LevelFeedbackSchema.properties,
+ # denormalization
+ creatorName: { type: 'string' }
+ levelName: { type: 'string' }
+ levelID: { type: 'string' }
+
+ creator: c.objectId(links: [{rel: 'extra', href: "/db/user/{($)}"}])
+ created: c.date( { title: 'Created', readOnly: true })
+
+ level: LevelFeedbackLevelSchema
+ rating: { type: 'number', minimum: 1, maximum: 5 }
+ review: { type: 'string' }
+
+c.extendBasicProperties LevelFeedbackSchema, 'level.feedback'
+
+module.exports = LevelFeedbackSchema
diff --git a/server/schemas/level_session.coffee b/server/schemas/level_session.coffee
new file mode 100644
index 000000000..a025da2bf
--- /dev/null
+++ b/server/schemas/level_session.coffee
@@ -0,0 +1,65 @@
+c = require './common'
+
+LevelSessionPlayerSchema = c.object {
+ id: c.objectId(links: [{rel: 'extra', href: "/db/user/{($)}"}])
+ time: { type: 'Number' }
+ changes: { type: 'Number' }
+}
+
+LevelSessionLevelSchema = c.object {required: ['original', 'majorVersion']}, {
+ original: c.objectId({})
+ majorVersion: {type: 'integer', minimum: 0, default: 0}}
+
+LevelSessionSchema = c.object {
+ title: "Session"
+ description: "A single session for a given level."
+}
+
+_.extend LevelSessionSchema.properties,
+ # denormalization
+ creatorName: { type: 'string' }
+ levelName: { type: 'string' }
+ levelID: { type: 'string' }
+ multiplayer: { type: 'boolean' }
+
+ creator: c.objectId(links: [{rel: 'extra', href: "/db/user/{($)}"}])
+ created: c.date( { title: 'Created', readOnly: true })
+ changed: c.date( { title: 'Changed', readOnly: true })
+ level: LevelSessionLevelSchema
+ screenshot: { type: 'string' }
+ state: c.object {}, {
+ complete: { type: 'boolean' }
+ scripts: c.object {}, {
+ ended: { type: 'object', additionalProperties: { type: 'number' }}
+ currentScript: { type: ['null', 'string']}
+ currentScriptOffset: { type: 'number' }}
+ selected: { type: ['null', 'string'] }
+ playing: { type: 'boolean' }
+ frame: { type: 'number' }
+ thangs: { type: 'object', additionalProperties: {
+ title: 'Thang'
+ type: 'object'
+ properties: {
+ methods: { type: 'object', additionalProperties: {
+ title: 'Thang Method'
+ type: 'object'
+ properties: {
+ metrics: { type: 'object' }
+ source: { type: 'string' }
+ }
+ }}
+ }
+ }}
+ }
+
+ # TODO: specify this more
+ code: { type: 'object' }
+ players: { type: 'object' }
+ chat: { type: 'array' }
+
+
+
+c.extendBasicProperties LevelSessionSchema, 'level.session'
+c.extendPermissionsProperties LevelSessionSchema, 'level.session'
+
+module.exports = LevelSessionSchema
diff --git a/server/schemas/level_system.coffee b/server/schemas/level_system.coffee
new file mode 100644
index 000000000..9ecf69eff
--- /dev/null
+++ b/server/schemas/level_system.coffee
@@ -0,0 +1,77 @@
+c = require './common'
+metaschema = require './metaschema'
+
+jitterSystemCode = """
+class Jitter extends System
+ constructor: (world, config) ->
+ super world, config
+ @idlers = @addRegistry (thang) -> thang.exists and thang.acts and thang.moves and thang.action is 'idle'
+
+ update: ->
+ # We return a simple numeric hash that will combine to a frame hash
+ # help us determine whether this frame has changed in resimulations.
+ hash = 0
+ for thang in @idlers
+ hash += thang.pos.x += 0.5 - Math.random()
+ hash += thang.pos.y += 0.5 - Math.random()
+ thang.hasMoved = true
+ return hash
+"""
+
+PropertyDocumentationSchema = c.object {
+ title: "Property Documentation"
+ description: "Documentation entry for a property this System will add to its Thang which other Systems might want to also use."
+ "default":
+ name: "foo"
+ type: "object"
+ description: "This System provides a 'foo' property to satisfy all one's foobar needs. Use it wisely."
+ required: ['name', 'type', 'description']
+},
+ name: {type: 'string', pattern: c.identifierPattern, title: "Name", description: "Name of the property."}
+ type: c.shortString(title: "Type", description: "Intended type of the property.") # not actual JS types, just whatever they describe...
+ description: {type: 'string', description: "Description of the property.", maxLength: 1000}
+ args: c.array {title: "Arguments", description: "If this property has type 'function', then provide documentation for any function arguments."}, c.FunctionArgumentSchema
+
+DependencySchema = c.object {
+ title: "System Dependency"
+ description: "A System upon which this System depends."
+ "default":
+ #original: ?
+ majorVersion: 0
+ required: ["original", "majorVersion"]
+ format: 'latest-version-reference'
+ links: [{rel: "db", href: "/db/level.system/{(original)}/version/{(majorVersion)}"}]
+},
+ original: c.objectId(title: "Original", description: "A reference to another System upon which this System depends.")
+ majorVersion: {title: "Major Version", description: "Which major version of the System this System needs.", type: 'integer', minimum: 0}
+
+LevelSystemSchema = c.object {
+ title: "System"
+ description: "A System which can affect Level behavior."
+ required: ["name", "description", "code", "dependencies", "propertyDocumentation", "language"]
+ "default":
+ name: "JitterSystem"
+ description: "This System makes all idle, movable Thangs jitter around."
+ code: jitterSystemCode
+ language: "coffeescript"
+ dependencies: [] # TODO: should depend on something by default
+ propertyDocumentation: []
+}
+c.extendNamedProperties LevelSystemSchema # let's have the name be the first property
+LevelSystemSchema.properties.name.pattern = c.classNamePattern
+_.extend LevelSystemSchema.properties,
+ description: {title: "Description", description: "A short explanation of what this System does.", type: "string", maxLength: 2000, "default": "This System doesn't do anything yet."}
+ language: {type: "string", title: "Language", description: "Which programming language this System is written in.", "enum": ["coffeescript"]}
+ code: {title: "Code", description: "The code for this System, as a CoffeeScript class. TODO: add link to documentation for how to write these.", "default": jitterSystemCode, type: "string", format: "coffee"}
+ js: {title: "JavaScript", description: "The transpiled JavaScript code for this System", type: "string", format: "hidden"}
+ dependencies: c.array {title: "Dependencies", description: "An array of Systems upon which this System depends.", "default": [], uniqueItems: true}, DependencySchema
+ propertyDocumentation: c.array {title: "Property Documentation", description: "An array of documentation entries for each notable property this System will add to its Level which other Systems might want to also use.", "default": []}, PropertyDocumentationSchema
+ configSchema: _.extend metaschema, {title: "Configuration Schema", description: "A schema for validating the arguments that can be passed to this System as configuration.", default: {type: 'object', additionalProperties: false}}
+ official: {type: "boolean", title: "Official", description: "Whether this is an official CodeCombat System.", "default": false}
+
+c.extendBasicProperties LevelSystemSchema, 'level.system'
+c.extendSearchableProperties LevelSystemSchema
+c.extendVersionedProperties LevelSystemSchema, 'level.system'
+c.extendPermissionsProperties LevelSystemSchema, 'level.system'
+
+module.exports = LevelSystemSchema
diff --git a/server/schemas/metaschema.coffee b/server/schemas/metaschema.coffee
new file mode 100644
index 000000000..4d9d7c0d8
--- /dev/null
+++ b/server/schemas/metaschema.coffee
@@ -0,0 +1,132 @@
+# The JSON Schema Core/Validation Meta-Schema, but with titles and descriptions added to make it easier to edit in Treema, and in CoffeeScript
+
+module.exports =
+ id: "metaschema"
+ displayProperty: "title"
+ $schema: "http://json-schema.org/draft-04/schema#"
+ title: "Schema"
+ description: "Core schema meta-schema"
+ definitions:
+ schemaArray:
+ type: "array"
+ minItems: 1
+ items: { $ref: "#" }
+ title: "Array of Schemas"
+ "default": [{}]
+ positiveInteger:
+ type: "integer"
+ minimum: 0
+ title: "Positive Integer"
+ positiveIntegerDefault0:
+ allOf: [ { $ref: "#/definitions/positiveInteger" }, { "default": 0 } ]
+ simpleTypes:
+ title: "Single Type"
+ "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
+ stringArray:
+ type: "array"
+ items: { type: "string" }
+ minItems: 1
+ uniqueItems: true
+ title: "String Array"
+ "default": ['']
+ type: "object"
+ properties:
+ id:
+ type: "string"
+ format: "uri"
+ $schema:
+ type: "string"
+ format: "uri"
+ "default": "http://json-schema.org/draft-04/schema#"
+ title:
+ type: "string"
+ description:
+ type: "string"
+ "default": {}
+ multipleOf:
+ type: "number"
+ minimum: 0
+ exclusiveMinimum: true
+ maximum:
+ type: "number"
+ exclusiveMaximum:
+ type: "boolean"
+ "default": false
+ minimum:
+ type: "number"
+ exclusiveMinimum:
+ type: "boolean"
+ "default": false
+ maxLength: { $ref: "#/definitions/positiveInteger" }
+ minLength: { $ref: "#/definitions/positiveIntegerDefault0" }
+ pattern:
+ type: "string"
+ format: "regex"
+ additionalItems:
+ anyOf: [
+ { type: "boolean", "default": false }
+ { $ref: "#" }
+ ]
+ items:
+ anyOf: [
+ { $ref: "#" }
+ { $ref: "#/definitions/schemaArray" }
+ ]
+ "default": {}
+ maxItems: { $ref: "#/definitions/positiveInteger" }
+ minItems: { $ref: "#/definitions/positiveIntegerDefault0" }
+ uniqueItems:
+ type: "boolean"
+ "default": false
+ maxProperties: { $ref: "#/definitions/positiveInteger" }
+ minProperties: { $ref: "#/definitions/positiveIntegerDefault0" }
+ required: { $ref: "#/definitions/stringArray" }
+ additionalProperties:
+ anyOf: [
+ { type: "boolean", "default": true }
+ { $ref: "#" }
+ ]
+ "default": {}
+ definitions:
+ type: "object"
+ additionalProperties: { $ref: "#" }
+ "default": {}
+ properties:
+ type: "object"
+ additionalProperties: { $ref: "#" }
+ "default": {}
+ patternProperties:
+ type: "object"
+ additionalProperties: { $ref: "#" }
+ "default": {}
+ dependencies:
+ type: "object"
+ additionalProperties:
+ anyOf: [
+ { $ref: "#" }
+ { $ref: "#/definitions/stringArray" }
+ ]
+ "enum":
+ type: "array"
+ minItems: 1
+ uniqueItems: true
+ "default": ['']
+ type:
+ anyOf: [
+ { $ref: "#/definitions/simpleTypes" }
+ {
+ type: "array"
+ items: { $ref: "#/definitions/simpleTypes" }
+ minItems: 1
+ uniqueItems: true
+ title: "Array of Types"
+ "default": ['string']
+ }]
+ allOf: { $ref: "#/definitions/schemaArray" }
+ anyOf: { $ref: "#/definitions/schemaArray" }
+ oneOf: { $ref: "#/definitions/schemaArray" }
+ not: { $ref: "#" }
+ dependencies:
+ exclusiveMaximum: [ "maximum" ]
+ exclusiveMinimum: [ "minimum" ]
+ "default": {}
diff --git a/server/schemas/thang_component.coffee b/server/schemas/thang_component.coffee
new file mode 100644
index 000000000..bab65f029
--- /dev/null
+++ b/server/schemas/thang_component.coffee
@@ -0,0 +1,15 @@
+c = require './common'
+
+module.exports = ThangComponentSchema = c.object {
+ title: "Component"
+ description: "Configuration for a Component that this Thang uses."
+ format: 'thang-component'
+ required: ['original', 'majorVersion']
+ 'default':
+ majorVersion: 0
+ config: {}
+ links: [{rel: "db", href: "/db/level.component/{(original)}/version/{(majorVersion)}"}]
+},
+ original: c.objectId(title: "Original", description: "A reference to the original Component being configured.", format: "hidden")
+ config: c.object {title: "Configuration", description: "Component-specific configuration properties.", additionalProperties: true, format: 'thang-component-configuration'}
+ majorVersion: {title: "Major Version", description: "Which major version of the Component is being used.", type: 'integer', minimum: 0, default: 0, format: "hidden"}
diff --git a/server/schemas/thang_type.coffee b/server/schemas/thang_type.coffee
new file mode 100644
index 000000000..b52a228e6
--- /dev/null
+++ b/server/schemas/thang_type.coffee
@@ -0,0 +1,133 @@
+c = require './common'
+ThangComponentSchema = require './thang_component'
+
+ThangTypeSchema = c.object()
+c.extendNamedProperties ThangTypeSchema # name first
+
+ShapeObjectSchema = c.object { title: 'Shape' },
+ fc: { type: 'string', title: 'Fill Color' }
+ lf: { type: 'array', title: 'Linear Gradient Fill' }
+ ls: { type: 'array', title: 'Linear Gradient Stroke' }
+ p: { type: 'string', title: 'Path' }
+ de: { type: 'array', title: 'Draw Ellipse' }
+ sc: { type: 'string', title: 'Stroke Color' }
+ ss: { type: 'array', title: 'Stroke Style' }
+ t: c.array {}, { type: 'number', title: 'Transform' }
+ m: { type: 'string', title: 'Mask' }
+
+ContainerObjectSchema = c.object { format: 'container' },
+ b: c.array { title: 'Bounds' }, { type: 'number' }
+ c: c.array { title: 'Children' }, { anyOf: [
+ { type: 'string', title: 'Shape Child' },
+ c.object { title: 'Container Child' }
+ gn: { type: 'string', title: 'Global Name' }
+ t: c.array {}, { type: 'number' }
+ ]}
+
+RawAnimationObjectSchema = c.object {},
+ bounds: c.array { title: 'Bounds' }, { type: 'number' }
+ frameBounds: c.array { title: 'Frame Bounds' }, c.array { title: 'Bounds' }, { type: 'number' }
+ shapes: c.array {},
+ bn: { type: 'string', title: 'Block Name' }
+ gn: { type: 'string', title: 'Global Name' }
+ im : { type: 'boolean', title: 'Is Mask' }
+ m: { type: 'string', title: 'Uses Mask' }
+ containers: c.array {},
+ bn: { type: 'string', title: 'Block Name' }
+ gn: { type: 'string', title: 'Global Name' }
+ t: c.array {}, { type: 'number' }
+ o: { type: 'boolean', title: 'Starts Hidden (_off)'}
+ al: { type: 'number', title: 'Alpha'}
+ animations: c.array {},
+ bn: { type: 'string', title: 'Block Name' }
+ gn: { type: 'string', title: 'Global Name' }
+ t: c.array {}, { type: 'number', title: 'Transform' }
+ a: c.array { title: 'Arguments' }
+ tweens: c.array {},
+ c.array { title: 'Function Chain', },
+ c.object { title: 'Function Call' },
+ n: { type: 'string', title: 'Name' }
+ a: c.array { title: 'Arguments' }
+ graphics: c.array {},
+ bn: { type: 'string', title: 'Block Name' }
+ p: { type: 'string', title: 'Path' }
+
+PositionsSchema = c.object { title: 'Positions', description: 'Customize position offsets.' },
+ registration: c.point2d { title: 'Registration Point', description: "Action-specific registration point override." }
+ torso: c.point2d { title: 'Torso Offset', description: "Action-specific torso offset override." }
+ mouth: c.point2d { title: 'Mouth Offset', description: "Action-specific mouth offset override." }
+ aboveHead: c.point2d { title: 'Above Head Offset', description: "Action-specific above-head offset override." }
+
+ActionSchema = c.object {},
+ animation: { type: 'string', description: 'Raw animation being sourced', format: 'raw-animation' }
+ container: { type: 'string', description: 'Name of the container to show' }
+ relatedActions: c.object { },
+ begin: { $ref: '#/definitions/action' }
+ end: { $ref: '#/definitions/action' }
+ main: { $ref: '#/definitions/action' }
+ fore: { $ref: '#/definitions/action' }
+ back: { $ref: '#/definitions/action' }
+ side: { $ref: '#/definitions/action' }
+
+ "?0?011?11?11": { $ref: '#/definitions/action', title: "NW corner" }
+ "?0?11011?11?": { $ref: '#/definitions/action', title: "NE corner, flipped" }
+ "?0?111111111": { $ref: '#/definitions/action', title: "N face" }
+ "?11011011?0?": { $ref: '#/definitions/action', title: "SW corner, top" }
+ "11?11?110?0?": { $ref: '#/definitions/action', title: "SE corner, top, flipped" }
+ "?11011?0????": { $ref: '#/definitions/action', title: "SW corner, bottom" }
+ "11?110?0????": { $ref: '#/definitions/action', title: "SE corner, bottom, flipped" }
+ "?11011?11?11": { $ref: '#/definitions/action', title: "W face" }
+ "11?11011?11?": { $ref: '#/definitions/action', title: "E face, flipped" }
+ "011111111111": { $ref: '#/definitions/action', title: "NW elbow" }
+ "110111111111": { $ref: '#/definitions/action', title: "NE elbow, flipped" }
+ "111111111?0?": { $ref: '#/definitions/action', title: "S face, top" }
+ "111111?0????": { $ref: '#/definitions/action', title: "S face, bottom" }
+ "111111111011": { $ref: '#/definitions/action', title: "SW elbow, top" }
+ "111111111110": { $ref: '#/definitions/action', title: "SE elbow, top, flipped" }
+ "111111011?11": { $ref: '#/definitions/action', title: "SW elbow, bottom" }
+ "11111111011?": { $ref: '#/definitions/action', title: "SE elbow, bottom, flipped" }
+ "111111111111": { $ref: '#/definitions/action', title: "Middle" }
+
+ loops: { type: 'boolean' }
+ speed: { type: 'number' }
+ goesTo: { type: 'string', description: 'Action (animation?) to which we switch after this animation.' }
+ frames: { type: 'string', pattern:'^[0-9,]+$', description: 'Manually way to specify frames.' }
+ framerate: { type: 'number', description: 'Get this from the HTML output.' }
+ positions: PositionsSchema
+ scale: { title: 'Scale', type: 'number' }
+ flipX: { title: "Flip X", type: 'boolean', description: "Flip this animation horizontally?" }
+ flipY: { title: "Flip Y", type: 'boolean', description: "Flip this animation vertically?" }
+
+SoundSchema = c.sound({delay: { type: 'number' }})
+
+_.extend ThangTypeSchema.properties,
+ raw: c.object {title: 'Raw Vector Data'},
+ shapes: c.object {title: 'Shapes', additionalProperties: ShapeObjectSchema}
+ containers: c.object {title: 'Containers', additionalProperties: ContainerObjectSchema}
+ animations: c.object {title: 'Animations', additionalProperties: RawAnimationObjectSchema}
+
+ actions: c.object { title: 'Actions', additionalProperties: { $ref: '#/definitions/action' } }
+ soundTriggers: c.object { title: "Sound Triggers", additionalProperties: c.array({}, { $ref: '#/definitions/sound' }) },
+ say: c.object { format: 'slug-props', additionalProperties: { $ref: '#/definitions/sound' } },
+ defaultSimlish: c.array({}, { $ref: '#/definitions/sound' })
+ swearingSimlish: c.array({}, { $ref: '#/definitions/sound' })
+ rotationType: { title: 'Rotation', type: 'string', enum: ['isometric', 'fixed']}
+ matchWorldDimensions: { title: 'Match World Dimensions', type: 'boolean' }
+ shadow: { title: 'Shadow Diameter', type: 'number', format: 'meters', description: "Shadow diameter in meters" }
+ layerPriority: { title: 'Layer Priority', type: 'integer', description: "Within its layer, sprites are sorted by layer priority, then y, then z." }
+ scale: { title: 'Scale', type: 'number' }
+ positions: PositionsSchema
+ snap: c.object { title: "Snap", description: "In the level editor, snap positioning to these intervals.", required: ['x', 'y'] },
+ x: { title: "Snap X", type: 'number', description: "Snap to this many meters in the x-direction.", default: 4}
+ y: { title: "Snap Y", type: 'number', description: "Snap to this many meters in the y-direction.", default: 4 }
+ components: c.array {title: "Components", description: "Thangs are configured by changing the Components attached to them.", uniqueItems: true, format: 'thang-components-array'}, ThangComponentSchema # TODO: uniqueness should be based on "original", not whole thing
+
+ThangTypeSchema.definitions =
+ action: ActionSchema
+ sound: SoundSchema
+
+c.extendBasicProperties(ThangTypeSchema, 'thang.type')
+c.extendSearchableProperties(ThangTypeSchema)
+c.extendVersionedProperties(ThangTypeSchema, 'thang.type')
+
+module.exports = ThangTypeSchema
diff --git a/server/schemas/user.coffee b/server/schemas/user.coffee
new file mode 100644
index 000000000..3ee39a944
--- /dev/null
+++ b/server/schemas/user.coffee
@@ -0,0 +1,48 @@
+c = require './common'
+emailSubscriptions = ['announcement', 'tester', 'level_creator', 'developer', 'article_editor', 'translator', 'support']
+
+UserSchema = c.object {},
+ name: c.shortString({title: 'Display Name', default:''})
+ email: c.shortString({title: 'Email', format: 'email'})
+ firstName: c.shortString({title: 'First Name'})
+ lastName: c.shortString({title: 'Last Name'})
+ gender: {type: 'string', 'enum': ['male', 'female']}
+ password: {type: 'string', maxLength: 256, minLength: 2, title:'Password'}
+ passwordReset: {type: 'string'}
+ photoURL: {type: 'string', format: 'url', required: false}
+
+ facebookID: c.shortString({title: 'Facebook ID'})
+ gplusID: c.shortString({title: 'G+ ID'})
+
+ wizardColor1: c.pct({title: 'Wizard Clothes Color'})
+ volume: c.pct({title: 'Volume'})
+ music: {type: 'boolean', default: true}
+ #autocastDelay, or more complex autocast options? I guess I'll see what I need when trying to hook up Scott's suggested autocast behavior
+
+ emailSubscriptions: c.array {uniqueItems: true, 'default': ['announcement']}, {'enum': emailSubscriptions}
+
+ # server controlled
+ permissions: c.array {'default': []}, c.shortString()
+ dateCreated: c.date({title: 'Date Joined'})
+ anonymous: {type: 'boolean', 'default': true}
+ testGroupNumber: {type: 'integer', minimum: 0, maximum: 256, exclusiveMaximum: true}
+ mailChimp: {type: 'object'}
+ hourOfCode: {type: 'boolean'}
+ hourOfCodeComplete: {type: 'boolean'}
+
+ emailLower: c.shortString()
+ nameLower: c.shortString()
+ passwordHash: {type: 'string', maxLength: 256}
+
+ # client side
+ #gravatarProfile: {} (should only ever be kept locally)
+ emailHash: {type: 'string'}
+
+ #Internationalization stuff
+ preferredLanguage: {type: 'string', default: 'en', 'enum': c.getLanguageCodeArray()}
+
+ signedCLA: c.date({title: 'Date Signed the CLA'})
+
+c.extendBasicProperties UserSchema, 'user'
+
+module.exports = UserSchema
diff --git a/server/sprites.coffee b/server/sprites.coffee
new file mode 100644
index 000000000..42e5fc623
--- /dev/null
+++ b/server/sprites.coffee
@@ -0,0 +1,14 @@
+# for sprite page, which uses the age of the files to
+# order the sprites and make the more recent ones
+# show up at the top
+
+module.exports.setupRoutes = (app) ->
+ app.get('/server/sprite-info', (req, res) ->
+ exec = require('child_process').exec
+
+ child = exec('ls -c1t app/assets/images/sprites/',
+ (error, stdout, stderr) ->
+ res.write(stdout)
+ res.end()
+ )
+ )
diff --git a/server_config.js b/server_config.js
new file mode 100644
index 000000000..b4d5f395f
--- /dev/null
+++ b/server_config.js
@@ -0,0 +1,41 @@
+var config = {};
+
+config.unittest = process.argv.indexOf('--unittest') > -1;
+
+config.port = process.env.COCO_PORT || process.env.COCO_NODE_PORT || 3000;
+config.ssl_port =
+ process.env.COCO_SSL_PORT || process.env.COCO_SSL_NODE_PORT || 3443;
+
+config.mongo = {};
+config.mongo.port = process.env.COCO_MONGO_PORT || 27017;
+config.mongo.host = process.env.COCO_MONGO_HOST || 'localhost';
+config.mongo.db = process.env.COCO_MONGO_DATABASE_NAME || 'coco';
+
+if(config.unittest) {
+ config.port += 1;
+ config.ssl_port += 1;
+ config.mongo.host = 'localhost';
+}
+
+else {
+ config.mongo.username = process.env.COCO_MONGO_USERNAME || '';
+ config.mongo.password = process.env.COCO_MONGO_PASSWORD || '';
+}
+
+config.mail = {};
+config.mail.service = process.env.COCO_MAIL_SERVICE_NAME || "Zoho";
+config.mail.username = process.env.COCO_MAIL_SERVICE_USERNAME || "";
+config.mail.password = process.env.COCO_MAIL_SERVICE_PASSWORD || "";
+config.mail.mailchimpAPIKey = process.env.COCO_MAILCHIMP_API_KEY || '';
+
+config.salt = process.env.COCO_SALT || 'pepper';
+config.cookie_secret = process.env.COCO_COOKIE_SECRET || 'chips ahoy';
+
+config.isProduction = config.mongo.host != 'localhost';
+
+if(!config.unittest && !config.isProduction) {
+ // change artificially slow down non-static requests for testing
+ config.slow_down = false;
+}
+
+module.exports = config;
diff --git a/test/app/first.coffee b/test/app/first.coffee
new file mode 100644
index 000000000..022272833
--- /dev/null
+++ b/test/app/first.coffee
@@ -0,0 +1,26 @@
+# Karma has real issues logging arbitrary objects
+# But we log them all the time
+# Wrapping console.log so this stops messing up our tests.
+
+ll = console.log
+
+console.log = (splat...) ->
+ try
+ s = (JSON.stringify(i) for i in splat)
+ ll(_.string.join(', ', s...))
+ catch TypeError
+ console.log('could not log what you tried to log')
+
+console.warn = (splat...) ->
+ console.log("WARN", splat...)
+
+console.error = (splat...) ->
+ console.log("ERROR", splat...)
+
+
+# When the page loads the first time, it doesn't actually load if there's no 'me' loaded.
+# Get past this by creating a fake 'me'
+
+#User = require 'models/User'
+#auth = require 'lib/auth'
+#auth.me = new User({anonymous:true})
\ No newline at end of file
diff --git a/test/app/lib/forms.coffee b/test/app/lib/forms.coffee
new file mode 100644
index 000000000..c7f040d99
--- /dev/null
+++ b/test/app/lib/forms.coffee
@@ -0,0 +1,21 @@
+describe 'forms library', ->
+ forms = require 'lib/forms'
+ Router = require 'lib/Router'
+ #it 'adds errors to the create account form', ->
+ # router = new Router()
+ # router.openRoute('home')
+ #
+ # # doesn't work
+ # console.log "going to click", $('button[data-target="modal/signup"]').click().length, "signup buttons"
+ # forms.applyErrorsToForm($('#signup-modal'), [message:"is bad", property:"email"])
+ # messages = $('#signup-modal .help-inline')
+ # expect(messages.length).toBe(1)
+ # expect($('#signup-modal .error').length).toBe(1)
+ # expect(messages.text()).toBe('Email is bad.')
+ #
+ #it 'clears errors from the create account form', ->
+ # expect($('#signup-modal .help-inline').length).toBe(1)
+ # expect($('#signup-modal .error').length).toBe(1)
+ # forms.clearFormAlerts($('#signup-modal'))
+ # expect($('#signup-modal .help-inline').length).toBe(0)
+ # expect($('#signup-modal .error').length).toBe(0)
\ No newline at end of file
diff --git a/test/app/lib/goal_manager.coffee b/test/app/lib/goal_manager.coffee
new file mode 100644
index 000000000..2550676b5
--- /dev/null
+++ b/test/app/lib/goal_manager.coffee
@@ -0,0 +1,56 @@
+xdescribe 'GoalManager', ->
+ GoalManager = require('lib/goal_manager')
+
+ liveState =
+ stateMap:
+ '1':{health:10}
+ '2':{health:5}
+
+ halfLiveState =
+ stateMap:
+ '1':{health:0}
+ '2':{health:5}
+
+ deadState =
+ stateMap:
+ '1':{health:0}
+ '2':{health:-5}
+
+
+ it 'can tell when everyone is dead', ->
+ gm = new GoalManager(1)
+ world =
+ frames: [liveState, liveState, liveState]
+ gm.setWorld(world)
+
+ goal = { id: 'die', name: 'Kill Everyone', killGuy: ['1','2'] }
+ gm.addGoal goal
+
+ expect(gm.goalStates['die'].complete).toBe(false)
+
+ world.frames.push(deadState)
+ world.frames.push(deadState)
+ gm.setWorld(world)
+ expect(gm.goalStates['die'].complete).toBe(true)
+ expect(gm.goalStates['die'].frameCompleted).toBe(3)
+
+ it 'can tell when someone is saved', ->
+ gm = new GoalManager(1)
+ world =
+ frames: [liveState, liveState, liveState, deadState, deadState]
+ gm.setWorld(world)
+
+ goal = { id: 'live', name: 'Save guy 2', saveGuy: '2' }
+ gm.addGoal goal
+
+ expect(gm.goalStates['live'].complete).toBe(false)
+ world =
+ frames: [liveState, liveState, liveState, liveState, liveState]
+ gm.setWorld(world)
+ expect(gm.goalStates['live'].complete).toBe(true)
+
+# world.frames.push(deadState)
+# world.frames.push(deadState)
+# gm.setWorld(world)
+# expect(gm.goalStates['live'].complete).toBe(true)
+# expect(gm.goalStates['live'].frameCompleted).toBe(3)
\ No newline at end of file
diff --git a/test/app/lib/path.coffee b/test/app/lib/path.coffee
new file mode 100644
index 000000000..031b49025
--- /dev/null
+++ b/test/app/lib/path.coffee
@@ -0,0 +1,26 @@
+describe('Path.createPath', ->
+ path = require 'lib/surface/path'
+ it('ignores the first point', ->
+ points = [[0,0], [1,1], [2,2]]
+ g = new createjs.Graphics()
+ g.lineTo = jasmine.createSpy('graphicz')
+ path.createPath(points, {tail_color:[100,100,100,0.0]}, g)
+ expect(g.lineTo.calls.length).toBe(2)
+ expect(g.lineTo.calls[0].args[0]).toBe(points[1][0])
+ )
+
+# # BROKEN
+ xit('dots correctly', ->
+ points = ([x,x] for x in [0..30])
+ g = new createjs.Graphics()
+ calls = []
+ funcs = ['lineTo', 'moveTo', 'beginStroke', 'endStroke', 'setStrokeStyle']
+ for funcName in funcs
+ f = (funcName) => (args...) =>
+ calls.push("#{funcName}(#{args})")
+ g[funcName] = jasmine.createSpy('graphics').andCallFake(f(funcName))
+ path.createPath(points, {dotted:true}, g)
+ expect(g.beginStroke.calls.length).toBe(4)
+ )
+)
+
diff --git a/test/app/lib/router.coffee b/test/app/lib/router.coffee
new file mode 100644
index 000000000..43fe3f9c5
--- /dev/null
+++ b/test/app/lib/router.coffee
@@ -0,0 +1,9 @@
+describe 'Router', ->
+ Router = require 'lib/Router'
+ it 'caches the home view', ->
+ router = new Router()
+ router.openRoute('home')
+ expect(router.cache['home']).toBe(router.currentView)
+ home = router.currentView
+ router.openRoute('home')
+ expect(router.cache['home']).toBe(router.currentView)
diff --git a/test/app/lib/script_manager.coffee b/test/app/lib/script_manager.coffee
new file mode 100644
index 000000000..b130399c7
--- /dev/null
+++ b/test/app/lib/script_manager.coffee
@@ -0,0 +1,151 @@
+describe('ScriptManager', ->
+ SM = require 'lib/ScriptManager'
+ it('broadcasts note with event upon hearing from channel', ->
+ note = {channel: 'cnn', event: {1:1}}
+ noteGroup = {duration: 0, notes: [note]}
+ script = {channel: 'pbs', noteChain: [noteGroup]}
+
+ sm = new SM([script])
+ sm.paused = false
+
+ gotEvent = {}
+ f = (event) -> gotEvent = event
+ Backbone.Mediator.subscribe('cnn', f, @)
+ Backbone.Mediator.publish('pbs')
+ expect(gotEvent[1]).toBe(note.event[1])
+ sm.destroy()
+ Backbone.Mediator.unsubscribe('cnn', f, @)
+ )
+
+ xit('is silent when script event do not match', ->
+ note = {channel: 'cnn', event: {1:1}}
+ noteGroup = {duration: 0, notes: [note]}
+ script =
+ channel: 'pbs'
+ eventPrereqs: [
+ eventProps: 'foo'
+ equalTo: 'bar'
+ ]
+
+ noteChain: [noteGroup]
+
+ sm = new SM([script])
+ sm.paused = false
+
+ gotEvent = null
+ f = (event) -> gotEvent = event
+ Backbone.Mediator.subscribe('cnn', f, @)
+
+ # bunch of mismatches
+ Backbone.Mediator.publish('pbs', {foo:'rad'})
+ expect(gotEvent).toBeNull()
+ Backbone.Mediator.publish('pbs', 'bar')
+ Backbone.Mediator.publish('pbs')
+ Backbone.Mediator.publish('pbs', {foo:'bar'})
+ expect(gotEvent[1]).toBe(note.event[1])
+ sm.destroy()
+ Backbone.Mediator.unsubscribe('cnn', f, @)
+ )
+
+ xit('makes no subscriptions when something is invalid', ->
+ note = {event: {1:1}} # channel is required
+ noteGroup = {notes: [note]}
+ script = {channel: 'pbs', noteChain: [noteGroup]}
+ sm = new SM([script])
+ expect(sm.subscriptions['pbs']).toBe(undefined)
+ sm.destroy()
+ )
+
+ xit('fills out lots of notes based on note group properties', ->
+ note = {channel: 'cnn', event: {1:1}}
+
+ noteGroup =
+ duration: 0
+ botPos: [1,2]
+ botMessage: 'testers'
+ domHighlight: '#code-area'
+ surfaceHighlights: ['Guy0', 'Guy1']
+ scrubToTime: 20
+ notes: [note]
+
+ script = {channel: 'pbs', noteChain: [noteGroup]}
+
+ sm = new SM([script])
+ sm.paused = false
+
+ Backbone.Mediator.publish('pbs')
+ expect(sm.lastNoteGroup.notes.length).toBe(7)
+ channels = (note.channel for note in sm.lastNoteGroup.notes)
+ expect(channels).toContain('cnn')
+ expect(channels).toContain('level-bot-move')
+ expect(channels).toContain('level-bot-say')
+ expect(channels).toContain('level-highlight-dom')
+ expect(channels).toContain('level-highlight-sprites')
+ expect(channels).toContain('level-set-time')
+ expect(channels).toContain('level-disable-controls')
+ sm.destroy()
+ )
+
+ it('releases notes based on user confirmation', ->
+ note1 = {channel: 'cnn', event: {1:1}}
+ note2 = {channel: 'cbs', event: {2:2}}
+ noteGroup1 = {duration: 0, notes: [note1]}
+ noteGroup2 = {duration: 0, notes: [note2]}
+ script = {channel: 'pbs', noteChain: [noteGroup1, noteGroup2]}
+ sm = new SM([script])
+ sm.paused = false
+
+ gotCnnEvent = null
+ f1 = (event) -> gotCnnEvent = event
+ Backbone.Mediator.subscribe('cnn', f1, @)
+
+ gotCbsEvent = null
+ f2 = (event) -> gotCbsEvent = event
+ Backbone.Mediator.subscribe('cbs', f2, @)
+
+ Backbone.Mediator.publish('pbs')
+ expect(gotCnnEvent[1]).toBe(1)
+ expect(gotCbsEvent).toBeNull()
+ expect(sm.scriptInProgress).toBe(true)
+ runs(-> Backbone.Mediator.publish('end-current-script'))
+ f = -> gotCbsEvent?
+ waitsFor(f, "The next event should have been published", 20)
+ f = ->
+ expect(gotCnnEvent[1]).toBe(1)
+ expect(gotCbsEvent[2]).toBe(2)
+ expect(sm.scriptInProgress).toBe(true)
+ Backbone.Mediator.publish('end-current-script')
+ expect(sm.scriptInProgress).toBe(false)
+ sm.destroy()
+ Backbone.Mediator.unsubscribe('cnn', f1, @)
+ Backbone.Mediator.unsubscribe('cbs', f2, @)
+ runs(f)
+ )
+
+ xit('ignores triggers for scripts waiting for other scripts to fire', ->
+ # channel2 won't fire the cbs notification until channel1 does its thing
+ note1 = {channel: 'cnn', event: {1:1}}
+ note2 = {channel: 'cbs', event: {2:2}}
+ noteGroup1 = {duration: 0, notes: [note1]}
+ noteGroup2 = {duration: 0, notes: [note2]}
+ script1 = {channel: 'channel1', id: 'channel1Script', noteChain: [noteGroup1]}
+ script2 = {channel: 'channel2', scriptPrereqs: ['channel1Script'], noteChain: [noteGroup2]}
+
+ sm = new SM([script1, script2])
+ sm.paused = false
+ gotCbsEvent = null
+ f = (event) -> gotCbsEvent = event
+ Backbone.Mediator.subscribe('cbs', f, @)
+
+ Backbone.Mediator.publish('channel2')
+ expect(gotCbsEvent).toBeNull() # channel1 hasn't done its thing yet
+ Backbone.Mediator.publish('channel1')
+ expect(gotCbsEvent).toBeNull() # channel2 needs to be triggered again
+ Backbone.Mediator.publish('channel2')
+ expect(gotCbsEvent).toBeNull() # channel1 is still waiting for user confirmation
+ Backbone.Mediator.publish('end-current-script')
+ expect(gotCbsEvent[1]).toBe(2) # and finally the second script is fired
+ sm.destroy()
+ Backbone.Mediator.unsubscribe('cnn', f, @)
+ )
+)
diff --git a/test/app/lib/surface/camera.coffee b/test/app/lib/surface/camera.coffee
new file mode 100644
index 000000000..cedbea13f
--- /dev/null
+++ b/test/app/lib/surface/camera.coffee
@@ -0,0 +1,186 @@
+describe 'Camera (Surface point of view)', ->
+ Camera = require 'lib/surface/Camera'
+
+ expectPositionsEqual = (p1, p2) ->
+ expect(p1.x).toBeCloseTo p2.x
+ expect(p1.y).toBeCloseTo p2.y
+ expect(p1.z).toBeCloseTo p2.z if p2.z?
+
+ checkConversionsFromWorldPos = (wop, cam) ->
+ # wop = world pos
+ # sup = surface pos
+ # cap = canvas pos
+ # scp = screen pos
+
+ sup = cam.worldToSurface wop
+ expect(sup.x).toBeCloseTo wop.x * Camera.PPM
+ expect(sup.y).toBeCloseTo cam.surfaceHeight - (wop.y + wop.z * cam.z2y) * cam.y2x * Camera.PPM
+
+ cap = cam.worldToCanvas wop
+ expect(cap.x).toBeCloseTo (sup.x - cam.surfaceViewport.x) * cam.zoom
+ expect(cap.y).toBeCloseTo (sup.y - cam.surfaceViewport.y) * cam.zoom
+
+
+ scp = cam.worldToScreen wop
+ # If we ever want to use screen conversion, then make it and add this test
+ #expect(scp.x).toBeCloseTo cap.x * @someCanvasToScreenXScaleFactor
+ #expect(scp.y).toBeCloseTo cap.y * @someCanvasToScreenYScaleFactor
+
+ wop2 = cam.surfaceToWorld sup
+ expect(wop2.x).toBeCloseTo wop.x
+ expect(wop2.y).toBeCloseTo wop.y + wop.z * cam.z2y
+
+ # Make sure to call all twelve conversions in here. Can be redundant.
+ expectPositionsEqual sup, cam.worldToSurface wop2 # 0
+ expectPositionsEqual cap, cam.surfaceToCanvas sup # 1
+ expectPositionsEqual scp, cam.canvasToScreen cap # 2
+ expectPositionsEqual cap, cam.screenToCanvas scp # 3
+ expectPositionsEqual sup, cam.canvasToSurface cap # 4
+ expectPositionsEqual wop2, cam.surfaceToWorld sup # 5
+ expectPositionsEqual wop2, cam.canvasToWorld cap # 6
+ expectPositionsEqual cap, cam.worldToCanvas wop # 7
+ expectPositionsEqual scp, cam.worldToScreen wop # 8
+ expectPositionsEqual scp, cam.surfaceToScreen sup # 9
+ expectPositionsEqual sup, cam.screenToSurface scp # 10
+ expectPositionsEqual wop2, cam.screenToWorld scp # 11
+
+ checkCameraPos = (cam, wop) ->
+ botFOV = cam.x2y * cam.vFOV / (cam.y2x + cam.x2y)
+ botDist = (cam.worldViewport.height) * Math.sin(cam.angle) / Math.sin(botFOV)
+ camDist = (cam.worldViewport.height / 2) * Math.sin(Math.PI - cam.angle - botFOV) / Math.sin(botFOV)
+ targetPos =
+ x: cam.worldViewport.cx
+ y: cam.worldViewport.cy - camDist * cam.y2x * cam.z2y
+ z: camDist * cam.z2x * cam.y2z
+ #console.log "botFOV", botFOV * 180 / Math.PI, "botDist", botDist, "camDist", camDist, "target pos", targetPos, "actual pos", cam.cameraWorldPos()
+ expectPositionsEqual cam.cameraWorldPos(), targetPos
+
+ if wop
+ dx = targetPos.x - wop.x
+ dy = targetPos.y - wop.y
+ dz = targetPos.z - wop.z
+ d = cam.distanceTo wop
+ expect(d).toBeCloseTo Math.sqrt(dx * dx + dy * dy + dz * dz)
+ # This is fairly vulnerable to numerical instability, so we limit the number of digits to consider.
+ decimalPlaces = 3 - Math.floor(Math.log(d / camDist) / Math.log(10))
+ expect(cam.distanceRatioTo wop).toBeCloseTo d / camDist, decimalPlaces
+
+ testWops = [
+ {x: 3, y: 4, z: 7}
+ {x: -4, y: 12, z: 2}
+ {x: 0, y: 0, z: 0}
+ ]
+ testCanvasSizes = [
+ {width: 100, height: 100}
+ {width: 200, height: 50}
+ ]
+ testLayer = {scaleX: 1, scaleY: 1, regX: 0, regY: 0}
+ testZooms = [0.5, 1, 2]
+ testZoomTargets = [
+ null,
+ {x: 50, y: 50}
+ {x: 0, y: 150}
+ ]
+ testAngles = [0, Math.PI / 4, null, Math.PI / 2]
+ testFOVs = [Math.PI / 6, Math.PI / 3, Math.PI / 2, Math.PI]
+
+ it 'handles lots of different cases correctly', ->
+ for wop in testWops
+ for size in testCanvasSizes
+ for zoom in testZooms
+ for target in testZoomTargets
+ for angle in testAngles
+ for fov in testFOVs
+ cam = new Camera size.width, size.height, size.width * Camera.MPP, size.height * Camera.MPP, testLayer, zoom, null, angle, fov
+ checkCameraPos cam, wop
+ cam.zoomTo target, zoom, 0
+ checkConversionsFromWorldPos wop, cam
+ checkCameraPos cam, wop
+
+ it 'works at 90 degrees', ->
+ cam = new Camera 100, 100, 100 * Camera.MPP, 100 * Camera.MPP, testLayer, 1, null, Math.PI / 2
+ expect(cam.x2y).toBeCloseTo 1
+ expect(cam.x2z).toBeGreaterThan 9001
+ expect(cam.z2y).toBeCloseTo 0
+
+ it 'works at 0 degrees', ->
+ cam = new Camera 100, 100, 100 * Camera.MPP, 100 * Camera.MPP, testLayer, 1, null, 0
+ expect(cam.x2y).toBeGreaterThan 9001
+ expect(cam.x2z).toBeCloseTo 1
+ expect(cam.z2y).toBeGreaterThan 9001
+
+ it 'works at 45 degrees', ->
+ cam = new Camera 100, 100, 100 * Camera.MPP, 100 * Camera.MPP, testLayer, 1, null, Math.PI / 4
+ expect(cam.x2y).toBeCloseTo Math.sqrt(2)
+ expect(cam.x2z).toBeCloseTo Math.sqrt(2)
+ expect(cam.z2y).toBeCloseTo 1
+
+ it 'works at default angle of asin(0.75) ~= 48.9 degrees', ->
+ cam = new Camera 100, 100, 100 * Camera.MPP, 100 * Camera.MPP, testLayer, 1
+ angle = Math.asin 3 / 4
+ expect(cam.angle).toBeCloseTo angle
+ expect(cam.x2y).toBeCloseTo 4 / 3
+ expect(cam.x2z).toBeCloseTo 1 / Math.cos angle
+ expect(cam.z2y).toBeCloseTo (4 / 3) * Math.cos angle
+
+ it 'works at 2x zoom, 90 degrees', ->
+ cam = new Camera 100, 100, 100 * Camera.MPP, 100 * Camera.MPP, testLayer, 2, null, Math.PI / 2
+ checkCameraPos cam
+ wop = x: 5, y: 2.5, z: 7
+ cap = cam.worldToCanvas wop
+ expectPositionsEqual cap, {x: 50, y: 100}
+ cam.zoomTo {x: 50, y: 75}, 2, 0
+ checkCameraPos cam
+ cap = cam.worldToCanvas wop
+ expectPositionsEqual cap, {x: 50, y: 50}
+ cam.zoomTo {x: 50, y: 75}, 4, 0
+ checkCameraPos cam
+ cap = cam.worldToCanvas wop
+ expectPositionsEqual cap, {x: 50, y: 50}
+ # Now let's try zooming on the edge of the screen; we should be bounded to the surface viewport
+ cam.zoomTo {x: 100, y: 100}, 2, 0
+ checkCameraPos cam
+ cap = cam.worldToCanvas wop
+ expectPositionsEqual cap, {x: 0, y: 50}
+
+ it 'works at 2x zoom, 30 degrees', ->
+ cam = new Camera 100, 100, 100 * Camera.MPP, 2 * 100 * Camera.MPP, testLayer, 2, null, Math.PI / 6
+ expect(cam.x2y).toBeCloseTo 2
+ expect(cam.x2z).toBeCloseTo 1 / Math.cos(Math.PI / 6)
+ checkCameraPos cam
+ wop = x: 5, y: 4, z: 6 * cam.y2z # like x: 5, y: 10 out of world width: 10, height: 20
+ sup = cam.worldToSurface wop
+ expect(cam.surfaceToWorld(sup).y).toBeCloseTo 10
+ expectPositionsEqual sup, {x: 50, y: 50}
+ cap = cam.surfaceToCanvas sup
+ expectPositionsEqual cap, {x: 50, y: 50}
+ # Zoom to bottom edge of screen
+ cam.zoomTo {x: 50, y: 100}, 2, 0
+ checkCameraPos cam
+ cap = cam.worldToCanvas wop
+ expectPositionsEqual cap, {x: 50, y: 0}
+ cam.zoomTo {x: 50, y: 100}, 4, 0
+ checkCameraPos cam
+ cap = cam.worldToCanvas wop
+ expectPositionsEqual cap, {x: 50, y: -100}
+
+ it 'works at 2x zoom, 60 degree hFOV', ->
+ cam = new Camera 100, 100, 100 * Camera.MPP, 100 * Camera.MPP, testLayer, 2, null, null, 0.01
+ checkCameraPos cam
+
+ it 'works at 2x zoom, 60 degree hFOV, 40 degree hFOV', ->
+ cam = new Camera 100, 63.041494, 100 * Camera.MPP, 63.041494 * Camera.MPP, testLayer, 2, null, null, Math.PI / 3
+ checkCameraPos cam
+
+ it 'works on a surface wider than it is tall, 30 degrees, default viewing upper left corner', ->
+ cam = new Camera 100, 100, 200 * Camera.MPP, 2 * 50 * Camera.MPP, testLayer, 1, {x: 0, y: 0}, Math.PI / 6
+ checkCameraPos cam
+ expect(cam.zoom).toBeCloseTo 2
+ wop = x: 5, y: 4, z: 6 * cam.y2z # like x: 5, y: 10 out of world width: 20, height: 10
+ cap = cam.worldToCanvas wop
+ expectPositionsEqual cap, {x: 100, y: 0}
+ # Zoom to far right edge of screen and try to zoom out
+ cam.zoomTo {x: 9001, y: 25}, 0.1, 0
+ checkCameraPos cam
+ cap = cam.worldToCanvas wop
+ expectPositionsEqual cap, {x: -200, y: 0}
diff --git a/test/app/lib/world/goal_manager.coffee b/test/app/lib/world/goal_manager.coffee
new file mode 100644
index 000000000..6cf485cab
--- /dev/null
+++ b/test/app/lib/world/goal_manager.coffee
@@ -0,0 +1,186 @@
+describe('World', ->
+ GoalManager = require 'lib/world/GoalManager'
+ validator = require 'validators/goal'
+
+ killGoal = { name: 'Kill Guy', killGuy: ['Guy1', 'Guy2'], id:'killguy'}
+ saveGoal = { name: 'Save Guy', saveGuy: ['Guy1', 'Guy2'], id:'saveguy'}
+ getToLocGoal = { name: 'Go there', getToLocation: {target:'Frying Pan', who:'Potato'}, id:'id'}
+ keepFromLocGoal = { name: 'Go there', keepFromLocation: {target:'Frying Pan', who:'Potato'}, id:'id'}
+ leaveMapGoal = { name: 'Go away', leaveOffSide: {who:'Yall'}, id:'id'}
+ stayMapGoal = { name: 'Stay here', keepFromLeavingOffSide: {who:'Yall'}, id:'id'}
+ getItemGoal = { name: 'Mine', getItem: {who:'Grabby', itemID:'Sandwich'}, id:'id'}
+ keepItemGoal = { name: 'Not Yours', keepFromGettingItem: {who:'Grabby', itemID:'Sandwich'}, id:'id'}
+
+ it 'uses valid goals', ->
+ goals = [
+ killGoal, saveGoal,
+ getToLocGoal, keepFromLocGoal,
+ leaveMapGoal, stayMapGoal,
+ getItemGoal, keepItemGoal,
+ ]
+ for goal in goals
+ result = validator(goal)
+ expect(result.valid).toBe(true)
+
+ it('handles kill goal', ->
+ gm = new GoalManager()
+ gm.setGoals([killGoal])
+ gm.worldGenerationWillBegin()
+ gm.submitWorldGenerationEvent('world:thang-died', {thang:{id:'Guy1'}}, 10)
+ gm.worldGenerationEnded()
+ goalStates = gm.getGoalStates()
+ expect(goalStates.killguy.status).toBe('incomplete')
+ expect(goalStates.killguy.killed.Guy1).toBe(true)
+ expect(goalStates.killguy.killed.Guy2).toBe(false)
+ expect(goalStates.killguy.keyFrame).toBe(0)
+
+ gm.submitWorldGenerationEvent('world:thang-died', {thang:{id:'Guy2'}}, 20)
+ goalStates = gm.getGoalStates()
+ expect(goalStates.killguy.status).toBe('success')
+ expect(goalStates.killguy.killed.Guy1).toBe(true)
+ expect(goalStates.killguy.killed.Guy2).toBe(true)
+ expect(goalStates.killguy.keyFrame).toBe(20)
+ )
+
+ it('handles save goal', ->
+ gm = new GoalManager()
+ gm.setGoals([saveGoal])
+ gm.worldGenerationWillBegin()
+ gm.submitWorldGenerationEvent('world:thang-died', {thang:{id:'Guy1'}}, 10)
+ gm.worldGenerationEnded()
+ goalStates = gm.getGoalStates()
+ expect(goalStates.saveguy.status).toBe('failure')
+ expect(goalStates.saveguy.killed.Guy1).toBe(true)
+ expect(goalStates.saveguy.killed.Guy2).toBe(false)
+ expect(goalStates.saveguy.keyFrame).toBe(10)
+
+ gm = new GoalManager()
+ gm.setGoals([saveGoal])
+ gm.worldGenerationWillBegin()
+ gm.worldGenerationEnded()
+ goalStates = gm.getGoalStates()
+ expect(goalStates.saveguy.status).toBe('success')
+ expect(goalStates.saveguy.killed.Guy1).toBe(false)
+ expect(goalStates.saveguy.killed.Guy2).toBe(false)
+ expect(goalStates.saveguy.keyFrame).toBe('end')
+ )
+
+ it 'handles getToLocation', ->
+ gm = new GoalManager()
+ gm.setGoals([getToLocGoal])
+ gm.worldGenerationWillBegin()
+ gm.worldGenerationEnded()
+ goalStates = gm.getGoalStates()
+ expect(goalStates.id.status).toBe('incomplete')
+ expect(goalStates.id.arrived.Potato).toBe(false)
+ expect(goalStates.id.keyFrame).toBe(0)
+
+ gm = new GoalManager()
+ gm.setGoals([getToLocGoal])
+ gm.worldGenerationWillBegin()
+ gm.submitWorldGenerationEvent('world:thang-touched-goal', {actor:{id:'Potato'}, touched:{id:'Frying Pan'}}, 10)
+ gm.worldGenerationEnded()
+ goalStates = gm.getGoalStates()
+ expect(goalStates.id.status).toBe('success')
+ expect(goalStates.id.arrived.Potato).toBe(true)
+ expect(goalStates.id.keyFrame).toBe(10)
+
+ it 'handles keepFromLocation', ->
+ gm = new GoalManager()
+ gm.setGoals([keepFromLocGoal])
+ gm.worldGenerationWillBegin()
+ gm.submitWorldGenerationEvent('world:thang-touched-goal', {actor:{id:'Potato'}, touched:{id:'Frying Pan'}}, 10)
+ gm.worldGenerationEnded()
+ goalStates = gm.getGoalStates()
+ expect(goalStates.id.status).toBe('failure')
+ expect(goalStates.id.arrived.Potato).toBe(true)
+ expect(goalStates.id.keyFrame).toBe(10)
+
+ gm = new GoalManager()
+ gm.setGoals([keepFromLocGoal])
+ gm.worldGenerationWillBegin()
+ gm.worldGenerationEnded()
+ goalStates = gm.getGoalStates()
+ expect(goalStates.id.status).toBe('success')
+ expect(goalStates.id.arrived.Potato).toBe(false)
+ expect(goalStates.id.keyFrame).toBe('end')
+
+ it 'handles leaveOffSide', ->
+ gm = new GoalManager()
+ gm.setGoals([leaveMapGoal])
+ gm.worldGenerationWillBegin()
+ gm.worldGenerationEnded()
+ goalStates = gm.getGoalStates()
+ expect(goalStates.id.status).toBe('incomplete')
+ expect(goalStates.id.left.Yall).toBe(false)
+ expect(goalStates.id.keyFrame).toBe(0)
+
+ gm = new GoalManager()
+ gm.setGoals([leaveMapGoal])
+ gm.worldGenerationWillBegin()
+ gm.submitWorldGenerationEvent('world:thang-left-map', {thang:{id:'Yall'}}, 10)
+ gm.worldGenerationEnded()
+ goalStates = gm.getGoalStates()
+ expect(goalStates.id.status).toBe('success')
+ expect(goalStates.id.left.Yall).toBe(true)
+ expect(goalStates.id.keyFrame).toBe(10)
+
+ it 'handles keepFromLeavingOffSide', ->
+ gm = new GoalManager()
+ gm.setGoals([stayMapGoal])
+ gm.worldGenerationWillBegin()
+ gm.submitWorldGenerationEvent('world:thang-left-map', {thang:{id:'Yall'}}, 10)
+ gm.worldGenerationEnded()
+ goalStates = gm.getGoalStates()
+ expect(goalStates.id.status).toBe('failure')
+ expect(goalStates.id.left.Yall).toBe(true)
+ expect(goalStates.id.keyFrame).toBe(10)
+
+ gm = new GoalManager()
+ gm.setGoals([stayMapGoal])
+ gm.worldGenerationWillBegin()
+ gm.worldGenerationEnded()
+ goalStates = gm.getGoalStates()
+ expect(goalStates.id.status).toBe('success')
+ expect(goalStates.id.left.Yall).toBe(false)
+ expect(goalStates.id.keyFrame).toBe('end')
+
+ it 'handles getItem', ->
+ gm = new GoalManager()
+ gm.setGoals([getItemGoal])
+ gm.worldGenerationWillBegin()
+ gm.worldGenerationEnded()
+ goalStates = gm.getGoalStates()
+ expect(goalStates.id.status).toBe('incomplete')
+ expect(goalStates.id.collected.Grabby).toBe(false)
+ expect(goalStates.id.keyFrame).toBe(0)
+
+ gm = new GoalManager()
+ gm.setGoals([getItemGoal])
+ gm.worldGenerationWillBegin()
+ gm.submitWorldGenerationEvent('world:thang-collected-item', {actor:{id:'Grabby'}, item:{id:'Sandwich'}}, 10)
+ gm.worldGenerationEnded()
+ goalStates = gm.getGoalStates()
+ expect(goalStates.id.status).toBe('success')
+ expect(goalStates.id.collected.Grabby).toBe(true)
+ expect(goalStates.id.keyFrame).toBe(10)
+
+ it 'handles keepFromGettingItem', ->
+ gm = new GoalManager()
+ gm.setGoals([keepItemGoal])
+ gm.worldGenerationWillBegin()
+ gm.submitWorldGenerationEvent('world:thang-collected-item', {actor:{id:'Grabby'}, item:{id:'Sandwich'}}, 10)
+ gm.worldGenerationEnded()
+ goalStates = gm.getGoalStates()
+ expect(goalStates.id.status).toBe('failure')
+ expect(goalStates.id.collected.Grabby).toBe(true)
+ expect(goalStates.id.keyFrame).toBe(10)
+
+ gm = new GoalManager()
+ gm.setGoals([keepItemGoal])
+ gm.worldGenerationWillBegin()
+ gm.worldGenerationEnded()
+ goalStates = gm.getGoalStates()
+ expect(goalStates.id.status).toBe('success')
+ expect(goalStates.id.collected.Grabby).toBe(false)
+ expect(goalStates.id.keyFrame).toBe('end'))
\ No newline at end of file
diff --git a/test/app/lib/world/rectangle.coffee b/test/app/lib/world/rectangle.coffee
new file mode 100644
index 000000000..1f674a8a8
--- /dev/null
+++ b/test/app/lib/world/rectangle.coffee
@@ -0,0 +1,81 @@
+describe 'Rectangle', ->
+ Rectangle = require 'lib/world/rectangle'
+ Vector = require 'lib/world/vector'
+
+ it 'contains its own center', ->
+ rect = new Rectangle 0, 0, 10, 10
+ expect(rect.containsPoint(new Vector 0, 0)).toBe true
+
+ it 'contains a point when rotated', ->
+ rect = new Rectangle 0, -20, 40, 40, 3 * Math.PI / 4
+ p = new Vector 0, 2
+ expect(rect.containsPoint(p, true)).toBe true
+
+ it 'correctly calculates distance to a faraway point', ->
+ rect = new Rectangle 100, 50, 20, 40
+ p = new Vector 200, 300
+ d = 10 * Math.sqrt(610)
+ expect(rect.distanceToPoint(p)).toBeCloseTo d
+ rect.rotation = Math.PI / 2
+ d = 80 * Math.sqrt(10)
+ expect(rect.distanceToPoint(p)).toBeCloseTo d
+
+ it 'does not modify itself or target Vector when calculating distance', ->
+ rect = new Rectangle -100, -200, 1, 100
+ rect2 = rect.copy()
+ p = new Vector -100.25, -101
+ p2 = p.copy()
+ rect.distanceToPoint(p)
+ expect(p.x).toEqual p2.x
+ expect(p.y).toEqual p2.y
+ expect(rect.x).toEqual rect2.x
+ expect(rect.y).toEqual rect2.y
+ expect(rect.width).toEqual rect2.width
+ expect(rect.height).toEqual rect2.height
+ expect(rect.rotation).toEqual rect2.rotation
+
+ it 'correctly calculates distance to contained point', ->
+ rect = new Rectangle -100, -200, 1, 100
+ rect2 = rect.copy()
+ p = new Vector -100.25, -160
+ p2 = p.copy()
+ expect(rect.distanceToPoint(p)).toBe 0
+ rect.rotation = 0.00000001 * Math.PI
+ expect(rect.distanceToPoint(p)).toBe 0
+
+ it 'has predictable vertices', ->
+ rect = new Rectangle 50, 50, 100, 100
+ v = rect.vertices()
+ expect(v[0].x).toEqual 0
+ expect(v[0].y).toEqual 0
+ expect(v[1].x).toEqual 0
+ expect(v[1].y).toEqual 100
+ expect(v[2].x).toEqual 100
+ expect(v[2].y).toEqual 100
+ expect(v[3].x).toEqual 100
+ expect(v[3].y).toEqual 0
+
+ it 'has predictable vertices when rotated', ->
+ rect = new Rectangle 50, 50, 100, 100, Math.PI / 4
+ v = rect.vertices()
+ d = (Math.sqrt(2 * 100 * 100) - 100) / 2
+ expect(v[0].x).toBeCloseTo -d
+ expect(v[0].y).toBeCloseTo 50
+ expect(v[1].x).toBeCloseTo 50
+ expect(v[1].y).toBeCloseTo 100 + d
+ expect(v[2].x).toBeCloseTo 100 + d
+ expect(v[2].y).toBeCloseTo 50
+ expect(v[3].x).toBeCloseTo 50
+ expect(v[3].y).toBeCloseTo -d
+
+ it 'is its own AABB when not rotated', ->
+ rect = new Rectangle 10, 20, 30, 40
+ aabb = rect.axisAlignedBoundingBox()
+ for prop in ["x", "y", "width", "height"]
+ expect(rect[prop]).toBe aabb[prop]
+
+ it 'is its own AABB when rotated 180', ->
+ rect = new Rectangle 10, 20, 30, 40, Math.PI
+ aabb = rect.axisAlignedBoundingBox()
+ for prop in ["x", "y", "width", "height"]
+ expect(rect[prop]).toBe aabb[prop]
diff --git a/test/app/lib/world/thang.coffee b/test/app/lib/world/thang.coffee
new file mode 100644
index 000000000..3e275d6df
--- /dev/null
+++ b/test/app/lib/world/thang.coffee
@@ -0,0 +1,14 @@
+describe 'Thang', ->
+ Thang = require 'lib/world/thang'
+ World = require 'lib/world/world'
+ Rectangle = require 'lib/world/rectangle'
+ Vector = require 'lib/world/vector'
+ world = new World()
+
+ #it 'intersects itself', ->
+ # spyOn(Vector, 'subtract').andCallThrough()
+ # for thang in world.thangs
+ # spyOn(thang, 'intersects').andCallThrough()
+ # expect(thang.intersects thang).toBeTruthy()
+ # #console.log thang.intersects.calls[0].args + ''
+ # #console.log "Vector.subtract calls: " + Vector.subtract.calls.length
diff --git a/test/app/lib/world/vector.coffee b/test/app/lib/world/vector.coffee
new file mode 100644
index 000000000..ac5d017d1
--- /dev/null
+++ b/test/app/lib/world/vector.coffee
@@ -0,0 +1,29 @@
+describe 'Vector', ->
+ Rectangle = require 'lib/world/rectangle'
+ Vector = require 'lib/world/vector'
+
+ it 'rotates properly', ->
+ v = new Vector 200, 300
+ v.rotate Math.PI / 2
+ expect(v.x).toBeCloseTo -300
+ expect(v.y).toBeCloseTo 200
+
+ v.rotate Math.PI / 4
+ expect(v.x).toBeCloseTo -250 * Math.sqrt 2
+ expect(v.y).toBeCloseTo -50 * Math.sqrt 2
+
+ it 'hardly moves when rotated a tiny bit', ->
+ v = new Vector -100.25, -101
+ v2 = v.copy()
+ v2.rotate 0.0000001 * Math.PI
+ expect(v.distance v2).toBeCloseTo 0
+
+ v = new Vector 100.25, -101
+ v2 = v.copy()
+ v2.rotate 1.99999999 * Math.PI
+ expect(v.distance v2).toBeCloseTo 0
+
+ v = new Vector 10.25, 301
+ v2 = v.copy()
+ v2.rotate -0.0000001 * Math.PI
+ expect(v.distance v2).toBeCloseTo 0
diff --git a/test/app/lib/world/world.coffee b/test/app/lib/world/world.coffee
new file mode 100644
index 000000000..bfef231df
--- /dev/null
+++ b/test/app/lib/world/world.coffee
@@ -0,0 +1,2 @@
+describe 'World', ->
+ World = require 'lib/world/world'
diff --git a/test/app/views/editor/components_tab_view.coffee b/test/app/views/editor/components_tab_view.coffee
new file mode 100644
index 000000000..a7c311041
--- /dev/null
+++ b/test/app/views/editor/components_tab_view.coffee
@@ -0,0 +1,4 @@
+describe 'editor/level/thangs_tab', ->
+ ComponentsTabView = require 'views/editor/level/components_tab_view'
+
+ it 'does stuff', ->
diff --git a/test/app/views/editor/editor_view.coffee b/test/app/views/editor/editor_view.coffee
new file mode 100644
index 000000000..cb20bd2bd
--- /dev/null
+++ b/test/app/views/editor/editor_view.coffee
@@ -0,0 +1,4 @@
+describe 'editor/level', ->
+ EditorLevelView = require 'views/editor/level_view'
+
+ it 'does stuff', ->
diff --git a/test/app/views/editor/scripts_tab_view.coffee b/test/app/views/editor/scripts_tab_view.coffee
new file mode 100644
index 000000000..079f12403
--- /dev/null
+++ b/test/app/views/editor/scripts_tab_view.coffee
@@ -0,0 +1,4 @@
+describe 'editor/level/scripts_tab', ->
+ ScriptsTabView = require 'views/editor/level/scripts_tab_view'
+
+ it 'does stuff', ->
diff --git a/test/app/views/editor/settings_tab_view.coffee b/test/app/views/editor/settings_tab_view.coffee
new file mode 100644
index 000000000..9da3147ba
--- /dev/null
+++ b/test/app/views/editor/settings_tab_view.coffee
@@ -0,0 +1,4 @@
+describe 'editor/level/settings_tab', ->
+ SettingsTabView = require 'views/editor/level/settings_tab_view'
+
+ it 'does stuff', ->
diff --git a/test/app/views/editor/systems_tab_view.coffee b/test/app/views/editor/systems_tab_view.coffee
new file mode 100644
index 000000000..c1f940ae9
--- /dev/null
+++ b/test/app/views/editor/systems_tab_view.coffee
@@ -0,0 +1,4 @@
+describe 'editor/level/thangs_tab', ->
+ SystemsTabView = require 'views/editor/level/systems_tab_view'
+
+ it 'does stuff', ->
diff --git a/test/app/views/editor/thangs_tab_view.coffee b/test/app/views/editor/thangs_tab_view.coffee
new file mode 100644
index 000000000..e95a2e9eb
--- /dev/null
+++ b/test/app/views/editor/thangs_tab_view.coffee
@@ -0,0 +1,4 @@
+describe 'editor/level/thangs_tab', ->
+ ThangsTabView = require 'views/editor/level/thangs_tab_view'
+
+ it 'does stuff', ->
diff --git a/test/server/auth.spec.coffee b/test/server/auth.spec.coffee
new file mode 100644
index 000000000..3a34d9b7c
--- /dev/null
+++ b/test/server/auth.spec.coffee
@@ -0,0 +1,69 @@
+require './common'
+
+describe '/auth/whoami', ->
+ http = require 'http'
+ it 'returns 200', (done) ->
+ http.get(getURL('/auth/whoami'), (response) ->
+ expect(response).toBeDefined()
+ expect(response.statusCode).toBe(200)
+ done()
+ )
+
+describe '/auth/login', ->
+ url = getURL('/auth/login')
+ request = require 'request'
+
+ it 'clears Users first', (done) ->
+ User.remove {}, (err) ->
+ throw err if err
+ done()
+
+ it 'finds no user', (done) ->
+ req = request.post(url, (error, response) ->
+ expect(response).toBeDefined()
+ expect(response.statusCode).toBe(401)
+ done()
+ )
+ form = req.form()
+ form.append('username', 'scott@gmail.com')
+ form.append('password', 'nada')
+
+ it 'creates a user', (done) ->
+ req = request.post(getURL('/db/user'),
+ (error, response) ->
+ expect(response).toBeDefined()
+ expect(response.statusCode).toBe(200)
+ done()
+ )
+ form = req.form()
+ form.append('email', 'scott@gmail.com')
+ form.append('password', 'nada')
+
+ it 'finds that created user', (done) ->
+ req = request.post(url, (error, response) ->
+ expect(response).toBeDefined()
+ expect(response.statusCode).toBe(200)
+ done()
+ )
+ form = req.form()
+ form.append('username', 'scott@gmail.com')
+ form.append('password', 'nada')
+
+ it 'rejects wrong passwords', (done) ->
+ req = request.post(url, (error, response) ->
+ expect(response.statusCode).toBe(401)
+ expect(response.body.indexOf("wrong, wrong")).toBeGreaterThan(-1)
+ done()
+ )
+ form = req.form()
+ form.append('username', 'scott@gmail.com')
+ form.append('password', 'blahblah')
+
+ it 'is completely case insensitive', (done) ->
+ req = request.post(url, (error, response) ->
+ expect(response.statusCode).toBe(200)
+ done()
+ )
+ form = req.form()
+ form.append('username', 'scoTT@gmaIL.com')
+ form.append('password', 'NaDa')
\ No newline at end of file
diff --git a/test/server/common.coffee b/test/server/common.coffee
new file mode 100644
index 000000000..13c5db06d
--- /dev/null
+++ b/test/server/common.coffee
@@ -0,0 +1,144 @@
+# import this at the top of every file so we're not juggling connections
+# and common libraries are available
+
+GLOBAL._ = require('lodash')
+_.str = require('underscore.string')
+_.mixin(_.str.exports())
+GLOBAL.mongoose = require 'mongoose'
+mongoose.connect('mongodb://localhost/coco_unittest')
+
+models_path = '../../server/models/'
+
+include_models = [
+ 'Article'
+ 'Campaign'
+ 'CampaignStatus'
+ 'Level'
+ 'LevelComponent'
+ 'LevelSystem'
+ 'LevelDraft'
+ 'LevelSession'
+ 'LevelThangType'
+ 'User'
+]
+
+for m in include_models
+ GLOBAL[m] = require models_path+m
+
+async = require 'async'
+
+GLOBAL.clearModels = (models, done) ->
+
+ funcs = []
+ for model in models
+ if model is User
+ unittest.users = {}
+ wrapped = (m) -> (callback) ->
+ m.remove {}, (err) ->
+ callback(err, true)
+ funcs.push(wrapped(model))
+
+ async.parallel funcs, (err, results) ->
+ done(err)
+
+GLOBAL.saveModels = (models, done) ->
+
+ funcs = []
+ for model in models
+ wrapped = (m) -> (callback) ->
+ m.save (err) ->
+ callback(err, true)
+ funcs.push(wrapped(model))
+
+ async.parallel funcs, (err, results) ->
+ done(err)
+
+GLOBAL.simplePermissions = [target:'public', access:'owner']
+GLOBAL.ObjectId = mongoose.Types.ObjectId
+GLOBAL.request = require 'request'
+
+GLOBAL.unittest = {}
+unittest.users = unittest.users or {}
+
+unittest.getNormalJoe = (done, force) ->
+ unittest.getUser('normal@jo.com', 'food', done, force)
+unittest.getOtherSam = (done, force) ->
+ unittest.getUser('other@sam.com', 'beer', done, force)
+unittest.getAdmin = (done, force) ->
+ unittest.getUser('admin@afc.com', '80yqxpb38j', done, force)
+
+unittest.getUser = (email, password, done, force) ->
+ # Creates the user if it doesn't already exist.
+
+ return done(unittest.users[email]) if unittest.users[email] and not force
+ request = require 'request'
+ request.post getURL('/auth/logout'), ->
+ req = request.post(getURL('/db/user'), (err, response, body) ->
+ throw err if err
+ User.findOne({email:email}).exec((err, user) ->
+ if password is '80yqxpb38j'
+ user.set('permissions', [ 'admin' ])
+ user.save (err) ->
+ wrapUpGetUser(email, user, done)
+ else
+ wrapUpGetUser(email, user, done)
+ )
+ )
+ form = req.form()
+ form.append('email', email)
+ form.append('password', password)
+
+wrapUpGetUser = (email, user, done) ->
+ unittest.users[email] = user
+ return done(unittest.users[email])
+
+GLOBAL.getURL = (path) ->
+ return 'http://localhost:3001' + path
+
+GLOBAL.loginJoe = (done) ->
+ request.post getURL('/auth/logout'), ->
+ unittest.getNormalJoe (user) ->
+ req = request.post(getURL('/auth/login'), (error, response) ->
+ expect(response.statusCode).toBe(200)
+ done(user)
+ )
+ form = req.form()
+ form.append('username', 'normal@jo.com')
+ form.append('password', 'food')
+
+GLOBAL.loginSam = (done) ->
+ request.post getURL('/auth/logout'), ->
+ unittest.getOtherSam (user) ->
+ req = request.post(getURL('/auth/login'), (error, response) ->
+ expect(response.statusCode).toBe(200)
+ done(user)
+ )
+ form = req.form()
+ form.append('username', 'other@sam.com')
+ form.append('password', 'beer')
+
+GLOBAL.loginAdmin = (done) ->
+ request.post getURL('/auth/logout'), ->
+ unittest.getAdmin (user) ->
+ req = request.post(getURL('/auth/login'), (error, response) ->
+ expect(response.statusCode).toBe(200)
+ done(user)
+ )
+ form = req.form()
+ form.append('username', 'admin@afc.com')
+ form.append('password', '80yqxpb38j')
+ # find some other way to make the admin object an admin... maybe directly?
+
+GLOBAL.dropGridFS = (done) ->
+ if mongoose.connection.readyState is 2
+ mongoose.connection.once 'open', ->
+ _drop(done)
+ else
+ _drop(done)
+
+_drop = (done) ->
+ files = mongoose.connection.db.collection('media.files')
+ files.remove {}, ->
+ chunks = mongoose.connection.db.collection('media.chunks')
+ chunks.remove {}, ->
+ done()
diff --git a/test/server/file.spec.coffee b/test/server/file.spec.coffee
new file mode 100644
index 000000000..eebf36af0
--- /dev/null
+++ b/test/server/file.spec.coffee
@@ -0,0 +1,130 @@
+require './common'
+
+describe '/file', ->
+ url = getURL('/file')
+ files = []
+
+ it 'deletes all the files first', (done) ->
+ dropGridFS ->
+ done()
+
+ it 'posts good', (done) ->
+ options = {
+ uri:url
+ json: {
+ url: 'http://scotterickson.info/images/where-are-you.jpg'
+ filename: 'where-are-you.jpg'
+ mimetype: 'image/jpeg'
+ description: 'None!'
+ }
+ }
+
+ func = (err, res, body) ->
+ expect(res.statusCode).toBe(200)
+ expect(body.metadata.description).toBe('None!')
+ files.push(body)
+
+ collection = mongoose.connection.db.collection('media.files')
+ collection.findOne {}, (err, result) ->
+ expect(result.metadata.name).toBe('Where are you')
+ expect(result.metadata.createdFor+'').toBe([]+'')
+ done()
+
+ request.post(options, func)
+
+ it 'gets good', (done) ->
+ request.get {uri:url+'/'+files[0]._id}, (err, res) ->
+ expect(res.statusCode).toBe(200)
+ expect(res.headers['content-type']).toBe('image/jpeg')
+ done()
+
+
+ it 'returns 404 for missing files', (done) ->
+ id = '000000000000000000000000'
+ request.get {uri:url+'/'+id}, (err, res) ->
+ expect(res.statusCode).toBe(404)
+ done()
+
+ it 'returns 404 for invalid ids', (done) ->
+ request.get {uri:url+'/thiswillnotwork'}, (err, res) ->
+ expect(res.statusCode).toBe(404)
+ done()
+
+ it 'posts data directly', (done) ->
+ options = {
+ uri:url
+ }
+
+
+ func = (err, res, body) ->
+ expect(res.statusCode).toBe(200)
+ body = JSON.parse(body)
+
+ expect(body.metadata.description).toBe('rando-info')
+ files.push(body)
+
+ collection = mongoose.connection.db.collection('media.files')
+ collection.find({_id:mongoose.Types.ObjectId(body._id)}).toArray (err, results) ->
+ expect(results[0].metadata.name).toBe('Ittybitty')
+ done()
+
+ # the only way I could figure out how to get request to do what I wanted...
+ r = request.post(options, func)
+ form = r.form()
+ form.append('postName', 'my_buffer')
+ form.append('filename', 'ittybitty.data')
+ form.append('mimetype', 'application/octet-stream')
+ form.append('description', 'rando-info')
+ form.append('my_buffer', request('http://scotterickson.info/images/where-are-you.jpg'))
+
+ it 'does not overwrite existing files', (done) ->
+ options = {
+ uri:url
+ json: {
+ url: 'http://scotterickson.info/images/scott.jpg'
+ filename: 'where-are-you.jpg'
+ mimetype: 'image/jpeg'
+ description: 'Face'
+ }
+ }
+
+ func = (err, res, body) ->
+ expect(res.statusCode).toBe(409)
+ collection = mongoose.connection.db.collection('media.files')
+ collection.find({}).toArray (err, results) ->
+ # ittybitty.data, and just one Where are you.jpg
+ expect(results.length).toBe(2)
+ for f in results
+ expect(f.metadata.description).not.toBe('Face')
+ done()
+
+ request.post(options, func)
+
+ it 'does overwrite existing files if force is true', (done) ->
+ options = {
+ uri:url
+ json: {
+ url: 'http://scotterickson.info/images/scott.jpg'
+ filename: 'where-are-you.jpg'
+ mimetype: 'image/jpeg'
+ description: 'Face'
+ force: true
+ }
+ }
+
+ func = (err, res, body) ->
+ expect(res.statusCode).toBe(200)
+ collection = mongoose.connection.db.collection('media.files')
+ collection.find({}).toArray (err, results) ->
+ # ittybitty.data, and just one Where are you.jpg
+ expect(results.length).toBe(2)
+ hit = false
+ for f in results
+ hit = true if f.metadata.description is 'Face'
+ expect(hit).toBe(true)
+ done()
+
+ request.post(options, func)
+
+
+ # TODO: test server errors, see what they do
\ No newline at end of file
diff --git a/test/server/handlers/article.spec.coffee b/test/server/handlers/article.spec.coffee
new file mode 100644
index 000000000..6ab256641
--- /dev/null
+++ b/test/server/handlers/article.spec.coffee
@@ -0,0 +1,79 @@
+require '../common'
+
+describe '/db/article', ->
+ request = require 'request'
+ it 'clears the db first', (done) ->
+ clearModels [User, Article], (err) ->
+ throw err if err
+ done()
+
+ article = {name: 'Yo', body:'yo ma'}
+ url = getURL('/db/article')
+ articles = {}
+
+ it 'does not allow non-admins to create Articles.', (done) ->
+ loginJoe ->
+ request.post {uri:url, json:article}, (err, res, body) ->
+ expect(res.statusCode).toBe(403)
+ done()
+
+ it 'allows admins to create Articles', (done) ->
+ loginAdmin ->
+ request.post {uri:url, json:article}, (err, res, body) ->
+ expect(res.statusCode).toBe(200)
+ expect(body.slug).not.toBeUndefined()
+ expect(body.body).not.toBeUndefined()
+ expect(body.name).not.toBeUndefined()
+ expect(body.original).not.toBeUndefined()
+ expect(body.creator).not.toBeUndefined()
+ articles[0] = body
+ done()
+
+ it 'allows admins to make new minor versions', (done) ->
+ new_article = _.clone(articles[0])
+ new_article.body = '...'
+ request.post {uri:url, json:new_article}, (err, res, body) ->
+ expect(res.statusCode).toBe(200)
+ expect(body.version.major).toBe(0)
+ expect(body.version.minor).toBe(1)
+ expect(body._id).not.toBe(articles[0]._id)
+ expect(body.parent).toBe(articles[0]._id)
+ expect(body.creator).not.toBeUndefined()
+ articles[1] = body
+ done()
+
+ it 'allows admins to make new major versions', (done) ->
+ new_article = _.clone(articles[1])
+ delete new_article.version
+ request.post {uri:url, json:new_article}, (err, res, body) ->
+ expect(res.statusCode).toBe(200)
+ expect(body.version.major).toBe(1)
+ expect(body.version.minor).toBe(0)
+ expect(body._id).not.toBe(articles[1]._id)
+ expect(body.parent).toBe(articles[1]._id)
+ articles[2] = body
+ done()
+
+ it 'grants access for regular users', (done) ->
+ loginJoe ->
+ request.get {uri:url+'/'+articles[0]._id}, (err, res, body) ->
+ body = JSON.parse(body)
+ expect(res.statusCode).toBe(200)
+ expect(body.body).toBe(articles[0].body)
+ done()
+
+
+ it 'does not allow regular users to make new versions', (done) ->
+ new_article = _.clone(articles[2])
+ request.post {uri:url, json:new_article}, (err, res, body) ->
+ expect(res.statusCode).toBe(403)
+ done()
+
+ it 'allows name changes from one version to the next', (done) ->
+ loginAdmin ->
+ new_article = _.clone(articles[0])
+ new_article.name = "Yo mama now is the larger"
+ request.post {uri:url, json:new_article}, (err, res, body) ->
+ expect(res.statusCode).toBe(200)
+ expect(body.name).toBe(new_article.name)
+ done()
diff --git a/test/server/handlers/campaign.spec.coffee b/test/server/handlers/campaign.spec.coffee
new file mode 100644
index 000000000..dc967a56c
--- /dev/null
+++ b/test/server/handlers/campaign.spec.coffee
@@ -0,0 +1,42 @@
+require '../common'
+
+describe '/db/campaign', ->
+ request = require 'request'
+ it 'clears the db first', (done) ->
+ clearModels [User, Campaign], (err) ->
+ throw err if err
+ done()
+
+ campaign = {name: 'A', description:'B'}
+ url = getURL('/db/campaign')
+ campaigns = {}
+
+ it 'allows making Campaigns.', (done) ->
+ loginJoe (user) ->
+ campaign.permissions = [access: 'owner', target: user._id]
+ request.post {uri:url, json:campaign}, (err, res, body) ->
+ expect(res.statusCode).toBe(200)
+ expect(body.permissions).not.toBeUndefined()
+ campaigns[0] = body
+ done()
+
+ it 'does not allow other users access', (done) ->
+ loginSam (user) ->
+ request.get {uri:url+'/'+campaigns[0]._id}, (err, res, body) ->
+ expect(res.statusCode).toBe(403)
+ done()
+
+ it 'allows editing permissions.', (done) ->
+ loginJoe (user) ->
+ campaigns[0].permissions.push(access: 'read', target: 'public')
+ request.put {uri:url, json:campaigns[0]}, (err, res, body) ->
+ expect(res.statusCode).toBe(200)
+ expect(body.permissions.length).toBe(2)
+ campaigns[0] = body
+ done()
+
+ it 'allows anyone to access it through public permissions', (done) ->
+ loginSam (user) ->
+ request.get {uri:url+'/'+campaigns[0]._id}, (err, res, body) ->
+ expect(res.statusCode).toBe(200)
+ done()
diff --git a/test/server/handlers/campaign_status.spec.coffee b/test/server/handlers/campaign_status.spec.coffee
new file mode 100644
index 000000000..db8edaf5d
--- /dev/null
+++ b/test/server/handlers/campaign_status.spec.coffee
@@ -0,0 +1,21 @@
+require '../common'
+
+describe '/db/campaign_status', ->
+ request = require 'request'
+ it 'clears the db first', (done) ->
+ clearModels [Campaign, CampaignStatus], (err) ->
+ throw err if err
+ done()
+
+ user = new User(name:'sup')
+ campaign = new Campaign(name:'Project Vengeance.', permissions: simplePermissions)
+ stat = {campaign: campaign._id, user: user._id}
+ url = getURL('/db/campaign_status')
+
+ it 'can make a CampaignStatus, and ignores the user property given.', (done) ->
+ loginJoe (joe) ->
+ request.post {uri:url, json:stat}, (err, res, body) ->
+ expect(res.statusCode).toBe(200)
+ expect(body.user).toBe(joe._id.toString())
+ expect(body.user).not.toBe(user._id.toString())
+ done()
diff --git a/test/server/handlers/db-id-version.spec.coffee b/test/server/handlers/db-id-version.spec.coffee
new file mode 100644
index 000000000..81433f4b5
--- /dev/null
+++ b/test/server/handlers/db-id-version.spec.coffee
@@ -0,0 +1,68 @@
+require '../common'
+
+describe '/db//version', ->
+ request = require 'request'
+ it 'clears the db first', (done) ->
+ clearModels [User, Article], (err) ->
+ throw err if err
+ done()
+
+ article = {name: 'Yo', body:'yo ma'}
+ url = getURL('/db/article')
+ articles = {}
+
+ it 'sets up', (done) ->
+ loginAdmin ->
+ request.post {uri:url, json:article}, (err, res, body) ->
+ expect(res.statusCode).toBe(200)
+ articles[0] = body
+ new_article = _.clone(articles[0])
+ new_article.body = '...'
+ request.post {uri:url, json:new_article}, (err, res, body) ->
+ expect(res.statusCode).toBe(200)
+ articles[1] = body
+ new_article = _.clone(articles[1])
+ delete new_article.version
+ request.post {uri:url, json:new_article}, (err, res, body) ->
+ expect(res.statusCode).toBe(200)
+ articles[2] = body
+ done()
+
+ createVersionUrl = (versionString=null) ->
+ original = articles[0]._id
+ url = getURL("/db/article/#{original}/version")
+ url += ('/' + versionString) if versionString?
+ url
+
+ it 'can fetch the latest absolute version', (done) ->
+ baseUrl = createVersionUrl()
+ request.get {uri:baseUrl}, (err, res, body) ->
+ body = JSON.parse(body)
+ expect(res.statusCode).toBe(200)
+ expect(body.version.major).toBe(1)
+ expect(body.version.minor).toBe(0)
+ done()
+
+ it 'can fetch the latest major version', (done) ->
+ baseUrl = createVersionUrl('0')
+ request.get {uri:baseUrl}, (err, res, body) ->
+ body = JSON.parse(body)
+ expect(res.statusCode).toBe(200)
+ expect(body.version.major).toBe(0)
+ expect(body.version.minor).toBe(1)
+ done()
+
+ it 'can fetch a particular version', (done) ->
+ baseUrl = createVersionUrl('0.0')
+ request.get {uri:baseUrl}, (err, res, body) ->
+ body = JSON.parse(body)
+ expect(res.statusCode).toBe(200)
+ expect(body.version.major).toBe(0)
+ expect(body.version.minor).toBe(0)
+ done()
+
+ it 'returns 404 when no doc is found', (done) ->
+ baseUrl = createVersionUrl('3.14')
+ request.get {uri:baseUrl}, (err, res, body) ->
+ expect(res.statusCode).toBe(404)
+ done()
diff --git a/test/server/handlers/level.spec.coffee b/test/server/handlers/level.spec.coffee
new file mode 100644
index 000000000..901451ff4
--- /dev/null
+++ b/test/server/handlers/level.spec.coffee
@@ -0,0 +1,21 @@
+require '../common'
+
+describe 'Level', ->
+
+ level =
+ name: "King's Peak 3"
+ description: 'Climb a mountain.'
+ permissions: simplePermissions
+
+ url = getURL('/db/level')
+
+ it 'clears things first', (done) ->
+ clearModels [Level], (err) ->
+ expect(err).toBeNull()
+ done()
+
+ it 'can make a Level.', (done) ->
+ loginJoe (joe) ->
+ request.post {uri:url, json:level}, (err, res, body) ->
+ expect(res.statusCode).toBe(200)
+ done()
\ No newline at end of file
diff --git a/test/server/handlers/level_component.spec.coffee b/test/server/handlers/level_component.spec.coffee
new file mode 100644
index 000000000..d88b62142
--- /dev/null
+++ b/test/server/handlers/level_component.spec.coffee
@@ -0,0 +1,36 @@
+require '../common'
+
+describe 'LevelComponent', ->
+
+ component =
+ name:'Bashes Everything'
+ description:'Makes the unit uncontrollably bash anything bashable, using the bash system.'
+ code: 'bash();'
+ language: 'javascript'
+ official: true
+ permissions:simplePermissions
+
+ components = {}
+
+ url = getURL('/db/level.component')
+
+ it 'clears things first', (done) ->
+ clearModels [Level, LevelComponent], (err) ->
+ expect(err).toBeNull()
+ done()
+
+ it 'can make a LevelComponent, without setting official.', (done) ->
+ loginJoe (joe) ->
+ request.post {uri:url, json:component}, (err, res, body) ->
+ expect(res.statusCode).toBe(200)
+ expect(body.official).toBeUndefined()
+ components[0] = body
+ done()
+
+ it 'can allows admins to edit the official property.', (done) ->
+ components[0].official = true
+ loginAdmin (joe) ->
+ request.post {uri:url, json:components[0]}, (err, res, body) ->
+ expect(body.official).toBe(true)
+ expect(res.statusCode).toBe(200)
+ done()
diff --git a/test/server/handlers/level_draft.spec.coffee b/test/server/handlers/level_draft.spec.coffee
new file mode 100644
index 000000000..006cdb787
--- /dev/null
+++ b/test/server/handlers/level_draft.spec.coffee
@@ -0,0 +1,23 @@
+require '../common'
+
+describe '/db/campaign_draft', ->
+
+ draft = {
+ level: {}
+ user: 'yoyoyo'
+ }
+
+ request = require 'request'
+ it 'clears the db first', (done) ->
+ clearModels [LevelDraft], (err) ->
+ throw err if err
+ done()
+
+ url = getURL('/db/level_draft')
+
+ it 'can make a LevelDraft, and ignores the user property given.', (done) ->
+ loginJoe (joe) ->
+ request.post {uri:url, json:draft}, (err, res, body) ->
+ expect(res.statusCode).toBe(200)
+ expect(body.user).toBe(joe._id.toString())
+ done()
\ No newline at end of file
diff --git a/test/server/handlers/level_system.spec.coffee b/test/server/handlers/level_system.spec.coffee
new file mode 100644
index 000000000..19fc29725
--- /dev/null
+++ b/test/server/handlers/level_system.spec.coffee
@@ -0,0 +1,39 @@
+require '../common'
+
+describe 'LevelSystem', ->
+
+ raw =
+ name:'Bashing'
+ description:'Performs Thang bashing updates for Bashes Thangs.'
+ code: """class Bashing extends System
+ constructor: (world) ->
+ super world
+ """
+ language: 'coffeescript'
+ official: true
+ permissions:simplePermissions
+
+ systems = {}
+
+ url = getURL('/db/level.system')
+
+ it 'clears things first', (done) ->
+ clearModels [Level, LevelSystem], (err) ->
+ expect(err).toBeNull()
+ done()
+
+ it 'can make a LevelSystem, without setting official.', (done) ->
+ loginJoe (joe) ->
+ request.post {uri:url, json:system}, (err, res, body) ->
+ expect(res.statusCode).toBe(200)
+ expect(body.official).toBeUndefined()
+ systems[0] = body
+ done()
+
+ it 'can allows admins to edit the official property.', (done) ->
+ systems[0].official = true
+ loginAdmin (joe) ->
+ request.post {uri:url, json:systems[0]}, (err, res, body) ->
+ expect(body.official).toBe(true)
+ expect(res.statusCode).toBe(200)
+ done()
diff --git a/test/server/handlers/user.spec.coffee b/test/server/handlers/user.spec.coffee
new file mode 100644
index 000000000..6d1e126b8
--- /dev/null
+++ b/test/server/handlers/user.spec.coffee
@@ -0,0 +1,172 @@
+require '../common'
+
+describe 'POST /db/user', ->
+ request = require 'request'
+ it 'clears the db first', (done) ->
+ clearModels [User], (err) ->
+ throw err if err
+ done()
+
+ it 'converts the password into a hash', (done) ->
+ unittest.getNormalJoe (user) ->
+ expect(user).toBeTruthy()
+ expect(user.get('password')).toBeUndefined()
+ expect(user?.get('passwordHash')).not.toBeUndefined()
+ if user?.get('passwordHash')?
+ expect(user.get('passwordHash')[..5]).toBe('948c7e')
+ expect(user.get('permissions').length).toBe(0)
+ done()
+
+ it 'serves the user through /db/user/id', (done) ->
+ unittest.getNormalJoe (user) ->
+ url = getURL('/db/user/'+user._id)
+ request.get url, (err, res, body) ->
+ expect(res.statusCode).toBe(200)
+ user = JSON.parse(body)
+ expect(user.email).toBe('normal@jo.com')
+ expect(user.passwordHash).toBeUndefined()
+ done()
+
+ it 'creates admins based on passwords', (done) ->
+ request.post getURL('/auth/logout'), ->
+ unittest.getAdmin (user) ->
+ expect(user).not.toBeUndefined()
+ if user
+ expect(user.get('permissions').length).toBe(1)
+ expect(user.get('permissions')[0]).toBe('admin')
+ done()
+
+ it 'does not return the full user object for regular users.', (done) ->
+ loginJoe ->
+ unittest.getAdmin (user) ->
+
+ url = getURL('/db/user/'+user._id)
+ request.get url, (err, res, body) ->
+ expect(res.statusCode).toBe(200)
+ user = JSON.parse(body)
+ expect(user.email).toBeUndefined()
+ expect(user.passwordHash).toBeUndefined()
+ done()
+
+
+describe 'PUT /db/user', ->
+
+ it 'denies requests without any data', (done) ->
+ req = request.post getURL('/auth/logout'),
+ (err, res) ->
+ expect(res.statusCode).toBe(200)
+ req = request.put getURL('/db/user'),
+ (err, res) ->
+ expect(res.statusCode).toBe(422)
+ expect(res.body).toBe('No input.')
+ done()
+
+ it 'logs in as normal joe', (done) ->
+ loginJoe -> done()
+
+ it 'denies requests to edit someone who is not joe', (done) ->
+ unittest.getAdmin (admin) ->
+ req = request.put getURL('/db/user'),
+ (err, res) ->
+ expect(res.statusCode).toBe(403)
+ done()
+ req.form().append('_id', admin.id)
+
+ it 'denies invalid data', (done) ->
+ unittest.getNormalJoe (joe) ->
+ req = request.put getURL('/db/user'),
+ (err, res) ->
+ expect(res.statusCode).toBe(422)
+ expect(res.body.indexOf('too long')).toBeGreaterThan(-1)
+ done()
+ form = req.form()
+ form.append('_id', joe.id)
+ form.append('email', "farghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghlfarghlarghl")
+
+ it 'logs in as admin', (done) ->
+ loginAdmin -> done()
+
+
+ it 'denies non-existent ids', (done) ->
+ req = request.put getURL('/db/user'),
+ (err, res) ->
+ expect(res.statusCode).toBe(404)
+ expect(res.body).toBe('Resource not found.')
+ done()
+ done()
+ form = req.form()
+ form.append('_id', '513108d4cb8b610000000004')
+ form.append('email', "perfectly@good.com")
+
+ it 'denies if the email being changed is already taken', (done) ->
+ unittest.getNormalJoe (joe) ->
+ unittest.getAdmin (admin) ->
+ req = request.put getURL('/db/user'), (err, res) ->
+ expect(res.statusCode).toBe(409)
+ expect(res.body.indexOf('already used')).toBeGreaterThan(-1)
+ done()
+ form = req.form()
+ form.append('_id', String(admin._id))
+ form.append('email', joe.get('email').toUpperCase())
+
+ it 'works', (done) ->
+ unittest.getNormalJoe (joe) ->
+ req = request.put getURL('/db/user'), (err, res) ->
+ expect(res.statusCode).toBe(200)
+ unittest.getUser('New@email.com', 'null', (joe) ->
+ expect(joe.get('name')).toBe('Wilhelm')
+ expect(joe.get('emailLower')).toBe('new@email.com')
+ expect(joe.get('email')).toBe('New@email.com')
+ done())
+ form = req.form()
+ form.append('_id', String(joe._id))
+ form.append('email', 'New@email.com')
+ form.append('name', 'Wilhelm')
+
+describe 'GET /db/user', ->
+ request = require 'request'
+ it 'logs in as admin', (done) ->
+ req = request.post(getURL('/auth/login'), (error, response) ->
+ expect(response.statusCode).toBe(200)
+ done()
+ )
+ form = req.form()
+ form.append('username', 'admin@afc.com')
+ form.append('password', '80yqxpb38j')
+
+ it 'is able to do a sweet query', (done) ->
+ conditions = [
+ ['limit', 20]
+ ['where', 'email']
+ ['equals', 'admin@afc.com']
+ ['sort', '-dateCreated']
+ ]
+ options = {
+ url: getURL('/db/user')
+ qs: {
+ conditions: JSON.stringify(conditions)
+ }
+ }
+
+ req = request.get(options, (error, response) ->
+ expect(response.statusCode).toBe(200)
+ res = JSON.parse(response.body)
+ expect(res.length).toBeGreaterThan(0)
+ done()
+ )
+
+ it 'rejects bad conditions', (done) ->
+ conditions = [
+ ['lime', 20]
+ ]
+ options = {
+ url: getURL('/db/user')
+ qs: {
+ conditions: JSON.stringify(conditions)
+ }
+ }
+
+ req = request.get(options, (error, response) ->
+ expect(response.statusCode).toBe(422)
+ done()
+ )
diff --git a/test/server/models/CampaignStatus.spec.coffee b/test/server/models/CampaignStatus.spec.coffee
new file mode 100644
index 000000000..676af0c69
--- /dev/null
+++ b/test/server/models/CampaignStatus.spec.coffee
@@ -0,0 +1,34 @@
+require '../common'
+
+describe 'CampaignStatus', ->
+
+ user = new User(name:'sup')
+ campaign = new Campaign(name:'Project Vengeance.', permissions: simplePermissions)
+ stat = new CampaignStatus(user: user._id, campaign: campaign._id)
+
+ it 'clears things first', (done) ->
+ clearModels [User, Campaign, CampaignStatus], (err) ->
+ expect(err).toBeNull()
+ done()
+
+ it 'can be saved', (done) ->
+ saveModels [user, campaign, stat], (err) ->
+ expect(err).toBeNull()
+ done()
+
+ it 'can populate', (done) ->
+ CampaignStatus
+ .findOne({_id:stat._id})
+ .populate('user')
+ .populate('campaign')
+ .exec (err, c) ->
+ expect(err).toBe(null)
+ expect(c.user.get('name')).not.toBeUndefined()
+ expect(c.campaign.get('name')).not.toBeUndefined()
+ done()
+
+ it 'rejects duplicates', (done) ->
+ stat2 = new CampaignStatus(user: user._id, campaign: campaign._id)
+ stat2.save (err) ->
+ expect(err).not.toBe(null)
+ done()
diff --git a/test/server/models/Level.spec.coffee b/test/server/models/Level.spec.coffee
new file mode 100644
index 000000000..ce061d46c
--- /dev/null
+++ b/test/server/models/Level.spec.coffee
@@ -0,0 +1,30 @@
+require '../common'
+
+describe 'Level', ->
+
+ level = new Level(
+ name: "King's Peak"
+ description: 'Climb a mountain!!!'
+ permissions: simplePermissions
+ original: new ObjectId()
+ )
+
+ it 'clears things first', (done) ->
+ clearModels [Level], (err) ->
+ expect(err).toBeNull()
+ done()
+
+ it 'saves', (done) ->
+ level.save (err) ->
+ throw err if err
+ done()
+
+ it 'loads again after being saved', (done) ->
+ url = getURL('/db/level/'+level._id)
+ request.get url, (err, res, body) ->
+ expect(res.statusCode).toBe(200)
+ sameLevel = JSON.parse(body)
+ expect(sameLevel.name).toEqual(level.get 'name')
+ expect(sameLevel.description).toEqual(level.get 'description')
+ expect(sameLevel.permissions).toEqual(simplePermissions)
+ done()
diff --git a/test/server/models/LevelComponent.spec.coffee b/test/server/models/LevelComponent.spec.coffee
new file mode 100644
index 000000000..793ca70ed
--- /dev/null
+++ b/test/server/models/LevelComponent.spec.coffee
@@ -0,0 +1,24 @@
+require '../common'
+
+describe 'LevelComponent', ->
+
+ raw =
+ name:'Bashes Everything'
+ description:'Makes the unit uncontrollably bash anything bashable, using the bash system.'
+ code: 'bash();'
+ language: 'javascript'
+ official: true
+ permissions:simplePermissions
+
+ comp = new LevelComponent(raw)
+
+ it 'clears things first', (done) ->
+ LevelComponent.remove {}, (err) ->
+ expect(err).toBeNull()
+ done()
+
+ it 'can be saved', (done) ->
+ comp.save (err) ->
+ expect(err).toBeNull()
+ done()
+
diff --git a/test/server/models/LevelDraft.spec.coffee b/test/server/models/LevelDraft.spec.coffee
new file mode 100644
index 000000000..aae9e3374
--- /dev/null
+++ b/test/server/models/LevelDraft.spec.coffee
@@ -0,0 +1,31 @@
+require '../common'
+
+describe 'LevelDraft', ->
+
+ level = new Level(
+ name: "King's Peak Redux"
+ description: 'Climb a mountain.'
+ permissions: simplePermissions
+ original: new ObjectId()
+ )
+
+ it 'clears things first', (done) ->
+ clearModels [Level, LevelDraft], (err) ->
+ expect(err).toBeNull()
+ done()
+
+ it 'saves', (done) ->
+ level.save (err) ->
+ throw err if err
+
+ draft = new LevelDraft(
+ user: new ObjectId()
+ level: level
+ )
+
+ draft.save (err) ->
+ throw err if err
+
+ LevelDraft.findOne {_id:draft._id}, (err, fetched) ->
+ expect(fetched.level.original).not.toBeUndefined()
+ done()
diff --git a/test/server/models/LevelSession.spec.coffee b/test/server/models/LevelSession.spec.coffee
new file mode 100644
index 000000000..3fa170e4e
--- /dev/null
+++ b/test/server/models/LevelSession.spec.coffee
@@ -0,0 +1,19 @@
+require '../common'
+
+describe 'LevelSession', ->
+
+ session = new LevelSession(
+ permissions: simplePermissions
+ )
+
+ it 'clears things first', (done) ->
+ clearModels [LevelSession], (err) ->
+ expect(err).toBeNull()
+ done()
+
+ it 'saves', (done) ->
+ session.save (err) ->
+ throw err if err
+ done()
+
+
diff --git a/test/server/models/LevelSystem.spec.coffee b/test/server/models/LevelSystem.spec.coffee
new file mode 100644
index 000000000..70080d717
--- /dev/null
+++ b/test/server/models/LevelSystem.spec.coffee
@@ -0,0 +1,26 @@
+require '../common'
+
+describe 'LevelSystem', ->
+
+ raw =
+ name:'Bashing'
+ description:'Performs Thang bashing updates for Bashes Thangs.'
+ code: """class Bashing extends System
+ constructor: (world) ->
+ super world
+ """
+ language: 'coffeescript'
+ official: true
+ permissions:simplePermissions
+
+ comp = new LevelSystem(raw)
+
+ it 'clears things first', (done) ->
+ LevelSystem.remove {}, (err) ->
+ expect(err).toBeNull()
+ done()
+
+ it 'can be saved', (done) ->
+ comp.save (err) ->
+ expect(err).toBeNull()
+ done()
diff --git a/test/server/models/LevelThangType.spec.coffee b/test/server/models/LevelThangType.spec.coffee
new file mode 100644
index 000000000..a884a53a2
--- /dev/null
+++ b/test/server/models/LevelThangType.spec.coffee
@@ -0,0 +1,17 @@
+require '../common'
+
+describe 'LevelThangType', ->
+
+ thang_type = new LevelThangType(
+ permissions: simplePermissions
+ )
+
+ it 'clears things first', (done) ->
+ clearModels [LevelThangType], (err) ->
+ expect(err).toBeNull()
+ done()
+
+ it 'saves', (done) ->
+ thang_type.save (err) ->
+ throw err if err
+ done()
diff --git a/test/server/models/article.spec.coffee b/test/server/models/article.spec.coffee
new file mode 100644
index 000000000..6e3ff5c64
--- /dev/null
+++ b/test/server/models/article.spec.coffee
@@ -0,0 +1,13 @@
+require '../common'
+
+describe 'Article', ->
+
+ it 'clears things first', (done) ->
+ Article.remove {}, (err) ->
+ expect(err).toBeNull()
+ done()
+
+ it 'can be saved', (done) ->
+ article = new Article(name:'List Comprehension', body:"A programmer's best friend.")
+ article.save (err) ->
+ done()
\ No newline at end of file
diff --git a/test/server/models/campaign.spec.coffee b/test/server/models/campaign.spec.coffee
new file mode 100644
index 000000000..fb7717373
--- /dev/null
+++ b/test/server/models/campaign.spec.coffee
@@ -0,0 +1,26 @@
+require '../common'
+
+describe 'Campaign', ->
+
+ raw =
+ name:'Battlefield 1942'
+ description:'Vacation all over the world!'
+ levels: []
+ permissions:[
+ target:'not_the_public'
+ access:'owner'
+ ]
+
+ campaign = new Campaign(raw)
+
+ it 'clears things first', (done) ->
+ Campaign.remove {}, (err) ->
+ expect(err).toBeNull()
+ done()
+
+ it 'can be saved', (done) ->
+ campaign.save (err) ->
+ expect(err).toBeNull()
+ done()
+
+
diff --git a/test/server/models/plugins.spec.coffee b/test/server/models/plugins.spec.coffee
new file mode 100644
index 000000000..630286905
--- /dev/null
+++ b/test/server/models/plugins.spec.coffee
@@ -0,0 +1,354 @@
+require '../common'
+
+describe 'NamePlugin', ->
+
+ article = new Article(
+ name: "Alpha"
+ body: 'What does it mean?'
+ )
+
+ it 'clears things first', (done) ->
+ clearModels [Article], (err) ->
+ expect(err).toBeNull()
+ done()
+
+ it 'saves', (done) ->
+ article.save (err) ->
+ throw err if err
+ done()
+
+ it 'does not allow name conflicts', (done) ->
+ c2 = new Article(
+ name: "Alpha"
+ body: 'The misunderstood Greek character.'
+ )
+
+ c2.save (err) ->
+ expect(err.code).toBe(409)
+ done()
+
+ it 'prevents slugs from being valid ObjectIds', (done) ->
+ c2 = new Article(
+ name: "522e0f149aaa330000000002"
+ body: '... fish.'
+ )
+
+ c2.save (err) ->
+ expect(err.code).toBe(422)
+ done()
+
+
+describe 'VersionedPlugin', ->
+ it 'clears things first', (done) ->
+ clearModels [Article], (err) ->
+ expect(err).toBeNull()
+ done()
+
+ it 'can create new major versions', (done) ->
+ firstArticle = new Article(name:'List Comp1', body:"A programmer's best friend.")
+ firstArticle.original = firstArticle._id
+
+ firstArticle.save (err) ->
+ throw err if err
+
+ secondObject = firstArticle.toObject()
+ secondObject['body'] = "Not as good as lambda."
+
+ firstArticle.makeNewMajorVersion secondObject, (err, secondArticle) ->
+ throw err if err
+
+ secondArticle.save (err) ->
+ throw err if err
+
+ thirdObject = secondArticle.toObject()
+ thirdObject['body'] = "..."
+
+ secondArticle.makeNewMajorVersion thirdObject, (err, thirdArticle) ->
+ throw err if err
+
+ thirdArticle.save ->
+
+ Article.find {original:firstArticle.original}, (err, results) ->
+ expect(results.length).toBe(3)
+ expect(results[0].version.major).toBe(2)
+ expect(results[1].version.major).toBe(1)
+ expect(results[2].version.major).toBe(0)
+
+ expect(results[0].version.minor).toBe(0)
+ expect(results[1].version.minor).toBe(0)
+ expect(results[2].version.minor).toBe(0)
+
+ expect(results[2].version.isLatestMajor).toBe(false)
+ expect(results[1].version.isLatestMajor).toBe(false)
+ expect(results[0].version.isLatestMajor).toBe(true)
+
+ expect(results[2].version.isLatestMinor).toBe(true)
+ expect(results[1].version.isLatestMinor).toBe(true)
+ expect(results[0].version.isLatestMinor).toBe(true)
+
+ expect(results[2].index).toBeUndefined()
+ expect(results[1].index).toBeUndefined()
+ expect(results[0].index).toBe(true)
+
+ done()
+
+ it 'works if you do not successfully save the new major version', (done) ->
+ firstArticle = new Article(name:'List Comp2', body:"A programmer's best friend.")
+ firstArticle.original = firstArticle._id
+
+ firstArticle.save (err) ->
+ throw err if err
+
+ secondObject = firstArticle.toObject()
+ secondObject['body'] = "Not as good as lambda."
+
+ firstArticle.makeNewMajorVersion secondObject, (err, forgottenSecondArticle) ->
+ throw err if err
+
+ firstArticle.makeNewMajorVersion secondObject, (err, realSecondArticle) ->
+ throw err if err
+ expect(realSecondArticle.version.major).toBe(1)
+ done()
+
+ it 'can create new minor versions', (done) ->
+ firstArticle = new Article(name:'List Comp3', body:"A programmer's best friend.")
+ firstArticle.original = firstArticle._id
+
+ firstArticle.save (err) ->
+ throw err if err
+
+ secondObject = firstArticle.toObject()
+ secondObject['body'] = "Not as good as lambda."
+
+ firstArticle.makeNewMinorVersion secondObject, 0, (err, secondArticle) ->
+ throw err if err
+
+ secondArticle.save (err) ->
+ throw err if err
+
+ thirdObject = secondArticle.toObject()
+ thirdObject['body'] = "..."
+
+ secondArticle.makeNewMinorVersion thirdObject, 0, (err, thirdArticle) ->
+ throw err if err
+
+ thirdArticle.save ->
+
+ Article.find {original:firstArticle.original}, (err, results) ->
+ expect(results.length).toBe(3)
+ expect(results[0].version.major).toBe(0)
+ expect(results[1].version.major).toBe(0)
+ expect(results[2].version.major).toBe(0)
+
+ expect(results[2].version.minor).toBe(0)
+ expect(results[1].version.minor).toBe(1)
+ expect(results[0].version.minor).toBe(2)
+
+ expect(results[2].version.isLatestMajor).toBe(false)
+ expect(results[1].version.isLatestMajor).toBe(false)
+ expect(results[0].version.isLatestMajor).toBe(true)
+
+ expect(results[2].version.isLatestMinor).toBe(false)
+ expect(results[1].version.isLatestMinor).toBe(false)
+ expect(results[0].version.isLatestMinor).toBe(true)
+
+ expect(results[2].index).toBeUndefined()
+ expect(results[1].index).toBeUndefined()
+ expect(results[0].index).toBe(true)
+
+ done()
+
+ it 'works if you do not successfully save the new minor version', (done) ->
+ firstArticle = new Article(
+ name:'List Comp4',
+ body:"A programmer's best friend."
+ index: true
+ )
+ firstArticle.original = firstArticle._id
+
+ firstArticle.save (err) ->
+ throw err if err
+
+ secondObject = firstArticle.toObject()
+ secondObject['body'] = "Not as good as lambda."
+
+ firstArticle.makeNewMinorVersion secondObject, 0, (err, forgottenSecondArticle) ->
+ throw err if err
+
+ firstArticle.makeNewMinorVersion secondObject, 0, (err, realSecondArticle) ->
+ throw err if err
+ expect(realSecondArticle.version.minor).toBe(1)
+ done()
+
+ it 'works if you add a new minor version for an old major version', (done) ->
+ firstArticle = new Article(name:'List Comp4.5', body:"A programmer's best friend.")
+ firstArticle.original = firstArticle._id
+
+ firstArticle.save (err) ->
+ throw err if err
+
+ secondObject = firstArticle.toObject()
+ secondObject['body'] = "Not as good as lambda."
+
+ firstArticle.makeNewMajorVersion secondObject, (err, secondArticle) ->
+ throw err if err
+
+ secondArticle.save (err) ->
+ throw err if err
+
+ thirdObject = secondArticle.toObject()
+ thirdObject['body'] = "..."
+
+ Article.findOne {_id: firstArticle._id}, (err, firstArticle) ->
+
+ firstArticle.makeNewMinorVersion thirdObject, 0, (err, thirdArticle) ->
+ throw err if err
+
+ thirdArticle.save ->
+
+ Article.find {original:firstArticle.original}, (err, results) ->
+ expect(results.length).toBe(3)
+ expect(results[2].version.major).toBe(0)
+ expect(results[1].version.major).toBe(0)
+ expect(results[0].version.major).toBe(1)
+
+ expect(results[2].version.minor).toBe(0)
+ expect(results[1].version.minor).toBe(1)
+ expect(results[0].version.minor).toBe(0)
+
+ expect(results[2].version.isLatestMajor).toBe(false)
+ expect(results[1].version.isLatestMajor).toBe(false)
+ expect(results[0].version.isLatestMajor).toBe(true)
+
+ expect(results[2].version.isLatestMinor).toBe(false)
+ expect(results[1].version.isLatestMinor).toBe(true)
+ expect(results[0].version.isLatestMinor).toBe(true)
+
+ expect(results[2].index).toBeUndefined()
+ expect(results[1].index).toBeUndefined()
+ expect(results[0].index).toBe(true)
+ done()
+
+ it 'only keeps slugs for the absolute latest versions', (done) ->
+ firstArticle = new Article(name:'List Comp4.6', body:"A programmer's best friend.")
+ firstArticle.original = firstArticle._id
+
+ firstArticle.save (err) ->
+ throw err if err
+
+ secondObject = firstArticle.toObject()
+ secondObject['body'] = "Not as good as lambda."
+
+ firstArticle.makeNewMajorVersion secondObject, (err, secondArticle) ->
+ throw err if err
+
+ secondArticle.save (err) ->
+ throw err if err
+
+ thirdObject = secondArticle.toObject()
+ thirdObject['body'] = "..."
+
+ Article.findOne {_id: firstArticle._id}, (err, firstArticle) ->
+
+ firstArticle.makeNewMinorVersion thirdObject, 0, (err, thirdArticle) ->
+ throw err if err
+
+ thirdArticle.save ->
+
+ Article.find {original:firstArticle.original}, (err, results) ->
+ expect(results.length).toBe(3)
+ expect(results[2].slug).toBeUndefined()
+ expect(results[1].slug).toBeUndefined()
+ expect(results[0].slug).not.toBeUndefined()
+ done()
+
+
+describe 'SearchablePlugin', ->
+ it 'clears things first', (done) ->
+ clearModels [Article], (err) ->
+ expect(err).toBeNull()
+ done()
+
+ it 'can do a text search', (done) ->
+ # absolutely does not work at all if you don't save an article first
+ firstArticle = new Article(
+ name:'List Comp5',
+ body:"A programmer's best friend.",
+ index:true
+ )
+ firstArticle.original = firstArticle._id
+
+ firstArticle.save (err) ->
+ throw err if err
+
+ Article.textSearch 'best', {filter:{ index: true}}, (err, results) ->
+ expect(err).toBeNull()
+ if results
+ expect(results.results.length).toBeGreaterThan(0)
+ else
+ console.log('ERROR:', err)
+ done()
+
+
+ it 'keeps the index property up to date', (done) ->
+ firstArticle = new Article(name:'List Comp6', body:"A programmer's best friend.")
+ firstArticle.original = firstArticle._id
+
+ firstArticle.save (err) ->
+ throw err if err
+
+ secondObject = firstArticle.toObject()
+ secondObject['body'] = "Not as good as lambda."
+
+ firstArticle.makeNewMinorVersion secondObject, 0, (err, secondArticle) ->
+ throw err if err
+
+ secondArticle.save (err) ->
+ throw err if err
+
+ thirdObject = secondArticle.toObject()
+ thirdObject['body'] = "..."
+
+ secondArticle.makeNewMajorVersion thirdObject, (err, thirdArticle) ->
+ throw err if err
+
+ thirdArticle.save ->
+ throw err if err
+
+ Article.find {original:firstArticle.original}, (err, results) ->
+ expect(results[2].index).toBeUndefined()
+ expect(results[1].index).toBeUndefined()
+ expect(results[0].index).toBe(true)
+ done()
+
+ raw =
+ name:'Battlefield 1942'
+ description:'Vacation all over the world!'
+ permissions:[
+ target:'not_the_public'
+ access:'owner'
+ ]
+
+ campaign = new Campaign(raw)
+
+ it 'clears things first', (done) ->
+ Campaign.remove {}, (err) ->
+ expect(err).toBeNull()
+ done()
+
+ it 'hides private entities from public searches', (done) ->
+ campaign.save (err) ->
+ throw err if err
+ done()
+
+ Campaign.textSearch 'battlefield', {filter:{ index: true}}, (err, results) ->
+ expect(results.results.length).toBe(0)
+ done()
+
+ it 'allows private searches for owning users', (done) ->
+ campaign.save (err) ->
+ throw err if err
+
+ Campaign.textSearch 'battlefield', {filter: { index: 'not_the_public' }}, (err, results) ->
+ expect(results.results.length).toBeGreaterThan(0)
+ done()
diff --git a/test/vendor/example.coffee b/test/vendor/example.coffee
new file mode 100644
index 000000000..d055d440e
--- /dev/null
+++ b/test/vendor/example.coffee
@@ -0,0 +1 @@
+massivelyUsefulTestExample = "test..."
diff --git a/vendor/scripts/Box2dWeb-2.1.a.3.js b/vendor/scripts/Box2dWeb-2.1.a.3.js
new file mode 100644
index 000000000..66ab96823
--- /dev/null
+++ b/vendor/scripts/Box2dWeb-2.1.a.3.js
@@ -0,0 +1,10866 @@
+/*
+* Copyright (c) 2006-2007 Erin Catto http://www.gphysics.com
+*
+* This software is provided 'as-is', without any express or implied
+* warranty. In no event will the authors be held liable for any damages
+* arising from the use of this software.
+* Permission is granted to anyone to use this software for any purpose,
+* including commercial applications, and to alter it and redistribute it
+* freely, subject to the following restrictions:
+* 1. The origin of this software must not be misrepresented; you must not
+* claim that you wrote the original software. If you use this software
+* in a product, an acknowledgment in the product documentation would be
+* appreciated but is not required.
+* 2. Altered source versions must be plainly marked as such, and must not be
+* misrepresented as being the original software.
+* 3. This notice may not be removed or altered from any source distribution.
+*/
+var Box2D = {};
+
+(function (a2j, undefined) {
+
+ if(!(Object.prototype.defineProperty instanceof Function)
+ && Object.prototype.__defineGetter__ instanceof Function
+ && Object.prototype.__defineSetter__ instanceof Function)
+ {
+ Object.defineProperty = function(obj, p, cfg) {
+ if(cfg.get instanceof Function)
+ obj.__defineGetter__(p, cfg.get);
+ if(cfg.set instanceof Function)
+ obj.__defineSetter__(p, cfg.set);
+ }
+ }
+
+ function emptyFn() {};
+ a2j.inherit = function(cls, base) {
+ var tmpCtr = cls;
+ emptyFn.prototype = base.prototype;
+ cls.prototype = new emptyFn;
+ cls.prototype.constructor = tmpCtr;
+ };
+
+ a2j.generateCallback = function generateCallback(context, cb) {
+ return function () {
+ cb.apply(context, arguments);
+ };
+ };
+
+ a2j.NVector = function NVector(length) {
+ if (length === undefined) length = 0;
+ var tmp = new Array(length || 0);
+ for (var i = 0; i < length; ++i)
+ tmp[i] = 0;
+ return tmp;
+ };
+
+ a2j.is = function is(o1, o2) {
+ if (o1 === null) return false;
+ if ((o2 instanceof Function) && (o1 instanceof o2)) return true;
+ if ((o1.constructor.__implements != undefined) && (o1.constructor.__implements[o2])) return true;
+ return false;
+ };
+
+ a2j.parseUInt = function(v) {
+ return Math.abs(parseInt(v));
+ }
+
+})(Box2D);
+
+//#TODO remove assignments from global namespace
+var Vector = Array;
+var Vector_a2j_Number = Box2D.NVector;
+//package structure
+if (typeof(Box2D) === "undefined") Box2D = {};
+if (typeof(Box2D.Collision) === "undefined") Box2D.Collision = {};
+if (typeof(Box2D.Collision.Shapes) === "undefined") Box2D.Collision.Shapes = {};
+if (typeof(Box2D.Common) === "undefined") Box2D.Common = {};
+if (typeof(Box2D.Common.Math) === "undefined") Box2D.Common.Math = {};
+if (typeof(Box2D.Dynamics) === "undefined") Box2D.Dynamics = {};
+if (typeof(Box2D.Dynamics.Contacts) === "undefined") Box2D.Dynamics.Contacts = {};
+if (typeof(Box2D.Dynamics.Controllers) === "undefined") Box2D.Dynamics.Controllers = {};
+if (typeof(Box2D.Dynamics.Joints) === "undefined") Box2D.Dynamics.Joints = {};
+//pre-definitions
+(function () {
+ Box2D.Collision.IBroadPhase = 'Box2D.Collision.IBroadPhase';
+
+ function b2AABB() {
+ b2AABB.b2AABB.apply(this, arguments);
+ };
+ Box2D.Collision.b2AABB = b2AABB;
+
+ function b2Bound() {
+ b2Bound.b2Bound.apply(this, arguments);
+ };
+ Box2D.Collision.b2Bound = b2Bound;
+
+ function b2BoundValues() {
+ b2BoundValues.b2BoundValues.apply(this, arguments);
+ if (this.constructor === b2BoundValues) this.b2BoundValues.apply(this, arguments);
+ };
+ Box2D.Collision.b2BoundValues = b2BoundValues;
+
+ function b2Collision() {
+ b2Collision.b2Collision.apply(this, arguments);
+ };
+ Box2D.Collision.b2Collision = b2Collision;
+
+ function b2ContactID() {
+ b2ContactID.b2ContactID.apply(this, arguments);
+ if (this.constructor === b2ContactID) this.b2ContactID.apply(this, arguments);
+ };
+ Box2D.Collision.b2ContactID = b2ContactID;
+
+ function b2ContactPoint() {
+ b2ContactPoint.b2ContactPoint.apply(this, arguments);
+ };
+ Box2D.Collision.b2ContactPoint = b2ContactPoint;
+
+ function b2Distance() {
+ b2Distance.b2Distance.apply(this, arguments);
+ };
+ Box2D.Collision.b2Distance = b2Distance;
+
+ function b2DistanceInput() {
+ b2DistanceInput.b2DistanceInput.apply(this, arguments);
+ };
+ Box2D.Collision.b2DistanceInput = b2DistanceInput;
+
+ function b2DistanceOutput() {
+ b2DistanceOutput.b2DistanceOutput.apply(this, arguments);
+ };
+ Box2D.Collision.b2DistanceOutput = b2DistanceOutput;
+
+ function b2DistanceProxy() {
+ b2DistanceProxy.b2DistanceProxy.apply(this, arguments);
+ };
+ Box2D.Collision.b2DistanceProxy = b2DistanceProxy;
+
+ function b2DynamicTree() {
+ b2DynamicTree.b2DynamicTree.apply(this, arguments);
+ if (this.constructor === b2DynamicTree) this.b2DynamicTree.apply(this, arguments);
+ };
+ Box2D.Collision.b2DynamicTree = b2DynamicTree;
+
+ function b2DynamicTreeBroadPhase() {
+ b2DynamicTreeBroadPhase.b2DynamicTreeBroadPhase.apply(this, arguments);
+ };
+ Box2D.Collision.b2DynamicTreeBroadPhase = b2DynamicTreeBroadPhase;
+
+ function b2DynamicTreeNode() {
+ b2DynamicTreeNode.b2DynamicTreeNode.apply(this, arguments);
+ };
+ Box2D.Collision.b2DynamicTreeNode = b2DynamicTreeNode;
+
+ function b2DynamicTreePair() {
+ b2DynamicTreePair.b2DynamicTreePair.apply(this, arguments);
+ };
+ Box2D.Collision.b2DynamicTreePair = b2DynamicTreePair;
+
+ function b2Manifold() {
+ b2Manifold.b2Manifold.apply(this, arguments);
+ if (this.constructor === b2Manifold) this.b2Manifold.apply(this, arguments);
+ };
+ Box2D.Collision.b2Manifold = b2Manifold;
+
+ function b2ManifoldPoint() {
+ b2ManifoldPoint.b2ManifoldPoint.apply(this, arguments);
+ if (this.constructor === b2ManifoldPoint) this.b2ManifoldPoint.apply(this, arguments);
+ };
+ Box2D.Collision.b2ManifoldPoint = b2ManifoldPoint;
+
+ function b2Point() {
+ b2Point.b2Point.apply(this, arguments);
+ };
+ Box2D.Collision.b2Point = b2Point;
+
+ function b2RayCastInput() {
+ b2RayCastInput.b2RayCastInput.apply(this, arguments);
+ if (this.constructor === b2RayCastInput) this.b2RayCastInput.apply(this, arguments);
+ };
+ Box2D.Collision.b2RayCastInput = b2RayCastInput;
+
+ function b2RayCastOutput() {
+ b2RayCastOutput.b2RayCastOutput.apply(this, arguments);
+ };
+ Box2D.Collision.b2RayCastOutput = b2RayCastOutput;
+
+ function b2Segment() {
+ b2Segment.b2Segment.apply(this, arguments);
+ };
+ Box2D.Collision.b2Segment = b2Segment;
+
+ function b2SeparationFunction() {
+ b2SeparationFunction.b2SeparationFunction.apply(this, arguments);
+ };
+ Box2D.Collision.b2SeparationFunction = b2SeparationFunction;
+
+ function b2Simplex() {
+ b2Simplex.b2Simplex.apply(this, arguments);
+ if (this.constructor === b2Simplex) this.b2Simplex.apply(this, arguments);
+ };
+ Box2D.Collision.b2Simplex = b2Simplex;
+
+ function b2SimplexCache() {
+ b2SimplexCache.b2SimplexCache.apply(this, arguments);
+ };
+ Box2D.Collision.b2SimplexCache = b2SimplexCache;
+
+ function b2SimplexVertex() {
+ b2SimplexVertex.b2SimplexVertex.apply(this, arguments);
+ };
+ Box2D.Collision.b2SimplexVertex = b2SimplexVertex;
+
+ function b2TimeOfImpact() {
+ b2TimeOfImpact.b2TimeOfImpact.apply(this, arguments);
+ };
+ Box2D.Collision.b2TimeOfImpact = b2TimeOfImpact;
+
+ function b2TOIInput() {
+ b2TOIInput.b2TOIInput.apply(this, arguments);
+ };
+ Box2D.Collision.b2TOIInput = b2TOIInput;
+
+ function b2WorldManifold() {
+ b2WorldManifold.b2WorldManifold.apply(this, arguments);
+ if (this.constructor === b2WorldManifold) this.b2WorldManifold.apply(this, arguments);
+ };
+ Box2D.Collision.b2WorldManifold = b2WorldManifold;
+
+ function ClipVertex() {
+ ClipVertex.ClipVertex.apply(this, arguments);
+ };
+ Box2D.Collision.ClipVertex = ClipVertex;
+
+ function Features() {
+ Features.Features.apply(this, arguments);
+ };
+ Box2D.Collision.Features = Features;
+
+ function b2CircleShape() {
+ b2CircleShape.b2CircleShape.apply(this, arguments);
+ if (this.constructor === b2CircleShape) this.b2CircleShape.apply(this, arguments);
+ };
+ Box2D.Collision.Shapes.b2CircleShape = b2CircleShape;
+
+ function b2EdgeChainDef() {
+ b2EdgeChainDef.b2EdgeChainDef.apply(this, arguments);
+ if (this.constructor === b2EdgeChainDef) this.b2EdgeChainDef.apply(this, arguments);
+ };
+ Box2D.Collision.Shapes.b2EdgeChainDef = b2EdgeChainDef;
+
+ function b2EdgeShape() {
+ b2EdgeShape.b2EdgeShape.apply(this, arguments);
+ if (this.constructor === b2EdgeShape) this.b2EdgeShape.apply(this, arguments);
+ };
+ Box2D.Collision.Shapes.b2EdgeShape = b2EdgeShape;
+
+ function b2MassData() {
+ b2MassData.b2MassData.apply(this, arguments);
+ };
+ Box2D.Collision.Shapes.b2MassData = b2MassData;
+
+ function b2PolygonShape() {
+ b2PolygonShape.b2PolygonShape.apply(this, arguments);
+ if (this.constructor === b2PolygonShape) this.b2PolygonShape.apply(this, arguments);
+ };
+ Box2D.Collision.Shapes.b2PolygonShape = b2PolygonShape;
+
+ function b2Shape() {
+ b2Shape.b2Shape.apply(this, arguments);
+ if (this.constructor === b2Shape) this.b2Shape.apply(this, arguments);
+ };
+ Box2D.Collision.Shapes.b2Shape = b2Shape;
+ Box2D.Common.b2internal = 'Box2D.Common.b2internal';
+
+ function b2Color() {
+ b2Color.b2Color.apply(this, arguments);
+ if (this.constructor === b2Color) this.b2Color.apply(this, arguments);
+ };
+ Box2D.Common.b2Color = b2Color;
+
+ function b2Settings() {
+ b2Settings.b2Settings.apply(this, arguments);
+ };
+ Box2D.Common.b2Settings = b2Settings;
+
+ function b2Mat22() {
+ b2Mat22.b2Mat22.apply(this, arguments);
+ if (this.constructor === b2Mat22) this.b2Mat22.apply(this, arguments);
+ };
+ Box2D.Common.Math.b2Mat22 = b2Mat22;
+
+ function b2Mat33() {
+ b2Mat33.b2Mat33.apply(this, arguments);
+ if (this.constructor === b2Mat33) this.b2Mat33.apply(this, arguments);
+ };
+ Box2D.Common.Math.b2Mat33 = b2Mat33;
+
+ function b2Math() {
+ b2Math.b2Math.apply(this, arguments);
+ };
+ Box2D.Common.Math.b2Math = b2Math;
+
+ function b2Sweep() {
+ b2Sweep.b2Sweep.apply(this, arguments);
+ };
+ Box2D.Common.Math.b2Sweep = b2Sweep;
+
+ function b2Transform() {
+ b2Transform.b2Transform.apply(this, arguments);
+ if (this.constructor === b2Transform) this.b2Transform.apply(this, arguments);
+ };
+ Box2D.Common.Math.b2Transform = b2Transform;
+
+ function b2Vec2() {
+ b2Vec2.b2Vec2.apply(this, arguments);
+ if (this.constructor === b2Vec2) this.b2Vec2.apply(this, arguments);
+ };
+ Box2D.Common.Math.b2Vec2 = b2Vec2;
+
+ function b2Vec3() {
+ b2Vec3.b2Vec3.apply(this, arguments);
+ if (this.constructor === b2Vec3) this.b2Vec3.apply(this, arguments);
+ };
+ Box2D.Common.Math.b2Vec3 = b2Vec3;
+
+ function b2Body() {
+ b2Body.b2Body.apply(this, arguments);
+ if (this.constructor === b2Body) this.b2Body.apply(this, arguments);
+ };
+ Box2D.Dynamics.b2Body = b2Body;
+
+ function b2BodyDef() {
+ b2BodyDef.b2BodyDef.apply(this, arguments);
+ if (this.constructor === b2BodyDef) this.b2BodyDef.apply(this, arguments);
+ };
+ Box2D.Dynamics.b2BodyDef = b2BodyDef;
+
+ function b2ContactFilter() {
+ b2ContactFilter.b2ContactFilter.apply(this, arguments);
+ };
+ Box2D.Dynamics.b2ContactFilter = b2ContactFilter;
+
+ function b2ContactImpulse() {
+ b2ContactImpulse.b2ContactImpulse.apply(this, arguments);
+ };
+ Box2D.Dynamics.b2ContactImpulse = b2ContactImpulse;
+
+ function b2ContactListener() {
+ b2ContactListener.b2ContactListener.apply(this, arguments);
+ };
+ Box2D.Dynamics.b2ContactListener = b2ContactListener;
+
+ function b2ContactManager() {
+ b2ContactManager.b2ContactManager.apply(this, arguments);
+ if (this.constructor === b2ContactManager) this.b2ContactManager.apply(this, arguments);
+ };
+ Box2D.Dynamics.b2ContactManager = b2ContactManager;
+
+ function b2DebugDraw() {
+ b2DebugDraw.b2DebugDraw.apply(this, arguments);
+ if (this.constructor === b2DebugDraw) this.b2DebugDraw.apply(this, arguments);
+ };
+ Box2D.Dynamics.b2DebugDraw = b2DebugDraw;
+
+ function b2DestructionListener() {
+ b2DestructionListener.b2DestructionListener.apply(this, arguments);
+ };
+ Box2D.Dynamics.b2DestructionListener = b2DestructionListener;
+
+ function b2FilterData() {
+ b2FilterData.b2FilterData.apply(this, arguments);
+ };
+ Box2D.Dynamics.b2FilterData = b2FilterData;
+
+ function b2Fixture() {
+ b2Fixture.b2Fixture.apply(this, arguments);
+ if (this.constructor === b2Fixture) this.b2Fixture.apply(this, arguments);
+ };
+ Box2D.Dynamics.b2Fixture = b2Fixture;
+
+ function b2FixtureDef() {
+ b2FixtureDef.b2FixtureDef.apply(this, arguments);
+ if (this.constructor === b2FixtureDef) this.b2FixtureDef.apply(this, arguments);
+ };
+ Box2D.Dynamics.b2FixtureDef = b2FixtureDef;
+
+ function b2Island() {
+ b2Island.b2Island.apply(this, arguments);
+ if (this.constructor === b2Island) this.b2Island.apply(this, arguments);
+ };
+ Box2D.Dynamics.b2Island = b2Island;
+
+ function b2TimeStep() {
+ b2TimeStep.b2TimeStep.apply(this, arguments);
+ };
+ Box2D.Dynamics.b2TimeStep = b2TimeStep;
+
+ function b2World() {
+ b2World.b2World.apply(this, arguments);
+ if (this.constructor === b2World) this.b2World.apply(this, arguments);
+ };
+ Box2D.Dynamics.b2World = b2World;
+
+ function b2CircleContact() {
+ b2CircleContact.b2CircleContact.apply(this, arguments);
+ };
+ Box2D.Dynamics.Contacts.b2CircleContact = b2CircleContact;
+
+ function b2Contact() {
+ b2Contact.b2Contact.apply(this, arguments);
+ if (this.constructor === b2Contact) this.b2Contact.apply(this, arguments);
+ };
+ Box2D.Dynamics.Contacts.b2Contact = b2Contact;
+
+ function b2ContactConstraint() {
+ b2ContactConstraint.b2ContactConstraint.apply(this, arguments);
+ if (this.constructor === b2ContactConstraint) this.b2ContactConstraint.apply(this, arguments);
+ };
+ Box2D.Dynamics.Contacts.b2ContactConstraint = b2ContactConstraint;
+
+ function b2ContactConstraintPoint() {
+ b2ContactConstraintPoint.b2ContactConstraintPoint.apply(this, arguments);
+ };
+ Box2D.Dynamics.Contacts.b2ContactConstraintPoint = b2ContactConstraintPoint;
+
+ function b2ContactEdge() {
+ b2ContactEdge.b2ContactEdge.apply(this, arguments);
+ };
+ Box2D.Dynamics.Contacts.b2ContactEdge = b2ContactEdge;
+
+ function b2ContactFactory() {
+ b2ContactFactory.b2ContactFactory.apply(this, arguments);
+ if (this.constructor === b2ContactFactory) this.b2ContactFactory.apply(this, arguments);
+ };
+ Box2D.Dynamics.Contacts.b2ContactFactory = b2ContactFactory;
+
+ function b2ContactRegister() {
+ b2ContactRegister.b2ContactRegister.apply(this, arguments);
+ };
+ Box2D.Dynamics.Contacts.b2ContactRegister = b2ContactRegister;
+
+ function b2ContactResult() {
+ b2ContactResult.b2ContactResult.apply(this, arguments);
+ };
+ Box2D.Dynamics.Contacts.b2ContactResult = b2ContactResult;
+
+ function b2ContactSolver() {
+ b2ContactSolver.b2ContactSolver.apply(this, arguments);
+ if (this.constructor === b2ContactSolver) this.b2ContactSolver.apply(this, arguments);
+ };
+ Box2D.Dynamics.Contacts.b2ContactSolver = b2ContactSolver;
+
+ function b2EdgeAndCircleContact() {
+ b2EdgeAndCircleContact.b2EdgeAndCircleContact.apply(this, arguments);
+ };
+ Box2D.Dynamics.Contacts.b2EdgeAndCircleContact = b2EdgeAndCircleContact;
+
+ function b2NullContact() {
+ b2NullContact.b2NullContact.apply(this, arguments);
+ if (this.constructor === b2NullContact) this.b2NullContact.apply(this, arguments);
+ };
+ Box2D.Dynamics.Contacts.b2NullContact = b2NullContact;
+
+ function b2PolyAndCircleContact() {
+ b2PolyAndCircleContact.b2PolyAndCircleContact.apply(this, arguments);
+ };
+ Box2D.Dynamics.Contacts.b2PolyAndCircleContact = b2PolyAndCircleContact;
+
+ function b2PolyAndEdgeContact() {
+ b2PolyAndEdgeContact.b2PolyAndEdgeContact.apply(this, arguments);
+ };
+ Box2D.Dynamics.Contacts.b2PolyAndEdgeContact = b2PolyAndEdgeContact;
+
+ function b2PolygonContact() {
+ b2PolygonContact.b2PolygonContact.apply(this, arguments);
+ };
+ Box2D.Dynamics.Contacts.b2PolygonContact = b2PolygonContact;
+
+ function b2PositionSolverManifold() {
+ b2PositionSolverManifold.b2PositionSolverManifold.apply(this, arguments);
+ if (this.constructor === b2PositionSolverManifold) this.b2PositionSolverManifold.apply(this, arguments);
+ };
+ Box2D.Dynamics.Contacts.b2PositionSolverManifold = b2PositionSolverManifold;
+
+ function b2BuoyancyController() {
+ b2BuoyancyController.b2BuoyancyController.apply(this, arguments);
+ };
+ Box2D.Dynamics.Controllers.b2BuoyancyController = b2BuoyancyController;
+
+ function b2ConstantAccelController() {
+ b2ConstantAccelController.b2ConstantAccelController.apply(this, arguments);
+ };
+ Box2D.Dynamics.Controllers.b2ConstantAccelController = b2ConstantAccelController;
+
+ function b2ConstantForceController() {
+ b2ConstantForceController.b2ConstantForceController.apply(this, arguments);
+ };
+ Box2D.Dynamics.Controllers.b2ConstantForceController = b2ConstantForceController;
+
+ function b2Controller() {
+ b2Controller.b2Controller.apply(this, arguments);
+ };
+ Box2D.Dynamics.Controllers.b2Controller = b2Controller;
+
+ function b2ControllerEdge() {
+ b2ControllerEdge.b2ControllerEdge.apply(this, arguments);
+ };
+ Box2D.Dynamics.Controllers.b2ControllerEdge = b2ControllerEdge;
+
+ function b2GravityController() {
+ b2GravityController.b2GravityController.apply(this, arguments);
+ };
+ Box2D.Dynamics.Controllers.b2GravityController = b2GravityController;
+
+ function b2TensorDampingController() {
+ b2TensorDampingController.b2TensorDampingController.apply(this, arguments);
+ };
+ Box2D.Dynamics.Controllers.b2TensorDampingController = b2TensorDampingController;
+
+ function b2DistanceJoint() {
+ b2DistanceJoint.b2DistanceJoint.apply(this, arguments);
+ if (this.constructor === b2DistanceJoint) this.b2DistanceJoint.apply(this, arguments);
+ };
+ Box2D.Dynamics.Joints.b2DistanceJoint = b2DistanceJoint;
+
+ function b2DistanceJointDef() {
+ b2DistanceJointDef.b2DistanceJointDef.apply(this, arguments);
+ if (this.constructor === b2DistanceJointDef) this.b2DistanceJointDef.apply(this, arguments);
+ };
+ Box2D.Dynamics.Joints.b2DistanceJointDef = b2DistanceJointDef;
+
+ function b2FrictionJoint() {
+ b2FrictionJoint.b2FrictionJoint.apply(this, arguments);
+ if (this.constructor === b2FrictionJoint) this.b2FrictionJoint.apply(this, arguments);
+ };
+ Box2D.Dynamics.Joints.b2FrictionJoint = b2FrictionJoint;
+
+ function b2FrictionJointDef() {
+ b2FrictionJointDef.b2FrictionJointDef.apply(this, arguments);
+ if (this.constructor === b2FrictionJointDef) this.b2FrictionJointDef.apply(this, arguments);
+ };
+ Box2D.Dynamics.Joints.b2FrictionJointDef = b2FrictionJointDef;
+
+ function b2GearJoint() {
+ b2GearJoint.b2GearJoint.apply(this, arguments);
+ if (this.constructor === b2GearJoint) this.b2GearJoint.apply(this, arguments);
+ };
+ Box2D.Dynamics.Joints.b2GearJoint = b2GearJoint;
+
+ function b2GearJointDef() {
+ b2GearJointDef.b2GearJointDef.apply(this, arguments);
+ if (this.constructor === b2GearJointDef) this.b2GearJointDef.apply(this, arguments);
+ };
+ Box2D.Dynamics.Joints.b2GearJointDef = b2GearJointDef;
+
+ function b2Jacobian() {
+ b2Jacobian.b2Jacobian.apply(this, arguments);
+ };
+ Box2D.Dynamics.Joints.b2Jacobian = b2Jacobian;
+
+ function b2Joint() {
+ b2Joint.b2Joint.apply(this, arguments);
+ if (this.constructor === b2Joint) this.b2Joint.apply(this, arguments);
+ };
+ Box2D.Dynamics.Joints.b2Joint = b2Joint;
+
+ function b2JointDef() {
+ b2JointDef.b2JointDef.apply(this, arguments);
+ if (this.constructor === b2JointDef) this.b2JointDef.apply(this, arguments);
+ };
+ Box2D.Dynamics.Joints.b2JointDef = b2JointDef;
+
+ function b2JointEdge() {
+ b2JointEdge.b2JointEdge.apply(this, arguments);
+ };
+ Box2D.Dynamics.Joints.b2JointEdge = b2JointEdge;
+
+ function b2LineJoint() {
+ b2LineJoint.b2LineJoint.apply(this, arguments);
+ if (this.constructor === b2LineJoint) this.b2LineJoint.apply(this, arguments);
+ };
+ Box2D.Dynamics.Joints.b2LineJoint = b2LineJoint;
+
+ function b2LineJointDef() {
+ b2LineJointDef.b2LineJointDef.apply(this, arguments);
+ if (this.constructor === b2LineJointDef) this.b2LineJointDef.apply(this, arguments);
+ };
+ Box2D.Dynamics.Joints.b2LineJointDef = b2LineJointDef;
+
+ function b2MouseJoint() {
+ b2MouseJoint.b2MouseJoint.apply(this, arguments);
+ if (this.constructor === b2MouseJoint) this.b2MouseJoint.apply(this, arguments);
+ };
+ Box2D.Dynamics.Joints.b2MouseJoint = b2MouseJoint;
+
+ function b2MouseJointDef() {
+ b2MouseJointDef.b2MouseJointDef.apply(this, arguments);
+ if (this.constructor === b2MouseJointDef) this.b2MouseJointDef.apply(this, arguments);
+ };
+ Box2D.Dynamics.Joints.b2MouseJointDef = b2MouseJointDef;
+
+ function b2PrismaticJoint() {
+ b2PrismaticJoint.b2PrismaticJoint.apply(this, arguments);
+ if (this.constructor === b2PrismaticJoint) this.b2PrismaticJoint.apply(this, arguments);
+ };
+ Box2D.Dynamics.Joints.b2PrismaticJoint = b2PrismaticJoint;
+
+ function b2PrismaticJointDef() {
+ b2PrismaticJointDef.b2PrismaticJointDef.apply(this, arguments);
+ if (this.constructor === b2PrismaticJointDef) this.b2PrismaticJointDef.apply(this, arguments);
+ };
+ Box2D.Dynamics.Joints.b2PrismaticJointDef = b2PrismaticJointDef;
+
+ function b2PulleyJoint() {
+ b2PulleyJoint.b2PulleyJoint.apply(this, arguments);
+ if (this.constructor === b2PulleyJoint) this.b2PulleyJoint.apply(this, arguments);
+ };
+ Box2D.Dynamics.Joints.b2PulleyJoint = b2PulleyJoint;
+
+ function b2PulleyJointDef() {
+ b2PulleyJointDef.b2PulleyJointDef.apply(this, arguments);
+ if (this.constructor === b2PulleyJointDef) this.b2PulleyJointDef.apply(this, arguments);
+ };
+ Box2D.Dynamics.Joints.b2PulleyJointDef = b2PulleyJointDef;
+
+ function b2RevoluteJoint() {
+ b2RevoluteJoint.b2RevoluteJoint.apply(this, arguments);
+ if (this.constructor === b2RevoluteJoint) this.b2RevoluteJoint.apply(this, arguments);
+ };
+ Box2D.Dynamics.Joints.b2RevoluteJoint = b2RevoluteJoint;
+
+ function b2RevoluteJointDef() {
+ b2RevoluteJointDef.b2RevoluteJointDef.apply(this, arguments);
+ if (this.constructor === b2RevoluteJointDef) this.b2RevoluteJointDef.apply(this, arguments);
+ };
+ Box2D.Dynamics.Joints.b2RevoluteJointDef = b2RevoluteJointDef;
+
+ function b2WeldJoint() {
+ b2WeldJoint.b2WeldJoint.apply(this, arguments);
+ if (this.constructor === b2WeldJoint) this.b2WeldJoint.apply(this, arguments);
+ };
+ Box2D.Dynamics.Joints.b2WeldJoint = b2WeldJoint;
+
+ function b2WeldJointDef() {
+ b2WeldJointDef.b2WeldJointDef.apply(this, arguments);
+ if (this.constructor === b2WeldJointDef) this.b2WeldJointDef.apply(this, arguments);
+ };
+ Box2D.Dynamics.Joints.b2WeldJointDef = b2WeldJointDef;
+})(); //definitions
+Box2D.postDefs = [];
+(function () {
+ var b2CircleShape = Box2D.Collision.Shapes.b2CircleShape,
+ b2EdgeChainDef = Box2D.Collision.Shapes.b2EdgeChainDef,
+ b2EdgeShape = Box2D.Collision.Shapes.b2EdgeShape,
+ b2MassData = Box2D.Collision.Shapes.b2MassData,
+ b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape,
+ b2Shape = Box2D.Collision.Shapes.b2Shape,
+ b2Color = Box2D.Common.b2Color,
+ b2internal = Box2D.Common.b2internal,
+ b2Settings = Box2D.Common.b2Settings,
+ b2Mat22 = Box2D.Common.Math.b2Mat22,
+ b2Mat33 = Box2D.Common.Math.b2Mat33,
+ b2Math = Box2D.Common.Math.b2Math,
+ b2Sweep = Box2D.Common.Math.b2Sweep,
+ b2Transform = Box2D.Common.Math.b2Transform,
+ b2Vec2 = Box2D.Common.Math.b2Vec2,
+ b2Vec3 = Box2D.Common.Math.b2Vec3,
+ b2AABB = Box2D.Collision.b2AABB,
+ b2Bound = Box2D.Collision.b2Bound,
+ b2BoundValues = Box2D.Collision.b2BoundValues,
+ b2Collision = Box2D.Collision.b2Collision,
+ b2ContactID = Box2D.Collision.b2ContactID,
+ b2ContactPoint = Box2D.Collision.b2ContactPoint,
+ b2Distance = Box2D.Collision.b2Distance,
+ b2DistanceInput = Box2D.Collision.b2DistanceInput,
+ b2DistanceOutput = Box2D.Collision.b2DistanceOutput,
+ b2DistanceProxy = Box2D.Collision.b2DistanceProxy,
+ b2DynamicTree = Box2D.Collision.b2DynamicTree,
+ b2DynamicTreeBroadPhase = Box2D.Collision.b2DynamicTreeBroadPhase,
+ b2DynamicTreeNode = Box2D.Collision.b2DynamicTreeNode,
+ b2DynamicTreePair = Box2D.Collision.b2DynamicTreePair,
+ b2Manifold = Box2D.Collision.b2Manifold,
+ b2ManifoldPoint = Box2D.Collision.b2ManifoldPoint,
+ b2Point = Box2D.Collision.b2Point,
+ b2RayCastInput = Box2D.Collision.b2RayCastInput,
+ b2RayCastOutput = Box2D.Collision.b2RayCastOutput,
+ b2Segment = Box2D.Collision.b2Segment,
+ b2SeparationFunction = Box2D.Collision.b2SeparationFunction,
+ b2Simplex = Box2D.Collision.b2Simplex,
+ b2SimplexCache = Box2D.Collision.b2SimplexCache,
+ b2SimplexVertex = Box2D.Collision.b2SimplexVertex,
+ b2TimeOfImpact = Box2D.Collision.b2TimeOfImpact,
+ b2TOIInput = Box2D.Collision.b2TOIInput,
+ b2WorldManifold = Box2D.Collision.b2WorldManifold,
+ ClipVertex = Box2D.Collision.ClipVertex,
+ Features = Box2D.Collision.Features,
+ IBroadPhase = Box2D.Collision.IBroadPhase;
+
+ b2AABB.b2AABB = function () {
+ this.lowerBound = new b2Vec2();
+ this.upperBound = new b2Vec2();
+ };
+ b2AABB.prototype.IsValid = function () {
+ var dX = this.upperBound.x - this.lowerBound.x;
+ var dY = this.upperBound.y - this.lowerBound.y;
+ var valid = dX >= 0.0 && dY >= 0.0;
+ valid = valid && this.lowerBound.IsValid() && this.upperBound.IsValid();
+ return valid;
+ }
+ b2AABB.prototype.GetCenter = function () {
+ return new b2Vec2((this.lowerBound.x + this.upperBound.x) / 2, (this.lowerBound.y + this.upperBound.y) / 2);
+ }
+ b2AABB.prototype.GetExtents = function () {
+ return new b2Vec2((this.upperBound.x - this.lowerBound.x) / 2, (this.upperBound.y - this.lowerBound.y) / 2);
+ }
+ b2AABB.prototype.Contains = function (aabb) {
+ var result = true;
+ result = result && this.lowerBound.x <= aabb.lowerBound.x;
+ result = result && this.lowerBound.y <= aabb.lowerBound.y;
+ result = result && aabb.upperBound.x <= this.upperBound.x;
+ result = result && aabb.upperBound.y <= this.upperBound.y;
+ return result;
+ }
+ b2AABB.prototype.RayCast = function (output, input) {
+ var tmin = (-Number.MAX_VALUE);
+ var tmax = Number.MAX_VALUE;
+ var pX = input.p1.x;
+ var pY = input.p1.y;
+ var dX = input.p2.x - input.p1.x;
+ var dY = input.p2.y - input.p1.y;
+ var absDX = Math.abs(dX);
+ var absDY = Math.abs(dY);
+ var normal = output.normal;
+ var inv_d = 0;
+ var t1 = 0;
+ var t2 = 0;
+ var t3 = 0;
+ var s = 0; {
+ if (absDX < Number.MIN_VALUE) {
+ if (pX < this.lowerBound.x || this.upperBound.x < pX) return false;
+ }
+ else {
+ inv_d = 1.0 / dX;
+ t1 = (this.lowerBound.x - pX) * inv_d;
+ t2 = (this.upperBound.x - pX) * inv_d;
+ s = (-1.0);
+ if (t1 > t2) {
+ t3 = t1;
+ t1 = t2;
+ t2 = t3;
+ s = 1.0;
+ }
+ if (t1 > tmin) {
+ normal.x = s;
+ normal.y = 0;
+ tmin = t1;
+ }
+ tmax = Math.min(tmax, t2);
+ if (tmin > tmax) return false;
+ }
+ } {
+ if (absDY < Number.MIN_VALUE) {
+ if (pY < this.lowerBound.y || this.upperBound.y < pY) return false;
+ }
+ else {
+ inv_d = 1.0 / dY;
+ t1 = (this.lowerBound.y - pY) * inv_d;
+ t2 = (this.upperBound.y - pY) * inv_d;
+ s = (-1.0);
+ if (t1 > t2) {
+ t3 = t1;
+ t1 = t2;
+ t2 = t3;
+ s = 1.0;
+ }
+ if (t1 > tmin) {
+ normal.y = s;
+ normal.x = 0;
+ tmin = t1;
+ }
+ tmax = Math.min(tmax, t2);
+ if (tmin > tmax) return false;
+ }
+ }
+ output.fraction = tmin;
+ return true;
+ }
+ b2AABB.prototype.TestOverlap = function (other) {
+ var d1X = other.lowerBound.x - this.upperBound.x;
+ var d1Y = other.lowerBound.y - this.upperBound.y;
+ var d2X = this.lowerBound.x - other.upperBound.x;
+ var d2Y = this.lowerBound.y - other.upperBound.y;
+ if (d1X > 0.0 || d1Y > 0.0) return false;
+ if (d2X > 0.0 || d2Y > 0.0) return false;
+ return true;
+ }
+ b2AABB.Combine = function (aabb1, aabb2) {
+ var aabb = new b2AABB();
+ aabb.Combine(aabb1, aabb2);
+ return aabb;
+ }
+ b2AABB.prototype.Combine = function (aabb1, aabb2) {
+ this.lowerBound.x = Math.min(aabb1.lowerBound.x, aabb2.lowerBound.x);
+ this.lowerBound.y = Math.min(aabb1.lowerBound.y, aabb2.lowerBound.y);
+ this.upperBound.x = Math.max(aabb1.upperBound.x, aabb2.upperBound.x);
+ this.upperBound.y = Math.max(aabb1.upperBound.y, aabb2.upperBound.y);
+ }
+ b2Bound.b2Bound = function () {};
+ b2Bound.prototype.IsLower = function () {
+ return (this.value & 1) == 0;
+ }
+ b2Bound.prototype.IsUpper = function () {
+ return (this.value & 1) == 1;
+ }
+ b2Bound.prototype.Swap = function (b) {
+ var tempValue = this.value;
+ var tempProxy = this.proxy;
+ var tempStabbingCount = this.stabbingCount;
+ this.value = b.value;
+ this.proxy = b.proxy;
+ this.stabbingCount = b.stabbingCount;
+ b.value = tempValue;
+ b.proxy = tempProxy;
+ b.stabbingCount = tempStabbingCount;
+ }
+ b2BoundValues.b2BoundValues = function () {};
+ b2BoundValues.prototype.b2BoundValues = function () {
+ this.lowerValues = new Vector_a2j_Number();
+ this.lowerValues[0] = 0.0;
+ this.lowerValues[1] = 0.0;
+ this.upperValues = new Vector_a2j_Number();
+ this.upperValues[0] = 0.0;
+ this.upperValues[1] = 0.0;
+ }
+ b2Collision.b2Collision = function () {};
+ b2Collision.ClipSegmentToLine = function (vOut, vIn, normal, offset) {
+ if (offset === undefined) offset = 0;
+ var cv;
+ var numOut = 0;
+ cv = vIn[0];
+ var vIn0 = cv.v;
+ cv = vIn[1];
+ var vIn1 = cv.v;
+ var distance0 = normal.x * vIn0.x + normal.y * vIn0.y - offset;
+ var distance1 = normal.x * vIn1.x + normal.y * vIn1.y - offset;
+ if (distance0 <= 0.0) vOut[numOut++].Set(vIn[0]);
+ if (distance1 <= 0.0) vOut[numOut++].Set(vIn[1]);
+ if (distance0 * distance1 < 0.0) {
+ var interp = distance0 / (distance0 - distance1);
+ cv = vOut[numOut];
+ var tVec = cv.v;
+ tVec.x = vIn0.x + interp * (vIn1.x - vIn0.x);
+ tVec.y = vIn0.y + interp * (vIn1.y - vIn0.y);
+ cv = vOut[numOut];
+ var cv2;
+ if (distance0 > 0.0) {
+ cv2 = vIn[0];
+ cv.id = cv2.id;
+ }
+ else {
+ cv2 = vIn[1];
+ cv.id = cv2.id;
+ }++numOut;
+ }
+ return numOut;
+ }
+ b2Collision.EdgeSeparation = function (poly1, xf1, edge1, poly2, xf2) {
+ if (edge1 === undefined) edge1 = 0;
+ var count1 = parseInt(poly1.m_vertexCount);
+ var vertices1 = poly1.m_vertices;
+ var normals1 = poly1.m_normals;
+ var count2 = parseInt(poly2.m_vertexCount);
+ var vertices2 = poly2.m_vertices;
+ var tMat;
+ var tVec;
+ tMat = xf1.R;
+ tVec = normals1[edge1];
+ var normal1WorldX = (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ var normal1WorldY = (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ tMat = xf2.R;
+ var normal1X = (tMat.col1.x * normal1WorldX + tMat.col1.y * normal1WorldY);
+ var normal1Y = (tMat.col2.x * normal1WorldX + tMat.col2.y * normal1WorldY);
+ var index = 0;
+ var minDot = Number.MAX_VALUE;
+ for (var i = 0; i < count2; ++i) {
+ tVec = vertices2[i];
+ var dot = tVec.x * normal1X + tVec.y * normal1Y;
+ if (dot < minDot) {
+ minDot = dot;
+ index = i;
+ }
+ }
+ tVec = vertices1[edge1];
+ tMat = xf1.R;
+ var v1X = xf1.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ var v1Y = xf1.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ tVec = vertices2[index];
+ tMat = xf2.R;
+ var v2X = xf2.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ var v2Y = xf2.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ v2X -= v1X;
+ v2Y -= v1Y;
+ var separation = v2X * normal1WorldX + v2Y * normal1WorldY;
+ return separation;
+ }
+ b2Collision.FindMaxSeparation = function (edgeIndex, poly1, xf1, poly2, xf2) {
+ var count1 = parseInt(poly1.m_vertexCount);
+ var normals1 = poly1.m_normals;
+ var tVec;
+ var tMat;
+ tMat = xf2.R;
+ tVec = poly2.m_centroid;
+ var dX = xf2.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ var dY = xf2.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ tMat = xf1.R;
+ tVec = poly1.m_centroid;
+ dX -= xf1.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ dY -= xf1.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ var dLocal1X = (dX * xf1.R.col1.x + dY * xf1.R.col1.y);
+ var dLocal1Y = (dX * xf1.R.col2.x + dY * xf1.R.col2.y);
+ var edge = 0;
+ var maxDot = (-Number.MAX_VALUE);
+ for (var i = 0; i < count1; ++i) {
+ tVec = normals1[i];
+ var dot = (tVec.x * dLocal1X + tVec.y * dLocal1Y);
+ if (dot > maxDot) {
+ maxDot = dot;
+ edge = i;
+ }
+ }
+ var s = b2Collision.EdgeSeparation(poly1, xf1, edge, poly2, xf2);
+ var prevEdge = parseInt(edge - 1 >= 0 ? edge - 1 : count1 - 1);
+ var sPrev = b2Collision.EdgeSeparation(poly1, xf1, prevEdge, poly2, xf2);
+ var nextEdge = parseInt(edge + 1 < count1 ? edge + 1 : 0);
+ var sNext = b2Collision.EdgeSeparation(poly1, xf1, nextEdge, poly2, xf2);
+ var bestEdge = 0;
+ var bestSeparation = 0;
+ var increment = 0;
+ if (sPrev > s && sPrev > sNext) {
+ increment = (-1);
+ bestEdge = prevEdge;
+ bestSeparation = sPrev;
+ }
+ else if (sNext > s) {
+ increment = 1;
+ bestEdge = nextEdge;
+ bestSeparation = sNext;
+ }
+ else {
+ edgeIndex[0] = edge;
+ return s;
+ }
+ while (true) {
+ if (increment == (-1)) edge = bestEdge - 1 >= 0 ? bestEdge - 1 : count1 - 1;
+ else edge = bestEdge + 1 < count1 ? bestEdge + 1 : 0;s = b2Collision.EdgeSeparation(poly1, xf1, edge, poly2, xf2);
+ if (s > bestSeparation) {
+ bestEdge = edge;
+ bestSeparation = s;
+ }
+ else {
+ break;
+ }
+ }
+ edgeIndex[0] = bestEdge;
+ return bestSeparation;
+ }
+ b2Collision.FindIncidentEdge = function (c, poly1, xf1, edge1, poly2, xf2) {
+ if (edge1 === undefined) edge1 = 0;
+ var count1 = parseInt(poly1.m_vertexCount);
+ var normals1 = poly1.m_normals;
+ var count2 = parseInt(poly2.m_vertexCount);
+ var vertices2 = poly2.m_vertices;
+ var normals2 = poly2.m_normals;
+ var tMat;
+ var tVec;
+ tMat = xf1.R;
+ tVec = normals1[edge1];
+ var normal1X = (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ var normal1Y = (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ tMat = xf2.R;
+ var tX = (tMat.col1.x * normal1X + tMat.col1.y * normal1Y);
+ normal1Y = (tMat.col2.x * normal1X + tMat.col2.y * normal1Y);
+ normal1X = tX;
+ var index = 0;
+ var minDot = Number.MAX_VALUE;
+ for (var i = 0; i < count2; ++i) {
+ tVec = normals2[i];
+ var dot = (normal1X * tVec.x + normal1Y * tVec.y);
+ if (dot < minDot) {
+ minDot = dot;
+ index = i;
+ }
+ }
+ var tClip;
+ var i1 = parseInt(index);
+ var i2 = parseInt(i1 + 1 < count2 ? i1 + 1 : 0);
+ tClip = c[0];
+ tVec = vertices2[i1];
+ tMat = xf2.R;
+ tClip.v.x = xf2.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ tClip.v.y = xf2.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ tClip.id.features.referenceEdge = edge1;
+ tClip.id.features.incidentEdge = i1;
+ tClip.id.features.incidentVertex = 0;
+ tClip = c[1];
+ tVec = vertices2[i2];
+ tMat = xf2.R;
+ tClip.v.x = xf2.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ tClip.v.y = xf2.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ tClip.id.features.referenceEdge = edge1;
+ tClip.id.features.incidentEdge = i2;
+ tClip.id.features.incidentVertex = 1;
+ }
+ b2Collision.MakeClipPointVector = function () {
+ var r = new Vector(2);
+ r[0] = new ClipVertex();
+ r[1] = new ClipVertex();
+ return r;
+ }
+ b2Collision.CollidePolygons = function (manifold, polyA, xfA, polyB, xfB) {
+ var cv;
+ manifold.m_pointCount = 0;
+ var totalRadius = polyA.m_radius + polyB.m_radius;
+ var edgeA = 0;
+ b2Collision.s_edgeAO[0] = edgeA;
+ var separationA = b2Collision.FindMaxSeparation(b2Collision.s_edgeAO, polyA, xfA, polyB, xfB);
+ edgeA = b2Collision.s_edgeAO[0];
+ if (separationA > totalRadius) return;
+ var edgeB = 0;
+ b2Collision.s_edgeBO[0] = edgeB;
+ var separationB = b2Collision.FindMaxSeparation(b2Collision.s_edgeBO, polyB, xfB, polyA, xfA);
+ edgeB = b2Collision.s_edgeBO[0];
+ if (separationB > totalRadius) return;
+ var poly1;
+ var poly2;
+ var xf1;
+ var xf2;
+ var edge1 = 0;
+ var flip = 0;
+ var k_relativeTol = 0.98;
+ var k_absoluteTol = 0.001;
+ var tMat;
+ if (separationB > k_relativeTol * separationA + k_absoluteTol) {
+ poly1 = polyB;
+ poly2 = polyA;
+ xf1 = xfB;
+ xf2 = xfA;
+ edge1 = edgeB;
+ manifold.m_type = b2Manifold.e_faceB;
+ flip = 1;
+ }
+ else {
+ poly1 = polyA;
+ poly2 = polyB;
+ xf1 = xfA;
+ xf2 = xfB;
+ edge1 = edgeA;
+ manifold.m_type = b2Manifold.e_faceA;
+ flip = 0;
+ }
+ var incidentEdge = b2Collision.s_incidentEdge;
+ b2Collision.FindIncidentEdge(incidentEdge, poly1, xf1, edge1, poly2, xf2);
+ var count1 = parseInt(poly1.m_vertexCount);
+ var vertices1 = poly1.m_vertices;
+ var local_v11 = vertices1[edge1];
+ var local_v12;
+ if (edge1 + 1 < count1) {
+ local_v12 = vertices1[parseInt(edge1 + 1)];
+ }
+ else {
+ local_v12 = vertices1[0];
+ }
+ var localTangent = b2Collision.s_localTangent;
+ localTangent.Set(local_v12.x - local_v11.x, local_v12.y - local_v11.y);
+ localTangent.Normalize();
+ var localNormal = b2Collision.s_localNormal;
+ localNormal.x = localTangent.y;
+ localNormal.y = (-localTangent.x);
+ var planePoint = b2Collision.s_planePoint;
+ planePoint.Set(0.5 * (local_v11.x + local_v12.x), 0.5 * (local_v11.y + local_v12.y));
+ var tangent = b2Collision.s_tangent;
+ tMat = xf1.R;
+ tangent.x = (tMat.col1.x * localTangent.x + tMat.col2.x * localTangent.y);
+ tangent.y = (tMat.col1.y * localTangent.x + tMat.col2.y * localTangent.y);
+ var tangent2 = b2Collision.s_tangent2;
+ tangent2.x = (-tangent.x);
+ tangent2.y = (-tangent.y);
+ var normal = b2Collision.s_normal;
+ normal.x = tangent.y;
+ normal.y = (-tangent.x);
+ var v11 = b2Collision.s_v11;
+ var v12 = b2Collision.s_v12;
+ v11.x = xf1.position.x + (tMat.col1.x * local_v11.x + tMat.col2.x * local_v11.y);
+ v11.y = xf1.position.y + (tMat.col1.y * local_v11.x + tMat.col2.y * local_v11.y);
+ v12.x = xf1.position.x + (tMat.col1.x * local_v12.x + tMat.col2.x * local_v12.y);
+ v12.y = xf1.position.y + (tMat.col1.y * local_v12.x + tMat.col2.y * local_v12.y);
+ var frontOffset = normal.x * v11.x + normal.y * v11.y;
+ var sideOffset1 = (-tangent.x * v11.x) - tangent.y * v11.y + totalRadius;
+ var sideOffset2 = tangent.x * v12.x + tangent.y * v12.y + totalRadius;
+ var clipPoints1 = b2Collision.s_clipPoints1;
+ var clipPoints2 = b2Collision.s_clipPoints2;
+ var np = 0;
+ np = b2Collision.ClipSegmentToLine(clipPoints1, incidentEdge, tangent2, sideOffset1);
+ if (np < 2) return;
+ np = b2Collision.ClipSegmentToLine(clipPoints2, clipPoints1, tangent, sideOffset2);
+ if (np < 2) return;
+ manifold.m_localPlaneNormal.SetV(localNormal);
+ manifold.m_localPoint.SetV(planePoint);
+ var pointCount = 0;
+ for (var i = 0; i < b2Settings.b2_maxManifoldPoints; ++i) {
+ cv = clipPoints2[i];
+ var separation = normal.x * cv.v.x + normal.y * cv.v.y - frontOffset;
+ if (separation <= totalRadius) {
+ var cp = manifold.m_points[pointCount];
+ tMat = xf2.R;
+ var tX = cv.v.x - xf2.position.x;
+ var tY = cv.v.y - xf2.position.y;
+ cp.m_localPoint.x = (tX * tMat.col1.x + tY * tMat.col1.y);
+ cp.m_localPoint.y = (tX * tMat.col2.x + tY * tMat.col2.y);
+ cp.m_id.Set(cv.id);
+ cp.m_id.features.flip = flip;
+ ++pointCount;
+ }
+ }
+ manifold.m_pointCount = pointCount;
+ }
+ b2Collision.CollideCircles = function (manifold, circle1, xf1, circle2, xf2) {
+ manifold.m_pointCount = 0;
+ var tMat;
+ var tVec;
+ tMat = xf1.R;
+ tVec = circle1.m_p;
+ var p1X = xf1.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ var p1Y = xf1.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ tMat = xf2.R;
+ tVec = circle2.m_p;
+ var p2X = xf2.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ var p2Y = xf2.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ var dX = p2X - p1X;
+ var dY = p2Y - p1Y;
+ var distSqr = dX * dX + dY * dY;
+ var radius = circle1.m_radius + circle2.m_radius;
+ if (distSqr > radius * radius) {
+ return;
+ }
+ manifold.m_type = b2Manifold.e_circles;
+ manifold.m_localPoint.SetV(circle1.m_p);
+ manifold.m_localPlaneNormal.SetZero();
+ manifold.m_pointCount = 1;
+ manifold.m_points[0].m_localPoint.SetV(circle2.m_p);
+ manifold.m_points[0].m_id.key = 0;
+ }
+ b2Collision.CollidePolygonAndCircle = function (manifold, polygon, xf1, circle, xf2) {
+ manifold.m_pointCount = 0;
+ var tPoint;
+ var dX = 0;
+ var dY = 0;
+ var positionX = 0;
+ var positionY = 0;
+ var tVec;
+ var tMat;
+ tMat = xf2.R;
+ tVec = circle.m_p;
+ var cX = xf2.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ var cY = xf2.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ dX = cX - xf1.position.x;
+ dY = cY - xf1.position.y;
+ tMat = xf1.R;
+ var cLocalX = (dX * tMat.col1.x + dY * tMat.col1.y);
+ var cLocalY = (dX * tMat.col2.x + dY * tMat.col2.y);
+ var dist = 0;
+ var normalIndex = 0;
+ var separation = (-Number.MAX_VALUE);
+ var radius = polygon.m_radius + circle.m_radius;
+ var vertexCount = parseInt(polygon.m_vertexCount);
+ var vertices = polygon.m_vertices;
+ var normals = polygon.m_normals;
+ for (var i = 0; i < vertexCount; ++i) {
+ tVec = vertices[i];
+ dX = cLocalX - tVec.x;
+ dY = cLocalY - tVec.y;
+ tVec = normals[i];
+ var s = tVec.x * dX + tVec.y * dY;
+ if (s > radius) {
+ return;
+ }
+ if (s > separation) {
+ separation = s;
+ normalIndex = i;
+ }
+ }
+ var vertIndex1 = parseInt(normalIndex);
+ var vertIndex2 = parseInt(vertIndex1 + 1 < vertexCount ? vertIndex1 + 1 : 0);
+ var v1 = vertices[vertIndex1];
+ var v2 = vertices[vertIndex2];
+ if (separation < Number.MIN_VALUE) {
+ manifold.m_pointCount = 1;
+ manifold.m_type = b2Manifold.e_faceA;
+ manifold.m_localPlaneNormal.SetV(normals[normalIndex]);
+ manifold.m_localPoint.x = 0.5 * (v1.x + v2.x);
+ manifold.m_localPoint.y = 0.5 * (v1.y + v2.y);
+ manifold.m_points[0].m_localPoint.SetV(circle.m_p);
+ manifold.m_points[0].m_id.key = 0;
+ return;
+ }
+ var u1 = (cLocalX - v1.x) * (v2.x - v1.x) + (cLocalY - v1.y) * (v2.y - v1.y);
+ var u2 = (cLocalX - v2.x) * (v1.x - v2.x) + (cLocalY - v2.y) * (v1.y - v2.y);
+ if (u1 <= 0.0) {
+ if ((cLocalX - v1.x) * (cLocalX - v1.x) + (cLocalY - v1.y) * (cLocalY - v1.y) > radius * radius) return;
+ manifold.m_pointCount = 1;
+ manifold.m_type = b2Manifold.e_faceA;
+ manifold.m_localPlaneNormal.x = cLocalX - v1.x;
+ manifold.m_localPlaneNormal.y = cLocalY - v1.y;
+ manifold.m_localPlaneNormal.Normalize();
+ manifold.m_localPoint.SetV(v1);
+ manifold.m_points[0].m_localPoint.SetV(circle.m_p);
+ manifold.m_points[0].m_id.key = 0;
+ }
+ else if (u2 <= 0) {
+ if ((cLocalX - v2.x) * (cLocalX - v2.x) + (cLocalY - v2.y) * (cLocalY - v2.y) > radius * radius) return;
+ manifold.m_pointCount = 1;
+ manifold.m_type = b2Manifold.e_faceA;
+ manifold.m_localPlaneNormal.x = cLocalX - v2.x;
+ manifold.m_localPlaneNormal.y = cLocalY - v2.y;
+ manifold.m_localPlaneNormal.Normalize();
+ manifold.m_localPoint.SetV(v2);
+ manifold.m_points[0].m_localPoint.SetV(circle.m_p);
+ manifold.m_points[0].m_id.key = 0;
+ }
+ else {
+ var faceCenterX = 0.5 * (v1.x + v2.x);
+ var faceCenterY = 0.5 * (v1.y + v2.y);
+ separation = (cLocalX - faceCenterX) * normals[vertIndex1].x + (cLocalY - faceCenterY) * normals[vertIndex1].y;
+ if (separation > radius) return;
+ manifold.m_pointCount = 1;
+ manifold.m_type = b2Manifold.e_faceA;
+ manifold.m_localPlaneNormal.x = normals[vertIndex1].x;
+ manifold.m_localPlaneNormal.y = normals[vertIndex1].y;
+ manifold.m_localPlaneNormal.Normalize();
+ manifold.m_localPoint.Set(faceCenterX, faceCenterY);
+ manifold.m_points[0].m_localPoint.SetV(circle.m_p);
+ manifold.m_points[0].m_id.key = 0;
+ }
+ }
+ b2Collision.TestOverlap = function (a, b) {
+ var t1 = b.lowerBound;
+ var t2 = a.upperBound;
+ var d1X = t1.x - t2.x;
+ var d1Y = t1.y - t2.y;
+ t1 = a.lowerBound;
+ t2 = b.upperBound;
+ var d2X = t1.x - t2.x;
+ var d2Y = t1.y - t2.y;
+ if (d1X > 0.0 || d1Y > 0.0) return false;
+ if (d2X > 0.0 || d2Y > 0.0) return false;
+ return true;
+ }
+ Box2D.postDefs.push(function () {
+ Box2D.Collision.b2Collision.s_incidentEdge = b2Collision.MakeClipPointVector();
+ Box2D.Collision.b2Collision.s_clipPoints1 = b2Collision.MakeClipPointVector();
+ Box2D.Collision.b2Collision.s_clipPoints2 = b2Collision.MakeClipPointVector();
+ Box2D.Collision.b2Collision.s_edgeAO = new Vector_a2j_Number(1);
+ Box2D.Collision.b2Collision.s_edgeBO = new Vector_a2j_Number(1);
+ Box2D.Collision.b2Collision.s_localTangent = new b2Vec2();
+ Box2D.Collision.b2Collision.s_localNormal = new b2Vec2();
+ Box2D.Collision.b2Collision.s_planePoint = new b2Vec2();
+ Box2D.Collision.b2Collision.s_normal = new b2Vec2();
+ Box2D.Collision.b2Collision.s_tangent = new b2Vec2();
+ Box2D.Collision.b2Collision.s_tangent2 = new b2Vec2();
+ Box2D.Collision.b2Collision.s_v11 = new b2Vec2();
+ Box2D.Collision.b2Collision.s_v12 = new b2Vec2();
+ Box2D.Collision.b2Collision.b2CollidePolyTempVec = new b2Vec2();
+ Box2D.Collision.b2Collision.b2_nullFeature = 0x000000ff;
+ });
+ b2ContactID.b2ContactID = function () {
+ this.features = new Features();
+ };
+ b2ContactID.prototype.b2ContactID = function () {
+ this.features._m_id = this;
+ }
+ b2ContactID.prototype.Set = function (id) {
+ this.key = id._key;
+ }
+ b2ContactID.prototype.Copy = function () {
+ var id = new b2ContactID();
+ id.key = this.key;
+ return id;
+ }
+ Object.defineProperty(b2ContactID.prototype, 'key', {
+ enumerable: false,
+ configurable: true,
+ get: function () {
+ return this._key;
+ }
+ });
+ Object.defineProperty(b2ContactID.prototype, 'key', {
+ enumerable: false,
+ configurable: true,
+ set: function (value) {
+ if (value === undefined) value = 0;
+ this._key = value;
+ this.features._referenceEdge = this._key & 0x000000ff;
+ this.features._incidentEdge = ((this._key & 0x0000ff00) >> 8) & 0x000000ff;
+ this.features._incidentVertex = ((this._key & 0x00ff0000) >> 16) & 0x000000ff;
+ this.features._flip = ((this._key & 0xff000000) >> 24) & 0x000000ff;
+ }
+ });
+ b2ContactPoint.b2ContactPoint = function () {
+ this.position = new b2Vec2();
+ this.velocity = new b2Vec2();
+ this.normal = new b2Vec2();
+ this.id = new b2ContactID();
+ };
+ b2Distance.b2Distance = function () {};
+ b2Distance.Distance = function (output, cache, input) {
+ ++b2Distance.b2_gjkCalls;
+ var proxyA = input.proxyA;
+ var proxyB = input.proxyB;
+ var transformA = input.transformA;
+ var transformB = input.transformB;
+ var simplex = b2Distance.s_simplex;
+ simplex.ReadCache(cache, proxyA, transformA, proxyB, transformB);
+ var vertices = simplex.m_vertices;
+ var k_maxIters = 20;
+ var saveA = b2Distance.s_saveA;
+ var saveB = b2Distance.s_saveB;
+ var saveCount = 0;
+ var closestPoint = simplex.GetClosestPoint();
+ var distanceSqr1 = closestPoint.LengthSquared();
+ var distanceSqr2 = distanceSqr1;
+ var i = 0;
+ var p;
+ var iter = 0;
+ while (iter < k_maxIters) {
+ saveCount = simplex.m_count;
+ for (i = 0;
+ i < saveCount; i++) {
+ saveA[i] = vertices[i].indexA;
+ saveB[i] = vertices[i].indexB;
+ }
+ switch (simplex.m_count) {
+ case 1:
+ break;
+ case 2:
+ simplex.Solve2();
+ break;
+ case 3:
+ simplex.Solve3();
+ break;
+ default:
+ b2Settings.b2Assert(false);
+ }
+ if (simplex.m_count == 3) {
+ break;
+ }
+ p = simplex.GetClosestPoint();
+ distanceSqr2 = p.LengthSquared();
+ if (distanceSqr2 > distanceSqr1) {}
+ distanceSqr1 = distanceSqr2;
+ var d = simplex.GetSearchDirection();
+ if (d.LengthSquared() < Number.MIN_VALUE * Number.MIN_VALUE) {
+ break;
+ }
+ var vertex = vertices[simplex.m_count];
+ vertex.indexA = proxyA.GetSupport(b2Math.MulTMV(transformA.R, d.GetNegative()));
+ vertex.wA = b2Math.MulX(transformA, proxyA.GetVertex(vertex.indexA));
+ vertex.indexB = proxyB.GetSupport(b2Math.MulTMV(transformB.R, d));
+ vertex.wB = b2Math.MulX(transformB, proxyB.GetVertex(vertex.indexB));
+ vertex.w = b2Math.SubtractVV(vertex.wB, vertex.wA);
+ ++iter;
+ ++b2Distance.b2_gjkIters;
+ var duplicate = false;
+ for (i = 0;
+ i < saveCount; i++) {
+ if (vertex.indexA == saveA[i] && vertex.indexB == saveB[i]) {
+ duplicate = true;
+ break;
+ }
+ }
+ if (duplicate) {
+ break;
+ }++simplex.m_count;
+ }
+ b2Distance.b2_gjkMaxIters = b2Math.Max(b2Distance.b2_gjkMaxIters, iter);
+ simplex.GetWitnessPoints(output.pointA, output.pointB);
+ output.distance = b2Math.SubtractVV(output.pointA, output.pointB).Length();
+ output.iterations = iter;
+ simplex.WriteCache(cache);
+ if (input.useRadii) {
+ var rA = proxyA.m_radius;
+ var rB = proxyB.m_radius;
+ if (output.distance > rA + rB && output.distance > Number.MIN_VALUE) {
+ output.distance -= rA + rB;
+ var normal = b2Math.SubtractVV(output.pointB, output.pointA);
+ normal.Normalize();
+ output.pointA.x += rA * normal.x;
+ output.pointA.y += rA * normal.y;
+ output.pointB.x -= rB * normal.x;
+ output.pointB.y -= rB * normal.y;
+ }
+ else {
+ p = new b2Vec2();
+ p.x = .5 * (output.pointA.x + output.pointB.x);
+ p.y = .5 * (output.pointA.y + output.pointB.y);
+ output.pointA.x = output.pointB.x = p.x;
+ output.pointA.y = output.pointB.y = p.y;
+ output.distance = 0.0;
+ }
+ }
+ }
+ Box2D.postDefs.push(function () {
+ Box2D.Collision.b2Distance.s_simplex = new b2Simplex();
+ Box2D.Collision.b2Distance.s_saveA = new Vector_a2j_Number(3);
+ Box2D.Collision.b2Distance.s_saveB = new Vector_a2j_Number(3);
+ });
+ b2DistanceInput.b2DistanceInput = function () {};
+ b2DistanceOutput.b2DistanceOutput = function () {
+ this.pointA = new b2Vec2();
+ this.pointB = new b2Vec2();
+ };
+ b2DistanceProxy.b2DistanceProxy = function () {};
+ b2DistanceProxy.prototype.Set = function (shape) {
+ switch (shape.GetType()) {
+ case b2Shape.e_circleShape:
+ {
+ var circle = (shape instanceof b2CircleShape ? shape : null);
+ this.m_vertices = new Vector(1, true);
+ this.m_vertices[0] = circle.m_p;
+ this.m_count = 1;
+ this.m_radius = circle.m_radius;
+ }
+ break;
+ case b2Shape.e_polygonShape:
+ {
+ var polygon = (shape instanceof b2PolygonShape ? shape : null);
+ this.m_vertices = polygon.m_vertices;
+ this.m_count = polygon.m_vertexCount;
+ this.m_radius = polygon.m_radius;
+ }
+ break;
+ default:
+ b2Settings.b2Assert(false);
+ }
+ }
+ b2DistanceProxy.prototype.GetSupport = function (d) {
+ var bestIndex = 0;
+ var bestValue = this.m_vertices[0].x * d.x + this.m_vertices[0].y * d.y;
+ for (var i = 1; i < this.m_count; ++i) {
+ var value = this.m_vertices[i].x * d.x + this.m_vertices[i].y * d.y;
+ if (value > bestValue) {
+ bestIndex = i;
+ bestValue = value;
+ }
+ }
+ return bestIndex;
+ }
+ b2DistanceProxy.prototype.GetSupportVertex = function (d) {
+ var bestIndex = 0;
+ var bestValue = this.m_vertices[0].x * d.x + this.m_vertices[0].y * d.y;
+ for (var i = 1; i < this.m_count; ++i) {
+ var value = this.m_vertices[i].x * d.x + this.m_vertices[i].y * d.y;
+ if (value > bestValue) {
+ bestIndex = i;
+ bestValue = value;
+ }
+ }
+ return this.m_vertices[bestIndex];
+ }
+ b2DistanceProxy.prototype.GetVertexCount = function () {
+ return this.m_count;
+ }
+ b2DistanceProxy.prototype.GetVertex = function (index) {
+ if (index === undefined) index = 0;
+ b2Settings.b2Assert(0 <= index && index < this.m_count);
+ return this.m_vertices[index];
+ }
+ b2DynamicTree.b2DynamicTree = function () {};
+ b2DynamicTree.prototype.b2DynamicTree = function () {
+ this.m_root = null;
+ this.m_freeList = null;
+ this.m_path = 0;
+ this.m_insertionCount = 0;
+ }
+ b2DynamicTree.prototype.CreateProxy = function (aabb, userData) {
+ var node = this.AllocateNode();
+ var extendX = b2Settings.b2_aabbExtension;
+ var extendY = b2Settings.b2_aabbExtension;
+ node.aabb.lowerBound.x = aabb.lowerBound.x - extendX;
+ node.aabb.lowerBound.y = aabb.lowerBound.y - extendY;
+ node.aabb.upperBound.x = aabb.upperBound.x + extendX;
+ node.aabb.upperBound.y = aabb.upperBound.y + extendY;
+ node.userData = userData;
+ this.InsertLeaf(node);
+ return node;
+ }
+ b2DynamicTree.prototype.DestroyProxy = function (proxy) {
+ this.RemoveLeaf(proxy);
+ this.FreeNode(proxy);
+ }
+ b2DynamicTree.prototype.MoveProxy = function (proxy, aabb, displacement) {
+ b2Settings.b2Assert(proxy.IsLeaf());
+ if (proxy.aabb.Contains(aabb)) {
+ return false;
+ }
+ this.RemoveLeaf(proxy);
+ var extendX = b2Settings.b2_aabbExtension + b2Settings.b2_aabbMultiplier * (displacement.x > 0 ? displacement.x : (-displacement.x));
+ var extendY = b2Settings.b2_aabbExtension + b2Settings.b2_aabbMultiplier * (displacement.y > 0 ? displacement.y : (-displacement.y));
+ proxy.aabb.lowerBound.x = aabb.lowerBound.x - extendX;
+ proxy.aabb.lowerBound.y = aabb.lowerBound.y - extendY;
+ proxy.aabb.upperBound.x = aabb.upperBound.x + extendX;
+ proxy.aabb.upperBound.y = aabb.upperBound.y + extendY;
+ this.InsertLeaf(proxy);
+ return true;
+ }
+ b2DynamicTree.prototype.Rebalance = function (iterations) {
+ if (iterations === undefined) iterations = 0;
+ if (this.m_root == null) return;
+ for (var i = 0; i < iterations; i++) {
+ var node = this.m_root;
+ var bit = 0;
+ while (node.IsLeaf() == false) {
+ node = (this.m_path >> bit) & 1 ? node.child2 : node.child1;
+ bit = (bit + 1) & 31;
+ }++this.m_path;
+ this.RemoveLeaf(node);
+ this.InsertLeaf(node);
+ }
+ }
+ b2DynamicTree.prototype.GetFatAABB = function (proxy) {
+ return proxy.aabb;
+ }
+ b2DynamicTree.prototype.GetUserData = function (proxy) {
+ return proxy.userData;
+ }
+ b2DynamicTree.prototype.Query = function (callback, aabb) {
+ if (this.m_root == null) return;
+ var stack = new Vector();
+ var count = 0;
+ stack[count++] = this.m_root;
+ while (count > 0) {
+ var node = stack[--count];
+ if (node.aabb.TestOverlap(aabb)) {
+ if (node.IsLeaf()) {
+ var proceed = callback(node);
+ if (!proceed) return;
+ }
+ else {
+ stack[count++] = node.child1;
+ stack[count++] = node.child2;
+ }
+ }
+ }
+ }
+ b2DynamicTree.prototype.RayCast = function (callback, input) {
+ if (this.m_root == null) return;
+ var p1 = input.p1;
+ var p2 = input.p2;
+ var r = b2Math.SubtractVV(p1, p2);
+ r.Normalize();
+ var v = b2Math.CrossFV(1.0, r);
+ var abs_v = b2Math.AbsV(v);
+ var maxFraction = input.maxFraction;
+ var segmentAABB = new b2AABB();
+ var tX = 0;
+ var tY = 0; {
+ tX = p1.x + maxFraction * (p2.x - p1.x);
+ tY = p1.y + maxFraction * (p2.y - p1.y);
+ segmentAABB.lowerBound.x = Math.min(p1.x, tX);
+ segmentAABB.lowerBound.y = Math.min(p1.y, tY);
+ segmentAABB.upperBound.x = Math.max(p1.x, tX);
+ segmentAABB.upperBound.y = Math.max(p1.y, tY);
+ }
+ var stack = new Vector();
+ var count = 0;
+ stack[count++] = this.m_root;
+ while (count > 0) {
+ var node = stack[--count];
+ if (node.aabb.TestOverlap(segmentAABB) == false) {
+ continue;
+ }
+ var c = node.aabb.GetCenter();
+ var h = node.aabb.GetExtents();
+ var separation = Math.abs(v.x * (p1.x - c.x) + v.y * (p1.y - c.y)) - abs_v.x * h.x - abs_v.y * h.y;
+ if (separation > 0.0) continue;
+ if (node.IsLeaf()) {
+ var subInput = new b2RayCastInput();
+ subInput.p1 = input.p1;
+ subInput.p2 = input.p2;
+ subInput.maxFraction = input.maxFraction;
+ maxFraction = callback(subInput, node);
+ if (maxFraction == 0.0) return;
+ if (maxFraction > 0.0) {
+ tX = p1.x + maxFraction * (p2.x - p1.x);
+ tY = p1.y + maxFraction * (p2.y - p1.y);
+ segmentAABB.lowerBound.x = Math.min(p1.x, tX);
+ segmentAABB.lowerBound.y = Math.min(p1.y, tY);
+ segmentAABB.upperBound.x = Math.max(p1.x, tX);
+ segmentAABB.upperBound.y = Math.max(p1.y, tY);
+ }
+ }
+ else {
+ stack[count++] = node.child1;
+ stack[count++] = node.child2;
+ }
+ }
+ }
+ b2DynamicTree.prototype.AllocateNode = function () {
+ if (this.m_freeList) {
+ var node = this.m_freeList;
+ this.m_freeList = node.parent;
+ node.parent = null;
+ node.child1 = null;
+ node.child2 = null;
+ return node;
+ }
+ return new b2DynamicTreeNode();
+ }
+ b2DynamicTree.prototype.FreeNode = function (node) {
+ node.parent = this.m_freeList;
+ this.m_freeList = node;
+ }
+ b2DynamicTree.prototype.InsertLeaf = function (leaf) {
+ ++this.m_insertionCount;
+ if (this.m_root == null) {
+ this.m_root = leaf;
+ this.m_root.parent = null;
+ return;
+ }
+ var center = leaf.aabb.GetCenter();
+ var sibling = this.m_root;
+ if (sibling.IsLeaf() == false) {
+ do {
+ var child1 = sibling.child1;
+ var child2 = sibling.child2;
+ var norm1 = Math.abs((child1.aabb.lowerBound.x + child1.aabb.upperBound.x) / 2 - center.x) + Math.abs((child1.aabb.lowerBound.y + child1.aabb.upperBound.y) / 2 - center.y);
+ var norm2 = Math.abs((child2.aabb.lowerBound.x + child2.aabb.upperBound.x) / 2 - center.x) + Math.abs((child2.aabb.lowerBound.y + child2.aabb.upperBound.y) / 2 - center.y);
+ if (norm1 < norm2) {
+ sibling = child1;
+ }
+ else {
+ sibling = child2;
+ }
+ }
+ while (sibling.IsLeaf() == false)
+ }
+ var node1 = sibling.parent;
+ var node2 = this.AllocateNode();
+ node2.parent = node1;
+ node2.userData = null;
+ node2.aabb.Combine(leaf.aabb, sibling.aabb);
+ if (node1) {
+ if (sibling.parent.child1 == sibling) {
+ node1.child1 = node2;
+ }
+ else {
+ node1.child2 = node2;
+ }
+ node2.child1 = sibling;
+ node2.child2 = leaf;
+ sibling.parent = node2;
+ leaf.parent = node2;
+ do {
+ if (node1.aabb.Contains(node2.aabb)) break;
+ node1.aabb.Combine(node1.child1.aabb, node1.child2.aabb);
+ node2 = node1;
+ node1 = node1.parent;
+ }
+ while (node1)
+ }
+ else {
+ node2.child1 = sibling;
+ node2.child2 = leaf;
+ sibling.parent = node2;
+ leaf.parent = node2;
+ this.m_root = node2;
+ }
+ }
+ b2DynamicTree.prototype.RemoveLeaf = function (leaf) {
+ if (leaf == this.m_root) {
+ this.m_root = null;
+ return;
+ }
+ var node2 = leaf.parent;
+ var node1 = node2.parent;
+ var sibling;
+ if (node2.child1 == leaf) {
+ sibling = node2.child2;
+ }
+ else {
+ sibling = node2.child1;
+ }
+ if (node1) {
+ if (node1.child1 == node2) {
+ node1.child1 = sibling;
+ }
+ else {
+ node1.child2 = sibling;
+ }
+ sibling.parent = node1;
+ this.FreeNode(node2);
+ while (node1) {
+ var oldAABB = node1.aabb;
+ node1.aabb = b2AABB.Combine(node1.child1.aabb, node1.child2.aabb);
+ if (oldAABB.Contains(node1.aabb)) break;
+ node1 = node1.parent;
+ }
+ }
+ else {
+ this.m_root = sibling;
+ sibling.parent = null;
+ this.FreeNode(node2);
+ }
+ }
+ b2DynamicTreeBroadPhase.b2DynamicTreeBroadPhase = function () {
+ this.m_tree = new b2DynamicTree();
+ this.m_moveBuffer = new Vector();
+ this.m_pairBuffer = new Vector();
+ this.m_pairCount = 0;
+ };
+ b2DynamicTreeBroadPhase.prototype.CreateProxy = function (aabb, userData) {
+ var proxy = this.m_tree.CreateProxy(aabb, userData);
+ ++this.m_proxyCount;
+ this.BufferMove(proxy);
+ return proxy;
+ }
+ b2DynamicTreeBroadPhase.prototype.DestroyProxy = function (proxy) {
+ this.UnBufferMove(proxy);
+ --this.m_proxyCount;
+ this.m_tree.DestroyProxy(proxy);
+ }
+ b2DynamicTreeBroadPhase.prototype.MoveProxy = function (proxy, aabb, displacement) {
+ var buffer = this.m_tree.MoveProxy(proxy, aabb, displacement);
+ if (buffer) {
+ this.BufferMove(proxy);
+ }
+ }
+ b2DynamicTreeBroadPhase.prototype.TestOverlap = function (proxyA, proxyB) {
+ var aabbA = this.m_tree.GetFatAABB(proxyA);
+ var aabbB = this.m_tree.GetFatAABB(proxyB);
+ return aabbA.TestOverlap(aabbB);
+ }
+ b2DynamicTreeBroadPhase.prototype.GetUserData = function (proxy) {
+ return this.m_tree.GetUserData(proxy);
+ }
+ b2DynamicTreeBroadPhase.prototype.GetFatAABB = function (proxy) {
+ return this.m_tree.GetFatAABB(proxy);
+ }
+ b2DynamicTreeBroadPhase.prototype.GetProxyCount = function () {
+ return this.m_proxyCount;
+ }
+ b2DynamicTreeBroadPhase.prototype.UpdatePairs = function (callback) {
+ var __this = this;
+ __this.m_pairCount = 0;
+ var i = 0,
+ queryProxy;
+ for (i = 0;
+ i < __this.m_moveBuffer.length; ++i) {
+ queryProxy = __this.m_moveBuffer[i];
+
+ function QueryCallback(proxy) {
+ if (proxy == queryProxy) return true;
+ if (__this.m_pairCount == __this.m_pairBuffer.length) {
+ __this.m_pairBuffer[__this.m_pairCount] = new b2DynamicTreePair();
+ }
+ var pair = __this.m_pairBuffer[__this.m_pairCount];
+ pair.proxyA = proxy < queryProxy ? proxy : queryProxy;
+ pair.proxyB = proxy >= queryProxy ? proxy : queryProxy;++__this.m_pairCount;
+ return true;
+ };
+ var fatAABB = __this.m_tree.GetFatAABB(queryProxy);
+ __this.m_tree.Query(QueryCallback, fatAABB);
+ }
+ __this.m_moveBuffer.length = 0;
+ for (var i = 0; i < __this.m_pairCount;) {
+ var primaryPair = __this.m_pairBuffer[i];
+ var userDataA = __this.m_tree.GetUserData(primaryPair.proxyA);
+ var userDataB = __this.m_tree.GetUserData(primaryPair.proxyB);
+ callback(userDataA, userDataB);
+ ++i;
+ while (i < __this.m_pairCount) {
+ var pair = __this.m_pairBuffer[i];
+ if (pair.proxyA != primaryPair.proxyA || pair.proxyB != primaryPair.proxyB) {
+ break;
+ }++i;
+ }
+ }
+ }
+ b2DynamicTreeBroadPhase.prototype.Query = function (callback, aabb) {
+ this.m_tree.Query(callback, aabb);
+ }
+ b2DynamicTreeBroadPhase.prototype.RayCast = function (callback, input) {
+ this.m_tree.RayCast(callback, input);
+ }
+ b2DynamicTreeBroadPhase.prototype.Validate = function () {}
+ b2DynamicTreeBroadPhase.prototype.Rebalance = function (iterations) {
+ if (iterations === undefined) iterations = 0;
+ this.m_tree.Rebalance(iterations);
+ }
+ b2DynamicTreeBroadPhase.prototype.BufferMove = function (proxy) {
+ this.m_moveBuffer[this.m_moveBuffer.length] = proxy;
+ }
+ b2DynamicTreeBroadPhase.prototype.UnBufferMove = function (proxy) {
+ var i = parseInt(this.m_moveBuffer.indexOf(proxy));
+ this.m_moveBuffer.splice(i, 1);
+ }
+ b2DynamicTreeBroadPhase.prototype.ComparePairs = function (pair1, pair2) {
+ return 0;
+ }
+ b2DynamicTreeBroadPhase.__implements = {};
+ b2DynamicTreeBroadPhase.__implements[IBroadPhase] = true;
+ b2DynamicTreeNode.b2DynamicTreeNode = function () {
+ this.aabb = new b2AABB();
+ };
+ b2DynamicTreeNode.prototype.IsLeaf = function () {
+ return this.child1 == null;
+ }
+ b2DynamicTreePair.b2DynamicTreePair = function () {};
+ b2Manifold.b2Manifold = function () {
+ this.m_pointCount = 0;
+ };
+ b2Manifold.prototype.b2Manifold = function () {
+ this.m_points = new Vector(b2Settings.b2_maxManifoldPoints);
+ for (var i = 0; i < b2Settings.b2_maxManifoldPoints; i++) {
+ this.m_points[i] = new b2ManifoldPoint();
+ }
+ this.m_localPlaneNormal = new b2Vec2();
+ this.m_localPoint = new b2Vec2();
+ }
+ b2Manifold.prototype.Reset = function () {
+ for (var i = 0; i < b2Settings.b2_maxManifoldPoints; i++) {
+ ((this.m_points[i] instanceof b2ManifoldPoint ? this.m_points[i] : null)).Reset();
+ }
+ this.m_localPlaneNormal.SetZero();
+ this.m_localPoint.SetZero();
+ this.m_type = 0;
+ this.m_pointCount = 0;
+ }
+ b2Manifold.prototype.Set = function (m) {
+ this.m_pointCount = m.m_pointCount;
+ for (var i = 0; i < b2Settings.b2_maxManifoldPoints; i++) {
+ ((this.m_points[i] instanceof b2ManifoldPoint ? this.m_points[i] : null)).Set(m.m_points[i]);
+ }
+ this.m_localPlaneNormal.SetV(m.m_localPlaneNormal);
+ this.m_localPoint.SetV(m.m_localPoint);
+ this.m_type = m.m_type;
+ }
+ b2Manifold.prototype.Copy = function () {
+ var copy = new b2Manifold();
+ copy.Set(this);
+ return copy;
+ }
+ Box2D.postDefs.push(function () {
+ Box2D.Collision.b2Manifold.e_circles = 0x0001;
+ Box2D.Collision.b2Manifold.e_faceA = 0x0002;
+ Box2D.Collision.b2Manifold.e_faceB = 0x0004;
+ });
+ b2ManifoldPoint.b2ManifoldPoint = function () {
+ this.m_localPoint = new b2Vec2();
+ this.m_id = new b2ContactID();
+ };
+ b2ManifoldPoint.prototype.b2ManifoldPoint = function () {
+ this.Reset();
+ }
+ b2ManifoldPoint.prototype.Reset = function () {
+ this.m_localPoint.SetZero();
+ this.m_normalImpulse = 0.0;
+ this.m_tangentImpulse = 0.0;
+ this.m_id.key = 0;
+ }
+ b2ManifoldPoint.prototype.Set = function (m) {
+ this.m_localPoint.SetV(m.m_localPoint);
+ this.m_normalImpulse = m.m_normalImpulse;
+ this.m_tangentImpulse = m.m_tangentImpulse;
+ this.m_id.Set(m.m_id);
+ }
+ b2Point.b2Point = function () {
+ this.p = new b2Vec2();
+ };
+ b2Point.prototype.Support = function (xf, vX, vY) {
+ if (vX === undefined) vX = 0;
+ if (vY === undefined) vY = 0;
+ return this.p;
+ }
+ b2Point.prototype.GetFirstVertex = function (xf) {
+ return this.p;
+ }
+ b2RayCastInput.b2RayCastInput = function () {
+ this.p1 = new b2Vec2();
+ this.p2 = new b2Vec2();
+ };
+ b2RayCastInput.prototype.b2RayCastInput = function (p1, p2, maxFraction) {
+ if (p1 === undefined) p1 = null;
+ if (p2 === undefined) p2 = null;
+ if (maxFraction === undefined) maxFraction = 1;
+ if (p1) this.p1.SetV(p1);
+ if (p2) this.p2.SetV(p2);
+ this.maxFraction = maxFraction;
+ }
+ b2RayCastOutput.b2RayCastOutput = function () {
+ this.normal = new b2Vec2();
+ };
+ b2Segment.b2Segment = function () {
+ this.p1 = new b2Vec2();
+ this.p2 = new b2Vec2();
+ };
+ b2Segment.prototype.TestSegment = function (lambda, normal, segment, maxLambda) {
+ if (maxLambda === undefined) maxLambda = 0;
+ var s = segment.p1;
+ var rX = segment.p2.x - s.x;
+ var rY = segment.p2.y - s.y;
+ var dX = this.p2.x - this.p1.x;
+ var dY = this.p2.y - this.p1.y;
+ var nX = dY;
+ var nY = (-dX);
+ var k_slop = 100.0 * Number.MIN_VALUE;
+ var denom = (-(rX * nX + rY * nY));
+ if (denom > k_slop) {
+ var bX = s.x - this.p1.x;
+ var bY = s.y - this.p1.y;
+ var a = (bX * nX + bY * nY);
+ if (0.0 <= a && a <= maxLambda * denom) {
+ var mu2 = (-rX * bY) + rY * bX;
+ if ((-k_slop * denom) <= mu2 && mu2 <= denom * (1.0 + k_slop)) {
+ a /= denom;
+ var nLen = Math.sqrt(nX * nX + nY * nY);
+ nX /= nLen;
+ nY /= nLen;
+ lambda[0] = a;
+ normal.Set(nX, nY);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ b2Segment.prototype.Extend = function (aabb) {
+ this.ExtendForward(aabb);
+ this.ExtendBackward(aabb);
+ }
+ b2Segment.prototype.ExtendForward = function (aabb) {
+ var dX = this.p2.x - this.p1.x;
+ var dY = this.p2.y - this.p1.y;
+ var lambda = Math.min(dX > 0 ? (aabb.upperBound.x - this.p1.x) / dX : dX < 0 ? (aabb.lowerBound.x - this.p1.x) / dX : Number.POSITIVE_INFINITY,
+ dY > 0 ? (aabb.upperBound.y - this.p1.y) / dY : dY < 0 ? (aabb.lowerBound.y - this.p1.y) / dY : Number.POSITIVE_INFINITY);
+ this.p2.x = this.p1.x + dX * lambda;
+ this.p2.y = this.p1.y + dY * lambda;
+ }
+ b2Segment.prototype.ExtendBackward = function (aabb) {
+ var dX = (-this.p2.x) + this.p1.x;
+ var dY = (-this.p2.y) + this.p1.y;
+ var lambda = Math.min(dX > 0 ? (aabb.upperBound.x - this.p2.x) / dX : dX < 0 ? (aabb.lowerBound.x - this.p2.x) / dX : Number.POSITIVE_INFINITY,
+ dY > 0 ? (aabb.upperBound.y - this.p2.y) / dY : dY < 0 ? (aabb.lowerBound.y - this.p2.y) / dY : Number.POSITIVE_INFINITY);
+ this.p1.x = this.p2.x + dX * lambda;
+ this.p1.y = this.p2.y + dY * lambda;
+ }
+ b2SeparationFunction.b2SeparationFunction = function () {
+ this.m_localPoint = new b2Vec2();
+ this.m_axis = new b2Vec2();
+ };
+ b2SeparationFunction.prototype.Initialize = function (cache, proxyA, transformA, proxyB, transformB) {
+ this.m_proxyA = proxyA;
+ this.m_proxyB = proxyB;
+ var count = parseInt(cache.count);
+ b2Settings.b2Assert(0 < count && count < 3);
+ var localPointA;
+ var localPointA1;
+ var localPointA2;
+ var localPointB;
+ var localPointB1;
+ var localPointB2;
+ var pointAX = 0;
+ var pointAY = 0;
+ var pointBX = 0;
+ var pointBY = 0;
+ var normalX = 0;
+ var normalY = 0;
+ var tMat;
+ var tVec;
+ var s = 0;
+ var sgn = 0;
+ if (count == 1) {
+ this.m_type = b2SeparationFunction.e_points;
+ localPointA = this.m_proxyA.GetVertex(cache.indexA[0]);
+ localPointB = this.m_proxyB.GetVertex(cache.indexB[0]);
+ tVec = localPointA;
+ tMat = transformA.R;
+ pointAX = transformA.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ pointAY = transformA.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ tVec = localPointB;
+ tMat = transformB.R;
+ pointBX = transformB.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ pointBY = transformB.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ this.m_axis.x = pointBX - pointAX;
+ this.m_axis.y = pointBY - pointAY;
+ this.m_axis.Normalize();
+ }
+ else if (cache.indexB[0] == cache.indexB[1]) {
+ this.m_type = b2SeparationFunction.e_faceA;
+ localPointA1 = this.m_proxyA.GetVertex(cache.indexA[0]);
+ localPointA2 = this.m_proxyA.GetVertex(cache.indexA[1]);
+ localPointB = this.m_proxyB.GetVertex(cache.indexB[0]);
+ this.m_localPoint.x = 0.5 * (localPointA1.x + localPointA2.x);
+ this.m_localPoint.y = 0.5 * (localPointA1.y + localPointA2.y);
+ this.m_axis = b2Math.CrossVF(b2Math.SubtractVV(localPointA2, localPointA1), 1.0);
+ this.m_axis.Normalize();
+ tVec = this.m_axis;
+ tMat = transformA.R;
+ normalX = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y;
+ normalY = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y;
+ tVec = this.m_localPoint;
+ tMat = transformA.R;
+ pointAX = transformA.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ pointAY = transformA.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ tVec = localPointB;
+ tMat = transformB.R;
+ pointBX = transformB.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ pointBY = transformB.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ s = (pointBX - pointAX) * normalX + (pointBY - pointAY) * normalY;
+ if (s < 0.0) {
+ this.m_axis.NegativeSelf();
+ }
+ }
+ else if (cache.indexA[0] == cache.indexA[0]) {
+ this.m_type = b2SeparationFunction.e_faceB;
+ localPointB1 = this.m_proxyB.GetVertex(cache.indexB[0]);
+ localPointB2 = this.m_proxyB.GetVertex(cache.indexB[1]);
+ localPointA = this.m_proxyA.GetVertex(cache.indexA[0]);
+ this.m_localPoint.x = 0.5 * (localPointB1.x + localPointB2.x);
+ this.m_localPoint.y = 0.5 * (localPointB1.y + localPointB2.y);
+ this.m_axis = b2Math.CrossVF(b2Math.SubtractVV(localPointB2, localPointB1), 1.0);
+ this.m_axis.Normalize();
+ tVec = this.m_axis;
+ tMat = transformB.R;
+ normalX = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y;
+ normalY = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y;
+ tVec = this.m_localPoint;
+ tMat = transformB.R;
+ pointBX = transformB.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ pointBY = transformB.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ tVec = localPointA;
+ tMat = transformA.R;
+ pointAX = transformA.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ pointAY = transformA.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ s = (pointAX - pointBX) * normalX + (pointAY - pointBY) * normalY;
+ if (s < 0.0) {
+ this.m_axis.NegativeSelf();
+ }
+ }
+ else {
+ localPointA1 = this.m_proxyA.GetVertex(cache.indexA[0]);
+ localPointA2 = this.m_proxyA.GetVertex(cache.indexA[1]);
+ localPointB1 = this.m_proxyB.GetVertex(cache.indexB[0]);
+ localPointB2 = this.m_proxyB.GetVertex(cache.indexB[1]);
+ var pA = b2Math.MulX(transformA, localPointA);
+ var dA = b2Math.MulMV(transformA.R, b2Math.SubtractVV(localPointA2, localPointA1));
+ var pB = b2Math.MulX(transformB, localPointB);
+ var dB = b2Math.MulMV(transformB.R, b2Math.SubtractVV(localPointB2, localPointB1));
+ var a = dA.x * dA.x + dA.y * dA.y;
+ var e = dB.x * dB.x + dB.y * dB.y;
+ var r = b2Math.SubtractVV(dB, dA);
+ var c = dA.x * r.x + dA.y * r.y;
+ var f = dB.x * r.x + dB.y * r.y;
+ var b = dA.x * dB.x + dA.y * dB.y;
+ var denom = a * e - b * b;
+ s = 0.0;
+ if (denom != 0.0) {
+ s = b2Math.Clamp((b * f - c * e) / denom, 0.0, 1.0);
+ }
+ var t = (b * s + f) / e;
+ if (t < 0.0) {
+ t = 0.0;
+ s = b2Math.Clamp((b - c) / a, 0.0, 1.0);
+ }
+ localPointA = new b2Vec2();
+ localPointA.x = localPointA1.x + s * (localPointA2.x - localPointA1.x);
+ localPointA.y = localPointA1.y + s * (localPointA2.y - localPointA1.y);
+ localPointB = new b2Vec2();
+ localPointB.x = localPointB1.x + s * (localPointB2.x - localPointB1.x);
+ localPointB.y = localPointB1.y + s * (localPointB2.y - localPointB1.y);
+ if (s == 0.0 || s == 1.0) {
+ this.m_type = b2SeparationFunction.e_faceB;
+ this.m_axis = b2Math.CrossVF(b2Math.SubtractVV(localPointB2, localPointB1), 1.0);
+ this.m_axis.Normalize();
+ this.m_localPoint = localPointB;
+ tVec = this.m_axis;
+ tMat = transformB.R;
+ normalX = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y;
+ normalY = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y;
+ tVec = this.m_localPoint;
+ tMat = transformB.R;
+ pointBX = transformB.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ pointBY = transformB.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ tVec = localPointA;
+ tMat = transformA.R;
+ pointAX = transformA.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ pointAY = transformA.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ sgn = (pointAX - pointBX) * normalX + (pointAY - pointBY) * normalY;
+ if (s < 0.0) {
+ this.m_axis.NegativeSelf();
+ }
+ }
+ else {
+ this.m_type = b2SeparationFunction.e_faceA;
+ this.m_axis = b2Math.CrossVF(b2Math.SubtractVV(localPointA2, localPointA1), 1.0);
+ this.m_localPoint = localPointA;
+ tVec = this.m_axis;
+ tMat = transformA.R;
+ normalX = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y;
+ normalY = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y;
+ tVec = this.m_localPoint;
+ tMat = transformA.R;
+ pointAX = transformA.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ pointAY = transformA.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ tVec = localPointB;
+ tMat = transformB.R;
+ pointBX = transformB.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ pointBY = transformB.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ sgn = (pointBX - pointAX) * normalX + (pointBY - pointAY) * normalY;
+ if (s < 0.0) {
+ this.m_axis.NegativeSelf();
+ }
+ }
+ }
+ }
+ b2SeparationFunction.prototype.Evaluate = function (transformA, transformB) {
+ var axisA;
+ var axisB;
+ var localPointA;
+ var localPointB;
+ var pointA;
+ var pointB;
+ var seperation = 0;
+ var normal;
+ switch (this.m_type) {
+ case b2SeparationFunction.e_points:
+ {
+ axisA = b2Math.MulTMV(transformA.R, this.m_axis);
+ axisB = b2Math.MulTMV(transformB.R, this.m_axis.GetNegative());
+ localPointA = this.m_proxyA.GetSupportVertex(axisA);
+ localPointB = this.m_proxyB.GetSupportVertex(axisB);
+ pointA = b2Math.MulX(transformA, localPointA);
+ pointB = b2Math.MulX(transformB, localPointB);
+ seperation = (pointB.x - pointA.x) * this.m_axis.x + (pointB.y - pointA.y) * this.m_axis.y;
+ return seperation;
+ }
+ case b2SeparationFunction.e_faceA:
+ {
+ normal = b2Math.MulMV(transformA.R, this.m_axis);
+ pointA = b2Math.MulX(transformA, this.m_localPoint);
+ axisB = b2Math.MulTMV(transformB.R, normal.GetNegative());
+ localPointB = this.m_proxyB.GetSupportVertex(axisB);
+ pointB = b2Math.MulX(transformB, localPointB);
+ seperation = (pointB.x - pointA.x) * normal.x + (pointB.y - pointA.y) * normal.y;
+ return seperation;
+ }
+ case b2SeparationFunction.e_faceB:
+ {
+ normal = b2Math.MulMV(transformB.R, this.m_axis);
+ pointB = b2Math.MulX(transformB, this.m_localPoint);
+ axisA = b2Math.MulTMV(transformA.R, normal.GetNegative());
+ localPointA = this.m_proxyA.GetSupportVertex(axisA);
+ pointA = b2Math.MulX(transformA, localPointA);
+ seperation = (pointA.x - pointB.x) * normal.x + (pointA.y - pointB.y) * normal.y;
+ return seperation;
+ }
+ default:
+ b2Settings.b2Assert(false);
+ return 0.0;
+ }
+ }
+ Box2D.postDefs.push(function () {
+ Box2D.Collision.b2SeparationFunction.e_points = 0x01;
+ Box2D.Collision.b2SeparationFunction.e_faceA = 0x02;
+ Box2D.Collision.b2SeparationFunction.e_faceB = 0x04;
+ });
+ b2Simplex.b2Simplex = function () {
+ this.m_v1 = new b2SimplexVertex();
+ this.m_v2 = new b2SimplexVertex();
+ this.m_v3 = new b2SimplexVertex();
+ this.m_vertices = new Vector(3);
+ };
+ b2Simplex.prototype.b2Simplex = function () {
+ this.m_vertices[0] = this.m_v1;
+ this.m_vertices[1] = this.m_v2;
+ this.m_vertices[2] = this.m_v3;
+ }
+ b2Simplex.prototype.ReadCache = function (cache, proxyA, transformA, proxyB, transformB) {
+ b2Settings.b2Assert(0 <= cache.count && cache.count <= 3);
+ var wALocal;
+ var wBLocal;
+ this.m_count = cache.count;
+ var vertices = this.m_vertices;
+ for (var i = 0; i < this.m_count; i++) {
+ var v = vertices[i];
+ v.indexA = cache.indexA[i];
+ v.indexB = cache.indexB[i];
+ wALocal = proxyA.GetVertex(v.indexA);
+ wBLocal = proxyB.GetVertex(v.indexB);
+ v.wA = b2Math.MulX(transformA, wALocal);
+ v.wB = b2Math.MulX(transformB, wBLocal);
+ v.w = b2Math.SubtractVV(v.wB, v.wA);
+ v.a = 0;
+ }
+ if (this.m_count > 1) {
+ var metric1 = cache.metric;
+ var metric2 = this.GetMetric();
+ if (metric2 < .5 * metric1 || 2.0 * metric1 < metric2 || metric2 < Number.MIN_VALUE) {
+ this.m_count = 0;
+ }
+ }
+ if (this.m_count == 0) {
+ v = vertices[0];
+ v.indexA = 0;
+ v.indexB = 0;
+ wALocal = proxyA.GetVertex(0);
+ wBLocal = proxyB.GetVertex(0);
+ v.wA = b2Math.MulX(transformA, wALocal);
+ v.wB = b2Math.MulX(transformB, wBLocal);
+ v.w = b2Math.SubtractVV(v.wB, v.wA);
+ this.m_count = 1;
+ }
+ }
+ b2Simplex.prototype.WriteCache = function (cache) {
+ cache.metric = this.GetMetric();
+ cache.count = Box2D.parseUInt(this.m_count);
+ var vertices = this.m_vertices;
+ for (var i = 0; i < this.m_count; i++) {
+ cache.indexA[i] = Box2D.parseUInt(vertices[i].indexA);
+ cache.indexB[i] = Box2D.parseUInt(vertices[i].indexB);
+ }
+ }
+ b2Simplex.prototype.GetSearchDirection = function () {
+ switch (this.m_count) {
+ case 1:
+ return this.m_v1.w.GetNegative();
+ case 2:
+ {
+ var e12 = b2Math.SubtractVV(this.m_v2.w, this.m_v1.w);
+ var sgn = b2Math.CrossVV(e12, this.m_v1.w.GetNegative());
+ if (sgn > 0.0) {
+ return b2Math.CrossFV(1.0, e12);
+ }
+ else {
+ return b2Math.CrossVF(e12, 1.0);
+ }
+ }
+ default:
+ b2Settings.b2Assert(false);
+ return new b2Vec2();
+ }
+ }
+ b2Simplex.prototype.GetClosestPoint = function () {
+ switch (this.m_count) {
+ case 0:
+ b2Settings.b2Assert(false);
+ return new b2Vec2();
+ case 1:
+ return this.m_v1.w;
+ case 2:
+ return new b2Vec2(this.m_v1.a * this.m_v1.w.x + this.m_v2.a * this.m_v2.w.x, this.m_v1.a * this.m_v1.w.y + this.m_v2.a * this.m_v2.w.y);
+ default:
+ b2Settings.b2Assert(false);
+ return new b2Vec2();
+ }
+ }
+ b2Simplex.prototype.GetWitnessPoints = function (pA, pB) {
+ switch (this.m_count) {
+ case 0:
+ b2Settings.b2Assert(false);
+ break;
+ case 1:
+ pA.SetV(this.m_v1.wA);
+ pB.SetV(this.m_v1.wB);
+ break;
+ case 2:
+ pA.x = this.m_v1.a * this.m_v1.wA.x + this.m_v2.a * this.m_v2.wA.x;
+ pA.y = this.m_v1.a * this.m_v1.wA.y + this.m_v2.a * this.m_v2.wA.y;
+ pB.x = this.m_v1.a * this.m_v1.wB.x + this.m_v2.a * this.m_v2.wB.x;
+ pB.y = this.m_v1.a * this.m_v1.wB.y + this.m_v2.a * this.m_v2.wB.y;
+ break;
+ case 3:
+ pB.x = pA.x = this.m_v1.a * this.m_v1.wA.x + this.m_v2.a * this.m_v2.wA.x + this.m_v3.a * this.m_v3.wA.x;
+ pB.y = pA.y = this.m_v1.a * this.m_v1.wA.y + this.m_v2.a * this.m_v2.wA.y + this.m_v3.a * this.m_v3.wA.y;
+ break;
+ default:
+ b2Settings.b2Assert(false);
+ break;
+ }
+ }
+ b2Simplex.prototype.GetMetric = function () {
+ switch (this.m_count) {
+ case 0:
+ b2Settings.b2Assert(false);
+ return 0.0;
+ case 1:
+ return 0.0;
+ case 2:
+ return b2Math.SubtractVV(this.m_v1.w, this.m_v2.w).Length();
+ case 3:
+ return b2Math.CrossVV(b2Math.SubtractVV(this.m_v2.w, this.m_v1.w), b2Math.SubtractVV(this.m_v3.w, this.m_v1.w));
+ default:
+ b2Settings.b2Assert(false);
+ return 0.0;
+ }
+ }
+ b2Simplex.prototype.Solve2 = function () {
+ var w1 = this.m_v1.w;
+ var w2 = this.m_v2.w;
+ var e12 = b2Math.SubtractVV(w2, w1);
+ var d12_2 = (-(w1.x * e12.x + w1.y * e12.y));
+ if (d12_2 <= 0.0) {
+ this.m_v1.a = 1.0;
+ this.m_count = 1;
+ return;
+ }
+ var d12_1 = (w2.x * e12.x + w2.y * e12.y);
+ if (d12_1 <= 0.0) {
+ this.m_v2.a = 1.0;
+ this.m_count = 1;
+ this.m_v1.Set(this.m_v2);
+ return;
+ }
+ var inv_d12 = 1.0 / (d12_1 + d12_2);
+ this.m_v1.a = d12_1 * inv_d12;
+ this.m_v2.a = d12_2 * inv_d12;
+ this.m_count = 2;
+ }
+ b2Simplex.prototype.Solve3 = function () {
+ var w1 = this.m_v1.w;
+ var w2 = this.m_v2.w;
+ var w3 = this.m_v3.w;
+ var e12 = b2Math.SubtractVV(w2, w1);
+ var w1e12 = b2Math.Dot(w1, e12);
+ var w2e12 = b2Math.Dot(w2, e12);
+ var d12_1 = w2e12;
+ var d12_2 = (-w1e12);
+ var e13 = b2Math.SubtractVV(w3, w1);
+ var w1e13 = b2Math.Dot(w1, e13);
+ var w3e13 = b2Math.Dot(w3, e13);
+ var d13_1 = w3e13;
+ var d13_2 = (-w1e13);
+ var e23 = b2Math.SubtractVV(w3, w2);
+ var w2e23 = b2Math.Dot(w2, e23);
+ var w3e23 = b2Math.Dot(w3, e23);
+ var d23_1 = w3e23;
+ var d23_2 = (-w2e23);
+ var n123 = b2Math.CrossVV(e12, e13);
+ var d123_1 = n123 * b2Math.CrossVV(w2, w3);
+ var d123_2 = n123 * b2Math.CrossVV(w3, w1);
+ var d123_3 = n123 * b2Math.CrossVV(w1, w2);
+ if (d12_2 <= 0.0 && d13_2 <= 0.0) {
+ this.m_v1.a = 1.0;
+ this.m_count = 1;
+ return;
+ }
+ if (d12_1 > 0.0 && d12_2 > 0.0 && d123_3 <= 0.0) {
+ var inv_d12 = 1.0 / (d12_1 + d12_2);
+ this.m_v1.a = d12_1 * inv_d12;
+ this.m_v2.a = d12_2 * inv_d12;
+ this.m_count = 2;
+ return;
+ }
+ if (d13_1 > 0.0 && d13_2 > 0.0 && d123_2 <= 0.0) {
+ var inv_d13 = 1.0 / (d13_1 + d13_2);
+ this.m_v1.a = d13_1 * inv_d13;
+ this.m_v3.a = d13_2 * inv_d13;
+ this.m_count = 2;
+ this.m_v2.Set(this.m_v3);
+ return;
+ }
+ if (d12_1 <= 0.0 && d23_2 <= 0.0) {
+ this.m_v2.a = 1.0;
+ this.m_count = 1;
+ this.m_v1.Set(this.m_v2);
+ return;
+ }
+ if (d13_1 <= 0.0 && d23_1 <= 0.0) {
+ this.m_v3.a = 1.0;
+ this.m_count = 1;
+ this.m_v1.Set(this.m_v3);
+ return;
+ }
+ if (d23_1 > 0.0 && d23_2 > 0.0 && d123_1 <= 0.0) {
+ var inv_d23 = 1.0 / (d23_1 + d23_2);
+ this.m_v2.a = d23_1 * inv_d23;
+ this.m_v3.a = d23_2 * inv_d23;
+ this.m_count = 2;
+ this.m_v1.Set(this.m_v3);
+ return;
+ }
+ var inv_d123 = 1.0 / (d123_1 + d123_2 + d123_3);
+ this.m_v1.a = d123_1 * inv_d123;
+ this.m_v2.a = d123_2 * inv_d123;
+ this.m_v3.a = d123_3 * inv_d123;
+ this.m_count = 3;
+ }
+ b2SimplexCache.b2SimplexCache = function () {
+ this.indexA = new Vector_a2j_Number(3);
+ this.indexB = new Vector_a2j_Number(3);
+ };
+ b2SimplexVertex.b2SimplexVertex = function () {};
+ b2SimplexVertex.prototype.Set = function (other) {
+ this.wA.SetV(other.wA);
+ this.wB.SetV(other.wB);
+ this.w.SetV(other.w);
+ this.a = other.a;
+ this.indexA = other.indexA;
+ this.indexB = other.indexB;
+ }
+ b2TimeOfImpact.b2TimeOfImpact = function () {};
+ b2TimeOfImpact.TimeOfImpact = function (input) {
+ ++b2TimeOfImpact.b2_toiCalls;
+ var proxyA = input.proxyA;
+ var proxyB = input.proxyB;
+ var sweepA = input.sweepA;
+ var sweepB = input.sweepB;
+ b2Settings.b2Assert(sweepA.t0 == sweepB.t0);
+ b2Settings.b2Assert(1.0 - sweepA.t0 > Number.MIN_VALUE);
+ var radius = proxyA.m_radius + proxyB.m_radius;
+ var tolerance = input.tolerance;
+ var alpha = 0.0;
+ var k_maxIterations = 1000;
+ var iter = 0;
+ var target = 0.0;
+ b2TimeOfImpact.s_cache.count = 0;
+ b2TimeOfImpact.s_distanceInput.useRadii = false;
+ for (;;) {
+ sweepA.GetTransform(b2TimeOfImpact.s_xfA, alpha);
+ sweepB.GetTransform(b2TimeOfImpact.s_xfB, alpha);
+ b2TimeOfImpact.s_distanceInput.proxyA = proxyA;
+ b2TimeOfImpact.s_distanceInput.proxyB = proxyB;
+ b2TimeOfImpact.s_distanceInput.transformA = b2TimeOfImpact.s_xfA;
+ b2TimeOfImpact.s_distanceInput.transformB = b2TimeOfImpact.s_xfB;
+ b2Distance.Distance(b2TimeOfImpact.s_distanceOutput, b2TimeOfImpact.s_cache, b2TimeOfImpact.s_distanceInput);
+ if (b2TimeOfImpact.s_distanceOutput.distance <= 0.0) {
+ alpha = 1.0;
+ break;
+ }
+ b2TimeOfImpact.s_fcn.Initialize(b2TimeOfImpact.s_cache, proxyA, b2TimeOfImpact.s_xfA, proxyB, b2TimeOfImpact.s_xfB);
+ var separation = b2TimeOfImpact.s_fcn.Evaluate(b2TimeOfImpact.s_xfA, b2TimeOfImpact.s_xfB);
+ if (separation <= 0.0) {
+ alpha = 1.0;
+ break;
+ }
+ if (iter == 0) {
+ if (separation > radius) {
+ target = b2Math.Max(radius - tolerance, 0.75 * radius);
+ }
+ else {
+ target = b2Math.Max(separation - tolerance, 0.02 * radius);
+ }
+ }
+ if (separation - target < 0.5 * tolerance) {
+ if (iter == 0) {
+ alpha = 1.0;
+ break;
+ }
+ break;
+ }
+ var newAlpha = alpha; {
+ var x1 = alpha;
+ var x2 = 1.0;
+ var f1 = separation;
+ sweepA.GetTransform(b2TimeOfImpact.s_xfA, x2);
+ sweepB.GetTransform(b2TimeOfImpact.s_xfB, x2);
+ var f2 = b2TimeOfImpact.s_fcn.Evaluate(b2TimeOfImpact.s_xfA, b2TimeOfImpact.s_xfB);
+ if (f2 >= target) {
+ alpha = 1.0;
+ break;
+ }
+ var rootIterCount = 0;
+ for (;;) {
+ var x = 0;
+ if (rootIterCount & 1) {
+ x = x1 + (target - f1) * (x2 - x1) / (f2 - f1);
+ }
+ else {
+ x = 0.5 * (x1 + x2);
+ }
+ sweepA.GetTransform(b2TimeOfImpact.s_xfA, x);
+ sweepB.GetTransform(b2TimeOfImpact.s_xfB, x);
+ var f = b2TimeOfImpact.s_fcn.Evaluate(b2TimeOfImpact.s_xfA, b2TimeOfImpact.s_xfB);
+ if (b2Math.Abs(f - target) < 0.025 * tolerance) {
+ newAlpha = x;
+ break;
+ }
+ if (f > target) {
+ x1 = x;
+ f1 = f;
+ }
+ else {
+ x2 = x;
+ f2 = f;
+ }++rootIterCount;
+ ++b2TimeOfImpact.b2_toiRootIters;
+ if (rootIterCount == 50) {
+ break;
+ }
+ }
+ b2TimeOfImpact.b2_toiMaxRootIters = b2Math.Max(b2TimeOfImpact.b2_toiMaxRootIters, rootIterCount);
+ }
+ if (newAlpha < (1.0 + 100.0 * Number.MIN_VALUE) * alpha) {
+ break;
+ }
+ alpha = newAlpha;
+ iter++;
+ ++b2TimeOfImpact.b2_toiIters;
+ if (iter == k_maxIterations) {
+ break;
+ }
+ }
+ b2TimeOfImpact.b2_toiMaxIters = b2Math.Max(b2TimeOfImpact.b2_toiMaxIters, iter);
+ return alpha;
+ }
+ Box2D.postDefs.push(function () {
+ Box2D.Collision.b2TimeOfImpact.b2_toiCalls = 0;
+ Box2D.Collision.b2TimeOfImpact.b2_toiIters = 0;
+ Box2D.Collision.b2TimeOfImpact.b2_toiMaxIters = 0;
+ Box2D.Collision.b2TimeOfImpact.b2_toiRootIters = 0;
+ Box2D.Collision.b2TimeOfImpact.b2_toiMaxRootIters = 0;
+ Box2D.Collision.b2TimeOfImpact.s_cache = new b2SimplexCache();
+ Box2D.Collision.b2TimeOfImpact.s_distanceInput = new b2DistanceInput();
+ Box2D.Collision.b2TimeOfImpact.s_xfA = new b2Transform();
+ Box2D.Collision.b2TimeOfImpact.s_xfB = new b2Transform();
+ Box2D.Collision.b2TimeOfImpact.s_fcn = new b2SeparationFunction();
+ Box2D.Collision.b2TimeOfImpact.s_distanceOutput = new b2DistanceOutput();
+ });
+ b2TOIInput.b2TOIInput = function () {
+ this.proxyA = new b2DistanceProxy();
+ this.proxyB = new b2DistanceProxy();
+ this.sweepA = new b2Sweep();
+ this.sweepB = new b2Sweep();
+ };
+ b2WorldManifold.b2WorldManifold = function () {
+ this.m_normal = new b2Vec2();
+ };
+ b2WorldManifold.prototype.b2WorldManifold = function () {
+ this.m_points = new Vector(b2Settings.b2_maxManifoldPoints);
+ for (var i = 0; i < b2Settings.b2_maxManifoldPoints; i++) {
+ this.m_points[i] = new b2Vec2();
+ }
+ }
+ b2WorldManifold.prototype.Initialize = function (manifold, xfA, radiusA, xfB, radiusB) {
+ if (radiusA === undefined) radiusA = 0;
+ if (radiusB === undefined) radiusB = 0;
+ if (manifold.m_pointCount == 0) {
+ return;
+ }
+ var i = 0;
+ var tVec;
+ var tMat;
+ var normalX = 0;
+ var normalY = 0;
+ var planePointX = 0;
+ var planePointY = 0;
+ var clipPointX = 0;
+ var clipPointY = 0;
+ switch (manifold.m_type) {
+ case b2Manifold.e_circles:
+ {
+ tMat = xfA.R;
+ tVec = manifold.m_localPoint;
+ var pointAX = xfA.position.x + tMat.col1.x * tVec.x + tMat.col2.x * tVec.y;
+ var pointAY = xfA.position.y + tMat.col1.y * tVec.x + tMat.col2.y * tVec.y;
+ tMat = xfB.R;
+ tVec = manifold.m_points[0].m_localPoint;
+ var pointBX = xfB.position.x + tMat.col1.x * tVec.x + tMat.col2.x * tVec.y;
+ var pointBY = xfB.position.y + tMat.col1.y * tVec.x + tMat.col2.y * tVec.y;
+ var dX = pointBX - pointAX;
+ var dY = pointBY - pointAY;
+ var d2 = dX * dX + dY * dY;
+ if (d2 > Number.MIN_VALUE * Number.MIN_VALUE) {
+ var d = Math.sqrt(d2);
+ this.m_normal.x = dX / d;
+ this.m_normal.y = dY / d;
+ }
+ else {
+ this.m_normal.x = 1;
+ this.m_normal.y = 0;
+ }
+ var cAX = pointAX + radiusA * this.m_normal.x;
+ var cAY = pointAY + radiusA * this.m_normal.y;
+ var cBX = pointBX - radiusB * this.m_normal.x;
+ var cBY = pointBY - radiusB * this.m_normal.y;
+ this.m_points[0].x = 0.5 * (cAX + cBX);
+ this.m_points[0].y = 0.5 * (cAY + cBY);
+ }
+ break;
+ case b2Manifold.e_faceA:
+ {
+ tMat = xfA.R;
+ tVec = manifold.m_localPlaneNormal;
+ normalX = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y;
+ normalY = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y;
+ tMat = xfA.R;
+ tVec = manifold.m_localPoint;
+ planePointX = xfA.position.x + tMat.col1.x * tVec.x + tMat.col2.x * tVec.y;
+ planePointY = xfA.position.y + tMat.col1.y * tVec.x + tMat.col2.y * tVec.y;
+ this.m_normal.x = normalX;
+ this.m_normal.y = normalY;
+ for (i = 0;
+ i < manifold.m_pointCount; i++) {
+ tMat = xfB.R;
+ tVec = manifold.m_points[i].m_localPoint;
+ clipPointX = xfB.position.x + tMat.col1.x * tVec.x + tMat.col2.x * tVec.y;
+ clipPointY = xfB.position.y + tMat.col1.y * tVec.x + tMat.col2.y * tVec.y;
+ this.m_points[i].x = clipPointX + 0.5 * (radiusA - (clipPointX - planePointX) * normalX - (clipPointY - planePointY) * normalY - radiusB) * normalX;
+ this.m_points[i].y = clipPointY + 0.5 * (radiusA - (clipPointX - planePointX) * normalX - (clipPointY - planePointY) * normalY - radiusB) * normalY;
+ }
+ }
+ break;
+ case b2Manifold.e_faceB:
+ {
+ tMat = xfB.R;
+ tVec = manifold.m_localPlaneNormal;
+ normalX = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y;
+ normalY = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y;
+ tMat = xfB.R;
+ tVec = manifold.m_localPoint;
+ planePointX = xfB.position.x + tMat.col1.x * tVec.x + tMat.col2.x * tVec.y;
+ planePointY = xfB.position.y + tMat.col1.y * tVec.x + tMat.col2.y * tVec.y;
+ this.m_normal.x = (-normalX);
+ this.m_normal.y = (-normalY);
+ for (i = 0;
+ i < manifold.m_pointCount; i++) {
+ tMat = xfA.R;
+ tVec = manifold.m_points[i].m_localPoint;
+ clipPointX = xfA.position.x + tMat.col1.x * tVec.x + tMat.col2.x * tVec.y;
+ clipPointY = xfA.position.y + tMat.col1.y * tVec.x + tMat.col2.y * tVec.y;
+ this.m_points[i].x = clipPointX + 0.5 * (radiusB - (clipPointX - planePointX) * normalX - (clipPointY - planePointY) * normalY - radiusA) * normalX;
+ this.m_points[i].y = clipPointY + 0.5 * (radiusB - (clipPointX - planePointX) * normalX - (clipPointY - planePointY) * normalY - radiusA) * normalY;
+ }
+ }
+ break;
+ }
+ }
+ ClipVertex.ClipVertex = function () {
+ this.v = new b2Vec2();
+ this.id = new b2ContactID();
+ };
+ ClipVertex.prototype.Set = function (other) {
+ this.v.SetV(other.v);
+ this.id.Set(other.id);
+ }
+ Features.Features = function () {};
+ Object.defineProperty(Features.prototype, 'referenceEdge', {
+ enumerable: false,
+ configurable: true,
+ get: function () {
+ return this._referenceEdge;
+ }
+ });
+ Object.defineProperty(Features.prototype, 'referenceEdge', {
+ enumerable: false,
+ configurable: true,
+ set: function (value) {
+ if (value === undefined) value = 0;
+ this._referenceEdge = value;
+ this._m_id._key = (this._m_id._key & 0xffffff00) | (this._referenceEdge & 0x000000ff);
+ }
+ });
+ Object.defineProperty(Features.prototype, 'incidentEdge', {
+ enumerable: false,
+ configurable: true,
+ get: function () {
+ return this._incidentEdge;
+ }
+ });
+ Object.defineProperty(Features.prototype, 'incidentEdge', {
+ enumerable: false,
+ configurable: true,
+ set: function (value) {
+ if (value === undefined) value = 0;
+ this._incidentEdge = value;
+ this._m_id._key = (this._m_id._key & 0xffff00ff) | ((this._incidentEdge << 8) & 0x0000ff00);
+ }
+ });
+ Object.defineProperty(Features.prototype, 'incidentVertex', {
+ enumerable: false,
+ configurable: true,
+ get: function () {
+ return this._incidentVertex;
+ }
+ });
+ Object.defineProperty(Features.prototype, 'incidentVertex', {
+ enumerable: false,
+ configurable: true,
+ set: function (value) {
+ if (value === undefined) value = 0;
+ this._incidentVertex = value;
+ this._m_id._key = (this._m_id._key & 0xff00ffff) | ((this._incidentVertex << 16) & 0x00ff0000);
+ }
+ });
+ Object.defineProperty(Features.prototype, 'flip', {
+ enumerable: false,
+ configurable: true,
+ get: function () {
+ return this._flip;
+ }
+ });
+ Object.defineProperty(Features.prototype, 'flip', {
+ enumerable: false,
+ configurable: true,
+ set: function (value) {
+ if (value === undefined) value = 0;
+ this._flip = value;
+ this._m_id._key = (this._m_id._key & 0x00ffffff) | ((this._flip << 24) & 0xff000000);
+ }
+ });
+})();
+(function () {
+ var b2Color = Box2D.Common.b2Color,
+ b2internal = Box2D.Common.b2internal,
+ b2Settings = Box2D.Common.b2Settings,
+ b2CircleShape = Box2D.Collision.Shapes.b2CircleShape,
+ b2EdgeChainDef = Box2D.Collision.Shapes.b2EdgeChainDef,
+ b2EdgeShape = Box2D.Collision.Shapes.b2EdgeShape,
+ b2MassData = Box2D.Collision.Shapes.b2MassData,
+ b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape,
+ b2Shape = Box2D.Collision.Shapes.b2Shape,
+ b2Mat22 = Box2D.Common.Math.b2Mat22,
+ b2Mat33 = Box2D.Common.Math.b2Mat33,
+ b2Math = Box2D.Common.Math.b2Math,
+ b2Sweep = Box2D.Common.Math.b2Sweep,
+ b2Transform = Box2D.Common.Math.b2Transform,
+ b2Vec2 = Box2D.Common.Math.b2Vec2,
+ b2Vec3 = Box2D.Common.Math.b2Vec3,
+ b2Body = Box2D.Dynamics.b2Body,
+ b2BodyDef = Box2D.Dynamics.b2BodyDef,
+ b2ContactFilter = Box2D.Dynamics.b2ContactFilter,
+ b2ContactImpulse = Box2D.Dynamics.b2ContactImpulse,
+ b2ContactListener = Box2D.Dynamics.b2ContactListener,
+ b2ContactManager = Box2D.Dynamics.b2ContactManager,
+ b2DebugDraw = Box2D.Dynamics.b2DebugDraw,
+ b2DestructionListener = Box2D.Dynamics.b2DestructionListener,
+ b2FilterData = Box2D.Dynamics.b2FilterData,
+ b2Fixture = Box2D.Dynamics.b2Fixture,
+ b2FixtureDef = Box2D.Dynamics.b2FixtureDef,
+ b2Island = Box2D.Dynamics.b2Island,
+ b2TimeStep = Box2D.Dynamics.b2TimeStep,
+ b2World = Box2D.Dynamics.b2World,
+ b2AABB = Box2D.Collision.b2AABB,
+ b2Bound = Box2D.Collision.b2Bound,
+ b2BoundValues = Box2D.Collision.b2BoundValues,
+ b2Collision = Box2D.Collision.b2Collision,
+ b2ContactID = Box2D.Collision.b2ContactID,
+ b2ContactPoint = Box2D.Collision.b2ContactPoint,
+ b2Distance = Box2D.Collision.b2Distance,
+ b2DistanceInput = Box2D.Collision.b2DistanceInput,
+ b2DistanceOutput = Box2D.Collision.b2DistanceOutput,
+ b2DistanceProxy = Box2D.Collision.b2DistanceProxy,
+ b2DynamicTree = Box2D.Collision.b2DynamicTree,
+ b2DynamicTreeBroadPhase = Box2D.Collision.b2DynamicTreeBroadPhase,
+ b2DynamicTreeNode = Box2D.Collision.b2DynamicTreeNode,
+ b2DynamicTreePair = Box2D.Collision.b2DynamicTreePair,
+ b2Manifold = Box2D.Collision.b2Manifold,
+ b2ManifoldPoint = Box2D.Collision.b2ManifoldPoint,
+ b2Point = Box2D.Collision.b2Point,
+ b2RayCastInput = Box2D.Collision.b2RayCastInput,
+ b2RayCastOutput = Box2D.Collision.b2RayCastOutput,
+ b2Segment = Box2D.Collision.b2Segment,
+ b2SeparationFunction = Box2D.Collision.b2SeparationFunction,
+ b2Simplex = Box2D.Collision.b2Simplex,
+ b2SimplexCache = Box2D.Collision.b2SimplexCache,
+ b2SimplexVertex = Box2D.Collision.b2SimplexVertex,
+ b2TimeOfImpact = Box2D.Collision.b2TimeOfImpact,
+ b2TOIInput = Box2D.Collision.b2TOIInput,
+ b2WorldManifold = Box2D.Collision.b2WorldManifold,
+ ClipVertex = Box2D.Collision.ClipVertex,
+ Features = Box2D.Collision.Features,
+ IBroadPhase = Box2D.Collision.IBroadPhase;
+
+ Box2D.inherit(b2CircleShape, Box2D.Collision.Shapes.b2Shape);
+ b2CircleShape.prototype.__super = Box2D.Collision.Shapes.b2Shape.prototype;
+ b2CircleShape.b2CircleShape = function () {
+ Box2D.Collision.Shapes.b2Shape.b2Shape.apply(this, arguments);
+ this.m_p = new b2Vec2();
+ };
+ b2CircleShape.prototype.Copy = function () {
+ var s = new b2CircleShape();
+ s.Set(this);
+ return s;
+ }
+ b2CircleShape.prototype.Set = function (other) {
+ this.__super.Set.call(this, other);
+ if (Box2D.is(other, b2CircleShape)) {
+ var other2 = (other instanceof b2CircleShape ? other : null);
+ this.m_p.SetV(other2.m_p);
+ }
+ }
+ b2CircleShape.prototype.TestPoint = function (transform, p) {
+ var tMat = transform.R;
+ var dX = transform.position.x + (tMat.col1.x * this.m_p.x + tMat.col2.x * this.m_p.y);
+ var dY = transform.position.y + (tMat.col1.y * this.m_p.x + tMat.col2.y * this.m_p.y);
+ dX = p.x - dX;
+ dY = p.y - dY;
+ return (dX * dX + dY * dY) <= this.m_radius * this.m_radius;
+ }
+ b2CircleShape.prototype.RayCast = function (output, input, transform) {
+ var tMat = transform.R;
+ var positionX = transform.position.x + (tMat.col1.x * this.m_p.x + tMat.col2.x * this.m_p.y);
+ var positionY = transform.position.y + (tMat.col1.y * this.m_p.x + tMat.col2.y * this.m_p.y);
+ var sX = input.p1.x - positionX;
+ var sY = input.p1.y - positionY;
+ var b = (sX * sX + sY * sY) - this.m_radius * this.m_radius;
+ var rX = input.p2.x - input.p1.x;
+ var rY = input.p2.y - input.p1.y;
+ var c = (sX * rX + sY * rY);
+ var rr = (rX * rX + rY * rY);
+ var sigma = c * c - rr * b;
+ if (sigma < 0.0 || rr < Number.MIN_VALUE) {
+ return false;
+ }
+ var a = (-(c + Math.sqrt(sigma)));
+ if (0.0 <= a && a <= input.maxFraction * rr) {
+ a /= rr;
+ output.fraction = a;
+ output.normal.x = sX + a * rX;
+ output.normal.y = sY + a * rY;
+ output.normal.Normalize();
+ return true;
+ }
+ return false;
+ }
+ b2CircleShape.prototype.ComputeAABB = function (aabb, transform) {
+ var tMat = transform.R;
+ var pX = transform.position.x + (tMat.col1.x * this.m_p.x + tMat.col2.x * this.m_p.y);
+ var pY = transform.position.y + (tMat.col1.y * this.m_p.x + tMat.col2.y * this.m_p.y);
+ aabb.lowerBound.Set(pX - this.m_radius, pY - this.m_radius);
+ aabb.upperBound.Set(pX + this.m_radius, pY + this.m_radius);
+ }
+ b2CircleShape.prototype.ComputeMass = function (massData, density) {
+ if (density === undefined) density = 0;
+ massData.mass = density * b2Settings.b2_pi * this.m_radius * this.m_radius;
+ massData.center.SetV(this.m_p);
+ massData.I = massData.mass * (0.5 * this.m_radius * this.m_radius + (this.m_p.x * this.m_p.x + this.m_p.y * this.m_p.y));
+ }
+ b2CircleShape.prototype.ComputeSubmergedArea = function (normal, offset, xf, c) {
+ if (offset === undefined) offset = 0;
+ var p = b2Math.MulX(xf, this.m_p);
+ var l = (-(b2Math.Dot(normal, p) - offset));
+ if (l < (-this.m_radius) + Number.MIN_VALUE) {
+ return 0;
+ }
+ if (l > this.m_radius) {
+ c.SetV(p);
+ return Math.PI * this.m_radius * this.m_radius;
+ }
+ var r2 = this.m_radius * this.m_radius;
+ var l2 = l * l;
+ var area = r2 * (Math.asin(l / this.m_radius) + Math.PI / 2) + l * Math.sqrt(r2 - l2);
+ var com = (-2 / 3 * Math.pow(r2 - l2, 1.5) / area);
+ c.x = p.x + normal.x * com;
+ c.y = p.y + normal.y * com;
+ return area;
+ }
+ b2CircleShape.prototype.GetLocalPosition = function () {
+ return this.m_p;
+ }
+ b2CircleShape.prototype.SetLocalPosition = function (position) {
+ this.m_p.SetV(position);
+ }
+ b2CircleShape.prototype.GetRadius = function () {
+ return this.m_radius;
+ }
+ b2CircleShape.prototype.SetRadius = function (radius) {
+ if (radius === undefined) radius = 0;
+ this.m_radius = radius;
+ }
+ b2CircleShape.prototype.b2CircleShape = function (radius) {
+ if (radius === undefined) radius = 0;
+ this.__super.b2Shape.call(this);
+ this.m_type = b2Shape.e_circleShape;
+ this.m_radius = radius;
+ }
+ b2EdgeChainDef.b2EdgeChainDef = function () {};
+ b2EdgeChainDef.prototype.b2EdgeChainDef = function () {
+ this.vertexCount = 0;
+ this.isALoop = true;
+ this.vertices = [];
+ }
+ Box2D.inherit(b2EdgeShape, Box2D.Collision.Shapes.b2Shape);
+ b2EdgeShape.prototype.__super = Box2D.Collision.Shapes.b2Shape.prototype;
+ b2EdgeShape.b2EdgeShape = function () {
+ Box2D.Collision.Shapes.b2Shape.b2Shape.apply(this, arguments);
+ this.s_supportVec = new b2Vec2();
+ this.m_v1 = new b2Vec2();
+ this.m_v2 = new b2Vec2();
+ this.m_coreV1 = new b2Vec2();
+ this.m_coreV2 = new b2Vec2();
+ this.m_normal = new b2Vec2();
+ this.m_direction = new b2Vec2();
+ this.m_cornerDir1 = new b2Vec2();
+ this.m_cornerDir2 = new b2Vec2();
+ };
+ b2EdgeShape.prototype.TestPoint = function (transform, p) {
+ return false;
+ }
+ b2EdgeShape.prototype.RayCast = function (output, input, transform) {
+ var tMat;
+ var rX = input.p2.x - input.p1.x;
+ var rY = input.p2.y - input.p1.y;
+ tMat = transform.R;
+ var v1X = transform.position.x + (tMat.col1.x * this.m_v1.x + tMat.col2.x * this.m_v1.y);
+ var v1Y = transform.position.y + (tMat.col1.y * this.m_v1.x + tMat.col2.y * this.m_v1.y);
+ var nX = transform.position.y + (tMat.col1.y * this.m_v2.x + tMat.col2.y * this.m_v2.y) - v1Y;
+ var nY = (-(transform.position.x + (tMat.col1.x * this.m_v2.x + tMat.col2.x * this.m_v2.y) - v1X));
+ var k_slop = 100.0 * Number.MIN_VALUE;
+ var denom = (-(rX * nX + rY * nY));
+ if (denom > k_slop) {
+ var bX = input.p1.x - v1X;
+ var bY = input.p1.y - v1Y;
+ var a = (bX * nX + bY * nY);
+ if (0.0 <= a && a <= input.maxFraction * denom) {
+ var mu2 = (-rX * bY) + rY * bX;
+ if ((-k_slop * denom) <= mu2 && mu2 <= denom * (1.0 + k_slop)) {
+ a /= denom;
+ output.fraction = a;
+ var nLen = Math.sqrt(nX * nX + nY * nY);
+ output.normal.x = nX / nLen;
+ output.normal.y = nY / nLen;
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ b2EdgeShape.prototype.ComputeAABB = function (aabb, transform) {
+ var tMat = transform.R;
+ var v1X = transform.position.x + (tMat.col1.x * this.m_v1.x + tMat.col2.x * this.m_v1.y);
+ var v1Y = transform.position.y + (tMat.col1.y * this.m_v1.x + tMat.col2.y * this.m_v1.y);
+ var v2X = transform.position.x + (tMat.col1.x * this.m_v2.x + tMat.col2.x * this.m_v2.y);
+ var v2Y = transform.position.y + (tMat.col1.y * this.m_v2.x + tMat.col2.y * this.m_v2.y);
+ if (v1X < v2X) {
+ aabb.lowerBound.x = v1X;
+ aabb.upperBound.x = v2X;
+ }
+ else {
+ aabb.lowerBound.x = v2X;
+ aabb.upperBound.x = v1X;
+ }
+ if (v1Y < v2Y) {
+ aabb.lowerBound.y = v1Y;
+ aabb.upperBound.y = v2Y;
+ }
+ else {
+ aabb.lowerBound.y = v2Y;
+ aabb.upperBound.y = v1Y;
+ }
+ }
+ b2EdgeShape.prototype.ComputeMass = function (massData, density) {
+ if (density === undefined) density = 0;
+ massData.mass = 0;
+ massData.center.SetV(this.m_v1);
+ massData.I = 0;
+ }
+ b2EdgeShape.prototype.ComputeSubmergedArea = function (normal, offset, xf, c) {
+ if (offset === undefined) offset = 0;
+ var v0 = new b2Vec2(normal.x * offset, normal.y * offset);
+ var v1 = b2Math.MulX(xf, this.m_v1);
+ var v2 = b2Math.MulX(xf, this.m_v2);
+ var d1 = b2Math.Dot(normal, v1) - offset;
+ var d2 = b2Math.Dot(normal, v2) - offset;
+ if (d1 > 0) {
+ if (d2 > 0) {
+ return 0;
+ }
+ else {
+ v1.x = (-d2 / (d1 - d2) * v1.x) + d1 / (d1 - d2) * v2.x;
+ v1.y = (-d2 / (d1 - d2) * v1.y) + d1 / (d1 - d2) * v2.y;
+ }
+ }
+ else {
+ if (d2 > 0) {
+ v2.x = (-d2 / (d1 - d2) * v1.x) + d1 / (d1 - d2) * v2.x;
+ v2.y = (-d2 / (d1 - d2) * v1.y) + d1 / (d1 - d2) * v2.y;
+ }
+ else {}
+ }
+ c.x = (v0.x + v1.x + v2.x) / 3;
+ c.y = (v0.y + v1.y + v2.y) / 3;
+ return 0.5 * ((v1.x - v0.x) * (v2.y - v0.y) - (v1.y - v0.y) * (v2.x - v0.x));
+ }
+ b2EdgeShape.prototype.GetLength = function () {
+ return this.m_length;
+ }
+ b2EdgeShape.prototype.GetVertex1 = function () {
+ return this.m_v1;
+ }
+ b2EdgeShape.prototype.GetVertex2 = function () {
+ return this.m_v2;
+ }
+ b2EdgeShape.prototype.GetCoreVertex1 = function () {
+ return this.m_coreV1;
+ }
+ b2EdgeShape.prototype.GetCoreVertex2 = function () {
+ return this.m_coreV2;
+ }
+ b2EdgeShape.prototype.GetNormalVector = function () {
+ return this.m_normal;
+ }
+ b2EdgeShape.prototype.GetDirectionVector = function () {
+ return this.m_direction;
+ }
+ b2EdgeShape.prototype.GetCorner1Vector = function () {
+ return this.m_cornerDir1;
+ }
+ b2EdgeShape.prototype.GetCorner2Vector = function () {
+ return this.m_cornerDir2;
+ }
+ b2EdgeShape.prototype.Corner1IsConvex = function () {
+ return this.m_cornerConvex1;
+ }
+ b2EdgeShape.prototype.Corner2IsConvex = function () {
+ return this.m_cornerConvex2;
+ }
+ b2EdgeShape.prototype.GetFirstVertex = function (xf) {
+ var tMat = xf.R;
+ return new b2Vec2(xf.position.x + (tMat.col1.x * this.m_coreV1.x + tMat.col2.x * this.m_coreV1.y), xf.position.y + (tMat.col1.y * this.m_coreV1.x + tMat.col2.y * this.m_coreV1.y));
+ }
+ b2EdgeShape.prototype.GetNextEdge = function () {
+ return this.m_nextEdge;
+ }
+ b2EdgeShape.prototype.GetPrevEdge = function () {
+ return this.m_prevEdge;
+ }
+ b2EdgeShape.prototype.Support = function (xf, dX, dY) {
+ if (dX === undefined) dX = 0;
+ if (dY === undefined) dY = 0;
+ var tMat = xf.R;
+ var v1X = xf.position.x + (tMat.col1.x * this.m_coreV1.x + tMat.col2.x * this.m_coreV1.y);
+ var v1Y = xf.position.y + (tMat.col1.y * this.m_coreV1.x + tMat.col2.y * this.m_coreV1.y);
+ var v2X = xf.position.x + (tMat.col1.x * this.m_coreV2.x + tMat.col2.x * this.m_coreV2.y);
+ var v2Y = xf.position.y + (tMat.col1.y * this.m_coreV2.x + tMat.col2.y * this.m_coreV2.y);
+ if ((v1X * dX + v1Y * dY) > (v2X * dX + v2Y * dY)) {
+ this.s_supportVec.x = v1X;
+ this.s_supportVec.y = v1Y;
+ }
+ else {
+ this.s_supportVec.x = v2X;
+ this.s_supportVec.y = v2Y;
+ }
+ return this.s_supportVec;
+ }
+ b2EdgeShape.prototype.b2EdgeShape = function (v1, v2) {
+ this.__super.b2Shape.call(this);
+ this.m_type = b2Shape.e_edgeShape;
+ this.m_prevEdge = null;
+ this.m_nextEdge = null;
+ this.m_v1 = v1;
+ this.m_v2 = v2;
+ this.m_direction.Set(this.m_v2.x - this.m_v1.x, this.m_v2.y - this.m_v1.y);
+ this.m_length = this.m_direction.Normalize();
+ this.m_normal.Set(this.m_direction.y, (-this.m_direction.x));
+ this.m_coreV1.Set((-b2Settings.b2_toiSlop * (this.m_normal.x - this.m_direction.x)) + this.m_v1.x, (-b2Settings.b2_toiSlop * (this.m_normal.y - this.m_direction.y)) + this.m_v1.y);
+ this.m_coreV2.Set((-b2Settings.b2_toiSlop * (this.m_normal.x + this.m_direction.x)) + this.m_v2.x, (-b2Settings.b2_toiSlop * (this.m_normal.y + this.m_direction.y)) + this.m_v2.y);
+ this.m_cornerDir1 = this.m_normal;
+ this.m_cornerDir2.Set((-this.m_normal.x), (-this.m_normal.y));
+ }
+ b2EdgeShape.prototype.SetPrevEdge = function (edge, core, cornerDir, convex) {
+ this.m_prevEdge = edge;
+ this.m_coreV1 = core;
+ this.m_cornerDir1 = cornerDir;
+ this.m_cornerConvex1 = convex;
+ }
+ b2EdgeShape.prototype.SetNextEdge = function (edge, core, cornerDir, convex) {
+ this.m_nextEdge = edge;
+ this.m_coreV2 = core;
+ this.m_cornerDir2 = cornerDir;
+ this.m_cornerConvex2 = convex;
+ }
+ b2MassData.b2MassData = function () {
+ this.mass = 0.0;
+ this.center = new b2Vec2(0, 0);
+ this.I = 0.0;
+ };
+ Box2D.inherit(b2PolygonShape, Box2D.Collision.Shapes.b2Shape);
+ b2PolygonShape.prototype.__super = Box2D.Collision.Shapes.b2Shape.prototype;
+ b2PolygonShape.b2PolygonShape = function () {
+ Box2D.Collision.Shapes.b2Shape.b2Shape.apply(this, arguments);
+ };
+ b2PolygonShape.prototype.Copy = function () {
+ var s = new b2PolygonShape();
+ s.Set(this);
+ return s;
+ }
+ b2PolygonShape.prototype.Set = function (other) {
+ this.__super.Set.call(this, other);
+ if (Box2D.is(other, b2PolygonShape)) {
+ var other2 = (other instanceof b2PolygonShape ? other : null);
+ this.m_centroid.SetV(other2.m_centroid);
+ this.m_vertexCount = other2.m_vertexCount;
+ this.Reserve(this.m_vertexCount);
+ for (var i = 0; i < this.m_vertexCount; i++) {
+ this.m_vertices[i].SetV(other2.m_vertices[i]);
+ this.m_normals[i].SetV(other2.m_normals[i]);
+ }
+ }
+ }
+ b2PolygonShape.prototype.SetAsArray = function (vertices, vertexCount) {
+ if (vertexCount === undefined) vertexCount = 0;
+ var v = new Vector();
+ var i = 0,
+ tVec;
+ for (i = 0;
+ i < vertices.length; ++i) {
+ tVec = vertices[i];
+ v.push(tVec);
+ }
+ this.SetAsVector(v, vertexCount);
+ }
+ b2PolygonShape.AsArray = function (vertices, vertexCount) {
+ if (vertexCount === undefined) vertexCount = 0;
+ var polygonShape = new b2PolygonShape();
+ polygonShape.SetAsArray(vertices, vertexCount);
+ return polygonShape;
+ }
+ b2PolygonShape.prototype.SetAsVector = function (vertices, vertexCount) {
+ if (vertexCount === undefined) vertexCount = 0;
+ if (vertexCount == 0) vertexCount = vertices.length;
+ b2Settings.b2Assert(2 <= vertexCount);
+ this.m_vertexCount = vertexCount;
+ this.Reserve(vertexCount);
+ var i = 0;
+ for (i = 0;
+ i < this.m_vertexCount; i++) {
+ this.m_vertices[i].SetV(vertices[i]);
+ }
+ for (i = 0;
+ i < this.m_vertexCount; ++i) {
+ var i1 = parseInt(i);
+ var i2 = parseInt(i + 1 < this.m_vertexCount ? i + 1 : 0);
+ var edge = b2Math.SubtractVV(this.m_vertices[i2], this.m_vertices[i1]);
+ b2Settings.b2Assert(edge.LengthSquared() > Number.MIN_VALUE);
+ this.m_normals[i].SetV(b2Math.CrossVF(edge, 1.0));
+ this.m_normals[i].Normalize();
+ }
+ this.m_centroid = b2PolygonShape.ComputeCentroid(this.m_vertices, this.m_vertexCount);
+ }
+ b2PolygonShape.AsVector = function (vertices, vertexCount) {
+ if (vertexCount === undefined) vertexCount = 0;
+ var polygonShape = new b2PolygonShape();
+ polygonShape.SetAsVector(vertices, vertexCount);
+ return polygonShape;
+ }
+ b2PolygonShape.prototype.SetAsBox = function (hx, hy) {
+ if (hx === undefined) hx = 0;
+ if (hy === undefined) hy = 0;
+ this.m_vertexCount = 4;
+ this.Reserve(4);
+ this.m_vertices[0].Set((-hx), (-hy));
+ this.m_vertices[1].Set(hx, (-hy));
+ this.m_vertices[2].Set(hx, hy);
+ this.m_vertices[3].Set((-hx), hy);
+ this.m_normals[0].Set(0.0, (-1.0));
+ this.m_normals[1].Set(1.0, 0.0);
+ this.m_normals[2].Set(0.0, 1.0);
+ this.m_normals[3].Set((-1.0), 0.0);
+ this.m_centroid.SetZero();
+ }
+ b2PolygonShape.AsBox = function (hx, hy) {
+ if (hx === undefined) hx = 0;
+ if (hy === undefined) hy = 0;
+ var polygonShape = new b2PolygonShape();
+ polygonShape.SetAsBox(hx, hy);
+ return polygonShape;
+ }
+ b2PolygonShape.prototype.SetAsOrientedBox = function (hx, hy, center, angle) {
+ if (hx === undefined) hx = 0;
+ if (hy === undefined) hy = 0;
+ if (center === undefined) center = null;
+ if (angle === undefined) angle = 0.0;
+ this.m_vertexCount = 4;
+ this.Reserve(4);
+ this.m_vertices[0].Set((-hx), (-hy));
+ this.m_vertices[1].Set(hx, (-hy));
+ this.m_vertices[2].Set(hx, hy);
+ this.m_vertices[3].Set((-hx), hy);
+ this.m_normals[0].Set(0.0, (-1.0));
+ this.m_normals[1].Set(1.0, 0.0);
+ this.m_normals[2].Set(0.0, 1.0);
+ this.m_normals[3].Set((-1.0), 0.0);
+ this.m_centroid = center;
+ var xf = new b2Transform();
+ xf.position = center;
+ xf.R.Set(angle);
+ for (var i = 0; i < this.m_vertexCount; ++i) {
+ this.m_vertices[i] = b2Math.MulX(xf, this.m_vertices[i]);
+ this.m_normals[i] = b2Math.MulMV(xf.R, this.m_normals[i]);
+ }
+ }
+ b2PolygonShape.AsOrientedBox = function (hx, hy, center, angle) {
+ if (hx === undefined) hx = 0;
+ if (hy === undefined) hy = 0;
+ if (center === undefined) center = null;
+ if (angle === undefined) angle = 0.0;
+ var polygonShape = new b2PolygonShape();
+ polygonShape.SetAsOrientedBox(hx, hy, center, angle);
+ return polygonShape;
+ }
+ b2PolygonShape.prototype.SetAsEdge = function (v1, v2) {
+ this.m_vertexCount = 2;
+ this.Reserve(2);
+ this.m_vertices[0].SetV(v1);
+ this.m_vertices[1].SetV(v2);
+ this.m_centroid.x = 0.5 * (v1.x + v2.x);
+ this.m_centroid.y = 0.5 * (v1.y + v2.y);
+ this.m_normals[0] = b2Math.CrossVF(b2Math.SubtractVV(v2, v1), 1.0);
+ this.m_normals[0].Normalize();
+ this.m_normals[1].x = (-this.m_normals[0].x);
+ this.m_normals[1].y = (-this.m_normals[0].y);
+ }
+ b2PolygonShape.AsEdge = function (v1, v2) {
+ var polygonShape = new b2PolygonShape();
+ polygonShape.SetAsEdge(v1, v2);
+ return polygonShape;
+ }
+ b2PolygonShape.prototype.TestPoint = function (xf, p) {
+ var tVec;
+ var tMat = xf.R;
+ var tX = p.x - xf.position.x;
+ var tY = p.y - xf.position.y;
+ var pLocalX = (tX * tMat.col1.x + tY * tMat.col1.y);
+ var pLocalY = (tX * tMat.col2.x + tY * tMat.col2.y);
+ for (var i = 0; i < this.m_vertexCount; ++i) {
+ tVec = this.m_vertices[i];
+ tX = pLocalX - tVec.x;
+ tY = pLocalY - tVec.y;
+ tVec = this.m_normals[i];
+ var dot = (tVec.x * tX + tVec.y * tY);
+ if (dot > 0.0) {
+ return false;
+ }
+ }
+ return true;
+ }
+ b2PolygonShape.prototype.RayCast = function (output, input, transform) {
+ var lower = 0.0;
+ var upper = input.maxFraction;
+ var tX = 0;
+ var tY = 0;
+ var tMat;
+ var tVec;
+ tX = input.p1.x - transform.position.x;
+ tY = input.p1.y - transform.position.y;
+ tMat = transform.R;
+ var p1X = (tX * tMat.col1.x + tY * tMat.col1.y);
+ var p1Y = (tX * tMat.col2.x + tY * tMat.col2.y);
+ tX = input.p2.x - transform.position.x;
+ tY = input.p2.y - transform.position.y;
+ tMat = transform.R;
+ var p2X = (tX * tMat.col1.x + tY * tMat.col1.y);
+ var p2Y = (tX * tMat.col2.x + tY * tMat.col2.y);
+ var dX = p2X - p1X;
+ var dY = p2Y - p1Y;
+ var index = parseInt((-1));
+ for (var i = 0; i < this.m_vertexCount; ++i) {
+ tVec = this.m_vertices[i];
+ tX = tVec.x - p1X;
+ tY = tVec.y - p1Y;
+ tVec = this.m_normals[i];
+ var numerator = (tVec.x * tX + tVec.y * tY);
+ var denominator = (tVec.x * dX + tVec.y * dY);
+ if (denominator == 0.0) {
+ if (numerator < 0.0) {
+ return false;
+ }
+ }
+ else {
+ if (denominator < 0.0 && numerator < lower * denominator) {
+ lower = numerator / denominator;
+ index = i;
+ }
+ else if (denominator > 0.0 && numerator < upper * denominator) {
+ upper = numerator / denominator;
+ }
+ }
+ if (upper < lower - Number.MIN_VALUE) {
+ return false;
+ }
+ }
+ if (index >= 0) {
+ output.fraction = lower;
+ tMat = transform.R;
+ tVec = this.m_normals[index];
+ output.normal.x = (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ output.normal.y = (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ return true;
+ }
+ return false;
+ }
+ b2PolygonShape.prototype.ComputeAABB = function (aabb, xf) {
+ var tMat = xf.R;
+ var tVec = this.m_vertices[0];
+ var lowerX = xf.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ var lowerY = xf.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ var upperX = lowerX;
+ var upperY = lowerY;
+ for (var i = 1; i < this.m_vertexCount; ++i) {
+ tVec = this.m_vertices[i];
+ var vX = xf.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ var vY = xf.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ lowerX = lowerX < vX ? lowerX : vX;
+ lowerY = lowerY < vY ? lowerY : vY;
+ upperX = upperX > vX ? upperX : vX;
+ upperY = upperY > vY ? upperY : vY;
+ }
+ aabb.lowerBound.x = lowerX - this.m_radius;
+ aabb.lowerBound.y = lowerY - this.m_radius;
+ aabb.upperBound.x = upperX + this.m_radius;
+ aabb.upperBound.y = upperY + this.m_radius;
+ }
+ b2PolygonShape.prototype.ComputeMass = function (massData, density) {
+ if (density === undefined) density = 0;
+ if (this.m_vertexCount == 2) {
+ massData.center.x = 0.5 * (this.m_vertices[0].x + this.m_vertices[1].x);
+ massData.center.y = 0.5 * (this.m_vertices[0].y + this.m_vertices[1].y);
+ massData.mass = 0.0;
+ massData.I = 0.0;
+ return;
+ }
+ var centerX = 0.0;
+ var centerY = 0.0;
+ var area = 0.0;
+ var I = 0.0;
+ var p1X = 0.0;
+ var p1Y = 0.0;
+ var k_inv3 = 1.0 / 3.0;
+ for (var i = 0; i < this.m_vertexCount; ++i) {
+ var p2 = this.m_vertices[i];
+ var p3 = i + 1 < this.m_vertexCount ? this.m_vertices[parseInt(i + 1)] : this.m_vertices[0];
+ var e1X = p2.x - p1X;
+ var e1Y = p2.y - p1Y;
+ var e2X = p3.x - p1X;
+ var e2Y = p3.y - p1Y;
+ var D = e1X * e2Y - e1Y * e2X;
+ var triangleArea = 0.5 * D;area += triangleArea;
+ centerX += triangleArea * k_inv3 * (p1X + p2.x + p3.x);
+ centerY += triangleArea * k_inv3 * (p1Y + p2.y + p3.y);
+ var px = p1X;
+ var py = p1Y;
+ var ex1 = e1X;
+ var ey1 = e1Y;
+ var ex2 = e2X;
+ var ey2 = e2Y;
+ var intx2 = k_inv3 * (0.25 * (ex1 * ex1 + ex2 * ex1 + ex2 * ex2) + (px * ex1 + px * ex2)) + 0.5 * px * px;
+ var inty2 = k_inv3 * (0.25 * (ey1 * ey1 + ey2 * ey1 + ey2 * ey2) + (py * ey1 + py * ey2)) + 0.5 * py * py;I += D * (intx2 + inty2);
+ }
+ massData.mass = density * area;
+ centerX *= 1.0 / area;
+ centerY *= 1.0 / area;
+ massData.center.Set(centerX, centerY);
+ massData.I = density * I;
+ }
+ b2PolygonShape.prototype.ComputeSubmergedArea = function (normal, offset, xf, c) {
+ if (offset === undefined) offset = 0;
+ var normalL = b2Math.MulTMV(xf.R, normal);
+ var offsetL = offset - b2Math.Dot(normal, xf.position);
+ var depths = new Vector_a2j_Number();
+ var diveCount = 0;
+ var intoIndex = parseInt((-1));
+ var outoIndex = parseInt((-1));
+ var lastSubmerged = false;
+ var i = 0;
+ for (i = 0;
+ i < this.m_vertexCount; ++i) {
+ depths[i] = b2Math.Dot(normalL, this.m_vertices[i]) - offsetL;
+ var isSubmerged = depths[i] < (-Number.MIN_VALUE);
+ if (i > 0) {
+ if (isSubmerged) {
+ if (!lastSubmerged) {
+ intoIndex = i - 1;
+ diveCount++;
+ }
+ }
+ else {
+ if (lastSubmerged) {
+ outoIndex = i - 1;
+ diveCount++;
+ }
+ }
+ }
+ lastSubmerged = isSubmerged;
+ }
+ switch (diveCount) {
+ case 0:
+ if (lastSubmerged) {
+ var md = new b2MassData();
+ this.ComputeMass(md, 1);
+ c.SetV(b2Math.MulX(xf, md.center));
+ return md.mass;
+ }
+ else {
+ return 0;
+ }
+ break;
+ case 1:
+ if (intoIndex == (-1)) {
+ intoIndex = this.m_vertexCount - 1;
+ }
+ else {
+ outoIndex = this.m_vertexCount - 1;
+ }
+ break;
+ }
+ var intoIndex2 = parseInt((intoIndex + 1) % this.m_vertexCount);
+ var outoIndex2 = parseInt((outoIndex + 1) % this.m_vertexCount);
+ var intoLamdda = (0 - depths[intoIndex]) / (depths[intoIndex2] - depths[intoIndex]);
+ var outoLamdda = (0 - depths[outoIndex]) / (depths[outoIndex2] - depths[outoIndex]);
+ var intoVec = new b2Vec2(this.m_vertices[intoIndex].x * (1 - intoLamdda) + this.m_vertices[intoIndex2].x * intoLamdda, this.m_vertices[intoIndex].y * (1 - intoLamdda) + this.m_vertices[intoIndex2].y * intoLamdda);
+ var outoVec = new b2Vec2(this.m_vertices[outoIndex].x * (1 - outoLamdda) + this.m_vertices[outoIndex2].x * outoLamdda, this.m_vertices[outoIndex].y * (1 - outoLamdda) + this.m_vertices[outoIndex2].y * outoLamdda);
+ var area = 0;
+ var center = new b2Vec2();
+ var p2 = this.m_vertices[intoIndex2];
+ var p3;
+ i = intoIndex2;
+ while (i != outoIndex2) {
+ i = (i + 1) % this.m_vertexCount;
+ if (i == outoIndex2) p3 = outoVec;
+ else p3 = this.m_vertices[i];
+ var triangleArea = 0.5 * ((p2.x - intoVec.x) * (p3.y - intoVec.y) - (p2.y - intoVec.y) * (p3.x - intoVec.x));
+ area += triangleArea;
+ center.x += triangleArea * (intoVec.x + p2.x + p3.x) / 3;
+ center.y += triangleArea * (intoVec.y + p2.y + p3.y) / 3;
+ p2 = p3;
+ }
+ center.Multiply(1 / area);
+ c.SetV(b2Math.MulX(xf, center));
+ return area;
+ }
+ b2PolygonShape.prototype.GetVertexCount = function () {
+ return this.m_vertexCount;
+ }
+ b2PolygonShape.prototype.GetVertices = function () {
+ return this.m_vertices;
+ }
+ b2PolygonShape.prototype.GetNormals = function () {
+ return this.m_normals;
+ }
+ b2PolygonShape.prototype.GetSupport = function (d) {
+ var bestIndex = 0;
+ var bestValue = this.m_vertices[0].x * d.x + this.m_vertices[0].y * d.y;
+ for (var i = 1; i < this.m_vertexCount; ++i) {
+ var value = this.m_vertices[i].x * d.x + this.m_vertices[i].y * d.y;
+ if (value > bestValue) {
+ bestIndex = i;
+ bestValue = value;
+ }
+ }
+ return bestIndex;
+ }
+ b2PolygonShape.prototype.GetSupportVertex = function (d) {
+ var bestIndex = 0;
+ var bestValue = this.m_vertices[0].x * d.x + this.m_vertices[0].y * d.y;
+ for (var i = 1; i < this.m_vertexCount; ++i) {
+ var value = this.m_vertices[i].x * d.x + this.m_vertices[i].y * d.y;
+ if (value > bestValue) {
+ bestIndex = i;
+ bestValue = value;
+ }
+ }
+ return this.m_vertices[bestIndex];
+ }
+ b2PolygonShape.prototype.Validate = function () {
+ return false;
+ }
+ b2PolygonShape.prototype.b2PolygonShape = function () {
+ this.__super.b2Shape.call(this);
+ this.m_type = b2Shape.e_polygonShape;
+ this.m_centroid = new b2Vec2();
+ this.m_vertices = new Vector();
+ this.m_normals = new Vector();
+ }
+ b2PolygonShape.prototype.Reserve = function (count) {
+ if (count === undefined) count = 0;
+ for (var i = parseInt(this.m_vertices.length); i < count; i++) {
+ this.m_vertices[i] = new b2Vec2();
+ this.m_normals[i] = new b2Vec2();
+ }
+ }
+ b2PolygonShape.ComputeCentroid = function (vs, count) {
+ if (count === undefined) count = 0;
+ var c = new b2Vec2();
+ var area = 0.0;
+ var p1X = 0.0;
+ var p1Y = 0.0;
+ var inv3 = 1.0 / 3.0;
+ for (var i = 0; i < count; ++i) {
+ var p2 = vs[i];
+ var p3 = i + 1 < count ? vs[parseInt(i + 1)] : vs[0];
+ var e1X = p2.x - p1X;
+ var e1Y = p2.y - p1Y;
+ var e2X = p3.x - p1X;
+ var e2Y = p3.y - p1Y;
+ var D = (e1X * e2Y - e1Y * e2X);
+ var triangleArea = 0.5 * D;area += triangleArea;
+ c.x += triangleArea * inv3 * (p1X + p2.x + p3.x);
+ c.y += triangleArea * inv3 * (p1Y + p2.y + p3.y);
+ }
+ c.x *= 1.0 / area;
+ c.y *= 1.0 / area;
+ return c;
+ }
+ b2PolygonShape.ComputeOBB = function (obb, vs, count) {
+ if (count === undefined) count = 0;
+ var i = 0;
+ var p = new Vector(count + 1);
+ for (i = 0;
+ i < count; ++i) {
+ p[i] = vs[i];
+ }
+ p[count] = p[0];
+ var minArea = Number.MAX_VALUE;
+ for (i = 1;
+ i <= count; ++i) {
+ var root = p[parseInt(i - 1)];
+ var uxX = p[i].x - root.x;
+ var uxY = p[i].y - root.y;
+ var length = Math.sqrt(uxX * uxX + uxY * uxY);
+ uxX /= length;
+ uxY /= length;
+ var uyX = (-uxY);
+ var uyY = uxX;
+ var lowerX = Number.MAX_VALUE;
+ var lowerY = Number.MAX_VALUE;
+ var upperX = (-Number.MAX_VALUE);
+ var upperY = (-Number.MAX_VALUE);
+ for (var j = 0; j < count; ++j) {
+ var dX = p[j].x - root.x;
+ var dY = p[j].y - root.y;
+ var rX = (uxX * dX + uxY * dY);
+ var rY = (uyX * dX + uyY * dY);
+ if (rX < lowerX) lowerX = rX;
+ if (rY < lowerY) lowerY = rY;
+ if (rX > upperX) upperX = rX;
+ if (rY > upperY) upperY = rY;
+ }
+ var area = (upperX - lowerX) * (upperY - lowerY);
+ if (area < 0.95 * minArea) {
+ minArea = area;
+ obb.R.col1.x = uxX;
+ obb.R.col1.y = uxY;
+ obb.R.col2.x = uyX;
+ obb.R.col2.y = uyY;
+ var centerX = 0.5 * (lowerX + upperX);
+ var centerY = 0.5 * (lowerY + upperY);
+ var tMat = obb.R;
+ obb.center.x = root.x + (tMat.col1.x * centerX + tMat.col2.x * centerY);
+ obb.center.y = root.y + (tMat.col1.y * centerX + tMat.col2.y * centerY);
+ obb.extents.x = 0.5 * (upperX - lowerX);
+ obb.extents.y = 0.5 * (upperY - lowerY);
+ }
+ }
+ }
+ Box2D.postDefs.push(function () {
+ Box2D.Collision.Shapes.b2PolygonShape.s_mat = new b2Mat22();
+ });
+ b2Shape.b2Shape = function () {};
+ b2Shape.prototype.Copy = function () {
+ return null;
+ }
+ b2Shape.prototype.Set = function (other) {
+ this.m_radius = other.m_radius;
+ }
+ b2Shape.prototype.GetType = function () {
+ return this.m_type;
+ }
+ b2Shape.prototype.TestPoint = function (xf, p) {
+ return false;
+ }
+ b2Shape.prototype.RayCast = function (output, input, transform) {
+ return false;
+ }
+ b2Shape.prototype.ComputeAABB = function (aabb, xf) {}
+ b2Shape.prototype.ComputeMass = function (massData, density) {
+ if (density === undefined) density = 0;
+ }
+ b2Shape.prototype.ComputeSubmergedArea = function (normal, offset, xf, c) {
+ if (offset === undefined) offset = 0;
+ return 0;
+ }
+ b2Shape.TestOverlap = function (shape1, transform1, shape2, transform2) {
+ var input = new b2DistanceInput();
+ input.proxyA = new b2DistanceProxy();
+ input.proxyA.Set(shape1);
+ input.proxyB = new b2DistanceProxy();
+ input.proxyB.Set(shape2);
+ input.transformA = transform1;
+ input.transformB = transform2;
+ input.useRadii = true;
+ var simplexCache = new b2SimplexCache();
+ simplexCache.count = 0;
+ var output = new b2DistanceOutput();
+ b2Distance.Distance(output, simplexCache, input);
+ return output.distance < 10.0 * Number.MIN_VALUE;
+ }
+ b2Shape.prototype.b2Shape = function () {
+ this.m_type = b2Shape.e_unknownShape;
+ this.m_radius = b2Settings.b2_linearSlop;
+ }
+ Box2D.postDefs.push(function () {
+ Box2D.Collision.Shapes.b2Shape.e_unknownShape = parseInt((-1));
+ Box2D.Collision.Shapes.b2Shape.e_circleShape = 0;
+ Box2D.Collision.Shapes.b2Shape.e_polygonShape = 1;
+ Box2D.Collision.Shapes.b2Shape.e_edgeShape = 2;
+ Box2D.Collision.Shapes.b2Shape.e_shapeTypeCount = 3;
+ Box2D.Collision.Shapes.b2Shape.e_hitCollide = 1;
+ Box2D.Collision.Shapes.b2Shape.e_missCollide = 0;
+ Box2D.Collision.Shapes.b2Shape.e_startsInsideCollide = parseInt((-1));
+ });
+})();
+(function () {
+ var b2Color = Box2D.Common.b2Color,
+ b2internal = Box2D.Common.b2internal,
+ b2Settings = Box2D.Common.b2Settings,
+ b2Mat22 = Box2D.Common.Math.b2Mat22,
+ b2Mat33 = Box2D.Common.Math.b2Mat33,
+ b2Math = Box2D.Common.Math.b2Math,
+ b2Sweep = Box2D.Common.Math.b2Sweep,
+ b2Transform = Box2D.Common.Math.b2Transform,
+ b2Vec2 = Box2D.Common.Math.b2Vec2,
+ b2Vec3 = Box2D.Common.Math.b2Vec3;
+
+ b2Color.b2Color = function () {
+ this._r = 0;
+ this._g = 0;
+ this._b = 0;
+ };
+ b2Color.prototype.b2Color = function (rr, gg, bb) {
+ if (rr === undefined) rr = 0;
+ if (gg === undefined) gg = 0;
+ if (bb === undefined) bb = 0;
+ this._r = Box2D.parseUInt(255 * b2Math.Clamp(rr, 0.0, 1.0));
+ this._g = Box2D.parseUInt(255 * b2Math.Clamp(gg, 0.0, 1.0));
+ this._b = Box2D.parseUInt(255 * b2Math.Clamp(bb, 0.0, 1.0));
+ }
+ b2Color.prototype.Set = function (rr, gg, bb) {
+ if (rr === undefined) rr = 0;
+ if (gg === undefined) gg = 0;
+ if (bb === undefined) bb = 0;
+ this._r = Box2D.parseUInt(255 * b2Math.Clamp(rr, 0.0, 1.0));
+ this._g = Box2D.parseUInt(255 * b2Math.Clamp(gg, 0.0, 1.0));
+ this._b = Box2D.parseUInt(255 * b2Math.Clamp(bb, 0.0, 1.0));
+ }
+ Object.defineProperty(b2Color.prototype, 'r', {
+ enumerable: false,
+ configurable: true,
+ set: function (rr) {
+ if (rr === undefined) rr = 0;
+ this._r = Box2D.parseUInt(255 * b2Math.Clamp(rr, 0.0, 1.0));
+ }
+ });
+ Object.defineProperty(b2Color.prototype, 'g', {
+ enumerable: false,
+ configurable: true,
+ set: function (gg) {
+ if (gg === undefined) gg = 0;
+ this._g = Box2D.parseUInt(255 * b2Math.Clamp(gg, 0.0, 1.0));
+ }
+ });
+ Object.defineProperty(b2Color.prototype, 'b', {
+ enumerable: false,
+ configurable: true,
+ set: function (bb) {
+ if (bb === undefined) bb = 0;
+ this._b = Box2D.parseUInt(255 * b2Math.Clamp(bb, 0.0, 1.0));
+ }
+ });
+ Object.defineProperty(b2Color.prototype, 'color', {
+ enumerable: false,
+ configurable: true,
+ get: function () {
+ return (this._r << 16) | (this._g << 8) | (this._b);
+ }
+ });
+ b2Settings.b2Settings = function () {};
+ b2Settings.b2MixFriction = function (friction1, friction2) {
+ if (friction1 === undefined) friction1 = 0;
+ if (friction2 === undefined) friction2 = 0;
+ return Math.sqrt(friction1 * friction2);
+ }
+ b2Settings.b2MixRestitution = function (restitution1, restitution2) {
+ if (restitution1 === undefined) restitution1 = 0;
+ if (restitution2 === undefined) restitution2 = 0;
+ return restitution1 > restitution2 ? restitution1 : restitution2;
+ }
+ b2Settings.b2Assert = function (a) {
+ if (!a) {
+ throw "Assertion Failed";
+ }
+ }
+ Box2D.postDefs.push(function () {
+ Box2D.Common.b2Settings.VERSION = "2.1alpha";
+ Box2D.Common.b2Settings.USHRT_MAX = 0x0000ffff;
+ Box2D.Common.b2Settings.b2_pi = Math.PI;
+ Box2D.Common.b2Settings.b2_maxManifoldPoints = 2;
+ Box2D.Common.b2Settings.b2_aabbExtension = 0.1;
+ Box2D.Common.b2Settings.b2_aabbMultiplier = 2.0;
+ Box2D.Common.b2Settings.b2_polygonRadius = 2.0 * b2Settings.b2_linearSlop;
+ Box2D.Common.b2Settings.b2_linearSlop = 0.005;
+ Box2D.Common.b2Settings.b2_angularSlop = 2.0 / 180.0 * b2Settings.b2_pi;
+ Box2D.Common.b2Settings.b2_toiSlop = 8.0 * b2Settings.b2_linearSlop;
+ Box2D.Common.b2Settings.b2_maxTOIContactsPerIsland = 32;
+ Box2D.Common.b2Settings.b2_maxTOIJointsPerIsland = 32;
+ Box2D.Common.b2Settings.b2_velocityThreshold = 1.0;
+ Box2D.Common.b2Settings.b2_maxLinearCorrection = 0.2;
+ Box2D.Common.b2Settings.b2_maxAngularCorrection = 8.0 / 180.0 * b2Settings.b2_pi;
+ Box2D.Common.b2Settings.b2_maxTranslation = 2.0;
+ Box2D.Common.b2Settings.b2_maxTranslationSquared = b2Settings.b2_maxTranslation * b2Settings.b2_maxTranslation;
+ Box2D.Common.b2Settings.b2_maxRotation = 0.5 * b2Settings.b2_pi;
+ Box2D.Common.b2Settings.b2_maxRotationSquared = b2Settings.b2_maxRotation * b2Settings.b2_maxRotation;
+ Box2D.Common.b2Settings.b2_contactBaumgarte = 0.2;
+ Box2D.Common.b2Settings.b2_timeToSleep = 0.5;
+ Box2D.Common.b2Settings.b2_linearSleepTolerance = 0.01;
+ Box2D.Common.b2Settings.b2_angularSleepTolerance = 2.0 / 180.0 * b2Settings.b2_pi;
+ });
+})();
+(function () {
+ var b2AABB = Box2D.Collision.b2AABB,
+ b2Color = Box2D.Common.b2Color,
+ b2internal = Box2D.Common.b2internal,
+ b2Settings = Box2D.Common.b2Settings,
+ b2Mat22 = Box2D.Common.Math.b2Mat22,
+ b2Mat33 = Box2D.Common.Math.b2Mat33,
+ b2Math = Box2D.Common.Math.b2Math,
+ b2Sweep = Box2D.Common.Math.b2Sweep,
+ b2Transform = Box2D.Common.Math.b2Transform,
+ b2Vec2 = Box2D.Common.Math.b2Vec2,
+ b2Vec3 = Box2D.Common.Math.b2Vec3;
+
+ b2Mat22.b2Mat22 = function () {
+ this.col1 = new b2Vec2();
+ this.col2 = new b2Vec2();
+ };
+ b2Mat22.prototype.b2Mat22 = function () {
+ this.SetIdentity();
+ }
+ b2Mat22.FromAngle = function (angle) {
+ if (angle === undefined) angle = 0;
+ var mat = new b2Mat22();
+ mat.Set(angle);
+ return mat;
+ }
+ b2Mat22.FromVV = function (c1, c2) {
+ var mat = new b2Mat22();
+ mat.SetVV(c1, c2);
+ return mat;
+ }
+ b2Mat22.prototype.Set = function (angle) {
+ if (angle === undefined) angle = 0;
+ var c = Math.cos(angle);
+ var s = Math.sin(angle);
+ this.col1.x = c;
+ this.col2.x = (-s);
+ this.col1.y = s;
+ this.col2.y = c;
+ }
+ b2Mat22.prototype.SetVV = function (c1, c2) {
+ this.col1.SetV(c1);
+ this.col2.SetV(c2);
+ }
+ b2Mat22.prototype.Copy = function () {
+ var mat = new b2Mat22();
+ mat.SetM(this);
+ return mat;
+ }
+ b2Mat22.prototype.SetM = function (m) {
+ this.col1.SetV(m.col1);
+ this.col2.SetV(m.col2);
+ }
+ b2Mat22.prototype.AddM = function (m) {
+ this.col1.x += m.col1.x;
+ this.col1.y += m.col1.y;
+ this.col2.x += m.col2.x;
+ this.col2.y += m.col2.y;
+ }
+ b2Mat22.prototype.SetIdentity = function () {
+ this.col1.x = 1.0;
+ this.col2.x = 0.0;
+ this.col1.y = 0.0;
+ this.col2.y = 1.0;
+ }
+ b2Mat22.prototype.SetZero = function () {
+ this.col1.x = 0.0;
+ this.col2.x = 0.0;
+ this.col1.y = 0.0;
+ this.col2.y = 0.0;
+ }
+ b2Mat22.prototype.GetAngle = function () {
+ return Math.atan2(this.col1.y, this.col1.x);
+ }
+ b2Mat22.prototype.GetInverse = function (out) {
+ var a = this.col1.x;
+ var b = this.col2.x;
+ var c = this.col1.y;
+ var d = this.col2.y;
+ var det = a * d - b * c;
+ if (det != 0.0) {
+ det = 1.0 / det;
+ }
+ out.col1.x = det * d;
+ out.col2.x = (-det * b);
+ out.col1.y = (-det * c);
+ out.col2.y = det * a;
+ return out;
+ }
+ b2Mat22.prototype.Solve = function (out, bX, bY) {
+ if (bX === undefined) bX = 0;
+ if (bY === undefined) bY = 0;
+ var a11 = this.col1.x;
+ var a12 = this.col2.x;
+ var a21 = this.col1.y;
+ var a22 = this.col2.y;
+ var det = a11 * a22 - a12 * a21;
+ if (det != 0.0) {
+ det = 1.0 / det;
+ }
+ out.x = det * (a22 * bX - a12 * bY);
+ out.y = det * (a11 * bY - a21 * bX);
+ return out;
+ }
+ b2Mat22.prototype.Abs = function () {
+ this.col1.Abs();
+ this.col2.Abs();
+ }
+ b2Mat33.b2Mat33 = function () {
+ this.col1 = new b2Vec3();
+ this.col2 = new b2Vec3();
+ this.col3 = new b2Vec3();
+ };
+ b2Mat33.prototype.b2Mat33 = function (c1, c2, c3) {
+ if (c1 === undefined) c1 = null;
+ if (c2 === undefined) c2 = null;
+ if (c3 === undefined) c3 = null;
+ if (!c1 && !c2 && !c3) {
+ this.col1.SetZero();
+ this.col2.SetZero();
+ this.col3.SetZero();
+ }
+ else {
+ this.col1.SetV(c1);
+ this.col2.SetV(c2);
+ this.col3.SetV(c3);
+ }
+ }
+ b2Mat33.prototype.SetVVV = function (c1, c2, c3) {
+ this.col1.SetV(c1);
+ this.col2.SetV(c2);
+ this.col3.SetV(c3);
+ }
+ b2Mat33.prototype.Copy = function () {
+ return new b2Mat33(this.col1, this.col2, this.col3);
+ }
+ b2Mat33.prototype.SetM = function (m) {
+ this.col1.SetV(m.col1);
+ this.col2.SetV(m.col2);
+ this.col3.SetV(m.col3);
+ }
+ b2Mat33.prototype.AddM = function (m) {
+ this.col1.x += m.col1.x;
+ this.col1.y += m.col1.y;
+ this.col1.z += m.col1.z;
+ this.col2.x += m.col2.x;
+ this.col2.y += m.col2.y;
+ this.col2.z += m.col2.z;
+ this.col3.x += m.col3.x;
+ this.col3.y += m.col3.y;
+ this.col3.z += m.col3.z;
+ }
+ b2Mat33.prototype.SetIdentity = function () {
+ this.col1.x = 1.0;
+ this.col2.x = 0.0;
+ this.col3.x = 0.0;
+ this.col1.y = 0.0;
+ this.col2.y = 1.0;
+ this.col3.y = 0.0;
+ this.col1.z = 0.0;
+ this.col2.z = 0.0;
+ this.col3.z = 1.0;
+ }
+ b2Mat33.prototype.SetZero = function () {
+ this.col1.x = 0.0;
+ this.col2.x = 0.0;
+ this.col3.x = 0.0;
+ this.col1.y = 0.0;
+ this.col2.y = 0.0;
+ this.col3.y = 0.0;
+ this.col1.z = 0.0;
+ this.col2.z = 0.0;
+ this.col3.z = 0.0;
+ }
+ b2Mat33.prototype.Solve22 = function (out, bX, bY) {
+ if (bX === undefined) bX = 0;
+ if (bY === undefined) bY = 0;
+ var a11 = this.col1.x;
+ var a12 = this.col2.x;
+ var a21 = this.col1.y;
+ var a22 = this.col2.y;
+ var det = a11 * a22 - a12 * a21;
+ if (det != 0.0) {
+ det = 1.0 / det;
+ }
+ out.x = det * (a22 * bX - a12 * bY);
+ out.y = det * (a11 * bY - a21 * bX);
+ return out;
+ }
+ b2Mat33.prototype.Solve33 = function (out, bX, bY, bZ) {
+ if (bX === undefined) bX = 0;
+ if (bY === undefined) bY = 0;
+ if (bZ === undefined) bZ = 0;
+ var a11 = this.col1.x;
+ var a21 = this.col1.y;
+ var a31 = this.col1.z;
+ var a12 = this.col2.x;
+ var a22 = this.col2.y;
+ var a32 = this.col2.z;
+ var a13 = this.col3.x;
+ var a23 = this.col3.y;
+ var a33 = this.col3.z;
+ var det = a11 * (a22 * a33 - a32 * a23) + a21 * (a32 * a13 - a12 * a33) + a31 * (a12 * a23 - a22 * a13);
+ if (det != 0.0) {
+ det = 1.0 / det;
+ }
+ out.x = det * (bX * (a22 * a33 - a32 * a23) + bY * (a32 * a13 - a12 * a33) + bZ * (a12 * a23 - a22 * a13));
+ out.y = det * (a11 * (bY * a33 - bZ * a23) + a21 * (bZ * a13 - bX * a33) + a31 * (bX * a23 - bY * a13));
+ out.z = det * (a11 * (a22 * bZ - a32 * bY) + a21 * (a32 * bX - a12 * bZ) + a31 * (a12 * bY - a22 * bX));
+ return out;
+ }
+ b2Math.b2Math = function () {};
+ b2Math.IsValid = function (x) {
+ if (x === undefined) x = 0;
+ return isFinite(x);
+ }
+ b2Math.Dot = function (a, b) {
+ return a.x * b.x + a.y * b.y;
+ }
+ b2Math.CrossVV = function (a, b) {
+ return a.x * b.y - a.y * b.x;
+ }
+ b2Math.CrossVF = function (a, s) {
+ if (s === undefined) s = 0;
+ var v = new b2Vec2(s * a.y, (-s * a.x));
+ return v;
+ }
+ b2Math.CrossFV = function (s, a) {
+ if (s === undefined) s = 0;
+ var v = new b2Vec2((-s * a.y), s * a.x);
+ return v;
+ }
+ b2Math.MulMV = function (A, v) {
+ var u = new b2Vec2(A.col1.x * v.x + A.col2.x * v.y, A.col1.y * v.x + A.col2.y * v.y);
+ return u;
+ }
+ b2Math.MulTMV = function (A, v) {
+ var u = new b2Vec2(b2Math.Dot(v, A.col1), b2Math.Dot(v, A.col2));
+ return u;
+ }
+ b2Math.MulX = function (T, v) {
+ var a = b2Math.MulMV(T.R, v);
+ a.x += T.position.x;
+ a.y += T.position.y;
+ return a;
+ }
+ b2Math.MulXT = function (T, v) {
+ var a = b2Math.SubtractVV(v, T.position);
+ var tX = (a.x * T.R.col1.x + a.y * T.R.col1.y);
+ a.y = (a.x * T.R.col2.x + a.y * T.R.col2.y);
+ a.x = tX;
+ return a;
+ }
+ b2Math.AddVV = function (a, b) {
+ var v = new b2Vec2(a.x + b.x, a.y + b.y);
+ return v;
+ }
+ b2Math.SubtractVV = function (a, b) {
+ var v = new b2Vec2(a.x - b.x, a.y - b.y);
+ return v;
+ }
+ b2Math.Distance = function (a, b) {
+ var cX = a.x - b.x;
+ var cY = a.y - b.y;
+ return Math.sqrt(cX * cX + cY * cY);
+ }
+ b2Math.DistanceSquared = function (a, b) {
+ var cX = a.x - b.x;
+ var cY = a.y - b.y;
+ return (cX * cX + cY * cY);
+ }
+ b2Math.MulFV = function (s, a) {
+ if (s === undefined) s = 0;
+ var v = new b2Vec2(s * a.x, s * a.y);
+ return v;
+ }
+ b2Math.AddMM = function (A, B) {
+ var C = b2Mat22.FromVV(b2Math.AddVV(A.col1, B.col1), b2Math.AddVV(A.col2, B.col2));
+ return C;
+ }
+ b2Math.MulMM = function (A, B) {
+ var C = b2Mat22.FromVV(b2Math.MulMV(A, B.col1), b2Math.MulMV(A, B.col2));
+ return C;
+ }
+ b2Math.MulTMM = function (A, B) {
+ var c1 = new b2Vec2(b2Math.Dot(A.col1, B.col1), b2Math.Dot(A.col2, B.col1));
+ var c2 = new b2Vec2(b2Math.Dot(A.col1, B.col2), b2Math.Dot(A.col2, B.col2));
+ var C = b2Mat22.FromVV(c1, c2);
+ return C;
+ }
+ b2Math.Abs = function (a) {
+ if (a === undefined) a = 0;
+ return a > 0.0 ? a : (-a);
+ }
+ b2Math.AbsV = function (a) {
+ var b = new b2Vec2(b2Math.Abs(a.x), b2Math.Abs(a.y));
+ return b;
+ }
+ b2Math.AbsM = function (A) {
+ var B = b2Mat22.FromVV(b2Math.AbsV(A.col1), b2Math.AbsV(A.col2));
+ return B;
+ }
+ b2Math.Min = function (a, b) {
+ if (a === undefined) a = 0;
+ if (b === undefined) b = 0;
+ return a < b ? a : b;
+ }
+ b2Math.MinV = function (a, b) {
+ var c = new b2Vec2(b2Math.Min(a.x, b.x), b2Math.Min(a.y, b.y));
+ return c;
+ }
+ b2Math.Max = function (a, b) {
+ if (a === undefined) a = 0;
+ if (b === undefined) b = 0;
+ return a > b ? a : b;
+ }
+ b2Math.MaxV = function (a, b) {
+ var c = new b2Vec2(b2Math.Max(a.x, b.x), b2Math.Max(a.y, b.y));
+ return c;
+ }
+ b2Math.Clamp = function (a, low, high) {
+ if (a === undefined) a = 0;
+ if (low === undefined) low = 0;
+ if (high === undefined) high = 0;
+ return a < low ? low : a > high ? high : a;
+ }
+ b2Math.ClampV = function (a, low, high) {
+ return b2Math.MaxV(low, b2Math.MinV(a, high));
+ }
+ b2Math.Swap = function (a, b) {
+ var tmp = a[0];
+ a[0] = b[0];
+ b[0] = tmp;
+ }
+ b2Math.Random = function () {
+ return Math.random() * 2 - 1;
+ }
+ b2Math.RandomRange = function (lo, hi) {
+ if (lo === undefined) lo = 0;
+ if (hi === undefined) hi = 0;
+ var r = Math.random();
+ r = (hi - lo) * r + lo;
+ return r;
+ }
+ b2Math.NextPowerOfTwo = function (x) {
+ if (x === undefined) x = 0;
+ x |= (x >> 1) & 0x7FFFFFFF;
+ x |= (x >> 2) & 0x3FFFFFFF;
+ x |= (x >> 4) & 0x0FFFFFFF;
+ x |= (x >> 8) & 0x00FFFFFF;
+ x |= (x >> 16) & 0x0000FFFF;
+ return x + 1;
+ }
+ b2Math.IsPowerOfTwo = function (x) {
+ if (x === undefined) x = 0;
+ var result = x > 0 && (x & (x - 1)) == 0;
+ return result;
+ }
+ Box2D.postDefs.push(function () {
+ Box2D.Common.Math.b2Math.b2Vec2_zero = new b2Vec2(0.0, 0.0);
+ Box2D.Common.Math.b2Math.b2Mat22_identity = b2Mat22.FromVV(new b2Vec2(1.0, 0.0), new b2Vec2(0.0, 1.0));
+ Box2D.Common.Math.b2Math.b2Transform_identity = new b2Transform(b2Math.b2Vec2_zero, b2Math.b2Mat22_identity);
+ });
+ b2Sweep.b2Sweep = function () {
+ this.localCenter = new b2Vec2();
+ this.c0 = new b2Vec2;
+ this.c = new b2Vec2();
+ };
+ b2Sweep.prototype.Set = function (other) {
+ this.localCenter.SetV(other.localCenter);
+ this.c0.SetV(other.c0);
+ this.c.SetV(other.c);
+ this.a0 = other.a0;
+ this.a = other.a;
+ this.t0 = other.t0;
+ }
+ b2Sweep.prototype.Copy = function () {
+ var copy = new b2Sweep();
+ copy.localCenter.SetV(this.localCenter);
+ copy.c0.SetV(this.c0);
+ copy.c.SetV(this.c);
+ copy.a0 = this.a0;
+ copy.a = this.a;
+ copy.t0 = this.t0;
+ return copy;
+ }
+ b2Sweep.prototype.GetTransform = function (xf, alpha) {
+ if (alpha === undefined) alpha = 0;
+ xf.position.x = (1.0 - alpha) * this.c0.x + alpha * this.c.x;
+ xf.position.y = (1.0 - alpha) * this.c0.y + alpha * this.c.y;
+ var angle = (1.0 - alpha) * this.a0 + alpha * this.a;
+ xf.R.Set(angle);
+ var tMat = xf.R;
+ xf.position.x -= (tMat.col1.x * this.localCenter.x + tMat.col2.x * this.localCenter.y);
+ xf.position.y -= (tMat.col1.y * this.localCenter.x + tMat.col2.y * this.localCenter.y);
+ }
+ b2Sweep.prototype.Advance = function (t) {
+ if (t === undefined) t = 0;
+ if (this.t0 < t && 1.0 - this.t0 > Number.MIN_VALUE) {
+ var alpha = (t - this.t0) / (1.0 - this.t0);
+ this.c0.x = (1.0 - alpha) * this.c0.x + alpha * this.c.x;
+ this.c0.y = (1.0 - alpha) * this.c0.y + alpha * this.c.y;
+ this.a0 = (1.0 - alpha) * this.a0 + alpha * this.a;
+ this.t0 = t;
+ }
+ }
+ b2Transform.b2Transform = function () {
+ this.position = new b2Vec2;
+ this.R = new b2Mat22();
+ };
+ b2Transform.prototype.b2Transform = function (pos, r) {
+ if (pos === undefined) pos = null;
+ if (r === undefined) r = null;
+ if (pos) {
+ this.position.SetV(pos);
+ this.R.SetM(r);
+ }
+ }
+ b2Transform.prototype.Initialize = function (pos, r) {
+ this.position.SetV(pos);
+ this.R.SetM(r);
+ }
+ b2Transform.prototype.SetIdentity = function () {
+ this.position.SetZero();
+ this.R.SetIdentity();
+ }
+ b2Transform.prototype.Set = function (x) {
+ this.position.SetV(x.position);
+ this.R.SetM(x.R);
+ }
+ b2Transform.prototype.GetAngle = function () {
+ return Math.atan2(this.R.col1.y, this.R.col1.x);
+ }
+ b2Vec2.b2Vec2 = function () {};
+ b2Vec2.prototype.b2Vec2 = function (x_, y_) {
+ if (x_ === undefined) x_ = 0;
+ if (y_ === undefined) y_ = 0;
+ this.x = x_;
+ this.y = y_;
+ }
+ b2Vec2.prototype.SetZero = function () {
+ this.x = 0.0;
+ this.y = 0.0;
+ }
+ b2Vec2.prototype.Set = function (x_, y_) {
+ if (x_ === undefined) x_ = 0;
+ if (y_ === undefined) y_ = 0;
+ this.x = x_;
+ this.y = y_;
+ }
+ b2Vec2.prototype.SetV = function (v) {
+ this.x = v.x;
+ this.y = v.y;
+ }
+ b2Vec2.prototype.GetNegative = function () {
+ return new b2Vec2((-this.x), (-this.y));
+ }
+ b2Vec2.prototype.NegativeSelf = function () {
+ this.x = (-this.x);
+ this.y = (-this.y);
+ }
+ b2Vec2.Make = function (x_, y_) {
+ if (x_ === undefined) x_ = 0;
+ if (y_ === undefined) y_ = 0;
+ return new b2Vec2(x_, y_);
+ }
+ b2Vec2.prototype.Copy = function () {
+ return new b2Vec2(this.x, this.y);
+ }
+ b2Vec2.prototype.Add = function (v) {
+ this.x += v.x;
+ this.y += v.y;
+ }
+ b2Vec2.prototype.Subtract = function (v) {
+ this.x -= v.x;
+ this.y -= v.y;
+ }
+ b2Vec2.prototype.Multiply = function (a) {
+ if (a === undefined) a = 0;
+ this.x *= a;
+ this.y *= a;
+ }
+ b2Vec2.prototype.MulM = function (A) {
+ var tX = this.x;
+ this.x = A.col1.x * tX + A.col2.x * this.y;
+ this.y = A.col1.y * tX + A.col2.y * this.y;
+ }
+ b2Vec2.prototype.MulTM = function (A) {
+ var tX = b2Math.Dot(this, A.col1);
+ this.y = b2Math.Dot(this, A.col2);
+ this.x = tX;
+ }
+ b2Vec2.prototype.CrossVF = function (s) {
+ if (s === undefined) s = 0;
+ var tX = this.x;
+ this.x = s * this.y;
+ this.y = (-s * tX);
+ }
+ b2Vec2.prototype.CrossFV = function (s) {
+ if (s === undefined) s = 0;
+ var tX = this.x;
+ this.x = (-s * this.y);
+ this.y = s * tX;
+ }
+ b2Vec2.prototype.MinV = function (b) {
+ this.x = this.x < b.x ? this.x : b.x;
+ this.y = this.y < b.y ? this.y : b.y;
+ }
+ b2Vec2.prototype.MaxV = function (b) {
+ this.x = this.x > b.x ? this.x : b.x;
+ this.y = this.y > b.y ? this.y : b.y;
+ }
+ b2Vec2.prototype.Abs = function () {
+ if (this.x < 0) this.x = (-this.x);
+ if (this.y < 0) this.y = (-this.y);
+ }
+ b2Vec2.prototype.Length = function () {
+ return Math.sqrt(this.x * this.x + this.y * this.y);
+ }
+ b2Vec2.prototype.LengthSquared = function () {
+ return (this.x * this.x + this.y * this.y);
+ }
+ b2Vec2.prototype.Normalize = function () {
+ var length = Math.sqrt(this.x * this.x + this.y * this.y);
+ if (length < Number.MIN_VALUE) {
+ return 0.0;
+ }
+ var invLength = 1.0 / length;
+ this.x *= invLength;
+ this.y *= invLength;
+ return length;
+ }
+ b2Vec2.prototype.IsValid = function () {
+ return b2Math.IsValid(this.x) && b2Math.IsValid(this.y);
+ }
+ b2Vec3.b2Vec3 = function () {};
+ b2Vec3.prototype.b2Vec3 = function (x, y, z) {
+ if (x === undefined) x = 0;
+ if (y === undefined) y = 0;
+ if (z === undefined) z = 0;
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+ b2Vec3.prototype.SetZero = function () {
+ this.x = this.y = this.z = 0.0;
+ }
+ b2Vec3.prototype.Set = function (x, y, z) {
+ if (x === undefined) x = 0;
+ if (y === undefined) y = 0;
+ if (z === undefined) z = 0;
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ }
+ b2Vec3.prototype.SetV = function (v) {
+ this.x = v.x;
+ this.y = v.y;
+ this.z = v.z;
+ }
+ b2Vec3.prototype.GetNegative = function () {
+ return new b2Vec3((-this.x), (-this.y), (-this.z));
+ }
+ b2Vec3.prototype.NegativeSelf = function () {
+ this.x = (-this.x);
+ this.y = (-this.y);
+ this.z = (-this.z);
+ }
+ b2Vec3.prototype.Copy = function () {
+ return new b2Vec3(this.x, this.y, this.z);
+ }
+ b2Vec3.prototype.Add = function (v) {
+ this.x += v.x;
+ this.y += v.y;
+ this.z += v.z;
+ }
+ b2Vec3.prototype.Subtract = function (v) {
+ this.x -= v.x;
+ this.y -= v.y;
+ this.z -= v.z;
+ }
+ b2Vec3.prototype.Multiply = function (a) {
+ if (a === undefined) a = 0;
+ this.x *= a;
+ this.y *= a;
+ this.z *= a;
+ }
+})();
+(function () {
+ var b2ControllerEdge = Box2D.Dynamics.Controllers.b2ControllerEdge,
+ b2Mat22 = Box2D.Common.Math.b2Mat22,
+ b2Mat33 = Box2D.Common.Math.b2Mat33,
+ b2Math = Box2D.Common.Math.b2Math,
+ b2Sweep = Box2D.Common.Math.b2Sweep,
+ b2Transform = Box2D.Common.Math.b2Transform,
+ b2Vec2 = Box2D.Common.Math.b2Vec2,
+ b2Vec3 = Box2D.Common.Math.b2Vec3,
+ b2Color = Box2D.Common.b2Color,
+ b2internal = Box2D.Common.b2internal,
+ b2Settings = Box2D.Common.b2Settings,
+ b2AABB = Box2D.Collision.b2AABB,
+ b2Bound = Box2D.Collision.b2Bound,
+ b2BoundValues = Box2D.Collision.b2BoundValues,
+ b2Collision = Box2D.Collision.b2Collision,
+ b2ContactID = Box2D.Collision.b2ContactID,
+ b2ContactPoint = Box2D.Collision.b2ContactPoint,
+ b2Distance = Box2D.Collision.b2Distance,
+ b2DistanceInput = Box2D.Collision.b2DistanceInput,
+ b2DistanceOutput = Box2D.Collision.b2DistanceOutput,
+ b2DistanceProxy = Box2D.Collision.b2DistanceProxy,
+ b2DynamicTree = Box2D.Collision.b2DynamicTree,
+ b2DynamicTreeBroadPhase = Box2D.Collision.b2DynamicTreeBroadPhase,
+ b2DynamicTreeNode = Box2D.Collision.b2DynamicTreeNode,
+ b2DynamicTreePair = Box2D.Collision.b2DynamicTreePair,
+ b2Manifold = Box2D.Collision.b2Manifold,
+ b2ManifoldPoint = Box2D.Collision.b2ManifoldPoint,
+ b2Point = Box2D.Collision.b2Point,
+ b2RayCastInput = Box2D.Collision.b2RayCastInput,
+ b2RayCastOutput = Box2D.Collision.b2RayCastOutput,
+ b2Segment = Box2D.Collision.b2Segment,
+ b2SeparationFunction = Box2D.Collision.b2SeparationFunction,
+ b2Simplex = Box2D.Collision.b2Simplex,
+ b2SimplexCache = Box2D.Collision.b2SimplexCache,
+ b2SimplexVertex = Box2D.Collision.b2SimplexVertex,
+ b2TimeOfImpact = Box2D.Collision.b2TimeOfImpact,
+ b2TOIInput = Box2D.Collision.b2TOIInput,
+ b2WorldManifold = Box2D.Collision.b2WorldManifold,
+ ClipVertex = Box2D.Collision.ClipVertex,
+ Features = Box2D.Collision.Features,
+ IBroadPhase = Box2D.Collision.IBroadPhase,
+ b2CircleShape = Box2D.Collision.Shapes.b2CircleShape,
+ b2EdgeChainDef = Box2D.Collision.Shapes.b2EdgeChainDef,
+ b2EdgeShape = Box2D.Collision.Shapes.b2EdgeShape,
+ b2MassData = Box2D.Collision.Shapes.b2MassData,
+ b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape,
+ b2Shape = Box2D.Collision.Shapes.b2Shape,
+ b2Body = Box2D.Dynamics.b2Body,
+ b2BodyDef = Box2D.Dynamics.b2BodyDef,
+ b2ContactFilter = Box2D.Dynamics.b2ContactFilter,
+ b2ContactImpulse = Box2D.Dynamics.b2ContactImpulse,
+ b2ContactListener = Box2D.Dynamics.b2ContactListener,
+ b2ContactManager = Box2D.Dynamics.b2ContactManager,
+ b2DebugDraw = Box2D.Dynamics.b2DebugDraw,
+ b2DestructionListener = Box2D.Dynamics.b2DestructionListener,
+ b2FilterData = Box2D.Dynamics.b2FilterData,
+ b2Fixture = Box2D.Dynamics.b2Fixture,
+ b2FixtureDef = Box2D.Dynamics.b2FixtureDef,
+ b2Island = Box2D.Dynamics.b2Island,
+ b2TimeStep = Box2D.Dynamics.b2TimeStep,
+ b2World = Box2D.Dynamics.b2World,
+ b2CircleContact = Box2D.Dynamics.Contacts.b2CircleContact,
+ b2Contact = Box2D.Dynamics.Contacts.b2Contact,
+ b2ContactConstraint = Box2D.Dynamics.Contacts.b2ContactConstraint,
+ b2ContactConstraintPoint = Box2D.Dynamics.Contacts.b2ContactConstraintPoint,
+ b2ContactEdge = Box2D.Dynamics.Contacts.b2ContactEdge,
+ b2ContactFactory = Box2D.Dynamics.Contacts.b2ContactFactory,
+ b2ContactRegister = Box2D.Dynamics.Contacts.b2ContactRegister,
+ b2ContactResult = Box2D.Dynamics.Contacts.b2ContactResult,
+ b2ContactSolver = Box2D.Dynamics.Contacts.b2ContactSolver,
+ b2EdgeAndCircleContact = Box2D.Dynamics.Contacts.b2EdgeAndCircleContact,
+ b2NullContact = Box2D.Dynamics.Contacts.b2NullContact,
+ b2PolyAndCircleContact = Box2D.Dynamics.Contacts.b2PolyAndCircleContact,
+ b2PolyAndEdgeContact = Box2D.Dynamics.Contacts.b2PolyAndEdgeContact,
+ b2PolygonContact = Box2D.Dynamics.Contacts.b2PolygonContact,
+ b2PositionSolverManifold = Box2D.Dynamics.Contacts.b2PositionSolverManifold,
+ b2Controller = Box2D.Dynamics.Controllers.b2Controller,
+ b2DistanceJoint = Box2D.Dynamics.Joints.b2DistanceJoint,
+ b2DistanceJointDef = Box2D.Dynamics.Joints.b2DistanceJointDef,
+ b2FrictionJoint = Box2D.Dynamics.Joints.b2FrictionJoint,
+ b2FrictionJointDef = Box2D.Dynamics.Joints.b2FrictionJointDef,
+ b2GearJoint = Box2D.Dynamics.Joints.b2GearJoint,
+ b2GearJointDef = Box2D.Dynamics.Joints.b2GearJointDef,
+ b2Jacobian = Box2D.Dynamics.Joints.b2Jacobian,
+ b2Joint = Box2D.Dynamics.Joints.b2Joint,
+ b2JointDef = Box2D.Dynamics.Joints.b2JointDef,
+ b2JointEdge = Box2D.Dynamics.Joints.b2JointEdge,
+ b2LineJoint = Box2D.Dynamics.Joints.b2LineJoint,
+ b2LineJointDef = Box2D.Dynamics.Joints.b2LineJointDef,
+ b2MouseJoint = Box2D.Dynamics.Joints.b2MouseJoint,
+ b2MouseJointDef = Box2D.Dynamics.Joints.b2MouseJointDef,
+ b2PrismaticJoint = Box2D.Dynamics.Joints.b2PrismaticJoint,
+ b2PrismaticJointDef = Box2D.Dynamics.Joints.b2PrismaticJointDef,
+ b2PulleyJoint = Box2D.Dynamics.Joints.b2PulleyJoint,
+ b2PulleyJointDef = Box2D.Dynamics.Joints.b2PulleyJointDef,
+ b2RevoluteJoint = Box2D.Dynamics.Joints.b2RevoluteJoint,
+ b2RevoluteJointDef = Box2D.Dynamics.Joints.b2RevoluteJointDef,
+ b2WeldJoint = Box2D.Dynamics.Joints.b2WeldJoint,
+ b2WeldJointDef = Box2D.Dynamics.Joints.b2WeldJointDef;
+
+ b2Body.b2Body = function () {
+ this.m_xf = new b2Transform();
+ this.m_sweep = new b2Sweep();
+ this.m_linearVelocity = new b2Vec2();
+ this.m_force = new b2Vec2();
+ };
+ b2Body.prototype.connectEdges = function (s1, s2, angle1) {
+ if (angle1 === undefined) angle1 = 0;
+ var angle2 = Math.atan2(s2.GetDirectionVector().y, s2.GetDirectionVector().x);
+ var coreOffset = Math.tan((angle2 - angle1) * 0.5);
+ var core = b2Math.MulFV(coreOffset, s2.GetDirectionVector());
+ core = b2Math.SubtractVV(core, s2.GetNormalVector());
+ core = b2Math.MulFV(b2Settings.b2_toiSlop, core);
+ core = b2Math.AddVV(core, s2.GetVertex1());
+ var cornerDir = b2Math.AddVV(s1.GetDirectionVector(), s2.GetDirectionVector());
+ cornerDir.Normalize();
+ var convex = b2Math.Dot(s1.GetDirectionVector(), s2.GetNormalVector()) > 0.0;
+ s1.SetNextEdge(s2, core, cornerDir, convex);
+ s2.SetPrevEdge(s1, core, cornerDir, convex);
+ return angle2;
+ }
+ b2Body.prototype.CreateFixture = function (def) {
+ if (this.m_world.IsLocked() == true) {
+ return null;
+ }
+ var fixture = new b2Fixture();
+ fixture.Create(this, this.m_xf, def);
+ if (this.m_flags & b2Body.e_activeFlag) {
+ var broadPhase = this.m_world.m_contactManager.m_broadPhase;
+ fixture.CreateProxy(broadPhase, this.m_xf);
+ }
+ fixture.m_next = this.m_fixtureList;
+ this.m_fixtureList = fixture;
+ ++this.m_fixtureCount;
+ fixture.m_body = this;
+ if (fixture.m_density > 0.0) {
+ this.ResetMassData();
+ }
+ this.m_world.m_flags |= b2World.e_newFixture;
+ return fixture;
+ }
+ b2Body.prototype.CreateFixture2 = function (shape, density) {
+ if (density === undefined) density = 0.0;
+ var def = new b2FixtureDef();
+ def.shape = shape;
+ def.density = density;
+ return this.CreateFixture(def);
+ }
+ b2Body.prototype.DestroyFixture = function (fixture) {
+ if (this.m_world.IsLocked() == true) {
+ return;
+ }
+ var node = this.m_fixtureList;
+ var ppF = null;
+ var found = false;
+ while (node != null) {
+ if (node == fixture) {
+ if (ppF) ppF.m_next = fixture.m_next;
+ else this.m_fixtureList = fixture.m_next;
+ found = true;
+ break;
+ }
+ ppF = node;
+ node = node.m_next;
+ }
+ var edge = this.m_contactList;
+ while (edge) {
+ var c = edge.contact;
+ edge = edge.next;
+ var fixtureA = c.GetFixtureA();
+ var fixtureB = c.GetFixtureB();
+ if (fixture == fixtureA || fixture == fixtureB) {
+ this.m_world.m_contactManager.Destroy(c);
+ }
+ }
+ if (this.m_flags & b2Body.e_activeFlag) {
+ var broadPhase = this.m_world.m_contactManager.m_broadPhase;
+ fixture.DestroyProxy(broadPhase);
+ }
+ else {}
+ fixture.Destroy();
+ fixture.m_body = null;
+ fixture.m_next = null;
+ --this.m_fixtureCount;
+ this.ResetMassData();
+ }
+ b2Body.prototype.SetPositionAndAngle = function (position, angle) {
+ if (angle === undefined) angle = 0;
+ var f;
+ if (this.m_world.IsLocked() == true) {
+ return;
+ }
+ this.m_xf.R.Set(angle);
+ this.m_xf.position.SetV(position);
+ var tMat = this.m_xf.R;
+ var tVec = this.m_sweep.localCenter;
+ this.m_sweep.c.x = (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ this.m_sweep.c.y = (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ this.m_sweep.c.x += this.m_xf.position.x;
+ this.m_sweep.c.y += this.m_xf.position.y;
+ this.m_sweep.c0.SetV(this.m_sweep.c);
+ this.m_sweep.a0 = this.m_sweep.a = angle;
+ var broadPhase = this.m_world.m_contactManager.m_broadPhase;
+ for (f = this.m_fixtureList;
+ f; f = f.m_next) {
+ f.Synchronize(broadPhase, this.m_xf, this.m_xf);
+ }
+ this.m_world.m_contactManager.FindNewContacts();
+ }
+ b2Body.prototype.SetTransform = function (xf) {
+ this.SetPositionAndAngle(xf.position, xf.GetAngle());
+ }
+ b2Body.prototype.GetTransform = function () {
+ return this.m_xf;
+ }
+ b2Body.prototype.GetPosition = function () {
+ return this.m_xf.position;
+ }
+ b2Body.prototype.SetPosition = function (position) {
+ this.SetPositionAndAngle(position, this.GetAngle());
+ }
+ b2Body.prototype.GetAngle = function () {
+ return this.m_sweep.a;
+ }
+ b2Body.prototype.SetAngle = function (angle) {
+ if (angle === undefined) angle = 0;
+ this.SetPositionAndAngle(this.GetPosition(), angle);
+ }
+ b2Body.prototype.GetWorldCenter = function () {
+ return this.m_sweep.c;
+ }
+ b2Body.prototype.GetLocalCenter = function () {
+ return this.m_sweep.localCenter;
+ }
+ b2Body.prototype.SetLinearVelocity = function (v) {
+ if (this.m_type == b2Body.b2_staticBody) {
+ return;
+ }
+ this.m_linearVelocity.SetV(v);
+ }
+ b2Body.prototype.GetLinearVelocity = function () {
+ return this.m_linearVelocity;
+ }
+ b2Body.prototype.SetAngularVelocity = function (omega) {
+ if (omega === undefined) omega = 0;
+ if (this.m_type == b2Body.b2_staticBody) {
+ return;
+ }
+ this.m_angularVelocity = omega;
+ }
+ b2Body.prototype.GetAngularVelocity = function () {
+ return this.m_angularVelocity;
+ }
+ b2Body.prototype.GetDefinition = function () {
+ var bd = new b2BodyDef();
+ bd.type = this.GetType();
+ bd.allowSleep = (this.m_flags & b2Body.e_allowSleepFlag) == b2Body.e_allowSleepFlag;
+ bd.angle = this.GetAngle();
+ bd.angularDamping = this.m_angularDamping;
+ bd.angularVelocity = this.m_angularVelocity;
+ bd.fixedRotation = (this.m_flags & b2Body.e_fixedRotationFlag) == b2Body.e_fixedRotationFlag;
+ bd.bullet = (this.m_flags & b2Body.e_bulletFlag) == b2Body.e_bulletFlag;
+ bd.awake = (this.m_flags & b2Body.e_awakeFlag) == b2Body.e_awakeFlag;
+ bd.linearDamping = this.m_linearDamping;
+ bd.linearVelocity.SetV(this.GetLinearVelocity());
+ bd.position = this.GetPosition();
+ bd.userData = this.GetUserData();
+ return bd;
+ }
+ b2Body.prototype.ApplyForce = function (force, point) {
+ if (this.m_type != b2Body.b2_dynamicBody) {
+ return;
+ }
+ if (this.IsAwake() == false) {
+ this.SetAwake(true);
+ }
+ this.m_force.x += force.x;
+ this.m_force.y += force.y;
+ this.m_torque += ((point.x - this.m_sweep.c.x) * force.y - (point.y - this.m_sweep.c.y) * force.x);
+ }
+ b2Body.prototype.ApplyTorque = function (torque) {
+ if (torque === undefined) torque = 0;
+ if (this.m_type != b2Body.b2_dynamicBody) {
+ return;
+ }
+ if (this.IsAwake() == false) {
+ this.SetAwake(true);
+ }
+ this.m_torque += torque;
+ }
+ b2Body.prototype.ApplyImpulse = function (impulse, point) {
+ if (this.m_type != b2Body.b2_dynamicBody) {
+ return;
+ }
+ if (this.IsAwake() == false) {
+ this.SetAwake(true);
+ }
+ this.m_linearVelocity.x += this.m_invMass * impulse.x;
+ this.m_linearVelocity.y += this.m_invMass * impulse.y;
+ this.m_angularVelocity += this.m_invI * ((point.x - this.m_sweep.c.x) * impulse.y - (point.y - this.m_sweep.c.y) * impulse.x);
+ }
+ b2Body.prototype.Split = function (callback) {
+ var linearVelocity = this.GetLinearVelocity().Copy();
+ var angularVelocity = this.GetAngularVelocity();
+ var center = this.GetWorldCenter();
+ var body1 = this;
+ var body2 = this.m_world.CreateBody(this.GetDefinition());
+ var prev;
+ for (var f = body1.m_fixtureList; f;) {
+ if (callback(f)) {
+ var next = f.m_next;
+ if (prev) {
+ prev.m_next = next;
+ }
+ else {
+ body1.m_fixtureList = next;
+ }
+ body1.m_fixtureCount--;
+ f.m_next = body2.m_fixtureList;
+ body2.m_fixtureList = f;
+ body2.m_fixtureCount++;
+ f.m_body = body2;
+ f = next;
+ }
+ else {
+ prev = f;
+ f = f.m_next;
+ }
+ }
+ body1.ResetMassData();
+ body2.ResetMassData();
+ var center1 = body1.GetWorldCenter();
+ var center2 = body2.GetWorldCenter();
+ var velocity1 = b2Math.AddVV(linearVelocity, b2Math.CrossFV(angularVelocity, b2Math.SubtractVV(center1, center)));
+ var velocity2 = b2Math.AddVV(linearVelocity, b2Math.CrossFV(angularVelocity, b2Math.SubtractVV(center2, center)));
+ body1.SetLinearVelocity(velocity1);
+ body2.SetLinearVelocity(velocity2);
+ body1.SetAngularVelocity(angularVelocity);
+ body2.SetAngularVelocity(angularVelocity);
+ body1.SynchronizeFixtures();
+ body2.SynchronizeFixtures();
+ return body2;
+ }
+ b2Body.prototype.Merge = function (other) {
+ var f;
+ for (f = other.m_fixtureList;
+ f;) {
+ var next = f.m_next;
+ other.m_fixtureCount--;
+ f.m_next = this.m_fixtureList;
+ this.m_fixtureList = f;
+ this.m_fixtureCount++;
+ f.m_body = body2;
+ f = next;
+ }
+ body1.m_fixtureCount = 0;
+ var body1 = this;
+ var body2 = other;
+ var center1 = body1.GetWorldCenter();
+ var center2 = body2.GetWorldCenter();
+ var velocity1 = body1.GetLinearVelocity().Copy();
+ var velocity2 = body2.GetLinearVelocity().Copy();
+ var angular1 = body1.GetAngularVelocity();
+ var angular = body2.GetAngularVelocity();
+ body1.ResetMassData();
+ this.SynchronizeFixtures();
+ }
+ b2Body.prototype.GetMass = function () {
+ return this.m_mass;
+ }
+ b2Body.prototype.GetInertia = function () {
+ return this.m_I;
+ }
+ b2Body.prototype.GetMassData = function (data) {
+ data.mass = this.m_mass;
+ data.I = this.m_I;
+ data.center.SetV(this.m_sweep.localCenter);
+ }
+ b2Body.prototype.SetMassData = function (massData) {
+ b2Settings.b2Assert(this.m_world.IsLocked() == false);
+ if (this.m_world.IsLocked() == true) {
+ return;
+ }
+ if (this.m_type != b2Body.b2_dynamicBody) {
+ return;
+ }
+ this.m_invMass = 0.0;
+ this.m_I = 0.0;
+ this.m_invI = 0.0;
+ this.m_mass = massData.mass;
+ if (this.m_mass <= 0.0) {
+ this.m_mass = 1.0;
+ }
+ this.m_invMass = 1.0 / this.m_mass;
+ if (massData.I > 0.0 && (this.m_flags & b2Body.e_fixedRotationFlag) == 0) {
+ this.m_I = massData.I - this.m_mass * (massData.center.x * massData.center.x + massData.center.y * massData.center.y);
+ this.m_invI = 1.0 / this.m_I;
+ }
+ var oldCenter = this.m_sweep.c.Copy();
+ this.m_sweep.localCenter.SetV(massData.center);
+ this.m_sweep.c0.SetV(b2Math.MulX(this.m_xf, this.m_sweep.localCenter));
+ this.m_sweep.c.SetV(this.m_sweep.c0);
+ this.m_linearVelocity.x += this.m_angularVelocity * (-(this.m_sweep.c.y - oldCenter.y));
+ this.m_linearVelocity.y += this.m_angularVelocity * (+(this.m_sweep.c.x - oldCenter.x));
+ }
+ b2Body.prototype.ResetMassData = function () {
+ this.m_mass = 0.0;
+ this.m_invMass = 0.0;
+ this.m_I = 0.0;
+ this.m_invI = 0.0;
+ this.m_sweep.localCenter.SetZero();
+ if (this.m_type == b2Body.b2_staticBody || this.m_type == b2Body.b2_kinematicBody) {
+ return;
+ }
+ var center = b2Vec2.Make(0, 0);
+ for (var f = this.m_fixtureList; f; f = f.m_next) {
+ if (f.m_density == 0.0) {
+ continue;
+ }
+ var massData = f.GetMassData();
+ this.m_mass += massData.mass;
+ center.x += massData.center.x * massData.mass;
+ center.y += massData.center.y * massData.mass;
+ this.m_I += massData.I;
+ }
+ if (this.m_mass > 0.0) {
+ this.m_invMass = 1.0 / this.m_mass;
+ center.x *= this.m_invMass;
+ center.y *= this.m_invMass;
+ }
+ else {
+ this.m_mass = 1.0;
+ this.m_invMass = 1.0;
+ }
+ if (this.m_I > 0.0 && (this.m_flags & b2Body.e_fixedRotationFlag) == 0) {
+ this.m_I -= this.m_mass * (center.x * center.x + center.y * center.y);
+ this.m_I *= this.m_inertiaScale;
+ b2Settings.b2Assert(this.m_I > 0);
+ this.m_invI = 1.0 / this.m_I;
+ }
+ else {
+ this.m_I = 0.0;
+ this.m_invI = 0.0;
+ }
+ var oldCenter = this.m_sweep.c.Copy();
+ this.m_sweep.localCenter.SetV(center);
+ this.m_sweep.c0.SetV(b2Math.MulX(this.m_xf, this.m_sweep.localCenter));
+ this.m_sweep.c.SetV(this.m_sweep.c0);
+ this.m_linearVelocity.x += this.m_angularVelocity * (-(this.m_sweep.c.y - oldCenter.y));
+ this.m_linearVelocity.y += this.m_angularVelocity * (+(this.m_sweep.c.x - oldCenter.x));
+ }
+ b2Body.prototype.GetWorldPoint = function (localPoint) {
+ var A = this.m_xf.R;
+ var u = new b2Vec2(A.col1.x * localPoint.x + A.col2.x * localPoint.y, A.col1.y * localPoint.x + A.col2.y * localPoint.y);
+ u.x += this.m_xf.position.x;
+ u.y += this.m_xf.position.y;
+ return u;
+ }
+ b2Body.prototype.GetWorldVector = function (localVector) {
+ return b2Math.MulMV(this.m_xf.R, localVector);
+ }
+ b2Body.prototype.GetLocalPoint = function (worldPoint) {
+ return b2Math.MulXT(this.m_xf, worldPoint);
+ }
+ b2Body.prototype.GetLocalVector = function (worldVector) {
+ return b2Math.MulTMV(this.m_xf.R, worldVector);
+ }
+ b2Body.prototype.GetLinearVelocityFromWorldPoint = function (worldPoint) {
+ return new b2Vec2(this.m_linearVelocity.x - this.m_angularVelocity * (worldPoint.y - this.m_sweep.c.y), this.m_linearVelocity.y + this.m_angularVelocity * (worldPoint.x - this.m_sweep.c.x));
+ }
+ b2Body.prototype.GetLinearVelocityFromLocalPoint = function (localPoint) {
+ var A = this.m_xf.R;
+ var worldPoint = new b2Vec2(A.col1.x * localPoint.x + A.col2.x * localPoint.y, A.col1.y * localPoint.x + A.col2.y * localPoint.y);
+ worldPoint.x += this.m_xf.position.x;
+ worldPoint.y += this.m_xf.position.y;
+ return new b2Vec2(this.m_linearVelocity.x - this.m_angularVelocity * (worldPoint.y - this.m_sweep.c.y), this.m_linearVelocity.y + this.m_angularVelocity * (worldPoint.x - this.m_sweep.c.x));
+ }
+ b2Body.prototype.GetLinearDamping = function () {
+ return this.m_linearDamping;
+ }
+ b2Body.prototype.SetLinearDamping = function (linearDamping) {
+ if (linearDamping === undefined) linearDamping = 0;
+ this.m_linearDamping = linearDamping;
+ }
+ b2Body.prototype.GetAngularDamping = function () {
+ return this.m_angularDamping;
+ }
+ b2Body.prototype.SetAngularDamping = function (angularDamping) {
+ if (angularDamping === undefined) angularDamping = 0;
+ this.m_angularDamping = angularDamping;
+ }
+ b2Body.prototype.SetType = function (type) {
+ if (type === undefined) type = 0;
+ if (this.m_type == type) {
+ return;
+ }
+ this.m_type = type;
+ this.ResetMassData();
+ if (this.m_type == b2Body.b2_staticBody) {
+ this.m_linearVelocity.SetZero();
+ this.m_angularVelocity = 0.0;
+ }
+ this.SetAwake(true);
+ this.m_force.SetZero();
+ this.m_torque = 0.0;
+ for (var ce = this.m_contactList; ce; ce = ce.next) {
+ ce.contact.FlagForFiltering();
+ }
+ }
+ b2Body.prototype.GetType = function () {
+ return this.m_type;
+ }
+ b2Body.prototype.SetBullet = function (flag) {
+ if (flag) {
+ this.m_flags |= b2Body.e_bulletFlag;
+ }
+ else {
+ this.m_flags &= ~b2Body.e_bulletFlag;
+ }
+ }
+ b2Body.prototype.IsBullet = function () {
+ return (this.m_flags & b2Body.e_bulletFlag) == b2Body.e_bulletFlag;
+ }
+ b2Body.prototype.SetSleepingAllowed = function (flag) {
+ if (flag) {
+ this.m_flags |= b2Body.e_allowSleepFlag;
+ }
+ else {
+ this.m_flags &= ~b2Body.e_allowSleepFlag;
+ this.SetAwake(true);
+ }
+ }
+ b2Body.prototype.SetAwake = function (flag) {
+ if (flag) {
+ this.m_flags |= b2Body.e_awakeFlag;
+ this.m_sleepTime = 0.0;
+ }
+ else {
+ this.m_flags &= ~b2Body.e_awakeFlag;
+ this.m_sleepTime = 0.0;
+ this.m_linearVelocity.SetZero();
+ this.m_angularVelocity = 0.0;
+ this.m_force.SetZero();
+ this.m_torque = 0.0;
+ }
+ }
+ b2Body.prototype.IsAwake = function () {
+ return (this.m_flags & b2Body.e_awakeFlag) == b2Body.e_awakeFlag;
+ }
+ b2Body.prototype.SetFixedRotation = function (fixed) {
+ if (fixed) {
+ this.m_flags |= b2Body.e_fixedRotationFlag;
+ }
+ else {
+ this.m_flags &= ~b2Body.e_fixedRotationFlag;
+ }
+ this.ResetMassData();
+ }
+ b2Body.prototype.IsFixedRotation = function () {
+ return (this.m_flags & b2Body.e_fixedRotationFlag) == b2Body.e_fixedRotationFlag;
+ }
+ b2Body.prototype.SetActive = function (flag) {
+ if (flag == this.IsActive()) {
+ return;
+ }
+ var broadPhase;
+ var f;
+ if (flag) {
+ this.m_flags |= b2Body.e_activeFlag;
+ broadPhase = this.m_world.m_contactManager.m_broadPhase;
+ for (f = this.m_fixtureList;
+ f; f = f.m_next) {
+ f.CreateProxy(broadPhase, this.m_xf);
+ }
+ }
+ else {
+ this.m_flags &= ~b2Body.e_activeFlag;
+ broadPhase = this.m_world.m_contactManager.m_broadPhase;
+ for (f = this.m_fixtureList;
+ f; f = f.m_next) {
+ f.DestroyProxy(broadPhase);
+ }
+ var ce = this.m_contactList;
+ while (ce) {
+ var ce0 = ce;
+ ce = ce.next;
+ this.m_world.m_contactManager.Destroy(ce0.contact);
+ }
+ this.m_contactList = null;
+ }
+ }
+ b2Body.prototype.IsActive = function () {
+ return (this.m_flags & b2Body.e_activeFlag) == b2Body.e_activeFlag;
+ }
+ b2Body.prototype.IsSleepingAllowed = function () {
+ return (this.m_flags & b2Body.e_allowSleepFlag) == b2Body.e_allowSleepFlag;
+ }
+ b2Body.prototype.GetFixtureList = function () {
+ return this.m_fixtureList;
+ }
+ b2Body.prototype.GetJointList = function () {
+ return this.m_jointList;
+ }
+ b2Body.prototype.GetControllerList = function () {
+ return this.m_controllerList;
+ }
+ b2Body.prototype.GetContactList = function () {
+ return this.m_contactList;
+ }
+ b2Body.prototype.GetNext = function () {
+ return this.m_next;
+ }
+ b2Body.prototype.GetUserData = function () {
+ return this.m_userData;
+ }
+ b2Body.prototype.SetUserData = function (data) {
+ this.m_userData = data;
+ }
+ b2Body.prototype.GetWorld = function () {
+ return this.m_world;
+ }
+ b2Body.prototype.b2Body = function (bd, world) {
+ this.m_flags = 0;
+ if (bd.bullet) {
+ this.m_flags |= b2Body.e_bulletFlag;
+ }
+ if (bd.fixedRotation) {
+ this.m_flags |= b2Body.e_fixedRotationFlag;
+ }
+ if (bd.allowSleep) {
+ this.m_flags |= b2Body.e_allowSleepFlag;
+ }
+ if (bd.awake) {
+ this.m_flags |= b2Body.e_awakeFlag;
+ }
+ if (bd.active) {
+ this.m_flags |= b2Body.e_activeFlag;
+ }
+ this.m_world = world;
+ this.m_xf.position.SetV(bd.position);
+ this.m_xf.R.Set(bd.angle);
+ this.m_sweep.localCenter.SetZero();
+ this.m_sweep.t0 = 1.0;
+ this.m_sweep.a0 = this.m_sweep.a = bd.angle;
+ var tMat = this.m_xf.R;
+ var tVec = this.m_sweep.localCenter;
+ this.m_sweep.c.x = (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ this.m_sweep.c.y = (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ this.m_sweep.c.x += this.m_xf.position.x;
+ this.m_sweep.c.y += this.m_xf.position.y;
+ this.m_sweep.c0.SetV(this.m_sweep.c);
+ this.m_jointList = null;
+ this.m_controllerList = null;
+ this.m_contactList = null;
+ this.m_controllerCount = 0;
+ this.m_prev = null;
+ this.m_next = null;
+ this.m_linearVelocity.SetV(bd.linearVelocity);
+ this.m_angularVelocity = bd.angularVelocity;
+ this.m_linearDamping = bd.linearDamping;
+ this.m_angularDamping = bd.angularDamping;
+ this.m_force.Set(0.0, 0.0);
+ this.m_torque = 0.0;
+ this.m_sleepTime = 0.0;
+ this.m_type = bd.type;
+ if (this.m_type == b2Body.b2_dynamicBody) {
+ this.m_mass = 1.0;
+ this.m_invMass = 1.0;
+ }
+ else {
+ this.m_mass = 0.0;
+ this.m_invMass = 0.0;
+ }
+ this.m_I = 0.0;
+ this.m_invI = 0.0;
+ this.m_inertiaScale = bd.inertiaScale;
+ this.m_userData = bd.userData;
+ this.m_fixtureList = null;
+ this.m_fixtureCount = 0;
+ }
+ b2Body.prototype.SynchronizeFixtures = function () {
+ var xf1 = b2Body.s_xf1;
+ xf1.R.Set(this.m_sweep.a0);
+ var tMat = xf1.R;
+ var tVec = this.m_sweep.localCenter;
+ xf1.position.x = this.m_sweep.c0.x - (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ xf1.position.y = this.m_sweep.c0.y - (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ var f;
+ var broadPhase = this.m_world.m_contactManager.m_broadPhase;
+ for (f = this.m_fixtureList;
+ f; f = f.m_next) {
+ f.Synchronize(broadPhase, xf1, this.m_xf);
+ }
+ }
+ b2Body.prototype.SynchronizeTransform = function () {
+ this.m_xf.R.Set(this.m_sweep.a);
+ var tMat = this.m_xf.R;
+ var tVec = this.m_sweep.localCenter;
+ this.m_xf.position.x = this.m_sweep.c.x - (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ this.m_xf.position.y = this.m_sweep.c.y - (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ }
+ b2Body.prototype.ShouldCollide = function (other) {
+ if (this.m_type != b2Body.b2_dynamicBody && other.m_type != b2Body.b2_dynamicBody) {
+ return false;
+ }
+ for (var jn = this.m_jointList; jn; jn = jn.next) {
+ if (jn.other == other) if (jn.joint.m_collideConnected == false) {
+ return false;
+ }
+ }
+ return true;
+ }
+ b2Body.prototype.Advance = function (t) {
+ if (t === undefined) t = 0;
+ this.m_sweep.Advance(t);
+ this.m_sweep.c.SetV(this.m_sweep.c0);
+ this.m_sweep.a = this.m_sweep.a0;
+ this.SynchronizeTransform();
+ }
+ Box2D.postDefs.push(function () {
+ Box2D.Dynamics.b2Body.s_xf1 = new b2Transform();
+ Box2D.Dynamics.b2Body.e_islandFlag = 0x0001;
+ Box2D.Dynamics.b2Body.e_awakeFlag = 0x0002;
+ Box2D.Dynamics.b2Body.e_allowSleepFlag = 0x0004;
+ Box2D.Dynamics.b2Body.e_bulletFlag = 0x0008;
+ Box2D.Dynamics.b2Body.e_fixedRotationFlag = 0x0010;
+ Box2D.Dynamics.b2Body.e_activeFlag = 0x0020;
+ Box2D.Dynamics.b2Body.b2_staticBody = 0;
+ Box2D.Dynamics.b2Body.b2_kinematicBody = 1;
+ Box2D.Dynamics.b2Body.b2_dynamicBody = 2;
+ });
+ b2BodyDef.b2BodyDef = function () {
+ this.position = new b2Vec2();
+ this.linearVelocity = new b2Vec2();
+ };
+ b2BodyDef.prototype.b2BodyDef = function () {
+ this.userData = null;
+ this.position.Set(0.0, 0.0);
+ this.angle = 0.0;
+ this.linearVelocity.Set(0, 0);
+ this.angularVelocity = 0.0;
+ this.linearDamping = 0.0;
+ this.angularDamping = 0.0;
+ this.allowSleep = true;
+ this.awake = true;
+ this.fixedRotation = false;
+ this.bullet = false;
+ this.type = b2Body.b2_staticBody;
+ this.active = true;
+ this.inertiaScale = 1.0;
+ }
+ b2ContactFilter.b2ContactFilter = function () {};
+ b2ContactFilter.prototype.ShouldCollide = function (fixtureA, fixtureB) {
+ var filter1 = fixtureA.GetFilterData();
+ var filter2 = fixtureB.GetFilterData();
+ if (filter1.groupIndex == filter2.groupIndex && filter1.groupIndex != 0) {
+ return filter1.groupIndex > 0;
+ }
+ var collide = (filter1.maskBits & filter2.categoryBits) != 0 && (filter1.categoryBits & filter2.maskBits) != 0;
+ return collide;
+ }
+ b2ContactFilter.prototype.RayCollide = function (userData, fixture) {
+ if (!userData) return true;
+ return this.ShouldCollide((userData instanceof b2Fixture ? userData : null), fixture);
+ }
+ Box2D.postDefs.push(function () {
+ Box2D.Dynamics.b2ContactFilter.b2_defaultFilter = new b2ContactFilter();
+ });
+ b2ContactImpulse.b2ContactImpulse = function () {
+ this.normalImpulses = new Vector_a2j_Number(b2Settings.b2_maxManifoldPoints);
+ this.tangentImpulses = new Vector_a2j_Number(b2Settings.b2_maxManifoldPoints);
+ };
+ b2ContactListener.b2ContactListener = function () {};
+ b2ContactListener.prototype.BeginContact = function (contact) {}
+ b2ContactListener.prototype.EndContact = function (contact) {}
+ b2ContactListener.prototype.PreSolve = function (contact, oldManifold) {}
+ b2ContactListener.prototype.PostSolve = function (contact, impulse) {}
+ Box2D.postDefs.push(function () {
+ Box2D.Dynamics.b2ContactListener.b2_defaultListener = new b2ContactListener();
+ });
+ b2ContactManager.b2ContactManager = function () {};
+ b2ContactManager.prototype.b2ContactManager = function () {
+ this.m_world = null;
+ this.m_contactCount = 0;
+ this.m_contactFilter = b2ContactFilter.b2_defaultFilter;
+ this.m_contactListener = b2ContactListener.b2_defaultListener;
+ this.m_contactFactory = new b2ContactFactory(this.m_allocator);
+ this.m_broadPhase = new b2DynamicTreeBroadPhase();
+ }
+ b2ContactManager.prototype.AddPair = function (proxyUserDataA, proxyUserDataB) {
+ var fixtureA = (proxyUserDataA instanceof b2Fixture ? proxyUserDataA : null);
+ var fixtureB = (proxyUserDataB instanceof b2Fixture ? proxyUserDataB : null);
+ var bodyA = fixtureA.GetBody();
+ var bodyB = fixtureB.GetBody();
+ if (bodyA == bodyB) return;
+ var edge = bodyB.GetContactList();
+ while (edge) {
+ if (edge.other == bodyA) {
+ var fA = edge.contact.GetFixtureA();
+ var fB = edge.contact.GetFixtureB();
+ if (fA == fixtureA && fB == fixtureB) return;
+ if (fA == fixtureB && fB == fixtureA) return;
+ }
+ edge = edge.next;
+ }
+ if (bodyB.ShouldCollide(bodyA) == false) {
+ return;
+ }
+ if (this.m_contactFilter.ShouldCollide(fixtureA, fixtureB) == false) {
+ return;
+ }
+ var c = this.m_contactFactory.Create(fixtureA, fixtureB);
+ fixtureA = c.GetFixtureA();
+ fixtureB = c.GetFixtureB();
+ bodyA = fixtureA.m_body;
+ bodyB = fixtureB.m_body;
+ c.m_prev = null;
+ c.m_next = this.m_world.m_contactList;
+ if (this.m_world.m_contactList != null) {
+ this.m_world.m_contactList.m_prev = c;
+ }
+ this.m_world.m_contactList = c;
+ c.m_nodeA.contact = c;
+ c.m_nodeA.other = bodyB;
+ c.m_nodeA.prev = null;
+ c.m_nodeA.next = bodyA.m_contactList;
+ if (bodyA.m_contactList != null) {
+ bodyA.m_contactList.prev = c.m_nodeA;
+ }
+ bodyA.m_contactList = c.m_nodeA;
+ c.m_nodeB.contact = c;
+ c.m_nodeB.other = bodyA;
+ c.m_nodeB.prev = null;
+ c.m_nodeB.next = bodyB.m_contactList;
+ if (bodyB.m_contactList != null) {
+ bodyB.m_contactList.prev = c.m_nodeB;
+ }
+ bodyB.m_contactList = c.m_nodeB;
+ ++this.m_world.m_contactCount;
+ return;
+ }
+ b2ContactManager.prototype.FindNewContacts = function () {
+ this.m_broadPhase.UpdatePairs(Box2D.generateCallback(this, this.AddPair));
+ }
+ b2ContactManager.prototype.Destroy = function (c) {
+ var fixtureA = c.GetFixtureA();
+ var fixtureB = c.GetFixtureB();
+ var bodyA = fixtureA.GetBody();
+ var bodyB = fixtureB.GetBody();
+ if (c.IsTouching()) {
+ this.m_contactListener.EndContact(c);
+ }
+ if (c.m_prev) {
+ c.m_prev.m_next = c.m_next;
+ }
+ if (c.m_next) {
+ c.m_next.m_prev = c.m_prev;
+ }
+ if (c == this.m_world.m_contactList) {
+ this.m_world.m_contactList = c.m_next;
+ }
+ if (c.m_nodeA.prev) {
+ c.m_nodeA.prev.next = c.m_nodeA.next;
+ }
+ if (c.m_nodeA.next) {
+ c.m_nodeA.next.prev = c.m_nodeA.prev;
+ }
+ if (c.m_nodeA == bodyA.m_contactList) {
+ bodyA.m_contactList = c.m_nodeA.next;
+ }
+ if (c.m_nodeB.prev) {
+ c.m_nodeB.prev.next = c.m_nodeB.next;
+ }
+ if (c.m_nodeB.next) {
+ c.m_nodeB.next.prev = c.m_nodeB.prev;
+ }
+ if (c.m_nodeB == bodyB.m_contactList) {
+ bodyB.m_contactList = c.m_nodeB.next;
+ }
+ this.m_contactFactory.Destroy(c);
+ --this.m_contactCount;
+ }
+ b2ContactManager.prototype.Collide = function () {
+ var c = this.m_world.m_contactList;
+ while (c) {
+ var fixtureA = c.GetFixtureA();
+ var fixtureB = c.GetFixtureB();
+ var bodyA = fixtureA.GetBody();
+ var bodyB = fixtureB.GetBody();
+ if (bodyA.IsAwake() == false && bodyB.IsAwake() == false) {
+ c = c.GetNext();
+ continue;
+ }
+ if (c.m_flags & b2Contact.e_filterFlag) {
+ if (bodyB.ShouldCollide(bodyA) == false) {
+ var cNuke = c;
+ c = cNuke.GetNext();
+ this.Destroy(cNuke);
+ continue;
+ }
+ if (this.m_contactFilter.ShouldCollide(fixtureA, fixtureB) == false) {
+ cNuke = c;
+ c = cNuke.GetNext();
+ this.Destroy(cNuke);
+ continue;
+ }
+ c.m_flags &= ~b2Contact.e_filterFlag;
+ }
+ var proxyA = fixtureA.m_proxy;
+ var proxyB = fixtureB.m_proxy;
+ var overlap = this.m_broadPhase.TestOverlap(proxyA, proxyB);
+ if (overlap == false) {
+ cNuke = c;
+ c = cNuke.GetNext();
+ this.Destroy(cNuke);
+ continue;
+ }
+ c.Update(this.m_contactListener);
+ c = c.GetNext();
+ }
+ }
+ Box2D.postDefs.push(function () {
+ Box2D.Dynamics.b2ContactManager.s_evalCP = new b2ContactPoint();
+ });
+ b2DebugDraw.b2DebugDraw = function () {};
+ b2DebugDraw.prototype.b2DebugDraw = function () {}
+ b2DebugDraw.prototype.SetFlags = function (flags) {
+ if (flags === undefined) flags = 0;
+ }
+ b2DebugDraw.prototype.GetFlags = function () {}
+ b2DebugDraw.prototype.AppendFlags = function (flags) {
+ if (flags === undefined) flags = 0;
+ }
+ b2DebugDraw.prototype.ClearFlags = function (flags) {
+ if (flags === undefined) flags = 0;
+ }
+ b2DebugDraw.prototype.SetSprite = function (sprite) {}
+ b2DebugDraw.prototype.GetSprite = function () {}
+ b2DebugDraw.prototype.SetDrawScale = function (drawScale) {
+ if (drawScale === undefined) drawScale = 0;
+ }
+ b2DebugDraw.prototype.GetDrawScale = function () {}
+ b2DebugDraw.prototype.SetLineThickness = function (lineThickness) {
+ if (lineThickness === undefined) lineThickness = 0;
+ }
+ b2DebugDraw.prototype.GetLineThickness = function () {}
+ b2DebugDraw.prototype.SetAlpha = function (alpha) {
+ if (alpha === undefined) alpha = 0;
+ }
+ b2DebugDraw.prototype.GetAlpha = function () {}
+ b2DebugDraw.prototype.SetFillAlpha = function (alpha) {
+ if (alpha === undefined) alpha = 0;
+ }
+ b2DebugDraw.prototype.GetFillAlpha = function () {}
+ b2DebugDraw.prototype.SetXFormScale = function (xformScale) {
+ if (xformScale === undefined) xformScale = 0;
+ }
+ b2DebugDraw.prototype.GetXFormScale = function () {}
+ b2DebugDraw.prototype.DrawPolygon = function (vertices, vertexCount, color) {
+ if (vertexCount === undefined) vertexCount = 0;
+ }
+ b2DebugDraw.prototype.DrawSolidPolygon = function (vertices, vertexCount, color) {
+ if (vertexCount === undefined) vertexCount = 0;
+ }
+ b2DebugDraw.prototype.DrawCircle = function (center, radius, color) {
+ if (radius === undefined) radius = 0;
+ }
+ b2DebugDraw.prototype.DrawSolidCircle = function (center, radius, axis, color) {
+ if (radius === undefined) radius = 0;
+ }
+ b2DebugDraw.prototype.DrawSegment = function (p1, p2, color) {}
+ b2DebugDraw.prototype.DrawTransform = function (xf) {}
+ Box2D.postDefs.push(function () {
+ Box2D.Dynamics.b2DebugDraw.e_shapeBit = 0x0001;
+ Box2D.Dynamics.b2DebugDraw.e_jointBit = 0x0002;
+ Box2D.Dynamics.b2DebugDraw.e_aabbBit = 0x0004;
+ Box2D.Dynamics.b2DebugDraw.e_pairBit = 0x0008;
+ Box2D.Dynamics.b2DebugDraw.e_centerOfMassBit = 0x0010;
+ Box2D.Dynamics.b2DebugDraw.e_controllerBit = 0x0020;
+ });
+ b2DestructionListener.b2DestructionListener = function () {};
+ b2DestructionListener.prototype.SayGoodbyeJoint = function (joint) {}
+ b2DestructionListener.prototype.SayGoodbyeFixture = function (fixture) {}
+ b2FilterData.b2FilterData = function () {
+ this.categoryBits = 0x0001;
+ this.maskBits = 0xFFFF;
+ this.groupIndex = 0;
+ };
+ b2FilterData.prototype.Copy = function () {
+ var copy = new b2FilterData();
+ copy.categoryBits = this.categoryBits;
+ copy.maskBits = this.maskBits;
+ copy.groupIndex = this.groupIndex;
+ return copy;
+ }
+ b2Fixture.b2Fixture = function () {
+ this.m_filter = new b2FilterData();
+ };
+ b2Fixture.prototype.GetType = function () {
+ return this.m_shape.GetType();
+ }
+ b2Fixture.prototype.GetShape = function () {
+ return this.m_shape;
+ }
+ b2Fixture.prototype.SetSensor = function (sensor) {
+ if (this.m_isSensor == sensor) return;
+ this.m_isSensor = sensor;
+ if (this.m_body == null) return;
+ var edge = this.m_body.GetContactList();
+ while (edge) {
+ var contact = edge.contact;
+ var fixtureA = contact.GetFixtureA();
+ var fixtureB = contact.GetFixtureB();
+ if (fixtureA == this || fixtureB == this) contact.SetSensor(fixtureA.IsSensor() || fixtureB.IsSensor());
+ edge = edge.next;
+ }
+ }
+ b2Fixture.prototype.IsSensor = function () {
+ return this.m_isSensor;
+ }
+ b2Fixture.prototype.SetFilterData = function (filter) {
+ this.m_filter = filter.Copy();
+ if (this.m_body) return;
+ var edge = this.m_body.GetContactList();
+ while (edge) {
+ var contact = edge.contact;
+ var fixtureA = contact.GetFixtureA();
+ var fixtureB = contact.GetFixtureB();
+ if (fixtureA == this || fixtureB == this) contact.FlagForFiltering();
+ edge = edge.next;
+ }
+ }
+ b2Fixture.prototype.GetFilterData = function () {
+ return this.m_filter.Copy();
+ }
+ b2Fixture.prototype.GetBody = function () {
+ return this.m_body;
+ }
+ b2Fixture.prototype.GetNext = function () {
+ return this.m_next;
+ }
+ b2Fixture.prototype.GetUserData = function () {
+ return this.m_userData;
+ }
+ b2Fixture.prototype.SetUserData = function (data) {
+ this.m_userData = data;
+ }
+ b2Fixture.prototype.TestPoint = function (p) {
+ return this.m_shape.TestPoint(this.m_body.GetTransform(), p);
+ }
+ b2Fixture.prototype.RayCast = function (output, input) {
+ return this.m_shape.RayCast(output, input, this.m_body.GetTransform());
+ }
+ b2Fixture.prototype.GetMassData = function (massData) {
+ if (massData === undefined) massData = null;
+ if (massData == null) {
+ massData = new b2MassData();
+ }
+ this.m_shape.ComputeMass(massData, this.m_density);
+ return massData;
+ }
+ b2Fixture.prototype.SetDensity = function (density) {
+ if (density === undefined) density = 0;
+ this.m_density = density;
+ }
+ b2Fixture.prototype.GetDensity = function () {
+ return this.m_density;
+ }
+ b2Fixture.prototype.GetFriction = function () {
+ return this.m_friction;
+ }
+ b2Fixture.prototype.SetFriction = function (friction) {
+ if (friction === undefined) friction = 0;
+ this.m_friction = friction;
+ }
+ b2Fixture.prototype.GetRestitution = function () {
+ return this.m_restitution;
+ }
+ b2Fixture.prototype.SetRestitution = function (restitution) {
+ if (restitution === undefined) restitution = 0;
+ this.m_restitution = restitution;
+ }
+ b2Fixture.prototype.GetAABB = function () {
+ return this.m_aabb;
+ }
+ b2Fixture.prototype.b2Fixture = function () {
+ this.m_aabb = new b2AABB();
+ this.m_userData = null;
+ this.m_body = null;
+ this.m_next = null;
+ this.m_shape = null;
+ this.m_density = 0.0;
+ this.m_friction = 0.0;
+ this.m_restitution = 0.0;
+ }
+ b2Fixture.prototype.Create = function (body, xf, def) {
+ this.m_userData = def.userData;
+ this.m_friction = def.friction;
+ this.m_restitution = def.restitution;
+ this.m_body = body;
+ this.m_next = null;
+ this.m_filter = def.filter.Copy();
+ this.m_isSensor = def.isSensor;
+ this.m_shape = def.shape.Copy();
+ this.m_density = def.density;
+ }
+ b2Fixture.prototype.Destroy = function () {
+ this.m_shape = null;
+ }
+ b2Fixture.prototype.CreateProxy = function (broadPhase, xf) {
+ this.m_shape.ComputeAABB(this.m_aabb, xf);
+ this.m_proxy = broadPhase.CreateProxy(this.m_aabb, this);
+ }
+ b2Fixture.prototype.DestroyProxy = function (broadPhase) {
+ if (this.m_proxy == null) {
+ return;
+ }
+ broadPhase.DestroyProxy(this.m_proxy);
+ this.m_proxy = null;
+ }
+ b2Fixture.prototype.Synchronize = function (broadPhase, transform1, transform2) {
+ if (!this.m_proxy) return;
+ var aabb1 = new b2AABB();
+ var aabb2 = new b2AABB();
+ this.m_shape.ComputeAABB(aabb1, transform1);
+ this.m_shape.ComputeAABB(aabb2, transform2);
+ this.m_aabb.Combine(aabb1, aabb2);
+ var displacement = b2Math.SubtractVV(transform2.position, transform1.position);
+ broadPhase.MoveProxy(this.m_proxy, this.m_aabb, displacement);
+ }
+ b2FixtureDef.b2FixtureDef = function () {
+ this.filter = new b2FilterData();
+ };
+ b2FixtureDef.prototype.b2FixtureDef = function () {
+ this.shape = null;
+ this.userData = null;
+ this.friction = 0.2;
+ this.restitution = 0.0;
+ this.density = 0.0;
+ this.filter.categoryBits = 0x0001;
+ this.filter.maskBits = 0xFFFF;
+ this.filter.groupIndex = 0;
+ this.isSensor = false;
+ }
+ b2Island.b2Island = function () {};
+ b2Island.prototype.b2Island = function () {
+ this.m_bodies = new Vector();
+ this.m_contacts = new Vector();
+ this.m_joints = new Vector();
+ }
+ b2Island.prototype.Initialize = function (bodyCapacity, contactCapacity, jointCapacity, allocator, listener, contactSolver) {
+ if (bodyCapacity === undefined) bodyCapacity = 0;
+ if (contactCapacity === undefined) contactCapacity = 0;
+ if (jointCapacity === undefined) jointCapacity = 0;
+ var i = 0;
+ this.m_bodyCapacity = bodyCapacity;
+ this.m_contactCapacity = contactCapacity;
+ this.m_jointCapacity = jointCapacity;
+ this.m_bodyCount = 0;
+ this.m_contactCount = 0;
+ this.m_jointCount = 0;
+ this.m_allocator = allocator;
+ this.m_listener = listener;
+ this.m_contactSolver = contactSolver;
+ for (i = this.m_bodies.length;
+ i < bodyCapacity; i++)
+ this.m_bodies[i] = null;
+ for (i = this.m_contacts.length;
+ i < contactCapacity; i++)
+ this.m_contacts[i] = null;
+ for (i = this.m_joints.length;
+ i < jointCapacity; i++)
+ this.m_joints[i] = null;
+ }
+ b2Island.prototype.Clear = function () {
+ this.m_bodyCount = 0;
+ this.m_contactCount = 0;
+ this.m_jointCount = 0;
+ }
+ b2Island.prototype.Solve = function (step, gravity, allowSleep) {
+ var i = 0;
+ var j = 0;
+ var b;
+ var joint;
+ for (i = 0;
+ i < this.m_bodyCount; ++i) {
+ b = this.m_bodies[i];
+ if (b.GetType() != b2Body.b2_dynamicBody) continue;
+ b.m_linearVelocity.x += step.dt * (gravity.x + b.m_invMass * b.m_force.x);
+ b.m_linearVelocity.y += step.dt * (gravity.y + b.m_invMass * b.m_force.y);
+ b.m_angularVelocity += step.dt * b.m_invI * b.m_torque;
+ b.m_linearVelocity.Multiply(b2Math.Clamp(1.0 - step.dt * b.m_linearDamping, 0.0, 1.0));
+ b.m_angularVelocity *= b2Math.Clamp(1.0 - step.dt * b.m_angularDamping, 0.0, 1.0);
+ }
+ this.m_contactSolver.Initialize(step, this.m_contacts, this.m_contactCount, this.m_allocator);
+ var contactSolver = this.m_contactSolver;
+ contactSolver.InitVelocityConstraints(step);
+ for (i = 0;
+ i < this.m_jointCount; ++i) {
+ joint = this.m_joints[i];
+ joint.InitVelocityConstraints(step);
+ }
+ for (i = 0;
+ i < step.velocityIterations; ++i) {
+ for (j = 0;
+ j < this.m_jointCount; ++j) {
+ joint = this.m_joints[j];
+ joint.SolveVelocityConstraints(step);
+ }
+ contactSolver.SolveVelocityConstraints();
+ }
+ for (i = 0;
+ i < this.m_jointCount; ++i) {
+ joint = this.m_joints[i];
+ joint.FinalizeVelocityConstraints();
+ }
+ contactSolver.FinalizeVelocityConstraints();
+ for (i = 0;
+ i < this.m_bodyCount; ++i) {
+ b = this.m_bodies[i];
+ if (b.GetType() == b2Body.b2_staticBody) continue;
+ var translationX = step.dt * b.m_linearVelocity.x;
+ var translationY = step.dt * b.m_linearVelocity.y;
+ if ((translationX * translationX + translationY * translationY) > b2Settings.b2_maxTranslationSquared) {
+ b.m_linearVelocity.Normalize();
+ b.m_linearVelocity.x *= b2Settings.b2_maxTranslation * step.inv_dt;
+ b.m_linearVelocity.y *= b2Settings.b2_maxTranslation * step.inv_dt;
+ }
+ var rotation = step.dt * b.m_angularVelocity;
+ if (rotation * rotation > b2Settings.b2_maxRotationSquared) {
+ if (b.m_angularVelocity < 0.0) {
+ b.m_angularVelocity = (-b2Settings.b2_maxRotation * step.inv_dt);
+ }
+ else {
+ b.m_angularVelocity = b2Settings.b2_maxRotation * step.inv_dt;
+ }
+ }
+ b.m_sweep.c0.SetV(b.m_sweep.c);
+ b.m_sweep.a0 = b.m_sweep.a;
+ b.m_sweep.c.x += step.dt * b.m_linearVelocity.x;
+ b.m_sweep.c.y += step.dt * b.m_linearVelocity.y;
+ b.m_sweep.a += step.dt * b.m_angularVelocity;
+ b.SynchronizeTransform();
+ }
+ for (i = 0;
+ i < step.positionIterations; ++i) {
+ var contactsOkay = contactSolver.SolvePositionConstraints(b2Settings.b2_contactBaumgarte);
+ var jointsOkay = true;
+ for (j = 0;
+ j < this.m_jointCount; ++j) {
+ joint = this.m_joints[j];
+ var jointOkay = joint.SolvePositionConstraints(b2Settings.b2_contactBaumgarte);
+ jointsOkay = jointsOkay && jointOkay;
+ }
+ if (contactsOkay && jointsOkay) {
+ break;
+ }
+ }
+ this.Report(contactSolver.m_constraints);
+ if (allowSleep) {
+ var minSleepTime = Number.MAX_VALUE;
+ var linTolSqr = b2Settings.b2_linearSleepTolerance * b2Settings.b2_linearSleepTolerance;
+ var angTolSqr = b2Settings.b2_angularSleepTolerance * b2Settings.b2_angularSleepTolerance;
+ for (i = 0;
+ i < this.m_bodyCount; ++i) {
+ b = this.m_bodies[i];
+ if (b.GetType() == b2Body.b2_staticBody) {
+ continue;
+ }
+ if ((b.m_flags & b2Body.e_allowSleepFlag) == 0) {
+ b.m_sleepTime = 0.0;
+ minSleepTime = 0.0;
+ }
+ if ((b.m_flags & b2Body.e_allowSleepFlag) == 0 || b.m_angularVelocity * b.m_angularVelocity > angTolSqr || b2Math.Dot(b.m_linearVelocity, b.m_linearVelocity) > linTolSqr) {
+ b.m_sleepTime = 0.0;
+ minSleepTime = 0.0;
+ }
+ else {
+ b.m_sleepTime += step.dt;
+ minSleepTime = b2Math.Min(minSleepTime, b.m_sleepTime);
+ }
+ }
+ if (minSleepTime >= b2Settings.b2_timeToSleep) {
+ for (i = 0;
+ i < this.m_bodyCount; ++i) {
+ b = this.m_bodies[i];
+ b.SetAwake(false);
+ }
+ }
+ }
+ }
+ b2Island.prototype.SolveTOI = function (subStep) {
+ var i = 0;
+ var j = 0;
+ this.m_contactSolver.Initialize(subStep, this.m_contacts, this.m_contactCount, this.m_allocator);
+ var contactSolver = this.m_contactSolver;
+ for (i = 0;
+ i < this.m_jointCount; ++i) {
+ this.m_joints[i].InitVelocityConstraints(subStep);
+ }
+ for (i = 0;
+ i < subStep.velocityIterations; ++i) {
+ contactSolver.SolveVelocityConstraints();
+ for (j = 0;
+ j < this.m_jointCount; ++j) {
+ this.m_joints[j].SolveVelocityConstraints(subStep);
+ }
+ }
+ for (i = 0;
+ i < this.m_bodyCount; ++i) {
+ var b = this.m_bodies[i];
+ if (b.GetType() == b2Body.b2_staticBody) continue;
+ var translationX = subStep.dt * b.m_linearVelocity.x;
+ var translationY = subStep.dt * b.m_linearVelocity.y;
+ if ((translationX * translationX + translationY * translationY) > b2Settings.b2_maxTranslationSquared) {
+ b.m_linearVelocity.Normalize();
+ b.m_linearVelocity.x *= b2Settings.b2_maxTranslation * subStep.inv_dt;
+ b.m_linearVelocity.y *= b2Settings.b2_maxTranslation * subStep.inv_dt;
+ }
+ var rotation = subStep.dt * b.m_angularVelocity;
+ if (rotation * rotation > b2Settings.b2_maxRotationSquared) {
+ if (b.m_angularVelocity < 0.0) {
+ b.m_angularVelocity = (-b2Settings.b2_maxRotation * subStep.inv_dt);
+ }
+ else {
+ b.m_angularVelocity = b2Settings.b2_maxRotation * subStep.inv_dt;
+ }
+ }
+ b.m_sweep.c0.SetV(b.m_sweep.c);
+ b.m_sweep.a0 = b.m_sweep.a;
+ b.m_sweep.c.x += subStep.dt * b.m_linearVelocity.x;
+ b.m_sweep.c.y += subStep.dt * b.m_linearVelocity.y;
+ b.m_sweep.a += subStep.dt * b.m_angularVelocity;
+ b.SynchronizeTransform();
+ }
+ var k_toiBaumgarte = 0.75;
+ for (i = 0;
+ i < subStep.positionIterations; ++i) {
+ var contactsOkay = contactSolver.SolvePositionConstraints(k_toiBaumgarte);
+ var jointsOkay = true;
+ for (j = 0;
+ j < this.m_jointCount; ++j) {
+ var jointOkay = this.m_joints[j].SolvePositionConstraints(b2Settings.b2_contactBaumgarte);
+ jointsOkay = jointsOkay && jointOkay;
+ }
+ if (contactsOkay && jointsOkay) {
+ break;
+ }
+ }
+ this.Report(contactSolver.m_constraints);
+ }
+ b2Island.prototype.Report = function (constraints) {
+ if (this.m_listener == null) {
+ return;
+ }
+ for (var i = 0; i < this.m_contactCount; ++i) {
+ var c = this.m_contacts[i];
+ var cc = constraints[i];
+ for (var j = 0; j < cc.pointCount; ++j) {
+ b2Island.s_impulse.normalImpulses[j] = cc.points[j].normalImpulse;
+ b2Island.s_impulse.tangentImpulses[j] = cc.points[j].tangentImpulse;
+ }
+ this.m_listener.PostSolve(c, b2Island.s_impulse);
+ }
+ }
+ b2Island.prototype.AddBody = function (body) {
+ body.m_islandIndex = this.m_bodyCount;
+ this.m_bodies[this.m_bodyCount++] = body;
+ }
+ b2Island.prototype.AddContact = function (contact) {
+ this.m_contacts[this.m_contactCount++] = contact;
+ }
+ b2Island.prototype.AddJoint = function (joint) {
+ this.m_joints[this.m_jointCount++] = joint;
+ }
+ Box2D.postDefs.push(function () {
+ Box2D.Dynamics.b2Island.s_impulse = new b2ContactImpulse();
+ });
+ b2TimeStep.b2TimeStep = function () {};
+ b2TimeStep.prototype.Set = function (step) {
+ this.dt = step.dt;
+ this.inv_dt = step.inv_dt;
+ this.positionIterations = step.positionIterations;
+ this.velocityIterations = step.velocityIterations;
+ this.warmStarting = step.warmStarting;
+ }
+ b2World.b2World = function () {
+ this.s_stack = new Vector();
+ this.m_contactManager = new b2ContactManager();
+ this.m_contactSolver = new b2ContactSolver();
+ this.m_island = new b2Island();
+ };
+ b2World.prototype.b2World = function (gravity, doSleep) {
+ this.m_destructionListener = null;
+ this.m_debugDraw = null;
+ this.m_bodyList = null;
+ this.m_contactList = null;
+ this.m_jointList = null;
+ this.m_controllerList = null;
+ this.m_bodyCount = 0;
+ this.m_contactCount = 0;
+ this.m_jointCount = 0;
+ this.m_controllerCount = 0;
+ b2World.m_warmStarting = true;
+ b2World.m_continuousPhysics = true;
+ this.m_allowSleep = doSleep;
+ this.m_gravity = gravity;
+ this.m_inv_dt0 = 0.0;
+ this.m_contactManager.m_world = this;
+ var bd = new b2BodyDef();
+ this.m_groundBody = this.CreateBody(bd);
+ }
+ b2World.prototype.SetDestructionListener = function (listener) {
+ this.m_destructionListener = listener;
+ }
+ b2World.prototype.SetContactFilter = function (filter) {
+ this.m_contactManager.m_contactFilter = filter;
+ }
+ b2World.prototype.SetContactListener = function (listener) {
+ this.m_contactManager.m_contactListener = listener;
+ }
+ b2World.prototype.SetDebugDraw = function (debugDraw) {
+ this.m_debugDraw = debugDraw;
+ }
+ b2World.prototype.SetBroadPhase = function (broadPhase) {
+ var oldBroadPhase = this.m_contactManager.m_broadPhase;
+ this.m_contactManager.m_broadPhase = broadPhase;
+ for (var b = this.m_bodyList; b; b = b.m_next) {
+ for (var f = b.m_fixtureList; f; f = f.m_next) {
+ f.m_proxy = broadPhase.CreateProxy(oldBroadPhase.GetFatAABB(f.m_proxy), f);
+ }
+ }
+ }
+ b2World.prototype.Validate = function () {
+ this.m_contactManager.m_broadPhase.Validate();
+ }
+ b2World.prototype.GetProxyCount = function () {
+ return this.m_contactManager.m_broadPhase.GetProxyCount();
+ }
+ b2World.prototype.CreateBody = function (def) {
+ if (this.IsLocked() == true) {
+ return null;
+ }
+ var b = new b2Body(def, this);
+ b.m_prev = null;
+ b.m_next = this.m_bodyList;
+ if (this.m_bodyList) {
+ this.m_bodyList.m_prev = b;
+ }
+ this.m_bodyList = b;
+ ++this.m_bodyCount;
+ return b;
+ }
+ b2World.prototype.DestroyBody = function (b) {
+ if (this.IsLocked() == true) {
+ return;
+ }
+ var jn = b.m_jointList;
+ while (jn) {
+ var jn0 = jn;
+ jn = jn.next;
+ if (this.m_destructionListener) {
+ this.m_destructionListener.SayGoodbyeJoint(jn0.joint);
+ }
+ this.DestroyJoint(jn0.joint);
+ }
+ var coe = b.m_controllerList;
+ while (coe) {
+ var coe0 = coe;
+ coe = coe.nextController;
+ coe0.controller.RemoveBody(b);
+ }
+ var ce = b.m_contactList;
+ while (ce) {
+ var ce0 = ce;
+ ce = ce.next;
+ this.m_contactManager.Destroy(ce0.contact);
+ }
+ b.m_contactList = null;
+ var f = b.m_fixtureList;
+ while (f) {
+ var f0 = f;
+ f = f.m_next;
+ if (this.m_destructionListener) {
+ this.m_destructionListener.SayGoodbyeFixture(f0);
+ }
+ f0.DestroyProxy(this.m_contactManager.m_broadPhase);
+ f0.Destroy();
+ }
+ b.m_fixtureList = null;
+ b.m_fixtureCount = 0;
+ if (b.m_prev) {
+ b.m_prev.m_next = b.m_next;
+ }
+ if (b.m_next) {
+ b.m_next.m_prev = b.m_prev;
+ }
+ if (b == this.m_bodyList) {
+ this.m_bodyList = b.m_next;
+ }--this.m_bodyCount;
+ }
+ b2World.prototype.CreateJoint = function (def) {
+ var j = b2Joint.Create(def, null);
+ j.m_prev = null;
+ j.m_next = this.m_jointList;
+ if (this.m_jointList) {
+ this.m_jointList.m_prev = j;
+ }
+ this.m_jointList = j;
+ ++this.m_jointCount;
+ j.m_edgeA.joint = j;
+ j.m_edgeA.other = j.m_bodyB;
+ j.m_edgeA.prev = null;
+ j.m_edgeA.next = j.m_bodyA.m_jointList;
+ if (j.m_bodyA.m_jointList) j.m_bodyA.m_jointList.prev = j.m_edgeA;
+ j.m_bodyA.m_jointList = j.m_edgeA;
+ j.m_edgeB.joint = j;
+ j.m_edgeB.other = j.m_bodyA;
+ j.m_edgeB.prev = null;
+ j.m_edgeB.next = j.m_bodyB.m_jointList;
+ if (j.m_bodyB.m_jointList) j.m_bodyB.m_jointList.prev = j.m_edgeB;
+ j.m_bodyB.m_jointList = j.m_edgeB;
+ var bodyA = def.bodyA;
+ var bodyB = def.bodyB;
+ if (def.collideConnected == false) {
+ var edge = bodyB.GetContactList();
+ while (edge) {
+ if (edge.other == bodyA) {
+ edge.contact.FlagForFiltering();
+ }
+ edge = edge.next;
+ }
+ }
+ return j;
+ }
+ b2World.prototype.DestroyJoint = function (j) {
+ var collideConnected = j.m_collideConnected;
+ if (j.m_prev) {
+ j.m_prev.m_next = j.m_next;
+ }
+ if (j.m_next) {
+ j.m_next.m_prev = j.m_prev;
+ }
+ if (j == this.m_jointList) {
+ this.m_jointList = j.m_next;
+ }
+ var bodyA = j.m_bodyA;
+ var bodyB = j.m_bodyB;
+ bodyA.SetAwake(true);
+ bodyB.SetAwake(true);
+ if (j.m_edgeA.prev) {
+ j.m_edgeA.prev.next = j.m_edgeA.next;
+ }
+ if (j.m_edgeA.next) {
+ j.m_edgeA.next.prev = j.m_edgeA.prev;
+ }
+ if (j.m_edgeA == bodyA.m_jointList) {
+ bodyA.m_jointList = j.m_edgeA.next;
+ }
+ j.m_edgeA.prev = null;
+ j.m_edgeA.next = null;
+ if (j.m_edgeB.prev) {
+ j.m_edgeB.prev.next = j.m_edgeB.next;
+ }
+ if (j.m_edgeB.next) {
+ j.m_edgeB.next.prev = j.m_edgeB.prev;
+ }
+ if (j.m_edgeB == bodyB.m_jointList) {
+ bodyB.m_jointList = j.m_edgeB.next;
+ }
+ j.m_edgeB.prev = null;
+ j.m_edgeB.next = null;
+ b2Joint.Destroy(j, null);
+ --this.m_jointCount;
+ if (collideConnected == false) {
+ var edge = bodyB.GetContactList();
+ while (edge) {
+ if (edge.other == bodyA) {
+ edge.contact.FlagForFiltering();
+ }
+ edge = edge.next;
+ }
+ }
+ }
+ b2World.prototype.AddController = function (c) {
+ c.m_next = this.m_controllerList;
+ c.m_prev = null;
+ this.m_controllerList = c;
+ c.m_world = this;
+ this.m_controllerCount++;
+ return c;
+ }
+ b2World.prototype.RemoveController = function (c) {
+ if (c.m_prev) c.m_prev.m_next = c.m_next;
+ if (c.m_next) c.m_next.m_prev = c.m_prev;
+ if (this.m_controllerList == c) this.m_controllerList = c.m_next;
+ this.m_controllerCount--;
+ }
+ b2World.prototype.CreateController = function (controller) {
+ if (controller.m_world != this) throw new Error("Controller can only be a member of one world");
+ controller.m_next = this.m_controllerList;
+ controller.m_prev = null;
+ if (this.m_controllerList) this.m_controllerList.m_prev = controller;
+ this.m_controllerList = controller;
+ ++this.m_controllerCount;
+ controller.m_world = this;
+ return controller;
+ }
+ b2World.prototype.DestroyController = function (controller) {
+ controller.Clear();
+ if (controller.m_next) controller.m_next.m_prev = controller.m_prev;
+ if (controller.m_prev) controller.m_prev.m_next = controller.m_next;
+ if (controller == this.m_controllerList) this.m_controllerList = controller.m_next;
+ --this.m_controllerCount;
+ }
+ b2World.prototype.SetWarmStarting = function (flag) {
+ b2World.m_warmStarting = flag;
+ }
+ b2World.prototype.SetContinuousPhysics = function (flag) {
+ b2World.m_continuousPhysics = flag;
+ }
+ b2World.prototype.GetBodyCount = function () {
+ return this.m_bodyCount;
+ }
+ b2World.prototype.GetJointCount = function () {
+ return this.m_jointCount;
+ }
+ b2World.prototype.GetContactCount = function () {
+ return this.m_contactCount;
+ }
+ b2World.prototype.SetGravity = function (gravity) {
+ this.m_gravity = gravity;
+ }
+ b2World.prototype.GetGravity = function () {
+ return this.m_gravity;
+ }
+ b2World.prototype.GetGroundBody = function () {
+ return this.m_groundBody;
+ }
+ b2World.prototype.Step = function (dt, velocityIterations, positionIterations) {
+ if (dt === undefined) dt = 0;
+ if (velocityIterations === undefined) velocityIterations = 0;
+ if (positionIterations === undefined) positionIterations = 0;
+ if (this.m_flags & b2World.e_newFixture) {
+ this.m_contactManager.FindNewContacts();
+ this.m_flags &= ~b2World.e_newFixture;
+ }
+ this.m_flags |= b2World.e_locked;
+ var step = b2World.s_timestep2;
+ step.dt = dt;
+ step.velocityIterations = velocityIterations;
+ step.positionIterations = positionIterations;
+ if (dt > 0.0) {
+ step.inv_dt = 1.0 / dt;
+ }
+ else {
+ step.inv_dt = 0.0;
+ }
+ step.dtRatio = this.m_inv_dt0 * dt;
+ step.warmStarting = b2World.m_warmStarting;
+ this.m_contactManager.Collide();
+ if (step.dt > 0.0) {
+ this.Solve(step);
+ }
+ if (b2World.m_continuousPhysics && step.dt > 0.0) {
+ this.SolveTOI(step);
+ }
+ if (step.dt > 0.0) {
+ this.m_inv_dt0 = step.inv_dt;
+ }
+ this.m_flags &= ~b2World.e_locked;
+ }
+ b2World.prototype.ClearForces = function () {
+ for (var body = this.m_bodyList; body; body = body.m_next) {
+ body.m_force.SetZero();
+ body.m_torque = 0.0;
+ }
+ }
+ b2World.prototype.DrawDebugData = function () {
+ if (this.m_debugDraw == null) {
+ return;
+ }
+ this.m_debugDraw.m_sprite.graphics.clear();
+ var flags = this.m_debugDraw.GetFlags();
+ var i = 0;
+ var b;
+ var f;
+ var s;
+ var j;
+ var bp;
+ var invQ = new b2Vec2;
+ var x1 = new b2Vec2;
+ var x2 = new b2Vec2;
+ var xf;
+ var b1 = new b2AABB();
+ var b2 = new b2AABB();
+ var vs = [new b2Vec2(), new b2Vec2(), new b2Vec2(), new b2Vec2()];
+ var color = new b2Color(0, 0, 0);
+ if (flags & b2DebugDraw.e_shapeBit) {
+ for (b = this.m_bodyList;
+ b; b = b.m_next) {
+ xf = b.m_xf;
+ for (f = b.GetFixtureList();
+ f; f = f.m_next) {
+ s = f.GetShape();
+ if (b.IsActive() == false) {
+ color.Set(0.5, 0.5, 0.3);
+ this.DrawShape(s, xf, color);
+ }
+ else if (b.GetType() == b2Body.b2_staticBody) {
+ color.Set(0.5, 0.9, 0.5);
+ this.DrawShape(s, xf, color);
+ }
+ else if (b.GetType() == b2Body.b2_kinematicBody) {
+ color.Set(0.5, 0.5, 0.9);
+ this.DrawShape(s, xf, color);
+ }
+ else if (b.IsAwake() == false) {
+ color.Set(0.6, 0.6, 0.6);
+ this.DrawShape(s, xf, color);
+ }
+ else {
+ color.Set(0.9, 0.7, 0.7);
+ this.DrawShape(s, xf, color);
+ }
+ }
+ }
+ }
+ if (flags & b2DebugDraw.e_jointBit) {
+ for (j = this.m_jointList;
+ j; j = j.m_next) {
+ this.DrawJoint(j);
+ }
+ }
+ if (flags & b2DebugDraw.e_controllerBit) {
+ for (var c = this.m_controllerList; c; c = c.m_next) {
+ c.Draw(this.m_debugDraw);
+ }
+ }
+ if (flags & b2DebugDraw.e_pairBit) {
+ color.Set(0.3, 0.9, 0.9);
+ for (var contact = this.m_contactManager.m_contactList; contact; contact = contact.GetNext()) {
+ var fixtureA = contact.GetFixtureA();
+ var fixtureB = contact.GetFixtureB();
+ var cA = fixtureA.GetAABB().GetCenter();
+ var cB = fixtureB.GetAABB().GetCenter();
+ this.m_debugDraw.DrawSegment(cA, cB, color);
+ }
+ }
+ if (flags & b2DebugDraw.e_aabbBit) {
+ bp = this.m_contactManager.m_broadPhase;
+ vs = [new b2Vec2(), new b2Vec2(), new b2Vec2(), new b2Vec2()];
+ for (b = this.m_bodyList;
+ b; b = b.GetNext()) {
+ if (b.IsActive() == false) {
+ continue;
+ }
+ for (f = b.GetFixtureList();
+ f; f = f.GetNext()) {
+ var aabb = bp.GetFatAABB(f.m_proxy);
+ vs[0].Set(aabb.lowerBound.x, aabb.lowerBound.y);
+ vs[1].Set(aabb.upperBound.x, aabb.lowerBound.y);
+ vs[2].Set(aabb.upperBound.x, aabb.upperBound.y);
+ vs[3].Set(aabb.lowerBound.x, aabb.upperBound.y);
+ this.m_debugDraw.DrawPolygon(vs, 4, color);
+ }
+ }
+ }
+ if (flags & b2DebugDraw.e_centerOfMassBit) {
+ for (b = this.m_bodyList;
+ b; b = b.m_next) {
+ xf = b2World.s_xf;
+ xf.R = b.m_xf.R;
+ xf.position = b.GetWorldCenter();
+ this.m_debugDraw.DrawTransform(xf);
+ }
+ }
+ }
+ b2World.prototype.QueryAABB = function (callback, aabb) {
+ var __this = this;
+ var broadPhase = __this.m_contactManager.m_broadPhase;
+
+ function WorldQueryWrapper(proxy) {
+ return callback(broadPhase.GetUserData(proxy));
+ };
+ broadPhase.Query(WorldQueryWrapper, aabb);
+ }
+ b2World.prototype.QueryShape = function (callback, shape, transform) {
+ var __this = this;
+ if (transform === undefined) transform = null;
+ if (transform == null) {
+ transform = new b2Transform();
+ transform.SetIdentity();
+ }
+ var broadPhase = __this.m_contactManager.m_broadPhase;
+
+ function WorldQueryWrapper(proxy) {
+ var fixture = (broadPhase.GetUserData(proxy) instanceof b2Fixture ? broadPhase.GetUserData(proxy) : null);
+ if (b2Shape.TestOverlap(shape, transform, fixture.GetShape(), fixture.GetBody().GetTransform())) return callback(fixture);
+ return true;
+ };
+ var aabb = new b2AABB();
+ shape.ComputeAABB(aabb, transform);
+ broadPhase.Query(WorldQueryWrapper, aabb);
+ }
+ b2World.prototype.QueryPoint = function (callback, p) {
+ var __this = this;
+ var broadPhase = __this.m_contactManager.m_broadPhase;
+
+ function WorldQueryWrapper(proxy) {
+ var fixture = (broadPhase.GetUserData(proxy) instanceof b2Fixture ? broadPhase.GetUserData(proxy) : null);
+ if (fixture.TestPoint(p)) return callback(fixture);
+ return true;
+ };
+ var aabb = new b2AABB();
+ aabb.lowerBound.Set(p.x - b2Settings.b2_linearSlop, p.y - b2Settings.b2_linearSlop);
+ aabb.upperBound.Set(p.x + b2Settings.b2_linearSlop, p.y + b2Settings.b2_linearSlop);
+ broadPhase.Query(WorldQueryWrapper, aabb);
+ }
+ b2World.prototype.RayCast = function (callback, point1, point2) {
+ var __this = this;
+ var broadPhase = __this.m_contactManager.m_broadPhase;
+ var output = new b2RayCastOutput;
+
+ function RayCastWrapper(input, proxy) {
+ var userData = broadPhase.GetUserData(proxy);
+ var fixture = (userData instanceof b2Fixture ? userData : null);
+ var hit = fixture.RayCast(output, input);
+ if (hit) {
+ var fraction = output.fraction;
+ var point = new b2Vec2((1.0 - fraction) * point1.x + fraction * point2.x, (1.0 - fraction) * point1.y + fraction * point2.y);
+ return callback(fixture, point, output.normal, fraction);
+ }
+ return input.maxFraction;
+ };
+ var input = new b2RayCastInput(point1, point2);
+ broadPhase.RayCast(RayCastWrapper, input);
+ }
+ b2World.prototype.RayCastOne = function (point1, point2) {
+ var __this = this;
+ var result;
+
+ function RayCastOneWrapper(fixture, point, normal, fraction) {
+ if (fraction === undefined) fraction = 0;
+ result = fixture;
+ return fraction;
+ };
+ __this.RayCast(RayCastOneWrapper, point1, point2);
+ return result;
+ }
+ b2World.prototype.RayCastAll = function (point1, point2) {
+ var __this = this;
+ var result = new Vector();
+
+ function RayCastAllWrapper(fixture, point, normal, fraction) {
+ if (fraction === undefined) fraction = 0;
+ result[result.length] = fixture;
+ return 1;
+ };
+ __this.RayCast(RayCastAllWrapper, point1, point2);
+ return result;
+ }
+ b2World.prototype.GetBodyList = function () {
+ return this.m_bodyList;
+ }
+ b2World.prototype.GetJointList = function () {
+ return this.m_jointList;
+ }
+ b2World.prototype.GetContactList = function () {
+ return this.m_contactList;
+ }
+ b2World.prototype.IsLocked = function () {
+ return (this.m_flags & b2World.e_locked) > 0;
+ }
+ b2World.prototype.Solve = function (step) {
+ var b;
+ for (var controller = this.m_controllerList; controller; controller = controller.m_next) {
+ controller.Step(step);
+ }
+ var island = this.m_island;
+ island.Initialize(this.m_bodyCount, this.m_contactCount, this.m_jointCount, null, this.m_contactManager.m_contactListener, this.m_contactSolver);
+ for (b = this.m_bodyList;
+ b; b = b.m_next) {
+ b.m_flags &= ~b2Body.e_islandFlag;
+ }
+ for (var c = this.m_contactList; c; c = c.m_next) {
+ c.m_flags &= ~b2Contact.e_islandFlag;
+ }
+ for (var j = this.m_jointList; j; j = j.m_next) {
+ j.m_islandFlag = false;
+ }
+ var stackSize = parseInt(this.m_bodyCount);
+ var stack = this.s_stack;
+ for (var seed = this.m_bodyList; seed; seed = seed.m_next) {
+ if (seed.m_flags & b2Body.e_islandFlag) {
+ continue;
+ }
+ if (seed.IsAwake() == false || seed.IsActive() == false) {
+ continue;
+ }
+ if (seed.GetType() == b2Body.b2_staticBody) {
+ continue;
+ }
+ island.Clear();
+ var stackCount = 0;
+ stack[stackCount++] = seed;
+ seed.m_flags |= b2Body.e_islandFlag;
+ while (stackCount > 0) {
+ b = stack[--stackCount];
+ island.AddBody(b);
+ if (b.IsAwake() == false) {
+ b.SetAwake(true);
+ }
+ if (b.GetType() == b2Body.b2_staticBody) {
+ continue;
+ }
+ var other;
+ for (var ce = b.m_contactList; ce; ce = ce.next) {
+ if (ce.contact.m_flags & b2Contact.e_islandFlag) {
+ continue;
+ }
+ if (ce.contact.IsSensor() == true || ce.contact.IsEnabled() == false || ce.contact.IsTouching() == false) {
+ continue;
+ }
+ island.AddContact(ce.contact);
+ ce.contact.m_flags |= b2Contact.e_islandFlag;
+ other = ce.other;
+ if (other.m_flags & b2Body.e_islandFlag) {
+ continue;
+ }
+ stack[stackCount++] = other;
+ other.m_flags |= b2Body.e_islandFlag;
+ }
+ for (var jn = b.m_jointList; jn; jn = jn.next) {
+ if (jn.joint.m_islandFlag == true) {
+ continue;
+ }
+ other = jn.other;
+ if (other.IsActive() == false) {
+ continue;
+ }
+ island.AddJoint(jn.joint);
+ jn.joint.m_islandFlag = true;
+ if (other.m_flags & b2Body.e_islandFlag) {
+ continue;
+ }
+ stack[stackCount++] = other;
+ other.m_flags |= b2Body.e_islandFlag;
+ }
+ }
+ island.Solve(step, this.m_gravity, this.m_allowSleep);
+ for (var i = 0; i < island.m_bodyCount; ++i) {
+ b = island.m_bodies[i];
+ if (b.GetType() == b2Body.b2_staticBody) {
+ b.m_flags &= ~b2Body.e_islandFlag;
+ }
+ }
+ }
+ for (i = 0;
+ i < stack.length; ++i) {
+ if (!stack[i]) break;
+ stack[i] = null;
+ }
+ for (b = this.m_bodyList;
+ b; b = b.m_next) {
+ if (b.IsAwake() == false || b.IsActive() == false) {
+ continue;
+ }
+ if (b.GetType() == b2Body.b2_staticBody) {
+ continue;
+ }
+ b.SynchronizeFixtures();
+ }
+ this.m_contactManager.FindNewContacts();
+ }
+ b2World.prototype.SolveTOI = function (step) {
+ var b;
+ var fA;
+ var fB;
+ var bA;
+ var bB;
+ var cEdge;
+ var j;
+ var island = this.m_island;
+ island.Initialize(this.m_bodyCount, b2Settings.b2_maxTOIContactsPerIsland, b2Settings.b2_maxTOIJointsPerIsland, null, this.m_contactManager.m_contactListener, this.m_contactSolver);
+ var queue = b2World.s_queue;
+ for (b = this.m_bodyList;
+ b; b = b.m_next) {
+ b.m_flags &= ~b2Body.e_islandFlag;
+ b.m_sweep.t0 = 0.0;
+ }
+ var c;
+ for (c = this.m_contactList;
+ c; c = c.m_next) {
+ c.m_flags &= ~ (b2Contact.e_toiFlag | b2Contact.e_islandFlag);
+ }
+ for (j = this.m_jointList;
+ j; j = j.m_next) {
+ j.m_islandFlag = false;
+ }
+ for (;;) {
+ var minContact = null;
+ var minTOI = 1.0;
+ for (c = this.m_contactList;
+ c; c = c.m_next) {
+ if (c.IsSensor() == true || c.IsEnabled() == false || c.IsContinuous() == false) {
+ continue;
+ }
+ var toi = 1.0;
+ if (c.m_flags & b2Contact.e_toiFlag) {
+ toi = c.m_toi;
+ }
+ else {
+ fA = c.m_fixtureA;
+ fB = c.m_fixtureB;
+ bA = fA.m_body;
+ bB = fB.m_body;
+ if ((bA.GetType() != b2Body.b2_dynamicBody || bA.IsAwake() == false) && (bB.GetType() != b2Body.b2_dynamicBody || bB.IsAwake() == false)) {
+ continue;
+ }
+ var t0 = bA.m_sweep.t0;
+ if (bA.m_sweep.t0 < bB.m_sweep.t0) {
+ t0 = bB.m_sweep.t0;
+ bA.m_sweep.Advance(t0);
+ }
+ else if (bB.m_sweep.t0 < bA.m_sweep.t0) {
+ t0 = bA.m_sweep.t0;
+ bB.m_sweep.Advance(t0);
+ }
+ toi = c.ComputeTOI(bA.m_sweep, bB.m_sweep);
+ b2Settings.b2Assert(0.0 <= toi && toi <= 1.0);
+ if (toi > 0.0 && toi < 1.0) {
+ toi = (1.0 - toi) * t0 + toi;
+ if (toi > 1) toi = 1;
+ }
+ c.m_toi = toi;
+ c.m_flags |= b2Contact.e_toiFlag;
+ }
+ if (Number.MIN_VALUE < toi && toi < minTOI) {
+ minContact = c;
+ minTOI = toi;
+ }
+ }
+ if (minContact == null || 1.0 - 100.0 * Number.MIN_VALUE < minTOI) {
+ break;
+ }
+ fA = minContact.m_fixtureA;
+ fB = minContact.m_fixtureB;
+ bA = fA.m_body;
+ bB = fB.m_body;
+ b2World.s_backupA.Set(bA.m_sweep);
+ b2World.s_backupB.Set(bB.m_sweep);
+ bA.Advance(minTOI);
+ bB.Advance(minTOI);
+ minContact.Update(this.m_contactManager.m_contactListener);
+ minContact.m_flags &= ~b2Contact.e_toiFlag;
+ if (minContact.IsSensor() == true || minContact.IsEnabled() == false) {
+ bA.m_sweep.Set(b2World.s_backupA);
+ bB.m_sweep.Set(b2World.s_backupB);
+ bA.SynchronizeTransform();
+ bB.SynchronizeTransform();
+ continue;
+ }
+ if (minContact.IsTouching() == false) {
+ continue;
+ }
+ var seed = bA;
+ if (seed.GetType() != b2Body.b2_dynamicBody) {
+ seed = bB;
+ }
+ island.Clear();
+ var queueStart = 0;
+ var queueSize = 0;
+ queue[queueStart + queueSize++] = seed;
+ seed.m_flags |= b2Body.e_islandFlag;
+ while (queueSize > 0) {
+ b = queue[queueStart++];
+ --queueSize;
+ island.AddBody(b);
+ if (b.IsAwake() == false) {
+ b.SetAwake(true);
+ }
+ if (b.GetType() != b2Body.b2_dynamicBody) {
+ continue;
+ }
+ for (cEdge = b.m_contactList;
+ cEdge; cEdge = cEdge.next) {
+ if (island.m_contactCount == island.m_contactCapacity) {
+ break;
+ }
+ if (cEdge.contact.m_flags & b2Contact.e_islandFlag) {
+ continue;
+ }
+ if (cEdge.contact.IsSensor() == true || cEdge.contact.IsEnabled() == false || cEdge.contact.IsTouching() == false) {
+ continue;
+ }
+ island.AddContact(cEdge.contact);
+ cEdge.contact.m_flags |= b2Contact.e_islandFlag;
+ var other = cEdge.other;
+ if (other.m_flags & b2Body.e_islandFlag) {
+ continue;
+ }
+ if (other.GetType() != b2Body.b2_staticBody) {
+ other.Advance(minTOI);
+ other.SetAwake(true);
+ }
+ queue[queueStart + queueSize] = other;
+ ++queueSize;
+ other.m_flags |= b2Body.e_islandFlag;
+ }
+ for (var jEdge = b.m_jointList; jEdge; jEdge = jEdge.next) {
+ if (island.m_jointCount == island.m_jointCapacity) continue;
+ if (jEdge.joint.m_islandFlag == true) continue;
+ other = jEdge.other;
+ if (other.IsActive() == false) {
+ continue;
+ }
+ island.AddJoint(jEdge.joint);
+ jEdge.joint.m_islandFlag = true;
+ if (other.m_flags & b2Body.e_islandFlag) continue;
+ if (other.GetType() != b2Body.b2_staticBody) {
+ other.Advance(minTOI);
+ other.SetAwake(true);
+ }
+ queue[queueStart + queueSize] = other;
+ ++queueSize;
+ other.m_flags |= b2Body.e_islandFlag;
+ }
+ }
+ var subStep = b2World.s_timestep;
+ subStep.warmStarting = false;
+ subStep.dt = (1.0 - minTOI) * step.dt;
+ subStep.inv_dt = 1.0 / subStep.dt;
+ subStep.dtRatio = 0.0;
+ subStep.velocityIterations = step.velocityIterations;
+ subStep.positionIterations = step.positionIterations;
+ island.SolveTOI(subStep);
+ var i = 0;
+ for (i = 0;
+ i < island.m_bodyCount; ++i) {
+ b = island.m_bodies[i];
+ b.m_flags &= ~b2Body.e_islandFlag;
+ if (b.IsAwake() == false) {
+ continue;
+ }
+ if (b.GetType() != b2Body.b2_dynamicBody) {
+ continue;
+ }
+ b.SynchronizeFixtures();
+ for (cEdge = b.m_contactList;
+ cEdge; cEdge = cEdge.next) {
+ cEdge.contact.m_flags &= ~b2Contact.e_toiFlag;
+ }
+ }
+ for (i = 0;
+ i < island.m_contactCount; ++i) {
+ c = island.m_contacts[i];
+ c.m_flags &= ~ (b2Contact.e_toiFlag | b2Contact.e_islandFlag);
+ }
+ for (i = 0;
+ i < island.m_jointCount; ++i) {
+ j = island.m_joints[i];
+ j.m_islandFlag = false;
+ }
+ this.m_contactManager.FindNewContacts();
+ }
+ }
+ b2World.prototype.DrawJoint = function (joint) {
+ var b1 = joint.GetBodyA();
+ var b2 = joint.GetBodyB();
+ var xf1 = b1.m_xf;
+ var xf2 = b2.m_xf;
+ var x1 = xf1.position;
+ var x2 = xf2.position;
+ var p1 = joint.GetAnchorA();
+ var p2 = joint.GetAnchorB();
+ var color = b2World.s_jointColor;
+ switch (joint.m_type) {
+ case b2Joint.e_distanceJoint:
+ this.m_debugDraw.DrawSegment(p1, p2, color);
+ break;
+ case b2Joint.e_pulleyJoint:
+ {
+ var pulley = ((joint instanceof b2PulleyJoint ? joint : null));
+ var s1 = pulley.GetGroundAnchorA();
+ var s2 = pulley.GetGroundAnchorB();
+ this.m_debugDraw.DrawSegment(s1, p1, color);
+ this.m_debugDraw.DrawSegment(s2, p2, color);
+ this.m_debugDraw.DrawSegment(s1, s2, color);
+ }
+ break;
+ case b2Joint.e_mouseJoint:
+ this.m_debugDraw.DrawSegment(p1, p2, color);
+ break;
+ default:
+ if (b1 != this.m_groundBody) this.m_debugDraw.DrawSegment(x1, p1, color);
+ this.m_debugDraw.DrawSegment(p1, p2, color);
+ if (b2 != this.m_groundBody) this.m_debugDraw.DrawSegment(x2, p2, color);
+ }
+ }
+ b2World.prototype.DrawShape = function (shape, xf, color) {
+ switch (shape.m_type) {
+ case b2Shape.e_circleShape:
+ {
+ var circle = ((shape instanceof b2CircleShape ? shape : null));
+ var center = b2Math.MulX(xf, circle.m_p);
+ var radius = circle.m_radius;
+ var axis = xf.R.col1;
+ this.m_debugDraw.DrawSolidCircle(center, radius, axis, color);
+ }
+ break;
+ case b2Shape.e_polygonShape:
+ {
+ var i = 0;
+ var poly = ((shape instanceof b2PolygonShape ? shape : null));
+ var vertexCount = parseInt(poly.GetVertexCount());
+ var localVertices = poly.GetVertices();
+ var vertices = new Vector(vertexCount);
+ for (i = 0;
+ i < vertexCount; ++i) {
+ vertices[i] = b2Math.MulX(xf, localVertices[i]);
+ }
+ this.m_debugDraw.DrawSolidPolygon(vertices, vertexCount, color);
+ }
+ break;
+ case b2Shape.e_edgeShape:
+ {
+ var edge = (shape instanceof b2EdgeShape ? shape : null);
+ this.m_debugDraw.DrawSegment(b2Math.MulX(xf, edge.GetVertex1()), b2Math.MulX(xf, edge.GetVertex2()), color);
+ }
+ break;
+ }
+ }
+ Box2D.postDefs.push(function () {
+ Box2D.Dynamics.b2World.s_timestep2 = new b2TimeStep();
+ Box2D.Dynamics.b2World.s_xf = new b2Transform();
+ Box2D.Dynamics.b2World.s_backupA = new b2Sweep();
+ Box2D.Dynamics.b2World.s_backupB = new b2Sweep();
+ Box2D.Dynamics.b2World.s_timestep = new b2TimeStep();
+ Box2D.Dynamics.b2World.s_queue = new Vector();
+ Box2D.Dynamics.b2World.s_jointColor = new b2Color(0.5, 0.8, 0.8);
+ Box2D.Dynamics.b2World.e_newFixture = 0x0001;
+ Box2D.Dynamics.b2World.e_locked = 0x0002;
+ });
+})();
+(function () {
+ var b2CircleShape = Box2D.Collision.Shapes.b2CircleShape,
+ b2EdgeChainDef = Box2D.Collision.Shapes.b2EdgeChainDef,
+ b2EdgeShape = Box2D.Collision.Shapes.b2EdgeShape,
+ b2MassData = Box2D.Collision.Shapes.b2MassData,
+ b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape,
+ b2Shape = Box2D.Collision.Shapes.b2Shape,
+ b2CircleContact = Box2D.Dynamics.Contacts.b2CircleContact,
+ b2Contact = Box2D.Dynamics.Contacts.b2Contact,
+ b2ContactConstraint = Box2D.Dynamics.Contacts.b2ContactConstraint,
+ b2ContactConstraintPoint = Box2D.Dynamics.Contacts.b2ContactConstraintPoint,
+ b2ContactEdge = Box2D.Dynamics.Contacts.b2ContactEdge,
+ b2ContactFactory = Box2D.Dynamics.Contacts.b2ContactFactory,
+ b2ContactRegister = Box2D.Dynamics.Contacts.b2ContactRegister,
+ b2ContactResult = Box2D.Dynamics.Contacts.b2ContactResult,
+ b2ContactSolver = Box2D.Dynamics.Contacts.b2ContactSolver,
+ b2EdgeAndCircleContact = Box2D.Dynamics.Contacts.b2EdgeAndCircleContact,
+ b2NullContact = Box2D.Dynamics.Contacts.b2NullContact,
+ b2PolyAndCircleContact = Box2D.Dynamics.Contacts.b2PolyAndCircleContact,
+ b2PolyAndEdgeContact = Box2D.Dynamics.Contacts.b2PolyAndEdgeContact,
+ b2PolygonContact = Box2D.Dynamics.Contacts.b2PolygonContact,
+ b2PositionSolverManifold = Box2D.Dynamics.Contacts.b2PositionSolverManifold,
+ b2Body = Box2D.Dynamics.b2Body,
+ b2BodyDef = Box2D.Dynamics.b2BodyDef,
+ b2ContactFilter = Box2D.Dynamics.b2ContactFilter,
+ b2ContactImpulse = Box2D.Dynamics.b2ContactImpulse,
+ b2ContactListener = Box2D.Dynamics.b2ContactListener,
+ b2ContactManager = Box2D.Dynamics.b2ContactManager,
+ b2DebugDraw = Box2D.Dynamics.b2DebugDraw,
+ b2DestructionListener = Box2D.Dynamics.b2DestructionListener,
+ b2FilterData = Box2D.Dynamics.b2FilterData,
+ b2Fixture = Box2D.Dynamics.b2Fixture,
+ b2FixtureDef = Box2D.Dynamics.b2FixtureDef,
+ b2Island = Box2D.Dynamics.b2Island,
+ b2TimeStep = Box2D.Dynamics.b2TimeStep,
+ b2World = Box2D.Dynamics.b2World,
+ b2Color = Box2D.Common.b2Color,
+ b2internal = Box2D.Common.b2internal,
+ b2Settings = Box2D.Common.b2Settings,
+ b2Mat22 = Box2D.Common.Math.b2Mat22,
+ b2Mat33 = Box2D.Common.Math.b2Mat33,
+ b2Math = Box2D.Common.Math.b2Math,
+ b2Sweep = Box2D.Common.Math.b2Sweep,
+ b2Transform = Box2D.Common.Math.b2Transform,
+ b2Vec2 = Box2D.Common.Math.b2Vec2,
+ b2Vec3 = Box2D.Common.Math.b2Vec3,
+ b2AABB = Box2D.Collision.b2AABB,
+ b2Bound = Box2D.Collision.b2Bound,
+ b2BoundValues = Box2D.Collision.b2BoundValues,
+ b2Collision = Box2D.Collision.b2Collision,
+ b2ContactID = Box2D.Collision.b2ContactID,
+ b2ContactPoint = Box2D.Collision.b2ContactPoint,
+ b2Distance = Box2D.Collision.b2Distance,
+ b2DistanceInput = Box2D.Collision.b2DistanceInput,
+ b2DistanceOutput = Box2D.Collision.b2DistanceOutput,
+ b2DistanceProxy = Box2D.Collision.b2DistanceProxy,
+ b2DynamicTree = Box2D.Collision.b2DynamicTree,
+ b2DynamicTreeBroadPhase = Box2D.Collision.b2DynamicTreeBroadPhase,
+ b2DynamicTreeNode = Box2D.Collision.b2DynamicTreeNode,
+ b2DynamicTreePair = Box2D.Collision.b2DynamicTreePair,
+ b2Manifold = Box2D.Collision.b2Manifold,
+ b2ManifoldPoint = Box2D.Collision.b2ManifoldPoint,
+ b2Point = Box2D.Collision.b2Point,
+ b2RayCastInput = Box2D.Collision.b2RayCastInput,
+ b2RayCastOutput = Box2D.Collision.b2RayCastOutput,
+ b2Segment = Box2D.Collision.b2Segment,
+ b2SeparationFunction = Box2D.Collision.b2SeparationFunction,
+ b2Simplex = Box2D.Collision.b2Simplex,
+ b2SimplexCache = Box2D.Collision.b2SimplexCache,
+ b2SimplexVertex = Box2D.Collision.b2SimplexVertex,
+ b2TimeOfImpact = Box2D.Collision.b2TimeOfImpact,
+ b2TOIInput = Box2D.Collision.b2TOIInput,
+ b2WorldManifold = Box2D.Collision.b2WorldManifold,
+ ClipVertex = Box2D.Collision.ClipVertex,
+ Features = Box2D.Collision.Features,
+ IBroadPhase = Box2D.Collision.IBroadPhase;
+
+ Box2D.inherit(b2CircleContact, Box2D.Dynamics.Contacts.b2Contact);
+ b2CircleContact.prototype.__super = Box2D.Dynamics.Contacts.b2Contact.prototype;
+ b2CircleContact.b2CircleContact = function () {
+ Box2D.Dynamics.Contacts.b2Contact.b2Contact.apply(this, arguments);
+ };
+ b2CircleContact.Create = function (allocator) {
+ return new b2CircleContact();
+ }
+ b2CircleContact.Destroy = function (contact, allocator) {}
+ b2CircleContact.prototype.Reset = function (fixtureA, fixtureB) {
+ this.__super.Reset.call(this, fixtureA, fixtureB);
+ }
+ b2CircleContact.prototype.Evaluate = function () {
+ var bA = this.m_fixtureA.GetBody();
+ var bB = this.m_fixtureB.GetBody();
+ b2Collision.CollideCircles(this.m_manifold, (this.m_fixtureA.GetShape() instanceof b2CircleShape ? this.m_fixtureA.GetShape() : null), bA.m_xf, (this.m_fixtureB.GetShape() instanceof b2CircleShape ? this.m_fixtureB.GetShape() : null), bB.m_xf);
+ }
+ b2Contact.b2Contact = function () {
+ this.m_nodeA = new b2ContactEdge();
+ this.m_nodeB = new b2ContactEdge();
+ this.m_manifold = new b2Manifold();
+ this.m_oldManifold = new b2Manifold();
+ };
+ b2Contact.prototype.GetManifold = function () {
+ return this.m_manifold;
+ }
+ b2Contact.prototype.GetWorldManifold = function (worldManifold) {
+ var bodyA = this.m_fixtureA.GetBody();
+ var bodyB = this.m_fixtureB.GetBody();
+ var shapeA = this.m_fixtureA.GetShape();
+ var shapeB = this.m_fixtureB.GetShape();
+ worldManifold.Initialize(this.m_manifold, bodyA.GetTransform(), shapeA.m_radius, bodyB.GetTransform(), shapeB.m_radius);
+ }
+ b2Contact.prototype.IsTouching = function () {
+ return (this.m_flags & b2Contact.e_touchingFlag) == b2Contact.e_touchingFlag;
+ }
+ b2Contact.prototype.IsContinuous = function () {
+ return (this.m_flags & b2Contact.e_continuousFlag) == b2Contact.e_continuousFlag;
+ }
+ b2Contact.prototype.SetSensor = function (sensor) {
+ if (sensor) {
+ this.m_flags |= b2Contact.e_sensorFlag;
+ }
+ else {
+ this.m_flags &= ~b2Contact.e_sensorFlag;
+ }
+ }
+ b2Contact.prototype.IsSensor = function () {
+ return (this.m_flags & b2Contact.e_sensorFlag) == b2Contact.e_sensorFlag;
+ }
+ b2Contact.prototype.SetEnabled = function (flag) {
+ if (flag) {
+ this.m_flags |= b2Contact.e_enabledFlag;
+ }
+ else {
+ this.m_flags &= ~b2Contact.e_enabledFlag;
+ }
+ }
+ b2Contact.prototype.IsEnabled = function () {
+ return (this.m_flags & b2Contact.e_enabledFlag) == b2Contact.e_enabledFlag;
+ }
+ b2Contact.prototype.GetNext = function () {
+ return this.m_next;
+ }
+ b2Contact.prototype.GetFixtureA = function () {
+ return this.m_fixtureA;
+ }
+ b2Contact.prototype.GetFixtureB = function () {
+ return this.m_fixtureB;
+ }
+ b2Contact.prototype.FlagForFiltering = function () {
+ this.m_flags |= b2Contact.e_filterFlag;
+ }
+ b2Contact.prototype.b2Contact = function () {}
+ b2Contact.prototype.Reset = function (fixtureA, fixtureB) {
+ if (fixtureA === undefined) fixtureA = null;
+ if (fixtureB === undefined) fixtureB = null;
+ this.m_flags = b2Contact.e_enabledFlag;
+ if (!fixtureA || !fixtureB) {
+ this.m_fixtureA = null;
+ this.m_fixtureB = null;
+ return;
+ }
+ if (fixtureA.IsSensor() || fixtureB.IsSensor()) {
+ this.m_flags |= b2Contact.e_sensorFlag;
+ }
+ var bodyA = fixtureA.GetBody();
+ var bodyB = fixtureB.GetBody();
+ if (bodyA.GetType() != b2Body.b2_dynamicBody || bodyA.IsBullet() || bodyB.GetType() != b2Body.b2_dynamicBody || bodyB.IsBullet()) {
+ this.m_flags |= b2Contact.e_continuousFlag;
+ }
+ this.m_fixtureA = fixtureA;
+ this.m_fixtureB = fixtureB;
+ this.m_manifold.m_pointCount = 0;
+ this.m_prev = null;
+ this.m_next = null;
+ this.m_nodeA.contact = null;
+ this.m_nodeA.prev = null;
+ this.m_nodeA.next = null;
+ this.m_nodeA.other = null;
+ this.m_nodeB.contact = null;
+ this.m_nodeB.prev = null;
+ this.m_nodeB.next = null;
+ this.m_nodeB.other = null;
+ }
+ b2Contact.prototype.Update = function (listener) {
+ var tManifold = this.m_oldManifold;
+ this.m_oldManifold = this.m_manifold;
+ this.m_manifold = tManifold;
+ this.m_flags |= b2Contact.e_enabledFlag;
+ var touching = false;
+ var wasTouching = (this.m_flags & b2Contact.e_touchingFlag) == b2Contact.e_touchingFlag;
+ var bodyA = this.m_fixtureA.m_body;
+ var bodyB = this.m_fixtureB.m_body;
+ var aabbOverlap = this.m_fixtureA.m_aabb.TestOverlap(this.m_fixtureB.m_aabb);
+ if (this.m_flags & b2Contact.e_sensorFlag) {
+ if (aabbOverlap) {
+ var shapeA = this.m_fixtureA.GetShape();
+ var shapeB = this.m_fixtureB.GetShape();
+ var xfA = bodyA.GetTransform();
+ var xfB = bodyB.GetTransform();
+ touching = b2Shape.TestOverlap(shapeA, xfA, shapeB, xfB);
+ }
+ this.m_manifold.m_pointCount = 0;
+ }
+ else {
+ if (bodyA.GetType() != b2Body.b2_dynamicBody || bodyA.IsBullet() || bodyB.GetType() != b2Body.b2_dynamicBody || bodyB.IsBullet()) {
+ this.m_flags |= b2Contact.e_continuousFlag;
+ }
+ else {
+ this.m_flags &= ~b2Contact.e_continuousFlag;
+ }
+ if (aabbOverlap) {
+ this.Evaluate();
+ touching = this.m_manifold.m_pointCount > 0;
+ for (var i = 0; i < this.m_manifold.m_pointCount; ++i) {
+ var mp2 = this.m_manifold.m_points[i];
+ mp2.m_normalImpulse = 0.0;
+ mp2.m_tangentImpulse = 0.0;
+ var id2 = mp2.m_id;
+ for (var j = 0; j < this.m_oldManifold.m_pointCount; ++j) {
+ var mp1 = this.m_oldManifold.m_points[j];
+ if (mp1.m_id.key == id2.key) {
+ mp2.m_normalImpulse = mp1.m_normalImpulse;
+ mp2.m_tangentImpulse = mp1.m_tangentImpulse;
+ break;
+ }
+ }
+ }
+ }
+ else {
+ this.m_manifold.m_pointCount = 0;
+ }
+ if (touching != wasTouching) {
+ bodyA.SetAwake(true);
+ bodyB.SetAwake(true);
+ }
+ }
+ if (touching) {
+ this.m_flags |= b2Contact.e_touchingFlag;
+ }
+ else {
+ this.m_flags &= ~b2Contact.e_touchingFlag;
+ }
+ if (wasTouching == false && touching == true) {
+ listener.BeginContact(this);
+ }
+ if (wasTouching == true && touching == false) {
+ listener.EndContact(this);
+ }
+ if ((this.m_flags & b2Contact.e_sensorFlag) == 0) {
+ listener.PreSolve(this, this.m_oldManifold);
+ }
+ }
+ b2Contact.prototype.Evaluate = function () {}
+ b2Contact.prototype.ComputeTOI = function (sweepA, sweepB) {
+ b2Contact.s_input.proxyA.Set(this.m_fixtureA.GetShape());
+ b2Contact.s_input.proxyB.Set(this.m_fixtureB.GetShape());
+ b2Contact.s_input.sweepA = sweepA;
+ b2Contact.s_input.sweepB = sweepB;
+ b2Contact.s_input.tolerance = b2Settings.b2_linearSlop;
+ return b2TimeOfImpact.TimeOfImpact(b2Contact.s_input);
+ }
+ Box2D.postDefs.push(function () {
+ Box2D.Dynamics.Contacts.b2Contact.e_sensorFlag = 0x0001;
+ Box2D.Dynamics.Contacts.b2Contact.e_continuousFlag = 0x0002;
+ Box2D.Dynamics.Contacts.b2Contact.e_islandFlag = 0x0004;
+ Box2D.Dynamics.Contacts.b2Contact.e_toiFlag = 0x0008;
+ Box2D.Dynamics.Contacts.b2Contact.e_touchingFlag = 0x0010;
+ Box2D.Dynamics.Contacts.b2Contact.e_enabledFlag = 0x0020;
+ Box2D.Dynamics.Contacts.b2Contact.e_filterFlag = 0x0040;
+ Box2D.Dynamics.Contacts.b2Contact.s_input = new b2TOIInput();
+ });
+ b2ContactConstraint.b2ContactConstraint = function () {
+ this.localPlaneNormal = new b2Vec2();
+ this.localPoint = new b2Vec2();
+ this.normal = new b2Vec2();
+ this.normalMass = new b2Mat22();
+ this.K = new b2Mat22();
+ };
+ b2ContactConstraint.prototype.b2ContactConstraint = function () {
+ this.points = new Vector(b2Settings.b2_maxManifoldPoints);
+ for (var i = 0; i < b2Settings.b2_maxManifoldPoints; i++) {
+ this.points[i] = new b2ContactConstraintPoint();
+ }
+ }
+ b2ContactConstraintPoint.b2ContactConstraintPoint = function () {
+ this.localPoint = new b2Vec2();
+ this.rA = new b2Vec2();
+ this.rB = new b2Vec2();
+ };
+ b2ContactEdge.b2ContactEdge = function () {};
+ b2ContactFactory.b2ContactFactory = function () {};
+ b2ContactFactory.prototype.b2ContactFactory = function (allocator) {
+ this.m_allocator = allocator;
+ this.InitializeRegisters();
+ }
+ b2ContactFactory.prototype.AddType = function (createFcn, destroyFcn, type1, type2) {
+ if (type1 === undefined) type1 = 0;
+ if (type2 === undefined) type2 = 0;
+ this.m_registers[type1][type2].createFcn = createFcn;
+ this.m_registers[type1][type2].destroyFcn = destroyFcn;
+ this.m_registers[type1][type2].primary = true;
+ if (type1 != type2) {
+ this.m_registers[type2][type1].createFcn = createFcn;
+ this.m_registers[type2][type1].destroyFcn = destroyFcn;
+ this.m_registers[type2][type1].primary = false;
+ }
+ }
+ b2ContactFactory.prototype.InitializeRegisters = function () {
+ this.m_registers = new Vector(b2Shape.e_shapeTypeCount);
+ for (var i = 0; i < b2Shape.e_shapeTypeCount; i++) {
+ this.m_registers[i] = new Vector(b2Shape.e_shapeTypeCount);
+ for (var j = 0; j < b2Shape.e_shapeTypeCount; j++) {
+ this.m_registers[i][j] = new b2ContactRegister();
+ }
+ }
+ this.AddType(b2CircleContact.Create, b2CircleContact.Destroy, b2Shape.e_circleShape, b2Shape.e_circleShape);
+ this.AddType(b2PolyAndCircleContact.Create, b2PolyAndCircleContact.Destroy, b2Shape.e_polygonShape, b2Shape.e_circleShape);
+ this.AddType(b2PolygonContact.Create, b2PolygonContact.Destroy, b2Shape.e_polygonShape, b2Shape.e_polygonShape);
+ this.AddType(b2EdgeAndCircleContact.Create, b2EdgeAndCircleContact.Destroy, b2Shape.e_edgeShape, b2Shape.e_circleShape);
+ this.AddType(b2PolyAndEdgeContact.Create, b2PolyAndEdgeContact.Destroy, b2Shape.e_polygonShape, b2Shape.e_edgeShape);
+ }
+ b2ContactFactory.prototype.Create = function (fixtureA, fixtureB) {
+ var type1 = parseInt(fixtureA.GetType());
+ var type2 = parseInt(fixtureB.GetType());
+ var reg = this.m_registers[type1][type2];
+ var c;
+ if (reg.pool) {
+ c = reg.pool;
+ reg.pool = c.m_next;
+ reg.poolCount--;
+ c.Reset(fixtureA, fixtureB);
+ return c;
+ }
+ var createFcn = reg.createFcn;
+ if (createFcn != null) {
+ if (reg.primary) {
+ c = createFcn(this.m_allocator);
+ c.Reset(fixtureA, fixtureB);
+ return c;
+ }
+ else {
+ c = createFcn(this.m_allocator);
+ c.Reset(fixtureB, fixtureA);
+ return c;
+ }
+ }
+ else {
+ return null;
+ }
+ }
+ b2ContactFactory.prototype.Destroy = function (contact) {
+ if (contact.m_manifold.m_pointCount > 0) {
+ contact.m_fixtureA.m_body.SetAwake(true);
+ contact.m_fixtureB.m_body.SetAwake(true);
+ }
+ var type1 = parseInt(contact.m_fixtureA.GetType());
+ var type2 = parseInt(contact.m_fixtureB.GetType());
+ var reg = this.m_registers[type1][type2];
+ if (true) {
+ reg.poolCount++;
+ contact.m_next = reg.pool;
+ reg.pool = contact;
+ }
+ var destroyFcn = reg.destroyFcn;
+ destroyFcn(contact, this.m_allocator);
+ }
+ b2ContactRegister.b2ContactRegister = function () {};
+ b2ContactResult.b2ContactResult = function () {
+ this.position = new b2Vec2();
+ this.normal = new b2Vec2();
+ this.id = new b2ContactID();
+ };
+ b2ContactSolver.b2ContactSolver = function () {
+ this.m_step = new b2TimeStep();
+ this.m_constraints = new Vector();
+ };
+ b2ContactSolver.prototype.b2ContactSolver = function () {}
+ b2ContactSolver.prototype.Initialize = function (step, contacts, contactCount, allocator) {
+ if (contactCount === undefined) contactCount = 0;
+ var contact;
+ this.m_step.Set(step);
+ this.m_allocator = allocator;
+ var i = 0;
+ var tVec;
+ var tMat;
+ this.m_constraintCount = contactCount;
+ while (this.m_constraints.length < this.m_constraintCount) {
+ this.m_constraints[this.m_constraints.length] = new b2ContactConstraint();
+ }
+ for (i = 0;
+ i < contactCount; ++i) {
+ contact = contacts[i];
+ var fixtureA = contact.m_fixtureA;
+ var fixtureB = contact.m_fixtureB;
+ var shapeA = fixtureA.m_shape;
+ var shapeB = fixtureB.m_shape;
+ var radiusA = shapeA.m_radius;
+ var radiusB = shapeB.m_radius;
+ var bodyA = fixtureA.m_body;
+ var bodyB = fixtureB.m_body;
+ var manifold = contact.GetManifold();
+ var friction = b2Settings.b2MixFriction(fixtureA.GetFriction(), fixtureB.GetFriction());
+ var restitution = b2Settings.b2MixRestitution(fixtureA.GetRestitution(), fixtureB.GetRestitution());
+ var vAX = bodyA.m_linearVelocity.x;
+ var vAY = bodyA.m_linearVelocity.y;
+ var vBX = bodyB.m_linearVelocity.x;
+ var vBY = bodyB.m_linearVelocity.y;
+ var wA = bodyA.m_angularVelocity;
+ var wB = bodyB.m_angularVelocity;
+ b2Settings.b2Assert(manifold.m_pointCount > 0);
+ b2ContactSolver.s_worldManifold.Initialize(manifold, bodyA.m_xf, radiusA, bodyB.m_xf, radiusB);
+ var normalX = b2ContactSolver.s_worldManifold.m_normal.x;
+ var normalY = b2ContactSolver.s_worldManifold.m_normal.y;
+ var cc = this.m_constraints[i];
+ cc.bodyA = bodyA;
+ cc.bodyB = bodyB;
+ cc.manifold = manifold;
+ cc.normal.x = normalX;
+ cc.normal.y = normalY;
+ cc.pointCount = manifold.m_pointCount;
+ cc.friction = friction;
+ cc.restitution = restitution;
+ cc.localPlaneNormal.x = manifold.m_localPlaneNormal.x;
+ cc.localPlaneNormal.y = manifold.m_localPlaneNormal.y;
+ cc.localPoint.x = manifold.m_localPoint.x;
+ cc.localPoint.y = manifold.m_localPoint.y;
+ cc.radius = radiusA + radiusB;
+ cc.type = manifold.m_type;
+ for (var k = 0; k < cc.pointCount; ++k) {
+ var cp = manifold.m_points[k];
+ var ccp = cc.points[k];
+ ccp.normalImpulse = cp.m_normalImpulse;
+ ccp.tangentImpulse = cp.m_tangentImpulse;
+ ccp.localPoint.SetV(cp.m_localPoint);
+ var rAX = ccp.rA.x = b2ContactSolver.s_worldManifold.m_points[k].x - bodyA.m_sweep.c.x;
+ var rAY = ccp.rA.y = b2ContactSolver.s_worldManifold.m_points[k].y - bodyA.m_sweep.c.y;
+ var rBX = ccp.rB.x = b2ContactSolver.s_worldManifold.m_points[k].x - bodyB.m_sweep.c.x;
+ var rBY = ccp.rB.y = b2ContactSolver.s_worldManifold.m_points[k].y - bodyB.m_sweep.c.y;
+ var rnA = rAX * normalY - rAY * normalX;
+ var rnB = rBX * normalY - rBY * normalX;
+ rnA *= rnA;
+ rnB *= rnB;
+ var kNormal = bodyA.m_invMass + bodyB.m_invMass + bodyA.m_invI * rnA + bodyB.m_invI * rnB;
+ ccp.normalMass = 1.0 / kNormal;
+ var kEqualized = bodyA.m_mass * bodyA.m_invMass + bodyB.m_mass * bodyB.m_invMass;
+ kEqualized += bodyA.m_mass * bodyA.m_invI * rnA + bodyB.m_mass * bodyB.m_invI * rnB;
+ ccp.equalizedMass = 1.0 / kEqualized;
+ var tangentX = normalY;
+ var tangentY = (-normalX);
+ var rtA = rAX * tangentY - rAY * tangentX;
+ var rtB = rBX * tangentY - rBY * tangentX;
+ rtA *= rtA;
+ rtB *= rtB;
+ var kTangent = bodyA.m_invMass + bodyB.m_invMass + bodyA.m_invI * rtA + bodyB.m_invI * rtB;
+ ccp.tangentMass = 1.0 / kTangent;
+ ccp.velocityBias = 0.0;
+ var tX = vBX + ((-wB * rBY)) - vAX - ((-wA * rAY));
+ var tY = vBY + (wB * rBX) - vAY - (wA * rAX);
+ var vRel = cc.normal.x * tX + cc.normal.y * tY;
+ if (vRel < (-b2Settings.b2_velocityThreshold)) {
+ ccp.velocityBias += (-cc.restitution * vRel);
+ }
+ }
+ if (cc.pointCount == 2) {
+ var ccp1 = cc.points[0];
+ var ccp2 = cc.points[1];
+ var invMassA = bodyA.m_invMass;
+ var invIA = bodyA.m_invI;
+ var invMassB = bodyB.m_invMass;
+ var invIB = bodyB.m_invI;
+ var rn1A = ccp1.rA.x * normalY - ccp1.rA.y * normalX;
+ var rn1B = ccp1.rB.x * normalY - ccp1.rB.y * normalX;
+ var rn2A = ccp2.rA.x * normalY - ccp2.rA.y * normalX;
+ var rn2B = ccp2.rB.x * normalY - ccp2.rB.y * normalX;
+ var k11 = invMassA + invMassB + invIA * rn1A * rn1A + invIB * rn1B * rn1B;
+ var k22 = invMassA + invMassB + invIA * rn2A * rn2A + invIB * rn2B * rn2B;
+ var k12 = invMassA + invMassB + invIA * rn1A * rn2A + invIB * rn1B * rn2B;
+ var k_maxConditionNumber = 100.0;
+ if (k11 * k11 < k_maxConditionNumber * (k11 * k22 - k12 * k12)) {
+ cc.K.col1.Set(k11, k12);
+ cc.K.col2.Set(k12, k22);
+ cc.K.GetInverse(cc.normalMass);
+ }
+ else {
+ cc.pointCount = 1;
+ }
+ }
+ }
+ }
+ b2ContactSolver.prototype.InitVelocityConstraints = function (step) {
+ var tVec;
+ var tVec2;
+ var tMat;
+ for (var i = 0; i < this.m_constraintCount; ++i) {
+ var c = this.m_constraints[i];
+ var bodyA = c.bodyA;
+ var bodyB = c.bodyB;
+ var invMassA = bodyA.m_invMass;
+ var invIA = bodyA.m_invI;
+ var invMassB = bodyB.m_invMass;
+ var invIB = bodyB.m_invI;
+ var normalX = c.normal.x;
+ var normalY = c.normal.y;
+ var tangentX = normalY;
+ var tangentY = (-normalX);
+ var tX = 0;
+ var j = 0;
+ var tCount = 0;
+ if (step.warmStarting) {
+ tCount = c.pointCount;
+ for (j = 0;
+ j < tCount; ++j) {
+ var ccp = c.points[j];
+ ccp.normalImpulse *= step.dtRatio;
+ ccp.tangentImpulse *= step.dtRatio;
+ var PX = ccp.normalImpulse * normalX + ccp.tangentImpulse * tangentX;
+ var PY = ccp.normalImpulse * normalY + ccp.tangentImpulse * tangentY;
+ bodyA.m_angularVelocity -= invIA * (ccp.rA.x * PY - ccp.rA.y * PX);
+ bodyA.m_linearVelocity.x -= invMassA * PX;
+ bodyA.m_linearVelocity.y -= invMassA * PY;
+ bodyB.m_angularVelocity += invIB * (ccp.rB.x * PY - ccp.rB.y * PX);
+ bodyB.m_linearVelocity.x += invMassB * PX;
+ bodyB.m_linearVelocity.y += invMassB * PY;
+ }
+ }
+ else {
+ tCount = c.pointCount;
+ for (j = 0;
+ j < tCount; ++j) {
+ var ccp2 = c.points[j];
+ ccp2.normalImpulse = 0.0;
+ ccp2.tangentImpulse = 0.0;
+ }
+ }
+ }
+ }
+ b2ContactSolver.prototype.SolveVelocityConstraints = function () {
+ var j = 0;
+ var ccp;
+ var rAX = 0;
+ var rAY = 0;
+ var rBX = 0;
+ var rBY = 0;
+ var dvX = 0;
+ var dvY = 0;
+ var vn = 0;
+ var vt = 0;
+ var lambda = 0;
+ var maxFriction = 0;
+ var newImpulse = 0;
+ var PX = 0;
+ var PY = 0;
+ var dX = 0;
+ var dY = 0;
+ var P1X = 0;
+ var P1Y = 0;
+ var P2X = 0;
+ var P2Y = 0;
+ var tMat;
+ var tVec;
+ for (var i = 0; i < this.m_constraintCount; ++i) {
+ var c = this.m_constraints[i];
+ var bodyA = c.bodyA;
+ var bodyB = c.bodyB;
+ var wA = bodyA.m_angularVelocity;
+ var wB = bodyB.m_angularVelocity;
+ var vA = bodyA.m_linearVelocity;
+ var vB = bodyB.m_linearVelocity;
+ var invMassA = bodyA.m_invMass;
+ var invIA = bodyA.m_invI;
+ var invMassB = bodyB.m_invMass;
+ var invIB = bodyB.m_invI;
+ var normalX = c.normal.x;
+ var normalY = c.normal.y;
+ var tangentX = normalY;
+ var tangentY = (-normalX);
+ var friction = c.friction;
+ var tX = 0;
+ for (j = 0;
+ j < c.pointCount; j++) {
+ ccp = c.points[j];
+ dvX = vB.x - wB * ccp.rB.y - vA.x + wA * ccp.rA.y;
+ dvY = vB.y + wB * ccp.rB.x - vA.y - wA * ccp.rA.x;
+ vt = dvX * tangentX + dvY * tangentY;
+ lambda = ccp.tangentMass * (-vt);
+ maxFriction = friction * ccp.normalImpulse;
+ newImpulse = b2Math.Clamp(ccp.tangentImpulse + lambda, (-maxFriction), maxFriction);
+ lambda = newImpulse - ccp.tangentImpulse;
+ PX = lambda * tangentX;
+ PY = lambda * tangentY;
+ vA.x -= invMassA * PX;
+ vA.y -= invMassA * PY;
+ wA -= invIA * (ccp.rA.x * PY - ccp.rA.y * PX);
+ vB.x += invMassB * PX;
+ vB.y += invMassB * PY;
+ wB += invIB * (ccp.rB.x * PY - ccp.rB.y * PX);
+ ccp.tangentImpulse = newImpulse;
+ }
+ var tCount = parseInt(c.pointCount);
+ if (c.pointCount == 1) {
+ ccp = c.points[0];
+ dvX = vB.x + ((-wB * ccp.rB.y)) - vA.x - ((-wA * ccp.rA.y));
+ dvY = vB.y + (wB * ccp.rB.x) - vA.y - (wA * ccp.rA.x);
+ vn = dvX * normalX + dvY * normalY;
+ lambda = (-ccp.normalMass * (vn - ccp.velocityBias));
+ newImpulse = ccp.normalImpulse + lambda;
+ newImpulse = newImpulse > 0 ? newImpulse : 0.0;
+ lambda = newImpulse - ccp.normalImpulse;
+ PX = lambda * normalX;
+ PY = lambda * normalY;
+ vA.x -= invMassA * PX;
+ vA.y -= invMassA * PY;
+ wA -= invIA * (ccp.rA.x * PY - ccp.rA.y * PX);
+ vB.x += invMassB * PX;
+ vB.y += invMassB * PY;
+ wB += invIB * (ccp.rB.x * PY - ccp.rB.y * PX);
+ ccp.normalImpulse = newImpulse;
+ }
+ else {
+ var cp1 = c.points[0];
+ var cp2 = c.points[1];
+ var aX = cp1.normalImpulse;
+ var aY = cp2.normalImpulse;
+ var dv1X = vB.x - wB * cp1.rB.y - vA.x + wA * cp1.rA.y;
+ var dv1Y = vB.y + wB * cp1.rB.x - vA.y - wA * cp1.rA.x;
+ var dv2X = vB.x - wB * cp2.rB.y - vA.x + wA * cp2.rA.y;
+ var dv2Y = vB.y + wB * cp2.rB.x - vA.y - wA * cp2.rA.x;
+ var vn1 = dv1X * normalX + dv1Y * normalY;
+ var vn2 = dv2X * normalX + dv2Y * normalY;
+ var bX = vn1 - cp1.velocityBias;
+ var bY = vn2 - cp2.velocityBias;
+ tMat = c.K;
+ bX -= tMat.col1.x * aX + tMat.col2.x * aY;
+ bY -= tMat.col1.y * aX + tMat.col2.y * aY;
+ var k_errorTol = 0.001;
+ for (;;) {
+ tMat = c.normalMass;
+ var xX = (-(tMat.col1.x * bX + tMat.col2.x * bY));
+ var xY = (-(tMat.col1.y * bX + tMat.col2.y * bY));
+ if (xX >= 0.0 && xY >= 0.0) {
+ dX = xX - aX;
+ dY = xY - aY;
+ P1X = dX * normalX;
+ P1Y = dX * normalY;
+ P2X = dY * normalX;
+ P2Y = dY * normalY;
+ vA.x -= invMassA * (P1X + P2X);
+ vA.y -= invMassA * (P1Y + P2Y);
+ wA -= invIA * (cp1.rA.x * P1Y - cp1.rA.y * P1X + cp2.rA.x * P2Y - cp2.rA.y * P2X);
+ vB.x += invMassB * (P1X + P2X);
+ vB.y += invMassB * (P1Y + P2Y);
+ wB += invIB * (cp1.rB.x * P1Y - cp1.rB.y * P1X + cp2.rB.x * P2Y - cp2.rB.y * P2X);
+ cp1.normalImpulse = xX;
+ cp2.normalImpulse = xY;
+ break;
+ }
+ xX = (-cp1.normalMass * bX);
+ xY = 0.0;
+ vn1 = 0.0;
+ vn2 = c.K.col1.y * xX + bY;
+ if (xX >= 0.0 && vn2 >= 0.0) {
+ dX = xX - aX;
+ dY = xY - aY;
+ P1X = dX * normalX;
+ P1Y = dX * normalY;
+ P2X = dY * normalX;
+ P2Y = dY * normalY;
+ vA.x -= invMassA * (P1X + P2X);
+ vA.y -= invMassA * (P1Y + P2Y);
+ wA -= invIA * (cp1.rA.x * P1Y - cp1.rA.y * P1X + cp2.rA.x * P2Y - cp2.rA.y * P2X);
+ vB.x += invMassB * (P1X + P2X);
+ vB.y += invMassB * (P1Y + P2Y);
+ wB += invIB * (cp1.rB.x * P1Y - cp1.rB.y * P1X + cp2.rB.x * P2Y - cp2.rB.y * P2X);
+ cp1.normalImpulse = xX;
+ cp2.normalImpulse = xY;
+ break;
+ }
+ xX = 0.0;
+ xY = (-cp2.normalMass * bY);
+ vn1 = c.K.col2.x * xY + bX;
+ vn2 = 0.0;
+ if (xY >= 0.0 && vn1 >= 0.0) {
+ dX = xX - aX;
+ dY = xY - aY;
+ P1X = dX * normalX;
+ P1Y = dX * normalY;
+ P2X = dY * normalX;
+ P2Y = dY * normalY;
+ vA.x -= invMassA * (P1X + P2X);
+ vA.y -= invMassA * (P1Y + P2Y);
+ wA -= invIA * (cp1.rA.x * P1Y - cp1.rA.y * P1X + cp2.rA.x * P2Y - cp2.rA.y * P2X);
+ vB.x += invMassB * (P1X + P2X);
+ vB.y += invMassB * (P1Y + P2Y);
+ wB += invIB * (cp1.rB.x * P1Y - cp1.rB.y * P1X + cp2.rB.x * P2Y - cp2.rB.y * P2X);
+ cp1.normalImpulse = xX;
+ cp2.normalImpulse = xY;
+ break;
+ }
+ xX = 0.0;
+ xY = 0.0;
+ vn1 = bX;
+ vn2 = bY;
+ if (vn1 >= 0.0 && vn2 >= 0.0) {
+ dX = xX - aX;
+ dY = xY - aY;
+ P1X = dX * normalX;
+ P1Y = dX * normalY;
+ P2X = dY * normalX;
+ P2Y = dY * normalY;
+ vA.x -= invMassA * (P1X + P2X);
+ vA.y -= invMassA * (P1Y + P2Y);
+ wA -= invIA * (cp1.rA.x * P1Y - cp1.rA.y * P1X + cp2.rA.x * P2Y - cp2.rA.y * P2X);
+ vB.x += invMassB * (P1X + P2X);
+ vB.y += invMassB * (P1Y + P2Y);
+ wB += invIB * (cp1.rB.x * P1Y - cp1.rB.y * P1X + cp2.rB.x * P2Y - cp2.rB.y * P2X);
+ cp1.normalImpulse = xX;
+ cp2.normalImpulse = xY;
+ break;
+ }
+ break;
+ }
+ }
+ bodyA.m_angularVelocity = wA;
+ bodyB.m_angularVelocity = wB;
+ }
+ }
+ b2ContactSolver.prototype.FinalizeVelocityConstraints = function () {
+ for (var i = 0; i < this.m_constraintCount; ++i) {
+ var c = this.m_constraints[i];
+ var m = c.manifold;
+ for (var j = 0; j < c.pointCount; ++j) {
+ var point1 = m.m_points[j];
+ var point2 = c.points[j];
+ point1.m_normalImpulse = point2.normalImpulse;
+ point1.m_tangentImpulse = point2.tangentImpulse;
+ }
+ }
+ }
+ b2ContactSolver.prototype.SolvePositionConstraints = function (baumgarte) {
+ if (baumgarte === undefined) baumgarte = 0;
+ var minSeparation = 0.0;
+ for (var i = 0; i < this.m_constraintCount; i++) {
+ var c = this.m_constraints[i];
+ var bodyA = c.bodyA;
+ var bodyB = c.bodyB;
+ var invMassA = bodyA.m_mass * bodyA.m_invMass;
+ var invIA = bodyA.m_mass * bodyA.m_invI;
+ var invMassB = bodyB.m_mass * bodyB.m_invMass;
+ var invIB = bodyB.m_mass * bodyB.m_invI;
+ b2ContactSolver.s_psm.Initialize(c);
+ var normal = b2ContactSolver.s_psm.m_normal;
+ for (var j = 0; j < c.pointCount; j++) {
+ var ccp = c.points[j];
+ var point = b2ContactSolver.s_psm.m_points[j];
+ var separation = b2ContactSolver.s_psm.m_separations[j];
+ var rAX = point.x - bodyA.m_sweep.c.x;
+ var rAY = point.y - bodyA.m_sweep.c.y;
+ var rBX = point.x - bodyB.m_sweep.c.x;
+ var rBY = point.y - bodyB.m_sweep.c.y;
+ minSeparation = minSeparation < separation ? minSeparation : separation;
+ var C = b2Math.Clamp(baumgarte * (separation + b2Settings.b2_linearSlop), (-b2Settings.b2_maxLinearCorrection), 0.0);
+ var impulse = (-ccp.equalizedMass * C);
+ var PX = impulse * normal.x;
+ var PY = impulse * normal.y;bodyA.m_sweep.c.x -= invMassA * PX;
+ bodyA.m_sweep.c.y -= invMassA * PY;
+ bodyA.m_sweep.a -= invIA * (rAX * PY - rAY * PX);
+ bodyA.SynchronizeTransform();
+ bodyB.m_sweep.c.x += invMassB * PX;
+ bodyB.m_sweep.c.y += invMassB * PY;
+ bodyB.m_sweep.a += invIB * (rBX * PY - rBY * PX);
+ bodyB.SynchronizeTransform();
+ }
+ }
+ return minSeparation > (-1.5 * b2Settings.b2_linearSlop);
+ }
+ Box2D.postDefs.push(function () {
+ Box2D.Dynamics.Contacts.b2ContactSolver.s_worldManifold = new b2WorldManifold();
+ Box2D.Dynamics.Contacts.b2ContactSolver.s_psm = new b2PositionSolverManifold();
+ });
+ Box2D.inherit(b2EdgeAndCircleContact, Box2D.Dynamics.Contacts.b2Contact);
+ b2EdgeAndCircleContact.prototype.__super = Box2D.Dynamics.Contacts.b2Contact.prototype;
+ b2EdgeAndCircleContact.b2EdgeAndCircleContact = function () {
+ Box2D.Dynamics.Contacts.b2Contact.b2Contact.apply(this, arguments);
+ };
+ b2EdgeAndCircleContact.Create = function (allocator) {
+ return new b2EdgeAndCircleContact();
+ }
+ b2EdgeAndCircleContact.Destroy = function (contact, allocator) {}
+ b2EdgeAndCircleContact.prototype.Reset = function (fixtureA, fixtureB) {
+ this.__super.Reset.call(this, fixtureA, fixtureB);
+ }
+ b2EdgeAndCircleContact.prototype.Evaluate = function () {
+ var bA = this.m_fixtureA.GetBody();
+ var bB = this.m_fixtureB.GetBody();
+ this.b2CollideEdgeAndCircle(this.m_manifold, (this.m_fixtureA.GetShape() instanceof b2EdgeShape ? this.m_fixtureA.GetShape() : null), bA.m_xf, (this.m_fixtureB.GetShape() instanceof b2CircleShape ? this.m_fixtureB.GetShape() : null), bB.m_xf);
+ }
+ b2EdgeAndCircleContact.prototype.b2CollideEdgeAndCircle = function (manifold, edge, xf1, circle, xf2) {}
+ Box2D.inherit(b2NullContact, Box2D.Dynamics.Contacts.b2Contact);
+ b2NullContact.prototype.__super = Box2D.Dynamics.Contacts.b2Contact.prototype;
+ b2NullContact.b2NullContact = function () {
+ Box2D.Dynamics.Contacts.b2Contact.b2Contact.apply(this, arguments);
+ };
+ b2NullContact.prototype.b2NullContact = function () {
+ this.__super.b2Contact.call(this);
+ }
+ b2NullContact.prototype.Evaluate = function () {}
+ Box2D.inherit(b2PolyAndCircleContact, Box2D.Dynamics.Contacts.b2Contact);
+ b2PolyAndCircleContact.prototype.__super = Box2D.Dynamics.Contacts.b2Contact.prototype;
+ b2PolyAndCircleContact.b2PolyAndCircleContact = function () {
+ Box2D.Dynamics.Contacts.b2Contact.b2Contact.apply(this, arguments);
+ };
+ b2PolyAndCircleContact.Create = function (allocator) {
+ return new b2PolyAndCircleContact();
+ }
+ b2PolyAndCircleContact.Destroy = function (contact, allocator) {}
+ b2PolyAndCircleContact.prototype.Reset = function (fixtureA, fixtureB) {
+ this.__super.Reset.call(this, fixtureA, fixtureB);
+ b2Settings.b2Assert(fixtureA.GetType() == b2Shape.e_polygonShape);
+ b2Settings.b2Assert(fixtureB.GetType() == b2Shape.e_circleShape);
+ }
+ b2PolyAndCircleContact.prototype.Evaluate = function () {
+ var bA = this.m_fixtureA.m_body;
+ var bB = this.m_fixtureB.m_body;
+ b2Collision.CollidePolygonAndCircle(this.m_manifold, (this.m_fixtureA.GetShape() instanceof b2PolygonShape ? this.m_fixtureA.GetShape() : null), bA.m_xf, (this.m_fixtureB.GetShape() instanceof b2CircleShape ? this.m_fixtureB.GetShape() : null), bB.m_xf);
+ }
+ Box2D.inherit(b2PolyAndEdgeContact, Box2D.Dynamics.Contacts.b2Contact);
+ b2PolyAndEdgeContact.prototype.__super = Box2D.Dynamics.Contacts.b2Contact.prototype;
+ b2PolyAndEdgeContact.b2PolyAndEdgeContact = function () {
+ Box2D.Dynamics.Contacts.b2Contact.b2Contact.apply(this, arguments);
+ };
+ b2PolyAndEdgeContact.Create = function (allocator) {
+ return new b2PolyAndEdgeContact();
+ }
+ b2PolyAndEdgeContact.Destroy = function (contact, allocator) {}
+ b2PolyAndEdgeContact.prototype.Reset = function (fixtureA, fixtureB) {
+ this.__super.Reset.call(this, fixtureA, fixtureB);
+ b2Settings.b2Assert(fixtureA.GetType() == b2Shape.e_polygonShape);
+ b2Settings.b2Assert(fixtureB.GetType() == b2Shape.e_edgeShape);
+ }
+ b2PolyAndEdgeContact.prototype.Evaluate = function () {
+ var bA = this.m_fixtureA.GetBody();
+ var bB = this.m_fixtureB.GetBody();
+ this.b2CollidePolyAndEdge(this.m_manifold, (this.m_fixtureA.GetShape() instanceof b2PolygonShape ? this.m_fixtureA.GetShape() : null), bA.m_xf, (this.m_fixtureB.GetShape() instanceof b2EdgeShape ? this.m_fixtureB.GetShape() : null), bB.m_xf);
+ }
+ b2PolyAndEdgeContact.prototype.b2CollidePolyAndEdge = function (manifold, polygon, xf1, edge, xf2) {}
+ Box2D.inherit(b2PolygonContact, Box2D.Dynamics.Contacts.b2Contact);
+ b2PolygonContact.prototype.__super = Box2D.Dynamics.Contacts.b2Contact.prototype;
+ b2PolygonContact.b2PolygonContact = function () {
+ Box2D.Dynamics.Contacts.b2Contact.b2Contact.apply(this, arguments);
+ };
+ b2PolygonContact.Create = function (allocator) {
+ return new b2PolygonContact();
+ }
+ b2PolygonContact.Destroy = function (contact, allocator) {}
+ b2PolygonContact.prototype.Reset = function (fixtureA, fixtureB) {
+ this.__super.Reset.call(this, fixtureA, fixtureB);
+ }
+ b2PolygonContact.prototype.Evaluate = function () {
+ var bA = this.m_fixtureA.GetBody();
+ var bB = this.m_fixtureB.GetBody();
+ b2Collision.CollidePolygons(this.m_manifold, (this.m_fixtureA.GetShape() instanceof b2PolygonShape ? this.m_fixtureA.GetShape() : null), bA.m_xf, (this.m_fixtureB.GetShape() instanceof b2PolygonShape ? this.m_fixtureB.GetShape() : null), bB.m_xf);
+ }
+ b2PositionSolverManifold.b2PositionSolverManifold = function () {};
+ b2PositionSolverManifold.prototype.b2PositionSolverManifold = function () {
+ this.m_normal = new b2Vec2();
+ this.m_separations = new Vector_a2j_Number(b2Settings.b2_maxManifoldPoints);
+ this.m_points = new Vector(b2Settings.b2_maxManifoldPoints);
+ for (var i = 0; i < b2Settings.b2_maxManifoldPoints; i++) {
+ this.m_points[i] = new b2Vec2();
+ }
+ }
+ b2PositionSolverManifold.prototype.Initialize = function (cc) {
+ b2Settings.b2Assert(cc.pointCount > 0);
+ var i = 0;
+ var clipPointX = 0;
+ var clipPointY = 0;
+ var tMat;
+ var tVec;
+ var planePointX = 0;
+ var planePointY = 0;
+ switch (cc.type) {
+ case b2Manifold.e_circles:
+ {
+ tMat = cc.bodyA.m_xf.R;
+ tVec = cc.localPoint;
+ var pointAX = cc.bodyA.m_xf.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ var pointAY = cc.bodyA.m_xf.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ tMat = cc.bodyB.m_xf.R;
+ tVec = cc.points[0].localPoint;
+ var pointBX = cc.bodyB.m_xf.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ var pointBY = cc.bodyB.m_xf.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ var dX = pointBX - pointAX;
+ var dY = pointBY - pointAY;
+ var d2 = dX * dX + dY * dY;
+ if (d2 > Number.MIN_VALUE * Number.MIN_VALUE) {
+ var d = Math.sqrt(d2);
+ this.m_normal.x = dX / d;
+ this.m_normal.y = dY / d;
+ }
+ else {
+ this.m_normal.x = 1.0;
+ this.m_normal.y = 0.0;
+ }
+ this.m_points[0].x = 0.5 * (pointAX + pointBX);
+ this.m_points[0].y = 0.5 * (pointAY + pointBY);
+ this.m_separations[0] = dX * this.m_normal.x + dY * this.m_normal.y - cc.radius;
+ }
+ break;
+ case b2Manifold.e_faceA:
+ {
+ tMat = cc.bodyA.m_xf.R;
+ tVec = cc.localPlaneNormal;
+ this.m_normal.x = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y;
+ this.m_normal.y = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y;
+ tMat = cc.bodyA.m_xf.R;
+ tVec = cc.localPoint;
+ planePointX = cc.bodyA.m_xf.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ planePointY = cc.bodyA.m_xf.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ tMat = cc.bodyB.m_xf.R;
+ for (i = 0;
+ i < cc.pointCount; ++i) {
+ tVec = cc.points[i].localPoint;
+ clipPointX = cc.bodyB.m_xf.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ clipPointY = cc.bodyB.m_xf.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ this.m_separations[i] = (clipPointX - planePointX) * this.m_normal.x + (clipPointY - planePointY) * this.m_normal.y - cc.radius;
+ this.m_points[i].x = clipPointX;
+ this.m_points[i].y = clipPointY;
+ }
+ }
+ break;
+ case b2Manifold.e_faceB:
+ {
+ tMat = cc.bodyB.m_xf.R;
+ tVec = cc.localPlaneNormal;
+ this.m_normal.x = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y;
+ this.m_normal.y = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y;
+ tMat = cc.bodyB.m_xf.R;
+ tVec = cc.localPoint;
+ planePointX = cc.bodyB.m_xf.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ planePointY = cc.bodyB.m_xf.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ tMat = cc.bodyA.m_xf.R;
+ for (i = 0;
+ i < cc.pointCount; ++i) {
+ tVec = cc.points[i].localPoint;
+ clipPointX = cc.bodyA.m_xf.position.x + (tMat.col1.x * tVec.x + tMat.col2.x * tVec.y);
+ clipPointY = cc.bodyA.m_xf.position.y + (tMat.col1.y * tVec.x + tMat.col2.y * tVec.y);
+ this.m_separations[i] = (clipPointX - planePointX) * this.m_normal.x + (clipPointY - planePointY) * this.m_normal.y - cc.radius;
+ this.m_points[i].Set(clipPointX, clipPointY);
+ }
+ this.m_normal.x *= (-1);
+ this.m_normal.y *= (-1);
+ }
+ break;
+ }
+ }
+ Box2D.postDefs.push(function () {
+ Box2D.Dynamics.Contacts.b2PositionSolverManifold.circlePointA = new b2Vec2();
+ Box2D.Dynamics.Contacts.b2PositionSolverManifold.circlePointB = new b2Vec2();
+ });
+})();
+(function () {
+ var b2Body = Box2D.Dynamics.b2Body,
+ b2BodyDef = Box2D.Dynamics.b2BodyDef,
+ b2ContactFilter = Box2D.Dynamics.b2ContactFilter,
+ b2ContactImpulse = Box2D.Dynamics.b2ContactImpulse,
+ b2ContactListener = Box2D.Dynamics.b2ContactListener,
+ b2ContactManager = Box2D.Dynamics.b2ContactManager,
+ b2DebugDraw = Box2D.Dynamics.b2DebugDraw,
+ b2DestructionListener = Box2D.Dynamics.b2DestructionListener,
+ b2FilterData = Box2D.Dynamics.b2FilterData,
+ b2Fixture = Box2D.Dynamics.b2Fixture,
+ b2FixtureDef = Box2D.Dynamics.b2FixtureDef,
+ b2Island = Box2D.Dynamics.b2Island,
+ b2TimeStep = Box2D.Dynamics.b2TimeStep,
+ b2World = Box2D.Dynamics.b2World,
+ b2Mat22 = Box2D.Common.Math.b2Mat22,
+ b2Mat33 = Box2D.Common.Math.b2Mat33,
+ b2Math = Box2D.Common.Math.b2Math,
+ b2Sweep = Box2D.Common.Math.b2Sweep,
+ b2Transform = Box2D.Common.Math.b2Transform,
+ b2Vec2 = Box2D.Common.Math.b2Vec2,
+ b2Vec3 = Box2D.Common.Math.b2Vec3,
+ b2Color = Box2D.Common.b2Color,
+ b2internal = Box2D.Common.b2internal,
+ b2Settings = Box2D.Common.b2Settings,
+ b2CircleShape = Box2D.Collision.Shapes.b2CircleShape,
+ b2EdgeChainDef = Box2D.Collision.Shapes.b2EdgeChainDef,
+ b2EdgeShape = Box2D.Collision.Shapes.b2EdgeShape,
+ b2MassData = Box2D.Collision.Shapes.b2MassData,
+ b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape,
+ b2Shape = Box2D.Collision.Shapes.b2Shape,
+ b2BuoyancyController = Box2D.Dynamics.Controllers.b2BuoyancyController,
+ b2ConstantAccelController = Box2D.Dynamics.Controllers.b2ConstantAccelController,
+ b2ConstantForceController = Box2D.Dynamics.Controllers.b2ConstantForceController,
+ b2Controller = Box2D.Dynamics.Controllers.b2Controller,
+ b2ControllerEdge = Box2D.Dynamics.Controllers.b2ControllerEdge,
+ b2GravityController = Box2D.Dynamics.Controllers.b2GravityController,
+ b2TensorDampingController = Box2D.Dynamics.Controllers.b2TensorDampingController;
+
+ Box2D.inherit(b2BuoyancyController, Box2D.Dynamics.Controllers.b2Controller);
+ b2BuoyancyController.prototype.__super = Box2D.Dynamics.Controllers.b2Controller.prototype;
+ b2BuoyancyController.b2BuoyancyController = function () {
+ Box2D.Dynamics.Controllers.b2Controller.b2Controller.apply(this, arguments);
+ this.normal = new b2Vec2(0, (-1));
+ this.offset = 0;
+ this.density = 0;
+ this.velocity = new b2Vec2(0, 0);
+ this.linearDrag = 2;
+ this.angularDrag = 1;
+ this.useDensity = false;
+ this.useWorldGravity = true;
+ this.gravity = null;
+ };
+ b2BuoyancyController.prototype.Step = function (step) {
+ if (!this.m_bodyList) return;
+ if (this.useWorldGravity) {
+ this.gravity = this.GetWorld().GetGravity().Copy();
+ }
+ for (var i = this.m_bodyList; i; i = i.nextBody) {
+ var body = i.body;
+ if (body.IsAwake() == false) {
+ continue;
+ }
+ var areac = new b2Vec2();
+ var massc = new b2Vec2();
+ var area = 0.0;
+ var mass = 0.0;
+ for (var fixture = body.GetFixtureList(); fixture; fixture = fixture.GetNext()) {
+ var sc = new b2Vec2();
+ var sarea = fixture.GetShape().ComputeSubmergedArea(this.normal, this.offset, body.GetTransform(), sc);
+ area += sarea;
+ areac.x += sarea * sc.x;
+ areac.y += sarea * sc.y;
+ var shapeDensity = 0;
+ if (this.useDensity) {
+ shapeDensity = 1;
+ }
+ else {
+ shapeDensity = 1;
+ }
+ mass += sarea * shapeDensity;
+ massc.x += sarea * sc.x * shapeDensity;
+ massc.y += sarea * sc.y * shapeDensity;
+ }
+ areac.x /= area;
+ areac.y /= area;
+ massc.x /= mass;
+ massc.y /= mass;
+ if (area < Number.MIN_VALUE) continue;
+ var buoyancyForce = this.gravity.GetNegative();
+ buoyancyForce.Multiply(this.density * area);
+ body.ApplyForce(buoyancyForce, massc);
+ var dragForce = body.GetLinearVelocityFromWorldPoint(areac);
+ dragForce.Subtract(this.velocity);
+ dragForce.Multiply((-this.linearDrag * area));
+ body.ApplyForce(dragForce, areac);
+ body.ApplyTorque((-body.GetInertia() / body.GetMass() * area * body.GetAngularVelocity() * this.angularDrag));
+ }
+ }
+ b2BuoyancyController.prototype.Draw = function (debugDraw) {
+ var r = 1000;
+ var p1 = new b2Vec2();
+ var p2 = new b2Vec2();
+ p1.x = this.normal.x * this.offset + this.normal.y * r;
+ p1.y = this.normal.y * this.offset - this.normal.x * r;
+ p2.x = this.normal.x * this.offset - this.normal.y * r;
+ p2.y = this.normal.y * this.offset + this.normal.x * r;
+ var color = new b2Color(0, 0, 1);
+ debugDraw.DrawSegment(p1, p2, color);
+ }
+ Box2D.inherit(b2ConstantAccelController, Box2D.Dynamics.Controllers.b2Controller);
+ b2ConstantAccelController.prototype.__super = Box2D.Dynamics.Controllers.b2Controller.prototype;
+ b2ConstantAccelController.b2ConstantAccelController = function () {
+ Box2D.Dynamics.Controllers.b2Controller.b2Controller.apply(this, arguments);
+ this.A = new b2Vec2(0, 0);
+ };
+ b2ConstantAccelController.prototype.Step = function (step) {
+ var smallA = new b2Vec2(this.A.x * step.dt, this.A.y * step.dt);
+ for (var i = this.m_bodyList; i; i = i.nextBody) {
+ var body = i.body;
+ if (!body.IsAwake()) continue;
+ body.SetLinearVelocity(new b2Vec2(body.GetLinearVelocity().x + smallA.x, body.GetLinearVelocity().y + smallA.y));
+ }
+ }
+ Box2D.inherit(b2ConstantForceController, Box2D.Dynamics.Controllers.b2Controller);
+ b2ConstantForceController.prototype.__super = Box2D.Dynamics.Controllers.b2Controller.prototype;
+ b2ConstantForceController.b2ConstantForceController = function () {
+ Box2D.Dynamics.Controllers.b2Controller.b2Controller.apply(this, arguments);
+ this.F = new b2Vec2(0, 0);
+ };
+ b2ConstantForceController.prototype.Step = function (step) {
+ for (var i = this.m_bodyList; i; i = i.nextBody) {
+ var body = i.body;
+ if (!body.IsAwake()) continue;
+ body.ApplyForce(this.F, body.GetWorldCenter());
+ }
+ }
+ b2Controller.b2Controller = function () {};
+ b2Controller.prototype.Step = function (step) {}
+ b2Controller.prototype.Draw = function (debugDraw) {}
+ b2Controller.prototype.AddBody = function (body) {
+ var edge = new b2ControllerEdge();
+ edge.controller = this;
+ edge.body = body;
+ edge.nextBody = this.m_bodyList;
+ edge.prevBody = null;
+ this.m_bodyList = edge;
+ if (edge.nextBody) edge.nextBody.prevBody = edge;
+ this.m_bodyCount++;
+ edge.nextController = body.m_controllerList;
+ edge.prevController = null;
+ body.m_controllerList = edge;
+ if (edge.nextController) edge.nextController.prevController = edge;
+ body.m_controllerCount++;
+ }
+ b2Controller.prototype.RemoveBody = function (body) {
+ var edge = body.m_controllerList;
+ while (edge && edge.controller != this)
+ edge = edge.nextController;
+ if (edge.prevBody) edge.prevBody.nextBody = edge.nextBody;
+ if (edge.nextBody) edge.nextBody.prevBody = edge.prevBody;
+ if (edge.nextController) edge.nextController.prevController = edge.prevController;
+ if (edge.prevController) edge.prevController.nextController = edge.nextController;
+ if (this.m_bodyList == edge) this.m_bodyList = edge.nextBody;
+ if (body.m_controllerList == edge) body.m_controllerList = edge.nextController;
+ body.m_controllerCount--;
+ this.m_bodyCount--;
+ }
+ b2Controller.prototype.Clear = function () {
+ while (this.m_bodyList)
+ this.RemoveBody(this.m_bodyList.body);
+ }
+ b2Controller.prototype.GetNext = function () {
+ return this.m_next;
+ }
+ b2Controller.prototype.GetWorld = function () {
+ return this.m_world;
+ }
+ b2Controller.prototype.GetBodyList = function () {
+ return this.m_bodyList;
+ }
+ b2ControllerEdge.b2ControllerEdge = function () {};
+ Box2D.inherit(b2GravityController, Box2D.Dynamics.Controllers.b2Controller);
+ b2GravityController.prototype.__super = Box2D.Dynamics.Controllers.b2Controller.prototype;
+ b2GravityController.b2GravityController = function () {
+ Box2D.Dynamics.Controllers.b2Controller.b2Controller.apply(this, arguments);
+ this.G = 1;
+ this.invSqr = true;
+ };
+ b2GravityController.prototype.Step = function (step) {
+ var i = null;
+ var body1 = null;
+ var p1 = null;
+ var mass1 = 0;
+ var j = null;
+ var body2 = null;
+ var p2 = null;
+ var dx = 0;
+ var dy = 0;
+ var r2 = 0;
+ var f = null;
+ if (this.invSqr) {
+ for (i = this.m_bodyList;
+ i; i = i.nextBody) {
+ body1 = i.body;
+ p1 = body1.GetWorldCenter();
+ mass1 = body1.GetMass();
+ for (j = this.m_bodyList;
+ j != i; j = j.nextBody) {
+ body2 = j.body;
+ p2 = body2.GetWorldCenter();
+ dx = p2.x - p1.x;
+ dy = p2.y - p1.y;
+ r2 = dx * dx + dy * dy;
+ if (r2 < Number.MIN_VALUE) continue;
+ f = new b2Vec2(dx, dy);
+ f.Multiply(this.G / r2 / Math.sqrt(r2) * mass1 * body2.GetMass());
+ if (body1.IsAwake()) body1.ApplyForce(f, p1);
+ f.Multiply((-1));
+ if (body2.IsAwake()) body2.ApplyForce(f, p2);
+ }
+ }
+ }
+ else {
+ for (i = this.m_bodyList;
+ i; i = i.nextBody) {
+ body1 = i.body;
+ p1 = body1.GetWorldCenter();
+ mass1 = body1.GetMass();
+ for (j = this.m_bodyList;
+ j != i; j = j.nextBody) {
+ body2 = j.body;
+ p2 = body2.GetWorldCenter();
+ dx = p2.x - p1.x;
+ dy = p2.y - p1.y;
+ r2 = dx * dx + dy * dy;
+ if (r2 < Number.MIN_VALUE) continue;
+ f = new b2Vec2(dx, dy);
+ f.Multiply(this.G / r2 * mass1 * body2.GetMass());
+ if (body1.IsAwake()) body1.ApplyForce(f, p1);
+ f.Multiply((-1));
+ if (body2.IsAwake()) body2.ApplyForce(f, p2);
+ }
+ }
+ }
+ }
+ Box2D.inherit(b2TensorDampingController, Box2D.Dynamics.Controllers.b2Controller);
+ b2TensorDampingController.prototype.__super = Box2D.Dynamics.Controllers.b2Controller.prototype;
+ b2TensorDampingController.b2TensorDampingController = function () {
+ Box2D.Dynamics.Controllers.b2Controller.b2Controller.apply(this, arguments);
+ this.T = new b2Mat22();
+ this.maxTimestep = 0;
+ };
+ b2TensorDampingController.prototype.SetAxisAligned = function (xDamping, yDamping) {
+ if (xDamping === undefined) xDamping = 0;
+ if (yDamping === undefined) yDamping = 0;
+ this.T.col1.x = (-xDamping);
+ this.T.col1.y = 0;
+ this.T.col2.x = 0;
+ this.T.col2.y = (-yDamping);
+ if (xDamping > 0 || yDamping > 0) {
+ this.maxTimestep = 1 / Math.max(xDamping, yDamping);
+ }
+ else {
+ this.maxTimestep = 0;
+ }
+ }
+ b2TensorDampingController.prototype.Step = function (step) {
+ var timestep = step.dt;
+ if (timestep <= Number.MIN_VALUE) return;
+ if (timestep > this.maxTimestep && this.maxTimestep > 0) timestep = this.maxTimestep;
+ for (var i = this.m_bodyList; i; i = i.nextBody) {
+ var body = i.body;
+ if (!body.IsAwake()) {
+ continue;
+ }
+ var damping = body.GetWorldVector(b2Math.MulMV(this.T, body.GetLocalVector(body.GetLinearVelocity())));
+ body.SetLinearVelocity(new b2Vec2(body.GetLinearVelocity().x + damping.x * timestep, body.GetLinearVelocity().y + damping.y * timestep));
+ }
+ }
+})();
+(function () {
+ var b2Color = Box2D.Common.b2Color,
+ b2internal = Box2D.Common.b2internal,
+ b2Settings = Box2D.Common.b2Settings,
+ b2Mat22 = Box2D.Common.Math.b2Mat22,
+ b2Mat33 = Box2D.Common.Math.b2Mat33,
+ b2Math = Box2D.Common.Math.b2Math,
+ b2Sweep = Box2D.Common.Math.b2Sweep,
+ b2Transform = Box2D.Common.Math.b2Transform,
+ b2Vec2 = Box2D.Common.Math.b2Vec2,
+ b2Vec3 = Box2D.Common.Math.b2Vec3,
+ b2DistanceJoint = Box2D.Dynamics.Joints.b2DistanceJoint,
+ b2DistanceJointDef = Box2D.Dynamics.Joints.b2DistanceJointDef,
+ b2FrictionJoint = Box2D.Dynamics.Joints.b2FrictionJoint,
+ b2FrictionJointDef = Box2D.Dynamics.Joints.b2FrictionJointDef,
+ b2GearJoint = Box2D.Dynamics.Joints.b2GearJoint,
+ b2GearJointDef = Box2D.Dynamics.Joints.b2GearJointDef,
+ b2Jacobian = Box2D.Dynamics.Joints.b2Jacobian,
+ b2Joint = Box2D.Dynamics.Joints.b2Joint,
+ b2JointDef = Box2D.Dynamics.Joints.b2JointDef,
+ b2JointEdge = Box2D.Dynamics.Joints.b2JointEdge,
+ b2LineJoint = Box2D.Dynamics.Joints.b2LineJoint,
+ b2LineJointDef = Box2D.Dynamics.Joints.b2LineJointDef,
+ b2MouseJoint = Box2D.Dynamics.Joints.b2MouseJoint,
+ b2MouseJointDef = Box2D.Dynamics.Joints.b2MouseJointDef,
+ b2PrismaticJoint = Box2D.Dynamics.Joints.b2PrismaticJoint,
+ b2PrismaticJointDef = Box2D.Dynamics.Joints.b2PrismaticJointDef,
+ b2PulleyJoint = Box2D.Dynamics.Joints.b2PulleyJoint,
+ b2PulleyJointDef = Box2D.Dynamics.Joints.b2PulleyJointDef,
+ b2RevoluteJoint = Box2D.Dynamics.Joints.b2RevoluteJoint,
+ b2RevoluteJointDef = Box2D.Dynamics.Joints.b2RevoluteJointDef,
+ b2WeldJoint = Box2D.Dynamics.Joints.b2WeldJoint,
+ b2WeldJointDef = Box2D.Dynamics.Joints.b2WeldJointDef,
+ b2Body = Box2D.Dynamics.b2Body,
+ b2BodyDef = Box2D.Dynamics.b2BodyDef,
+ b2ContactFilter = Box2D.Dynamics.b2ContactFilter,
+ b2ContactImpulse = Box2D.Dynamics.b2ContactImpulse,
+ b2ContactListener = Box2D.Dynamics.b2ContactListener,
+ b2ContactManager = Box2D.Dynamics.b2ContactManager,
+ b2DebugDraw = Box2D.Dynamics.b2DebugDraw,
+ b2DestructionListener = Box2D.Dynamics.b2DestructionListener,
+ b2FilterData = Box2D.Dynamics.b2FilterData,
+ b2Fixture = Box2D.Dynamics.b2Fixture,
+ b2FixtureDef = Box2D.Dynamics.b2FixtureDef,
+ b2Island = Box2D.Dynamics.b2Island,
+ b2TimeStep = Box2D.Dynamics.b2TimeStep,
+ b2World = Box2D.Dynamics.b2World;
+
+ Box2D.inherit(b2DistanceJoint, Box2D.Dynamics.Joints.b2Joint);
+ b2DistanceJoint.prototype.__super = Box2D.Dynamics.Joints.b2Joint.prototype;
+ b2DistanceJoint.b2DistanceJoint = function () {
+ Box2D.Dynamics.Joints.b2Joint.b2Joint.apply(this, arguments);
+ this.m_localAnchor1 = new b2Vec2();
+ this.m_localAnchor2 = new b2Vec2();
+ this.m_u = new b2Vec2();
+ };
+ b2DistanceJoint.prototype.GetAnchorA = function () {
+ return this.m_bodyA.GetWorldPoint(this.m_localAnchor1);
+ }
+ b2DistanceJoint.prototype.GetAnchorB = function () {
+ return this.m_bodyB.GetWorldPoint(this.m_localAnchor2);
+ }
+ b2DistanceJoint.prototype.GetReactionForce = function (inv_dt) {
+ if (inv_dt === undefined) inv_dt = 0;
+ return new b2Vec2(inv_dt * this.m_impulse * this.m_u.x, inv_dt * this.m_impulse * this.m_u.y);
+ }
+ b2DistanceJoint.prototype.GetReactionTorque = function (inv_dt) {
+ if (inv_dt === undefined) inv_dt = 0;
+ return 0.0;
+ }
+ b2DistanceJoint.prototype.GetLength = function () {
+ return this.m_length;
+ }
+ b2DistanceJoint.prototype.SetLength = function (length) {
+ if (length === undefined) length = 0;
+ this.m_length = length;
+ }
+ b2DistanceJoint.prototype.GetFrequency = function () {
+ return this.m_frequencyHz;
+ }
+ b2DistanceJoint.prototype.SetFrequency = function (hz) {
+ if (hz === undefined) hz = 0;
+ this.m_frequencyHz = hz;
+ }
+ b2DistanceJoint.prototype.GetDampingRatio = function () {
+ return this.m_dampingRatio;
+ }
+ b2DistanceJoint.prototype.SetDampingRatio = function (ratio) {
+ if (ratio === undefined) ratio = 0;
+ this.m_dampingRatio = ratio;
+ }
+ b2DistanceJoint.prototype.b2DistanceJoint = function (def) {
+ this.__super.b2Joint.call(this, def);
+ var tMat;
+ var tX = 0;
+ var tY = 0;
+ this.m_localAnchor1.SetV(def.localAnchorA);
+ this.m_localAnchor2.SetV(def.localAnchorB);
+ this.m_length = def.length;
+ this.m_frequencyHz = def.frequencyHz;
+ this.m_dampingRatio = def.dampingRatio;
+ this.m_impulse = 0.0;
+ this.m_gamma = 0.0;
+ this.m_bias = 0.0;
+ }
+ b2DistanceJoint.prototype.InitVelocityConstraints = function (step) {
+ var tMat;
+ var tX = 0;
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ tMat = bA.m_xf.R;
+ var r1X = this.m_localAnchor1.x - bA.m_sweep.localCenter.x;
+ var r1Y = this.m_localAnchor1.y - bA.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y);
+ r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y);
+ r1X = tX;
+ tMat = bB.m_xf.R;
+ var r2X = this.m_localAnchor2.x - bB.m_sweep.localCenter.x;
+ var r2Y = this.m_localAnchor2.y - bB.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y);
+ r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y);
+ r2X = tX;
+ this.m_u.x = bB.m_sweep.c.x + r2X - bA.m_sweep.c.x - r1X;
+ this.m_u.y = bB.m_sweep.c.y + r2Y - bA.m_sweep.c.y - r1Y;
+ var length = Math.sqrt(this.m_u.x * this.m_u.x + this.m_u.y * this.m_u.y);
+ if (length > b2Settings.b2_linearSlop) {
+ this.m_u.Multiply(1.0 / length);
+ }
+ else {
+ this.m_u.SetZero();
+ }
+ var cr1u = (r1X * this.m_u.y - r1Y * this.m_u.x);
+ var cr2u = (r2X * this.m_u.y - r2Y * this.m_u.x);
+ var invMass = bA.m_invMass + bA.m_invI * cr1u * cr1u + bB.m_invMass + bB.m_invI * cr2u * cr2u;
+ this.m_mass = invMass != 0.0 ? 1.0 / invMass : 0.0;
+ if (this.m_frequencyHz > 0.0) {
+ var C = length - this.m_length;
+ var omega = 2.0 * Math.PI * this.m_frequencyHz;
+ var d = 2.0 * this.m_mass * this.m_dampingRatio * omega;
+ var k = this.m_mass * omega * omega;
+ this.m_gamma = step.dt * (d + step.dt * k);
+ this.m_gamma = this.m_gamma != 0.0 ? 1 / this.m_gamma : 0.0;
+ this.m_bias = C * step.dt * k * this.m_gamma;
+ this.m_mass = invMass + this.m_gamma;
+ this.m_mass = this.m_mass != 0.0 ? 1.0 / this.m_mass : 0.0;
+ }
+ if (step.warmStarting) {
+ this.m_impulse *= step.dtRatio;
+ var PX = this.m_impulse * this.m_u.x;
+ var PY = this.m_impulse * this.m_u.y;
+ bA.m_linearVelocity.x -= bA.m_invMass * PX;
+ bA.m_linearVelocity.y -= bA.m_invMass * PY;
+ bA.m_angularVelocity -= bA.m_invI * (r1X * PY - r1Y * PX);
+ bB.m_linearVelocity.x += bB.m_invMass * PX;
+ bB.m_linearVelocity.y += bB.m_invMass * PY;
+ bB.m_angularVelocity += bB.m_invI * (r2X * PY - r2Y * PX);
+ }
+ else {
+ this.m_impulse = 0.0;
+ }
+ }
+ b2DistanceJoint.prototype.SolveVelocityConstraints = function (step) {
+ var tMat;
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ tMat = bA.m_xf.R;
+ var r1X = this.m_localAnchor1.x - bA.m_sweep.localCenter.x;
+ var r1Y = this.m_localAnchor1.y - bA.m_sweep.localCenter.y;
+ var tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y);
+ r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y);
+ r1X = tX;
+ tMat = bB.m_xf.R;
+ var r2X = this.m_localAnchor2.x - bB.m_sweep.localCenter.x;
+ var r2Y = this.m_localAnchor2.y - bB.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y);
+ r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y);
+ r2X = tX;
+ var v1X = bA.m_linearVelocity.x + ((-bA.m_angularVelocity * r1Y));
+ var v1Y = bA.m_linearVelocity.y + (bA.m_angularVelocity * r1X);
+ var v2X = bB.m_linearVelocity.x + ((-bB.m_angularVelocity * r2Y));
+ var v2Y = bB.m_linearVelocity.y + (bB.m_angularVelocity * r2X);
+ var Cdot = (this.m_u.x * (v2X - v1X) + this.m_u.y * (v2Y - v1Y));
+ var impulse = (-this.m_mass * (Cdot + this.m_bias + this.m_gamma * this.m_impulse));
+ this.m_impulse += impulse;
+ var PX = impulse * this.m_u.x;
+ var PY = impulse * this.m_u.y;
+ bA.m_linearVelocity.x -= bA.m_invMass * PX;
+ bA.m_linearVelocity.y -= bA.m_invMass * PY;
+ bA.m_angularVelocity -= bA.m_invI * (r1X * PY - r1Y * PX);
+ bB.m_linearVelocity.x += bB.m_invMass * PX;
+ bB.m_linearVelocity.y += bB.m_invMass * PY;
+ bB.m_angularVelocity += bB.m_invI * (r2X * PY - r2Y * PX);
+ }
+ b2DistanceJoint.prototype.SolvePositionConstraints = function (baumgarte) {
+ if (baumgarte === undefined) baumgarte = 0;
+ var tMat;
+ if (this.m_frequencyHz > 0.0) {
+ return true;
+ }
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ tMat = bA.m_xf.R;
+ var r1X = this.m_localAnchor1.x - bA.m_sweep.localCenter.x;
+ var r1Y = this.m_localAnchor1.y - bA.m_sweep.localCenter.y;
+ var tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y);
+ r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y);
+ r1X = tX;
+ tMat = bB.m_xf.R;
+ var r2X = this.m_localAnchor2.x - bB.m_sweep.localCenter.x;
+ var r2Y = this.m_localAnchor2.y - bB.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y);
+ r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y);
+ r2X = tX;
+ var dX = bB.m_sweep.c.x + r2X - bA.m_sweep.c.x - r1X;
+ var dY = bB.m_sweep.c.y + r2Y - bA.m_sweep.c.y - r1Y;
+ var length = Math.sqrt(dX * dX + dY * dY);
+ dX /= length;
+ dY /= length;
+ var C = length - this.m_length;
+ C = b2Math.Clamp(C, (-b2Settings.b2_maxLinearCorrection), b2Settings.b2_maxLinearCorrection);
+ var impulse = (-this.m_mass * C);
+ this.m_u.Set(dX, dY);
+ var PX = impulse * this.m_u.x;
+ var PY = impulse * this.m_u.y;
+ bA.m_sweep.c.x -= bA.m_invMass * PX;
+ bA.m_sweep.c.y -= bA.m_invMass * PY;
+ bA.m_sweep.a -= bA.m_invI * (r1X * PY - r1Y * PX);
+ bB.m_sweep.c.x += bB.m_invMass * PX;
+ bB.m_sweep.c.y += bB.m_invMass * PY;
+ bB.m_sweep.a += bB.m_invI * (r2X * PY - r2Y * PX);
+ bA.SynchronizeTransform();
+ bB.SynchronizeTransform();
+ return b2Math.Abs(C) < b2Settings.b2_linearSlop;
+ }
+ Box2D.inherit(b2DistanceJointDef, Box2D.Dynamics.Joints.b2JointDef);
+ b2DistanceJointDef.prototype.__super = Box2D.Dynamics.Joints.b2JointDef.prototype;
+ b2DistanceJointDef.b2DistanceJointDef = function () {
+ Box2D.Dynamics.Joints.b2JointDef.b2JointDef.apply(this, arguments);
+ this.localAnchorA = new b2Vec2();
+ this.localAnchorB = new b2Vec2();
+ };
+ b2DistanceJointDef.prototype.b2DistanceJointDef = function () {
+ this.__super.b2JointDef.call(this);
+ this.type = b2Joint.e_distanceJoint;
+ this.length = 1.0;
+ this.frequencyHz = 0.0;
+ this.dampingRatio = 0.0;
+ }
+ b2DistanceJointDef.prototype.Initialize = function (bA, bB, anchorA, anchorB) {
+ this.bodyA = bA;
+ this.bodyB = bB;
+ this.localAnchorA.SetV(this.bodyA.GetLocalPoint(anchorA));
+ this.localAnchorB.SetV(this.bodyB.GetLocalPoint(anchorB));
+ var dX = anchorB.x - anchorA.x;
+ var dY = anchorB.y - anchorA.y;
+ this.length = Math.sqrt(dX * dX + dY * dY);
+ this.frequencyHz = 0.0;
+ this.dampingRatio = 0.0;
+ }
+ Box2D.inherit(b2FrictionJoint, Box2D.Dynamics.Joints.b2Joint);
+ b2FrictionJoint.prototype.__super = Box2D.Dynamics.Joints.b2Joint.prototype;
+ b2FrictionJoint.b2FrictionJoint = function () {
+ Box2D.Dynamics.Joints.b2Joint.b2Joint.apply(this, arguments);
+ this.m_localAnchorA = new b2Vec2();
+ this.m_localAnchorB = new b2Vec2();
+ this.m_linearMass = new b2Mat22();
+ this.m_linearImpulse = new b2Vec2();
+ };
+ b2FrictionJoint.prototype.GetAnchorA = function () {
+ return this.m_bodyA.GetWorldPoint(this.m_localAnchorA);
+ }
+ b2FrictionJoint.prototype.GetAnchorB = function () {
+ return this.m_bodyB.GetWorldPoint(this.m_localAnchorB);
+ }
+ b2FrictionJoint.prototype.GetReactionForce = function (inv_dt) {
+ if (inv_dt === undefined) inv_dt = 0;
+ return new b2Vec2(inv_dt * this.m_linearImpulse.x, inv_dt * this.m_linearImpulse.y);
+ }
+ b2FrictionJoint.prototype.GetReactionTorque = function (inv_dt) {
+ if (inv_dt === undefined) inv_dt = 0;
+ return inv_dt * this.m_angularImpulse;
+ }
+ b2FrictionJoint.prototype.SetMaxForce = function (force) {
+ if (force === undefined) force = 0;
+ this.m_maxForce = force;
+ }
+ b2FrictionJoint.prototype.GetMaxForce = function () {
+ return this.m_maxForce;
+ }
+ b2FrictionJoint.prototype.SetMaxTorque = function (torque) {
+ if (torque === undefined) torque = 0;
+ this.m_maxTorque = torque;
+ }
+ b2FrictionJoint.prototype.GetMaxTorque = function () {
+ return this.m_maxTorque;
+ }
+ b2FrictionJoint.prototype.b2FrictionJoint = function (def) {
+ this.__super.b2Joint.call(this, def);
+ this.m_localAnchorA.SetV(def.localAnchorA);
+ this.m_localAnchorB.SetV(def.localAnchorB);
+ this.m_linearMass.SetZero();
+ this.m_angularMass = 0.0;
+ this.m_linearImpulse.SetZero();
+ this.m_angularImpulse = 0.0;
+ this.m_maxForce = def.maxForce;
+ this.m_maxTorque = def.maxTorque;
+ }
+ b2FrictionJoint.prototype.InitVelocityConstraints = function (step) {
+ var tMat;
+ var tX = 0;
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ tMat = bA.m_xf.R;
+ var rAX = this.m_localAnchorA.x - bA.m_sweep.localCenter.x;
+ var rAY = this.m_localAnchorA.y - bA.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * rAX + tMat.col2.x * rAY);
+ rAY = (tMat.col1.y * rAX + tMat.col2.y * rAY);
+ rAX = tX;
+ tMat = bB.m_xf.R;
+ var rBX = this.m_localAnchorB.x - bB.m_sweep.localCenter.x;
+ var rBY = this.m_localAnchorB.y - bB.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * rBX + tMat.col2.x * rBY);
+ rBY = (tMat.col1.y * rBX + tMat.col2.y * rBY);
+ rBX = tX;
+ var mA = bA.m_invMass;
+ var mB = bB.m_invMass;
+ var iA = bA.m_invI;
+ var iB = bB.m_invI;
+ var K = new b2Mat22();
+ K.col1.x = mA + mB;
+ K.col2.x = 0.0;
+ K.col1.y = 0.0;
+ K.col2.y = mA + mB;
+ K.col1.x += iA * rAY * rAY;
+ K.col2.x += (-iA * rAX * rAY);
+ K.col1.y += (-iA * rAX * rAY);
+ K.col2.y += iA * rAX * rAX;
+ K.col1.x += iB * rBY * rBY;
+ K.col2.x += (-iB * rBX * rBY);
+ K.col1.y += (-iB * rBX * rBY);
+ K.col2.y += iB * rBX * rBX;
+ K.GetInverse(this.m_linearMass);
+ this.m_angularMass = iA + iB;
+ if (this.m_angularMass > 0.0) {
+ this.m_angularMass = 1.0 / this.m_angularMass;
+ }
+ if (step.warmStarting) {
+ this.m_linearImpulse.x *= step.dtRatio;
+ this.m_linearImpulse.y *= step.dtRatio;
+ this.m_angularImpulse *= step.dtRatio;
+ var P = this.m_linearImpulse;
+ bA.m_linearVelocity.x -= mA * P.x;
+ bA.m_linearVelocity.y -= mA * P.y;
+ bA.m_angularVelocity -= iA * (rAX * P.y - rAY * P.x + this.m_angularImpulse);
+ bB.m_linearVelocity.x += mB * P.x;
+ bB.m_linearVelocity.y += mB * P.y;
+ bB.m_angularVelocity += iB * (rBX * P.y - rBY * P.x + this.m_angularImpulse);
+ }
+ else {
+ this.m_linearImpulse.SetZero();
+ this.m_angularImpulse = 0.0;
+ }
+ }
+ b2FrictionJoint.prototype.SolveVelocityConstraints = function (step) {
+ var tMat;
+ var tX = 0;
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ var vA = bA.m_linearVelocity;
+ var wA = bA.m_angularVelocity;
+ var vB = bB.m_linearVelocity;
+ var wB = bB.m_angularVelocity;
+ var mA = bA.m_invMass;
+ var mB = bB.m_invMass;
+ var iA = bA.m_invI;
+ var iB = bB.m_invI;
+ tMat = bA.m_xf.R;
+ var rAX = this.m_localAnchorA.x - bA.m_sweep.localCenter.x;
+ var rAY = this.m_localAnchorA.y - bA.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * rAX + tMat.col2.x * rAY);
+ rAY = (tMat.col1.y * rAX + tMat.col2.y * rAY);
+ rAX = tX;
+ tMat = bB.m_xf.R;
+ var rBX = this.m_localAnchorB.x - bB.m_sweep.localCenter.x;
+ var rBY = this.m_localAnchorB.y - bB.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * rBX + tMat.col2.x * rBY);
+ rBY = (tMat.col1.y * rBX + tMat.col2.y * rBY);
+ rBX = tX;
+ var maxImpulse = 0; {
+ var Cdot = wB - wA;
+ var impulse = (-this.m_angularMass * Cdot);
+ var oldImpulse = this.m_angularImpulse;
+ maxImpulse = step.dt * this.m_maxTorque;
+ this.m_angularImpulse = b2Math.Clamp(this.m_angularImpulse + impulse, (-maxImpulse), maxImpulse);
+ impulse = this.m_angularImpulse - oldImpulse;
+ wA -= iA * impulse;
+ wB += iB * impulse;
+ } {
+ var CdotX = vB.x - wB * rBY - vA.x + wA * rAY;
+ var CdotY = vB.y + wB * rBX - vA.y - wA * rAX;
+ var impulseV = b2Math.MulMV(this.m_linearMass, new b2Vec2((-CdotX), (-CdotY)));
+ var oldImpulseV = this.m_linearImpulse.Copy();
+ this.m_linearImpulse.Add(impulseV);
+ maxImpulse = step.dt * this.m_maxForce;
+ if (this.m_linearImpulse.LengthSquared() > maxImpulse * maxImpulse) {
+ this.m_linearImpulse.Normalize();
+ this.m_linearImpulse.Multiply(maxImpulse);
+ }
+ impulseV = b2Math.SubtractVV(this.m_linearImpulse, oldImpulseV);
+ vA.x -= mA * impulseV.x;
+ vA.y -= mA * impulseV.y;
+ wA -= iA * (rAX * impulseV.y - rAY * impulseV.x);
+ vB.x += mB * impulseV.x;
+ vB.y += mB * impulseV.y;
+ wB += iB * (rBX * impulseV.y - rBY * impulseV.x);
+ }
+ bA.m_angularVelocity = wA;
+ bB.m_angularVelocity = wB;
+ }
+ b2FrictionJoint.prototype.SolvePositionConstraints = function (baumgarte) {
+ if (baumgarte === undefined) baumgarte = 0;
+ return true;
+ }
+ Box2D.inherit(b2FrictionJointDef, Box2D.Dynamics.Joints.b2JointDef);
+ b2FrictionJointDef.prototype.__super = Box2D.Dynamics.Joints.b2JointDef.prototype;
+ b2FrictionJointDef.b2FrictionJointDef = function () {
+ Box2D.Dynamics.Joints.b2JointDef.b2JointDef.apply(this, arguments);
+ this.localAnchorA = new b2Vec2();
+ this.localAnchorB = new b2Vec2();
+ };
+ b2FrictionJointDef.prototype.b2FrictionJointDef = function () {
+ this.__super.b2JointDef.call(this);
+ this.type = b2Joint.e_frictionJoint;
+ this.maxForce = 0.0;
+ this.maxTorque = 0.0;
+ }
+ b2FrictionJointDef.prototype.Initialize = function (bA, bB, anchor) {
+ this.bodyA = bA;
+ this.bodyB = bB;
+ this.localAnchorA.SetV(this.bodyA.GetLocalPoint(anchor));
+ this.localAnchorB.SetV(this.bodyB.GetLocalPoint(anchor));
+ }
+ Box2D.inherit(b2GearJoint, Box2D.Dynamics.Joints.b2Joint);
+ b2GearJoint.prototype.__super = Box2D.Dynamics.Joints.b2Joint.prototype;
+ b2GearJoint.b2GearJoint = function () {
+ Box2D.Dynamics.Joints.b2Joint.b2Joint.apply(this, arguments);
+ this.m_groundAnchor1 = new b2Vec2();
+ this.m_groundAnchor2 = new b2Vec2();
+ this.m_localAnchor1 = new b2Vec2();
+ this.m_localAnchor2 = new b2Vec2();
+ this.m_J = new b2Jacobian();
+ };
+ b2GearJoint.prototype.GetAnchorA = function () {
+ return this.m_bodyA.GetWorldPoint(this.m_localAnchor1);
+ }
+ b2GearJoint.prototype.GetAnchorB = function () {
+ return this.m_bodyB.GetWorldPoint(this.m_localAnchor2);
+ }
+ b2GearJoint.prototype.GetReactionForce = function (inv_dt) {
+ if (inv_dt === undefined) inv_dt = 0;
+ return new b2Vec2(inv_dt * this.m_impulse * this.m_J.linearB.x, inv_dt * this.m_impulse * this.m_J.linearB.y);
+ }
+ b2GearJoint.prototype.GetReactionTorque = function (inv_dt) {
+ if (inv_dt === undefined) inv_dt = 0;
+ var tMat = this.m_bodyB.m_xf.R;
+ var rX = this.m_localAnchor1.x - this.m_bodyB.m_sweep.localCenter.x;
+ var rY = this.m_localAnchor1.y - this.m_bodyB.m_sweep.localCenter.y;
+ var tX = tMat.col1.x * rX + tMat.col2.x * rY;
+ rY = tMat.col1.y * rX + tMat.col2.y * rY;
+ rX = tX;
+ var PX = this.m_impulse * this.m_J.linearB.x;
+ var PY = this.m_impulse * this.m_J.linearB.y;
+ return inv_dt * (this.m_impulse * this.m_J.angularB - rX * PY + rY * PX);
+ }
+ b2GearJoint.prototype.GetRatio = function () {
+ return this.m_ratio;
+ }
+ b2GearJoint.prototype.SetRatio = function (ratio) {
+ if (ratio === undefined) ratio = 0;
+ this.m_ratio = ratio;
+ }
+ b2GearJoint.prototype.b2GearJoint = function (def) {
+ this.__super.b2Joint.call(this, def);
+ var type1 = parseInt(def.joint1.m_type);
+ var type2 = parseInt(def.joint2.m_type);
+ this.m_revolute1 = null;
+ this.m_prismatic1 = null;
+ this.m_revolute2 = null;
+ this.m_prismatic2 = null;
+ var coordinate1 = 0;
+ var coordinate2 = 0;
+ this.m_ground1 = def.joint1.GetBodyA();
+ this.m_bodyA = def.joint1.GetBodyB();
+ if (type1 == b2Joint.e_revoluteJoint) {
+ this.m_revolute1 = (def.joint1 instanceof b2RevoluteJoint ? def.joint1 : null);
+ this.m_groundAnchor1.SetV(this.m_revolute1.m_localAnchor1);
+ this.m_localAnchor1.SetV(this.m_revolute1.m_localAnchor2);
+ coordinate1 = this.m_revolute1.GetJointAngle();
+ }
+ else {
+ this.m_prismatic1 = (def.joint1 instanceof b2PrismaticJoint ? def.joint1 : null);
+ this.m_groundAnchor1.SetV(this.m_prismatic1.m_localAnchor1);
+ this.m_localAnchor1.SetV(this.m_prismatic1.m_localAnchor2);
+ coordinate1 = this.m_prismatic1.GetJointTranslation();
+ }
+ this.m_ground2 = def.joint2.GetBodyA();
+ this.m_bodyB = def.joint2.GetBodyB();
+ if (type2 == b2Joint.e_revoluteJoint) {
+ this.m_revolute2 = (def.joint2 instanceof b2RevoluteJoint ? def.joint2 : null);
+ this.m_groundAnchor2.SetV(this.m_revolute2.m_localAnchor1);
+ this.m_localAnchor2.SetV(this.m_revolute2.m_localAnchor2);
+ coordinate2 = this.m_revolute2.GetJointAngle();
+ }
+ else {
+ this.m_prismatic2 = (def.joint2 instanceof b2PrismaticJoint ? def.joint2 : null);
+ this.m_groundAnchor2.SetV(this.m_prismatic2.m_localAnchor1);
+ this.m_localAnchor2.SetV(this.m_prismatic2.m_localAnchor2);
+ coordinate2 = this.m_prismatic2.GetJointTranslation();
+ }
+ this.m_ratio = def.ratio;
+ this.m_constant = coordinate1 + this.m_ratio * coordinate2;
+ this.m_impulse = 0.0;
+ }
+ b2GearJoint.prototype.InitVelocityConstraints = function (step) {
+ var g1 = this.m_ground1;
+ var g2 = this.m_ground2;
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ var ugX = 0;
+ var ugY = 0;
+ var rX = 0;
+ var rY = 0;
+ var tMat;
+ var tVec;
+ var crug = 0;
+ var tX = 0;
+ var K = 0.0;
+ this.m_J.SetZero();
+ if (this.m_revolute1) {
+ this.m_J.angularA = (-1.0);
+ K += bA.m_invI;
+ }
+ else {
+ tMat = g1.m_xf.R;
+ tVec = this.m_prismatic1.m_localXAxis1;
+ ugX = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y;
+ ugY = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y;
+ tMat = bA.m_xf.R;
+ rX = this.m_localAnchor1.x - bA.m_sweep.localCenter.x;
+ rY = this.m_localAnchor1.y - bA.m_sweep.localCenter.y;
+ tX = tMat.col1.x * rX + tMat.col2.x * rY;
+ rY = tMat.col1.y * rX + tMat.col2.y * rY;
+ rX = tX;
+ crug = rX * ugY - rY * ugX;
+ this.m_J.linearA.Set((-ugX), (-ugY));
+ this.m_J.angularA = (-crug);
+ K += bA.m_invMass + bA.m_invI * crug * crug;
+ }
+ if (this.m_revolute2) {
+ this.m_J.angularB = (-this.m_ratio);
+ K += this.m_ratio * this.m_ratio * bB.m_invI;
+ }
+ else {
+ tMat = g2.m_xf.R;
+ tVec = this.m_prismatic2.m_localXAxis1;
+ ugX = tMat.col1.x * tVec.x + tMat.col2.x * tVec.y;
+ ugY = tMat.col1.y * tVec.x + tMat.col2.y * tVec.y;
+ tMat = bB.m_xf.R;
+ rX = this.m_localAnchor2.x - bB.m_sweep.localCenter.x;
+ rY = this.m_localAnchor2.y - bB.m_sweep.localCenter.y;
+ tX = tMat.col1.x * rX + tMat.col2.x * rY;
+ rY = tMat.col1.y * rX + tMat.col2.y * rY;
+ rX = tX;
+ crug = rX * ugY - rY * ugX;
+ this.m_J.linearB.Set((-this.m_ratio * ugX), (-this.m_ratio * ugY));
+ this.m_J.angularB = (-this.m_ratio * crug);
+ K += this.m_ratio * this.m_ratio * (bB.m_invMass + bB.m_invI * crug * crug);
+ }
+ this.m_mass = K > 0.0 ? 1.0 / K : 0.0;
+ if (step.warmStarting) {
+ bA.m_linearVelocity.x += bA.m_invMass * this.m_impulse * this.m_J.linearA.x;
+ bA.m_linearVelocity.y += bA.m_invMass * this.m_impulse * this.m_J.linearA.y;
+ bA.m_angularVelocity += bA.m_invI * this.m_impulse * this.m_J.angularA;
+ bB.m_linearVelocity.x += bB.m_invMass * this.m_impulse * this.m_J.linearB.x;
+ bB.m_linearVelocity.y += bB.m_invMass * this.m_impulse * this.m_J.linearB.y;
+ bB.m_angularVelocity += bB.m_invI * this.m_impulse * this.m_J.angularB;
+ }
+ else {
+ this.m_impulse = 0.0;
+ }
+ }
+ b2GearJoint.prototype.SolveVelocityConstraints = function (step) {
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ var Cdot = this.m_J.Compute(bA.m_linearVelocity, bA.m_angularVelocity, bB.m_linearVelocity, bB.m_angularVelocity);
+ var impulse = (-this.m_mass * Cdot);
+ this.m_impulse += impulse;
+ bA.m_linearVelocity.x += bA.m_invMass * impulse * this.m_J.linearA.x;
+ bA.m_linearVelocity.y += bA.m_invMass * impulse * this.m_J.linearA.y;
+ bA.m_angularVelocity += bA.m_invI * impulse * this.m_J.angularA;
+ bB.m_linearVelocity.x += bB.m_invMass * impulse * this.m_J.linearB.x;
+ bB.m_linearVelocity.y += bB.m_invMass * impulse * this.m_J.linearB.y;
+ bB.m_angularVelocity += bB.m_invI * impulse * this.m_J.angularB;
+ }
+ b2GearJoint.prototype.SolvePositionConstraints = function (baumgarte) {
+ if (baumgarte === undefined) baumgarte = 0;
+ var linearError = 0.0;
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ var coordinate1 = 0;
+ var coordinate2 = 0;
+ if (this.m_revolute1) {
+ coordinate1 = this.m_revolute1.GetJointAngle();
+ }
+ else {
+ coordinate1 = this.m_prismatic1.GetJointTranslation();
+ }
+ if (this.m_revolute2) {
+ coordinate2 = this.m_revolute2.GetJointAngle();
+ }
+ else {
+ coordinate2 = this.m_prismatic2.GetJointTranslation();
+ }
+ var C = this.m_constant - (coordinate1 + this.m_ratio * coordinate2);
+ var impulse = (-this.m_mass * C);
+ bA.m_sweep.c.x += bA.m_invMass * impulse * this.m_J.linearA.x;
+ bA.m_sweep.c.y += bA.m_invMass * impulse * this.m_J.linearA.y;
+ bA.m_sweep.a += bA.m_invI * impulse * this.m_J.angularA;
+ bB.m_sweep.c.x += bB.m_invMass * impulse * this.m_J.linearB.x;
+ bB.m_sweep.c.y += bB.m_invMass * impulse * this.m_J.linearB.y;
+ bB.m_sweep.a += bB.m_invI * impulse * this.m_J.angularB;
+ bA.SynchronizeTransform();
+ bB.SynchronizeTransform();
+ return linearError < b2Settings.b2_linearSlop;
+ }
+ Box2D.inherit(b2GearJointDef, Box2D.Dynamics.Joints.b2JointDef);
+ b2GearJointDef.prototype.__super = Box2D.Dynamics.Joints.b2JointDef.prototype;
+ b2GearJointDef.b2GearJointDef = function () {
+ Box2D.Dynamics.Joints.b2JointDef.b2JointDef.apply(this, arguments);
+ };
+ b2GearJointDef.prototype.b2GearJointDef = function () {
+ this.__super.b2JointDef.call(this);
+ this.type = b2Joint.e_gearJoint;
+ this.joint1 = null;
+ this.joint2 = null;
+ this.ratio = 1.0;
+ }
+ b2Jacobian.b2Jacobian = function () {
+ this.linearA = new b2Vec2();
+ this.linearB = new b2Vec2();
+ };
+ b2Jacobian.prototype.SetZero = function () {
+ this.linearA.SetZero();
+ this.angularA = 0.0;
+ this.linearB.SetZero();
+ this.angularB = 0.0;
+ }
+ b2Jacobian.prototype.Set = function (x1, a1, x2, a2) {
+ if (a1 === undefined) a1 = 0;
+ if (a2 === undefined) a2 = 0;
+ this.linearA.SetV(x1);
+ this.angularA = a1;
+ this.linearB.SetV(x2);
+ this.angularB = a2;
+ }
+ b2Jacobian.prototype.Compute = function (x1, a1, x2, a2) {
+ if (a1 === undefined) a1 = 0;
+ if (a2 === undefined) a2 = 0;
+ return (this.linearA.x * x1.x + this.linearA.y * x1.y) + this.angularA * a1 + (this.linearB.x * x2.x + this.linearB.y * x2.y) + this.angularB * a2;
+ }
+ b2Joint.b2Joint = function () {
+ this.m_edgeA = new b2JointEdge();
+ this.m_edgeB = new b2JointEdge();
+ this.m_localCenterA = new b2Vec2();
+ this.m_localCenterB = new b2Vec2();
+ };
+ b2Joint.prototype.GetType = function () {
+ return this.m_type;
+ }
+ b2Joint.prototype.GetAnchorA = function () {
+ return null;
+ }
+ b2Joint.prototype.GetAnchorB = function () {
+ return null;
+ }
+ b2Joint.prototype.GetReactionForce = function (inv_dt) {
+ if (inv_dt === undefined) inv_dt = 0;
+ return null;
+ }
+ b2Joint.prototype.GetReactionTorque = function (inv_dt) {
+ if (inv_dt === undefined) inv_dt = 0;
+ return 0.0;
+ }
+ b2Joint.prototype.GetBodyA = function () {
+ return this.m_bodyA;
+ }
+ b2Joint.prototype.GetBodyB = function () {
+ return this.m_bodyB;
+ }
+ b2Joint.prototype.GetNext = function () {
+ return this.m_next;
+ }
+ b2Joint.prototype.GetUserData = function () {
+ return this.m_userData;
+ }
+ b2Joint.prototype.SetUserData = function (data) {
+ this.m_userData = data;
+ }
+ b2Joint.prototype.IsActive = function () {
+ return this.m_bodyA.IsActive() && this.m_bodyB.IsActive();
+ }
+ b2Joint.Create = function (def, allocator) {
+ var joint = null;
+ switch (def.type) {
+ case b2Joint.e_distanceJoint:
+ {
+ joint = new b2DistanceJoint((def instanceof b2DistanceJointDef ? def : null));
+ }
+ break;
+ case b2Joint.e_mouseJoint:
+ {
+ joint = new b2MouseJoint((def instanceof b2MouseJointDef ? def : null));
+ }
+ break;
+ case b2Joint.e_prismaticJoint:
+ {
+ joint = new b2PrismaticJoint((def instanceof b2PrismaticJointDef ? def : null));
+ }
+ break;
+ case b2Joint.e_revoluteJoint:
+ {
+ joint = new b2RevoluteJoint((def instanceof b2RevoluteJointDef ? def : null));
+ }
+ break;
+ case b2Joint.e_pulleyJoint:
+ {
+ joint = new b2PulleyJoint((def instanceof b2PulleyJointDef ? def : null));
+ }
+ break;
+ case b2Joint.e_gearJoint:
+ {
+ joint = new b2GearJoint((def instanceof b2GearJointDef ? def : null));
+ }
+ break;
+ case b2Joint.e_lineJoint:
+ {
+ joint = new b2LineJoint((def instanceof b2LineJointDef ? def : null));
+ }
+ break;
+ case b2Joint.e_weldJoint:
+ {
+ joint = new b2WeldJoint((def instanceof b2WeldJointDef ? def : null));
+ }
+ break;
+ case b2Joint.e_frictionJoint:
+ {
+ joint = new b2FrictionJoint((def instanceof b2FrictionJointDef ? def : null));
+ }
+ break;
+ default:
+ break;
+ }
+ return joint;
+ }
+ b2Joint.Destroy = function (joint, allocator) {}
+ b2Joint.prototype.b2Joint = function (def) {
+ b2Settings.b2Assert(def.bodyA != def.bodyB);
+ this.m_type = def.type;
+ this.m_prev = null;
+ this.m_next = null;
+ this.m_bodyA = def.bodyA;
+ this.m_bodyB = def.bodyB;
+ this.m_collideConnected = def.collideConnected;
+ this.m_islandFlag = false;
+ this.m_userData = def.userData;
+ }
+ b2Joint.prototype.InitVelocityConstraints = function (step) {}
+ b2Joint.prototype.SolveVelocityConstraints = function (step) {}
+ b2Joint.prototype.FinalizeVelocityConstraints = function () {}
+ b2Joint.prototype.SolvePositionConstraints = function (baumgarte) {
+ if (baumgarte === undefined) baumgarte = 0;
+ return false;
+ }
+ Box2D.postDefs.push(function () {
+ Box2D.Dynamics.Joints.b2Joint.e_unknownJoint = 0;
+ Box2D.Dynamics.Joints.b2Joint.e_revoluteJoint = 1;
+ Box2D.Dynamics.Joints.b2Joint.e_prismaticJoint = 2;
+ Box2D.Dynamics.Joints.b2Joint.e_distanceJoint = 3;
+ Box2D.Dynamics.Joints.b2Joint.e_pulleyJoint = 4;
+ Box2D.Dynamics.Joints.b2Joint.e_mouseJoint = 5;
+ Box2D.Dynamics.Joints.b2Joint.e_gearJoint = 6;
+ Box2D.Dynamics.Joints.b2Joint.e_lineJoint = 7;
+ Box2D.Dynamics.Joints.b2Joint.e_weldJoint = 8;
+ Box2D.Dynamics.Joints.b2Joint.e_frictionJoint = 9;
+ Box2D.Dynamics.Joints.b2Joint.e_inactiveLimit = 0;
+ Box2D.Dynamics.Joints.b2Joint.e_atLowerLimit = 1;
+ Box2D.Dynamics.Joints.b2Joint.e_atUpperLimit = 2;
+ Box2D.Dynamics.Joints.b2Joint.e_equalLimits = 3;
+ });
+ b2JointDef.b2JointDef = function () {};
+ b2JointDef.prototype.b2JointDef = function () {
+ this.type = b2Joint.e_unknownJoint;
+ this.userData = null;
+ this.bodyA = null;
+ this.bodyB = null;
+ this.collideConnected = false;
+ }
+ b2JointEdge.b2JointEdge = function () {};
+ Box2D.inherit(b2LineJoint, Box2D.Dynamics.Joints.b2Joint);
+ b2LineJoint.prototype.__super = Box2D.Dynamics.Joints.b2Joint.prototype;
+ b2LineJoint.b2LineJoint = function () {
+ Box2D.Dynamics.Joints.b2Joint.b2Joint.apply(this, arguments);
+ this.m_localAnchor1 = new b2Vec2();
+ this.m_localAnchor2 = new b2Vec2();
+ this.m_localXAxis1 = new b2Vec2();
+ this.m_localYAxis1 = new b2Vec2();
+ this.m_axis = new b2Vec2();
+ this.m_perp = new b2Vec2();
+ this.m_K = new b2Mat22();
+ this.m_impulse = new b2Vec2();
+ };
+ b2LineJoint.prototype.GetAnchorA = function () {
+ return this.m_bodyA.GetWorldPoint(this.m_localAnchor1);
+ }
+ b2LineJoint.prototype.GetAnchorB = function () {
+ return this.m_bodyB.GetWorldPoint(this.m_localAnchor2);
+ }
+ b2LineJoint.prototype.GetReactionForce = function (inv_dt) {
+ if (inv_dt === undefined) inv_dt = 0;
+ return new b2Vec2(inv_dt * (this.m_impulse.x * this.m_perp.x + (this.m_motorImpulse + this.m_impulse.y) * this.m_axis.x), inv_dt * (this.m_impulse.x * this.m_perp.y + (this.m_motorImpulse + this.m_impulse.y) * this.m_axis.y));
+ }
+ b2LineJoint.prototype.GetReactionTorque = function (inv_dt) {
+ if (inv_dt === undefined) inv_dt = 0;
+ return inv_dt * this.m_impulse.y;
+ }
+ b2LineJoint.prototype.GetJointTranslation = function () {
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ var tMat;
+ var p1 = bA.GetWorldPoint(this.m_localAnchor1);
+ var p2 = bB.GetWorldPoint(this.m_localAnchor2);
+ var dX = p2.x - p1.x;
+ var dY = p2.y - p1.y;
+ var axis = bA.GetWorldVector(this.m_localXAxis1);
+ var translation = axis.x * dX + axis.y * dY;
+ return translation;
+ }
+ b2LineJoint.prototype.GetJointSpeed = function () {
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ var tMat;
+ tMat = bA.m_xf.R;
+ var r1X = this.m_localAnchor1.x - bA.m_sweep.localCenter.x;
+ var r1Y = this.m_localAnchor1.y - bA.m_sweep.localCenter.y;
+ var tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y);
+ r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y);
+ r1X = tX;
+ tMat = bB.m_xf.R;
+ var r2X = this.m_localAnchor2.x - bB.m_sweep.localCenter.x;
+ var r2Y = this.m_localAnchor2.y - bB.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y);
+ r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y);
+ r2X = tX;
+ var p1X = bA.m_sweep.c.x + r1X;
+ var p1Y = bA.m_sweep.c.y + r1Y;
+ var p2X = bB.m_sweep.c.x + r2X;
+ var p2Y = bB.m_sweep.c.y + r2Y;
+ var dX = p2X - p1X;
+ var dY = p2Y - p1Y;
+ var axis = bA.GetWorldVector(this.m_localXAxis1);
+ var v1 = bA.m_linearVelocity;
+ var v2 = bB.m_linearVelocity;
+ var w1 = bA.m_angularVelocity;
+ var w2 = bB.m_angularVelocity;
+ var speed = (dX * ((-w1 * axis.y)) + dY * (w1 * axis.x)) + (axis.x * (((v2.x + ((-w2 * r2Y))) - v1.x) - ((-w1 * r1Y))) + axis.y * (((v2.y + (w2 * r2X)) - v1.y) - (w1 * r1X)));
+ return speed;
+ }
+ b2LineJoint.prototype.IsLimitEnabled = function () {
+ return this.m_enableLimit;
+ }
+ b2LineJoint.prototype.EnableLimit = function (flag) {
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_enableLimit = flag;
+ }
+ b2LineJoint.prototype.GetLowerLimit = function () {
+ return this.m_lowerTranslation;
+ }
+ b2LineJoint.prototype.GetUpperLimit = function () {
+ return this.m_upperTranslation;
+ }
+ b2LineJoint.prototype.SetLimits = function (lower, upper) {
+ if (lower === undefined) lower = 0;
+ if (upper === undefined) upper = 0;
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_lowerTranslation = lower;
+ this.m_upperTranslation = upper;
+ }
+ b2LineJoint.prototype.IsMotorEnabled = function () {
+ return this.m_enableMotor;
+ }
+ b2LineJoint.prototype.EnableMotor = function (flag) {
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_enableMotor = flag;
+ }
+ b2LineJoint.prototype.SetMotorSpeed = function (speed) {
+ if (speed === undefined) speed = 0;
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_motorSpeed = speed;
+ }
+ b2LineJoint.prototype.GetMotorSpeed = function () {
+ return this.m_motorSpeed;
+ }
+ b2LineJoint.prototype.SetMaxMotorForce = function (force) {
+ if (force === undefined) force = 0;
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_maxMotorForce = force;
+ }
+ b2LineJoint.prototype.GetMaxMotorForce = function () {
+ return this.m_maxMotorForce;
+ }
+ b2LineJoint.prototype.GetMotorForce = function () {
+ return this.m_motorImpulse;
+ }
+ b2LineJoint.prototype.b2LineJoint = function (def) {
+ this.__super.b2Joint.call(this, def);
+ var tMat;
+ var tX = 0;
+ var tY = 0;
+ this.m_localAnchor1.SetV(def.localAnchorA);
+ this.m_localAnchor2.SetV(def.localAnchorB);
+ this.m_localXAxis1.SetV(def.localAxisA);
+ this.m_localYAxis1.x = (-this.m_localXAxis1.y);
+ this.m_localYAxis1.y = this.m_localXAxis1.x;
+ this.m_impulse.SetZero();
+ this.m_motorMass = 0.0;
+ this.m_motorImpulse = 0.0;
+ this.m_lowerTranslation = def.lowerTranslation;
+ this.m_upperTranslation = def.upperTranslation;
+ this.m_maxMotorForce = def.maxMotorForce;
+ this.m_motorSpeed = def.motorSpeed;
+ this.m_enableLimit = def.enableLimit;
+ this.m_enableMotor = def.enableMotor;
+ this.m_limitState = b2Joint.e_inactiveLimit;
+ this.m_axis.SetZero();
+ this.m_perp.SetZero();
+ }
+ b2LineJoint.prototype.InitVelocityConstraints = function (step) {
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ var tMat;
+ var tX = 0;
+ this.m_localCenterA.SetV(bA.GetLocalCenter());
+ this.m_localCenterB.SetV(bB.GetLocalCenter());
+ var xf1 = bA.GetTransform();
+ var xf2 = bB.GetTransform();
+ tMat = bA.m_xf.R;
+ var r1X = this.m_localAnchor1.x - this.m_localCenterA.x;
+ var r1Y = this.m_localAnchor1.y - this.m_localCenterA.y;
+ tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y);
+ r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y);
+ r1X = tX;
+ tMat = bB.m_xf.R;
+ var r2X = this.m_localAnchor2.x - this.m_localCenterB.x;
+ var r2Y = this.m_localAnchor2.y - this.m_localCenterB.y;
+ tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y);
+ r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y);
+ r2X = tX;
+ var dX = bB.m_sweep.c.x + r2X - bA.m_sweep.c.x - r1X;
+ var dY = bB.m_sweep.c.y + r2Y - bA.m_sweep.c.y - r1Y;
+ this.m_invMassA = bA.m_invMass;
+ this.m_invMassB = bB.m_invMass;
+ this.m_invIA = bA.m_invI;
+ this.m_invIB = bB.m_invI; {
+ this.m_axis.SetV(b2Math.MulMV(xf1.R, this.m_localXAxis1));
+ this.m_a1 = (dX + r1X) * this.m_axis.y - (dY + r1Y) * this.m_axis.x;
+ this.m_a2 = r2X * this.m_axis.y - r2Y * this.m_axis.x;
+ this.m_motorMass = this.m_invMassA + this.m_invMassB + this.m_invIA * this.m_a1 * this.m_a1 + this.m_invIB * this.m_a2 * this.m_a2;
+ this.m_motorMass = this.m_motorMass > Number.MIN_VALUE ? 1.0 / this.m_motorMass : 0.0;
+ } {
+ this.m_perp.SetV(b2Math.MulMV(xf1.R, this.m_localYAxis1));
+ this.m_s1 = (dX + r1X) * this.m_perp.y - (dY + r1Y) * this.m_perp.x;
+ this.m_s2 = r2X * this.m_perp.y - r2Y * this.m_perp.x;
+ var m1 = this.m_invMassA;
+ var m2 = this.m_invMassB;
+ var i1 = this.m_invIA;
+ var i2 = this.m_invIB;
+ this.m_K.col1.x = m1 + m2 + i1 * this.m_s1 * this.m_s1 + i2 * this.m_s2 * this.m_s2;
+ this.m_K.col1.y = i1 * this.m_s1 * this.m_a1 + i2 * this.m_s2 * this.m_a2;
+ this.m_K.col2.x = this.m_K.col1.y;
+ this.m_K.col2.y = m1 + m2 + i1 * this.m_a1 * this.m_a1 + i2 * this.m_a2 * this.m_a2;
+ }
+ if (this.m_enableLimit) {
+ var jointTransition = this.m_axis.x * dX + this.m_axis.y * dY;
+ if (b2Math.Abs(this.m_upperTranslation - this.m_lowerTranslation) < 2.0 * b2Settings.b2_linearSlop) {
+ this.m_limitState = b2Joint.e_equalLimits;
+ }
+ else if (jointTransition <= this.m_lowerTranslation) {
+ if (this.m_limitState != b2Joint.e_atLowerLimit) {
+ this.m_limitState = b2Joint.e_atLowerLimit;
+ this.m_impulse.y = 0.0;
+ }
+ }
+ else if (jointTransition >= this.m_upperTranslation) {
+ if (this.m_limitState != b2Joint.e_atUpperLimit) {
+ this.m_limitState = b2Joint.e_atUpperLimit;
+ this.m_impulse.y = 0.0;
+ }
+ }
+ else {
+ this.m_limitState = b2Joint.e_inactiveLimit;
+ this.m_impulse.y = 0.0;
+ }
+ }
+ else {
+ this.m_limitState = b2Joint.e_inactiveLimit;
+ }
+ if (this.m_enableMotor == false) {
+ this.m_motorImpulse = 0.0;
+ }
+ if (step.warmStarting) {
+ this.m_impulse.x *= step.dtRatio;
+ this.m_impulse.y *= step.dtRatio;
+ this.m_motorImpulse *= step.dtRatio;
+ var PX = this.m_impulse.x * this.m_perp.x + (this.m_motorImpulse + this.m_impulse.y) * this.m_axis.x;
+ var PY = this.m_impulse.x * this.m_perp.y + (this.m_motorImpulse + this.m_impulse.y) * this.m_axis.y;
+ var L1 = this.m_impulse.x * this.m_s1 + (this.m_motorImpulse + this.m_impulse.y) * this.m_a1;
+ var L2 = this.m_impulse.x * this.m_s2 + (this.m_motorImpulse + this.m_impulse.y) * this.m_a2;
+ bA.m_linearVelocity.x -= this.m_invMassA * PX;
+ bA.m_linearVelocity.y -= this.m_invMassA * PY;
+ bA.m_angularVelocity -= this.m_invIA * L1;
+ bB.m_linearVelocity.x += this.m_invMassB * PX;
+ bB.m_linearVelocity.y += this.m_invMassB * PY;
+ bB.m_angularVelocity += this.m_invIB * L2;
+ }
+ else {
+ this.m_impulse.SetZero();
+ this.m_motorImpulse = 0.0;
+ }
+ }
+ b2LineJoint.prototype.SolveVelocityConstraints = function (step) {
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ var v1 = bA.m_linearVelocity;
+ var w1 = bA.m_angularVelocity;
+ var v2 = bB.m_linearVelocity;
+ var w2 = bB.m_angularVelocity;
+ var PX = 0;
+ var PY = 0;
+ var L1 = 0;
+ var L2 = 0;
+ if (this.m_enableMotor && this.m_limitState != b2Joint.e_equalLimits) {
+ var Cdot = this.m_axis.x * (v2.x - v1.x) + this.m_axis.y * (v2.y - v1.y) + this.m_a2 * w2 - this.m_a1 * w1;
+ var impulse = this.m_motorMass * (this.m_motorSpeed - Cdot);
+ var oldImpulse = this.m_motorImpulse;
+ var maxImpulse = step.dt * this.m_maxMotorForce;
+ this.m_motorImpulse = b2Math.Clamp(this.m_motorImpulse + impulse, (-maxImpulse), maxImpulse);
+ impulse = this.m_motorImpulse - oldImpulse;
+ PX = impulse * this.m_axis.x;
+ PY = impulse * this.m_axis.y;
+ L1 = impulse * this.m_a1;
+ L2 = impulse * this.m_a2;
+ v1.x -= this.m_invMassA * PX;
+ v1.y -= this.m_invMassA * PY;
+ w1 -= this.m_invIA * L1;
+ v2.x += this.m_invMassB * PX;
+ v2.y += this.m_invMassB * PY;
+ w2 += this.m_invIB * L2;
+ }
+ var Cdot1 = this.m_perp.x * (v2.x - v1.x) + this.m_perp.y * (v2.y - v1.y) + this.m_s2 * w2 - this.m_s1 * w1;
+ if (this.m_enableLimit && this.m_limitState != b2Joint.e_inactiveLimit) {
+ var Cdot2 = this.m_axis.x * (v2.x - v1.x) + this.m_axis.y * (v2.y - v1.y) + this.m_a2 * w2 - this.m_a1 * w1;
+ var f1 = this.m_impulse.Copy();
+ var df = this.m_K.Solve(new b2Vec2(), (-Cdot1), (-Cdot2));
+ this.m_impulse.Add(df);
+ if (this.m_limitState == b2Joint.e_atLowerLimit) {
+ this.m_impulse.y = b2Math.Max(this.m_impulse.y, 0.0);
+ }
+ else if (this.m_limitState == b2Joint.e_atUpperLimit) {
+ this.m_impulse.y = b2Math.Min(this.m_impulse.y, 0.0);
+ }
+ var b = (-Cdot1) - (this.m_impulse.y - f1.y) * this.m_K.col2.x;
+ var f2r = 0;
+ if (this.m_K.col1.x != 0.0) {
+ f2r = b / this.m_K.col1.x + f1.x;
+ }
+ else {
+ f2r = f1.x;
+ }
+ this.m_impulse.x = f2r;
+ df.x = this.m_impulse.x - f1.x;
+ df.y = this.m_impulse.y - f1.y;
+ PX = df.x * this.m_perp.x + df.y * this.m_axis.x;
+ PY = df.x * this.m_perp.y + df.y * this.m_axis.y;
+ L1 = df.x * this.m_s1 + df.y * this.m_a1;
+ L2 = df.x * this.m_s2 + df.y * this.m_a2;
+ v1.x -= this.m_invMassA * PX;
+ v1.y -= this.m_invMassA * PY;
+ w1 -= this.m_invIA * L1;
+ v2.x += this.m_invMassB * PX;
+ v2.y += this.m_invMassB * PY;
+ w2 += this.m_invIB * L2;
+ }
+ else {
+ var df2 = 0;
+ if (this.m_K.col1.x != 0.0) {
+ df2 = ((-Cdot1)) / this.m_K.col1.x;
+ }
+ else {
+ df2 = 0.0;
+ }
+ this.m_impulse.x += df2;
+ PX = df2 * this.m_perp.x;
+ PY = df2 * this.m_perp.y;
+ L1 = df2 * this.m_s1;
+ L2 = df2 * this.m_s2;
+ v1.x -= this.m_invMassA * PX;
+ v1.y -= this.m_invMassA * PY;
+ w1 -= this.m_invIA * L1;
+ v2.x += this.m_invMassB * PX;
+ v2.y += this.m_invMassB * PY;
+ w2 += this.m_invIB * L2;
+ }
+ bA.m_linearVelocity.SetV(v1);
+ bA.m_angularVelocity = w1;
+ bB.m_linearVelocity.SetV(v2);
+ bB.m_angularVelocity = w2;
+ }
+ b2LineJoint.prototype.SolvePositionConstraints = function (baumgarte) {
+ if (baumgarte === undefined) baumgarte = 0;
+ var limitC = 0;
+ var oldLimitImpulse = 0;
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ var c1 = bA.m_sweep.c;
+ var a1 = bA.m_sweep.a;
+ var c2 = bB.m_sweep.c;
+ var a2 = bB.m_sweep.a;
+ var tMat;
+ var tX = 0;
+ var m1 = 0;
+ var m2 = 0;
+ var i1 = 0;
+ var i2 = 0;
+ var linearError = 0.0;
+ var angularError = 0.0;
+ var active = false;
+ var C2 = 0.0;
+ var R1 = b2Mat22.FromAngle(a1);
+ var R2 = b2Mat22.FromAngle(a2);
+ tMat = R1;
+ var r1X = this.m_localAnchor1.x - this.m_localCenterA.x;
+ var r1Y = this.m_localAnchor1.y - this.m_localCenterA.y;
+ tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y);
+ r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y);
+ r1X = tX;
+ tMat = R2;
+ var r2X = this.m_localAnchor2.x - this.m_localCenterB.x;
+ var r2Y = this.m_localAnchor2.y - this.m_localCenterB.y;
+ tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y);
+ r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y);
+ r2X = tX;
+ var dX = c2.x + r2X - c1.x - r1X;
+ var dY = c2.y + r2Y - c1.y - r1Y;
+ if (this.m_enableLimit) {
+ this.m_axis = b2Math.MulMV(R1, this.m_localXAxis1);
+ this.m_a1 = (dX + r1X) * this.m_axis.y - (dY + r1Y) * this.m_axis.x;
+ this.m_a2 = r2X * this.m_axis.y - r2Y * this.m_axis.x;
+ var translation = this.m_axis.x * dX + this.m_axis.y * dY;
+ if (b2Math.Abs(this.m_upperTranslation - this.m_lowerTranslation) < 2.0 * b2Settings.b2_linearSlop) {
+ C2 = b2Math.Clamp(translation, (-b2Settings.b2_maxLinearCorrection), b2Settings.b2_maxLinearCorrection);
+ linearError = b2Math.Abs(translation);
+ active = true;
+ }
+ else if (translation <= this.m_lowerTranslation) {
+ C2 = b2Math.Clamp(translation - this.m_lowerTranslation + b2Settings.b2_linearSlop, (-b2Settings.b2_maxLinearCorrection), 0.0);
+ linearError = this.m_lowerTranslation - translation;
+ active = true;
+ }
+ else if (translation >= this.m_upperTranslation) {
+ C2 = b2Math.Clamp(translation - this.m_upperTranslation + b2Settings.b2_linearSlop, 0.0, b2Settings.b2_maxLinearCorrection);
+ linearError = translation - this.m_upperTranslation;
+ active = true;
+ }
+ }
+ this.m_perp = b2Math.MulMV(R1, this.m_localYAxis1);
+ this.m_s1 = (dX + r1X) * this.m_perp.y - (dY + r1Y) * this.m_perp.x;
+ this.m_s2 = r2X * this.m_perp.y - r2Y * this.m_perp.x;
+ var impulse = new b2Vec2();
+ var C1 = this.m_perp.x * dX + this.m_perp.y * dY;
+ linearError = b2Math.Max(linearError, b2Math.Abs(C1));
+ angularError = 0.0;
+ if (active) {
+ m1 = this.m_invMassA;
+ m2 = this.m_invMassB;
+ i1 = this.m_invIA;
+ i2 = this.m_invIB;
+ this.m_K.col1.x = m1 + m2 + i1 * this.m_s1 * this.m_s1 + i2 * this.m_s2 * this.m_s2;
+ this.m_K.col1.y = i1 * this.m_s1 * this.m_a1 + i2 * this.m_s2 * this.m_a2;
+ this.m_K.col2.x = this.m_K.col1.y;
+ this.m_K.col2.y = m1 + m2 + i1 * this.m_a1 * this.m_a1 + i2 * this.m_a2 * this.m_a2;
+ this.m_K.Solve(impulse, (-C1), (-C2));
+ }
+ else {
+ m1 = this.m_invMassA;
+ m2 = this.m_invMassB;
+ i1 = this.m_invIA;
+ i2 = this.m_invIB;
+ var k11 = m1 + m2 + i1 * this.m_s1 * this.m_s1 + i2 * this.m_s2 * this.m_s2;
+ var impulse1 = 0;
+ if (k11 != 0.0) {
+ impulse1 = ((-C1)) / k11;
+ }
+ else {
+ impulse1 = 0.0;
+ }
+ impulse.x = impulse1;
+ impulse.y = 0.0;
+ }
+ var PX = impulse.x * this.m_perp.x + impulse.y * this.m_axis.x;
+ var PY = impulse.x * this.m_perp.y + impulse.y * this.m_axis.y;
+ var L1 = impulse.x * this.m_s1 + impulse.y * this.m_a1;
+ var L2 = impulse.x * this.m_s2 + impulse.y * this.m_a2;
+ c1.x -= this.m_invMassA * PX;
+ c1.y -= this.m_invMassA * PY;
+ a1 -= this.m_invIA * L1;
+ c2.x += this.m_invMassB * PX;
+ c2.y += this.m_invMassB * PY;
+ a2 += this.m_invIB * L2;
+ bA.m_sweep.a = a1;
+ bB.m_sweep.a = a2;
+ bA.SynchronizeTransform();
+ bB.SynchronizeTransform();
+ return linearError <= b2Settings.b2_linearSlop && angularError <= b2Settings.b2_angularSlop;
+ }
+ Box2D.inherit(b2LineJointDef, Box2D.Dynamics.Joints.b2JointDef);
+ b2LineJointDef.prototype.__super = Box2D.Dynamics.Joints.b2JointDef.prototype;
+ b2LineJointDef.b2LineJointDef = function () {
+ Box2D.Dynamics.Joints.b2JointDef.b2JointDef.apply(this, arguments);
+ this.localAnchorA = new b2Vec2();
+ this.localAnchorB = new b2Vec2();
+ this.localAxisA = new b2Vec2();
+ };
+ b2LineJointDef.prototype.b2LineJointDef = function () {
+ this.__super.b2JointDef.call(this);
+ this.type = b2Joint.e_lineJoint;
+ this.localAxisA.Set(1.0, 0.0);
+ this.enableLimit = false;
+ this.lowerTranslation = 0.0;
+ this.upperTranslation = 0.0;
+ this.enableMotor = false;
+ this.maxMotorForce = 0.0;
+ this.motorSpeed = 0.0;
+ }
+ b2LineJointDef.prototype.Initialize = function (bA, bB, anchor, axis) {
+ this.bodyA = bA;
+ this.bodyB = bB;
+ this.localAnchorA = this.bodyA.GetLocalPoint(anchor);
+ this.localAnchorB = this.bodyB.GetLocalPoint(anchor);
+ this.localAxisA = this.bodyA.GetLocalVector(axis);
+ }
+ Box2D.inherit(b2MouseJoint, Box2D.Dynamics.Joints.b2Joint);
+ b2MouseJoint.prototype.__super = Box2D.Dynamics.Joints.b2Joint.prototype;
+ b2MouseJoint.b2MouseJoint = function () {
+ Box2D.Dynamics.Joints.b2Joint.b2Joint.apply(this, arguments);
+ this.K = new b2Mat22();
+ this.K1 = new b2Mat22();
+ this.K2 = new b2Mat22();
+ this.m_localAnchor = new b2Vec2();
+ this.m_target = new b2Vec2();
+ this.m_impulse = new b2Vec2();
+ this.m_mass = new b2Mat22();
+ this.m_C = new b2Vec2();
+ };
+ b2MouseJoint.prototype.GetAnchorA = function () {
+ return this.m_target;
+ }
+ b2MouseJoint.prototype.GetAnchorB = function () {
+ return this.m_bodyB.GetWorldPoint(this.m_localAnchor);
+ }
+ b2MouseJoint.prototype.GetReactionForce = function (inv_dt) {
+ if (inv_dt === undefined) inv_dt = 0;
+ return new b2Vec2(inv_dt * this.m_impulse.x, inv_dt * this.m_impulse.y);
+ }
+ b2MouseJoint.prototype.GetReactionTorque = function (inv_dt) {
+ if (inv_dt === undefined) inv_dt = 0;
+ return 0.0;
+ }
+ b2MouseJoint.prototype.GetTarget = function () {
+ return this.m_target;
+ }
+ b2MouseJoint.prototype.SetTarget = function (target) {
+ if (this.m_bodyB.IsAwake() == false) {
+ this.m_bodyB.SetAwake(true);
+ }
+ this.m_target = target;
+ }
+ b2MouseJoint.prototype.GetMaxForce = function () {
+ return this.m_maxForce;
+ }
+ b2MouseJoint.prototype.SetMaxForce = function (maxForce) {
+ if (maxForce === undefined) maxForce = 0;
+ this.m_maxForce = maxForce;
+ }
+ b2MouseJoint.prototype.GetFrequency = function () {
+ return this.m_frequencyHz;
+ }
+ b2MouseJoint.prototype.SetFrequency = function (hz) {
+ if (hz === undefined) hz = 0;
+ this.m_frequencyHz = hz;
+ }
+ b2MouseJoint.prototype.GetDampingRatio = function () {
+ return this.m_dampingRatio;
+ }
+ b2MouseJoint.prototype.SetDampingRatio = function (ratio) {
+ if (ratio === undefined) ratio = 0;
+ this.m_dampingRatio = ratio;
+ }
+ b2MouseJoint.prototype.b2MouseJoint = function (def) {
+ this.__super.b2Joint.call(this, def);
+ this.m_target.SetV(def.target);
+ var tX = this.m_target.x - this.m_bodyB.m_xf.position.x;
+ var tY = this.m_target.y - this.m_bodyB.m_xf.position.y;
+ var tMat = this.m_bodyB.m_xf.R;
+ this.m_localAnchor.x = (tX * tMat.col1.x + tY * tMat.col1.y);
+ this.m_localAnchor.y = (tX * tMat.col2.x + tY * tMat.col2.y);
+ this.m_maxForce = def.maxForce;
+ this.m_impulse.SetZero();
+ this.m_frequencyHz = def.frequencyHz;
+ this.m_dampingRatio = def.dampingRatio;
+ this.m_beta = 0.0;
+ this.m_gamma = 0.0;
+ }
+ b2MouseJoint.prototype.InitVelocityConstraints = function (step) {
+ var b = this.m_bodyB;
+ var mass = b.GetMass();
+ var omega = 2.0 * Math.PI * this.m_frequencyHz;
+ var d = 2.0 * mass * this.m_dampingRatio * omega;
+ var k = mass * omega * omega;
+ this.m_gamma = step.dt * (d + step.dt * k);
+ this.m_gamma = this.m_gamma != 0 ? 1 / this.m_gamma : 0.0;
+ this.m_beta = step.dt * k * this.m_gamma;
+ var tMat;tMat = b.m_xf.R;
+ var rX = this.m_localAnchor.x - b.m_sweep.localCenter.x;
+ var rY = this.m_localAnchor.y - b.m_sweep.localCenter.y;
+ var tX = (tMat.col1.x * rX + tMat.col2.x * rY);rY = (tMat.col1.y * rX + tMat.col2.y * rY);
+ rX = tX;
+ var invMass = b.m_invMass;
+ var invI = b.m_invI;this.K1.col1.x = invMass;
+ this.K1.col2.x = 0.0;
+ this.K1.col1.y = 0.0;
+ this.K1.col2.y = invMass;
+ this.K2.col1.x = invI * rY * rY;
+ this.K2.col2.x = (-invI * rX * rY);
+ this.K2.col1.y = (-invI * rX * rY);
+ this.K2.col2.y = invI * rX * rX;
+ this.K.SetM(this.K1);
+ this.K.AddM(this.K2);
+ this.K.col1.x += this.m_gamma;
+ this.K.col2.y += this.m_gamma;
+ this.K.GetInverse(this.m_mass);
+ this.m_C.x = b.m_sweep.c.x + rX - this.m_target.x;
+ this.m_C.y = b.m_sweep.c.y + rY - this.m_target.y;
+ b.m_angularVelocity *= 0.98;
+ this.m_impulse.x *= step.dtRatio;
+ this.m_impulse.y *= step.dtRatio;
+ b.m_linearVelocity.x += invMass * this.m_impulse.x;
+ b.m_linearVelocity.y += invMass * this.m_impulse.y;
+ b.m_angularVelocity += invI * (rX * this.m_impulse.y - rY * this.m_impulse.x);
+ }
+ b2MouseJoint.prototype.SolveVelocityConstraints = function (step) {
+ var b = this.m_bodyB;
+ var tMat;
+ var tX = 0;
+ var tY = 0;
+ tMat = b.m_xf.R;
+ var rX = this.m_localAnchor.x - b.m_sweep.localCenter.x;
+ var rY = this.m_localAnchor.y - b.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * rX + tMat.col2.x * rY);
+ rY = (tMat.col1.y * rX + tMat.col2.y * rY);
+ rX = tX;
+ var CdotX = b.m_linearVelocity.x + ((-b.m_angularVelocity * rY));
+ var CdotY = b.m_linearVelocity.y + (b.m_angularVelocity * rX);
+ tMat = this.m_mass;
+ tX = CdotX + this.m_beta * this.m_C.x + this.m_gamma * this.m_impulse.x;
+ tY = CdotY + this.m_beta * this.m_C.y + this.m_gamma * this.m_impulse.y;
+ var impulseX = (-(tMat.col1.x * tX + tMat.col2.x * tY));
+ var impulseY = (-(tMat.col1.y * tX + tMat.col2.y * tY));
+ var oldImpulseX = this.m_impulse.x;
+ var oldImpulseY = this.m_impulse.y;
+ this.m_impulse.x += impulseX;
+ this.m_impulse.y += impulseY;
+ var maxImpulse = step.dt * this.m_maxForce;
+ if (this.m_impulse.LengthSquared() > maxImpulse * maxImpulse) {
+ this.m_impulse.Multiply(maxImpulse / this.m_impulse.Length());
+ }
+ impulseX = this.m_impulse.x - oldImpulseX;
+ impulseY = this.m_impulse.y - oldImpulseY;
+ b.m_linearVelocity.x += b.m_invMass * impulseX;
+ b.m_linearVelocity.y += b.m_invMass * impulseY;
+ b.m_angularVelocity += b.m_invI * (rX * impulseY - rY * impulseX);
+ }
+ b2MouseJoint.prototype.SolvePositionConstraints = function (baumgarte) {
+ if (baumgarte === undefined) baumgarte = 0;
+ return true;
+ }
+ Box2D.inherit(b2MouseJointDef, Box2D.Dynamics.Joints.b2JointDef);
+ b2MouseJointDef.prototype.__super = Box2D.Dynamics.Joints.b2JointDef.prototype;
+ b2MouseJointDef.b2MouseJointDef = function () {
+ Box2D.Dynamics.Joints.b2JointDef.b2JointDef.apply(this, arguments);
+ this.target = new b2Vec2();
+ };
+ b2MouseJointDef.prototype.b2MouseJointDef = function () {
+ this.__super.b2JointDef.call(this);
+ this.type = b2Joint.e_mouseJoint;
+ this.maxForce = 0.0;
+ this.frequencyHz = 5.0;
+ this.dampingRatio = 0.7;
+ }
+ Box2D.inherit(b2PrismaticJoint, Box2D.Dynamics.Joints.b2Joint);
+ b2PrismaticJoint.prototype.__super = Box2D.Dynamics.Joints.b2Joint.prototype;
+ b2PrismaticJoint.b2PrismaticJoint = function () {
+ Box2D.Dynamics.Joints.b2Joint.b2Joint.apply(this, arguments);
+ this.m_localAnchor1 = new b2Vec2();
+ this.m_localAnchor2 = new b2Vec2();
+ this.m_localXAxis1 = new b2Vec2();
+ this.m_localYAxis1 = new b2Vec2();
+ this.m_axis = new b2Vec2();
+ this.m_perp = new b2Vec2();
+ this.m_K = new b2Mat33();
+ this.m_impulse = new b2Vec3();
+ };
+ b2PrismaticJoint.prototype.GetAnchorA = function () {
+ return this.m_bodyA.GetWorldPoint(this.m_localAnchor1);
+ }
+ b2PrismaticJoint.prototype.GetAnchorB = function () {
+ return this.m_bodyB.GetWorldPoint(this.m_localAnchor2);
+ }
+ b2PrismaticJoint.prototype.GetReactionForce = function (inv_dt) {
+ if (inv_dt === undefined) inv_dt = 0;
+ return new b2Vec2(inv_dt * (this.m_impulse.x * this.m_perp.x + (this.m_motorImpulse + this.m_impulse.z) * this.m_axis.x), inv_dt * (this.m_impulse.x * this.m_perp.y + (this.m_motorImpulse + this.m_impulse.z) * this.m_axis.y));
+ }
+ b2PrismaticJoint.prototype.GetReactionTorque = function (inv_dt) {
+ if (inv_dt === undefined) inv_dt = 0;
+ return inv_dt * this.m_impulse.y;
+ }
+ b2PrismaticJoint.prototype.GetJointTranslation = function () {
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ var tMat;
+ var p1 = bA.GetWorldPoint(this.m_localAnchor1);
+ var p2 = bB.GetWorldPoint(this.m_localAnchor2);
+ var dX = p2.x - p1.x;
+ var dY = p2.y - p1.y;
+ var axis = bA.GetWorldVector(this.m_localXAxis1);
+ var translation = axis.x * dX + axis.y * dY;
+ return translation;
+ }
+ b2PrismaticJoint.prototype.GetJointSpeed = function () {
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ var tMat;
+ tMat = bA.m_xf.R;
+ var r1X = this.m_localAnchor1.x - bA.m_sweep.localCenter.x;
+ var r1Y = this.m_localAnchor1.y - bA.m_sweep.localCenter.y;
+ var tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y);
+ r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y);
+ r1X = tX;
+ tMat = bB.m_xf.R;
+ var r2X = this.m_localAnchor2.x - bB.m_sweep.localCenter.x;
+ var r2Y = this.m_localAnchor2.y - bB.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y);
+ r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y);
+ r2X = tX;
+ var p1X = bA.m_sweep.c.x + r1X;
+ var p1Y = bA.m_sweep.c.y + r1Y;
+ var p2X = bB.m_sweep.c.x + r2X;
+ var p2Y = bB.m_sweep.c.y + r2Y;
+ var dX = p2X - p1X;
+ var dY = p2Y - p1Y;
+ var axis = bA.GetWorldVector(this.m_localXAxis1);
+ var v1 = bA.m_linearVelocity;
+ var v2 = bB.m_linearVelocity;
+ var w1 = bA.m_angularVelocity;
+ var w2 = bB.m_angularVelocity;
+ var speed = (dX * ((-w1 * axis.y)) + dY * (w1 * axis.x)) + (axis.x * (((v2.x + ((-w2 * r2Y))) - v1.x) - ((-w1 * r1Y))) + axis.y * (((v2.y + (w2 * r2X)) - v1.y) - (w1 * r1X)));
+ return speed;
+ }
+ b2PrismaticJoint.prototype.IsLimitEnabled = function () {
+ return this.m_enableLimit;
+ }
+ b2PrismaticJoint.prototype.EnableLimit = function (flag) {
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_enableLimit = flag;
+ }
+ b2PrismaticJoint.prototype.GetLowerLimit = function () {
+ return this.m_lowerTranslation;
+ }
+ b2PrismaticJoint.prototype.GetUpperLimit = function () {
+ return this.m_upperTranslation;
+ }
+ b2PrismaticJoint.prototype.SetLimits = function (lower, upper) {
+ if (lower === undefined) lower = 0;
+ if (upper === undefined) upper = 0;
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_lowerTranslation = lower;
+ this.m_upperTranslation = upper;
+ }
+ b2PrismaticJoint.prototype.IsMotorEnabled = function () {
+ return this.m_enableMotor;
+ }
+ b2PrismaticJoint.prototype.EnableMotor = function (flag) {
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_enableMotor = flag;
+ }
+ b2PrismaticJoint.prototype.SetMotorSpeed = function (speed) {
+ if (speed === undefined) speed = 0;
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_motorSpeed = speed;
+ }
+ b2PrismaticJoint.prototype.GetMotorSpeed = function () {
+ return this.m_motorSpeed;
+ }
+ b2PrismaticJoint.prototype.SetMaxMotorForce = function (force) {
+ if (force === undefined) force = 0;
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_maxMotorForce = force;
+ }
+ b2PrismaticJoint.prototype.GetMotorForce = function () {
+ return this.m_motorImpulse;
+ }
+ b2PrismaticJoint.prototype.b2PrismaticJoint = function (def) {
+ this.__super.b2Joint.call(this, def);
+ var tMat;
+ var tX = 0;
+ var tY = 0;
+ this.m_localAnchor1.SetV(def.localAnchorA);
+ this.m_localAnchor2.SetV(def.localAnchorB);
+ this.m_localXAxis1.SetV(def.localAxisA);
+ this.m_localYAxis1.x = (-this.m_localXAxis1.y);
+ this.m_localYAxis1.y = this.m_localXAxis1.x;
+ this.m_refAngle = def.referenceAngle;
+ this.m_impulse.SetZero();
+ this.m_motorMass = 0.0;
+ this.m_motorImpulse = 0.0;
+ this.m_lowerTranslation = def.lowerTranslation;
+ this.m_upperTranslation = def.upperTranslation;
+ this.m_maxMotorForce = def.maxMotorForce;
+ this.m_motorSpeed = def.motorSpeed;
+ this.m_enableLimit = def.enableLimit;
+ this.m_enableMotor = def.enableMotor;
+ this.m_limitState = b2Joint.e_inactiveLimit;
+ this.m_axis.SetZero();
+ this.m_perp.SetZero();
+ }
+ b2PrismaticJoint.prototype.InitVelocityConstraints = function (step) {
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ var tMat;
+ var tX = 0;
+ this.m_localCenterA.SetV(bA.GetLocalCenter());
+ this.m_localCenterB.SetV(bB.GetLocalCenter());
+ var xf1 = bA.GetTransform();
+ var xf2 = bB.GetTransform();
+ tMat = bA.m_xf.R;
+ var r1X = this.m_localAnchor1.x - this.m_localCenterA.x;
+ var r1Y = this.m_localAnchor1.y - this.m_localCenterA.y;
+ tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y);
+ r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y);
+ r1X = tX;
+ tMat = bB.m_xf.R;
+ var r2X = this.m_localAnchor2.x - this.m_localCenterB.x;
+ var r2Y = this.m_localAnchor2.y - this.m_localCenterB.y;
+ tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y);
+ r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y);
+ r2X = tX;
+ var dX = bB.m_sweep.c.x + r2X - bA.m_sweep.c.x - r1X;
+ var dY = bB.m_sweep.c.y + r2Y - bA.m_sweep.c.y - r1Y;
+ this.m_invMassA = bA.m_invMass;
+ this.m_invMassB = bB.m_invMass;
+ this.m_invIA = bA.m_invI;
+ this.m_invIB = bB.m_invI; {
+ this.m_axis.SetV(b2Math.MulMV(xf1.R, this.m_localXAxis1));
+ this.m_a1 = (dX + r1X) * this.m_axis.y - (dY + r1Y) * this.m_axis.x;
+ this.m_a2 = r2X * this.m_axis.y - r2Y * this.m_axis.x;
+ this.m_motorMass = this.m_invMassA + this.m_invMassB + this.m_invIA * this.m_a1 * this.m_a1 + this.m_invIB * this.m_a2 * this.m_a2;
+ if (this.m_motorMass > Number.MIN_VALUE) this.m_motorMass = 1.0 / this.m_motorMass;
+ } {
+ this.m_perp.SetV(b2Math.MulMV(xf1.R, this.m_localYAxis1));
+ this.m_s1 = (dX + r1X) * this.m_perp.y - (dY + r1Y) * this.m_perp.x;
+ this.m_s2 = r2X * this.m_perp.y - r2Y * this.m_perp.x;
+ var m1 = this.m_invMassA;
+ var m2 = this.m_invMassB;
+ var i1 = this.m_invIA;
+ var i2 = this.m_invIB;
+ this.m_K.col1.x = m1 + m2 + i1 * this.m_s1 * this.m_s1 + i2 * this.m_s2 * this.m_s2;
+ this.m_K.col1.y = i1 * this.m_s1 + i2 * this.m_s2;
+ this.m_K.col1.z = i1 * this.m_s1 * this.m_a1 + i2 * this.m_s2 * this.m_a2;
+ this.m_K.col2.x = this.m_K.col1.y;
+ this.m_K.col2.y = i1 + i2;
+ this.m_K.col2.z = i1 * this.m_a1 + i2 * this.m_a2;
+ this.m_K.col3.x = this.m_K.col1.z;
+ this.m_K.col3.y = this.m_K.col2.z;
+ this.m_K.col3.z = m1 + m2 + i1 * this.m_a1 * this.m_a1 + i2 * this.m_a2 * this.m_a2;
+ }
+ if (this.m_enableLimit) {
+ var jointTransition = this.m_axis.x * dX + this.m_axis.y * dY;
+ if (b2Math.Abs(this.m_upperTranslation - this.m_lowerTranslation) < 2.0 * b2Settings.b2_linearSlop) {
+ this.m_limitState = b2Joint.e_equalLimits;
+ }
+ else if (jointTransition <= this.m_lowerTranslation) {
+ if (this.m_limitState != b2Joint.e_atLowerLimit) {
+ this.m_limitState = b2Joint.e_atLowerLimit;
+ this.m_impulse.z = 0.0;
+ }
+ }
+ else if (jointTransition >= this.m_upperTranslation) {
+ if (this.m_limitState != b2Joint.e_atUpperLimit) {
+ this.m_limitState = b2Joint.e_atUpperLimit;
+ this.m_impulse.z = 0.0;
+ }
+ }
+ else {
+ this.m_limitState = b2Joint.e_inactiveLimit;
+ this.m_impulse.z = 0.0;
+ }
+ }
+ else {
+ this.m_limitState = b2Joint.e_inactiveLimit;
+ }
+ if (this.m_enableMotor == false) {
+ this.m_motorImpulse = 0.0;
+ }
+ if (step.warmStarting) {
+ this.m_impulse.x *= step.dtRatio;
+ this.m_impulse.y *= step.dtRatio;
+ this.m_motorImpulse *= step.dtRatio;
+ var PX = this.m_impulse.x * this.m_perp.x + (this.m_motorImpulse + this.m_impulse.z) * this.m_axis.x;
+ var PY = this.m_impulse.x * this.m_perp.y + (this.m_motorImpulse + this.m_impulse.z) * this.m_axis.y;
+ var L1 = this.m_impulse.x * this.m_s1 + this.m_impulse.y + (this.m_motorImpulse + this.m_impulse.z) * this.m_a1;
+ var L2 = this.m_impulse.x * this.m_s2 + this.m_impulse.y + (this.m_motorImpulse + this.m_impulse.z) * this.m_a2;
+ bA.m_linearVelocity.x -= this.m_invMassA * PX;
+ bA.m_linearVelocity.y -= this.m_invMassA * PY;
+ bA.m_angularVelocity -= this.m_invIA * L1;
+ bB.m_linearVelocity.x += this.m_invMassB * PX;
+ bB.m_linearVelocity.y += this.m_invMassB * PY;
+ bB.m_angularVelocity += this.m_invIB * L2;
+ }
+ else {
+ this.m_impulse.SetZero();
+ this.m_motorImpulse = 0.0;
+ }
+ }
+ b2PrismaticJoint.prototype.SolveVelocityConstraints = function (step) {
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ var v1 = bA.m_linearVelocity;
+ var w1 = bA.m_angularVelocity;
+ var v2 = bB.m_linearVelocity;
+ var w2 = bB.m_angularVelocity;
+ var PX = 0;
+ var PY = 0;
+ var L1 = 0;
+ var L2 = 0;
+ if (this.m_enableMotor && this.m_limitState != b2Joint.e_equalLimits) {
+ var Cdot = this.m_axis.x * (v2.x - v1.x) + this.m_axis.y * (v2.y - v1.y) + this.m_a2 * w2 - this.m_a1 * w1;
+ var impulse = this.m_motorMass * (this.m_motorSpeed - Cdot);
+ var oldImpulse = this.m_motorImpulse;
+ var maxImpulse = step.dt * this.m_maxMotorForce;
+ this.m_motorImpulse = b2Math.Clamp(this.m_motorImpulse + impulse, (-maxImpulse), maxImpulse);
+ impulse = this.m_motorImpulse - oldImpulse;
+ PX = impulse * this.m_axis.x;
+ PY = impulse * this.m_axis.y;
+ L1 = impulse * this.m_a1;
+ L2 = impulse * this.m_a2;
+ v1.x -= this.m_invMassA * PX;
+ v1.y -= this.m_invMassA * PY;
+ w1 -= this.m_invIA * L1;
+ v2.x += this.m_invMassB * PX;
+ v2.y += this.m_invMassB * PY;
+ w2 += this.m_invIB * L2;
+ }
+ var Cdot1X = this.m_perp.x * (v2.x - v1.x) + this.m_perp.y * (v2.y - v1.y) + this.m_s2 * w2 - this.m_s1 * w1;
+ var Cdot1Y = w2 - w1;
+ if (this.m_enableLimit && this.m_limitState != b2Joint.e_inactiveLimit) {
+ var Cdot2 = this.m_axis.x * (v2.x - v1.x) + this.m_axis.y * (v2.y - v1.y) + this.m_a2 * w2 - this.m_a1 * w1;
+ var f1 = this.m_impulse.Copy();
+ var df = this.m_K.Solve33(new b2Vec3(), (-Cdot1X), (-Cdot1Y), (-Cdot2));
+ this.m_impulse.Add(df);
+ if (this.m_limitState == b2Joint.e_atLowerLimit) {
+ this.m_impulse.z = b2Math.Max(this.m_impulse.z, 0.0);
+ }
+ else if (this.m_limitState == b2Joint.e_atUpperLimit) {
+ this.m_impulse.z = b2Math.Min(this.m_impulse.z, 0.0);
+ }
+ var bX = (-Cdot1X) - (this.m_impulse.z - f1.z) * this.m_K.col3.x;
+ var bY = (-Cdot1Y) - (this.m_impulse.z - f1.z) * this.m_K.col3.y;
+ var f2r = this.m_K.Solve22(new b2Vec2(), bX, bY);
+ f2r.x += f1.x;
+ f2r.y += f1.y;
+ this.m_impulse.x = f2r.x;
+ this.m_impulse.y = f2r.y;
+ df.x = this.m_impulse.x - f1.x;
+ df.y = this.m_impulse.y - f1.y;
+ df.z = this.m_impulse.z - f1.z;
+ PX = df.x * this.m_perp.x + df.z * this.m_axis.x;
+ PY = df.x * this.m_perp.y + df.z * this.m_axis.y;
+ L1 = df.x * this.m_s1 + df.y + df.z * this.m_a1;
+ L2 = df.x * this.m_s2 + df.y + df.z * this.m_a2;
+ v1.x -= this.m_invMassA * PX;
+ v1.y -= this.m_invMassA * PY;
+ w1 -= this.m_invIA * L1;
+ v2.x += this.m_invMassB * PX;
+ v2.y += this.m_invMassB * PY;
+ w2 += this.m_invIB * L2;
+ }
+ else {
+ var df2 = this.m_K.Solve22(new b2Vec2(), (-Cdot1X), (-Cdot1Y));
+ this.m_impulse.x += df2.x;
+ this.m_impulse.y += df2.y;
+ PX = df2.x * this.m_perp.x;
+ PY = df2.x * this.m_perp.y;
+ L1 = df2.x * this.m_s1 + df2.y;
+ L2 = df2.x * this.m_s2 + df2.y;
+ v1.x -= this.m_invMassA * PX;
+ v1.y -= this.m_invMassA * PY;
+ w1 -= this.m_invIA * L1;
+ v2.x += this.m_invMassB * PX;
+ v2.y += this.m_invMassB * PY;
+ w2 += this.m_invIB * L2;
+ }
+ bA.m_linearVelocity.SetV(v1);
+ bA.m_angularVelocity = w1;
+ bB.m_linearVelocity.SetV(v2);
+ bB.m_angularVelocity = w2;
+ }
+ b2PrismaticJoint.prototype.SolvePositionConstraints = function (baumgarte) {
+ if (baumgarte === undefined) baumgarte = 0;
+ var limitC = 0;
+ var oldLimitImpulse = 0;
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ var c1 = bA.m_sweep.c;
+ var a1 = bA.m_sweep.a;
+ var c2 = bB.m_sweep.c;
+ var a2 = bB.m_sweep.a;
+ var tMat;
+ var tX = 0;
+ var m1 = 0;
+ var m2 = 0;
+ var i1 = 0;
+ var i2 = 0;
+ var linearError = 0.0;
+ var angularError = 0.0;
+ var active = false;
+ var C2 = 0.0;
+ var R1 = b2Mat22.FromAngle(a1);
+ var R2 = b2Mat22.FromAngle(a2);
+ tMat = R1;
+ var r1X = this.m_localAnchor1.x - this.m_localCenterA.x;
+ var r1Y = this.m_localAnchor1.y - this.m_localCenterA.y;
+ tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y);
+ r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y);
+ r1X = tX;
+ tMat = R2;
+ var r2X = this.m_localAnchor2.x - this.m_localCenterB.x;
+ var r2Y = this.m_localAnchor2.y - this.m_localCenterB.y;
+ tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y);
+ r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y);
+ r2X = tX;
+ var dX = c2.x + r2X - c1.x - r1X;
+ var dY = c2.y + r2Y - c1.y - r1Y;
+ if (this.m_enableLimit) {
+ this.m_axis = b2Math.MulMV(R1, this.m_localXAxis1);
+ this.m_a1 = (dX + r1X) * this.m_axis.y - (dY + r1Y) * this.m_axis.x;
+ this.m_a2 = r2X * this.m_axis.y - r2Y * this.m_axis.x;
+ var translation = this.m_axis.x * dX + this.m_axis.y * dY;
+ if (b2Math.Abs(this.m_upperTranslation - this.m_lowerTranslation) < 2.0 * b2Settings.b2_linearSlop) {
+ C2 = b2Math.Clamp(translation, (-b2Settings.b2_maxLinearCorrection), b2Settings.b2_maxLinearCorrection);
+ linearError = b2Math.Abs(translation);
+ active = true;
+ }
+ else if (translation <= this.m_lowerTranslation) {
+ C2 = b2Math.Clamp(translation - this.m_lowerTranslation + b2Settings.b2_linearSlop, (-b2Settings.b2_maxLinearCorrection), 0.0);
+ linearError = this.m_lowerTranslation - translation;
+ active = true;
+ }
+ else if (translation >= this.m_upperTranslation) {
+ C2 = b2Math.Clamp(translation - this.m_upperTranslation + b2Settings.b2_linearSlop, 0.0, b2Settings.b2_maxLinearCorrection);
+ linearError = translation - this.m_upperTranslation;
+ active = true;
+ }
+ }
+ this.m_perp = b2Math.MulMV(R1, this.m_localYAxis1);
+ this.m_s1 = (dX + r1X) * this.m_perp.y - (dY + r1Y) * this.m_perp.x;
+ this.m_s2 = r2X * this.m_perp.y - r2Y * this.m_perp.x;
+ var impulse = new b2Vec3();
+ var C1X = this.m_perp.x * dX + this.m_perp.y * dY;
+ var C1Y = a2 - a1 - this.m_refAngle;
+ linearError = b2Math.Max(linearError, b2Math.Abs(C1X));
+ angularError = b2Math.Abs(C1Y);
+ if (active) {
+ m1 = this.m_invMassA;
+ m2 = this.m_invMassB;
+ i1 = this.m_invIA;
+ i2 = this.m_invIB;
+ this.m_K.col1.x = m1 + m2 + i1 * this.m_s1 * this.m_s1 + i2 * this.m_s2 * this.m_s2;
+ this.m_K.col1.y = i1 * this.m_s1 + i2 * this.m_s2;
+ this.m_K.col1.z = i1 * this.m_s1 * this.m_a1 + i2 * this.m_s2 * this.m_a2;
+ this.m_K.col2.x = this.m_K.col1.y;
+ this.m_K.col2.y = i1 + i2;
+ this.m_K.col2.z = i1 * this.m_a1 + i2 * this.m_a2;
+ this.m_K.col3.x = this.m_K.col1.z;
+ this.m_K.col3.y = this.m_K.col2.z;
+ this.m_K.col3.z = m1 + m2 + i1 * this.m_a1 * this.m_a1 + i2 * this.m_a2 * this.m_a2;
+ this.m_K.Solve33(impulse, (-C1X), (-C1Y), (-C2));
+ }
+ else {
+ m1 = this.m_invMassA;
+ m2 = this.m_invMassB;
+ i1 = this.m_invIA;
+ i2 = this.m_invIB;
+ var k11 = m1 + m2 + i1 * this.m_s1 * this.m_s1 + i2 * this.m_s2 * this.m_s2;
+ var k12 = i1 * this.m_s1 + i2 * this.m_s2;
+ var k22 = i1 + i2;
+ this.m_K.col1.Set(k11, k12, 0.0);
+ this.m_K.col2.Set(k12, k22, 0.0);
+ var impulse1 = this.m_K.Solve22(new b2Vec2(), (-C1X), (-C1Y));
+ impulse.x = impulse1.x;
+ impulse.y = impulse1.y;
+ impulse.z = 0.0;
+ }
+ var PX = impulse.x * this.m_perp.x + impulse.z * this.m_axis.x;
+ var PY = impulse.x * this.m_perp.y + impulse.z * this.m_axis.y;
+ var L1 = impulse.x * this.m_s1 + impulse.y + impulse.z * this.m_a1;
+ var L2 = impulse.x * this.m_s2 + impulse.y + impulse.z * this.m_a2;
+ c1.x -= this.m_invMassA * PX;
+ c1.y -= this.m_invMassA * PY;
+ a1 -= this.m_invIA * L1;
+ c2.x += this.m_invMassB * PX;
+ c2.y += this.m_invMassB * PY;
+ a2 += this.m_invIB * L2;
+ bA.m_sweep.a = a1;
+ bB.m_sweep.a = a2;
+ bA.SynchronizeTransform();
+ bB.SynchronizeTransform();
+ return linearError <= b2Settings.b2_linearSlop && angularError <= b2Settings.b2_angularSlop;
+ }
+ Box2D.inherit(b2PrismaticJointDef, Box2D.Dynamics.Joints.b2JointDef);
+ b2PrismaticJointDef.prototype.__super = Box2D.Dynamics.Joints.b2JointDef.prototype;
+ b2PrismaticJointDef.b2PrismaticJointDef = function () {
+ Box2D.Dynamics.Joints.b2JointDef.b2JointDef.apply(this, arguments);
+ this.localAnchorA = new b2Vec2();
+ this.localAnchorB = new b2Vec2();
+ this.localAxisA = new b2Vec2();
+ };
+ b2PrismaticJointDef.prototype.b2PrismaticJointDef = function () {
+ this.__super.b2JointDef.call(this);
+ this.type = b2Joint.e_prismaticJoint;
+ this.localAxisA.Set(1.0, 0.0);
+ this.referenceAngle = 0.0;
+ this.enableLimit = false;
+ this.lowerTranslation = 0.0;
+ this.upperTranslation = 0.0;
+ this.enableMotor = false;
+ this.maxMotorForce = 0.0;
+ this.motorSpeed = 0.0;
+ }
+ b2PrismaticJointDef.prototype.Initialize = function (bA, bB, anchor, axis) {
+ this.bodyA = bA;
+ this.bodyB = bB;
+ this.localAnchorA = this.bodyA.GetLocalPoint(anchor);
+ this.localAnchorB = this.bodyB.GetLocalPoint(anchor);
+ this.localAxisA = this.bodyA.GetLocalVector(axis);
+ this.referenceAngle = this.bodyB.GetAngle() - this.bodyA.GetAngle();
+ }
+ Box2D.inherit(b2PulleyJoint, Box2D.Dynamics.Joints.b2Joint);
+ b2PulleyJoint.prototype.__super = Box2D.Dynamics.Joints.b2Joint.prototype;
+ b2PulleyJoint.b2PulleyJoint = function () {
+ Box2D.Dynamics.Joints.b2Joint.b2Joint.apply(this, arguments);
+ this.m_groundAnchor1 = new b2Vec2();
+ this.m_groundAnchor2 = new b2Vec2();
+ this.m_localAnchor1 = new b2Vec2();
+ this.m_localAnchor2 = new b2Vec2();
+ this.m_u1 = new b2Vec2();
+ this.m_u2 = new b2Vec2();
+ };
+ b2PulleyJoint.prototype.GetAnchorA = function () {
+ return this.m_bodyA.GetWorldPoint(this.m_localAnchor1);
+ }
+ b2PulleyJoint.prototype.GetAnchorB = function () {
+ return this.m_bodyB.GetWorldPoint(this.m_localAnchor2);
+ }
+ b2PulleyJoint.prototype.GetReactionForce = function (inv_dt) {
+ if (inv_dt === undefined) inv_dt = 0;
+ return new b2Vec2(inv_dt * this.m_impulse * this.m_u2.x, inv_dt * this.m_impulse * this.m_u2.y);
+ }
+ b2PulleyJoint.prototype.GetReactionTorque = function (inv_dt) {
+ if (inv_dt === undefined) inv_dt = 0;
+ return 0.0;
+ }
+ b2PulleyJoint.prototype.GetGroundAnchorA = function () {
+ var a = this.m_ground.m_xf.position.Copy();
+ a.Add(this.m_groundAnchor1);
+ return a;
+ }
+ b2PulleyJoint.prototype.GetGroundAnchorB = function () {
+ var a = this.m_ground.m_xf.position.Copy();
+ a.Add(this.m_groundAnchor2);
+ return a;
+ }
+ b2PulleyJoint.prototype.GetLength1 = function () {
+ var p = this.m_bodyA.GetWorldPoint(this.m_localAnchor1);
+ var sX = this.m_ground.m_xf.position.x + this.m_groundAnchor1.x;
+ var sY = this.m_ground.m_xf.position.y + this.m_groundAnchor1.y;
+ var dX = p.x - sX;
+ var dY = p.y - sY;
+ return Math.sqrt(dX * dX + dY * dY);
+ }
+ b2PulleyJoint.prototype.GetLength2 = function () {
+ var p = this.m_bodyB.GetWorldPoint(this.m_localAnchor2);
+ var sX = this.m_ground.m_xf.position.x + this.m_groundAnchor2.x;
+ var sY = this.m_ground.m_xf.position.y + this.m_groundAnchor2.y;
+ var dX = p.x - sX;
+ var dY = p.y - sY;
+ return Math.sqrt(dX * dX + dY * dY);
+ }
+ b2PulleyJoint.prototype.GetRatio = function () {
+ return this.m_ratio;
+ }
+ b2PulleyJoint.prototype.b2PulleyJoint = function (def) {
+ this.__super.b2Joint.call(this, def);
+ var tMat;
+ var tX = 0;
+ var tY = 0;
+ this.m_ground = this.m_bodyA.m_world.m_groundBody;
+ this.m_groundAnchor1.x = def.groundAnchorA.x - this.m_ground.m_xf.position.x;
+ this.m_groundAnchor1.y = def.groundAnchorA.y - this.m_ground.m_xf.position.y;
+ this.m_groundAnchor2.x = def.groundAnchorB.x - this.m_ground.m_xf.position.x;
+ this.m_groundAnchor2.y = def.groundAnchorB.y - this.m_ground.m_xf.position.y;
+ this.m_localAnchor1.SetV(def.localAnchorA);
+ this.m_localAnchor2.SetV(def.localAnchorB);
+ this.m_ratio = def.ratio;
+ this.m_constant = def.lengthA + this.m_ratio * def.lengthB;
+ this.m_maxLength1 = b2Math.Min(def.maxLengthA, this.m_constant - this.m_ratio * b2PulleyJoint.b2_minPulleyLength);
+ this.m_maxLength2 = b2Math.Min(def.maxLengthB, (this.m_constant - b2PulleyJoint.b2_minPulleyLength) / this.m_ratio);
+ this.m_impulse = 0.0;
+ this.m_limitImpulse1 = 0.0;
+ this.m_limitImpulse2 = 0.0;
+ }
+ b2PulleyJoint.prototype.InitVelocityConstraints = function (step) {
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ var tMat;
+ tMat = bA.m_xf.R;
+ var r1X = this.m_localAnchor1.x - bA.m_sweep.localCenter.x;
+ var r1Y = this.m_localAnchor1.y - bA.m_sweep.localCenter.y;
+ var tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y);
+ r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y);
+ r1X = tX;
+ tMat = bB.m_xf.R;
+ var r2X = this.m_localAnchor2.x - bB.m_sweep.localCenter.x;
+ var r2Y = this.m_localAnchor2.y - bB.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y);
+ r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y);
+ r2X = tX;
+ var p1X = bA.m_sweep.c.x + r1X;
+ var p1Y = bA.m_sweep.c.y + r1Y;
+ var p2X = bB.m_sweep.c.x + r2X;
+ var p2Y = bB.m_sweep.c.y + r2Y;
+ var s1X = this.m_ground.m_xf.position.x + this.m_groundAnchor1.x;
+ var s1Y = this.m_ground.m_xf.position.y + this.m_groundAnchor1.y;
+ var s2X = this.m_ground.m_xf.position.x + this.m_groundAnchor2.x;
+ var s2Y = this.m_ground.m_xf.position.y + this.m_groundAnchor2.y;
+ this.m_u1.Set(p1X - s1X, p1Y - s1Y);
+ this.m_u2.Set(p2X - s2X, p2Y - s2Y);
+ var length1 = this.m_u1.Length();
+ var length2 = this.m_u2.Length();
+ if (length1 > b2Settings.b2_linearSlop) {
+ this.m_u1.Multiply(1.0 / length1);
+ }
+ else {
+ this.m_u1.SetZero();
+ }
+ if (length2 > b2Settings.b2_linearSlop) {
+ this.m_u2.Multiply(1.0 / length2);
+ }
+ else {
+ this.m_u2.SetZero();
+ }
+ var C = this.m_constant - length1 - this.m_ratio * length2;
+ if (C > 0.0) {
+ this.m_state = b2Joint.e_inactiveLimit;
+ this.m_impulse = 0.0;
+ }
+ else {
+ this.m_state = b2Joint.e_atUpperLimit;
+ }
+ if (length1 < this.m_maxLength1) {
+ this.m_limitState1 = b2Joint.e_inactiveLimit;
+ this.m_limitImpulse1 = 0.0;
+ }
+ else {
+ this.m_limitState1 = b2Joint.e_atUpperLimit;
+ }
+ if (length2 < this.m_maxLength2) {
+ this.m_limitState2 = b2Joint.e_inactiveLimit;
+ this.m_limitImpulse2 = 0.0;
+ }
+ else {
+ this.m_limitState2 = b2Joint.e_atUpperLimit;
+ }
+ var cr1u1 = r1X * this.m_u1.y - r1Y * this.m_u1.x;
+ var cr2u2 = r2X * this.m_u2.y - r2Y * this.m_u2.x;
+ this.m_limitMass1 = bA.m_invMass + bA.m_invI * cr1u1 * cr1u1;
+ this.m_limitMass2 = bB.m_invMass + bB.m_invI * cr2u2 * cr2u2;
+ this.m_pulleyMass = this.m_limitMass1 + this.m_ratio * this.m_ratio * this.m_limitMass2;
+ this.m_limitMass1 = 1.0 / this.m_limitMass1;
+ this.m_limitMass2 = 1.0 / this.m_limitMass2;
+ this.m_pulleyMass = 1.0 / this.m_pulleyMass;
+ if (step.warmStarting) {
+ this.m_impulse *= step.dtRatio;
+ this.m_limitImpulse1 *= step.dtRatio;
+ this.m_limitImpulse2 *= step.dtRatio;
+ var P1X = ((-this.m_impulse) - this.m_limitImpulse1) * this.m_u1.x;
+ var P1Y = ((-this.m_impulse) - this.m_limitImpulse1) * this.m_u1.y;
+ var P2X = ((-this.m_ratio * this.m_impulse) - this.m_limitImpulse2) * this.m_u2.x;
+ var P2Y = ((-this.m_ratio * this.m_impulse) - this.m_limitImpulse2) * this.m_u2.y;
+ bA.m_linearVelocity.x += bA.m_invMass * P1X;
+ bA.m_linearVelocity.y += bA.m_invMass * P1Y;
+ bA.m_angularVelocity += bA.m_invI * (r1X * P1Y - r1Y * P1X);
+ bB.m_linearVelocity.x += bB.m_invMass * P2X;
+ bB.m_linearVelocity.y += bB.m_invMass * P2Y;
+ bB.m_angularVelocity += bB.m_invI * (r2X * P2Y - r2Y * P2X);
+ }
+ else {
+ this.m_impulse = 0.0;
+ this.m_limitImpulse1 = 0.0;
+ this.m_limitImpulse2 = 0.0;
+ }
+ }
+ b2PulleyJoint.prototype.SolveVelocityConstraints = function (step) {
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ var tMat;
+ tMat = bA.m_xf.R;
+ var r1X = this.m_localAnchor1.x - bA.m_sweep.localCenter.x;
+ var r1Y = this.m_localAnchor1.y - bA.m_sweep.localCenter.y;
+ var tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y);
+ r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y);
+ r1X = tX;
+ tMat = bB.m_xf.R;
+ var r2X = this.m_localAnchor2.x - bB.m_sweep.localCenter.x;
+ var r2Y = this.m_localAnchor2.y - bB.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y);
+ r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y);
+ r2X = tX;
+ var v1X = 0;
+ var v1Y = 0;
+ var v2X = 0;
+ var v2Y = 0;
+ var P1X = 0;
+ var P1Y = 0;
+ var P2X = 0;
+ var P2Y = 0;
+ var Cdot = 0;
+ var impulse = 0;
+ var oldImpulse = 0;
+ if (this.m_state == b2Joint.e_atUpperLimit) {
+ v1X = bA.m_linearVelocity.x + ((-bA.m_angularVelocity * r1Y));
+ v1Y = bA.m_linearVelocity.y + (bA.m_angularVelocity * r1X);
+ v2X = bB.m_linearVelocity.x + ((-bB.m_angularVelocity * r2Y));
+ v2Y = bB.m_linearVelocity.y + (bB.m_angularVelocity * r2X);
+ Cdot = (-(this.m_u1.x * v1X + this.m_u1.y * v1Y)) - this.m_ratio * (this.m_u2.x * v2X + this.m_u2.y * v2Y);
+ impulse = this.m_pulleyMass * ((-Cdot));
+ oldImpulse = this.m_impulse;
+ this.m_impulse = b2Math.Max(0.0, this.m_impulse + impulse);
+ impulse = this.m_impulse - oldImpulse;
+ P1X = (-impulse * this.m_u1.x);
+ P1Y = (-impulse * this.m_u1.y);
+ P2X = (-this.m_ratio * impulse * this.m_u2.x);
+ P2Y = (-this.m_ratio * impulse * this.m_u2.y);
+ bA.m_linearVelocity.x += bA.m_invMass * P1X;
+ bA.m_linearVelocity.y += bA.m_invMass * P1Y;
+ bA.m_angularVelocity += bA.m_invI * (r1X * P1Y - r1Y * P1X);
+ bB.m_linearVelocity.x += bB.m_invMass * P2X;
+ bB.m_linearVelocity.y += bB.m_invMass * P2Y;
+ bB.m_angularVelocity += bB.m_invI * (r2X * P2Y - r2Y * P2X);
+ }
+ if (this.m_limitState1 == b2Joint.e_atUpperLimit) {
+ v1X = bA.m_linearVelocity.x + ((-bA.m_angularVelocity * r1Y));
+ v1Y = bA.m_linearVelocity.y + (bA.m_angularVelocity * r1X);
+ Cdot = (-(this.m_u1.x * v1X + this.m_u1.y * v1Y));
+ impulse = (-this.m_limitMass1 * Cdot);
+ oldImpulse = this.m_limitImpulse1;
+ this.m_limitImpulse1 = b2Math.Max(0.0, this.m_limitImpulse1 + impulse);
+ impulse = this.m_limitImpulse1 - oldImpulse;
+ P1X = (-impulse * this.m_u1.x);
+ P1Y = (-impulse * this.m_u1.y);
+ bA.m_linearVelocity.x += bA.m_invMass * P1X;
+ bA.m_linearVelocity.y += bA.m_invMass * P1Y;
+ bA.m_angularVelocity += bA.m_invI * (r1X * P1Y - r1Y * P1X);
+ }
+ if (this.m_limitState2 == b2Joint.e_atUpperLimit) {
+ v2X = bB.m_linearVelocity.x + ((-bB.m_angularVelocity * r2Y));
+ v2Y = bB.m_linearVelocity.y + (bB.m_angularVelocity * r2X);
+ Cdot = (-(this.m_u2.x * v2X + this.m_u2.y * v2Y));
+ impulse = (-this.m_limitMass2 * Cdot);
+ oldImpulse = this.m_limitImpulse2;
+ this.m_limitImpulse2 = b2Math.Max(0.0, this.m_limitImpulse2 + impulse);
+ impulse = this.m_limitImpulse2 - oldImpulse;
+ P2X = (-impulse * this.m_u2.x);
+ P2Y = (-impulse * this.m_u2.y);
+ bB.m_linearVelocity.x += bB.m_invMass * P2X;
+ bB.m_linearVelocity.y += bB.m_invMass * P2Y;
+ bB.m_angularVelocity += bB.m_invI * (r2X * P2Y - r2Y * P2X);
+ }
+ }
+ b2PulleyJoint.prototype.SolvePositionConstraints = function (baumgarte) {
+ if (baumgarte === undefined) baumgarte = 0;
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ var tMat;
+ var s1X = this.m_ground.m_xf.position.x + this.m_groundAnchor1.x;
+ var s1Y = this.m_ground.m_xf.position.y + this.m_groundAnchor1.y;
+ var s2X = this.m_ground.m_xf.position.x + this.m_groundAnchor2.x;
+ var s2Y = this.m_ground.m_xf.position.y + this.m_groundAnchor2.y;
+ var r1X = 0;
+ var r1Y = 0;
+ var r2X = 0;
+ var r2Y = 0;
+ var p1X = 0;
+ var p1Y = 0;
+ var p2X = 0;
+ var p2Y = 0;
+ var length1 = 0;
+ var length2 = 0;
+ var C = 0;
+ var impulse = 0;
+ var oldImpulse = 0;
+ var oldLimitPositionImpulse = 0;
+ var tX = 0;
+ var linearError = 0.0;
+ if (this.m_state == b2Joint.e_atUpperLimit) {
+ tMat = bA.m_xf.R;
+ r1X = this.m_localAnchor1.x - bA.m_sweep.localCenter.x;
+ r1Y = this.m_localAnchor1.y - bA.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y);
+ r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y);
+ r1X = tX;
+ tMat = bB.m_xf.R;
+ r2X = this.m_localAnchor2.x - bB.m_sweep.localCenter.x;
+ r2Y = this.m_localAnchor2.y - bB.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y);
+ r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y);
+ r2X = tX;
+ p1X = bA.m_sweep.c.x + r1X;
+ p1Y = bA.m_sweep.c.y + r1Y;
+ p2X = bB.m_sweep.c.x + r2X;
+ p2Y = bB.m_sweep.c.y + r2Y;
+ this.m_u1.Set(p1X - s1X, p1Y - s1Y);
+ this.m_u2.Set(p2X - s2X, p2Y - s2Y);
+ length1 = this.m_u1.Length();
+ length2 = this.m_u2.Length();
+ if (length1 > b2Settings.b2_linearSlop) {
+ this.m_u1.Multiply(1.0 / length1);
+ }
+ else {
+ this.m_u1.SetZero();
+ }
+ if (length2 > b2Settings.b2_linearSlop) {
+ this.m_u2.Multiply(1.0 / length2);
+ }
+ else {
+ this.m_u2.SetZero();
+ }
+ C = this.m_constant - length1 - this.m_ratio * length2;
+ linearError = b2Math.Max(linearError, (-C));
+ C = b2Math.Clamp(C + b2Settings.b2_linearSlop, (-b2Settings.b2_maxLinearCorrection), 0.0);
+ impulse = (-this.m_pulleyMass * C);
+ p1X = (-impulse * this.m_u1.x);
+ p1Y = (-impulse * this.m_u1.y);
+ p2X = (-this.m_ratio * impulse * this.m_u2.x);
+ p2Y = (-this.m_ratio * impulse * this.m_u2.y);
+ bA.m_sweep.c.x += bA.m_invMass * p1X;
+ bA.m_sweep.c.y += bA.m_invMass * p1Y;
+ bA.m_sweep.a += bA.m_invI * (r1X * p1Y - r1Y * p1X);
+ bB.m_sweep.c.x += bB.m_invMass * p2X;
+ bB.m_sweep.c.y += bB.m_invMass * p2Y;
+ bB.m_sweep.a += bB.m_invI * (r2X * p2Y - r2Y * p2X);
+ bA.SynchronizeTransform();
+ bB.SynchronizeTransform();
+ }
+ if (this.m_limitState1 == b2Joint.e_atUpperLimit) {
+ tMat = bA.m_xf.R;
+ r1X = this.m_localAnchor1.x - bA.m_sweep.localCenter.x;
+ r1Y = this.m_localAnchor1.y - bA.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y);
+ r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y);
+ r1X = tX;
+ p1X = bA.m_sweep.c.x + r1X;
+ p1Y = bA.m_sweep.c.y + r1Y;
+ this.m_u1.Set(p1X - s1X, p1Y - s1Y);
+ length1 = this.m_u1.Length();
+ if (length1 > b2Settings.b2_linearSlop) {
+ this.m_u1.x *= 1.0 / length1;
+ this.m_u1.y *= 1.0 / length1;
+ }
+ else {
+ this.m_u1.SetZero();
+ }
+ C = this.m_maxLength1 - length1;
+ linearError = b2Math.Max(linearError, (-C));
+ C = b2Math.Clamp(C + b2Settings.b2_linearSlop, (-b2Settings.b2_maxLinearCorrection), 0.0);
+ impulse = (-this.m_limitMass1 * C);
+ p1X = (-impulse * this.m_u1.x);
+ p1Y = (-impulse * this.m_u1.y);
+ bA.m_sweep.c.x += bA.m_invMass * p1X;
+ bA.m_sweep.c.y += bA.m_invMass * p1Y;
+ bA.m_sweep.a += bA.m_invI * (r1X * p1Y - r1Y * p1X);
+ bA.SynchronizeTransform();
+ }
+ if (this.m_limitState2 == b2Joint.e_atUpperLimit) {
+ tMat = bB.m_xf.R;
+ r2X = this.m_localAnchor2.x - bB.m_sweep.localCenter.x;
+ r2Y = this.m_localAnchor2.y - bB.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y);
+ r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y);
+ r2X = tX;
+ p2X = bB.m_sweep.c.x + r2X;
+ p2Y = bB.m_sweep.c.y + r2Y;
+ this.m_u2.Set(p2X - s2X, p2Y - s2Y);
+ length2 = this.m_u2.Length();
+ if (length2 > b2Settings.b2_linearSlop) {
+ this.m_u2.x *= 1.0 / length2;
+ this.m_u2.y *= 1.0 / length2;
+ }
+ else {
+ this.m_u2.SetZero();
+ }
+ C = this.m_maxLength2 - length2;
+ linearError = b2Math.Max(linearError, (-C));
+ C = b2Math.Clamp(C + b2Settings.b2_linearSlop, (-b2Settings.b2_maxLinearCorrection), 0.0);
+ impulse = (-this.m_limitMass2 * C);
+ p2X = (-impulse * this.m_u2.x);
+ p2Y = (-impulse * this.m_u2.y);
+ bB.m_sweep.c.x += bB.m_invMass * p2X;
+ bB.m_sweep.c.y += bB.m_invMass * p2Y;
+ bB.m_sweep.a += bB.m_invI * (r2X * p2Y - r2Y * p2X);
+ bB.SynchronizeTransform();
+ }
+ return linearError < b2Settings.b2_linearSlop;
+ }
+ Box2D.postDefs.push(function () {
+ Box2D.Dynamics.Joints.b2PulleyJoint.b2_minPulleyLength = 2.0;
+ });
+ Box2D.inherit(b2PulleyJointDef, Box2D.Dynamics.Joints.b2JointDef);
+ b2PulleyJointDef.prototype.__super = Box2D.Dynamics.Joints.b2JointDef.prototype;
+ b2PulleyJointDef.b2PulleyJointDef = function () {
+ Box2D.Dynamics.Joints.b2JointDef.b2JointDef.apply(this, arguments);
+ this.groundAnchorA = new b2Vec2();
+ this.groundAnchorB = new b2Vec2();
+ this.localAnchorA = new b2Vec2();
+ this.localAnchorB = new b2Vec2();
+ };
+ b2PulleyJointDef.prototype.b2PulleyJointDef = function () {
+ this.__super.b2JointDef.call(this);
+ this.type = b2Joint.e_pulleyJoint;
+ this.groundAnchorA.Set((-1.0), 1.0);
+ this.groundAnchorB.Set(1.0, 1.0);
+ this.localAnchorA.Set((-1.0), 0.0);
+ this.localAnchorB.Set(1.0, 0.0);
+ this.lengthA = 0.0;
+ this.maxLengthA = 0.0;
+ this.lengthB = 0.0;
+ this.maxLengthB = 0.0;
+ this.ratio = 1.0;
+ this.collideConnected = true;
+ }
+ b2PulleyJointDef.prototype.Initialize = function (bA, bB, gaA, gaB, anchorA, anchorB, r) {
+ if (r === undefined) r = 0;
+ this.bodyA = bA;
+ this.bodyB = bB;
+ this.groundAnchorA.SetV(gaA);
+ this.groundAnchorB.SetV(gaB);
+ this.localAnchorA = this.bodyA.GetLocalPoint(anchorA);
+ this.localAnchorB = this.bodyB.GetLocalPoint(anchorB);
+ var d1X = anchorA.x - gaA.x;
+ var d1Y = anchorA.y - gaA.y;
+ this.lengthA = Math.sqrt(d1X * d1X + d1Y * d1Y);
+ var d2X = anchorB.x - gaB.x;
+ var d2Y = anchorB.y - gaB.y;
+ this.lengthB = Math.sqrt(d2X * d2X + d2Y * d2Y);
+ this.ratio = r;
+ var C = this.lengthA + this.ratio * this.lengthB;
+ this.maxLengthA = C - this.ratio * b2PulleyJoint.b2_minPulleyLength;
+ this.maxLengthB = (C - b2PulleyJoint.b2_minPulleyLength) / this.ratio;
+ }
+ Box2D.inherit(b2RevoluteJoint, Box2D.Dynamics.Joints.b2Joint);
+ b2RevoluteJoint.prototype.__super = Box2D.Dynamics.Joints.b2Joint.prototype;
+ b2RevoluteJoint.b2RevoluteJoint = function () {
+ Box2D.Dynamics.Joints.b2Joint.b2Joint.apply(this, arguments);
+ this.K = new b2Mat22();
+ this.K1 = new b2Mat22();
+ this.K2 = new b2Mat22();
+ this.K3 = new b2Mat22();
+ this.impulse3 = new b2Vec3();
+ this.impulse2 = new b2Vec2();
+ this.reduced = new b2Vec2();
+ this.m_localAnchor1 = new b2Vec2();
+ this.m_localAnchor2 = new b2Vec2();
+ this.m_impulse = new b2Vec3();
+ this.m_mass = new b2Mat33();
+ };
+ b2RevoluteJoint.prototype.GetAnchorA = function () {
+ return this.m_bodyA.GetWorldPoint(this.m_localAnchor1);
+ }
+ b2RevoluteJoint.prototype.GetAnchorB = function () {
+ return this.m_bodyB.GetWorldPoint(this.m_localAnchor2);
+ }
+ b2RevoluteJoint.prototype.GetReactionForce = function (inv_dt) {
+ if (inv_dt === undefined) inv_dt = 0;
+ return new b2Vec2(inv_dt * this.m_impulse.x, inv_dt * this.m_impulse.y);
+ }
+ b2RevoluteJoint.prototype.GetReactionTorque = function (inv_dt) {
+ if (inv_dt === undefined) inv_dt = 0;
+ return inv_dt * this.m_impulse.z;
+ }
+ b2RevoluteJoint.prototype.GetJointAngle = function () {
+ return this.m_bodyB.m_sweep.a - this.m_bodyA.m_sweep.a - this.m_referenceAngle;
+ }
+ b2RevoluteJoint.prototype.GetJointSpeed = function () {
+ return this.m_bodyB.m_angularVelocity - this.m_bodyA.m_angularVelocity;
+ }
+ b2RevoluteJoint.prototype.IsLimitEnabled = function () {
+ return this.m_enableLimit;
+ }
+ b2RevoluteJoint.prototype.EnableLimit = function (flag) {
+ this.m_enableLimit = flag;
+ }
+ b2RevoluteJoint.prototype.GetLowerLimit = function () {
+ return this.m_lowerAngle;
+ }
+ b2RevoluteJoint.prototype.GetUpperLimit = function () {
+ return this.m_upperAngle;
+ }
+ b2RevoluteJoint.prototype.SetLimits = function (lower, upper) {
+ if (lower === undefined) lower = 0;
+ if (upper === undefined) upper = 0;
+ this.m_lowerAngle = lower;
+ this.m_upperAngle = upper;
+ }
+ b2RevoluteJoint.prototype.IsMotorEnabled = function () {
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ return this.m_enableMotor;
+ }
+ b2RevoluteJoint.prototype.EnableMotor = function (flag) {
+ this.m_enableMotor = flag;
+ }
+ b2RevoluteJoint.prototype.SetMotorSpeed = function (speed) {
+ if (speed === undefined) speed = 0;
+ this.m_bodyA.SetAwake(true);
+ this.m_bodyB.SetAwake(true);
+ this.m_motorSpeed = speed;
+ }
+ b2RevoluteJoint.prototype.GetMotorSpeed = function () {
+ return this.m_motorSpeed;
+ }
+ b2RevoluteJoint.prototype.SetMaxMotorTorque = function (torque) {
+ if (torque === undefined) torque = 0;
+ this.m_maxMotorTorque = torque;
+ }
+ b2RevoluteJoint.prototype.GetMotorTorque = function () {
+ return this.m_maxMotorTorque;
+ }
+ b2RevoluteJoint.prototype.b2RevoluteJoint = function (def) {
+ this.__super.b2Joint.call(this, def);
+ this.m_localAnchor1.SetV(def.localAnchorA);
+ this.m_localAnchor2.SetV(def.localAnchorB);
+ this.m_referenceAngle = def.referenceAngle;
+ this.m_impulse.SetZero();
+ this.m_motorImpulse = 0.0;
+ this.m_lowerAngle = def.lowerAngle;
+ this.m_upperAngle = def.upperAngle;
+ this.m_maxMotorTorque = def.maxMotorTorque;
+ this.m_motorSpeed = def.motorSpeed;
+ this.m_enableLimit = def.enableLimit;
+ this.m_enableMotor = def.enableMotor;
+ this.m_limitState = b2Joint.e_inactiveLimit;
+ }
+ b2RevoluteJoint.prototype.InitVelocityConstraints = function (step) {
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ var tMat;
+ var tX = 0;
+ if (this.m_enableMotor || this.m_enableLimit) {}
+ tMat = bA.m_xf.R;
+ var r1X = this.m_localAnchor1.x - bA.m_sweep.localCenter.x;
+ var r1Y = this.m_localAnchor1.y - bA.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y);
+ r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y);
+ r1X = tX;
+ tMat = bB.m_xf.R;
+ var r2X = this.m_localAnchor2.x - bB.m_sweep.localCenter.x;
+ var r2Y = this.m_localAnchor2.y - bB.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y);
+ r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y);
+ r2X = tX;
+ var m1 = bA.m_invMass;
+ var m2 = bB.m_invMass;
+ var i1 = bA.m_invI;
+ var i2 = bB.m_invI;
+ this.m_mass.col1.x = m1 + m2 + r1Y * r1Y * i1 + r2Y * r2Y * i2;
+ this.m_mass.col2.x = (-r1Y * r1X * i1) - r2Y * r2X * i2;
+ this.m_mass.col3.x = (-r1Y * i1) - r2Y * i2;
+ this.m_mass.col1.y = this.m_mass.col2.x;
+ this.m_mass.col2.y = m1 + m2 + r1X * r1X * i1 + r2X * r2X * i2;
+ this.m_mass.col3.y = r1X * i1 + r2X * i2;
+ this.m_mass.col1.z = this.m_mass.col3.x;
+ this.m_mass.col2.z = this.m_mass.col3.y;
+ this.m_mass.col3.z = i1 + i2;
+ this.m_motorMass = 1.0 / (i1 + i2);
+ if (this.m_enableMotor == false) {
+ this.m_motorImpulse = 0.0;
+ }
+ if (this.m_enableLimit) {
+ var jointAngle = bB.m_sweep.a - bA.m_sweep.a - this.m_referenceAngle;
+ if (b2Math.Abs(this.m_upperAngle - this.m_lowerAngle) < 2.0 * b2Settings.b2_angularSlop) {
+ this.m_limitState = b2Joint.e_equalLimits;
+ }
+ else if (jointAngle <= this.m_lowerAngle) {
+ if (this.m_limitState != b2Joint.e_atLowerLimit) {
+ this.m_impulse.z = 0.0;
+ }
+ this.m_limitState = b2Joint.e_atLowerLimit;
+ }
+ else if (jointAngle >= this.m_upperAngle) {
+ if (this.m_limitState != b2Joint.e_atUpperLimit) {
+ this.m_impulse.z = 0.0;
+ }
+ this.m_limitState = b2Joint.e_atUpperLimit;
+ }
+ else {
+ this.m_limitState = b2Joint.e_inactiveLimit;
+ this.m_impulse.z = 0.0;
+ }
+ }
+ else {
+ this.m_limitState = b2Joint.e_inactiveLimit;
+ }
+ if (step.warmStarting) {
+ this.m_impulse.x *= step.dtRatio;
+ this.m_impulse.y *= step.dtRatio;
+ this.m_motorImpulse *= step.dtRatio;
+ var PX = this.m_impulse.x;
+ var PY = this.m_impulse.y;
+ bA.m_linearVelocity.x -= m1 * PX;
+ bA.m_linearVelocity.y -= m1 * PY;
+ bA.m_angularVelocity -= i1 * ((r1X * PY - r1Y * PX) + this.m_motorImpulse + this.m_impulse.z);
+ bB.m_linearVelocity.x += m2 * PX;
+ bB.m_linearVelocity.y += m2 * PY;
+ bB.m_angularVelocity += i2 * ((r2X * PY - r2Y * PX) + this.m_motorImpulse + this.m_impulse.z);
+ }
+ else {
+ this.m_impulse.SetZero();
+ this.m_motorImpulse = 0.0;
+ }
+ }
+ b2RevoluteJoint.prototype.SolveVelocityConstraints = function (step) {
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ var tMat;
+ var tX = 0;
+ var newImpulse = 0;
+ var r1X = 0;
+ var r1Y = 0;
+ var r2X = 0;
+ var r2Y = 0;
+ var v1 = bA.m_linearVelocity;
+ var w1 = bA.m_angularVelocity;
+ var v2 = bB.m_linearVelocity;
+ var w2 = bB.m_angularVelocity;
+ var m1 = bA.m_invMass;
+ var m2 = bB.m_invMass;
+ var i1 = bA.m_invI;
+ var i2 = bB.m_invI;
+ if (this.m_enableMotor && this.m_limitState != b2Joint.e_equalLimits) {
+ var Cdot = w2 - w1 - this.m_motorSpeed;
+ var impulse = this.m_motorMass * ((-Cdot));
+ var oldImpulse = this.m_motorImpulse;
+ var maxImpulse = step.dt * this.m_maxMotorTorque;
+ this.m_motorImpulse = b2Math.Clamp(this.m_motorImpulse + impulse, (-maxImpulse), maxImpulse);
+ impulse = this.m_motorImpulse - oldImpulse;
+ w1 -= i1 * impulse;
+ w2 += i2 * impulse;
+ }
+ if (this.m_enableLimit && this.m_limitState != b2Joint.e_inactiveLimit) {
+ tMat = bA.m_xf.R;
+ r1X = this.m_localAnchor1.x - bA.m_sweep.localCenter.x;
+ r1Y = this.m_localAnchor1.y - bA.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y);
+ r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y);
+ r1X = tX;
+ tMat = bB.m_xf.R;
+ r2X = this.m_localAnchor2.x - bB.m_sweep.localCenter.x;
+ r2Y = this.m_localAnchor2.y - bB.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y);
+ r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y);
+ r2X = tX;
+ var Cdot1X = v2.x + ((-w2 * r2Y)) - v1.x - ((-w1 * r1Y));
+ var Cdot1Y = v2.y + (w2 * r2X) - v1.y - (w1 * r1X);
+ var Cdot2 = w2 - w1;
+ this.m_mass.Solve33(this.impulse3, (-Cdot1X), (-Cdot1Y), (-Cdot2));
+ if (this.m_limitState == b2Joint.e_equalLimits) {
+ this.m_impulse.Add(this.impulse3);
+ }
+ else if (this.m_limitState == b2Joint.e_atLowerLimit) {
+ newImpulse = this.m_impulse.z + this.impulse3.z;
+ if (newImpulse < 0.0) {
+ this.m_mass.Solve22(this.reduced, (-Cdot1X), (-Cdot1Y));
+ this.impulse3.x = this.reduced.x;
+ this.impulse3.y = this.reduced.y;
+ this.impulse3.z = (-this.m_impulse.z);
+ this.m_impulse.x += this.reduced.x;
+ this.m_impulse.y += this.reduced.y;
+ this.m_impulse.z = 0.0;
+ }
+ }
+ else if (this.m_limitState == b2Joint.e_atUpperLimit) {
+ newImpulse = this.m_impulse.z + this.impulse3.z;
+ if (newImpulse > 0.0) {
+ this.m_mass.Solve22(this.reduced, (-Cdot1X), (-Cdot1Y));
+ this.impulse3.x = this.reduced.x;
+ this.impulse3.y = this.reduced.y;
+ this.impulse3.z = (-this.m_impulse.z);
+ this.m_impulse.x += this.reduced.x;
+ this.m_impulse.y += this.reduced.y;
+ this.m_impulse.z = 0.0;
+ }
+ }
+ v1.x -= m1 * this.impulse3.x;
+ v1.y -= m1 * this.impulse3.y;
+ w1 -= i1 * (r1X * this.impulse3.y - r1Y * this.impulse3.x + this.impulse3.z);
+ v2.x += m2 * this.impulse3.x;
+ v2.y += m2 * this.impulse3.y;
+ w2 += i2 * (r2X * this.impulse3.y - r2Y * this.impulse3.x + this.impulse3.z);
+ }
+ else {
+ tMat = bA.m_xf.R;
+ r1X = this.m_localAnchor1.x - bA.m_sweep.localCenter.x;
+ r1Y = this.m_localAnchor1.y - bA.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y);
+ r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y);
+ r1X = tX;
+ tMat = bB.m_xf.R;
+ r2X = this.m_localAnchor2.x - bB.m_sweep.localCenter.x;
+ r2Y = this.m_localAnchor2.y - bB.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y);
+ r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y);
+ r2X = tX;
+ var CdotX = v2.x + ((-w2 * r2Y)) - v1.x - ((-w1 * r1Y));
+ var CdotY = v2.y + (w2 * r2X) - v1.y - (w1 * r1X);
+ this.m_mass.Solve22(this.impulse2, (-CdotX), (-CdotY));
+ this.m_impulse.x += this.impulse2.x;
+ this.m_impulse.y += this.impulse2.y;
+ v1.x -= m1 * this.impulse2.x;
+ v1.y -= m1 * this.impulse2.y;
+ w1 -= i1 * (r1X * this.impulse2.y - r1Y * this.impulse2.x);
+ v2.x += m2 * this.impulse2.x;
+ v2.y += m2 * this.impulse2.y;
+ w2 += i2 * (r2X * this.impulse2.y - r2Y * this.impulse2.x);
+ }
+ bA.m_linearVelocity.SetV(v1);
+ bA.m_angularVelocity = w1;
+ bB.m_linearVelocity.SetV(v2);
+ bB.m_angularVelocity = w2;
+ }
+ b2RevoluteJoint.prototype.SolvePositionConstraints = function (baumgarte) {
+ if (baumgarte === undefined) baumgarte = 0;
+ var oldLimitImpulse = 0;
+ var C = 0;
+ var tMat;
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ var angularError = 0.0;
+ var positionError = 0.0;
+ var tX = 0;
+ var impulseX = 0;
+ var impulseY = 0;
+ if (this.m_enableLimit && this.m_limitState != b2Joint.e_inactiveLimit) {
+ var angle = bB.m_sweep.a - bA.m_sweep.a - this.m_referenceAngle;
+ var limitImpulse = 0.0;
+ if (this.m_limitState == b2Joint.e_equalLimits) {
+ C = b2Math.Clamp(angle - this.m_lowerAngle, (-b2Settings.b2_maxAngularCorrection), b2Settings.b2_maxAngularCorrection);
+ limitImpulse = (-this.m_motorMass * C);
+ angularError = b2Math.Abs(C);
+ }
+ else if (this.m_limitState == b2Joint.e_atLowerLimit) {
+ C = angle - this.m_lowerAngle;
+ angularError = (-C);
+ C = b2Math.Clamp(C + b2Settings.b2_angularSlop, (-b2Settings.b2_maxAngularCorrection), 0.0);
+ limitImpulse = (-this.m_motorMass * C);
+ }
+ else if (this.m_limitState == b2Joint.e_atUpperLimit) {
+ C = angle - this.m_upperAngle;
+ angularError = C;
+ C = b2Math.Clamp(C - b2Settings.b2_angularSlop, 0.0, b2Settings.b2_maxAngularCorrection);
+ limitImpulse = (-this.m_motorMass * C);
+ }
+ bA.m_sweep.a -= bA.m_invI * limitImpulse;
+ bB.m_sweep.a += bB.m_invI * limitImpulse;
+ bA.SynchronizeTransform();
+ bB.SynchronizeTransform();
+ } {
+ tMat = bA.m_xf.R;
+ var r1X = this.m_localAnchor1.x - bA.m_sweep.localCenter.x;
+ var r1Y = this.m_localAnchor1.y - bA.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * r1X + tMat.col2.x * r1Y);
+ r1Y = (tMat.col1.y * r1X + tMat.col2.y * r1Y);
+ r1X = tX;
+ tMat = bB.m_xf.R;
+ var r2X = this.m_localAnchor2.x - bB.m_sweep.localCenter.x;
+ var r2Y = this.m_localAnchor2.y - bB.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * r2X + tMat.col2.x * r2Y);
+ r2Y = (tMat.col1.y * r2X + tMat.col2.y * r2Y);
+ r2X = tX;
+ var CX = bB.m_sweep.c.x + r2X - bA.m_sweep.c.x - r1X;
+ var CY = bB.m_sweep.c.y + r2Y - bA.m_sweep.c.y - r1Y;
+ var CLengthSquared = CX * CX + CY * CY;
+ var CLength = Math.sqrt(CLengthSquared);
+ positionError = CLength;
+ var invMass1 = bA.m_invMass;
+ var invMass2 = bB.m_invMass;
+ var invI1 = bA.m_invI;
+ var invI2 = bB.m_invI;
+ var k_allowedStretch = 10.0 * b2Settings.b2_linearSlop;
+ if (CLengthSquared > k_allowedStretch * k_allowedStretch) {
+ var uX = CX / CLength;
+ var uY = CY / CLength;
+ var k = invMass1 + invMass2;
+ var m = 1.0 / k;
+ impulseX = m * ((-CX));
+ impulseY = m * ((-CY));
+ var k_beta = 0.5;
+ bA.m_sweep.c.x -= k_beta * invMass1 * impulseX;
+ bA.m_sweep.c.y -= k_beta * invMass1 * impulseY;
+ bB.m_sweep.c.x += k_beta * invMass2 * impulseX;
+ bB.m_sweep.c.y += k_beta * invMass2 * impulseY;
+ CX = bB.m_sweep.c.x + r2X - bA.m_sweep.c.x - r1X;
+ CY = bB.m_sweep.c.y + r2Y - bA.m_sweep.c.y - r1Y;
+ }
+ this.K1.col1.x = invMass1 + invMass2;
+ this.K1.col2.x = 0.0;
+ this.K1.col1.y = 0.0;
+ this.K1.col2.y = invMass1 + invMass2;
+ this.K2.col1.x = invI1 * r1Y * r1Y;
+ this.K2.col2.x = (-invI1 * r1X * r1Y);
+ this.K2.col1.y = (-invI1 * r1X * r1Y);
+ this.K2.col2.y = invI1 * r1X * r1X;
+ this.K3.col1.x = invI2 * r2Y * r2Y;
+ this.K3.col2.x = (-invI2 * r2X * r2Y);
+ this.K3.col1.y = (-invI2 * r2X * r2Y);
+ this.K3.col2.y = invI2 * r2X * r2X;
+ this.K.SetM(this.K1);
+ this.K.AddM(this.K2);
+ this.K.AddM(this.K3);
+ this.K.Solve(b2RevoluteJoint.tImpulse, (-CX), (-CY));
+ impulseX = b2RevoluteJoint.tImpulse.x;
+ impulseY = b2RevoluteJoint.tImpulse.y;
+ bA.m_sweep.c.x -= bA.m_invMass * impulseX;
+ bA.m_sweep.c.y -= bA.m_invMass * impulseY;
+ bA.m_sweep.a -= bA.m_invI * (r1X * impulseY - r1Y * impulseX);
+ bB.m_sweep.c.x += bB.m_invMass * impulseX;
+ bB.m_sweep.c.y += bB.m_invMass * impulseY;
+ bB.m_sweep.a += bB.m_invI * (r2X * impulseY - r2Y * impulseX);
+ bA.SynchronizeTransform();
+ bB.SynchronizeTransform();
+ }
+ return positionError <= b2Settings.b2_linearSlop && angularError <= b2Settings.b2_angularSlop;
+ }
+ Box2D.postDefs.push(function () {
+ Box2D.Dynamics.Joints.b2RevoluteJoint.tImpulse = new b2Vec2();
+ });
+ Box2D.inherit(b2RevoluteJointDef, Box2D.Dynamics.Joints.b2JointDef);
+ b2RevoluteJointDef.prototype.__super = Box2D.Dynamics.Joints.b2JointDef.prototype;
+ b2RevoluteJointDef.b2RevoluteJointDef = function () {
+ Box2D.Dynamics.Joints.b2JointDef.b2JointDef.apply(this, arguments);
+ this.localAnchorA = new b2Vec2();
+ this.localAnchorB = new b2Vec2();
+ };
+ b2RevoluteJointDef.prototype.b2RevoluteJointDef = function () {
+ this.__super.b2JointDef.call(this);
+ this.type = b2Joint.e_revoluteJoint;
+ this.localAnchorA.Set(0.0, 0.0);
+ this.localAnchorB.Set(0.0, 0.0);
+ this.referenceAngle = 0.0;
+ this.lowerAngle = 0.0;
+ this.upperAngle = 0.0;
+ this.maxMotorTorque = 0.0;
+ this.motorSpeed = 0.0;
+ this.enableLimit = false;
+ this.enableMotor = false;
+ }
+ b2RevoluteJointDef.prototype.Initialize = function (bA, bB, anchor) {
+ this.bodyA = bA;
+ this.bodyB = bB;
+ this.localAnchorA = this.bodyA.GetLocalPoint(anchor);
+ this.localAnchorB = this.bodyB.GetLocalPoint(anchor);
+ this.referenceAngle = this.bodyB.GetAngle() - this.bodyA.GetAngle();
+ }
+ Box2D.inherit(b2WeldJoint, Box2D.Dynamics.Joints.b2Joint);
+ b2WeldJoint.prototype.__super = Box2D.Dynamics.Joints.b2Joint.prototype;
+ b2WeldJoint.b2WeldJoint = function () {
+ Box2D.Dynamics.Joints.b2Joint.b2Joint.apply(this, arguments);
+ this.m_localAnchorA = new b2Vec2();
+ this.m_localAnchorB = new b2Vec2();
+ this.m_impulse = new b2Vec3();
+ this.m_mass = new b2Mat33();
+ };
+ b2WeldJoint.prototype.GetAnchorA = function () {
+ return this.m_bodyA.GetWorldPoint(this.m_localAnchorA);
+ }
+ b2WeldJoint.prototype.GetAnchorB = function () {
+ return this.m_bodyB.GetWorldPoint(this.m_localAnchorB);
+ }
+ b2WeldJoint.prototype.GetReactionForce = function (inv_dt) {
+ if (inv_dt === undefined) inv_dt = 0;
+ return new b2Vec2(inv_dt * this.m_impulse.x, inv_dt * this.m_impulse.y);
+ }
+ b2WeldJoint.prototype.GetReactionTorque = function (inv_dt) {
+ if (inv_dt === undefined) inv_dt = 0;
+ return inv_dt * this.m_impulse.z;
+ }
+ b2WeldJoint.prototype.b2WeldJoint = function (def) {
+ this.__super.b2Joint.call(this, def);
+ this.m_localAnchorA.SetV(def.localAnchorA);
+ this.m_localAnchorB.SetV(def.localAnchorB);
+ this.m_referenceAngle = def.referenceAngle;
+ this.m_impulse.SetZero();
+ this.m_mass = new b2Mat33();
+ }
+ b2WeldJoint.prototype.InitVelocityConstraints = function (step) {
+ var tMat;
+ var tX = 0;
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ tMat = bA.m_xf.R;
+ var rAX = this.m_localAnchorA.x - bA.m_sweep.localCenter.x;
+ var rAY = this.m_localAnchorA.y - bA.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * rAX + tMat.col2.x * rAY);
+ rAY = (tMat.col1.y * rAX + tMat.col2.y * rAY);
+ rAX = tX;
+ tMat = bB.m_xf.R;
+ var rBX = this.m_localAnchorB.x - bB.m_sweep.localCenter.x;
+ var rBY = this.m_localAnchorB.y - bB.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * rBX + tMat.col2.x * rBY);
+ rBY = (tMat.col1.y * rBX + tMat.col2.y * rBY);
+ rBX = tX;
+ var mA = bA.m_invMass;
+ var mB = bB.m_invMass;
+ var iA = bA.m_invI;
+ var iB = bB.m_invI;
+ this.m_mass.col1.x = mA + mB + rAY * rAY * iA + rBY * rBY * iB;
+ this.m_mass.col2.x = (-rAY * rAX * iA) - rBY * rBX * iB;
+ this.m_mass.col3.x = (-rAY * iA) - rBY * iB;
+ this.m_mass.col1.y = this.m_mass.col2.x;
+ this.m_mass.col2.y = mA + mB + rAX * rAX * iA + rBX * rBX * iB;
+ this.m_mass.col3.y = rAX * iA + rBX * iB;
+ this.m_mass.col1.z = this.m_mass.col3.x;
+ this.m_mass.col2.z = this.m_mass.col3.y;
+ this.m_mass.col3.z = iA + iB;
+ if (step.warmStarting) {
+ this.m_impulse.x *= step.dtRatio;
+ this.m_impulse.y *= step.dtRatio;
+ this.m_impulse.z *= step.dtRatio;
+ bA.m_linearVelocity.x -= mA * this.m_impulse.x;
+ bA.m_linearVelocity.y -= mA * this.m_impulse.y;
+ bA.m_angularVelocity -= iA * (rAX * this.m_impulse.y - rAY * this.m_impulse.x + this.m_impulse.z);
+ bB.m_linearVelocity.x += mB * this.m_impulse.x;
+ bB.m_linearVelocity.y += mB * this.m_impulse.y;
+ bB.m_angularVelocity += iB * (rBX * this.m_impulse.y - rBY * this.m_impulse.x + this.m_impulse.z);
+ }
+ else {
+ this.m_impulse.SetZero();
+ }
+ }
+ b2WeldJoint.prototype.SolveVelocityConstraints = function (step) {
+ var tMat;
+ var tX = 0;
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ var vA = bA.m_linearVelocity;
+ var wA = bA.m_angularVelocity;
+ var vB = bB.m_linearVelocity;
+ var wB = bB.m_angularVelocity;
+ var mA = bA.m_invMass;
+ var mB = bB.m_invMass;
+ var iA = bA.m_invI;
+ var iB = bB.m_invI;
+ tMat = bA.m_xf.R;
+ var rAX = this.m_localAnchorA.x - bA.m_sweep.localCenter.x;
+ var rAY = this.m_localAnchorA.y - bA.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * rAX + tMat.col2.x * rAY);
+ rAY = (tMat.col1.y * rAX + tMat.col2.y * rAY);
+ rAX = tX;
+ tMat = bB.m_xf.R;
+ var rBX = this.m_localAnchorB.x - bB.m_sweep.localCenter.x;
+ var rBY = this.m_localAnchorB.y - bB.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * rBX + tMat.col2.x * rBY);
+ rBY = (tMat.col1.y * rBX + tMat.col2.y * rBY);
+ rBX = tX;
+ var Cdot1X = vB.x - wB * rBY - vA.x + wA * rAY;
+ var Cdot1Y = vB.y + wB * rBX - vA.y - wA * rAX;
+ var Cdot2 = wB - wA;
+ var impulse = new b2Vec3();
+ this.m_mass.Solve33(impulse, (-Cdot1X), (-Cdot1Y), (-Cdot2));
+ this.m_impulse.Add(impulse);
+ vA.x -= mA * impulse.x;
+ vA.y -= mA * impulse.y;
+ wA -= iA * (rAX * impulse.y - rAY * impulse.x + impulse.z);
+ vB.x += mB * impulse.x;
+ vB.y += mB * impulse.y;
+ wB += iB * (rBX * impulse.y - rBY * impulse.x + impulse.z);
+ bA.m_angularVelocity = wA;
+ bB.m_angularVelocity = wB;
+ }
+ b2WeldJoint.prototype.SolvePositionConstraints = function (baumgarte) {
+ if (baumgarte === undefined) baumgarte = 0;
+ var tMat;
+ var tX = 0;
+ var bA = this.m_bodyA;
+ var bB = this.m_bodyB;
+ tMat = bA.m_xf.R;
+ var rAX = this.m_localAnchorA.x - bA.m_sweep.localCenter.x;
+ var rAY = this.m_localAnchorA.y - bA.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * rAX + tMat.col2.x * rAY);
+ rAY = (tMat.col1.y * rAX + tMat.col2.y * rAY);
+ rAX = tX;
+ tMat = bB.m_xf.R;
+ var rBX = this.m_localAnchorB.x - bB.m_sweep.localCenter.x;
+ var rBY = this.m_localAnchorB.y - bB.m_sweep.localCenter.y;
+ tX = (tMat.col1.x * rBX + tMat.col2.x * rBY);
+ rBY = (tMat.col1.y * rBX + tMat.col2.y * rBY);
+ rBX = tX;
+ var mA = bA.m_invMass;
+ var mB = bB.m_invMass;
+ var iA = bA.m_invI;
+ var iB = bB.m_invI;
+ var C1X = bB.m_sweep.c.x + rBX - bA.m_sweep.c.x - rAX;
+ var C1Y = bB.m_sweep.c.y + rBY - bA.m_sweep.c.y - rAY;
+ var C2 = bB.m_sweep.a - bA.m_sweep.a - this.m_referenceAngle;
+ var k_allowedStretch = 10.0 * b2Settings.b2_linearSlop;
+ var positionError = Math.sqrt(C1X * C1X + C1Y * C1Y);
+ var angularError = b2Math.Abs(C2);
+ if (positionError > k_allowedStretch) {
+ iA *= 1.0;
+ iB *= 1.0;
+ }
+ this.m_mass.col1.x = mA + mB + rAY * rAY * iA + rBY * rBY * iB;
+ this.m_mass.col2.x = (-rAY * rAX * iA) - rBY * rBX * iB;
+ this.m_mass.col3.x = (-rAY * iA) - rBY * iB;
+ this.m_mass.col1.y = this.m_mass.col2.x;
+ this.m_mass.col2.y = mA + mB + rAX * rAX * iA + rBX * rBX * iB;
+ this.m_mass.col3.y = rAX * iA + rBX * iB;
+ this.m_mass.col1.z = this.m_mass.col3.x;
+ this.m_mass.col2.z = this.m_mass.col3.y;
+ this.m_mass.col3.z = iA + iB;
+ var impulse = new b2Vec3();
+ this.m_mass.Solve33(impulse, (-C1X), (-C1Y), (-C2));
+ bA.m_sweep.c.x -= mA * impulse.x;
+ bA.m_sweep.c.y -= mA * impulse.y;
+ bA.m_sweep.a -= iA * (rAX * impulse.y - rAY * impulse.x + impulse.z);
+ bB.m_sweep.c.x += mB * impulse.x;
+ bB.m_sweep.c.y += mB * impulse.y;
+ bB.m_sweep.a += iB * (rBX * impulse.y - rBY * impulse.x + impulse.z);
+ bA.SynchronizeTransform();
+ bB.SynchronizeTransform();
+ return positionError <= b2Settings.b2_linearSlop && angularError <= b2Settings.b2_angularSlop;
+ }
+ Box2D.inherit(b2WeldJointDef, Box2D.Dynamics.Joints.b2JointDef);
+ b2WeldJointDef.prototype.__super = Box2D.Dynamics.Joints.b2JointDef.prototype;
+ b2WeldJointDef.b2WeldJointDef = function () {
+ Box2D.Dynamics.Joints.b2JointDef.b2JointDef.apply(this, arguments);
+ this.localAnchorA = new b2Vec2();
+ this.localAnchorB = new b2Vec2();
+ };
+ b2WeldJointDef.prototype.b2WeldJointDef = function () {
+ this.__super.b2JointDef.call(this);
+ this.type = b2Joint.e_weldJoint;
+ this.referenceAngle = 0.0;
+ }
+ b2WeldJointDef.prototype.Initialize = function (bA, bB, anchor) {
+ this.bodyA = bA;
+ this.bodyB = bB;
+ this.localAnchorA.SetV(this.bodyA.GetLocalPoint(anchor));
+ this.localAnchorB.SetV(this.bodyB.GetLocalPoint(anchor));
+ this.referenceAngle = this.bodyB.GetAngle() - this.bodyA.GetAngle();
+ }
+})();
+(function () {
+ var b2DebugDraw = Box2D.Dynamics.b2DebugDraw;
+ b2DebugDraw.b2DebugDraw = function () {
+ this.m_drawScale = 1.0;
+ this.m_lineThickness = 1.0;
+ this.m_alpha = 1.0;
+ this.m_fillAlpha = 1.0;
+ this.m_xformScale = 1.0;
+ var __this = this;
+ //#WORKAROUND
+ this.m_sprite = {
+ graphics: {
+ clear: function () {
+ __this.m_ctx.clearRect(0, 0, __this.m_ctx.canvas.width, __this.m_ctx.canvas.height)
+ }
+ }
+ };
+ };
+ b2DebugDraw.prototype._color = function (color, alpha) {
+ return "rgba(" + ((color & 0xFF0000) >> 16) + "," + ((color & 0xFF00) >> 8) + "," + (color & 0xFF) + "," + alpha + ")";
+ };
+ b2DebugDraw.prototype.b2DebugDraw = function () {
+ this.m_drawFlags = 0;
+ };
+ b2DebugDraw.prototype.SetFlags = function (flags) {
+ if (flags === undefined) flags = 0;
+ this.m_drawFlags = flags;
+ };
+ b2DebugDraw.prototype.GetFlags = function () {
+ return this.m_drawFlags;
+ };
+ b2DebugDraw.prototype.AppendFlags = function (flags) {
+ if (flags === undefined) flags = 0;
+ this.m_drawFlags |= flags;
+ };
+ b2DebugDraw.prototype.ClearFlags = function (flags) {
+ if (flags === undefined) flags = 0;
+ this.m_drawFlags &= ~flags;
+ };
+ b2DebugDraw.prototype.SetSprite = function (sprite) {
+ this.m_ctx = sprite;
+ };
+ b2DebugDraw.prototype.GetSprite = function () {
+ return this.m_ctx;
+ };
+ b2DebugDraw.prototype.SetDrawScale = function (drawScale) {
+ if (drawScale === undefined) drawScale = 0;
+ this.m_drawScale = drawScale;
+ };
+ b2DebugDraw.prototype.GetDrawScale = function () {
+ return this.m_drawScale;
+ };
+ b2DebugDraw.prototype.SetLineThickness = function (lineThickness) {
+ if (lineThickness === undefined) lineThickness = 0;
+ this.m_lineThickness = lineThickness;
+ this.m_ctx.strokeWidth = lineThickness;
+ };
+ b2DebugDraw.prototype.GetLineThickness = function () {
+ return this.m_lineThickness;
+ };
+ b2DebugDraw.prototype.SetAlpha = function (alpha) {
+ if (alpha === undefined) alpha = 0;
+ this.m_alpha = alpha;
+ };
+ b2DebugDraw.prototype.GetAlpha = function () {
+ return this.m_alpha;
+ };
+ b2DebugDraw.prototype.SetFillAlpha = function (alpha) {
+ if (alpha === undefined) alpha = 0;
+ this.m_fillAlpha = alpha;
+ };
+ b2DebugDraw.prototype.GetFillAlpha = function () {
+ return this.m_fillAlpha;
+ };
+ b2DebugDraw.prototype.SetXFormScale = function (xformScale) {
+ if (xformScale === undefined) xformScale = 0;
+ this.m_xformScale = xformScale;
+ };
+ b2DebugDraw.prototype.GetXFormScale = function () {
+ return this.m_xformScale;
+ };
+ b2DebugDraw.prototype.DrawPolygon = function (vertices, vertexCount, color) {
+ if (!vertexCount) return;
+ var s = this.m_ctx;
+ var drawScale = this.m_drawScale;
+ s.beginPath();
+ s.strokeStyle = this._color(color.color, this.m_alpha);
+ s.moveTo(vertices[0].x * drawScale, vertices[0].y * drawScale);
+ for (var i = 1; i < vertexCount; i++) {
+ s.lineTo(vertices[i].x * drawScale, vertices[i].y * drawScale);
+ }
+ s.lineTo(vertices[0].x * drawScale, vertices[0].y * drawScale);
+ s.closePath();
+ s.stroke();
+ };
+ b2DebugDraw.prototype.DrawSolidPolygon = function (vertices, vertexCount, color) {
+ if (!vertexCount) return;
+ var s = this.m_ctx;
+ var drawScale = this.m_drawScale;
+ s.beginPath();
+ s.strokeStyle = this._color(color.color, this.m_alpha);
+ s.fillStyle = this._color(color.color, this.m_fillAlpha);
+ s.moveTo(vertices[0].x * drawScale, vertices[0].y * drawScale);
+ for (var i = 1; i < vertexCount; i++) {
+ s.lineTo(vertices[i].x * drawScale, vertices[i].y * drawScale);
+ }
+ s.lineTo(vertices[0].x * drawScale, vertices[0].y * drawScale);
+ s.closePath();
+ s.fill();
+ s.stroke();
+ };
+ b2DebugDraw.prototype.DrawCircle = function (center, radius, color) {
+ if (!radius) return;
+ var s = this.m_ctx;
+ var drawScale = this.m_drawScale;
+ s.beginPath();
+ s.strokeStyle = this._color(color.color, this.m_alpha);
+ s.arc(center.x * drawScale, center.y * drawScale, radius * drawScale, 0, Math.PI * 2, true);
+ s.closePath();
+ s.stroke();
+ };
+ b2DebugDraw.prototype.DrawSolidCircle = function (center, radius, axis, color) {
+ if (!radius) return;
+ var s = this.m_ctx,
+ drawScale = this.m_drawScale,
+ cx = center.x * drawScale,
+ cy = center.y * drawScale;
+ s.moveTo(0, 0);
+ s.beginPath();
+ s.strokeStyle = this._color(color.color, this.m_alpha);
+ s.fillStyle = this._color(color.color, this.m_fillAlpha);
+ s.arc(cx, cy, radius * drawScale, 0, Math.PI * 2, true);
+ s.moveTo(cx, cy);
+ s.lineTo((center.x + axis.x * radius) * drawScale, (center.y + axis.y * radius) * drawScale);
+ s.closePath();
+ s.fill();
+ s.stroke();
+ };
+ b2DebugDraw.prototype.DrawSegment = function (p1, p2, color) {
+ var s = this.m_ctx,
+ drawScale = this.m_drawScale;
+ s.strokeStyle = this._color(color.color, this.m_alpha);
+ s.beginPath();
+ s.moveTo(p1.x * drawScale, p1.y * drawScale);
+ s.lineTo(p2.x * drawScale, p2.y * drawScale);
+ s.closePath();
+ s.stroke();
+ };
+ b2DebugDraw.prototype.DrawTransform = function (xf) {
+ var s = this.m_ctx,
+ drawScale = this.m_drawScale;
+ s.beginPath();
+ s.strokeStyle = this._color(0xff0000, this.m_alpha);
+ s.moveTo(xf.position.x * drawScale, xf.position.y * drawScale);
+ s.lineTo((xf.position.x + this.m_xformScale * xf.R.col1.x) * drawScale, (xf.position.y + this.m_xformScale * xf.R.col1.y) * drawScale);
+
+ s.strokeStyle = this._color(0xff00, this.m_alpha);
+ s.moveTo(xf.position.x * drawScale, xf.position.y * drawScale);
+ s.lineTo((xf.position.x + this.m_xformScale * xf.R.col2.x) * drawScale, (xf.position.y + this.m_xformScale * xf.R.col2.y) * drawScale);
+ s.closePath();
+ s.stroke();
+ };
+})(); //post-definitions
+var i;
+for (i = 0; i < Box2D.postDefs.length; ++i) Box2D.postDefs[i]();
+delete Box2D.postDefs;
diff --git a/vendor/scripts/backbone-mediator.js b/vendor/scripts/backbone-mediator.js
new file mode 100644
index 000000000..8e216e5f6
--- /dev/null
+++ b/vendor/scripts/backbone-mediator.js
@@ -0,0 +1,203 @@
+/**
+ * |-------------------|
+ * | Backbone-Mediator |
+ * |-------------------|
+ * Backbone-Mediator is freely distributable under the MIT license.
+ *
+ * More details & documentation
+ *
+ * @author Nicolas Gilbert
+ *
+ * @requires _
+ * @requires Backbone
+ */
+(function(factory){
+ 'use strict';
+
+ if (typeof define === 'function' && define.amd) {
+ define(['underscore', 'backbone'], factory);
+ } else {
+ factory(_, Backbone);
+ }
+
+})(function (_, Backbone){
+ 'use strict';
+
+ /**
+ * @static
+ */
+ var channels = {},
+ Subscriber,
+ /** @borrows Backbone.View#delegateEvents */
+ delegateEvents = Backbone.View.prototype.delegateEvents,
+ /** @borrows Backbone.View#delegateEvents */
+ undelegateEvents = Backbone.View.prototype.undelegateEvents;
+
+ /**
+ * @class
+ */
+ Backbone.Mediator = {
+
+ /**
+ * Subscribe to a channel
+ *
+ * @param channel
+ */
+ subscribe: function(channel, subscription, context, once) {
+ if (!channels[channel]) channels[channel] = [];
+ channels[channel].push({fn: subscription, context: context || this, once: once});
+ },
+
+ /**
+ * Trigger all callbacks for a channel
+ *
+ * @param channel
+ * @params N Extra parametter to pass to handler
+ */
+ publish: function(channel) {
+ if (!channels[channel]) return;
+
+ var args = [].slice.call(arguments, 1),
+ subscription;
+
+ for (var i = 0; i < channels[channel].length; i++) {
+ subscription = channels[channel][i];
+ subscription.fn.apply(subscription.context, args);
+ if (subscription.once) {
+ Backbone.Mediator.unsubscribe(channel, subscription.fn, subscription.context);
+ i--;
+ }
+ }
+ },
+
+ /**
+ * Cancel subscription
+ *
+ * @param channel
+ * @param fn
+ * @param context
+ */
+
+ unsubscribe: function(channel, fn, context){
+ if (!channels[channel]) return;
+
+ var subscription;
+ for (var i = 0; i < channels[channel].length; i++) {
+ subscription = channels[channel][i];
+ if (subscription.fn === fn && subscription.context === context) {
+ channels[channel].splice(i, 1);
+ i--;
+ }
+ }
+ },
+
+ /**
+ * Subscribing to one event only
+ *
+ * @param channel
+ * @param subscription
+ * @param context
+ */
+ subscribeOnce: function (channel, subscription, context) {
+ Backbone.Mediator.subscribe(channel, subscription, context, true);
+ }
+
+ };
+
+ Backbone.Mediator.channels = channels;
+
+ /**
+ * Allow to define convention-based subscriptions
+ * as an 'subscriptions' hash on a view. Subscriptions
+ * can then be easily setup and cleaned.
+ *
+ * @class
+ */
+
+
+ Subscriber = {
+
+ /**
+ * Extend delegateEvents() to set subscriptions
+ */
+ delegateEvents: function(){
+ delegateEvents.apply(this, arguments);
+ this.setSubscriptions();
+ },
+
+ /**
+ * Extend undelegateEvents() to unset subscriptions
+ */
+ undelegateEvents: function(){
+ undelegateEvents.apply(this, arguments);
+ this.unsetSubscriptions();
+ },
+
+ /** @property {Object} List of subscriptions, to be defined */
+ subscriptions: {},
+
+ /**
+ * Subscribe to each subscription
+ * @param {Object} [subscriptions] An optional hash of subscription to add
+ */
+
+ setSubscriptions: function(subscriptions){
+ if (subscriptions) _.extend(this.subscriptions || {}, subscriptions);
+ subscriptions = subscriptions || this.subscriptions;
+ if (!subscriptions || _.isEmpty(subscriptions)) return;
+ // Just to be sure we don't set duplicate
+ this.unsetSubscriptions(subscriptions);
+
+ _.each(subscriptions, function(subscription, channel){
+ var once;
+ if (subscription.$once) {
+ subscription = subscription.$once;
+ once = true;
+ }
+ if (_.isString(subscription)) {
+ subscription = this[subscription];
+ }
+ Backbone.Mediator.subscribe(channel, subscription, this, once);
+ }, this);
+ },
+
+ /**
+ * Unsubscribe to each subscription
+ * @param {Object} [subscriptions] An optional hash of subscription to remove
+ */
+ unsetSubscriptions: function(subscriptions){
+ subscriptions = subscriptions || this.subscriptions;
+ if (!subscriptions || _.isEmpty(subscriptions)) return;
+ _.each(subscriptions, function(subscription, channel){
+ if (_.isString(subscription)) {
+ subscription = this[subscription];
+ }
+ Backbone.Mediator.unsubscribe(channel, subscription.$once || subscription, this);
+ }, this);
+ }
+ };
+
+ /**
+ * @lends Backbone.View.prototype
+ */
+ _.extend(Backbone.View.prototype, Subscriber);
+
+ /**
+ * @lends Backbone.Mediator
+ */
+ _.extend(Backbone.Mediator, {
+ /**
+ * Shortcut for publish
+ * @function
+ */
+ pub: Backbone.Mediator.publish,
+ /**
+ * Shortcut for subscribe
+ * @function
+ */
+ sub: Backbone.Mediator.subscribe
+ });
+
+ return Backbone;
+
+});
\ No newline at end of file
diff --git a/vendor/scripts/bootstrap/bootstrap-affix.js b/vendor/scripts/bootstrap/bootstrap-affix.js
new file mode 100644
index 000000000..827ff458e
--- /dev/null
+++ b/vendor/scripts/bootstrap/bootstrap-affix.js
@@ -0,0 +1,117 @@
+/* ==========================================================
+ * bootstrap-affix.js v2.3.1
+ * http://twitter.github.com/bootstrap/javascript.html#affix
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* AFFIX CLASS DEFINITION
+ * ====================== */
+
+ var Affix = function (element, options) {
+ this.options = $.extend({}, $.fn.affix.defaults, options)
+ this.$window = $(window)
+ .on('scroll.affix.data-api', $.proxy(this.checkPosition, this))
+ .on('click.affix.data-api', $.proxy(function () { setTimeout($.proxy(this.checkPosition, this), 1) }, this))
+ this.$element = $(element)
+ this.checkPosition()
+ }
+
+ Affix.prototype.checkPosition = function () {
+ if (!this.$element.is(':visible')) return
+
+ var scrollHeight = $(document).height()
+ , scrollTop = this.$window.scrollTop()
+ , position = this.$element.offset()
+ , offset = this.options.offset
+ , offsetBottom = offset.bottom
+ , offsetTop = offset.top
+ , reset = 'affix affix-top affix-bottom'
+ , affix
+
+ if (typeof offset != 'object') offsetBottom = offsetTop = offset
+ if (typeof offsetTop == 'function') offsetTop = offset.top()
+ if (typeof offsetBottom == 'function') offsetBottom = offset.bottom()
+
+ affix = this.unpin != null && (scrollTop + this.unpin <= position.top) ?
+ false : offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ?
+ 'bottom' : offsetTop != null && scrollTop <= offsetTop ?
+ 'top' : false
+
+ if (this.affixed === affix) return
+
+ this.affixed = affix
+ this.unpin = affix == 'bottom' ? position.top - scrollTop : null
+
+ this.$element.removeClass(reset).addClass('affix' + (affix ? '-' + affix : ''))
+ }
+
+
+ /* AFFIX PLUGIN DEFINITION
+ * ======================= */
+
+ var old = $.fn.affix
+
+ $.fn.affix = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('affix')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('affix', (data = new Affix(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.affix.Constructor = Affix
+
+ $.fn.affix.defaults = {
+ offset: 0
+ }
+
+
+ /* AFFIX NO CONFLICT
+ * ================= */
+
+ $.fn.affix.noConflict = function () {
+ $.fn.affix = old
+ return this
+ }
+
+
+ /* AFFIX DATA-API
+ * ============== */
+
+ $(window).on('load', function () {
+ $('[data-spy="affix"]').each(function () {
+ var $spy = $(this)
+ , data = $spy.data()
+
+ data.offset = data.offset || {}
+
+ data.offsetBottom && (data.offset.bottom = data.offsetBottom)
+ data.offsetTop && (data.offset.top = data.offsetTop)
+
+ $spy.affix(data)
+ })
+ })
+
+
+}(window.jQuery);
\ No newline at end of file
diff --git a/vendor/scripts/bootstrap/bootstrap-alert.js b/vendor/scripts/bootstrap/bootstrap-alert.js
new file mode 100644
index 000000000..8917f9490
--- /dev/null
+++ b/vendor/scripts/bootstrap/bootstrap-alert.js
@@ -0,0 +1,99 @@
+/* ==========================================================
+ * bootstrap-alert.js v2.3.1
+ * http://twitter.github.com/bootstrap/javascript.html#alerts
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* ALERT CLASS DEFINITION
+ * ====================== */
+
+ var dismiss = '[data-dismiss="alert"]'
+ , Alert = function (el) {
+ $(el).on('click', dismiss, this.close)
+ }
+
+ Alert.prototype.close = function (e) {
+ var $this = $(this)
+ , selector = $this.attr('data-target')
+ , $parent
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+ }
+
+ $parent = $(selector)
+
+ e && e.preventDefault()
+
+ $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent())
+
+ $parent.trigger(e = $.Event('close'))
+
+ if (e.isDefaultPrevented()) return
+
+ $parent.removeClass('in')
+
+ function removeElement() {
+ $parent
+ .trigger('closed')
+ .remove()
+ }
+
+ $.support.transition && $parent.hasClass('fade') ?
+ $parent.on($.support.transition.end, removeElement) :
+ removeElement()
+ }
+
+
+ /* ALERT PLUGIN DEFINITION
+ * ======================= */
+
+ var old = $.fn.alert
+
+ $.fn.alert = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('alert')
+ if (!data) $this.data('alert', (data = new Alert(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ $.fn.alert.Constructor = Alert
+
+
+ /* ALERT NO CONFLICT
+ * ================= */
+
+ $.fn.alert.noConflict = function () {
+ $.fn.alert = old
+ return this
+ }
+
+
+ /* ALERT DATA-API
+ * ============== */
+
+ $(document).on('click.alert.data-api', dismiss, Alert.prototype.close)
+
+}(window.jQuery);
\ No newline at end of file
diff --git a/vendor/scripts/bootstrap/bootstrap-button.js b/vendor/scripts/bootstrap/bootstrap-button.js
new file mode 100644
index 000000000..66df0a296
--- /dev/null
+++ b/vendor/scripts/bootstrap/bootstrap-button.js
@@ -0,0 +1,105 @@
+/* ============================================================
+ * bootstrap-button.js v2.3.1
+ * http://twitter.github.com/bootstrap/javascript.html#buttons
+ * ============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* BUTTON PUBLIC CLASS DEFINITION
+ * ============================== */
+
+ var Button = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, $.fn.button.defaults, options)
+ }
+
+ Button.prototype.setState = function (state) {
+ var d = 'disabled'
+ , $el = this.$element
+ , data = $el.data()
+ , val = $el.is('input') ? 'val' : 'html'
+
+ state = state + 'Text'
+ data.resetText || $el.data('resetText', $el[val]())
+
+ $el[val](data[state] || this.options[state])
+
+ // push to event loop to allow forms to submit
+ setTimeout(function () {
+ state == 'loadingText' ?
+ $el.addClass(d).attr(d, d) :
+ $el.removeClass(d).removeAttr(d)
+ }, 0)
+ }
+
+ Button.prototype.toggle = function () {
+ var $parent = this.$element.closest('[data-toggle="buttons-radio"]')
+
+ $parent && $parent
+ .find('.active')
+ .removeClass('active')
+
+ this.$element.toggleClass('active')
+ }
+
+
+ /* BUTTON PLUGIN DEFINITION
+ * ======================== */
+
+ var old = $.fn.button
+
+ $.fn.button = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('button')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('button', (data = new Button(this, options)))
+ if (option == 'toggle') data.toggle()
+ else if (option) data.setState(option)
+ })
+ }
+
+ $.fn.button.defaults = {
+ loadingText: 'loading...'
+ }
+
+ $.fn.button.Constructor = Button
+
+
+ /* BUTTON NO CONFLICT
+ * ================== */
+
+ $.fn.button.noConflict = function () {
+ $.fn.button = old
+ return this
+ }
+
+
+ /* BUTTON DATA-API
+ * =============== */
+
+ $(document).on('click.button.data-api', '[data-toggle^=button]', function (e) {
+ var $btn = $(e.target)
+ if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
+ $btn.button('toggle')
+ })
+
+}(window.jQuery);
\ No newline at end of file
diff --git a/vendor/scripts/bootstrap/bootstrap-carousel.js b/vendor/scripts/bootstrap/bootstrap-carousel.js
new file mode 100644
index 000000000..b40edd7bf
--- /dev/null
+++ b/vendor/scripts/bootstrap/bootstrap-carousel.js
@@ -0,0 +1,207 @@
+/* ==========================================================
+ * bootstrap-carousel.js v2.3.1
+ * http://twitter.github.com/bootstrap/javascript.html#carousel
+ * ==========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* CAROUSEL CLASS DEFINITION
+ * ========================= */
+
+ var Carousel = function (element, options) {
+ this.$element = $(element)
+ this.$indicators = this.$element.find('.carousel-indicators')
+ this.options = options
+ this.options.pause == 'hover' && this.$element
+ .on('mouseenter', $.proxy(this.pause, this))
+ .on('mouseleave', $.proxy(this.cycle, this))
+ }
+
+ Carousel.prototype = {
+
+ cycle: function (e) {
+ if (!e) this.paused = false
+ if (this.interval) clearInterval(this.interval);
+ this.options.interval
+ && !this.paused
+ && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
+ return this
+ }
+
+ , getActiveIndex: function () {
+ this.$active = this.$element.find('.item.active')
+ this.$items = this.$active.parent().children()
+ return this.$items.index(this.$active)
+ }
+
+ , to: function (pos) {
+ var activeIndex = this.getActiveIndex()
+ , that = this
+
+ if (pos > (this.$items.length - 1) || pos < 0) return
+
+ if (this.sliding) {
+ return this.$element.one('slid', function () {
+ that.to(pos)
+ })
+ }
+
+ if (activeIndex == pos) {
+ return this.pause().cycle()
+ }
+
+ return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos]))
+ }
+
+ , pause: function (e) {
+ if (!e) this.paused = true
+ if (this.$element.find('.next, .prev').length && $.support.transition.end) {
+ this.$element.trigger($.support.transition.end)
+ this.cycle(true)
+ }
+ clearInterval(this.interval)
+ this.interval = null
+ return this
+ }
+
+ , next: function () {
+ if (this.sliding) return
+ return this.slide('next')
+ }
+
+ , prev: function () {
+ if (this.sliding) return
+ return this.slide('prev')
+ }
+
+ , slide: function (type, next) {
+ var $active = this.$element.find('.item.active')
+ , $next = next || $active[type]()
+ , isCycling = this.interval
+ , direction = type == 'next' ? 'left' : 'right'
+ , fallback = type == 'next' ? 'first' : 'last'
+ , that = this
+ , e
+
+ this.sliding = true
+
+ isCycling && this.pause()
+
+ $next = $next.length ? $next : this.$element.find('.item')[fallback]()
+
+ e = $.Event('slide', {
+ relatedTarget: $next[0]
+ , direction: direction
+ })
+
+ if ($next.hasClass('active')) return
+
+ if (this.$indicators.length) {
+ this.$indicators.find('.active').removeClass('active')
+ this.$element.one('slid', function () {
+ var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()])
+ $nextIndicator && $nextIndicator.addClass('active')
+ })
+ }
+
+ if ($.support.transition && this.$element.hasClass('slide')) {
+ this.$element.trigger(e)
+ if (e.isDefaultPrevented()) return
+ $next.addClass(type)
+ $next[0].offsetWidth // force reflow
+ $active.addClass(direction)
+ $next.addClass(direction)
+ this.$element.one($.support.transition.end, function () {
+ $next.removeClass([type, direction].join(' ')).addClass('active')
+ $active.removeClass(['active', direction].join(' '))
+ that.sliding = false
+ setTimeout(function () { that.$element.trigger('slid') }, 0)
+ })
+ } else {
+ this.$element.trigger(e)
+ if (e.isDefaultPrevented()) return
+ $active.removeClass('active')
+ $next.addClass('active')
+ this.sliding = false
+ this.$element.trigger('slid')
+ }
+
+ isCycling && this.cycle()
+
+ return this
+ }
+
+ }
+
+
+ /* CAROUSEL PLUGIN DEFINITION
+ * ========================== */
+
+ var old = $.fn.carousel
+
+ $.fn.carousel = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('carousel')
+ , options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option)
+ , action = typeof option == 'string' ? option : options.slide
+ if (!data) $this.data('carousel', (data = new Carousel(this, options)))
+ if (typeof option == 'number') data.to(option)
+ else if (action) data[action]()
+ else if (options.interval) data.pause().cycle()
+ })
+ }
+
+ $.fn.carousel.defaults = {
+ interval: 5000
+ , pause: 'hover'
+ }
+
+ $.fn.carousel.Constructor = Carousel
+
+
+ /* CAROUSEL NO CONFLICT
+ * ==================== */
+
+ $.fn.carousel.noConflict = function () {
+ $.fn.carousel = old
+ return this
+ }
+
+ /* CAROUSEL DATA-API
+ * ================= */
+
+ $(document).on('click.carousel.data-api', '[data-slide], [data-slide-to]', function (e) {
+ var $this = $(this), href
+ , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+ , options = $.extend({}, $target.data(), $this.data())
+ , slideIndex
+
+ $target.carousel(options)
+
+ if (slideIndex = $this.attr('data-slide-to')) {
+ $target.data('carousel').pause().to(slideIndex).cycle()
+ }
+
+ e.preventDefault()
+ })
+
+}(window.jQuery);
\ No newline at end of file
diff --git a/vendor/scripts/bootstrap/bootstrap-collapse.js b/vendor/scripts/bootstrap/bootstrap-collapse.js
new file mode 100644
index 000000000..2bede4a88
--- /dev/null
+++ b/vendor/scripts/bootstrap/bootstrap-collapse.js
@@ -0,0 +1,167 @@
+/* =============================================================
+ * bootstrap-collapse.js v2.3.1
+ * http://twitter.github.com/bootstrap/javascript.html#collapse
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* COLLAPSE PUBLIC CLASS DEFINITION
+ * ================================ */
+
+ var Collapse = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, $.fn.collapse.defaults, options)
+
+ if (this.options.parent) {
+ this.$parent = $(this.options.parent)
+ }
+
+ this.options.toggle && this.toggle()
+ }
+
+ Collapse.prototype = {
+
+ constructor: Collapse
+
+ , dimension: function () {
+ var hasWidth = this.$element.hasClass('width')
+ return hasWidth ? 'width' : 'height'
+ }
+
+ , show: function () {
+ var dimension
+ , scroll
+ , actives
+ , hasData
+
+ if (this.transitioning || this.$element.hasClass('in')) return
+
+ dimension = this.dimension()
+ scroll = $.camelCase(['scroll', dimension].join('-'))
+ actives = this.$parent && this.$parent.find('> .accordion-group > .in')
+
+ if (actives && actives.length) {
+ hasData = actives.data('collapse')
+ if (hasData && hasData.transitioning) return
+ actives.collapse('hide')
+ hasData || actives.data('collapse', null)
+ }
+
+ this.$element[dimension](0)
+ this.transition('addClass', $.Event('show'), 'shown')
+ $.support.transition && this.$element[dimension](this.$element[0][scroll])
+ }
+
+ , hide: function () {
+ var dimension
+ if (this.transitioning || !this.$element.hasClass('in')) return
+ dimension = this.dimension()
+ this.reset(this.$element[dimension]())
+ this.transition('removeClass', $.Event('hide'), 'hidden')
+ this.$element[dimension](0)
+ }
+
+ , reset: function (size) {
+ var dimension = this.dimension()
+
+ this.$element
+ .removeClass('collapse')
+ [dimension](size || 'auto')
+ [0].offsetWidth
+
+ this.$element[size !== null ? 'addClass' : 'removeClass']('collapse')
+
+ return this
+ }
+
+ , transition: function (method, startEvent, completeEvent) {
+ var that = this
+ , complete = function () {
+ if (startEvent.type == 'show') that.reset()
+ that.transitioning = 0
+ that.$element.trigger(completeEvent)
+ }
+
+ this.$element.trigger(startEvent)
+
+ if (startEvent.isDefaultPrevented()) return
+
+ this.transitioning = 1
+
+ this.$element[method]('in')
+
+ $.support.transition && this.$element.hasClass('collapse') ?
+ this.$element.one($.support.transition.end, complete) :
+ complete()
+ }
+
+ , toggle: function () {
+ this[this.$element.hasClass('in') ? 'hide' : 'show']()
+ }
+
+ }
+
+
+ /* COLLAPSE PLUGIN DEFINITION
+ * ========================== */
+
+ var old = $.fn.collapse
+
+ $.fn.collapse = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('collapse')
+ , options = $.extend({}, $.fn.collapse.defaults, $this.data(), typeof option == 'object' && option)
+ if (!data) $this.data('collapse', (data = new Collapse(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.collapse.defaults = {
+ toggle: true
+ }
+
+ $.fn.collapse.Constructor = Collapse
+
+
+ /* COLLAPSE NO CONFLICT
+ * ==================== */
+
+ $.fn.collapse.noConflict = function () {
+ $.fn.collapse = old
+ return this
+ }
+
+
+ /* COLLAPSE DATA-API
+ * ================= */
+
+ $(document).on('click.collapse.data-api', '[data-toggle=collapse]', function (e) {
+ var $this = $(this), href
+ , target = $this.attr('data-target')
+ || e.preventDefault()
+ || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
+ , option = $(target).data('collapse') ? 'toggle' : $this.data()
+ $this[$(target).hasClass('in') ? 'addClass' : 'removeClass']('collapsed')
+ $(target).collapse(option)
+ })
+
+}(window.jQuery);
\ No newline at end of file
diff --git a/vendor/scripts/bootstrap/bootstrap-dropdown.js b/vendor/scripts/bootstrap/bootstrap-dropdown.js
new file mode 100644
index 000000000..a1d51519f
--- /dev/null
+++ b/vendor/scripts/bootstrap/bootstrap-dropdown.js
@@ -0,0 +1,165 @@
+/* ============================================================
+ * bootstrap-dropdown.js v2.3.1
+ * http://twitter.github.com/bootstrap/javascript.html#dropdowns
+ * ============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* DROPDOWN CLASS DEFINITION
+ * ========================= */
+
+ var toggle = '[data-toggle=dropdown]'
+ , Dropdown = function (element) {
+ var $el = $(element).on('click.dropdown.data-api', this.toggle)
+ $('html').on('click.dropdown.data-api', function () {
+ $el.parent().removeClass('open')
+ })
+ }
+
+ Dropdown.prototype = {
+
+ constructor: Dropdown
+
+ , toggle: function (e) {
+ var $this = $(this)
+ , $parent
+ , isActive
+
+ if ($this.is('.disabled, :disabled')) return
+
+ $parent = getParent($this)
+
+ isActive = $parent.hasClass('open')
+
+ clearMenus()
+
+ if (!isActive) {
+ $parent.toggleClass('open')
+ }
+
+ $this.focus()
+
+ return false
+ }
+
+ , keydown: function (e) {
+ var $this
+ , $items
+ , $active
+ , $parent
+ , isActive
+ , index
+
+ if (!/(38|40|27)/.test(e.keyCode)) return
+
+ $this = $(this)
+
+ e.preventDefault()
+ e.stopPropagation()
+
+ if ($this.is('.disabled, :disabled')) return
+
+ $parent = getParent($this)
+
+ isActive = $parent.hasClass('open')
+
+ if (!isActive || (isActive && e.keyCode == 27)) {
+ if (e.which == 27) $parent.find(toggle).focus()
+ return $this.click()
+ }
+
+ $items = $('[role=menu] li:not(.divider):visible a', $parent)
+
+ if (!$items.length) return
+
+ index = $items.index($items.filter(':focus'))
+
+ if (e.keyCode == 38 && index > 0) index-- // up
+ if (e.keyCode == 40 && index < $items.length - 1) index++ // down
+ if (!~index) index = 0
+
+ $items
+ .eq(index)
+ .focus()
+ }
+
+ }
+
+ function clearMenus() {
+ $(toggle).each(function () {
+ getParent($(this)).removeClass('open')
+ })
+ }
+
+ function getParent($this) {
+ var selector = $this.attr('data-target')
+ , $parent
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && /#/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+ }
+
+ $parent = selector && $(selector)
+
+ if (!$parent || !$parent.length) $parent = $this.parent()
+
+ return $parent
+ }
+
+
+ /* DROPDOWN PLUGIN DEFINITION
+ * ========================== */
+
+ var old = $.fn.dropdown
+
+ $.fn.dropdown = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('dropdown')
+ if (!data) $this.data('dropdown', (data = new Dropdown(this)))
+ if (typeof option == 'string') data[option].call($this)
+ })
+ }
+
+ $.fn.dropdown.Constructor = Dropdown
+
+
+ /* DROPDOWN NO CONFLICT
+ * ==================== */
+
+ $.fn.dropdown.noConflict = function () {
+ $.fn.dropdown = old
+ return this
+ }
+
+
+ /* APPLY TO STANDARD DROPDOWN ELEMENTS
+ * =================================== */
+
+ $(document)
+ .on('click.dropdown.data-api', clearMenus)
+ .on('click.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
+ .on('click.dropdown-menu', function (e) { e.stopPropagation() })
+ .on('click.dropdown.data-api' , toggle, Dropdown.prototype.toggle)
+ .on('keydown.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown)
+
+}(window.jQuery);
diff --git a/vendor/scripts/bootstrap/bootstrap-modal.js b/vendor/scripts/bootstrap/bootstrap-modal.js
new file mode 100644
index 000000000..12abe06f1
--- /dev/null
+++ b/vendor/scripts/bootstrap/bootstrap-modal.js
@@ -0,0 +1,247 @@
+/* =========================================================
+ * bootstrap-modal.js v2.3.1
+ * http://twitter.github.com/bootstrap/javascript.html#modals
+ * =========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================= */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* MODAL CLASS DEFINITION
+ * ====================== */
+
+ var Modal = function (element, options) {
+ this.options = options
+ this.$element = $(element)
+ .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this))
+ this.options.remote && this.$element.find('.modal-body').load(this.options.remote)
+ }
+
+ Modal.prototype = {
+
+ constructor: Modal
+
+ , toggle: function () {
+ return this[!this.isShown ? 'show' : 'hide']()
+ }
+
+ , show: function () {
+ var that = this
+ , e = $.Event('show')
+
+ this.$element.trigger(e)
+
+ if (this.isShown || e.isDefaultPrevented()) return
+
+ this.isShown = true
+
+ this.escape()
+
+ this.backdrop(function () {
+ var transition = $.support.transition && that.$element.hasClass('fade')
+
+ if (!that.$element.parent().length) {
+ that.$element.appendTo(document.body) //don't move modals dom position
+ }
+
+ that.$element.show()
+
+ if (transition) {
+ that.$element[0].offsetWidth // force reflow
+ }
+
+ that.$element
+ .addClass('in')
+ .attr('aria-hidden', false)
+
+ that.enforceFocus()
+
+ transition ?
+ that.$element.one($.support.transition.end, function () { that.$element.focus().trigger('shown') }) :
+ that.$element.focus().trigger('shown')
+
+ })
+ }
+
+ , hide: function (e) {
+ e && e.preventDefault()
+
+ var that = this
+
+ e = $.Event('hide')
+
+ this.$element.trigger(e)
+
+ if (!this.isShown || e.isDefaultPrevented()) return
+
+ this.isShown = false
+
+ this.escape()
+
+ $(document).off('focusin.modal')
+
+ this.$element
+ .removeClass('in')
+ .attr('aria-hidden', true)
+
+ $.support.transition && this.$element.hasClass('fade') ?
+ this.hideWithTransition() :
+ this.hideModal()
+ }
+
+ , enforceFocus: function () {
+ var that = this
+ $(document).on('focusin.modal', function (e) {
+ if (that.$element[0] !== e.target && !that.$element.has(e.target).length) {
+ that.$element.focus()
+ }
+ })
+ }
+
+ , escape: function () {
+ var that = this
+ if (this.isShown && this.options.keyboard) {
+ this.$element.on('keyup.dismiss.modal', function ( e ) {
+ e.which == 27 && that.hide()
+ })
+ } else if (!this.isShown) {
+ this.$element.off('keyup.dismiss.modal')
+ }
+ }
+
+ , hideWithTransition: function () {
+ var that = this
+ , timeout = setTimeout(function () {
+ that.$element.off($.support.transition.end)
+ that.hideModal()
+ }, 500)
+
+ this.$element.one($.support.transition.end, function () {
+ clearTimeout(timeout)
+ that.hideModal()
+ })
+ }
+
+ , hideModal: function () {
+ var that = this
+ this.$element.hide()
+ this.backdrop(function () {
+ that.removeBackdrop()
+ that.$element.trigger('hidden')
+ })
+ }
+
+ , removeBackdrop: function () {
+ this.$backdrop && this.$backdrop.remove()
+ this.$backdrop = null
+ }
+
+ , backdrop: function (callback) {
+ var that = this
+ , animate = this.$element.hasClass('fade') ? 'fade' : ''
+
+ if (this.isShown && this.options.backdrop) {
+ var doAnimate = $.support.transition && animate
+
+ this.$backdrop = $('')
+ .appendTo(document.body)
+
+ this.$backdrop.click(
+ this.options.backdrop == 'static' ?
+ $.proxy(this.$element[0].focus, this.$element[0])
+ : $.proxy(this.hide, this)
+ )
+
+ if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
+
+ this.$backdrop.addClass('in')
+
+ if (!callback) return
+
+ doAnimate ?
+ this.$backdrop.one($.support.transition.end, callback) :
+ callback()
+
+ } else if (!this.isShown && this.$backdrop) {
+ this.$backdrop.removeClass('in')
+
+ $.support.transition && this.$element.hasClass('fade')?
+ this.$backdrop.one($.support.transition.end, callback) :
+ callback()
+
+ } else if (callback) {
+ callback()
+ }
+ }
+ }
+
+
+ /* MODAL PLUGIN DEFINITION
+ * ======================= */
+
+ var old = $.fn.modal
+
+ $.fn.modal = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('modal')
+ , options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option)
+ if (!data) $this.data('modal', (data = new Modal(this, options)))
+ if (typeof option == 'string') data[option]()
+ else if (options.show) data.show()
+ })
+ }
+
+ $.fn.modal.defaults = {
+ backdrop: true
+ , keyboard: true
+ , show: true
+ }
+
+ $.fn.modal.Constructor = Modal
+
+
+ /* MODAL NO CONFLICT
+ * ================= */
+
+ $.fn.modal.noConflict = function () {
+ $.fn.modal = old
+ return this
+ }
+
+
+ /* MODAL DATA-API
+ * ============== */
+
+ $(document).on('click.modal.data-api', '[data-toggle="modal"]', function (e) {
+ var $this = $(this)
+ , href = $this.attr('href')
+ , $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) //strip for ie7
+ , option = $target.data('modal') ? 'toggle' : $.extend({ remote:!/#/.test(href) && href }, $target.data(), $this.data())
+
+ e.preventDefault()
+
+ $target
+ .modal(option)
+ .one('hide', function () {
+ $this.focus()
+ })
+ })
+
+}(window.jQuery);
diff --git a/vendor/scripts/bootstrap/bootstrap-popover.js b/vendor/scripts/bootstrap/bootstrap-popover.js
new file mode 100644
index 000000000..e6d897cd3
--- /dev/null
+++ b/vendor/scripts/bootstrap/bootstrap-popover.js
@@ -0,0 +1,114 @@
+/* ===========================================================
+ * bootstrap-popover.js v2.3.1
+ * http://twitter.github.com/bootstrap/javascript.html#popovers
+ * ===========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * =========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* POPOVER PUBLIC CLASS DEFINITION
+ * =============================== */
+
+ var Popover = function (element, options) {
+ this.init('popover', element, options)
+ }
+
+
+ /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js
+ ========================================== */
+
+ Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, {
+
+ constructor: Popover
+
+ , setContent: function () {
+ var $tip = this.tip()
+ , title = this.getTitle()
+ , content = this.getContent()
+
+ $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
+ $tip.find('.popover-content')[this.options.html ? 'html' : 'text'](content)
+
+ $tip.removeClass('fade top bottom left right in')
+ }
+
+ , hasContent: function () {
+ return this.getTitle() || this.getContent()
+ }
+
+ , getContent: function () {
+ var content
+ , $e = this.$element
+ , o = this.options
+
+ content = (typeof o.content == 'function' ? o.content.call($e[0]) : o.content)
+ || $e.attr('data-content')
+
+ return content
+ }
+
+ , tip: function () {
+ if (!this.$tip) {
+ this.$tip = $(this.options.template)
+ }
+ return this.$tip
+ }
+
+ , destroy: function () {
+ this.hide().$element.off('.' + this.type).removeData(this.type)
+ }
+
+ })
+
+
+ /* POPOVER PLUGIN DEFINITION
+ * ======================= */
+
+ var old = $.fn.popover
+
+ $.fn.popover = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('popover')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('popover', (data = new Popover(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.popover.Constructor = Popover
+
+ $.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, {
+ placement: 'right'
+ , trigger: 'click'
+ , content: ''
+ , template: ''
+ })
+
+
+ /* POPOVER NO CONFLICT
+ * =================== */
+
+ $.fn.popover.noConflict = function () {
+ $.fn.popover = old
+ return this
+ }
+
+}(window.jQuery);
diff --git a/vendor/scripts/bootstrap/bootstrap-scrollspy.js b/vendor/scripts/bootstrap/bootstrap-scrollspy.js
new file mode 100644
index 000000000..ac1402b4b
--- /dev/null
+++ b/vendor/scripts/bootstrap/bootstrap-scrollspy.js
@@ -0,0 +1,162 @@
+/* =============================================================
+ * bootstrap-scrollspy.js v2.3.1
+ * http://twitter.github.com/bootstrap/javascript.html#scrollspy
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* SCROLLSPY CLASS DEFINITION
+ * ========================== */
+
+ function ScrollSpy(element, options) {
+ var process = $.proxy(this.process, this)
+ , $element = $(element).is('body') ? $(window) : $(element)
+ , href
+ this.options = $.extend({}, $.fn.scrollspy.defaults, options)
+ this.$scrollElement = $element.on('scroll.scroll-spy.data-api', process)
+ this.selector = (this.options.target
+ || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
+ || '') + ' .nav li > a'
+ this.$body = $('body')
+ this.refresh()
+ this.process()
+ }
+
+ ScrollSpy.prototype = {
+
+ constructor: ScrollSpy
+
+ , refresh: function () {
+ var self = this
+ , $targets
+
+ this.offsets = $([])
+ this.targets = $([])
+
+ $targets = this.$body
+ .find(this.selector)
+ .map(function () {
+ var $el = $(this)
+ , href = $el.data('target') || $el.attr('href')
+ , $href = /^#\w/.test(href) && $(href)
+ return ( $href
+ && $href.length
+ && [[ $href.position().top + (!$.isWindow(self.$scrollElement.get(0)) && self.$scrollElement.scrollTop()), href ]] ) || null
+ })
+ .sort(function (a, b) { return a[0] - b[0] })
+ .each(function () {
+ self.offsets.push(this[0])
+ self.targets.push(this[1])
+ })
+ }
+
+ , process: function () {
+ var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
+ , scrollHeight = this.$scrollElement[0].scrollHeight || this.$body[0].scrollHeight
+ , maxScroll = scrollHeight - this.$scrollElement.height()
+ , offsets = this.offsets
+ , targets = this.targets
+ , activeTarget = this.activeTarget
+ , i
+
+ if (scrollTop >= maxScroll) {
+ return activeTarget != (i = targets.last()[0])
+ && this.activate ( i )
+ }
+
+ for (i = offsets.length; i--;) {
+ activeTarget != targets[i]
+ && scrollTop >= offsets[i]
+ && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
+ && this.activate( targets[i] )
+ }
+ }
+
+ , activate: function (target) {
+ var active
+ , selector
+
+ this.activeTarget = target
+
+ $(this.selector)
+ .parent('.active')
+ .removeClass('active')
+
+ selector = this.selector
+ + '[data-target="' + target + '"],'
+ + this.selector + '[href="' + target + '"]'
+
+ active = $(selector)
+ .parent('li')
+ .addClass('active')
+
+ if (active.parent('.dropdown-menu').length) {
+ active = active.closest('li.dropdown').addClass('active')
+ }
+
+ active.trigger('activate')
+ }
+
+ }
+
+
+ /* SCROLLSPY PLUGIN DEFINITION
+ * =========================== */
+
+ var old = $.fn.scrollspy
+
+ $.fn.scrollspy = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('scrollspy')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('scrollspy', (data = new ScrollSpy(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.scrollspy.Constructor = ScrollSpy
+
+ $.fn.scrollspy.defaults = {
+ offset: 10
+ }
+
+
+ /* SCROLLSPY NO CONFLICT
+ * ===================== */
+
+ $.fn.scrollspy.noConflict = function () {
+ $.fn.scrollspy = old
+ return this
+ }
+
+
+ /* SCROLLSPY DATA-API
+ * ================== */
+
+ $(window).on('load', function () {
+ $('[data-spy="scroll"]').each(function () {
+ var $spy = $(this)
+ $spy.scrollspy($spy.data())
+ })
+ })
+
+}(window.jQuery);
\ No newline at end of file
diff --git a/vendor/scripts/bootstrap/bootstrap-tab.js b/vendor/scripts/bootstrap/bootstrap-tab.js
new file mode 100644
index 000000000..1d23df6c6
--- /dev/null
+++ b/vendor/scripts/bootstrap/bootstrap-tab.js
@@ -0,0 +1,144 @@
+/* ========================================================
+ * bootstrap-tab.js v2.3.1
+ * http://twitter.github.com/bootstrap/javascript.html#tabs
+ * ========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ======================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* TAB CLASS DEFINITION
+ * ==================== */
+
+ var Tab = function (element) {
+ this.element = $(element)
+ }
+
+ Tab.prototype = {
+
+ constructor: Tab
+
+ , show: function () {
+ var $this = this.element
+ , $ul = $this.closest('ul:not(.dropdown-menu)')
+ , selector = $this.attr('data-target')
+ , previous
+ , $target
+ , e
+
+ if (!selector) {
+ selector = $this.attr('href')
+ selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
+ }
+
+ if ( $this.parent('li').hasClass('active') ) return
+
+ previous = $ul.find('.active:last a')[0]
+
+ e = $.Event('show', {
+ relatedTarget: previous
+ })
+
+ $this.trigger(e)
+
+ if (e.isDefaultPrevented()) return
+
+ $target = $(selector)
+
+ this.activate($this.parent('li'), $ul)
+ this.activate($target, $target.parent(), function () {
+ $this.trigger({
+ type: 'shown'
+ , relatedTarget: previous
+ })
+ })
+ }
+
+ , activate: function ( element, container, callback) {
+ var $active = container.find('> .active')
+ , transition = callback
+ && $.support.transition
+ && $active.hasClass('fade')
+
+ function next() {
+ $active
+ .removeClass('active')
+ .find('> .dropdown-menu > .active')
+ .removeClass('active')
+
+ element.addClass('active')
+
+ if (transition) {
+ element[0].offsetWidth // reflow for transition
+ element.addClass('in')
+ } else {
+ element.removeClass('fade')
+ }
+
+ if ( element.parent('.dropdown-menu') ) {
+ element.closest('li.dropdown').addClass('active')
+ }
+
+ callback && callback()
+ }
+
+ transition ?
+ $active.one($.support.transition.end, next) :
+ next()
+
+ $active.removeClass('in')
+ }
+ }
+
+
+ /* TAB PLUGIN DEFINITION
+ * ===================== */
+
+ var old = $.fn.tab
+
+ $.fn.tab = function ( option ) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('tab')
+ if (!data) $this.data('tab', (data = new Tab(this)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.tab.Constructor = Tab
+
+
+ /* TAB NO CONFLICT
+ * =============== */
+
+ $.fn.tab.noConflict = function () {
+ $.fn.tab = old
+ return this
+ }
+
+
+ /* TAB DATA-API
+ * ============ */
+
+ $(document).on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
+ e.preventDefault()
+ $(this).tab('show')
+ })
+
+}(window.jQuery);
\ No newline at end of file
diff --git a/vendor/scripts/bootstrap/bootstrap-tooltip.js b/vendor/scripts/bootstrap/bootstrap-tooltip.js
new file mode 100644
index 000000000..835abbe68
--- /dev/null
+++ b/vendor/scripts/bootstrap/bootstrap-tooltip.js
@@ -0,0 +1,361 @@
+/* ===========================================================
+ * bootstrap-tooltip.js v2.3.1
+ * http://twitter.github.com/bootstrap/javascript.html#tooltips
+ * Inspired by the original jQuery.tipsy by Jason Frame
+ * ===========================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* TOOLTIP PUBLIC CLASS DEFINITION
+ * =============================== */
+
+ var Tooltip = function (element, options) {
+ this.init('tooltip', element, options)
+ }
+
+ Tooltip.prototype = {
+
+ constructor: Tooltip
+
+ , init: function (type, element, options) {
+ var eventIn
+ , eventOut
+ , triggers
+ , trigger
+ , i
+
+ this.type = type
+ this.$element = $(element)
+ this.options = this.getOptions(options)
+ this.enabled = true
+
+ triggers = this.options.trigger.split(' ')
+
+ for (i = triggers.length; i--;) {
+ trigger = triggers[i]
+ if (trigger == 'click') {
+ this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
+ } else if (trigger != 'manual') {
+ eventIn = trigger == 'hover' ? 'mouseenter' : 'focus'
+ eventOut = trigger == 'hover' ? 'mouseleave' : 'blur'
+ this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
+ this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
+ }
+ }
+
+ this.options.selector ?
+ (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
+ this.fixTitle()
+ }
+
+ , getOptions: function (options) {
+ options = $.extend({}, $.fn[this.type].defaults, this.$element.data(), options)
+
+ if (options.delay && typeof options.delay == 'number') {
+ options.delay = {
+ show: options.delay
+ , hide: options.delay
+ }
+ }
+
+ return options
+ }
+
+ , enter: function (e) {
+ var defaults = $.fn[this.type].defaults
+ , options = {}
+ , self
+
+ this._options && $.each(this._options, function (key, value) {
+ if (defaults[key] != value) options[key] = value
+ }, this)
+
+ self = $(e.currentTarget)[this.type](options).data(this.type)
+
+ if (!self.options.delay || !self.options.delay.show) return self.show()
+
+ clearTimeout(this.timeout)
+ self.hoverState = 'in'
+ this.timeout = setTimeout(function() {
+ if (self.hoverState == 'in') self.show()
+ }, self.options.delay.show)
+ }
+
+ , leave: function (e) {
+ var self = $(e.currentTarget)[this.type](this._options).data(this.type)
+
+ if (this.timeout) clearTimeout(this.timeout)
+ if (!self.options.delay || !self.options.delay.hide) return self.hide()
+
+ self.hoverState = 'out'
+ this.timeout = setTimeout(function() {
+ if (self.hoverState == 'out') self.hide()
+ }, self.options.delay.hide)
+ }
+
+ , show: function () {
+ var $tip
+ , pos
+ , actualWidth
+ , actualHeight
+ , placement
+ , tp
+ , e = $.Event('show')
+
+ if (this.hasContent() && this.enabled) {
+ this.$element.trigger(e)
+ if (e.isDefaultPrevented()) return
+ $tip = this.tip()
+ this.setContent()
+
+ if (this.options.animation) {
+ $tip.addClass('fade')
+ }
+
+ placement = typeof this.options.placement == 'function' ?
+ this.options.placement.call(this, $tip[0], this.$element[0]) :
+ this.options.placement
+
+ $tip
+ .detach()
+ .css({ top: 0, left: 0, display: 'block' })
+
+ this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element)
+
+ pos = this.getPosition()
+
+ actualWidth = $tip[0].offsetWidth
+ actualHeight = $tip[0].offsetHeight
+
+ switch (placement) {
+ case 'bottom':
+ tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
+ break
+ case 'top':
+ tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
+ break
+ case 'left':
+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
+ break
+ case 'right':
+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
+ break
+ }
+
+ this.applyPlacement(tp, placement)
+ this.$element.trigger('shown')
+ }
+ }
+
+ , applyPlacement: function(offset, placement){
+ var $tip = this.tip()
+ , width = $tip[0].offsetWidth
+ , height = $tip[0].offsetHeight
+ , actualWidth
+ , actualHeight
+ , delta
+ , replace
+
+ $tip
+ .offset(offset)
+ .addClass(placement)
+ .addClass('in')
+
+ actualWidth = $tip[0].offsetWidth
+ actualHeight = $tip[0].offsetHeight
+
+ if (placement == 'top' && actualHeight != height) {
+ offset.top = offset.top + height - actualHeight
+ replace = true
+ }
+
+ if (placement == 'bottom' || placement == 'top') {
+ delta = 0
+
+ if (offset.left < 0){
+ delta = offset.left * -2
+ offset.left = 0
+ $tip.offset(offset)
+ actualWidth = $tip[0].offsetWidth
+ actualHeight = $tip[0].offsetHeight
+ }
+
+ this.replaceArrow(delta - width + actualWidth, actualWidth, 'left')
+ } else {
+ this.replaceArrow(actualHeight - height, actualHeight, 'top')
+ }
+
+ if (replace) $tip.offset(offset)
+ }
+
+ , replaceArrow: function(delta, dimension, position){
+ this
+ .arrow()
+ .css(position, delta ? (50 * (1 - delta / dimension) + "%") : '')
+ }
+
+ , setContent: function () {
+ var $tip = this.tip()
+ , title = this.getTitle()
+
+ $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
+ $tip.removeClass('fade in top bottom left right')
+ }
+
+ , hide: function () {
+ var that = this
+ , $tip = this.tip()
+ , e = $.Event('hide')
+
+ this.$element.trigger(e)
+ if (e.isDefaultPrevented()) return
+
+ $tip.removeClass('in')
+
+ function removeWithAnimation() {
+ var timeout = setTimeout(function () {
+ $tip.off($.support.transition.end).detach()
+ }, 500)
+
+ $tip.one($.support.transition.end, function () {
+ clearTimeout(timeout)
+ $tip.detach()
+ })
+ }
+
+ $.support.transition && this.$tip.hasClass('fade') ?
+ removeWithAnimation() :
+ $tip.detach()
+
+ this.$element.trigger('hidden')
+
+ return this
+ }
+
+ , fixTitle: function () {
+ var $e = this.$element
+ if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
+ $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
+ }
+ }
+
+ , hasContent: function () {
+ return this.getTitle()
+ }
+
+ , getPosition: function () {
+ var el = this.$element[0]
+ return $.extend({}, (typeof el.getBoundingClientRect == 'function') ? el.getBoundingClientRect() : {
+ width: el.offsetWidth
+ , height: el.offsetHeight
+ }, this.$element.offset())
+ }
+
+ , getTitle: function () {
+ var title
+ , $e = this.$element
+ , o = this.options
+
+ title = $e.attr('data-original-title')
+ || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
+
+ return title
+ }
+
+ , tip: function () {
+ return this.$tip = this.$tip || $(this.options.template)
+ }
+
+ , arrow: function(){
+ return this.$arrow = this.$arrow || this.tip().find(".tooltip-arrow")
+ }
+
+ , validate: function () {
+ if (!this.$element[0].parentNode) {
+ this.hide()
+ this.$element = null
+ this.options = null
+ }
+ }
+
+ , enable: function () {
+ this.enabled = true
+ }
+
+ , disable: function () {
+ this.enabled = false
+ }
+
+ , toggleEnabled: function () {
+ this.enabled = !this.enabled
+ }
+
+ , toggle: function (e) {
+ var self = e ? $(e.currentTarget)[this.type](this._options).data(this.type) : this
+ self.tip().hasClass('in') ? self.hide() : self.show()
+ }
+
+ , destroy: function () {
+ this.hide().$element.off('.' + this.type).removeData(this.type)
+ }
+
+ }
+
+
+ /* TOOLTIP PLUGIN DEFINITION
+ * ========================= */
+
+ var old = $.fn.tooltip
+
+ $.fn.tooltip = function ( option ) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('tooltip')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.tooltip.Constructor = Tooltip
+
+ $.fn.tooltip.defaults = {
+ animation: true
+ , placement: 'top'
+ , selector: false
+ , template: ''
+ , trigger: 'hover focus'
+ , title: ''
+ , delay: 0
+ , html: false
+ , container: false
+ }
+
+
+ /* TOOLTIP NO CONFLICT
+ * =================== */
+
+ $.fn.tooltip.noConflict = function () {
+ $.fn.tooltip = old
+ return this
+ }
+
+}(window.jQuery);
diff --git a/vendor/scripts/bootstrap/bootstrap-transition.js b/vendor/scripts/bootstrap/bootstrap-transition.js
new file mode 100644
index 000000000..92719d37e
--- /dev/null
+++ b/vendor/scripts/bootstrap/bootstrap-transition.js
@@ -0,0 +1,60 @@
+/* ===================================================
+ * bootstrap-transition.js v2.3.1
+ * http://twitter.github.com/bootstrap/javascript.html#transitions
+ * ===================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ========================================================== */
+
+
+!function ($) {
+
+ "use strict"; // jshint ;_;
+
+
+ /* CSS TRANSITION SUPPORT (http://www.modernizr.com/)
+ * ======================================================= */
+
+ $(function () {
+
+ $.support.transition = (function () {
+
+ var transitionEnd = (function () {
+
+ var el = document.createElement('bootstrap')
+ , transEndEventNames = {
+ 'WebkitTransition' : 'webkitTransitionEnd'
+ , 'MozTransition' : 'transitionend'
+ , 'OTransition' : 'oTransitionEnd otransitionend'
+ , 'transition' : 'transitionend'
+ }
+ , name
+
+ for (name in transEndEventNames){
+ if (el.style[name] !== undefined) {
+ return transEndEventNames[name]
+ }
+ }
+
+ }())
+
+ return transitionEnd && {
+ end: transitionEnd
+ }
+
+ })()
+
+ })
+
+}(window.jQuery);
\ No newline at end of file
diff --git a/vendor/scripts/bootstrap/bootstrap-typeahead.js b/vendor/scripts/bootstrap/bootstrap-typeahead.js
new file mode 100644
index 000000000..280cde8be
--- /dev/null
+++ b/vendor/scripts/bootstrap/bootstrap-typeahead.js
@@ -0,0 +1,335 @@
+/* =============================================================
+ * bootstrap-typeahead.js v2.3.1
+ * http://twitter.github.com/bootstrap/javascript.html#typeahead
+ * =============================================================
+ * Copyright 2012 Twitter, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============================================================ */
+
+
+!function($){
+
+ "use strict"; // jshint ;_;
+
+
+ /* TYPEAHEAD PUBLIC CLASS DEFINITION
+ * ================================= */
+
+ var Typeahead = function (element, options) {
+ this.$element = $(element)
+ this.options = $.extend({}, $.fn.typeahead.defaults, options)
+ this.matcher = this.options.matcher || this.matcher
+ this.sorter = this.options.sorter || this.sorter
+ this.highlighter = this.options.highlighter || this.highlighter
+ this.updater = this.options.updater || this.updater
+ this.source = this.options.source
+ this.$menu = $(this.options.menu)
+ this.shown = false
+ this.listen()
+ }
+
+ Typeahead.prototype = {
+
+ constructor: Typeahead
+
+ , select: function () {
+ var val = this.$menu.find('.active').attr('data-value')
+ this.$element
+ .val(this.updater(val))
+ .change()
+ return this.hide()
+ }
+
+ , updater: function (item) {
+ return item
+ }
+
+ , show: function () {
+ var pos = $.extend({}, this.$element.position(), {
+ height: this.$element[0].offsetHeight
+ })
+
+ this.$menu
+ .insertAfter(this.$element)
+ .css({
+ top: pos.top + pos.height
+ , left: pos.left
+ })
+ .show()
+
+ this.shown = true
+ return this
+ }
+
+ , hide: function () {
+ this.$menu.hide()
+ this.shown = false
+ return this
+ }
+
+ , lookup: function (event) {
+ var items
+
+ this.query = this.$element.val()
+
+ if (!this.query || this.query.length < this.options.minLength) {
+ return this.shown ? this.hide() : this
+ }
+
+ items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source
+
+ return items ? this.process(items) : this
+ }
+
+ , process: function (items) {
+ var that = this
+
+ items = $.grep(items, function (item) {
+ return that.matcher(item)
+ })
+
+ items = this.sorter(items)
+
+ if (!items.length) {
+ return this.shown ? this.hide() : this
+ }
+
+ return this.render(items.slice(0, this.options.items)).show()
+ }
+
+ , matcher: function (item) {
+ return ~item.toLowerCase().indexOf(this.query.toLowerCase())
+ }
+
+ , sorter: function (items) {
+ var beginswith = []
+ , caseSensitive = []
+ , caseInsensitive = []
+ , item
+
+ while (item = items.shift()) {
+ if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
+ else if (~item.indexOf(this.query)) caseSensitive.push(item)
+ else caseInsensitive.push(item)
+ }
+
+ return beginswith.concat(caseSensitive, caseInsensitive)
+ }
+
+ , highlighter: function (item) {
+ var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
+ return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
+ return '' + match + ''
+ })
+ }
+
+ , render: function (items) {
+ var that = this
+
+ items = $(items).map(function (i, item) {
+ i = $(that.options.item).attr('data-value', item)
+ i.find('a').html(that.highlighter(item))
+ return i[0]
+ })
+
+ items.first().addClass('active')
+ this.$menu.html(items)
+ return this
+ }
+
+ , next: function (event) {
+ var active = this.$menu.find('.active').removeClass('active')
+ , next = active.next()
+
+ if (!next.length) {
+ next = $(this.$menu.find('li')[0])
+ }
+
+ next.addClass('active')
+ }
+
+ , prev: function (event) {
+ var active = this.$menu.find('.active').removeClass('active')
+ , prev = active.prev()
+
+ if (!prev.length) {
+ prev = this.$menu.find('li').last()
+ }
+
+ prev.addClass('active')
+ }
+
+ , listen: function () {
+ this.$element
+ .on('focus', $.proxy(this.focus, this))
+ .on('blur', $.proxy(this.blur, this))
+ .on('keypress', $.proxy(this.keypress, this))
+ .on('keyup', $.proxy(this.keyup, this))
+
+ if (this.eventSupported('keydown')) {
+ this.$element.on('keydown', $.proxy(this.keydown, this))
+ }
+
+ this.$menu
+ .on('click', $.proxy(this.click, this))
+ .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
+ .on('mouseleave', 'li', $.proxy(this.mouseleave, this))
+ }
+
+ , eventSupported: function(eventName) {
+ var isSupported = eventName in this.$element
+ if (!isSupported) {
+ this.$element.setAttribute(eventName, 'return;')
+ isSupported = typeof this.$element[eventName] === 'function'
+ }
+ return isSupported
+ }
+
+ , move: function (e) {
+ if (!this.shown) return
+
+ switch(e.keyCode) {
+ case 9: // tab
+ case 13: // enter
+ case 27: // escape
+ e.preventDefault()
+ break
+
+ case 38: // up arrow
+ e.preventDefault()
+ this.prev()
+ break
+
+ case 40: // down arrow
+ e.preventDefault()
+ this.next()
+ break
+ }
+
+ e.stopPropagation()
+ }
+
+ , keydown: function (e) {
+ this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27])
+ this.move(e)
+ }
+
+ , keypress: function (e) {
+ if (this.suppressKeyPressRepeat) return
+ this.move(e)
+ }
+
+ , keyup: function (e) {
+ switch(e.keyCode) {
+ case 40: // down arrow
+ case 38: // up arrow
+ case 16: // shift
+ case 17: // ctrl
+ case 18: // alt
+ break
+
+ case 9: // tab
+ case 13: // enter
+ if (!this.shown) return
+ this.select()
+ break
+
+ case 27: // escape
+ if (!this.shown) return
+ this.hide()
+ break
+
+ default:
+ this.lookup()
+ }
+
+ e.stopPropagation()
+ e.preventDefault()
+ }
+
+ , focus: function (e) {
+ this.focused = true
+ }
+
+ , blur: function (e) {
+ this.focused = false
+ if (!this.mousedover && this.shown) this.hide()
+ }
+
+ , click: function (e) {
+ e.stopPropagation()
+ e.preventDefault()
+ this.select()
+ this.$element.focus()
+ }
+
+ , mouseenter: function (e) {
+ this.mousedover = true
+ this.$menu.find('.active').removeClass('active')
+ $(e.currentTarget).addClass('active')
+ }
+
+ , mouseleave: function (e) {
+ this.mousedover = false
+ if (!this.focused && this.shown) this.hide()
+ }
+
+ }
+
+
+ /* TYPEAHEAD PLUGIN DEFINITION
+ * =========================== */
+
+ var old = $.fn.typeahead
+
+ $.fn.typeahead = function (option) {
+ return this.each(function () {
+ var $this = $(this)
+ , data = $this.data('typeahead')
+ , options = typeof option == 'object' && option
+ if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
+ if (typeof option == 'string') data[option]()
+ })
+ }
+
+ $.fn.typeahead.defaults = {
+ source: []
+ , items: 8
+ , menu: ''
+ , item: ''
+ , minLength: 1
+ }
+
+ $.fn.typeahead.Constructor = Typeahead
+
+
+ /* TYPEAHEAD NO CONFLICT
+ * =================== */
+
+ $.fn.typeahead.noConflict = function () {
+ $.fn.typeahead = old
+ return this
+ }
+
+
+ /* TYPEAHEAD DATA-API
+ * ================== */
+
+ $(document).on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
+ var $this = $(this)
+ if ($this.data('typeahead')) return
+ $this.typeahead($this.data())
+ })
+
+}(window.jQuery);
diff --git a/vendor/scripts/bootstrap/bootstrap.js b/vendor/scripts/bootstrap/bootstrap.js
new file mode 100644
index 000000000..f28d8bbc2
--- /dev/null
+++ b/vendor/scripts/bootstrap/bootstrap.js
@@ -0,0 +1,13 @@
+//= require bootstrap-transition
+//= require bootstrap-affix
+//= require bootstrap-alert
+//= require bootstrap-button
+//= require bootstrap-carousel
+//= require bootstrap-collapse
+//= require bootstrap-dropdown
+//= require bootstrap-modal
+//= require bootstrap-scrollspy
+//= require bootstrap-tab
+//= require bootstrap-tooltip
+//= require bootstrap-popover
+//= require bootstrap-typeahead
\ No newline at end of file
diff --git a/vendor/scripts/coffeescript-1.6.3.js b/vendor/scripts/coffeescript-1.6.3.js
new file mode 100644
index 000000000..bb1bfd654
--- /dev/null
+++ b/vendor/scripts/coffeescript-1.6.3.js
@@ -0,0 +1,12 @@
+/**
+ * CoffeeScript Compiler v1.6.3
+ * http://coffeescript.org
+ *
+ * Copyright 2011, Jeremy Ashkenas
+ * Released under the MIT License
+ */
+!function(root){var CoffeeScript=function(){function require(e){return require[e]}return require["./helpers"]=function(){var e={},t={exports:e};return function(){var t,n,i,s,r,o;e.starts=function(e,t,n){return t===e.substr(n,t.length)},e.ends=function(e,t,n){var i;return i=t.length,t===e.substr(e.length-i-(n||0),i)},e.repeat=r=function(e,t){var n;for(n="";t>0;)1&t&&(n+=e),t>>>=1,e+=e;return n},e.compact=function(e){var t,n,i,s;for(s=[],n=0,i=e.length;i>n;n++)t=e[n],t&&s.push(t);return s},e.count=function(e,t){var n,i;if(n=i=0,!t.length)return 1/0;for(;i=1+e.indexOf(t,i);)n++;return n},e.merge=function(e,t){return n(n({},e),t)},n=e.extend=function(e,t){var n,i;for(n in t)i=t[n],e[n]=i;return e},e.flatten=i=function(e){var t,n,s,r;for(n=[],s=0,r=e.length;r>s;s++)t=e[s],t instanceof Array?n=n.concat(i(t)):n.push(t);return n},e.del=function(e,t){var n;return n=e[t],delete e[t],n},e.last=s=function(e,t){return e[e.length-(t||0)-1]},e.some=null!=(o=Array.prototype.some)?o:function(e){var t,n,i;for(n=0,i=this.length;i>n;n++)if(t=this[n],e(t))return!0;return!1},e.invertLiterate=function(e){var t,n,i;return i=!0,n=function(){var n,s,r,o;for(r=e.split("\n"),o=[],n=0,s=r.length;s>n;n++)t=r[n],i&&/^([ ]{4}|[ ]{0,3}\t)/.test(t)?o.push(t):(i=/^\s*$/.test(t))?o.push(t):o.push("# "+t);return o}(),n.join("\n")},t=function(e,t){return t?{first_line:e.first_line,first_column:e.first_column,last_line:t.last_line,last_column:t.last_column}:e},e.addLocationDataFn=function(e,n){return function(i){return"object"==typeof i&&i.updateLocationDataIfMissing&&i.updateLocationDataIfMissing(t(e,n)),i}},e.locationDataToString=function(e){var t;return"2"in e&&"first_line"in e[2]?t=e[2]:"first_line"in e&&(t=e),t?""+(t.first_line+1)+":"+(t.first_column+1)+"-"+(""+(t.last_line+1)+":"+(t.last_column+1)):"No location data"},e.baseFileName=function(e,t,n){var i,s;return null==t&&(t=!1),null==n&&(n=!1),s=n?/\\|\//:/\//,i=e.split(s),e=i[i.length-1],t?(i=e.split("."),i.pop(),"coffee"===i[i.length-1]&&i.length>1&&i.pop(),i.join(".")):e},e.isCoffee=function(e){return/\.((lit)?coffee|coffee\.md)$/.test(e)},e.isLiterate=function(e){return/\.(litcoffee|coffee\.md)$/.test(e)},e.throwSyntaxError=function(e,t){var n;throw null==t.last_line&&(t.last_line=t.first_line),null==t.last_column&&(t.last_column=t.first_column),n=new SyntaxError(e),n.location=t,n},e.prettyErrorMessage=function(e,t,n,i){var s,o,a,c,h,l,u,p,d,f,m;return e.location?(m=e.location,h=m.first_line,c=m.first_column,u=m.last_line,l=m.last_column,s=n.split("\n")[h],f=c,a=h===u?l+1:s.length,p=r(" ",f)+r("^",a-f),i&&(o=function(e){return"[1;31m"+e+"[0m"},s=s.slice(0,f)+o(s.slice(f,a))+s.slice(a),p=o(p)),d=""+t+":"+(h+1)+":"+(c+1)+": error: "+e.message+"\n"+s+"\n"+p):e.stack||""+e}}.call(this),t.exports}(),require["./rewriter"]=function(){var e={},t={exports:e};return function(){var t,n,i,s,r,o,a,c,h,l,u,p,d,f,m,b,k,g,y=[].indexOf||function(e){for(var t=0,n=this.length;n>t;t++)if(t in this&&this[t]===e)return t;return-1},v=[].slice;for(d=function(e,t){var n;return n=[e,t],n.generated=!0,n},e.Rewriter=function(){function e(){}return e.prototype.rewrite=function(e){return this.tokens=e,this.removeLeadingNewlines(),this.removeMidExpressionNewlines(),this.closeOpenCalls(),this.closeOpenIndexes(),this.addImplicitIndentation(),this.tagPostfixConditionals(),this.addImplicitBracesAndParens(),this.addLocationDataToGeneratedTokens(),this.tokens},e.prototype.scanTokens=function(e){var t,n,i;for(i=this.tokens,t=0;n=i[t];)t+=e.call(this,n,t,i);return!0},e.prototype.detectEnd=function(e,t,n){var r,o,a,c,h;for(a=this.tokens,r=0;o=a[e];){if(0===r&&t.call(this,o,e))return n.call(this,o,e);if(!o||0>r)return n.call(this,o,e-1);c=o[0],y.call(s,c)>=0?r+=1:(h=o[0],y.call(i,h)>=0&&(r-=1)),e+=1}return e-1},e.prototype.removeLeadingNewlines=function(){var e,t,n,i,s;for(s=this.tokens,e=n=0,i=s.length;i>n&&(t=s[e][0],"TERMINATOR"===t);e=++n);return e?this.tokens.splice(0,e):void 0},e.prototype.removeMidExpressionNewlines=function(){return this.scanTokens(function(e,t,i){var s;return"TERMINATOR"===e[0]&&(s=this.tag(t+1),y.call(n,s)>=0)?(i.splice(t,1),0):1})},e.prototype.closeOpenCalls=function(){var e,t;return t=function(e,t){var n;return")"===(n=e[0])||"CALL_END"===n||"OUTDENT"===e[0]&&")"===this.tag(t-1)},e=function(e,t){return this.tokens["OUTDENT"===e[0]?t-1:t][0]="CALL_END"},this.scanTokens(function(n,i){return"CALL_START"===n[0]&&this.detectEnd(i+1,t,e),1})},e.prototype.closeOpenIndexes=function(){var e,t;return t=function(e){var t;return"]"===(t=e[0])||"INDEX_END"===t},e=function(e){return e[0]="INDEX_END"},this.scanTokens(function(n,i){return"INDEX_START"===n[0]&&this.detectEnd(i+1,t,e),1})},e.prototype.matchTags=function(){var e,t,n,i,s,r,o;for(t=arguments[0],i=2<=arguments.length?v.call(arguments,1):[],e=0,n=s=0,r=i.length;r>=0?r>s:s>r;n=r>=0?++s:--s){for(;"HERECOMMENT"===this.tag(t+n+e);)e+=2;if(null!=i[n]&&("string"==typeof i[n]&&(i[n]=[i[n]]),o=this.tag(t+n+e),y.call(i[n],o)<0))return!1}return!0},e.prototype.looksObjectish=function(e){return this.matchTags(e,"@",null,":")||this.matchTags(e,null,":")},e.prototype.findTagsBackwards=function(e,t){var n,r,o,a,c,h,u;for(n=[];e>=0&&(n.length||(a=this.tag(e),y.call(t,a)<0&&(c=this.tag(e),y.call(s,c)<0||this.tokens[e].generated)&&(h=this.tag(e),y.call(l,h)<0)));)r=this.tag(e),y.call(i,r)>=0&&n.push(this.tag(e)),o=this.tag(e),y.call(s,o)>=0&&n.length&&n.pop(),e-=1;return u=this.tag(e),y.call(t,u)>=0},e.prototype.addImplicitBracesAndParens=function(){var e;return e=[],this.scanTokens(function(t,n,h){var u,p,f,m,b,k,g,v,w,T,C,F,L,N,x,E,D,S,R,A,I,_,$,O,j,M;if(A=t[0],T=(n>0?h[n-1]:[])[0],v=(n"!==T&&"->"!==T&&"["!==T&&"("!==T&&","!==T&&"{"!==T&&"TRY"!==T&&"ELSE"!==T&&"="!==T)for(;b();)u();return k()&&e.pop(),e.push([A,n]),f(1)}if(y.call(s,A)>=0)return e.push([A,n]),f(1);if(y.call(i,A)>=0){for(;m();)b()?u():g()?p():e.pop();e.pop()}if((y.call(a,A)>=0&&t.spaced&&!t.stringEnd||"?"===A&&n>0&&!h[n-1].spaced)&&(y.call(r,v)>=0||y.call(c,v)>=0&&!(null!=(I=h[n+1])?I.spaced:void 0)&&!(null!=(_=h[n+1])?_.newLine:void 0)))return"?"===A&&(A=t[0]="FUNC_EXIST"),D(n+1),f(2);if(y.call(a,A)>=0&&this.matchTags(n+1,"INDENT",null,":")&&!this.findTagsBackwards(n,["CLASS","EXTENDS","IF","CATCH","SWITCH","LEADING_WHEN","FOR","WHILE","UNTIL"]))return D(n+1),e.push(["INDENT",n+2]),f(3);if(":"===A){for(C="@"===this.tag(n-2)?n-2:n-1;"HERECOMMENT"===this.tag(C-2);)C-=2;return R=0===C||($=this.tag(C-1),y.call(l,$)>=0)||h[C-1].newLine,x()&&(O=x(),N=O[0],L=O[1],("{"===N||"INDENT"===N&&"{"===this.tag(L-1))&&(R||","===this.tag(C-1)||"{"===this.tag(C-1)))?f(1):(S(C,!!R),f(2))}if("OUTDENT"===T&&b()&&("."===A||"?."===A||"::"===A||"?::"===A))return u(),f(1);if(g()&&y.call(l,A)>=0&&(x()[2].sameLine=!1),y.call(o,A)>=0)for(;m();)if(j=x(),N=j[0],L=j[1],M=j[2],F=M.sameLine,R=M.startsLine,b()&&","!==T)u();else if(g()&&F&&!R)p();else{if(!g()||"TERMINATOR"!==A||","===T||R&&this.looksObjectish(n+1))break;p()}if(","===A&&!this.looksObjectish(n+1)&&g()&&("TERMINATOR"!==v||!this.looksObjectish(n+2)))for(w="OUTDENT"===v?1:0;g();)p(n+w);return f(1)})},e.prototype.addLocationDataToGeneratedTokens=function(){return this.scanTokens(function(e,t,n){var i,s,r,o,a,c;return e[2]?1:e.generated||e.explicit?("{"===e[0]&&(r=null!=(a=n[t+1])?a[2]:void 0)?(s=r.first_line,i=r.first_column):(o=null!=(c=n[t-1])?c[2]:void 0)?(s=o.last_line,i=o.last_column):s=i=0,e[2]={first_line:s,first_column:i,last_line:s,last_column:i},1):1})},e.prototype.addImplicitIndentation=function(){var e,t,n,i,s;return s=n=i=null,t=function(e){var t,n;return";"!==e[1]&&(t=e[0],y.call(u,t)>=0)&&!("ELSE"===e[0]&&"THEN"!==s)&&!!("CATCH"!==(n=e[0])&&"FINALLY"!==n||"->"!==s&&"=>"!==s)},e=function(e,t){return this.tokens.splice(","===this.tag(t-1)?t-1:t,0,i)},this.scanTokens(function(r,o,a){var c,h,l,u,d;if(h=r[0],"TERMINATOR"===h&&"THEN"===this.tag(o+1))return a.splice(o,1),0;if("ELSE"===h&&"OUTDENT"!==this.tag(o-1))return a.splice.apply(a,[o,0].concat(v.call(this.indentation()))),2;if("CATCH"===h)for(c=l=1;2>=l;c=++l)if("OUTDENT"===(u=this.tag(o+c))||"TERMINATOR"===u||"FINALLY"===u)return a.splice.apply(a,[o+c,0].concat(v.call(this.indentation()))),2+c;return y.call(p,h)>=0&&"INDENT"!==this.tag(o+1)&&("ELSE"!==h||"IF"!==this.tag(o+1))?(s=h,d=this.indentation(!0),n=d[0],i=d[1],"THEN"===s&&(n.fromThen=!0),a.splice(o+1,0,n),this.detectEnd(o+2,t,e),"THEN"===h&&a.splice(o,1),1):1})},e.prototype.tagPostfixConditionals=function(){var e,t,n;return n=null,t=function(e,t){var n,i;return i=e[0],n=this.tokens[t-1][0],"TERMINATOR"===i||"INDENT"===i&&y.call(p,n)<0},e=function(e){return"INDENT"!==e[0]||e.generated&&!e.fromThen?n[0]="POST_"+n[0]:void 0},this.scanTokens(function(i,s){return"IF"!==i[0]?1:(n=i,this.detectEnd(s+1,t,e),1)})},e.prototype.indentation=function(e){var t,n;return null==e&&(e=!1),t=["INDENT",2],n=["OUTDENT",2],e&&(t.generated=n.generated=!0),e||(t.explicit=n.explicit=!0),[t,n]},e.prototype.generate=d,e.prototype.tag=function(e){var t;return null!=(t=this.tokens[e])?t[0]:void 0},e}(),t=[["(",")"],["[","]"],["{","}"],["INDENT","OUTDENT"],["CALL_START","CALL_END"],["PARAM_START","PARAM_END"],["INDEX_START","INDEX_END"]],e.INVERSES=h={},s=[],i=[],b=0,k=t.length;k>b;b++)g=t[b],f=g[0],m=g[1],s.push(h[m]=f),i.push(h[f]=m);n=["CATCH","WHEN","ELSE","FINALLY"].concat(i),a=["IDENTIFIER","SUPER",")","CALL_END","]","INDEX_END","@","THIS"],r=["IDENTIFIER","NUMBER","STRING","JS","REGEX","NEW","PARAM_START","CLASS","IF","TRY","SWITCH","THIS","BOOL","NULL","UNDEFINED","UNARY","SUPER","THROW","@","->","=>","[","(","{","--","++"],c=["+","-"],o=["POST_IF","FOR","WHILE","UNTIL","WHEN","BY","LOOP","TERMINATOR"],p=["ELSE","->","=>","TRY","FINALLY","THEN"],u=["TERMINATOR","CATCH","FINALLY","ELSE","OUTDENT","LEADING_WHEN"],l=["TERMINATOR","INDENT","OUTDENT"]}.call(this),t.exports}(),require["./lexer"]=function(){var e={},t={exports:e};return function(){var t,n,i,s,r,o,a,c,h,l,u,p,d,f,m,b,k,g,y,v,w,T,C,F,L,N,x,E,D,S,R,A,I,_,$,O,j,M,B,V,P,U,q,H,G,W,X,Y,K,z,J,Z,Q,et=[].indexOf||function(e){for(var t=0,n=this.length;n>t;t++)if(t in this&&this[t]===e)return t;return-1};Z=require("./rewriter"),O=Z.Rewriter,g=Z.INVERSES,Q=require("./helpers"),H=Q.count,z=Q.starts,q=Q.compact,X=Q.last,K=Q.repeat,G=Q.invertLiterate,Y=Q.locationDataToString,J=Q.throwSyntaxError,e.Lexer=L=function(){function e(){}return e.prototype.tokenize=function(e,t){var n,i,s,r;for(null==t&&(t={}),this.literate=t.literate,this.indent=0,this.indebt=0,this.outdebt=0,this.indents=[],this.ends=[],this.tokens=[],this.chunkLine=t.line||0,this.chunkColumn=t.column||0,e=this.clean(e),i=0;this.chunk=e.slice(i);)n=this.identifierToken()||this.commentToken()||this.whitespaceToken()||this.lineToken()||this.heredocToken()||this.stringToken()||this.numberToken()||this.regexToken()||this.jsToken()||this.literalToken(),r=this.getLineAndColumnFromChunk(n),this.chunkLine=r[0],this.chunkColumn=r[1],i+=n;return this.closeIndentation(),(s=this.ends.pop())&&this.error("missing "+s),t.rewrite===!1?this.tokens:(new O).rewrite(this.tokens)},e.prototype.clean=function(e){return e.charCodeAt(0)===t&&(e=e.slice(1)),e=e.replace(/\r/g,"").replace(V,""),U.test(e)&&(e="\n"+e,this.chunkLine--),this.literate&&(e=G(e)),e},e.prototype.identifierToken=function(){var e,t,n,i,s,c,h,l,u,p,d,f,m,k;return(h=b.exec(this.chunk))?(c=h[0],i=h[1],e=h[2],s=i.length,l=void 0,"own"===i&&"FOR"===this.tag()?(this.token("OWN",i),i.length):(n=e||(u=X(this.tokens))&&("."===(f=u[0])||"?."===f||"::"===f||"?::"===f||!u.spaced&&"@"===u[0]),p="IDENTIFIER",!n&&(et.call(w,i)>=0||et.call(a,i)>=0)&&(p=i.toUpperCase(),"WHEN"===p&&(m=this.tag(),et.call(T,m)>=0)?p="LEADING_WHEN":"FOR"===p?this.seenFor=!0:"UNLESS"===p?p="IF":et.call(P,p)>=0?p="UNARY":et.call(_,p)>=0&&("INSTANCEOF"!==p&&this.seenFor?(p="FOR"+p,this.seenFor=!1):(p="RELATION","!"===this.value()&&(l=this.tokens.pop(),i="!"+i)))),et.call(v,i)>=0&&(n?(p="IDENTIFIER",i=new String(i),i.reserved=!0):et.call($,i)>=0&&this.error('reserved word "'+i+'"')),n||(et.call(r,i)>=0&&(i=o[i]),p=function(){switch(i){case"!":return"UNARY";case"==":case"!=":return"COMPARE";case"&&":case"||":return"LOGIC";case"true":case"false":return"BOOL";case"break":case"continue":return"STATEMENT";default:return p}}()),d=this.token(p,i,0,s),l&&(k=[l[2].first_line,l[2].first_column],d[2].first_line=k[0],d[2].first_column=k[1]),e&&(t=c.lastIndexOf(":"),this.token(":",":",t,e.length)),c.length)):0},e.prototype.numberToken=function(){var e,t,n,i,s;return(n=R.exec(this.chunk))?(i=n[0],/^0[BOX]/.test(i)?this.error("radix prefix '"+i+"' must be lowercase"):/E/.test(i)&&!/^0x/.test(i)?this.error("exponential notation '"+i+"' must be indicated with a lowercase 'e'"):/^0\d*[89]/.test(i)?this.error("decimal literal '"+i+"' must not be prefixed with '0'"):/^0\d+/.test(i)&&this.error("octal literal '"+i+"' must be prefixed with '0o'"),t=i.length,(s=/^0o([0-7]+)/.exec(i))&&(i="0x"+parseInt(s[1],8).toString(16)),(e=/^0b([01]+)/.exec(i))&&(i="0x"+parseInt(e[1],2).toString(16)),this.token("NUMBER",i,0,t),t):0},e.prototype.stringToken=function(){var e,t,n;switch(this.chunk.charAt(0)){case"'":if(!(e=M.exec(this.chunk)))return 0;n=e[0],this.token("STRING",n.replace(x,"\\\n"),0,n.length);break;case'"':if(!(n=this.balancedString(this.chunk,'"')))return 0;0=0)?0:(n=I.exec(this.chunk))?(o=n,n=o[0],s=o[1],e=o[2],"/*"===s.slice(0,2)&&this.error("regular expressions cannot begin with `*`"),"//"===s&&(s="/(?:)/"),this.token("REGEX",""+s+e,0,n.length),n.length):0)},e.prototype.heregexToken=function(e){var t,n,i,s,r,o,a,c,h,l,u,p,d,f,b,k;if(s=e[0],t=e[1],n=e[2],0>t.indexOf("#{"))return a=t.replace(m,"").replace(/\//g,"\\/"),a.match(/^\*/)&&this.error("regular expressions cannot begin with `*`"),this.token("REGEX","/"+(a||"(?:)")+"/"+n,0,s.length),s.length;for(this.token("IDENTIFIER","RegExp",0,0),this.token("CALL_START","(",0,0),l=[],f=this.interpolateString(t,{regex:!0}),p=0,d=f.length;d>p;p++){if(h=f[p],c=h[0],u=h[1],"TOKENS"===c)l.push.apply(l,u);else if("NEOSTRING"===c){if(!(u=u.replace(m,"")))continue;u=u.replace(/\\/g,"\\\\"),h[0]="STRING",h[1]=this.makeString(u,'"',!0),l.push(h)}else this.error("Unexpected "+c);o=X(this.tokens),r=["+","+"],r[2]=o[2],l.push(r)}return l.pop(),"STRING"!==(null!=(b=l[0])?b[0]:void 0)&&(this.token("STRING",'""',0,0),this.token("+","+",0,0)),(k=this.tokens).push.apply(k,l),n&&(i=s.lastIndexOf(n),this.token(",",",",i,0),this.token("STRING",'"'+n+'"',i,n.length)),this.token(")",")",s.length-1,0),s.length},e.prototype.lineToken=function(){var e,t,n,i,s;if(!(n=E.exec(this.chunk)))return 0;if(t=n[0],this.seenFor=!1,s=t.length-1-t.lastIndexOf("\n"),i=this.unfinished(),s-this.indebt===this.indent)return i?this.suppressNewlines():this.newlineToken(0),t.length;if(s>this.indent){if(i)return this.indebt=s-this.indent,this.suppressNewlines(),t.length;e=s-this.indent+this.outdebt,this.token("INDENT",e,t.length-s,s),this.indents.push(e),this.ends.push("OUTDENT"),this.outdebt=this.indebt=0}else this.indebt=0,this.outdentToken(this.indent-s,i,t.length);return this.indent=s,t.length},e.prototype.outdentToken=function(e,t,n){for(var i,s;e>0;)s=this.indents.length-1,void 0===this.indents[s]?e=0:this.indents[s]===this.outdebt?(e-=this.outdebt,this.outdebt=0):this.indents[s]=0)&&this.error('reserved word "'+this.value()+"\" can't be assigned"),"||"===(a=t[1])||"&&"===a))return t[0]="COMPOUND_ASSIGN",t[1]+="=",r.length;if(";"===r)this.seenFor=!1,n="TERMINATOR";else if(et.call(N,r)>=0)n="MATH";else if(et.call(h,r)>=0)n="COMPARE";else if(et.call(l,r)>=0)n="COMPOUND_ASSIGN";else if(et.call(P,r)>=0)n="UNARY";else if(et.call(j,r)>=0)n="SHIFT";else if(et.call(F,r)>=0||"?"===r&&(null!=t?t.spaced:void 0))n="LOGIC";else if(t&&!t.spaced)if("("===r&&(c=t[0],et.call(i,c)>=0))"?"===t[0]&&(t[0]="FUNC_EXIST"),n="CALL_START";else if("["===r&&(u=t[0],et.call(k,u)>=0))switch(n="INDEX_START",t[0]){case"?":t[0]="INDEX_SOAK"}switch(r){case"(":case"{":case"[":this.ends.push(g[r]);break;case")":case"}":case"]":this.pair(r)}return this.token(n,r),r.length},e.prototype.sanitizeHeredoc=function(e,t){var n,i,s,r,o;if(s=t.indent,i=t.herecomment){if(p.test(e)&&this.error('block comment cannot contain "*/", starting'),e.indexOf("\n")<0)return e}else for(;r=d.exec(e);)n=r[1],(null===s||0<(o=n.length)&&o=1?h>c:c>h;i=h>=1?++c:--c)if(n)--n;else{switch(s=e.charAt(i)){case"\\":++n;continue;case t:if(a.pop(),!a.length)return e.slice(0,+i+1||9e9);t=a[a.length-1];continue}"}"!==t||'"'!==s&&"'"!==s?"}"===t&&"/"===s&&(r=f.exec(e.slice(i))||I.exec(e.slice(i)))?n+=r[0].length-1:"}"===t&&"{"===s?a.push(t="}"):'"'===t&&"#"===o&&"{"===s&&a.push(t="}"):a.push(t=s),o=s}return this.error("missing "+a.pop()+", starting")},e.prototype.interpolateString=function(t,n){var i,s,r,o,a,c,h,l,u,p,d,f,m,b,k,g,y,v,w,T,C,F,L,N,x,E,D,S;for(null==n&&(n={}),r=n.heredoc,y=n.regex,m=n.offsetInChunk,w=n.strOffset,u=n.lexedLength,m=m||0,w=w||0,u=u||t.length,r&&t.length>0&&"\n"===t[0]&&(t=t.slice(1),w++),F=[],b=0,o=-1;l=t.charAt(o+=1);)"\\"!==l?"#"===l&&"{"===t.charAt(o+1)&&(s=this.balancedString(t.slice(o+1),"}"))&&(o>b&&F.push(this.makeToken("NEOSTRING",t.slice(b,o),w+b)),a=s.slice(1,-1),a.length&&(E=this.getLineAndColumnFromChunk(w+o+1),p=E[0],i=E[1],f=(new e).tokenize(a,{line:p,column:i,rewrite:!1}),g=f.pop(),"TERMINATOR"===(null!=(D=f[0])?D[0]:void 0)&&(g=f.shift()),(h=f.length)&&(h>1&&(f.unshift(this.makeToken("(","(",w+o+1,0)),f.push(this.makeToken(")",")",w+o+1+a.length,0))),F.push(["TOKENS",f]))),o+=s.length,b=o+1):o+=1;if(o>b&&b1)&&this.token("(","(",m,0),o=N=0,x=F.length;x>N;o=++N)C=F[o],T=C[0],L=C[1],o&&(o&&(k=this.token("+","+")),d="TOKENS"===T?L[0]:C,k[2]={first_line:d[2].first_line,first_column:d[2].first_column,last_line:d[2].first_line,last_column:d[2].first_column}),"TOKENS"===T?(S=this.tokens).push.apply(S,L):"NEOSTRING"===T?(C[0]="STRING",C[1]=this.makeString(L,'"',r),this.tokens.push(C)):this.error("Unexpected "+T);return c&&(v=this.makeToken(")",")",m+u,0),v.stringEnd=!0,this.tokens.push(v)),F},e.prototype.pair=function(e){var t,n;return e!==(n=X(this.ends))?("OUTDENT"!==n&&this.error("unmatched "+e),this.indent-=t=X(this.indents),this.outdentToken(t,!0),this.pair(e)):this.ends.pop()},e.prototype.getLineAndColumnFromChunk=function(e){var t,n,i,s;return 0===e?[this.chunkLine,this.chunkColumn]:(s=e>=this.chunk.length?this.chunk:this.chunk.slice(0,+(e-1)+1||9e9),n=H(s,"\n"),t=this.chunkColumn,n>0?(i=s.split("\n"),t=X(i).length):t+=s.length,[this.chunkLine+n,t])},e.prototype.makeToken=function(e,t,n,i){var s,r,o,a,c;return null==n&&(n=0),null==i&&(i=t.length),r={},a=this.getLineAndColumnFromChunk(n),r.first_line=a[0],r.first_column=a[1],s=Math.max(0,i-1),c=this.getLineAndColumnFromChunk(n+s),r.last_line=c[0],r.last_column=c[1],o=[e,t,r]},e.prototype.token=function(e,t,n,i){var s;return s=this.makeToken(e,t,n,i),this.tokens.push(s),s},e.prototype.tag=function(e,t){var n;return(n=X(this.tokens,e))&&(t?n[0]=t:n[0])},e.prototype.value=function(e,t){var n;return(n=X(this.tokens,e))&&(t?n[1]=t:n[1])},e.prototype.unfinished=function(){var e;return C.test(this.chunk)||"\\"===(e=this.tag())||"."===e||"?."===e||"?::"===e||"UNARY"===e||"MATH"===e||"+"===e||"-"===e||"SHIFT"===e||"RELATION"===e||"COMPARE"===e||"LOGIC"===e||"THROW"===e||"EXTENDS"===e},e.prototype.escapeLines=function(e,t){return e.replace(x,t?"\\n":"")},e.prototype.makeString=function(e,t,n){return e?(e=e.replace(/\\([\s\S])/g,function(e,n){return"\n"===n||n===t?n:e}),e=e.replace(RegExp(""+t,"g"),"\\$&"),t+this.escapeLines(e,n)+t):t+t},e.prototype.error=function(e){return J(e,{first_line:this.chunkLine,first_column:this.chunkColumn})},e}(),w=["true","false","null","this","new","delete","typeof","in","instanceof","return","throw","break","continue","debugger","if","else","switch","for","while","do","try","catch","finally","class","extends","super"],a=["undefined","then","unless","until","loop","of","by","when"],o={and:"&&",or:"||",is:"==",isnt:"!=",not:"!",yes:"true",no:"false",on:"true",off:"false"},r=function(){var e;e=[];for(W in o)e.push(W);return e}(),a=a.concat(r),$=["case","default","function","var","void","with","const","let","enum","export","import","native","__hasProp","__extends","__slice","__bind","__indexOf","implements","interface","package","private","protected","public","static","yield"],B=["arguments","eval"],v=w.concat($).concat(B),e.RESERVED=$.concat(w).concat(a).concat(B),e.STRICT_PROSCRIBED=B,t=65279,b=/^([$A-Za-z_\x7f-\uffff][$\w\x7f-\uffff]*)([^\n\S]*:(?!:))?/,R=/^0b[01]+|^0o[0-7]+|^0x[\da-f]+|^\d*\.?\d+(?:e[+-]?\d+)?/i,u=/^("""|''')([\s\S]*?)(?:\n[^\n\S]*)?\1/,A=/^(?:[-=]>|[-+*\/%<>&|^!?=]=|>>>=?|([-+:])\1|([&|<>])\2=?|\?(\.|::)|\.{2,3})/,U=/^[^\n\S]+/,c=/^###([^#][\s\S]*?)(?:###[^\n\S]*|(?:###)$)|^(?:\s*#(?!##[^#]).*)+/,s=/^[-=]>/,E=/^(?:\n[^\n\S]*)+/,M=/^'[^\\']*(?:\\.[^\\']*)*'/,y=/^`[^\\`]*(?:\\.[^\\`]*)*`/,I=/^(\/(?![\s=])[^[\/\n\\]*(?:(?:\\[\s\S]|\[[^\]\n\\]*(?:\\[\s\S][^\]\n\\]*)*])[^[\/\n\\]*)*\/)([imgy]{0,4})(?!\w)/,f=/^\/{3}([\s\S]+?)\/{3}([imgy]{0,4})(?!\w)/,m=/\s+(?:#.*)?/g,x=/\n/g,d=/\n+([^\n\S]*)/g,p=/\*\//,C=/^\s*(?:,|\??\.(?![.\d])|::)/,V=/\s+$/,l=["-=","+=","/=","*=","%=","||=","&&=","?=","<<=",">>=",">>>=","&=","^=","|="],P=["!","~","NEW","TYPEOF","DELETE","DO"],F=["&&","||","&","|","^"],j=["<<",">>",">>>"],h=["==","!=","<",">","<=",">="],N=["*","/","%"],_=["IN","OF","INSTANCEOF"],n=["TRUE","FALSE"],D=["NUMBER","REGEX","BOOL","NULL","UNDEFINED","++","--"],S=D.concat(")","}","THIS","IDENTIFIER","STRING","]"),i=["IDENTIFIER","STRING","REGEX",")","]","}","?","::","@","THIS","SUPER"],k=i.concat("NUMBER","BOOL","NULL","UNDEFINED"),T=["INDENT","OUTDENT","TERMINATOR"]}.call(this),t.exports}(),require["./parser"]=function(){var e={},t={exports:e},n=function(){function e(){this.yy={}}var t={trace:function(){},yy:{},symbols_:{error:2,Root:3,Body:4,Block:5,TERMINATOR:6,Line:7,Expression:8,Statement:9,Return:10,Comment:11,STATEMENT:12,Value:13,Invocation:14,Code:15,Operation:16,Assign:17,If:18,Try:19,While:20,For:21,Switch:22,Class:23,Throw:24,INDENT:25,OUTDENT:26,Identifier:27,IDENTIFIER:28,AlphaNumeric:29,NUMBER:30,STRING:31,Literal:32,JS:33,REGEX:34,DEBUGGER:35,UNDEFINED:36,NULL:37,BOOL:38,Assignable:39,"=":40,AssignObj:41,ObjAssignable:42,":":43,ThisProperty:44,RETURN:45,HERECOMMENT:46,PARAM_START:47,ParamList:48,PARAM_END:49,FuncGlyph:50,"->":51,"=>":52,OptComma:53,",":54,Param:55,ParamVar:56,"...":57,Array:58,Object:59,Splat:60,SimpleAssignable:61,Accessor:62,Parenthetical:63,Range:64,This:65,".":66,"?.":67,"::":68,"?::":69,Index:70,INDEX_START:71,IndexValue:72,INDEX_END:73,INDEX_SOAK:74,Slice:75,"{":76,AssignList:77,"}":78,CLASS:79,EXTENDS:80,OptFuncExist:81,Arguments:82,SUPER:83,FUNC_EXIST:84,CALL_START:85,CALL_END:86,ArgList:87,THIS:88,"@":89,"[":90,"]":91,RangeDots:92,"..":93,Arg:94,SimpleArgs:95,TRY:96,Catch:97,FINALLY:98,CATCH:99,THROW:100,"(":101,")":102,WhileSource:103,WHILE:104,WHEN:105,UNTIL:106,Loop:107,LOOP:108,ForBody:109,FOR:110,ForStart:111,ForSource:112,ForVariables:113,OWN:114,ForValue:115,FORIN:116,FOROF:117,BY:118,SWITCH:119,Whens:120,ELSE:121,When:122,LEADING_WHEN:123,IfBlock:124,IF:125,POST_IF:126,UNARY:127,"-":128,"+":129,"--":130,"++":131,"?":132,MATH:133,SHIFT:134,COMPARE:135,LOGIC:136,RELATION:137,COMPOUND_ASSIGN:138,$accept:0,$end:1},terminals_:{2:"error",6:"TERMINATOR",12:"STATEMENT",25:"INDENT",26:"OUTDENT",28:"IDENTIFIER",30:"NUMBER",31:"STRING",33:"JS",34:"REGEX",35:"DEBUGGER",36:"UNDEFINED",37:"NULL",38:"BOOL",40:"=",43:":",45:"RETURN",46:"HERECOMMENT",47:"PARAM_START",49:"PARAM_END",51:"->",52:"=>",54:",",57:"...",66:".",67:"?.",68:"::",69:"?::",71:"INDEX_START",73:"INDEX_END",74:"INDEX_SOAK",76:"{",78:"}",79:"CLASS",80:"EXTENDS",83:"SUPER",84:"FUNC_EXIST",85:"CALL_START",86:"CALL_END",88:"THIS",89:"@",90:"[",91:"]",93:"..",96:"TRY",98:"FINALLY",99:"CATCH",100:"THROW",101:"(",102:")",104:"WHILE",105:"WHEN",106:"UNTIL",108:"LOOP",110:"FOR",114:"OWN",116:"FORIN",117:"FOROF",118:"BY",119:"SWITCH",121:"ELSE",123:"LEADING_WHEN",125:"IF",126:"POST_IF",127:"UNARY",128:"-",129:"+",130:"--",131:"++",132:"?",133:"MATH",134:"SHIFT",135:"COMPARE",136:"LOGIC",137:"RELATION",138:"COMPOUND_ASSIGN"},productions_:[0,[3,0],[3,1],[3,2],[4,1],[4,3],[4,2],[7,1],[7,1],[9,1],[9,1],[9,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[8,1],[5,2],[5,3],[27,1],[29,1],[29,1],[32,1],[32,1],[32,1],[32,1],[32,1],[32,1],[32,1],[17,3],[17,4],[17,5],[41,1],[41,3],[41,5],[41,1],[42,1],[42,1],[42,1],[10,2],[10,1],[11,1],[15,5],[15,2],[50,1],[50,1],[53,0],[53,1],[48,0],[48,1],[48,3],[48,4],[48,6],[55,1],[55,2],[55,3],[56,1],[56,1],[56,1],[56,1],[60,2],[61,1],[61,2],[61,2],[61,1],[39,1],[39,1],[39,1],[13,1],[13,1],[13,1],[13,1],[13,1],[62,2],[62,2],[62,2],[62,2],[62,1],[62,1],[70,3],[70,2],[72,1],[72,1],[59,4],[77,0],[77,1],[77,3],[77,4],[77,6],[23,1],[23,2],[23,3],[23,4],[23,2],[23,3],[23,4],[23,5],[14,3],[14,3],[14,1],[14,2],[81,0],[81,1],[82,2],[82,4],[65,1],[65,1],[44,2],[58,2],[58,4],[92,1],[92,1],[64,5],[75,3],[75,2],[75,2],[75,1],[87,1],[87,3],[87,4],[87,4],[87,6],[94,1],[94,1],[95,1],[95,3],[19,2],[19,3],[19,4],[19,5],[97,3],[97,3],[97,2],[24,2],[63,3],[63,5],[103,2],[103,4],[103,2],[103,4],[20,2],[20,2],[20,2],[20,1],[107,2],[107,2],[21,2],[21,2],[21,2],[109,2],[109,2],[111,2],[111,3],[115,1],[115,1],[115,1],[115,1],[113,1],[113,3],[112,2],[112,2],[112,4],[112,4],[112,4],[112,6],[112,6],[22,5],[22,7],[22,4],[22,6],[120,1],[120,2],[122,3],[122,4],[124,3],[124,5],[18,1],[18,3],[18,3],[18,3],[16,2],[16,2],[16,2],[16,2],[16,2],[16,2],[16,2],[16,2],[16,3],[16,3],[16,3],[16,3],[16,3],[16,3],[16,3],[16,3],[16,5],[16,4],[16,3]],performAction:function(e,t,n,i,s,r,o){var a=r.length-1;switch(s){case 1:return this.$=i.addLocationDataFn(o[a],o[a])(new i.Block);case 2:return this.$=r[a];case 3:return this.$=r[a-1];case 4:this.$=i.addLocationDataFn(o[a],o[a])(i.Block.wrap([r[a]]));break;case 5:this.$=i.addLocationDataFn(o[a-2],o[a])(r[a-2].push(r[a]));break;case 6:this.$=r[a-1];break;case 7:this.$=r[a];break;case 8:this.$=r[a];break;case 9:this.$=r[a];break;case 10:this.$=r[a];break;case 11:this.$=i.addLocationDataFn(o[a],o[a])(new i.Literal(r[a]));break;case 12:this.$=r[a];break;case 13:this.$=r[a];break;case 14:this.$=r[a];break;case 15:this.$=r[a];break;case 16:this.$=r[a];break;case 17:this.$=r[a];break;case 18:this.$=r[a];break;case 19:this.$=r[a];break;case 20:this.$=r[a];break;case 21:this.$=r[a];break;case 22:this.$=r[a];break;case 23:this.$=r[a];break;case 24:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.Block);break;case 25:this.$=i.addLocationDataFn(o[a-2],o[a])(r[a-1]);break;case 26:this.$=i.addLocationDataFn(o[a],o[a])(new i.Literal(r[a]));break;case 27:this.$=i.addLocationDataFn(o[a],o[a])(new i.Literal(r[a]));break;case 28:this.$=i.addLocationDataFn(o[a],o[a])(new i.Literal(r[a]));break;case 29:this.$=r[a];break;case 30:this.$=i.addLocationDataFn(o[a],o[a])(new i.Literal(r[a]));break;case 31:this.$=i.addLocationDataFn(o[a],o[a])(new i.Literal(r[a]));break;case 32:this.$=i.addLocationDataFn(o[a],o[a])(new i.Literal(r[a]));break;case 33:this.$=i.addLocationDataFn(o[a],o[a])(new i.Undefined);break;case 34:this.$=i.addLocationDataFn(o[a],o[a])(new i.Null);break;case 35:this.$=i.addLocationDataFn(o[a],o[a])(new i.Bool(r[a]));break;case 36:this.$=i.addLocationDataFn(o[a-2],o[a])(new i.Assign(r[a-2],r[a]));break;case 37:this.$=i.addLocationDataFn(o[a-3],o[a])(new i.Assign(r[a-3],r[a]));break;case 38:this.$=i.addLocationDataFn(o[a-4],o[a])(new i.Assign(r[a-4],r[a-1]));break;case 39:this.$=i.addLocationDataFn(o[a],o[a])(new i.Value(r[a]));break;case 40:this.$=i.addLocationDataFn(o[a-2],o[a])(new i.Assign(i.addLocationDataFn(o[a-2])(new i.Value(r[a-2])),r[a],"object"));break;case 41:this.$=i.addLocationDataFn(o[a-4],o[a])(new i.Assign(i.addLocationDataFn(o[a-4])(new i.Value(r[a-4])),r[a-1],"object"));break;case 42:this.$=r[a];break;case 43:this.$=r[a];break;case 44:this.$=r[a];break;case 45:this.$=r[a];break;case 46:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.Return(r[a]));break;case 47:this.$=i.addLocationDataFn(o[a],o[a])(new i.Return);break;case 48:this.$=i.addLocationDataFn(o[a],o[a])(new i.Comment(r[a]));break;case 49:this.$=i.addLocationDataFn(o[a-4],o[a])(new i.Code(r[a-3],r[a],r[a-1]));break;case 50:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.Code([],r[a],r[a-1]));
+break;case 51:this.$=i.addLocationDataFn(o[a],o[a])("func");break;case 52:this.$=i.addLocationDataFn(o[a],o[a])("boundfunc");break;case 53:this.$=r[a];break;case 54:this.$=r[a];break;case 55:this.$=i.addLocationDataFn(o[a],o[a])([]);break;case 56:this.$=i.addLocationDataFn(o[a],o[a])([r[a]]);break;case 57:this.$=i.addLocationDataFn(o[a-2],o[a])(r[a-2].concat(r[a]));break;case 58:this.$=i.addLocationDataFn(o[a-3],o[a])(r[a-3].concat(r[a]));break;case 59:this.$=i.addLocationDataFn(o[a-5],o[a])(r[a-5].concat(r[a-2]));break;case 60:this.$=i.addLocationDataFn(o[a],o[a])(new i.Param(r[a]));break;case 61:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.Param(r[a-1],null,!0));break;case 62:this.$=i.addLocationDataFn(o[a-2],o[a])(new i.Param(r[a-2],r[a]));break;case 63:this.$=r[a];break;case 64:this.$=r[a];break;case 65:this.$=r[a];break;case 66:this.$=r[a];break;case 67:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.Splat(r[a-1]));break;case 68:this.$=i.addLocationDataFn(o[a],o[a])(new i.Value(r[a]));break;case 69:this.$=i.addLocationDataFn(o[a-1],o[a])(r[a-1].add(r[a]));break;case 70:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.Value(r[a-1],[].concat(r[a])));break;case 71:this.$=r[a];break;case 72:this.$=r[a];break;case 73:this.$=i.addLocationDataFn(o[a],o[a])(new i.Value(r[a]));break;case 74:this.$=i.addLocationDataFn(o[a],o[a])(new i.Value(r[a]));break;case 75:this.$=r[a];break;case 76:this.$=i.addLocationDataFn(o[a],o[a])(new i.Value(r[a]));break;case 77:this.$=i.addLocationDataFn(o[a],o[a])(new i.Value(r[a]));break;case 78:this.$=i.addLocationDataFn(o[a],o[a])(new i.Value(r[a]));break;case 79:this.$=r[a];break;case 80:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.Access(r[a]));break;case 81:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.Access(r[a],"soak"));break;case 82:this.$=i.addLocationDataFn(o[a-1],o[a])([i.addLocationDataFn(o[a-1])(new i.Access(new i.Literal("prototype"))),i.addLocationDataFn(o[a])(new i.Access(r[a]))]);break;case 83:this.$=i.addLocationDataFn(o[a-1],o[a])([i.addLocationDataFn(o[a-1])(new i.Access(new i.Literal("prototype"),"soak")),i.addLocationDataFn(o[a])(new i.Access(r[a]))]);break;case 84:this.$=i.addLocationDataFn(o[a],o[a])(new i.Access(new i.Literal("prototype")));break;case 85:this.$=r[a];break;case 86:this.$=i.addLocationDataFn(o[a-2],o[a])(r[a-1]);break;case 87:this.$=i.addLocationDataFn(o[a-1],o[a])(i.extend(r[a],{soak:!0}));break;case 88:this.$=i.addLocationDataFn(o[a],o[a])(new i.Index(r[a]));break;case 89:this.$=i.addLocationDataFn(o[a],o[a])(new i.Slice(r[a]));break;case 90:this.$=i.addLocationDataFn(o[a-3],o[a])(new i.Obj(r[a-2],r[a-3].generated));break;case 91:this.$=i.addLocationDataFn(o[a],o[a])([]);break;case 92:this.$=i.addLocationDataFn(o[a],o[a])([r[a]]);break;case 93:this.$=i.addLocationDataFn(o[a-2],o[a])(r[a-2].concat(r[a]));break;case 94:this.$=i.addLocationDataFn(o[a-3],o[a])(r[a-3].concat(r[a]));break;case 95:this.$=i.addLocationDataFn(o[a-5],o[a])(r[a-5].concat(r[a-2]));break;case 96:this.$=i.addLocationDataFn(o[a],o[a])(new i.Class);break;case 97:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.Class(null,null,r[a]));break;case 98:this.$=i.addLocationDataFn(o[a-2],o[a])(new i.Class(null,r[a]));break;case 99:this.$=i.addLocationDataFn(o[a-3],o[a])(new i.Class(null,r[a-1],r[a]));break;case 100:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.Class(r[a]));break;case 101:this.$=i.addLocationDataFn(o[a-2],o[a])(new i.Class(r[a-1],null,r[a]));break;case 102:this.$=i.addLocationDataFn(o[a-3],o[a])(new i.Class(r[a-2],r[a]));break;case 103:this.$=i.addLocationDataFn(o[a-4],o[a])(new i.Class(r[a-3],r[a-1],r[a]));break;case 104:this.$=i.addLocationDataFn(o[a-2],o[a])(new i.Call(r[a-2],r[a],r[a-1]));break;case 105:this.$=i.addLocationDataFn(o[a-2],o[a])(new i.Call(r[a-2],r[a],r[a-1]));break;case 106:this.$=i.addLocationDataFn(o[a],o[a])(new i.Call("super",[new i.Splat(new i.Literal("arguments"))]));break;case 107:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.Call("super",r[a]));break;case 108:this.$=i.addLocationDataFn(o[a],o[a])(!1);break;case 109:this.$=i.addLocationDataFn(o[a],o[a])(!0);break;case 110:this.$=i.addLocationDataFn(o[a-1],o[a])([]);break;case 111:this.$=i.addLocationDataFn(o[a-3],o[a])(r[a-2]);break;case 112:this.$=i.addLocationDataFn(o[a],o[a])(new i.Value(new i.Literal("this")));break;case 113:this.$=i.addLocationDataFn(o[a],o[a])(new i.Value(new i.Literal("this")));break;case 114:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.Value(i.addLocationDataFn(o[a-1])(new i.Literal("this")),[i.addLocationDataFn(o[a])(new i.Access(r[a]))],"this"));break;case 115:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.Arr([]));break;case 116:this.$=i.addLocationDataFn(o[a-3],o[a])(new i.Arr(r[a-2]));break;case 117:this.$=i.addLocationDataFn(o[a],o[a])("inclusive");break;case 118:this.$=i.addLocationDataFn(o[a],o[a])("exclusive");break;case 119:this.$=i.addLocationDataFn(o[a-4],o[a])(new i.Range(r[a-3],r[a-1],r[a-2]));break;case 120:this.$=i.addLocationDataFn(o[a-2],o[a])(new i.Range(r[a-2],r[a],r[a-1]));break;case 121:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.Range(r[a-1],null,r[a]));break;case 122:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.Range(null,r[a],r[a-1]));break;case 123:this.$=i.addLocationDataFn(o[a],o[a])(new i.Range(null,null,r[a]));break;case 124:this.$=i.addLocationDataFn(o[a],o[a])([r[a]]);break;case 125:this.$=i.addLocationDataFn(o[a-2],o[a])(r[a-2].concat(r[a]));break;case 126:this.$=i.addLocationDataFn(o[a-3],o[a])(r[a-3].concat(r[a]));break;case 127:this.$=i.addLocationDataFn(o[a-3],o[a])(r[a-2]);break;case 128:this.$=i.addLocationDataFn(o[a-5],o[a])(r[a-5].concat(r[a-2]));break;case 129:this.$=r[a];break;case 130:this.$=r[a];break;case 131:this.$=r[a];break;case 132:this.$=i.addLocationDataFn(o[a-2],o[a])([].concat(r[a-2],r[a]));break;case 133:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.Try(r[a]));break;case 134:this.$=i.addLocationDataFn(o[a-2],o[a])(new i.Try(r[a-1],r[a][0],r[a][1]));break;case 135:this.$=i.addLocationDataFn(o[a-3],o[a])(new i.Try(r[a-2],null,null,r[a]));break;case 136:this.$=i.addLocationDataFn(o[a-4],o[a])(new i.Try(r[a-3],r[a-2][0],r[a-2][1],r[a]));break;case 137:this.$=i.addLocationDataFn(o[a-2],o[a])([r[a-1],r[a]]);break;case 138:this.$=i.addLocationDataFn(o[a-2],o[a])([i.addLocationDataFn(o[a-1])(new i.Value(r[a-1])),r[a]]);break;case 139:this.$=i.addLocationDataFn(o[a-1],o[a])([null,r[a]]);break;case 140:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.Throw(r[a]));break;case 141:this.$=i.addLocationDataFn(o[a-2],o[a])(new i.Parens(r[a-1]));break;case 142:this.$=i.addLocationDataFn(o[a-4],o[a])(new i.Parens(r[a-2]));break;case 143:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.While(r[a]));break;case 144:this.$=i.addLocationDataFn(o[a-3],o[a])(new i.While(r[a-2],{guard:r[a]}));break;case 145:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.While(r[a],{invert:!0}));break;case 146:this.$=i.addLocationDataFn(o[a-3],o[a])(new i.While(r[a-2],{invert:!0,guard:r[a]}));break;case 147:this.$=i.addLocationDataFn(o[a-1],o[a])(r[a-1].addBody(r[a]));break;case 148:this.$=i.addLocationDataFn(o[a-1],o[a])(r[a].addBody(i.addLocationDataFn(o[a-1])(i.Block.wrap([r[a-1]]))));break;case 149:this.$=i.addLocationDataFn(o[a-1],o[a])(r[a].addBody(i.addLocationDataFn(o[a-1])(i.Block.wrap([r[a-1]]))));break;case 150:this.$=i.addLocationDataFn(o[a],o[a])(r[a]);break;case 151:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.While(i.addLocationDataFn(o[a-1])(new i.Literal("true"))).addBody(r[a]));break;case 152:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.While(i.addLocationDataFn(o[a-1])(new i.Literal("true"))).addBody(i.addLocationDataFn(o[a])(i.Block.wrap([r[a]]))));break;case 153:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.For(r[a-1],r[a]));break;case 154:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.For(r[a-1],r[a]));break;case 155:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.For(r[a],r[a-1]));break;case 156:this.$=i.addLocationDataFn(o[a-1],o[a])({source:i.addLocationDataFn(o[a])(new i.Value(r[a]))});break;case 157:this.$=i.addLocationDataFn(o[a-1],o[a])(function(){return r[a].own=r[a-1].own,r[a].name=r[a-1][0],r[a].index=r[a-1][1],r[a]}());break;case 158:this.$=i.addLocationDataFn(o[a-1],o[a])(r[a]);break;case 159:this.$=i.addLocationDataFn(o[a-2],o[a])(function(){return r[a].own=!0,r[a]}());break;case 160:this.$=r[a];break;case 161:this.$=r[a];break;case 162:this.$=i.addLocationDataFn(o[a],o[a])(new i.Value(r[a]));break;case 163:this.$=i.addLocationDataFn(o[a],o[a])(new i.Value(r[a]));break;case 164:this.$=i.addLocationDataFn(o[a],o[a])([r[a]]);break;case 165:this.$=i.addLocationDataFn(o[a-2],o[a])([r[a-2],r[a]]);break;case 166:this.$=i.addLocationDataFn(o[a-1],o[a])({source:r[a]});break;case 167:this.$=i.addLocationDataFn(o[a-1],o[a])({source:r[a],object:!0});break;case 168:this.$=i.addLocationDataFn(o[a-3],o[a])({source:r[a-2],guard:r[a]});break;case 169:this.$=i.addLocationDataFn(o[a-3],o[a])({source:r[a-2],guard:r[a],object:!0});break;case 170:this.$=i.addLocationDataFn(o[a-3],o[a])({source:r[a-2],step:r[a]});break;case 171:this.$=i.addLocationDataFn(o[a-5],o[a])({source:r[a-4],guard:r[a-2],step:r[a]});break;case 172:this.$=i.addLocationDataFn(o[a-5],o[a])({source:r[a-4],step:r[a-2],guard:r[a]});break;case 173:this.$=i.addLocationDataFn(o[a-4],o[a])(new i.Switch(r[a-3],r[a-1]));break;case 174:this.$=i.addLocationDataFn(o[a-6],o[a])(new i.Switch(r[a-5],r[a-3],r[a-1]));break;case 175:this.$=i.addLocationDataFn(o[a-3],o[a])(new i.Switch(null,r[a-1]));break;case 176:this.$=i.addLocationDataFn(o[a-5],o[a])(new i.Switch(null,r[a-3],r[a-1]));break;case 177:this.$=r[a];break;case 178:this.$=i.addLocationDataFn(o[a-1],o[a])(r[a-1].concat(r[a]));break;case 179:this.$=i.addLocationDataFn(o[a-2],o[a])([[r[a-1],r[a]]]);break;case 180:this.$=i.addLocationDataFn(o[a-3],o[a])([[r[a-2],r[a-1]]]);break;case 181:this.$=i.addLocationDataFn(o[a-2],o[a])(new i.If(r[a-1],r[a],{type:r[a-2]}));break;case 182:this.$=i.addLocationDataFn(o[a-4],o[a])(r[a-4].addElse(new i.If(r[a-1],r[a],{type:r[a-2]})));break;case 183:this.$=r[a];break;case 184:this.$=i.addLocationDataFn(o[a-2],o[a])(r[a-2].addElse(r[a]));break;case 185:this.$=i.addLocationDataFn(o[a-2],o[a])(new i.If(r[a],i.addLocationDataFn(o[a-2])(i.Block.wrap([r[a-2]])),{type:r[a-1],statement:!0}));break;case 186:this.$=i.addLocationDataFn(o[a-2],o[a])(new i.If(r[a],i.addLocationDataFn(o[a-2])(i.Block.wrap([r[a-2]])),{type:r[a-1],statement:!0}));break;case 187:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.Op(r[a-1],r[a]));break;case 188:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.Op("-",r[a]));break;case 189:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.Op("+",r[a]));break;case 190:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.Op("--",r[a]));break;case 191:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.Op("++",r[a]));break;case 192:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.Op("--",r[a-1],null,!0));break;case 193:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.Op("++",r[a-1],null,!0));break;case 194:this.$=i.addLocationDataFn(o[a-1],o[a])(new i.Existence(r[a-1]));break;case 195:this.$=i.addLocationDataFn(o[a-2],o[a])(new i.Op("+",r[a-2],r[a]));break;case 196:this.$=i.addLocationDataFn(o[a-2],o[a])(new i.Op("-",r[a-2],r[a]));break;case 197:this.$=i.addLocationDataFn(o[a-2],o[a])(new i.Op(r[a-1],r[a-2],r[a]));break;case 198:this.$=i.addLocationDataFn(o[a-2],o[a])(new i.Op(r[a-1],r[a-2],r[a]));break;case 199:this.$=i.addLocationDataFn(o[a-2],o[a])(new i.Op(r[a-1],r[a-2],r[a]));break;case 200:this.$=i.addLocationDataFn(o[a-2],o[a])(new i.Op(r[a-1],r[a-2],r[a]));break;case 201:this.$=i.addLocationDataFn(o[a-2],o[a])(function(){return"!"===r[a-1].charAt(0)?new i.Op(r[a-1].slice(1),r[a-2],r[a]).invert():new i.Op(r[a-1],r[a-2],r[a])}());break;case 202:this.$=i.addLocationDataFn(o[a-2],o[a])(new i.Assign(r[a-2],r[a],r[a-1]));break;case 203:this.$=i.addLocationDataFn(o[a-4],o[a])(new i.Assign(r[a-4],r[a-1],r[a-3]));break;case 204:this.$=i.addLocationDataFn(o[a-3],o[a])(new i.Assign(r[a-3],r[a],r[a-2]));break;case 205:this.$=i.addLocationDataFn(o[a-2],o[a])(new i.Extends(r[a-2],r[a]))}},table:[{1:[2,1],3:1,4:2,5:3,7:4,8:6,9:7,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,5],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{1:[3]},{1:[2,2],6:[1,74]},{6:[1,75]},{1:[2,4],6:[2,4],26:[2,4],102:[2,4]},{4:77,7:4,8:6,9:7,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,26:[1,76],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{1:[2,7],6:[2,7],26:[2,7],102:[2,7],103:87,104:[1,65],106:[1,66],109:88,110:[1,68],111:69,126:[1,86],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{1:[2,8],6:[2,8],26:[2,8],102:[2,8],103:90,104:[1,65],106:[1,66],109:91,110:[1,68],111:69,126:[1,89]},{1:[2,12],6:[2,12],25:[2,12],26:[2,12],49:[2,12],54:[2,12],57:[2,12],62:93,66:[1,95],67:[1,96],68:[1,97],69:[1,98],70:99,71:[1,100],73:[2,12],74:[1,101],78:[2,12],81:92,84:[1,94],85:[2,108],86:[2,12],91:[2,12],93:[2,12],102:[2,12],104:[2,12],105:[2,12],106:[2,12],110:[2,12],118:[2,12],126:[2,12],128:[2,12],129:[2,12],132:[2,12],133:[2,12],134:[2,12],135:[2,12],136:[2,12],137:[2,12]},{1:[2,13],6:[2,13],25:[2,13],26:[2,13],49:[2,13],54:[2,13],57:[2,13],62:103,66:[1,95],67:[1,96],68:[1,97],69:[1,98],70:99,71:[1,100],73:[2,13],74:[1,101],78:[2,13],81:102,84:[1,94],85:[2,108],86:[2,13],91:[2,13],93:[2,13],102:[2,13],104:[2,13],105:[2,13],106:[2,13],110:[2,13],118:[2,13],126:[2,13],128:[2,13],129:[2,13],132:[2,13],133:[2,13],134:[2,13],135:[2,13],136:[2,13],137:[2,13]},{1:[2,14],6:[2,14],25:[2,14],26:[2,14],49:[2,14],54:[2,14],57:[2,14],73:[2,14],78:[2,14],86:[2,14],91:[2,14],93:[2,14],102:[2,14],104:[2,14],105:[2,14],106:[2,14],110:[2,14],118:[2,14],126:[2,14],128:[2,14],129:[2,14],132:[2,14],133:[2,14],134:[2,14],135:[2,14],136:[2,14],137:[2,14]},{1:[2,15],6:[2,15],25:[2,15],26:[2,15],49:[2,15],54:[2,15],57:[2,15],73:[2,15],78:[2,15],86:[2,15],91:[2,15],93:[2,15],102:[2,15],104:[2,15],105:[2,15],106:[2,15],110:[2,15],118:[2,15],126:[2,15],128:[2,15],129:[2,15],132:[2,15],133:[2,15],134:[2,15],135:[2,15],136:[2,15],137:[2,15]},{1:[2,16],6:[2,16],25:[2,16],26:[2,16],49:[2,16],54:[2,16],57:[2,16],73:[2,16],78:[2,16],86:[2,16],91:[2,16],93:[2,16],102:[2,16],104:[2,16],105:[2,16],106:[2,16],110:[2,16],118:[2,16],126:[2,16],128:[2,16],129:[2,16],132:[2,16],133:[2,16],134:[2,16],135:[2,16],136:[2,16],137:[2,16]},{1:[2,17],6:[2,17],25:[2,17],26:[2,17],49:[2,17],54:[2,17],57:[2,17],73:[2,17],78:[2,17],86:[2,17],91:[2,17],93:[2,17],102:[2,17],104:[2,17],105:[2,17],106:[2,17],110:[2,17],118:[2,17],126:[2,17],128:[2,17],129:[2,17],132:[2,17],133:[2,17],134:[2,17],135:[2,17],136:[2,17],137:[2,17]},{1:[2,18],6:[2,18],25:[2,18],26:[2,18],49:[2,18],54:[2,18],57:[2,18],73:[2,18],78:[2,18],86:[2,18],91:[2,18],93:[2,18],102:[2,18],104:[2,18],105:[2,18],106:[2,18],110:[2,18],118:[2,18],126:[2,18],128:[2,18],129:[2,18],132:[2,18],133:[2,18],134:[2,18],135:[2,18],136:[2,18],137:[2,18]},{1:[2,19],6:[2,19],25:[2,19],26:[2,19],49:[2,19],54:[2,19],57:[2,19],73:[2,19],78:[2,19],86:[2,19],91:[2,19],93:[2,19],102:[2,19],104:[2,19],105:[2,19],106:[2,19],110:[2,19],118:[2,19],126:[2,19],128:[2,19],129:[2,19],132:[2,19],133:[2,19],134:[2,19],135:[2,19],136:[2,19],137:[2,19]},{1:[2,20],6:[2,20],25:[2,20],26:[2,20],49:[2,20],54:[2,20],57:[2,20],73:[2,20],78:[2,20],86:[2,20],91:[2,20],93:[2,20],102:[2,20],104:[2,20],105:[2,20],106:[2,20],110:[2,20],118:[2,20],126:[2,20],128:[2,20],129:[2,20],132:[2,20],133:[2,20],134:[2,20],135:[2,20],136:[2,20],137:[2,20]},{1:[2,21],6:[2,21],25:[2,21],26:[2,21],49:[2,21],54:[2,21],57:[2,21],73:[2,21],78:[2,21],86:[2,21],91:[2,21],93:[2,21],102:[2,21],104:[2,21],105:[2,21],106:[2,21],110:[2,21],118:[2,21],126:[2,21],128:[2,21],129:[2,21],132:[2,21],133:[2,21],134:[2,21],135:[2,21],136:[2,21],137:[2,21]},{1:[2,22],6:[2,22],25:[2,22],26:[2,22],49:[2,22],54:[2,22],57:[2,22],73:[2,22],78:[2,22],86:[2,22],91:[2,22],93:[2,22],102:[2,22],104:[2,22],105:[2,22],106:[2,22],110:[2,22],118:[2,22],126:[2,22],128:[2,22],129:[2,22],132:[2,22],133:[2,22],134:[2,22],135:[2,22],136:[2,22],137:[2,22]},{1:[2,23],6:[2,23],25:[2,23],26:[2,23],49:[2,23],54:[2,23],57:[2,23],73:[2,23],78:[2,23],86:[2,23],91:[2,23],93:[2,23],102:[2,23],104:[2,23],105:[2,23],106:[2,23],110:[2,23],118:[2,23],126:[2,23],128:[2,23],129:[2,23],132:[2,23],133:[2,23],134:[2,23],135:[2,23],136:[2,23],137:[2,23]},{1:[2,9],6:[2,9],26:[2,9],102:[2,9],104:[2,9],106:[2,9],110:[2,9],126:[2,9]},{1:[2,10],6:[2,10],26:[2,10],102:[2,10],104:[2,10],106:[2,10],110:[2,10],126:[2,10]},{1:[2,11],6:[2,11],26:[2,11],102:[2,11],104:[2,11],106:[2,11],110:[2,11],126:[2,11]},{1:[2,75],6:[2,75],25:[2,75],26:[2,75],40:[1,104],49:[2,75],54:[2,75],57:[2,75],66:[2,75],67:[2,75],68:[2,75],69:[2,75],71:[2,75],73:[2,75],74:[2,75],78:[2,75],84:[2,75],85:[2,75],86:[2,75],91:[2,75],93:[2,75],102:[2,75],104:[2,75],105:[2,75],106:[2,75],110:[2,75],118:[2,75],126:[2,75],128:[2,75],129:[2,75],132:[2,75],133:[2,75],134:[2,75],135:[2,75],136:[2,75],137:[2,75]},{1:[2,76],6:[2,76],25:[2,76],26:[2,76],49:[2,76],54:[2,76],57:[2,76],66:[2,76],67:[2,76],68:[2,76],69:[2,76],71:[2,76],73:[2,76],74:[2,76],78:[2,76],84:[2,76],85:[2,76],86:[2,76],91:[2,76],93:[2,76],102:[2,76],104:[2,76],105:[2,76],106:[2,76],110:[2,76],118:[2,76],126:[2,76],128:[2,76],129:[2,76],132:[2,76],133:[2,76],134:[2,76],135:[2,76],136:[2,76],137:[2,76]},{1:[2,77],6:[2,77],25:[2,77],26:[2,77],49:[2,77],54:[2,77],57:[2,77],66:[2,77],67:[2,77],68:[2,77],69:[2,77],71:[2,77],73:[2,77],74:[2,77],78:[2,77],84:[2,77],85:[2,77],86:[2,77],91:[2,77],93:[2,77],102:[2,77],104:[2,77],105:[2,77],106:[2,77],110:[2,77],118:[2,77],126:[2,77],128:[2,77],129:[2,77],132:[2,77],133:[2,77],134:[2,77],135:[2,77],136:[2,77],137:[2,77]},{1:[2,78],6:[2,78],25:[2,78],26:[2,78],49:[2,78],54:[2,78],57:[2,78],66:[2,78],67:[2,78],68:[2,78],69:[2,78],71:[2,78],73:[2,78],74:[2,78],78:[2,78],84:[2,78],85:[2,78],86:[2,78],91:[2,78],93:[2,78],102:[2,78],104:[2,78],105:[2,78],106:[2,78],110:[2,78],118:[2,78],126:[2,78],128:[2,78],129:[2,78],132:[2,78],133:[2,78],134:[2,78],135:[2,78],136:[2,78],137:[2,78]},{1:[2,79],6:[2,79],25:[2,79],26:[2,79],49:[2,79],54:[2,79],57:[2,79],66:[2,79],67:[2,79],68:[2,79],69:[2,79],71:[2,79],73:[2,79],74:[2,79],78:[2,79],84:[2,79],85:[2,79],86:[2,79],91:[2,79],93:[2,79],102:[2,79],104:[2,79],105:[2,79],106:[2,79],110:[2,79],118:[2,79],126:[2,79],128:[2,79],129:[2,79],132:[2,79],133:[2,79],134:[2,79],135:[2,79],136:[2,79],137:[2,79]},{1:[2,106],6:[2,106],25:[2,106],26:[2,106],49:[2,106],54:[2,106],57:[2,106],66:[2,106],67:[2,106],68:[2,106],69:[2,106],71:[2,106],73:[2,106],74:[2,106],78:[2,106],82:105,84:[2,106],85:[1,106],86:[2,106],91:[2,106],93:[2,106],102:[2,106],104:[2,106],105:[2,106],106:[2,106],110:[2,106],118:[2,106],126:[2,106],128:[2,106],129:[2,106],132:[2,106],133:[2,106],134:[2,106],135:[2,106],136:[2,106],137:[2,106]},{6:[2,55],25:[2,55],27:110,28:[1,73],44:111,48:107,49:[2,55],54:[2,55],55:108,56:109,58:112,59:113,76:[1,70],89:[1,114],90:[1,115]},{5:116,25:[1,5]},{8:117,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{8:119,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{8:120,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{13:122,14:123,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:124,44:63,58:47,59:48,61:121,63:25,64:26,65:27,76:[1,70],83:[1,28],88:[1,58],89:[1,59],90:[1,57],101:[1,56]},{13:122,14:123,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:124,44:63,58:47,59:48,61:125,63:25,64:26,65:27,76:[1,70],83:[1,28],88:[1,58],89:[1,59],90:[1,57],101:[1,56]},{1:[2,72],6:[2,72],25:[2,72],26:[2,72],40:[2,72],49:[2,72],54:[2,72],57:[2,72],66:[2,72],67:[2,72],68:[2,72],69:[2,72],71:[2,72],73:[2,72],74:[2,72],78:[2,72],80:[1,129],84:[2,72],85:[2,72],86:[2,72],91:[2,72],93:[2,72],102:[2,72],104:[2,72],105:[2,72],106:[2,72],110:[2,72],118:[2,72],126:[2,72],128:[2,72],129:[2,72],130:[1,126],131:[1,127],132:[2,72],133:[2,72],134:[2,72],135:[2,72],136:[2,72],137:[2,72],138:[1,128]},{1:[2,183],6:[2,183],25:[2,183],26:[2,183],49:[2,183],54:[2,183],57:[2,183],73:[2,183],78:[2,183],86:[2,183],91:[2,183],93:[2,183],102:[2,183],104:[2,183],105:[2,183],106:[2,183],110:[2,183],118:[2,183],121:[1,130],126:[2,183],128:[2,183],129:[2,183],132:[2,183],133:[2,183],134:[2,183],135:[2,183],136:[2,183],137:[2,183]},{5:131,25:[1,5]},{5:132,25:[1,5]},{1:[2,150],6:[2,150],25:[2,150],26:[2,150],49:[2,150],54:[2,150],57:[2,150],73:[2,150],78:[2,150],86:[2,150],91:[2,150],93:[2,150],102:[2,150],104:[2,150],105:[2,150],106:[2,150],110:[2,150],118:[2,150],126:[2,150],128:[2,150],129:[2,150],132:[2,150],133:[2,150],134:[2,150],135:[2,150],136:[2,150],137:[2,150]},{5:133,25:[1,5]},{8:134,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,135],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{1:[2,96],5:136,6:[2,96],13:122,14:123,25:[1,5],26:[2,96],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:124,44:63,49:[2,96],54:[2,96],57:[2,96],58:47,59:48,61:138,63:25,64:26,65:27,73:[2,96],76:[1,70],78:[2,96],80:[1,137],83:[1,28],86:[2,96],88:[1,58],89:[1,59],90:[1,57],91:[2,96],93:[2,96],101:[1,56],102:[2,96],104:[2,96],105:[2,96],106:[2,96],110:[2,96],118:[2,96],126:[2,96],128:[2,96],129:[2,96],132:[2,96],133:[2,96],134:[2,96],135:[2,96],136:[2,96],137:[2,96]},{8:139,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{1:[2,47],6:[2,47],8:140,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,26:[2,47],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],102:[2,47],103:39,104:[2,47],106:[2,47],107:40,108:[1,67],109:41,110:[2,47],111:69,119:[1,42],124:37,125:[1,64],126:[2,47],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{1:[2,48],6:[2,48],25:[2,48],26:[2,48],54:[2,48],78:[2,48],102:[2,48],104:[2,48],106:[2,48],110:[2,48],126:[2,48]},{1:[2,73],6:[2,73],25:[2,73],26:[2,73],40:[2,73],49:[2,73],54:[2,73],57:[2,73],66:[2,73],67:[2,73],68:[2,73],69:[2,73],71:[2,73],73:[2,73],74:[2,73],78:[2,73],84:[2,73],85:[2,73],86:[2,73],91:[2,73],93:[2,73],102:[2,73],104:[2,73],105:[2,73],106:[2,73],110:[2,73],118:[2,73],126:[2,73],128:[2,73],129:[2,73],132:[2,73],133:[2,73],134:[2,73],135:[2,73],136:[2,73],137:[2,73]},{1:[2,74],6:[2,74],25:[2,74],26:[2,74],40:[2,74],49:[2,74],54:[2,74],57:[2,74],66:[2,74],67:[2,74],68:[2,74],69:[2,74],71:[2,74],73:[2,74],74:[2,74],78:[2,74],84:[2,74],85:[2,74],86:[2,74],91:[2,74],93:[2,74],102:[2,74],104:[2,74],105:[2,74],106:[2,74],110:[2,74],118:[2,74],126:[2,74],128:[2,74],129:[2,74],132:[2,74],133:[2,74],134:[2,74],135:[2,74],136:[2,74],137:[2,74]},{1:[2,29],6:[2,29],25:[2,29],26:[2,29],49:[2,29],54:[2,29],57:[2,29],66:[2,29],67:[2,29],68:[2,29],69:[2,29],71:[2,29],73:[2,29],74:[2,29],78:[2,29],84:[2,29],85:[2,29],86:[2,29],91:[2,29],93:[2,29],102:[2,29],104:[2,29],105:[2,29],106:[2,29],110:[2,29],118:[2,29],126:[2,29],128:[2,29],129:[2,29],132:[2,29],133:[2,29],134:[2,29],135:[2,29],136:[2,29],137:[2,29]},{1:[2,30],6:[2,30],25:[2,30],26:[2,30],49:[2,30],54:[2,30],57:[2,30],66:[2,30],67:[2,30],68:[2,30],69:[2,30],71:[2,30],73:[2,30],74:[2,30],78:[2,30],84:[2,30],85:[2,30],86:[2,30],91:[2,30],93:[2,30],102:[2,30],104:[2,30],105:[2,30],106:[2,30],110:[2,30],118:[2,30],126:[2,30],128:[2,30],129:[2,30],132:[2,30],133:[2,30],134:[2,30],135:[2,30],136:[2,30],137:[2,30]},{1:[2,31],6:[2,31],25:[2,31],26:[2,31],49:[2,31],54:[2,31],57:[2,31],66:[2,31],67:[2,31],68:[2,31],69:[2,31],71:[2,31],73:[2,31],74:[2,31],78:[2,31],84:[2,31],85:[2,31],86:[2,31],91:[2,31],93:[2,31],102:[2,31],104:[2,31],105:[2,31],106:[2,31],110:[2,31],118:[2,31],126:[2,31],128:[2,31],129:[2,31],132:[2,31],133:[2,31],134:[2,31],135:[2,31],136:[2,31],137:[2,31]},{1:[2,32],6:[2,32],25:[2,32],26:[2,32],49:[2,32],54:[2,32],57:[2,32],66:[2,32],67:[2,32],68:[2,32],69:[2,32],71:[2,32],73:[2,32],74:[2,32],78:[2,32],84:[2,32],85:[2,32],86:[2,32],91:[2,32],93:[2,32],102:[2,32],104:[2,32],105:[2,32],106:[2,32],110:[2,32],118:[2,32],126:[2,32],128:[2,32],129:[2,32],132:[2,32],133:[2,32],134:[2,32],135:[2,32],136:[2,32],137:[2,32]},{1:[2,33],6:[2,33],25:[2,33],26:[2,33],49:[2,33],54:[2,33],57:[2,33],66:[2,33],67:[2,33],68:[2,33],69:[2,33],71:[2,33],73:[2,33],74:[2,33],78:[2,33],84:[2,33],85:[2,33],86:[2,33],91:[2,33],93:[2,33],102:[2,33],104:[2,33],105:[2,33],106:[2,33],110:[2,33],118:[2,33],126:[2,33],128:[2,33],129:[2,33],132:[2,33],133:[2,33],134:[2,33],135:[2,33],136:[2,33],137:[2,33]},{1:[2,34],6:[2,34],25:[2,34],26:[2,34],49:[2,34],54:[2,34],57:[2,34],66:[2,34],67:[2,34],68:[2,34],69:[2,34],71:[2,34],73:[2,34],74:[2,34],78:[2,34],84:[2,34],85:[2,34],86:[2,34],91:[2,34],93:[2,34],102:[2,34],104:[2,34],105:[2,34],106:[2,34],110:[2,34],118:[2,34],126:[2,34],128:[2,34],129:[2,34],132:[2,34],133:[2,34],134:[2,34],135:[2,34],136:[2,34],137:[2,34]},{1:[2,35],6:[2,35],25:[2,35],26:[2,35],49:[2,35],54:[2,35],57:[2,35],66:[2,35],67:[2,35],68:[2,35],69:[2,35],71:[2,35],73:[2,35],74:[2,35],78:[2,35],84:[2,35],85:[2,35],86:[2,35],91:[2,35],93:[2,35],102:[2,35],104:[2,35],105:[2,35],106:[2,35],110:[2,35],118:[2,35],126:[2,35],128:[2,35],129:[2,35],132:[2,35],133:[2,35],134:[2,35],135:[2,35],136:[2,35],137:[2,35]},{4:141,7:4,8:6,9:7,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,142],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{8:143,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,147],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,60:148,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],87:145,88:[1,58],89:[1,59],90:[1,57],91:[1,144],94:146,96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{1:[2,112],6:[2,112],25:[2,112],26:[2,112],49:[2,112],54:[2,112],57:[2,112],66:[2,112],67:[2,112],68:[2,112],69:[2,112],71:[2,112],73:[2,112],74:[2,112],78:[2,112],84:[2,112],85:[2,112],86:[2,112],91:[2,112],93:[2,112],102:[2,112],104:[2,112],105:[2,112],106:[2,112],110:[2,112],118:[2,112],126:[2,112],128:[2,112],129:[2,112],132:[2,112],133:[2,112],134:[2,112],135:[2,112],136:[2,112],137:[2,112]},{1:[2,113],6:[2,113],25:[2,113],26:[2,113],27:149,28:[1,73],49:[2,113],54:[2,113],57:[2,113],66:[2,113],67:[2,113],68:[2,113],69:[2,113],71:[2,113],73:[2,113],74:[2,113],78:[2,113],84:[2,113],85:[2,113],86:[2,113],91:[2,113],93:[2,113],102:[2,113],104:[2,113],105:[2,113],106:[2,113],110:[2,113],118:[2,113],126:[2,113],128:[2,113],129:[2,113],132:[2,113],133:[2,113],134:[2,113],135:[2,113],136:[2,113],137:[2,113]},{25:[2,51]},{25:[2,52]},{1:[2,68],6:[2,68],25:[2,68],26:[2,68],40:[2,68],49:[2,68],54:[2,68],57:[2,68],66:[2,68],67:[2,68],68:[2,68],69:[2,68],71:[2,68],73:[2,68],74:[2,68],78:[2,68],80:[2,68],84:[2,68],85:[2,68],86:[2,68],91:[2,68],93:[2,68],102:[2,68],104:[2,68],105:[2,68],106:[2,68],110:[2,68],118:[2,68],126:[2,68],128:[2,68],129:[2,68],130:[2,68],131:[2,68],132:[2,68],133:[2,68],134:[2,68],135:[2,68],136:[2,68],137:[2,68],138:[2,68]},{1:[2,71],6:[2,71],25:[2,71],26:[2,71],40:[2,71],49:[2,71],54:[2,71],57:[2,71],66:[2,71],67:[2,71],68:[2,71],69:[2,71],71:[2,71],73:[2,71],74:[2,71],78:[2,71],80:[2,71],84:[2,71],85:[2,71],86:[2,71],91:[2,71],93:[2,71],102:[2,71],104:[2,71],105:[2,71],106:[2,71],110:[2,71],118:[2,71],126:[2,71],128:[2,71],129:[2,71],130:[2,71],131:[2,71],132:[2,71],133:[2,71],134:[2,71],135:[2,71],136:[2,71],137:[2,71],138:[2,71]},{8:150,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{8:151,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{8:152,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{5:153,8:154,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,5],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{27:159,28:[1,73],44:160,58:161,59:162,64:155,76:[1,70],89:[1,114],90:[1,57],113:156,114:[1,157],115:158},{112:163,116:[1,164],117:[1,165]},{6:[2,91],11:169,25:[2,91],27:170,28:[1,73],29:171,30:[1,71],31:[1,72],41:167,42:168,44:172,46:[1,46],54:[2,91],77:166,78:[2,91],89:[1,114]},{1:[2,27],6:[2,27],25:[2,27],26:[2,27],43:[2,27],49:[2,27],54:[2,27],57:[2,27],66:[2,27],67:[2,27],68:[2,27],69:[2,27],71:[2,27],73:[2,27],74:[2,27],78:[2,27],84:[2,27],85:[2,27],86:[2,27],91:[2,27],93:[2,27],102:[2,27],104:[2,27],105:[2,27],106:[2,27],110:[2,27],118:[2,27],126:[2,27],128:[2,27],129:[2,27],132:[2,27],133:[2,27],134:[2,27],135:[2,27],136:[2,27],137:[2,27]},{1:[2,28],6:[2,28],25:[2,28],26:[2,28],43:[2,28],49:[2,28],54:[2,28],57:[2,28],66:[2,28],67:[2,28],68:[2,28],69:[2,28],71:[2,28],73:[2,28],74:[2,28],78:[2,28],84:[2,28],85:[2,28],86:[2,28],91:[2,28],93:[2,28],102:[2,28],104:[2,28],105:[2,28],106:[2,28],110:[2,28],118:[2,28],126:[2,28],128:[2,28],129:[2,28],132:[2,28],133:[2,28],134:[2,28],135:[2,28],136:[2,28],137:[2,28]},{1:[2,26],6:[2,26],25:[2,26],26:[2,26],40:[2,26],43:[2,26],49:[2,26],54:[2,26],57:[2,26],66:[2,26],67:[2,26],68:[2,26],69:[2,26],71:[2,26],73:[2,26],74:[2,26],78:[2,26],80:[2,26],84:[2,26],85:[2,26],86:[2,26],91:[2,26],93:[2,26],102:[2,26],104:[2,26],105:[2,26],106:[2,26],110:[2,26],116:[2,26],117:[2,26],118:[2,26],126:[2,26],128:[2,26],129:[2,26],130:[2,26],131:[2,26],132:[2,26],133:[2,26],134:[2,26],135:[2,26],136:[2,26],137:[2,26],138:[2,26]},{1:[2,6],6:[2,6],7:173,8:6,9:7,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,26:[2,6],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],102:[2,6],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{1:[2,3]},{1:[2,24],6:[2,24],25:[2,24],26:[2,24],49:[2,24],54:[2,24],57:[2,24],73:[2,24],78:[2,24],86:[2,24],91:[2,24],93:[2,24],98:[2,24],99:[2,24],102:[2,24],104:[2,24],105:[2,24],106:[2,24],110:[2,24],118:[2,24],121:[2,24],123:[2,24],126:[2,24],128:[2,24],129:[2,24],132:[2,24],133:[2,24],134:[2,24],135:[2,24],136:[2,24],137:[2,24]},{6:[1,74],26:[1,174]},{1:[2,194],6:[2,194],25:[2,194],26:[2,194],49:[2,194],54:[2,194],57:[2,194],73:[2,194],78:[2,194],86:[2,194],91:[2,194],93:[2,194],102:[2,194],104:[2,194],105:[2,194],106:[2,194],110:[2,194],118:[2,194],126:[2,194],128:[2,194],129:[2,194],132:[2,194],133:[2,194],134:[2,194],135:[2,194],136:[2,194],137:[2,194]},{8:175,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{8:176,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{8:177,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{8:178,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{8:179,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{8:180,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{8:181,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{8:182,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{1:[2,149],6:[2,149],25:[2,149],26:[2,149],49:[2,149],54:[2,149],57:[2,149],73:[2,149],78:[2,149],86:[2,149],91:[2,149],93:[2,149],102:[2,149],104:[2,149],105:[2,149],106:[2,149],110:[2,149],118:[2,149],126:[2,149],128:[2,149],129:[2,149],132:[2,149],133:[2,149],134:[2,149],135:[2,149],136:[2,149],137:[2,149]},{1:[2,154],6:[2,154],25:[2,154],26:[2,154],49:[2,154],54:[2,154],57:[2,154],73:[2,154],78:[2,154],86:[2,154],91:[2,154],93:[2,154],102:[2,154],104:[2,154],105:[2,154],106:[2,154],110:[2,154],118:[2,154],126:[2,154],128:[2,154],129:[2,154],132:[2,154],133:[2,154],134:[2,154],135:[2,154],136:[2,154],137:[2,154]},{8:183,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{1:[2,148],6:[2,148],25:[2,148],26:[2,148],49:[2,148],54:[2,148],57:[2,148],73:[2,148],78:[2,148],86:[2,148],91:[2,148],93:[2,148],102:[2,148],104:[2,148],105:[2,148],106:[2,148],110:[2,148],118:[2,148],126:[2,148],128:[2,148],129:[2,148],132:[2,148],133:[2,148],134:[2,148],135:[2,148],136:[2,148],137:[2,148]},{1:[2,153],6:[2,153],25:[2,153],26:[2,153],49:[2,153],54:[2,153],57:[2,153],73:[2,153],78:[2,153],86:[2,153],91:[2,153],93:[2,153],102:[2,153],104:[2,153],105:[2,153],106:[2,153],110:[2,153],118:[2,153],126:[2,153],128:[2,153],129:[2,153],132:[2,153],133:[2,153],134:[2,153],135:[2,153],136:[2,153],137:[2,153]},{82:184,85:[1,106]},{1:[2,69],6:[2,69],25:[2,69],26:[2,69],40:[2,69],49:[2,69],54:[2,69],57:[2,69],66:[2,69],67:[2,69],68:[2,69],69:[2,69],71:[2,69],73:[2,69],74:[2,69],78:[2,69],80:[2,69],84:[2,69],85:[2,69],86:[2,69],91:[2,69],93:[2,69],102:[2,69],104:[2,69],105:[2,69],106:[2,69],110:[2,69],118:[2,69],126:[2,69],128:[2,69],129:[2,69],130:[2,69],131:[2,69],132:[2,69],133:[2,69],134:[2,69],135:[2,69],136:[2,69],137:[2,69],138:[2,69]},{85:[2,109]},{27:185,28:[1,73]},{27:186,28:[1,73]},{1:[2,84],6:[2,84],25:[2,84],26:[2,84],27:187,28:[1,73],40:[2,84],49:[2,84],54:[2,84],57:[2,84],66:[2,84],67:[2,84],68:[2,84],69:[2,84],71:[2,84],73:[2,84],74:[2,84],78:[2,84],80:[2,84],84:[2,84],85:[2,84],86:[2,84],91:[2,84],93:[2,84],102:[2,84],104:[2,84],105:[2,84],106:[2,84],110:[2,84],118:[2,84],126:[2,84],128:[2,84],129:[2,84],130:[2,84],131:[2,84],132:[2,84],133:[2,84],134:[2,84],135:[2,84],136:[2,84],137:[2,84],138:[2,84]},{27:188,28:[1,73]},{1:[2,85],6:[2,85],25:[2,85],26:[2,85],40:[2,85],49:[2,85],54:[2,85],57:[2,85],66:[2,85],67:[2,85],68:[2,85],69:[2,85],71:[2,85],73:[2,85],74:[2,85],78:[2,85],80:[2,85],84:[2,85],85:[2,85],86:[2,85],91:[2,85],93:[2,85],102:[2,85],104:[2,85],105:[2,85],106:[2,85],110:[2,85],118:[2,85],126:[2,85],128:[2,85],129:[2,85],130:[2,85],131:[2,85],132:[2,85],133:[2,85],134:[2,85],135:[2,85],136:[2,85],137:[2,85],138:[2,85]},{8:190,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],57:[1,194],58:47,59:48,61:36,63:25,64:26,65:27,72:189,75:191,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],92:192,93:[1,193],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{70:195,71:[1,100],74:[1,101]},{82:196,85:[1,106]},{1:[2,70],6:[2,70],25:[2,70],26:[2,70],40:[2,70],49:[2,70],54:[2,70],57:[2,70],66:[2,70],67:[2,70],68:[2,70],69:[2,70],71:[2,70],73:[2,70],74:[2,70],78:[2,70],80:[2,70],84:[2,70],85:[2,70],86:[2,70],91:[2,70],93:[2,70],102:[2,70],104:[2,70],105:[2,70],106:[2,70],110:[2,70],118:[2,70],126:[2,70],128:[2,70],129:[2,70],130:[2,70],131:[2,70],132:[2,70],133:[2,70],134:[2,70],135:[2,70],136:[2,70],137:[2,70],138:[2,70]},{6:[1,198],8:197,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,199],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{1:[2,107],6:[2,107],25:[2,107],26:[2,107],49:[2,107],54:[2,107],57:[2,107],66:[2,107],67:[2,107],68:[2,107],69:[2,107],71:[2,107],73:[2,107],74:[2,107],78:[2,107],84:[2,107],85:[2,107],86:[2,107],91:[2,107],93:[2,107],102:[2,107],104:[2,107],105:[2,107],106:[2,107],110:[2,107],118:[2,107],126:[2,107],128:[2,107],129:[2,107],132:[2,107],133:[2,107],134:[2,107],135:[2,107],136:[2,107],137:[2,107]},{8:202,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,147],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,60:148,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],86:[1,200],87:201,88:[1,58],89:[1,59],90:[1,57],94:146,96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{6:[2,53],25:[2,53],49:[1,203],53:205,54:[1,204]},{6:[2,56],25:[2,56],26:[2,56],49:[2,56],54:[2,56]},{6:[2,60],25:[2,60],26:[2,60],40:[1,207],49:[2,60],54:[2,60],57:[1,206]},{6:[2,63],25:[2,63],26:[2,63],40:[2,63],49:[2,63],54:[2,63],57:[2,63]},{6:[2,64],25:[2,64],26:[2,64],40:[2,64],49:[2,64],54:[2,64],57:[2,64]},{6:[2,65],25:[2,65],26:[2,65],40:[2,65],49:[2,65],54:[2,65],57:[2,65]},{6:[2,66],25:[2,66],26:[2,66],40:[2,66],49:[2,66],54:[2,66],57:[2,66]},{27:149,28:[1,73]},{8:202,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,147],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,60:148,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],87:145,88:[1,58],89:[1,59],90:[1,57],91:[1,144],94:146,96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{1:[2,50],6:[2,50],25:[2,50],26:[2,50],49:[2,50],54:[2,50],57:[2,50],73:[2,50],78:[2,50],86:[2,50],91:[2,50],93:[2,50],102:[2,50],104:[2,50],105:[2,50],106:[2,50],110:[2,50],118:[2,50],126:[2,50],128:[2,50],129:[2,50],132:[2,50],133:[2,50],134:[2,50],135:[2,50],136:[2,50],137:[2,50]},{1:[2,187],6:[2,187],25:[2,187],26:[2,187],49:[2,187],54:[2,187],57:[2,187],73:[2,187],78:[2,187],86:[2,187],91:[2,187],93:[2,187],102:[2,187],103:87,104:[2,187],105:[2,187],106:[2,187],109:88,110:[2,187],111:69,118:[2,187],126:[2,187],128:[2,187],129:[2,187],132:[1,78],133:[2,187],134:[2,187],135:[2,187],136:[2,187],137:[2,187]},{103:90,104:[1,65],106:[1,66],109:91,110:[1,68],111:69,126:[1,89]},{1:[2,188],6:[2,188],25:[2,188],26:[2,188],49:[2,188],54:[2,188],57:[2,188],73:[2,188],78:[2,188],86:[2,188],91:[2,188],93:[2,188],102:[2,188],103:87,104:[2,188],105:[2,188],106:[2,188],109:88,110:[2,188],111:69,118:[2,188],126:[2,188],128:[2,188],129:[2,188],132:[1,78],133:[2,188],134:[2,188],135:[2,188],136:[2,188],137:[2,188]},{1:[2,189],6:[2,189],25:[2,189],26:[2,189],49:[2,189],54:[2,189],57:[2,189],73:[2,189],78:[2,189],86:[2,189],91:[2,189],93:[2,189],102:[2,189],103:87,104:[2,189],105:[2,189],106:[2,189],109:88,110:[2,189],111:69,118:[2,189],126:[2,189],128:[2,189],129:[2,189],132:[1,78],133:[2,189],134:[2,189],135:[2,189],136:[2,189],137:[2,189]},{1:[2,190],6:[2,190],25:[2,190],26:[2,190],49:[2,190],54:[2,190],57:[2,190],66:[2,72],67:[2,72],68:[2,72],69:[2,72],71:[2,72],73:[2,190],74:[2,72],78:[2,190],84:[2,72],85:[2,72],86:[2,190],91:[2,190],93:[2,190],102:[2,190],104:[2,190],105:[2,190],106:[2,190],110:[2,190],118:[2,190],126:[2,190],128:[2,190],129:[2,190],132:[2,190],133:[2,190],134:[2,190],135:[2,190],136:[2,190],137:[2,190]},{62:93,66:[1,95],67:[1,96],68:[1,97],69:[1,98],70:99,71:[1,100],74:[1,101],81:92,84:[1,94],85:[2,108]},{62:103,66:[1,95],67:[1,96],68:[1,97],69:[1,98],70:99,71:[1,100],74:[1,101],81:102,84:[1,94],85:[2,108]},{66:[2,75],67:[2,75],68:[2,75],69:[2,75],71:[2,75],74:[2,75],84:[2,75],85:[2,75]},{1:[2,191],6:[2,191],25:[2,191],26:[2,191],49:[2,191],54:[2,191],57:[2,191],66:[2,72],67:[2,72],68:[2,72],69:[2,72],71:[2,72],73:[2,191],74:[2,72],78:[2,191],84:[2,72],85:[2,72],86:[2,191],91:[2,191],93:[2,191],102:[2,191],104:[2,191],105:[2,191],106:[2,191],110:[2,191],118:[2,191],126:[2,191],128:[2,191],129:[2,191],132:[2,191],133:[2,191],134:[2,191],135:[2,191],136:[2,191],137:[2,191]},{1:[2,192],6:[2,192],25:[2,192],26:[2,192],49:[2,192],54:[2,192],57:[2,192],73:[2,192],78:[2,192],86:[2,192],91:[2,192],93:[2,192],102:[2,192],104:[2,192],105:[2,192],106:[2,192],110:[2,192],118:[2,192],126:[2,192],128:[2,192],129:[2,192],132:[2,192],133:[2,192],134:[2,192],135:[2,192],136:[2,192],137:[2,192]},{1:[2,193],6:[2,193],25:[2,193],26:[2,193],49:[2,193],54:[2,193],57:[2,193],73:[2,193],78:[2,193],86:[2,193],91:[2,193],93:[2,193],102:[2,193],104:[2,193],105:[2,193],106:[2,193],110:[2,193],118:[2,193],126:[2,193],128:[2,193],129:[2,193],132:[2,193],133:[2,193],134:[2,193],135:[2,193],136:[2,193],137:[2,193]},{6:[1,210],8:208,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,209],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{8:211,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{5:212,25:[1,5],125:[1,213]},{1:[2,133],6:[2,133],25:[2,133],26:[2,133],49:[2,133],54:[2,133],57:[2,133],73:[2,133],78:[2,133],86:[2,133],91:[2,133],93:[2,133],97:214,98:[1,215],99:[1,216],102:[2,133],104:[2,133],105:[2,133],106:[2,133],110:[2,133],118:[2,133],126:[2,133],128:[2,133],129:[2,133],132:[2,133],133:[2,133],134:[2,133],135:[2,133],136:[2,133],137:[2,133]},{1:[2,147],6:[2,147],25:[2,147],26:[2,147],49:[2,147],54:[2,147],57:[2,147],73:[2,147],78:[2,147],86:[2,147],91:[2,147],93:[2,147],102:[2,147],104:[2,147],105:[2,147],106:[2,147],110:[2,147],118:[2,147],126:[2,147],128:[2,147],129:[2,147],132:[2,147],133:[2,147],134:[2,147],135:[2,147],136:[2,147],137:[2,147]},{1:[2,155],6:[2,155],25:[2,155],26:[2,155],49:[2,155],54:[2,155],57:[2,155],73:[2,155],78:[2,155],86:[2,155],91:[2,155],93:[2,155],102:[2,155],104:[2,155],105:[2,155],106:[2,155],110:[2,155],118:[2,155],126:[2,155],128:[2,155],129:[2,155],132:[2,155],133:[2,155],134:[2,155],135:[2,155],136:[2,155],137:[2,155]},{25:[1,217],103:87,104:[1,65],106:[1,66],109:88,110:[1,68],111:69,126:[1,86],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{120:218,122:219,123:[1,220]},{1:[2,97],6:[2,97],25:[2,97],26:[2,97],49:[2,97],54:[2,97],57:[2,97],73:[2,97],78:[2,97],86:[2,97],91:[2,97],93:[2,97],102:[2,97],104:[2,97],105:[2,97],106:[2,97],110:[2,97],118:[2,97],126:[2,97],128:[2,97],129:[2,97],132:[2,97],133:[2,97],134:[2,97],135:[2,97],136:[2,97],137:[2,97]},{8:221,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{1:[2,100],5:222,6:[2,100],25:[1,5],26:[2,100],49:[2,100],54:[2,100],57:[2,100],66:[2,72],67:[2,72],68:[2,72],69:[2,72],71:[2,72],73:[2,100],74:[2,72],78:[2,100],80:[1,223],84:[2,72],85:[2,72],86:[2,100],91:[2,100],93:[2,100],102:[2,100],104:[2,100],105:[2,100],106:[2,100],110:[2,100],118:[2,100],126:[2,100],128:[2,100],129:[2,100],132:[2,100],133:[2,100],134:[2,100],135:[2,100],136:[2,100],137:[2,100]},{1:[2,140],6:[2,140],25:[2,140],26:[2,140],49:[2,140],54:[2,140],57:[2,140],73:[2,140],78:[2,140],86:[2,140],91:[2,140],93:[2,140],102:[2,140],103:87,104:[2,140],105:[2,140],106:[2,140],109:88,110:[2,140],111:69,118:[2,140],126:[2,140],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{1:[2,46],6:[2,46],26:[2,46],102:[2,46],103:87,104:[2,46],106:[2,46],109:88,110:[2,46],111:69,126:[2,46],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{6:[1,74],102:[1,224]},{4:225,7:4,8:6,9:7,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{6:[2,129],25:[2,129],54:[2,129],57:[1,227],91:[2,129],92:226,93:[1,193],103:87,104:[1,65],106:[1,66],109:88,110:[1,68],111:69,126:[1,86],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{1:[2,115],6:[2,115],25:[2,115],26:[2,115],40:[2,115],49:[2,115],54:[2,115],57:[2,115],66:[2,115],67:[2,115],68:[2,115],69:[2,115],71:[2,115],73:[2,115],74:[2,115],78:[2,115],84:[2,115],85:[2,115],86:[2,115],91:[2,115],93:[2,115],102:[2,115],104:[2,115],105:[2,115],106:[2,115],110:[2,115],116:[2,115],117:[2,115],118:[2,115],126:[2,115],128:[2,115],129:[2,115],132:[2,115],133:[2,115],134:[2,115],135:[2,115],136:[2,115],137:[2,115]},{6:[2,53],25:[2,53],53:228,54:[1,229],91:[2,53]},{6:[2,124],25:[2,124],26:[2,124],54:[2,124],86:[2,124],91:[2,124]},{8:202,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,147],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,60:148,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],87:230,88:[1,58],89:[1,59],90:[1,57],94:146,96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{6:[2,130],25:[2,130],26:[2,130],54:[2,130],86:[2,130],91:[2,130]},{1:[2,114],6:[2,114],25:[2,114],26:[2,114],40:[2,114],43:[2,114],49:[2,114],54:[2,114],57:[2,114],66:[2,114],67:[2,114],68:[2,114],69:[2,114],71:[2,114],73:[2,114],74:[2,114],78:[2,114],80:[2,114],84:[2,114],85:[2,114],86:[2,114],91:[2,114],93:[2,114],102:[2,114],104:[2,114],105:[2,114],106:[2,114],110:[2,114],116:[2,114],117:[2,114],118:[2,114],126:[2,114],128:[2,114],129:[2,114],130:[2,114],131:[2,114],132:[2,114],133:[2,114],134:[2,114],135:[2,114],136:[2,114],137:[2,114],138:[2,114]},{5:231,25:[1,5],103:87,104:[1,65],106:[1,66],109:88,110:[1,68],111:69,126:[1,86],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{1:[2,143],6:[2,143],25:[2,143],26:[2,143],49:[2,143],54:[2,143],57:[2,143],73:[2,143],78:[2,143],86:[2,143],91:[2,143],93:[2,143],102:[2,143],103:87,104:[1,65],105:[1,232],106:[1,66],109:88,110:[1,68],111:69,118:[2,143],126:[2,143],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{1:[2,145],6:[2,145],25:[2,145],26:[2,145],49:[2,145],54:[2,145],57:[2,145],73:[2,145],78:[2,145],86:[2,145],91:[2,145],93:[2,145],102:[2,145],103:87,104:[1,65],105:[1,233],106:[1,66],109:88,110:[1,68],111:69,118:[2,145],126:[2,145],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{1:[2,151],6:[2,151],25:[2,151],26:[2,151],49:[2,151],54:[2,151],57:[2,151],73:[2,151],78:[2,151],86:[2,151],91:[2,151],93:[2,151],102:[2,151],104:[2,151],105:[2,151],106:[2,151],110:[2,151],118:[2,151],126:[2,151],128:[2,151],129:[2,151],132:[2,151],133:[2,151],134:[2,151],135:[2,151],136:[2,151],137:[2,151]},{1:[2,152],6:[2,152],25:[2,152],26:[2,152],49:[2,152],54:[2,152],57:[2,152],73:[2,152],78:[2,152],86:[2,152],91:[2,152],93:[2,152],102:[2,152],103:87,104:[1,65],105:[2,152],106:[1,66],109:88,110:[1,68],111:69,118:[2,152],126:[2,152],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{1:[2,156],6:[2,156],25:[2,156],26:[2,156],49:[2,156],54:[2,156],57:[2,156],73:[2,156],78:[2,156],86:[2,156],91:[2,156],93:[2,156],102:[2,156],104:[2,156],105:[2,156],106:[2,156],110:[2,156],118:[2,156],126:[2,156],128:[2,156],129:[2,156],132:[2,156],133:[2,156],134:[2,156],135:[2,156],136:[2,156],137:[2,156]},{116:[2,158],117:[2,158]},{27:159,28:[1,73],44:160,58:161,59:162,76:[1,70],89:[1,114],90:[1,115],113:234,115:158},{54:[1,235],116:[2,164],117:[2,164]},{54:[2,160],116:[2,160],117:[2,160]},{54:[2,161],116:[2,161],117:[2,161]},{54:[2,162],116:[2,162],117:[2,162]},{54:[2,163],116:[2,163],117:[2,163]},{1:[2,157],6:[2,157],25:[2,157],26:[2,157],49:[2,157],54:[2,157],57:[2,157],73:[2,157],78:[2,157],86:[2,157],91:[2,157],93:[2,157],102:[2,157],104:[2,157],105:[2,157],106:[2,157],110:[2,157],118:[2,157],126:[2,157],128:[2,157],129:[2,157],132:[2,157],133:[2,157],134:[2,157],135:[2,157],136:[2,157],137:[2,157]},{8:236,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{8:237,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{6:[2,53],25:[2,53],53:238,54:[1,239],78:[2,53]},{6:[2,92],25:[2,92],26:[2,92],54:[2,92],78:[2,92]},{6:[2,39],25:[2,39],26:[2,39],43:[1,240],54:[2,39],78:[2,39]},{6:[2,42],25:[2,42],26:[2,42],54:[2,42],78:[2,42]},{6:[2,43],25:[2,43],26:[2,43],43:[2,43],54:[2,43],78:[2,43]},{6:[2,44],25:[2,44],26:[2,44],43:[2,44],54:[2,44],78:[2,44]},{6:[2,45],25:[2,45],26:[2,45],43:[2,45],54:[2,45],78:[2,45]},{1:[2,5],6:[2,5],26:[2,5],102:[2,5]},{1:[2,25],6:[2,25],25:[2,25],26:[2,25],49:[2,25],54:[2,25],57:[2,25],73:[2,25],78:[2,25],86:[2,25],91:[2,25],93:[2,25],98:[2,25],99:[2,25],102:[2,25],104:[2,25],105:[2,25],106:[2,25],110:[2,25],118:[2,25],121:[2,25],123:[2,25],126:[2,25],128:[2,25],129:[2,25],132:[2,25],133:[2,25],134:[2,25],135:[2,25],136:[2,25],137:[2,25]},{1:[2,195],6:[2,195],25:[2,195],26:[2,195],49:[2,195],54:[2,195],57:[2,195],73:[2,195],78:[2,195],86:[2,195],91:[2,195],93:[2,195],102:[2,195],103:87,104:[2,195],105:[2,195],106:[2,195],109:88,110:[2,195],111:69,118:[2,195],126:[2,195],128:[2,195],129:[2,195],132:[1,78],133:[1,81],134:[2,195],135:[2,195],136:[2,195],137:[2,195]},{1:[2,196],6:[2,196],25:[2,196],26:[2,196],49:[2,196],54:[2,196],57:[2,196],73:[2,196],78:[2,196],86:[2,196],91:[2,196],93:[2,196],102:[2,196],103:87,104:[2,196],105:[2,196],106:[2,196],109:88,110:[2,196],111:69,118:[2,196],126:[2,196],128:[2,196],129:[2,196],132:[1,78],133:[1,81],134:[2,196],135:[2,196],136:[2,196],137:[2,196]},{1:[2,197],6:[2,197],25:[2,197],26:[2,197],49:[2,197],54:[2,197],57:[2,197],73:[2,197],78:[2,197],86:[2,197],91:[2,197],93:[2,197],102:[2,197],103:87,104:[2,197],105:[2,197],106:[2,197],109:88,110:[2,197],111:69,118:[2,197],126:[2,197],128:[2,197],129:[2,197],132:[1,78],133:[2,197],134:[2,197],135:[2,197],136:[2,197],137:[2,197]},{1:[2,198],6:[2,198],25:[2,198],26:[2,198],49:[2,198],54:[2,198],57:[2,198],73:[2,198],78:[2,198],86:[2,198],91:[2,198],93:[2,198],102:[2,198],103:87,104:[2,198],105:[2,198],106:[2,198],109:88,110:[2,198],111:69,118:[2,198],126:[2,198],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[2,198],135:[2,198],136:[2,198],137:[2,198]},{1:[2,199],6:[2,199],25:[2,199],26:[2,199],49:[2,199],54:[2,199],57:[2,199],73:[2,199],78:[2,199],86:[2,199],91:[2,199],93:[2,199],102:[2,199],103:87,104:[2,199],105:[2,199],106:[2,199],109:88,110:[2,199],111:69,118:[2,199],126:[2,199],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[2,199],136:[2,199],137:[1,85]},{1:[2,200],6:[2,200],25:[2,200],26:[2,200],49:[2,200],54:[2,200],57:[2,200],73:[2,200],78:[2,200],86:[2,200],91:[2,200],93:[2,200],102:[2,200],103:87,104:[2,200],105:[2,200],106:[2,200],109:88,110:[2,200],111:69,118:[2,200],126:[2,200],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[2,200],137:[1,85]},{1:[2,201],6:[2,201],25:[2,201],26:[2,201],49:[2,201],54:[2,201],57:[2,201],73:[2,201],78:[2,201],86:[2,201],91:[2,201],93:[2,201],102:[2,201],103:87,104:[2,201],105:[2,201],106:[2,201],109:88,110:[2,201],111:69,118:[2,201],126:[2,201],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[2,201],136:[2,201],137:[2,201]},{1:[2,186],6:[2,186],25:[2,186],26:[2,186],49:[2,186],54:[2,186],57:[2,186],73:[2,186],78:[2,186],86:[2,186],91:[2,186],93:[2,186],102:[2,186],103:87,104:[1,65],105:[2,186],106:[1,66],109:88,110:[1,68],111:69,118:[2,186],126:[1,86],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{1:[2,185],6:[2,185],25:[2,185],26:[2,185],49:[2,185],54:[2,185],57:[2,185],73:[2,185],78:[2,185],86:[2,185],91:[2,185],93:[2,185],102:[2,185],103:87,104:[1,65],105:[2,185],106:[1,66],109:88,110:[1,68],111:69,118:[2,185],126:[1,86],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{1:[2,104],6:[2,104],25:[2,104],26:[2,104],49:[2,104],54:[2,104],57:[2,104],66:[2,104],67:[2,104],68:[2,104],69:[2,104],71:[2,104],73:[2,104],74:[2,104],78:[2,104],84:[2,104],85:[2,104],86:[2,104],91:[2,104],93:[2,104],102:[2,104],104:[2,104],105:[2,104],106:[2,104],110:[2,104],118:[2,104],126:[2,104],128:[2,104],129:[2,104],132:[2,104],133:[2,104],134:[2,104],135:[2,104],136:[2,104],137:[2,104]},{1:[2,80],6:[2,80],25:[2,80],26:[2,80],40:[2,80],49:[2,80],54:[2,80],57:[2,80],66:[2,80],67:[2,80],68:[2,80],69:[2,80],71:[2,80],73:[2,80],74:[2,80],78:[2,80],80:[2,80],84:[2,80],85:[2,80],86:[2,80],91:[2,80],93:[2,80],102:[2,80],104:[2,80],105:[2,80],106:[2,80],110:[2,80],118:[2,80],126:[2,80],128:[2,80],129:[2,80],130:[2,80],131:[2,80],132:[2,80],133:[2,80],134:[2,80],135:[2,80],136:[2,80],137:[2,80],138:[2,80]},{1:[2,81],6:[2,81],25:[2,81],26:[2,81],40:[2,81],49:[2,81],54:[2,81],57:[2,81],66:[2,81],67:[2,81],68:[2,81],69:[2,81],71:[2,81],73:[2,81],74:[2,81],78:[2,81],80:[2,81],84:[2,81],85:[2,81],86:[2,81],91:[2,81],93:[2,81],102:[2,81],104:[2,81],105:[2,81],106:[2,81],110:[2,81],118:[2,81],126:[2,81],128:[2,81],129:[2,81],130:[2,81],131:[2,81],132:[2,81],133:[2,81],134:[2,81],135:[2,81],136:[2,81],137:[2,81],138:[2,81]},{1:[2,82],6:[2,82],25:[2,82],26:[2,82],40:[2,82],49:[2,82],54:[2,82],57:[2,82],66:[2,82],67:[2,82],68:[2,82],69:[2,82],71:[2,82],73:[2,82],74:[2,82],78:[2,82],80:[2,82],84:[2,82],85:[2,82],86:[2,82],91:[2,82],93:[2,82],102:[2,82],104:[2,82],105:[2,82],106:[2,82],110:[2,82],118:[2,82],126:[2,82],128:[2,82],129:[2,82],130:[2,82],131:[2,82],132:[2,82],133:[2,82],134:[2,82],135:[2,82],136:[2,82],137:[2,82],138:[2,82]},{1:[2,83],6:[2,83],25:[2,83],26:[2,83],40:[2,83],49:[2,83],54:[2,83],57:[2,83],66:[2,83],67:[2,83],68:[2,83],69:[2,83],71:[2,83],73:[2,83],74:[2,83],78:[2,83],80:[2,83],84:[2,83],85:[2,83],86:[2,83],91:[2,83],93:[2,83],102:[2,83],104:[2,83],105:[2,83],106:[2,83],110:[2,83],118:[2,83],126:[2,83],128:[2,83],129:[2,83],130:[2,83],131:[2,83],132:[2,83],133:[2,83],134:[2,83],135:[2,83],136:[2,83],137:[2,83],138:[2,83]},{73:[1,241]},{57:[1,194],73:[2,88],92:242,93:[1,193],103:87,104:[1,65],106:[1,66],109:88,110:[1,68],111:69,126:[1,86],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{73:[2,89]},{8:243,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,73:[2,123],76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{12:[2,117],28:[2,117],30:[2,117],31:[2,117],33:[2,117],34:[2,117],35:[2,117],36:[2,117],37:[2,117],38:[2,117],45:[2,117],46:[2,117],47:[2,117],51:[2,117],52:[2,117],73:[2,117],76:[2,117],79:[2,117],83:[2,117],88:[2,117],89:[2,117],90:[2,117],96:[2,117],100:[2,117],101:[2,117],104:[2,117],106:[2,117],108:[2,117],110:[2,117],119:[2,117],125:[2,117],127:[2,117],128:[2,117],129:[2,117],130:[2,117],131:[2,117]},{12:[2,118],28:[2,118],30:[2,118],31:[2,118],33:[2,118],34:[2,118],35:[2,118],36:[2,118],37:[2,118],38:[2,118],45:[2,118],46:[2,118],47:[2,118],51:[2,118],52:[2,118],73:[2,118],76:[2,118],79:[2,118],83:[2,118],88:[2,118],89:[2,118],90:[2,118],96:[2,118],100:[2,118],101:[2,118],104:[2,118],106:[2,118],108:[2,118],110:[2,118],119:[2,118],125:[2,118],127:[2,118],128:[2,118],129:[2,118],130:[2,118],131:[2,118]},{1:[2,87],6:[2,87],25:[2,87],26:[2,87],40:[2,87],49:[2,87],54:[2,87],57:[2,87],66:[2,87],67:[2,87],68:[2,87],69:[2,87],71:[2,87],73:[2,87],74:[2,87],78:[2,87],80:[2,87],84:[2,87],85:[2,87],86:[2,87],91:[2,87],93:[2,87],102:[2,87],104:[2,87],105:[2,87],106:[2,87],110:[2,87],118:[2,87],126:[2,87],128:[2,87],129:[2,87],130:[2,87],131:[2,87],132:[2,87],133:[2,87],134:[2,87],135:[2,87],136:[2,87],137:[2,87],138:[2,87]},{1:[2,105],6:[2,105],25:[2,105],26:[2,105],49:[2,105],54:[2,105],57:[2,105],66:[2,105],67:[2,105],68:[2,105],69:[2,105],71:[2,105],73:[2,105],74:[2,105],78:[2,105],84:[2,105],85:[2,105],86:[2,105],91:[2,105],93:[2,105],102:[2,105],104:[2,105],105:[2,105],106:[2,105],110:[2,105],118:[2,105],126:[2,105],128:[2,105],129:[2,105],132:[2,105],133:[2,105],134:[2,105],135:[2,105],136:[2,105],137:[2,105]},{1:[2,36],6:[2,36],25:[2,36],26:[2,36],49:[2,36],54:[2,36],57:[2,36],73:[2,36],78:[2,36],86:[2,36],91:[2,36],93:[2,36],102:[2,36],103:87,104:[2,36],105:[2,36],106:[2,36],109:88,110:[2,36],111:69,118:[2,36],126:[2,36],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{8:244,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{8:245,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{1:[2,110],6:[2,110],25:[2,110],26:[2,110],49:[2,110],54:[2,110],57:[2,110],66:[2,110],67:[2,110],68:[2,110],69:[2,110],71:[2,110],73:[2,110],74:[2,110],78:[2,110],84:[2,110],85:[2,110],86:[2,110],91:[2,110],93:[2,110],102:[2,110],104:[2,110],105:[2,110],106:[2,110],110:[2,110],118:[2,110],126:[2,110],128:[2,110],129:[2,110],132:[2,110],133:[2,110],134:[2,110],135:[2,110],136:[2,110],137:[2,110]},{6:[2,53],25:[2,53],53:246,54:[1,229],86:[2,53]},{6:[2,129],25:[2,129],26:[2,129],54:[2,129],57:[1,247],86:[2,129],91:[2,129],103:87,104:[1,65],106:[1,66],109:88,110:[1,68],111:69,126:[1,86],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{50:248,51:[1,60],52:[1,61]},{6:[2,54],25:[2,54],26:[2,54],27:110,28:[1,73],44:111,55:249,56:109,58:112,59:113,76:[1,70],89:[1,114],90:[1,115]},{6:[1,250],25:[1,251]},{6:[2,61],25:[2,61],26:[2,61],49:[2,61],54:[2,61]},{8:252,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{1:[2,202],6:[2,202],25:[2,202],26:[2,202],49:[2,202],54:[2,202],57:[2,202],73:[2,202],78:[2,202],86:[2,202],91:[2,202],93:[2,202],102:[2,202],103:87,104:[2,202],105:[2,202],106:[2,202],109:88,110:[2,202],111:69,118:[2,202],126:[2,202],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{8:253,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{8:254,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{1:[2,205],6:[2,205],25:[2,205],26:[2,205],49:[2,205],54:[2,205],57:[2,205],73:[2,205],78:[2,205],86:[2,205],91:[2,205],93:[2,205],102:[2,205],103:87,104:[2,205],105:[2,205],106:[2,205],109:88,110:[2,205],111:69,118:[2,205],126:[2,205],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{1:[2,184],6:[2,184],25:[2,184],26:[2,184],49:[2,184],54:[2,184],57:[2,184],73:[2,184],78:[2,184],86:[2,184],91:[2,184],93:[2,184],102:[2,184],104:[2,184],105:[2,184],106:[2,184],110:[2,184],118:[2,184],126:[2,184],128:[2,184],129:[2,184],132:[2,184],133:[2,184],134:[2,184],135:[2,184],136:[2,184],137:[2,184]},{8:255,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{1:[2,134],6:[2,134],25:[2,134],26:[2,134],49:[2,134],54:[2,134],57:[2,134],73:[2,134],78:[2,134],86:[2,134],91:[2,134],93:[2,134],98:[1,256],102:[2,134],104:[2,134],105:[2,134],106:[2,134],110:[2,134],118:[2,134],126:[2,134],128:[2,134],129:[2,134],132:[2,134],133:[2,134],134:[2,134],135:[2,134],136:[2,134],137:[2,134]},{5:257,25:[1,5]},{5:260,25:[1,5],27:258,28:[1,73],59:259,76:[1,70]},{120:261,122:219,123:[1,220]},{26:[1,262],121:[1,263],122:264,123:[1,220]},{26:[2,177],121:[2,177],123:[2,177]},{8:266,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],95:265,96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{1:[2,98],5:267,6:[2,98],25:[1,5],26:[2,98],49:[2,98],54:[2,98],57:[2,98],73:[2,98],78:[2,98],86:[2,98],91:[2,98],93:[2,98],102:[2,98],103:87,104:[1,65],105:[2,98],106:[1,66],109:88,110:[1,68],111:69,118:[2,98],126:[2,98],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{1:[2,101],6:[2,101],25:[2,101],26:[2,101],49:[2,101],54:[2,101],57:[2,101],73:[2,101],78:[2,101],86:[2,101],91:[2,101],93:[2,101],102:[2,101],104:[2,101],105:[2,101],106:[2,101],110:[2,101],118:[2,101],126:[2,101],128:[2,101],129:[2,101],132:[2,101],133:[2,101],134:[2,101],135:[2,101],136:[2,101],137:[2,101]},{8:268,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{1:[2,141],6:[2,141],25:[2,141],26:[2,141],49:[2,141],54:[2,141],57:[2,141],66:[2,141],67:[2,141],68:[2,141],69:[2,141],71:[2,141],73:[2,141],74:[2,141],78:[2,141],84:[2,141],85:[2,141],86:[2,141],91:[2,141],93:[2,141],102:[2,141],104:[2,141],105:[2,141],106:[2,141],110:[2,141],118:[2,141],126:[2,141],128:[2,141],129:[2,141],132:[2,141],133:[2,141],134:[2,141],135:[2,141],136:[2,141],137:[2,141]},{6:[1,74],26:[1,269]},{8:270,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{6:[2,67],12:[2,118],25:[2,67],28:[2,118],30:[2,118],31:[2,118],33:[2,118],34:[2,118],35:[2,118],36:[2,118],37:[2,118],38:[2,118],45:[2,118],46:[2,118],47:[2,118],51:[2,118],52:[2,118],54:[2,67],76:[2,118],79:[2,118],83:[2,118],88:[2,118],89:[2,118],90:[2,118],91:[2,67],96:[2,118],100:[2,118],101:[2,118],104:[2,118],106:[2,118],108:[2,118],110:[2,118],119:[2,118],125:[2,118],127:[2,118],128:[2,118],129:[2,118],130:[2,118],131:[2,118]},{6:[1,272],25:[1,273],91:[1,271]},{6:[2,54],8:202,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[2,54],26:[2,54],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,60:148,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],86:[2,54],88:[1,58],89:[1,59],90:[1,57],91:[2,54],94:274,96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{6:[2,53],25:[2,53],26:[2,53],53:275,54:[1,229]},{1:[2,181],6:[2,181],25:[2,181],26:[2,181],49:[2,181],54:[2,181],57:[2,181],73:[2,181],78:[2,181],86:[2,181],91:[2,181],93:[2,181],102:[2,181],104:[2,181],105:[2,181],106:[2,181],110:[2,181],118:[2,181],121:[2,181],126:[2,181],128:[2,181],129:[2,181],132:[2,181],133:[2,181],134:[2,181],135:[2,181],136:[2,181],137:[2,181]},{8:276,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{8:277,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{116:[2,159],117:[2,159]},{27:159,28:[1,73],44:160,58:161,59:162,76:[1,70],89:[1,114],90:[1,115],115:278},{1:[2,166],6:[2,166],25:[2,166],26:[2,166],49:[2,166],54:[2,166],57:[2,166],73:[2,166],78:[2,166],86:[2,166],91:[2,166],93:[2,166],102:[2,166],103:87,104:[2,166],105:[1,279],106:[2,166],109:88,110:[2,166],111:69,118:[1,280],126:[2,166],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{1:[2,167],6:[2,167],25:[2,167],26:[2,167],49:[2,167],54:[2,167],57:[2,167],73:[2,167],78:[2,167],86:[2,167],91:[2,167],93:[2,167],102:[2,167],103:87,104:[2,167],105:[1,281],106:[2,167],109:88,110:[2,167],111:69,118:[2,167],126:[2,167],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{6:[1,283],25:[1,284],78:[1,282]},{6:[2,54],11:169,25:[2,54],26:[2,54],27:170,28:[1,73],29:171,30:[1,71],31:[1,72],41:285,42:168,44:172,46:[1,46],78:[2,54],89:[1,114]},{8:286,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,287],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{1:[2,86],6:[2,86],25:[2,86],26:[2,86],40:[2,86],49:[2,86],54:[2,86],57:[2,86],66:[2,86],67:[2,86],68:[2,86],69:[2,86],71:[2,86],73:[2,86],74:[2,86],78:[2,86],80:[2,86],84:[2,86],85:[2,86],86:[2,86],91:[2,86],93:[2,86],102:[2,86],104:[2,86],105:[2,86],106:[2,86],110:[2,86],118:[2,86],126:[2,86],128:[2,86],129:[2,86],130:[2,86],131:[2,86],132:[2,86],133:[2,86],134:[2,86],135:[2,86],136:[2,86],137:[2,86],138:[2,86]},{8:288,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,73:[2,121],76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{73:[2,122],103:87,104:[1,65],106:[1,66],109:88,110:[1,68],111:69,126:[1,86],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{1:[2,37],6:[2,37],25:[2,37],26:[2,37],49:[2,37],54:[2,37],57:[2,37],73:[2,37],78:[2,37],86:[2,37],91:[2,37],93:[2,37],102:[2,37],103:87,104:[2,37],105:[2,37],106:[2,37],109:88,110:[2,37],111:69,118:[2,37],126:[2,37],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{26:[1,289],103:87,104:[1,65],106:[1,66],109:88,110:[1,68],111:69,126:[1,86],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{6:[1,272],25:[1,273],86:[1,290]},{6:[2,67],25:[2,67],26:[2,67],54:[2,67],86:[2,67],91:[2,67]},{5:291,25:[1,5]},{6:[2,57],25:[2,57],26:[2,57],49:[2,57],54:[2,57]},{27:110,28:[1,73],44:111,55:292,56:109,58:112,59:113,76:[1,70],89:[1,114],90:[1,115]},{6:[2,55],25:[2,55],26:[2,55],27:110,28:[1,73],44:111,48:293,54:[2,55],55:108,56:109,58:112,59:113,76:[1,70],89:[1,114],90:[1,115]},{6:[2,62],25:[2,62],26:[2,62],49:[2,62],54:[2,62],103:87,104:[1,65],106:[1,66],109:88,110:[1,68],111:69,126:[1,86],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{26:[1,294],103:87,104:[1,65],106:[1,66],109:88,110:[1,68],111:69,126:[1,86],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{1:[2,204],6:[2,204],25:[2,204],26:[2,204],49:[2,204],54:[2,204],57:[2,204],73:[2,204],78:[2,204],86:[2,204],91:[2,204],93:[2,204],102:[2,204],103:87,104:[2,204],105:[2,204],106:[2,204],109:88,110:[2,204],111:69,118:[2,204],126:[2,204],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{5:295,25:[1,5],103:87,104:[1,65],106:[1,66],109:88,110:[1,68],111:69,126:[1,86],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{5:296,25:[1,5]},{1:[2,135],6:[2,135],25:[2,135],26:[2,135],49:[2,135],54:[2,135],57:[2,135],73:[2,135],78:[2,135],86:[2,135],91:[2,135],93:[2,135],102:[2,135],104:[2,135],105:[2,135],106:[2,135],110:[2,135],118:[2,135],126:[2,135],128:[2,135],129:[2,135],132:[2,135],133:[2,135],134:[2,135],135:[2,135],136:[2,135],137:[2,135]},{5:297,25:[1,5]},{5:298,25:[1,5]},{1:[2,139],6:[2,139],25:[2,139],26:[2,139],49:[2,139],54:[2,139],57:[2,139],73:[2,139],78:[2,139],86:[2,139],91:[2,139],93:[2,139],98:[2,139],102:[2,139],104:[2,139],105:[2,139],106:[2,139],110:[2,139],118:[2,139],126:[2,139],128:[2,139],129:[2,139],132:[2,139],133:[2,139],134:[2,139],135:[2,139],136:[2,139],137:[2,139]},{26:[1,299],121:[1,300],122:264,123:[1,220]},{1:[2,175],6:[2,175],25:[2,175],26:[2,175],49:[2,175],54:[2,175],57:[2,175],73:[2,175],78:[2,175],86:[2,175],91:[2,175],93:[2,175],102:[2,175],104:[2,175],105:[2,175],106:[2,175],110:[2,175],118:[2,175],126:[2,175],128:[2,175],129:[2,175],132:[2,175],133:[2,175],134:[2,175],135:[2,175],136:[2,175],137:[2,175]},{5:301,25:[1,5]},{26:[2,178],121:[2,178],123:[2,178]},{5:302,25:[1,5],54:[1,303]},{25:[2,131],54:[2,131],103:87,104:[1,65],106:[1,66],109:88,110:[1,68],111:69,126:[1,86],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{1:[2,99],6:[2,99],25:[2,99],26:[2,99],49:[2,99],54:[2,99],57:[2,99],73:[2,99],78:[2,99],86:[2,99],91:[2,99],93:[2,99],102:[2,99],104:[2,99],105:[2,99],106:[2,99],110:[2,99],118:[2,99],126:[2,99],128:[2,99],129:[2,99],132:[2,99],133:[2,99],134:[2,99],135:[2,99],136:[2,99],137:[2,99]},{1:[2,102],5:304,6:[2,102],25:[1,5],26:[2,102],49:[2,102],54:[2,102],57:[2,102],73:[2,102],78:[2,102],86:[2,102],91:[2,102],93:[2,102],102:[2,102],103:87,104:[1,65],105:[2,102],106:[1,66],109:88,110:[1,68],111:69,118:[2,102],126:[2,102],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{102:[1,305]},{91:[1,306],103:87,104:[1,65],106:[1,66],109:88,110:[1,68],111:69,126:[1,86],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{1:[2,116],6:[2,116],25:[2,116],26:[2,116],40:[2,116],49:[2,116],54:[2,116],57:[2,116],66:[2,116],67:[2,116],68:[2,116],69:[2,116],71:[2,116],73:[2,116],74:[2,116],78:[2,116],84:[2,116],85:[2,116],86:[2,116],91:[2,116],93:[2,116],102:[2,116],104:[2,116],105:[2,116],106:[2,116],110:[2,116],116:[2,116],117:[2,116],118:[2,116],126:[2,116],128:[2,116],129:[2,116],132:[2,116],133:[2,116],134:[2,116],135:[2,116],136:[2,116],137:[2,116]},{8:202,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,60:148,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],94:307,96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{8:202,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,25:[1,147],27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,60:148,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],87:308,88:[1,58],89:[1,59],90:[1,57],94:146,96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{6:[2,125],25:[2,125],26:[2,125],54:[2,125],86:[2,125],91:[2,125]},{6:[1,272],25:[1,273],26:[1,309]},{1:[2,144],6:[2,144],25:[2,144],26:[2,144],49:[2,144],54:[2,144],57:[2,144],73:[2,144],78:[2,144],86:[2,144],91:[2,144],93:[2,144],102:[2,144],103:87,104:[1,65],105:[2,144],106:[1,66],109:88,110:[1,68],111:69,118:[2,144],126:[2,144],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{1:[2,146],6:[2,146],25:[2,146],26:[2,146],49:[2,146],54:[2,146],57:[2,146],73:[2,146],78:[2,146],86:[2,146],91:[2,146],93:[2,146],102:[2,146],103:87,104:[1,65],105:[2,146],106:[1,66],109:88,110:[1,68],111:69,118:[2,146],126:[2,146],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{116:[2,165],117:[2,165]},{8:310,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{8:311,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{8:312,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{1:[2,90],6:[2,90],25:[2,90],26:[2,90],40:[2,90],49:[2,90],54:[2,90],57:[2,90],66:[2,90],67:[2,90],68:[2,90],69:[2,90],71:[2,90],73:[2,90],74:[2,90],78:[2,90],84:[2,90],85:[2,90],86:[2,90],91:[2,90],93:[2,90],102:[2,90],104:[2,90],105:[2,90],106:[2,90],110:[2,90],116:[2,90],117:[2,90],118:[2,90],126:[2,90],128:[2,90],129:[2,90],132:[2,90],133:[2,90],134:[2,90],135:[2,90],136:[2,90],137:[2,90]},{11:169,27:170,28:[1,73],29:171,30:[1,71],31:[1,72],41:313,42:168,44:172,46:[1,46],89:[1,114]},{6:[2,91],11:169,25:[2,91],26:[2,91],27:170,28:[1,73],29:171,30:[1,71],31:[1,72],41:167,42:168,44:172,46:[1,46],54:[2,91],77:314,89:[1,114]},{6:[2,93],25:[2,93],26:[2,93],54:[2,93],78:[2,93]},{6:[2,40],25:[2,40],26:[2,40],54:[2,40],78:[2,40],103:87,104:[1,65],106:[1,66],109:88,110:[1,68],111:69,126:[1,86],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{8:315,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{73:[2,120],103:87,104:[1,65],106:[1,66],109:88,110:[1,68],111:69,126:[1,86],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{1:[2,38],6:[2,38],25:[2,38],26:[2,38],49:[2,38],54:[2,38],57:[2,38],73:[2,38],78:[2,38],86:[2,38],91:[2,38],93:[2,38],102:[2,38],104:[2,38],105:[2,38],106:[2,38],110:[2,38],118:[2,38],126:[2,38],128:[2,38],129:[2,38],132:[2,38],133:[2,38],134:[2,38],135:[2,38],136:[2,38],137:[2,38]},{1:[2,111],6:[2,111],25:[2,111],26:[2,111],49:[2,111],54:[2,111],57:[2,111],66:[2,111],67:[2,111],68:[2,111],69:[2,111],71:[2,111],73:[2,111],74:[2,111],78:[2,111],84:[2,111],85:[2,111],86:[2,111],91:[2,111],93:[2,111],102:[2,111],104:[2,111],105:[2,111],106:[2,111],110:[2,111],118:[2,111],126:[2,111],128:[2,111],129:[2,111],132:[2,111],133:[2,111],134:[2,111],135:[2,111],136:[2,111],137:[2,111]},{1:[2,49],6:[2,49],25:[2,49],26:[2,49],49:[2,49],54:[2,49],57:[2,49],73:[2,49],78:[2,49],86:[2,49],91:[2,49],93:[2,49],102:[2,49],104:[2,49],105:[2,49],106:[2,49],110:[2,49],118:[2,49],126:[2,49],128:[2,49],129:[2,49],132:[2,49],133:[2,49],134:[2,49],135:[2,49],136:[2,49],137:[2,49]},{6:[2,58],25:[2,58],26:[2,58],49:[2,58],54:[2,58]},{6:[2,53],25:[2,53],26:[2,53],53:316,54:[1,204]},{1:[2,203],6:[2,203],25:[2,203],26:[2,203],49:[2,203],54:[2,203],57:[2,203],73:[2,203],78:[2,203],86:[2,203],91:[2,203],93:[2,203],102:[2,203],104:[2,203],105:[2,203],106:[2,203],110:[2,203],118:[2,203],126:[2,203],128:[2,203],129:[2,203],132:[2,203],133:[2,203],134:[2,203],135:[2,203],136:[2,203],137:[2,203]},{1:[2,182],6:[2,182],25:[2,182],26:[2,182],49:[2,182],54:[2,182],57:[2,182],73:[2,182],78:[2,182],86:[2,182],91:[2,182],93:[2,182],102:[2,182],104:[2,182],105:[2,182],106:[2,182],110:[2,182],118:[2,182],121:[2,182],126:[2,182],128:[2,182],129:[2,182],132:[2,182],133:[2,182],134:[2,182],135:[2,182],136:[2,182],137:[2,182]},{1:[2,136],6:[2,136],25:[2,136],26:[2,136],49:[2,136],54:[2,136],57:[2,136],73:[2,136],78:[2,136],86:[2,136],91:[2,136],93:[2,136],102:[2,136],104:[2,136],105:[2,136],106:[2,136],110:[2,136],118:[2,136],126:[2,136],128:[2,136],129:[2,136],132:[2,136],133:[2,136],134:[2,136],135:[2,136],136:[2,136],137:[2,136]},{1:[2,137],6:[2,137],25:[2,137],26:[2,137],49:[2,137],54:[2,137],57:[2,137],73:[2,137],78:[2,137],86:[2,137],91:[2,137],93:[2,137],98:[2,137],102:[2,137],104:[2,137],105:[2,137],106:[2,137],110:[2,137],118:[2,137],126:[2,137],128:[2,137],129:[2,137],132:[2,137],133:[2,137],134:[2,137],135:[2,137],136:[2,137],137:[2,137]},{1:[2,138],6:[2,138],25:[2,138],26:[2,138],49:[2,138],54:[2,138],57:[2,138],73:[2,138],78:[2,138],86:[2,138],91:[2,138],93:[2,138],98:[2,138],102:[2,138],104:[2,138],105:[2,138],106:[2,138],110:[2,138],118:[2,138],126:[2,138],128:[2,138],129:[2,138],132:[2,138],133:[2,138],134:[2,138],135:[2,138],136:[2,138],137:[2,138]},{1:[2,173],6:[2,173],25:[2,173],26:[2,173],49:[2,173],54:[2,173],57:[2,173],73:[2,173],78:[2,173],86:[2,173],91:[2,173],93:[2,173],102:[2,173],104:[2,173],105:[2,173],106:[2,173],110:[2,173],118:[2,173],126:[2,173],128:[2,173],129:[2,173],132:[2,173],133:[2,173],134:[2,173],135:[2,173],136:[2,173],137:[2,173]},{5:317,25:[1,5]},{26:[1,318]},{6:[1,319],26:[2,179],121:[2,179],123:[2,179]},{8:320,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{1:[2,103],6:[2,103],25:[2,103],26:[2,103],49:[2,103],54:[2,103],57:[2,103],73:[2,103],78:[2,103],86:[2,103],91:[2,103],93:[2,103],102:[2,103],104:[2,103],105:[2,103],106:[2,103],110:[2,103],118:[2,103],126:[2,103],128:[2,103],129:[2,103],132:[2,103],133:[2,103],134:[2,103],135:[2,103],136:[2,103],137:[2,103]},{1:[2,142],6:[2,142],25:[2,142],26:[2,142],49:[2,142],54:[2,142],57:[2,142],66:[2,142],67:[2,142],68:[2,142],69:[2,142],71:[2,142],73:[2,142],74:[2,142],78:[2,142],84:[2,142],85:[2,142],86:[2,142],91:[2,142],93:[2,142],102:[2,142],104:[2,142],105:[2,142],106:[2,142],110:[2,142],118:[2,142],126:[2,142],128:[2,142],129:[2,142],132:[2,142],133:[2,142],134:[2,142],135:[2,142],136:[2,142],137:[2,142]},{1:[2,119],6:[2,119],25:[2,119],26:[2,119],49:[2,119],54:[2,119],57:[2,119],66:[2,119],67:[2,119],68:[2,119],69:[2,119],71:[2,119],73:[2,119],74:[2,119],78:[2,119],84:[2,119],85:[2,119],86:[2,119],91:[2,119],93:[2,119],102:[2,119],104:[2,119],105:[2,119],106:[2,119],110:[2,119],118:[2,119],126:[2,119],128:[2,119],129:[2,119],132:[2,119],133:[2,119],134:[2,119],135:[2,119],136:[2,119],137:[2,119]},{6:[2,126],25:[2,126],26:[2,126],54:[2,126],86:[2,126],91:[2,126]},{6:[2,53],25:[2,53],26:[2,53],53:321,54:[1,229]},{6:[2,127],25:[2,127],26:[2,127],54:[2,127],86:[2,127],91:[2,127]},{1:[2,168],6:[2,168],25:[2,168],26:[2,168],49:[2,168],54:[2,168],57:[2,168],73:[2,168],78:[2,168],86:[2,168],91:[2,168],93:[2,168],102:[2,168],103:87,104:[2,168],105:[2,168],106:[2,168],109:88,110:[2,168],111:69,118:[1,322],126:[2,168],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{1:[2,170],6:[2,170],25:[2,170],26:[2,170],49:[2,170],54:[2,170],57:[2,170],73:[2,170],78:[2,170],86:[2,170],91:[2,170],93:[2,170],102:[2,170],103:87,104:[2,170],105:[1,323],106:[2,170],109:88,110:[2,170],111:69,118:[2,170],126:[2,170],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{1:[2,169],6:[2,169],25:[2,169],26:[2,169],49:[2,169],54:[2,169],57:[2,169],73:[2,169],78:[2,169],86:[2,169],91:[2,169],93:[2,169],102:[2,169],103:87,104:[2,169],105:[2,169],106:[2,169],109:88,110:[2,169],111:69,118:[2,169],126:[2,169],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{6:[2,94],25:[2,94],26:[2,94],54:[2,94],78:[2,94]},{6:[2,53],25:[2,53],26:[2,53],53:324,54:[1,239]},{26:[1,325],103:87,104:[1,65],106:[1,66],109:88,110:[1,68],111:69,126:[1,86],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{6:[1,250],25:[1,251],26:[1,326]},{26:[1,327]},{1:[2,176],6:[2,176],25:[2,176],26:[2,176],49:[2,176],54:[2,176],57:[2,176],73:[2,176],78:[2,176],86:[2,176],91:[2,176],93:[2,176],102:[2,176],104:[2,176],105:[2,176],106:[2,176],110:[2,176],118:[2,176],126:[2,176],128:[2,176],129:[2,176],132:[2,176],133:[2,176],134:[2,176],135:[2,176],136:[2,176],137:[2,176]},{26:[2,180],121:[2,180],123:[2,180]},{25:[2,132],54:[2,132],103:87,104:[1,65],106:[1,66],109:88,110:[1,68],111:69,126:[1,86],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{6:[1,272],25:[1,273],26:[1,328]},{8:329,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{8:330,9:118,10:20,11:21,12:[1,22],13:8,14:9,15:10,16:11,17:12,18:13,19:14,20:15,21:16,22:17,23:18,24:19,27:62,28:[1,73],29:49,30:[1,71],31:[1,72],32:24,33:[1,50],34:[1,51],35:[1,52],36:[1,53],37:[1,54],38:[1,55],39:23,44:63,45:[1,45],46:[1,46],47:[1,29],50:30,51:[1,60],52:[1,61],58:47,59:48,61:36,63:25,64:26,65:27,76:[1,70],79:[1,43],83:[1,28],88:[1,58],89:[1,59],90:[1,57],96:[1,38],100:[1,44],101:[1,56],103:39,104:[1,65],106:[1,66],107:40,108:[1,67],109:41,110:[1,68],111:69,119:[1,42],124:37,125:[1,64],127:[1,31],128:[1,32],129:[1,33],130:[1,34],131:[1,35]},{6:[1,283],25:[1,284],26:[1,331]},{6:[2,41],25:[2,41],26:[2,41],54:[2,41],78:[2,41]},{6:[2,59],25:[2,59],26:[2,59],49:[2,59],54:[2,59]},{1:[2,174],6:[2,174],25:[2,174],26:[2,174],49:[2,174],54:[2,174],57:[2,174],73:[2,174],78:[2,174],86:[2,174],91:[2,174],93:[2,174],102:[2,174],104:[2,174],105:[2,174],106:[2,174],110:[2,174],118:[2,174],126:[2,174],128:[2,174],129:[2,174],132:[2,174],133:[2,174],134:[2,174],135:[2,174],136:[2,174],137:[2,174]},{6:[2,128],25:[2,128],26:[2,128],54:[2,128],86:[2,128],91:[2,128]},{1:[2,171],6:[2,171],25:[2,171],26:[2,171],49:[2,171],54:[2,171],57:[2,171],73:[2,171],78:[2,171],86:[2,171],91:[2,171],93:[2,171],102:[2,171],103:87,104:[2,171],105:[2,171],106:[2,171],109:88,110:[2,171],111:69,118:[2,171],126:[2,171],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{1:[2,172],6:[2,172],25:[2,172],26:[2,172],49:[2,172],54:[2,172],57:[2,172],73:[2,172],78:[2,172],86:[2,172],91:[2,172],93:[2,172],102:[2,172],103:87,104:[2,172],105:[2,172],106:[2,172],109:88,110:[2,172],111:69,118:[2,172],126:[2,172],128:[1,80],129:[1,79],132:[1,78],133:[1,81],134:[1,82],135:[1,83],136:[1,84],137:[1,85]},{6:[2,95],25:[2,95],26:[2,95],54:[2,95],78:[2,95]}],defaultActions:{60:[2,51],61:[2,52],75:[2,3],94:[2,109],191:[2,89]},parseError:function(e){throw new Error(e)
+},parse:function(e){function t(){var e;return e=n.lexer.lex()||1,"number"!=typeof e&&(e=n.symbols_[e]||e),e}var n=this,i=[0],s=[null],r=[],o=this.table,a="",c=0,h=0,l=0;this.lexer.setInput(e),this.lexer.yy=this.yy,this.yy.lexer=this.lexer,this.yy.parser=this,"undefined"==typeof this.lexer.yylloc&&(this.lexer.yylloc={});var u=this.lexer.yylloc;r.push(u);var p=this.lexer.options&&this.lexer.options.ranges;"function"==typeof this.yy.parseError&&(this.parseError=this.yy.parseError);for(var d,f,m,b,k,g,y,v,w,T={};;){if(m=i[i.length-1],this.defaultActions[m]?b=this.defaultActions[m]:((null===d||"undefined"==typeof d)&&(d=t()),b=o[m]&&o[m][d]),"undefined"==typeof b||!b.length||!b[0]){var C="";if(!l){w=[];for(g in o[m])this.terminals_[g]&&g>2&&w.push("'"+this.terminals_[g]+"'");C=this.lexer.showPosition?"Parse error on line "+(c+1)+":\n"+this.lexer.showPosition()+"\nExpecting "+w.join(", ")+", got '"+(this.terminals_[d]||d)+"'":"Parse error on line "+(c+1)+": Unexpected "+(1==d?"end of input":"'"+(this.terminals_[d]||d)+"'"),this.parseError(C,{text:this.lexer.match,token:this.terminals_[d]||d,line:this.lexer.yylineno,loc:u,expected:w})}}if(b[0]instanceof Array&&b.length>1)throw new Error("Parse Error: multiple actions possible at state: "+m+", token: "+d);switch(b[0]){case 1:i.push(d),s.push(this.lexer.yytext),r.push(this.lexer.yylloc),i.push(b[1]),d=null,f?(d=f,f=null):(h=this.lexer.yyleng,a=this.lexer.yytext,c=this.lexer.yylineno,u=this.lexer.yylloc,l>0&&l--);break;case 2:if(y=this.productions_[b[1]][1],T.$=s[s.length-y],T._$={first_line:r[r.length-(y||1)].first_line,last_line:r[r.length-1].last_line,first_column:r[r.length-(y||1)].first_column,last_column:r[r.length-1].last_column},p&&(T._$.range=[r[r.length-(y||1)].range[0],r[r.length-1].range[1]]),k=this.performAction.call(T,a,h,c,this.yy,b[1],s,r),"undefined"!=typeof k)return k;y&&(i=i.slice(0,2*-1*y),s=s.slice(0,-1*y),r=r.slice(0,-1*y)),i.push(this.productions_[b[1]][0]),s.push(T.$),r.push(T._$),v=o[i[i.length-2]][i[i.length-1]],i.push(v);break;case 3:return!0}}return!0}};return e.prototype=t,t.Parser=e,new e}();return"undefined"!=typeof require&&"undefined"!=typeof e&&(e.parser=n,e.Parser=n.Parser,e.parse=function(){return n.parse.apply(n,arguments)},e.main=function(t){t[1]||(console.log("Usage: "+t[0]+" FILE"),process.exit(1));var n=require("fs").readFileSync(require("path").normalize(t[1]),"utf8");return e.parser.parse(n)},"undefined"!=typeof t&&require.main===t&&e.main(process.argv.slice(1))),t.exports}(),require["./scope"]=function(){var e={},t={exports:e};return function(){var t,n,i,s;s=require("./helpers"),n=s.extend,i=s.last,e.Scope=t=function(){function e(t,n,i){this.parent=t,this.expressions=n,this.method=i,this.variables=[{name:"arguments",type:"arguments"}],this.positions={},this.parent||(e.root=this)}return e.root=null,e.prototype.add=function(e,t,n){return this.shared&&!n?this.parent.add(e,t,n):Object.prototype.hasOwnProperty.call(this.positions,e)?this.variables[this.positions[e]].type=t:this.positions[e]=this.variables.push({name:e,type:t})-1},e.prototype.namedMethod=function(){var e;return(null!=(e=this.method)?e.name:void 0)||!this.parent?this.method:this.parent.namedMethod()},e.prototype.find=function(e){return this.check(e)?!0:(this.add(e,"var"),!1)},e.prototype.parameter=function(e){return this.shared&&this.parent.check(e,!0)?void 0:this.add(e,"param")},e.prototype.check=function(e){var t;return!!(this.type(e)||(null!=(t=this.parent)?t.check(e):void 0))},e.prototype.temporary=function(e,t){return e.length>1?"_"+e+(t>1?t-1:""):"_"+(t+parseInt(e,36)).toString(36).replace(/\d/g,"a")},e.prototype.type=function(e){var t,n,i,s;for(s=this.variables,n=0,i=s.length;i>n;n++)if(t=s[n],t.name===e)return t.type;return null},e.prototype.freeVariable=function(e,t){var n,i;for(null==t&&(t=!0),n=0;this.check(i=this.temporary(e,n));)n++;return t&&this.add(i,"var",!0),i},e.prototype.assign=function(e,t){return this.add(e,{value:t,assigned:!0},!0),this.hasAssignments=!0},e.prototype.hasDeclarations=function(){return!!this.declaredVariables().length},e.prototype.declaredVariables=function(){var e,t,n,i,s,r;for(e=[],t=[],r=this.variables,i=0,s=r.length;s>i;i++)n=r[i],"var"===n.type&&("_"===n.name.charAt(0)?t:e).push(n.name);return e.sort().concat(t.sort())},e.prototype.assignedVariables=function(){var e,t,n,i,s;for(i=this.variables,s=[],t=0,n=i.length;n>t;t++)e=i[t],e.type.assigned&&s.push(""+e.name+" = "+e.type.value);return s},e}()}.call(this),t.exports}(),require["./nodes"]=function(){var e={},t={exports:e};return function(){var t,n,i,s,r,o,a,c,h,l,u,p,d,f,m,b,k,g,y,v,w,T,C,F,L,N,x,E,D,S,R,A,I,_,$,O,j,M,B,V,P,U,q,H,G,W,X,Y,K,z,J,Z,Q,et,tt,nt,it,st,rt,ot,at,ct,ht,lt,ut,pt,dt,ft,mt,bt,kt,gt={}.hasOwnProperty,yt=function(e,t){function n(){this.constructor=e}for(var i in t)gt.call(t,i)&&(e[i]=t[i]);return n.prototype=t.prototype,e.prototype=new n,e.__super__=t.prototype,e},vt=[].indexOf||function(e){for(var t=0,n=this.length;n>t;t++)if(t in this&&this[t]===e)return t;return-1},wt=[].slice;Error.stackTraceLimit=1/0,V=require("./scope").Scope,ft=require("./lexer"),$=ft.RESERVED,B=ft.STRICT_PROSCRIBED,mt=require("./helpers"),Q=mt.compact,it=mt.flatten,nt=mt.extend,at=mt.merge,et=mt.del,lt=mt.starts,tt=mt.ends,rt=mt.last,ht=mt.some,Z=mt.addLocationDataFn,ot=mt.locationDataToString,ut=mt.throwSyntaxError,e.extend=nt,e.addLocationDataFn=Z,J=function(){return!0},S=function(){return!1},G=function(){return this},D=function(){return this.negated=!this.negated,this},e.CodeFragment=l=function(){function e(e,t){var n;this.code=""+t,this.locationData=null!=e?e.locationData:void 0,this.type=(null!=e?null!=(n=e.constructor)?n.name:void 0:void 0)||"unknown"}return e.prototype.toString=function(){return""+this.code+(this.locationData?": "+ot(this.locationData):"")},e}(),st=function(e){var t;return function(){var n,i,s;for(s=[],n=0,i=e.length;i>n;n++)t=e[n],s.push(t.code);return s}().join("")},e.Base=s=function(){function e(){}return e.prototype.compile=function(e,t){return st(this.compileToFragments(e,t))},e.prototype.compileToFragments=function(e,t){var n;return e=nt({},e),t&&(e.level=t),n=this.unfoldSoak(e)||this,n.tab=e.indent,e.level!==N&&n.isStatement(e)?n.compileClosure(e):n.compileNode(e)},e.prototype.compileClosure=function(e){var t;return(t=this.jumps())&&t.error("cannot use a pure statement in an expression"),e.sharedScope=!0,c.wrap(this).compileNode(e)},e.prototype.cache=function(e,t,n){var s,r;return this.isComplex()?(s=new x(n||e.scope.freeVariable("ref")),r=new i(s,this),t?[r.compileToFragments(e,t),[this.makeCode(s.value)]]:[r,s]):(s=t?this.compileToFragments(e,t):this,[s,s])},e.prototype.cacheToCodeFragments=function(e){return[st(e[0]),st(e[1])]},e.prototype.makeReturn=function(e){var t;return t=this.unwrapAll(),e?new o(new x(""+e+".push"),[t]):new j(t)},e.prototype.contains=function(e){var t;return t=void 0,this.traverseChildren(!1,function(n){return e(n)?(t=n,!1):void 0}),t},e.prototype.lastNonComment=function(e){var t;for(t=e.length;t--;)if(!(e[t]instanceof u))return e[t];return null},e.prototype.toString=function(e,t){var n;return null==e&&(e=""),null==t&&(t=this.constructor.name),n="\n"+e+t,this.soak&&(n+="?"),this.eachChild(function(t){return n+=t.toString(e+H)}),n},e.prototype.eachChild=function(e){var t,n,i,s,r,o,a,c;if(!this.children)return this;for(a=this.children,i=0,r=a.length;r>i;i++)if(t=a[i],this[t])for(c=it([this[t]]),s=0,o=c.length;o>s;s++)if(n=c[s],e(n)===!1)return this;return this},e.prototype.traverseChildren=function(e,t){return this.eachChild(function(n){var i;return i=t(n),i!==!1?n.traverseChildren(e,t):void 0})},e.prototype.invert=function(){return new A("!",this)},e.prototype.unwrapAll=function(){var e;for(e=this;e!==(e=e.unwrap()););return e},e.prototype.children=[],e.prototype.isStatement=S,e.prototype.jumps=S,e.prototype.isComplex=J,e.prototype.isChainable=S,e.prototype.isAssignable=S,e.prototype.unwrap=G,e.prototype.unfoldSoak=S,e.prototype.assigns=S,e.prototype.updateLocationDataIfMissing=function(e){return this.locationData||(this.locationData=e),this.eachChild(function(t){return t.updateLocationDataIfMissing(e)})},e.prototype.error=function(e){return ut(e,this.locationData)},e.prototype.makeCode=function(e){return new l(this,e)},e.prototype.wrapInBraces=function(e){return[].concat(this.makeCode("("),e,this.makeCode(")"))},e.prototype.joinFragmentArrays=function(e,t){var n,i,s,r,o;for(n=[],s=r=0,o=e.length;o>r;s=++r)i=e[s],s&&n.push(this.makeCode(t)),n=n.concat(i);return n},e}(),e.Block=r=function(e){function t(e){this.expressions=Q(it(e||[]))}return yt(t,e),t.prototype.children=["expressions"],t.prototype.push=function(e){return this.expressions.push(e),this},t.prototype.pop=function(){return this.expressions.pop()},t.prototype.unshift=function(e){return this.expressions.unshift(e),this},t.prototype.unwrap=function(){return 1===this.expressions.length?this.expressions[0]:this},t.prototype.isEmpty=function(){return!this.expressions.length},t.prototype.isStatement=function(e){var t,n,i,s;for(s=this.expressions,n=0,i=s.length;i>n;n++)if(t=s[n],t.isStatement(e))return!0;return!1},t.prototype.jumps=function(e){var t,n,i,s;for(s=this.expressions,n=0,i=s.length;i>n;n++)if(t=s[n],t.jumps(e))return t},t.prototype.makeReturn=function(e){var t,n;for(n=this.expressions.length;n--;)if(t=this.expressions[n],!(t instanceof u)){this.expressions[n]=t.makeReturn(e),t instanceof j&&!t.expression&&this.expressions.splice(n,1);break}return this},t.prototype.compileToFragments=function(e,n){return null==e&&(e={}),e.scope?t.__super__.compileToFragments.call(this,e,n):this.compileRoot(e)},t.prototype.compileNode=function(e){var n,i,s,r,o,a,c,h,l;for(this.tab=e.indent,a=e.level===N,i=[],l=this.expressions,r=c=0,h=l.length;h>c;r=++c)o=l[r],o=o.unwrapAll(),o=o.unfoldSoak(e)||o,o instanceof t?i.push(o.compileNode(e)):a?(o.front=!0,s=o.compileToFragments(e),o.isStatement(e)||(s.unshift(this.makeCode(""+this.tab)),s.push(this.makeCode(";"))),i.push(s)):i.push(o.compileToFragments(e,C));return a?this.spaced?[].concat(this.joinFragmentArrays(i,"\n\n"),this.makeCode("\n")):this.joinFragmentArrays(i,"\n"):(n=i.length?this.joinFragmentArrays(i,", "):[this.makeCode("void 0")],i.length>1&&e.level>=C?this.wrapInBraces(n):n)},t.prototype.compileRoot=function(e){var t,n,i,s,r,o,a,c,h,l;for(e.indent=e.bare?"":H,e.level=N,this.spaced=!0,e.scope=new V(null,this,null),l=e.locals||[],c=0,h=l.length;h>c;c++)s=l[c],e.scope.parameter(s);return r=[],e.bare||(o=function(){var e,n,s,r;for(s=this.expressions,r=[],i=e=0,n=s.length;n>e&&(t=s[i],t.unwrap()instanceof u);i=++e)r.push(t);return r}.call(this),a=this.expressions.slice(o.length),this.expressions=o,o.length&&(r=this.compileNode(at(e,{indent:""})),r.push(this.makeCode("\n"))),this.expressions=a),n=this.compileWithDeclarations(e),e.bare?n:[].concat(r,this.makeCode("(function() {\n"),n,this.makeCode("\n}).call(this);\n"))},t.prototype.compileWithDeclarations=function(e){var t,n,i,s,r,o,a,c,h,l,p,d,f,m;for(s=[],o=[],d=this.expressions,r=l=0,p=d.length;p>l&&(i=d[r],i=i.unwrap(),i instanceof u||i instanceof x);r=++l);return e=at(e,{level:N}),r&&(a=this.expressions.splice(r,9e9),f=[this.spaced,!1],h=f[0],this.spaced=f[1],m=[this.compileNode(e),h],s=m[0],this.spaced=m[1],this.expressions=a),o=this.compileNode(e),c=e.scope,c.expressions===this&&(n=e.scope.hasDeclarations(),t=c.hasAssignments,n||t?(r&&s.push(this.makeCode("\n")),s.push(this.makeCode(""+this.tab+"var ")),n&&s.push(this.makeCode(c.declaredVariables().join(", "))),t&&(n&&s.push(this.makeCode(",\n"+(this.tab+H))),s.push(this.makeCode(c.assignedVariables().join(",\n"+(this.tab+H))))),s.push(this.makeCode(";\n"+(this.spaced?"\n":"")))):s.length&&o.length&&s.push(this.makeCode("\n"))),s.concat(o)},t.wrap=function(e){return 1===e.length&&e[0]instanceof t?e[0]:new t(e)},t}(s),e.Literal=x=function(e){function t(e){this.value=e}return yt(t,e),t.prototype.makeReturn=function(){return this.isStatement()?this:t.__super__.makeReturn.apply(this,arguments)},t.prototype.isAssignable=function(){return m.test(this.value)},t.prototype.isStatement=function(){var e;return"break"===(e=this.value)||"continue"===e||"debugger"===e},t.prototype.isComplex=S,t.prototype.assigns=function(e){return e===this.value},t.prototype.jumps=function(e){return"break"!==this.value||(null!=e?e.loop:void 0)||(null!=e?e.block:void 0)?"continue"!==this.value||(null!=e?e.loop:void 0)?void 0:this:this},t.prototype.compileNode=function(e){var t,n,i;return n="this"===this.value?(null!=(i=e.scope.method)?i.bound:void 0)?e.scope.method.context:this.value:this.value.reserved?'"'+this.value+'"':this.value,t=this.isStatement()?""+this.tab+n+";":n,[this.makeCode(t)]},t.prototype.toString=function(){return' "'+this.value+'"'},t}(s),e.Undefined=function(e){function t(){return bt=t.__super__.constructor.apply(this,arguments)}return yt(t,e),t.prototype.isAssignable=S,t.prototype.isComplex=S,t.prototype.compileNode=function(e){return[this.makeCode(e.level>=w?"(void 0)":"void 0")]},t}(s),e.Null=function(e){function t(){return kt=t.__super__.constructor.apply(this,arguments)}return yt(t,e),t.prototype.isAssignable=S,t.prototype.isComplex=S,t.prototype.compileNode=function(){return[this.makeCode("null")]},t}(s),e.Bool=function(e){function t(e){this.val=e}return yt(t,e),t.prototype.isAssignable=S,t.prototype.isComplex=S,t.prototype.compileNode=function(){return[this.makeCode(this.val)]},t}(s),e.Return=j=function(e){function t(e){e&&!e.unwrap().isUndefined&&(this.expression=e)}return yt(t,e),t.prototype.children=["expression"],t.prototype.isStatement=J,t.prototype.makeReturn=G,t.prototype.jumps=G,t.prototype.compileToFragments=function(e,n){var i,s;return i=null!=(s=this.expression)?s.makeReturn():void 0,!i||i instanceof t?t.__super__.compileToFragments.call(this,e,n):i.compileToFragments(e,n)},t.prototype.compileNode=function(e){var t;return t=[],t.push(this.makeCode(this.tab+("return"+(this.expression?" ":"")))),this.expression&&(t=t.concat(this.expression.compileToFragments(e,L))),t.push(this.makeCode(";")),t},t}(s),e.Value=K=function(e){function t(e,n,i){return!n&&e instanceof t?e:(this.base=e,this.properties=n||[],i&&(this[i]=!0),this)}return yt(t,e),t.prototype.children=["base","properties"],t.prototype.add=function(e){return this.properties=this.properties.concat(e),this},t.prototype.hasProperties=function(){return!!this.properties.length},t.prototype.isArray=function(){return!this.properties.length&&this.base instanceof n},t.prototype.isComplex=function(){return this.hasProperties()||this.base.isComplex()},t.prototype.isAssignable=function(){return this.hasProperties()||this.base.isAssignable()},t.prototype.isSimpleNumber=function(){return this.base instanceof x&&M.test(this.base.value)},t.prototype.isString=function(){return this.base instanceof x&&k.test(this.base.value)},t.prototype.isAtomic=function(){var e,t,n,i;for(i=this.properties.concat(this.base),t=0,n=i.length;n>t;t++)if(e=i[t],e.soak||e instanceof o)return!1;return!0},t.prototype.isStatement=function(e){return!this.properties.length&&this.base.isStatement(e)},t.prototype.assigns=function(e){return!this.properties.length&&this.base.assigns(e)},t.prototype.jumps=function(e){return!this.properties.length&&this.base.jumps(e)},t.prototype.isObject=function(e){return this.properties.length?!1:this.base instanceof R&&(!e||this.base.generated)},t.prototype.isSplice=function(){return rt(this.properties)instanceof P},t.prototype.unwrap=function(){return this.properties.length?this:this.base},t.prototype.cacheReference=function(e){var n,s,r,o;return r=rt(this.properties),this.properties.length<2&&!this.base.isComplex()&&!(null!=r?r.isComplex():void 0)?[this,this]:(n=new t(this.base,this.properties.slice(0,-1)),n.isComplex()&&(s=new x(e.scope.freeVariable("base")),n=new t(new _(new i(s,n)))),r?(r.isComplex()&&(o=new x(e.scope.freeVariable("name")),r=new v(new i(o,r.index)),o=new v(o)),[n.add(r),new t(s||n.base,[o||r])]):[n,s])},t.prototype.compileNode=function(e){var t,n,i,s,r;for(this.base.front=this.front,i=this.properties,t=this.base.compileToFragments(e,i.length?w:null),(this.base instanceof _||i.length)&&M.test(st(t))&&t.push(this.makeCode(".")),s=0,r=i.length;r>s;s++)n=i[s],t.push.apply(t,n.compileToFragments(e));return t},t.prototype.unfoldSoak=function(e){var n=this;return null!=this.unfoldedSoak?this.unfoldedSoak:this.unfoldedSoak=function(){var s,r,o,a,c,h,l,u,d,f;if(o=n.base.unfoldSoak(e))return(d=o.body.properties).push.apply(d,n.properties),o;for(f=n.properties,r=l=0,u=f.length;u>l;r=++l)if(a=f[r],a.soak)return a.soak=!1,s=new t(n.base,n.properties.slice(0,r)),h=new t(n.base,n.properties.slice(r)),s.isComplex()&&(c=new x(e.scope.freeVariable("ref")),s=new _(new i(c,s)),h.base=c),new g(new p(s),h,{soak:!0});return!1}()},t}(s),e.Comment=u=function(e){function t(e){this.comment=e}return yt(t,e),t.prototype.isStatement=J,t.prototype.makeReturn=G,t.prototype.compileNode=function(e,t){var n;return n="/*"+ct(this.comment,this.tab)+(vt.call(this.comment,"\n")>=0?"\n"+this.tab:"")+"*/\n",(t||e.level)===N&&(n=e.indent+n),[this.makeCode(n)]},t}(s),e.Call=o=function(e){function n(e,t,n){this.args=null!=t?t:[],this.soak=n,this.isNew=!1,this.isSuper="super"===e,this.variable=this.isSuper?null:e}return yt(n,e),n.prototype.children=["variable","args"],n.prototype.newInstance=function(){var e,t;return e=(null!=(t=this.variable)?t.base:void 0)||this.variable,e instanceof n&&!e.isNew?e.newInstance():this.isNew=!0,this},n.prototype.superReference=function(e){var n,i;return i=e.scope.namedMethod(),(null!=i?i.klass:void 0)?(n=[new t(new x("__super__"))],i["static"]&&n.push(new t(new x("constructor"))),n.push(new t(new x(i.name))),new K(new x(i.klass),n).compile(e)):(null!=i?i.ctor:void 0)?""+i.name+".__super__.constructor":this.error("cannot call super outside of an instance method.")},n.prototype.superThis=function(e){var t;return t=e.scope.method,t&&!t.klass&&t.context||"this"},n.prototype.unfoldSoak=function(e){var t,i,s,r,o,a,c,h,l;if(this.soak){if(this.variable){if(i=pt(e,this,"variable"))return i;h=new K(this.variable).cacheReference(e),s=h[0],o=h[1]}else s=new x(this.superReference(e)),o=new K(s);return o=new n(o,this.args),o.isNew=this.isNew,s=new x("typeof "+s.compile(e)+' === "function"'),new g(s,new K(o),{soak:!0})}for(t=this,r=[];;)if(t.variable instanceof n)r.push(t),t=t.variable;else{if(!(t.variable instanceof K))break;if(r.push(t),!((t=t.variable.base)instanceof n))break}for(l=r.reverse(),a=0,c=l.length;c>a;a++)t=l[a],i&&(t.variable instanceof n?t.variable=i:t.variable.base=i),i=pt(e,t,"variable");return i},n.prototype.compileNode=function(e){var t,n,i,s,r,o,a,c,h,l;if(null!=(h=this.variable)&&(h.front=this.front),s=U.compileSplattedArray(e,this.args,!0),s.length)return this.compileSplat(e,s);for(i=[],l=this.args,n=a=0,c=l.length;c>a;n=++a)t=l[n],n&&i.push(this.makeCode(", ")),i.push.apply(i,t.compileToFragments(e,C));return r=[],this.isSuper?(o=this.superReference(e)+(".call("+this.superThis(e)),i.length&&(o+=", "),r.push(this.makeCode(o))):(this.isNew&&r.push(this.makeCode("new ")),r.push.apply(r,this.variable.compileToFragments(e,w)),r.push(this.makeCode("("))),r.push.apply(r,i),r.push(this.makeCode(")")),r},n.prototype.compileSplat=function(e,t){var n,i,s,r,o,a;return this.isSuper?[].concat(this.makeCode(""+this.superReference(e)+".apply("+this.superThis(e)+", "),t,this.makeCode(")")):this.isNew?(r=this.tab+H,[].concat(this.makeCode("(function(func, args, ctor) {\n"+r+"ctor.prototype = func.prototype;\n"+r+"var child = new ctor, result = func.apply(child, args);\n"+r+"return Object(result) === result ? result : child;\n"+this.tab+"})("),this.variable.compileToFragments(e,C),this.makeCode(", "),t,this.makeCode(", function(){})"))):(n=[],i=new K(this.variable),(o=i.properties.pop())&&i.isComplex()?(a=e.scope.freeVariable("ref"),n=n.concat(this.makeCode("("+a+" = "),i.compileToFragments(e,C),this.makeCode(")"),o.compileToFragments(e))):(s=i.compileToFragments(e,w),M.test(st(s))&&(s=this.wrapInBraces(s)),o?(a=st(s),s.push.apply(s,o.compileToFragments(e))):a="null",n=n.concat(s)),n=n.concat(this.makeCode(".apply("+a+", "),t,this.makeCode(")")))},n}(s),e.Extends=d=function(e){function t(e,t){this.child=e,this.parent=t}return yt(t,e),t.prototype.children=["child","parent"],t.prototype.compileToFragments=function(e){return new o(new K(new x(dt("extends"))),[this.child,this.parent]).compileToFragments(e)},t}(s),e.Access=t=function(e){function t(e,t){this.name=e,this.name.asKey=!0,this.soak="soak"===t}return yt(t,e),t.prototype.children=["name"],t.prototype.compileToFragments=function(e){var t;return t=this.name.compileToFragments(e),m.test(st(t))?t.unshift(this.makeCode(".")):(t.unshift(this.makeCode("[")),t.push(this.makeCode("]"))),t},t.prototype.isComplex=S,t}(s),e.Index=v=function(e){function t(e){this.index=e}return yt(t,e),t.prototype.children=["index"],t.prototype.compileToFragments=function(e){return[].concat(this.makeCode("["),this.index.compileToFragments(e,L),this.makeCode("]"))},t.prototype.isComplex=function(){return this.index.isComplex()},t}(s),e.Range=O=function(e){function t(e,t,n){this.from=e,this.to=t,this.exclusive="exclusive"===n,this.equals=this.exclusive?"":"="}return yt(t,e),t.prototype.children=["from","to"],t.prototype.compileVariables=function(e){var t,n,i,s,r;return e=at(e,{top:!0}),n=this.cacheToCodeFragments(this.from.cache(e,C)),this.fromC=n[0],this.fromVar=n[1],i=this.cacheToCodeFragments(this.to.cache(e,C)),this.toC=i[0],this.toVar=i[1],(t=et(e,"step"))&&(s=this.cacheToCodeFragments(t.cache(e,C)),this.step=s[0],this.stepVar=s[1]),r=[this.fromVar.match(M),this.toVar.match(M)],this.fromNum=r[0],this.toNum=r[1],this.stepVar?this.stepNum=this.stepVar.match(M):void 0},t.prototype.compileNode=function(e){var t,n,i,s,r,o,a,c,h,l,u,p,d,f;return this.fromVar||this.compileVariables(e),e.index?(a=this.fromNum&&this.toNum,r=et(e,"index"),o=et(e,"name"),h=o&&o!==r,p=""+r+" = "+this.fromC,this.toC!==this.toVar&&(p+=", "+this.toC),this.step!==this.stepVar&&(p+=", "+this.step),d=[""+r+" <"+this.equals,""+r+" >"+this.equals],c=d[0],s=d[1],n=this.stepNum?+this.stepNum>0?""+c+" "+this.toVar:""+s+" "+this.toVar:a?(f=[+this.fromNum,+this.toNum],i=f[0],u=f[1],f,u>=i?""+c+" "+u:""+s+" "+u):(t=this.stepVar?""+this.stepVar+" > 0":""+this.fromVar+" <= "+this.toVar,""+t+" ? "+c+" "+this.toVar+" : "+s+" "+this.toVar),l=this.stepVar?""+r+" += "+this.stepVar:a?h?u>=i?"++"+r:"--"+r:u>=i?""+r+"++":""+r+"--":h?""+t+" ? ++"+r+" : --"+r:""+t+" ? "+r+"++ : "+r+"--",h&&(p=""+o+" = "+p),h&&(l=""+o+" = "+l),[this.makeCode(""+p+"; "+n+"; "+l)]):this.compileArray(e)},t.prototype.compileArray=function(e){var t,n,i,s,r,o,a,c,h,l,u,p,d;return this.fromNum&&this.toNum&&Math.abs(this.fromNum-this.toNum)<=20?(h=function(){d=[];for(var e=p=+this.fromNum,t=+this.toNum;t>=p?t>=e:e>=t;t>=p?e++:e--)d.push(e);return d}.apply(this),this.exclusive&&h.pop(),[this.makeCode("["+h.join(", ")+"]")]):(o=this.tab+H,r=e.scope.freeVariable("i"),l=e.scope.freeVariable("results"),c="\n"+o+l+" = [];",this.fromNum&&this.toNum?(e.index=r,n=st(this.compileNode(e))):(u=""+r+" = "+this.fromC+(this.toC!==this.toVar?", "+this.toC:""),i=""+this.fromVar+" <= "+this.toVar,n="var "+u+"; "+i+" ? "+r+" <"+this.equals+" "+this.toVar+" : "+r+" >"+this.equals+" "+this.toVar+"; "+i+" ? "+r+"++ : "+r+"--"),a="{ "+l+".push("+r+"); }\n"+o+"return "+l+";\n"+e.indent,s=function(e){return null!=e?e.contains(function(e){return e instanceof x&&"arguments"===e.value&&!e.asKey}):void 0},(s(this.from)||s(this.to))&&(t=", arguments"),[this.makeCode("(function() {"+c+"\n"+o+"for ("+n+")"+a+"}).apply(this"+(null!=t?t:"")+")")])},t}(s),e.Slice=P=function(e){function t(e){this.range=e,t.__super__.constructor.call(this)}return yt(t,e),t.prototype.children=["range"],t.prototype.compileNode=function(e){var t,n,i,s,r,o,a;return a=this.range,r=a.to,i=a.from,s=i&&i.compileToFragments(e,L)||[this.makeCode("0")],r&&(t=r.compileToFragments(e,L),n=st(t),(this.range.exclusive||-1!==+n)&&(o=", "+(this.range.exclusive?n:M.test(n)?""+(+n+1):(t=r.compileToFragments(e,w),"+"+st(t)+" + 1 || 9e9")))),[this.makeCode(".slice("+st(s)+(o||"")+")")]},t}(s),e.Obj=R=function(e){function t(e,t){this.generated=null!=t?t:!1,this.objects=this.properties=e||[]}return yt(t,e),t.prototype.children=["properties"],t.prototype.compileNode=function(e){var t,n,s,r,o,a,c,h,l,p,d,f,m;if(l=this.properties,!l.length)return[this.makeCode(this.front?"({})":"{}")];if(this.generated)for(p=0,f=l.length;f>p;p++)c=l[p],c instanceof K&&c.error("cannot have an implicit value in an implicit object");for(s=e.indent+=H,a=this.lastNonComment(this.properties),t=[],n=d=0,m=l.length;m>d;n=++d)h=l[n],o=n===l.length-1?"":h===a||h instanceof u?"\n":",\n",r=h instanceof u?"":s,h instanceof i&&h.variable instanceof K&&h.variable.hasProperties()&&h.variable.error("Invalid object key"),h instanceof K&&h["this"]&&(h=new i(h.properties[0].name,h,"object")),h instanceof u||(h instanceof i||(h=new i(h,h,"object")),(h.variable.base||h.variable).asKey=!0),r&&t.push(this.makeCode(r)),t.push.apply(t,h.compileToFragments(e,N)),o&&t.push(this.makeCode(o));return t.unshift(this.makeCode("{"+(l.length&&"\n"))),t.push(this.makeCode(""+(l.length&&"\n"+this.tab)+"}")),this.front?this.wrapInBraces(t):t},t.prototype.assigns=function(e){var t,n,i,s;for(s=this.properties,n=0,i=s.length;i>n;n++)if(t=s[n],t.assigns(e))return!0;return!1},t}(s),e.Arr=n=function(e){function t(e){this.objects=e||[]}return yt(t,e),t.prototype.children=["objects"],t.prototype.compileNode=function(e){var t,n,i,s,r,o,a;if(!this.objects.length)return[this.makeCode("[]")];if(e.indent+=H,t=U.compileSplattedArray(e,this.objects),t.length)return t;for(t=[],n=function(){var t,n,i,s;for(i=this.objects,s=[],t=0,n=i.length;n>t;t++)r=i[t],s.push(r.compileToFragments(e,C));return s}.call(this),s=o=0,a=n.length;a>o;s=++o)i=n[s],s&&t.push(this.makeCode(", ")),t.push.apply(t,i);return st(t).indexOf("\n")>=0?(t.unshift(this.makeCode("[\n"+e.indent)),t.push(this.makeCode("\n"+this.tab+"]"))):(t.unshift(this.makeCode("[")),t.push(this.makeCode("]"))),t},t.prototype.assigns=function(e){var t,n,i,s;for(s=this.objects,n=0,i=s.length;i>n;n++)if(t=s[n],t.assigns(e))return!0;return!1},t}(s),e.Class=a=function(e){function n(e,t,n){this.variable=e,this.parent=t,this.body=null!=n?n:new r,this.boundFuncs=[],this.body.classBody=!0}return yt(n,e),n.prototype.children=["variable","parent","body"],n.prototype.determineName=function(){var e,n;return this.variable?(e=(n=rt(this.variable.properties))?n instanceof t&&n.name.value:this.variable.base.value,vt.call(B,e)>=0&&this.variable.error("class variable name may not be "+e),e&&(e=m.test(e)&&e)):null},n.prototype.setContext=function(e){return this.body.traverseChildren(!1,function(t){return t.classBody?!1:t instanceof x&&"this"===t.value?t.value=e:t instanceof h&&(t.klass=e,t.bound)?t.context=e:void 0})},n.prototype.addBoundFunctions=function(e){var n,i,s,r,o;for(o=this.boundFuncs,s=0,r=o.length;r>s;s++)n=o[s],i=new K(new x("this"),[new t(n)]).compile(e),this.ctor.body.unshift(new x(""+i+" = "+dt("bind")+"("+i+", this)"))},n.prototype.addProperties=function(e,n,s){var r,o,a,c,l;return l=e.base.properties.slice(0),a=function(){var e;for(e=[];r=l.shift();)r instanceof i&&(o=r.variable.base,delete r.context,c=r.value,"constructor"===o.value?(this.ctor&&r.error("cannot define more than one constructor in a class"),c.bound&&r.error("cannot define a constructor as a bound function"),c instanceof h?r=this.ctor=c:(this.externalCtor=s.scope.freeVariable("class"),r=new i(new x(this.externalCtor),c))):r.variable["this"]?(c["static"]=!0,c.bound&&(c.context=n)):(r.variable=new K(new x(n),[new t(new x("prototype")),new t(o)]),c instanceof h&&c.bound&&(this.boundFuncs.push(o),c.bound=!1))),e.push(r);return e}.call(this),Q(a)},n.prototype.walkBody=function(e,t){var i=this;return this.traverseChildren(!1,function(s){var o,a,c,h,l,u,p;if(o=!0,s instanceof n)return!1;if(s instanceof r){for(p=a=s.expressions,c=l=0,u=p.length;u>l;c=++l)h=p[c],h instanceof K&&h.isObject(!0)&&(o=!1,a[c]=i.addProperties(h,e,t));s.expressions=a=it(a)}return o&&!(s instanceof n)})},n.prototype.hoistDirectivePrologue=function(){var e,t,n;for(t=0,e=this.body.expressions;(n=e[t])&&n instanceof u||n instanceof K&&n.isString();)++t;return this.directives=e.splice(0,t)},n.prototype.ensureConstructor=function(e,t){var n,s,r;return n=!this.ctor,this.ctor||(this.ctor=new h),this.ctor.ctor=this.ctor.name=e,this.ctor.klass=null,this.ctor.noReturn=!0,n?(this.parent&&(r=new x(""+e+".__super__.constructor.apply(this, arguments)")),this.externalCtor&&(r=new x(""+this.externalCtor+".apply(this, arguments)")),r&&(s=new x(t.scope.freeVariable("ref")),this.ctor.body.unshift(new i(s,r))),this.addBoundFunctions(t),r&&(this.ctor.body.push(s),this.ctor.body.makeReturn()),this.body.expressions.unshift(this.ctor)):this.addBoundFunctions(t)},n.prototype.compileNode=function(e){var t,n,s,r,o,a,l;return n=this.determineName(),o=n||"_Class",o.reserved&&(o="_"+o),r=new x(o),this.hoistDirectivePrologue(),this.setContext(o),this.walkBody(o,e),this.ensureConstructor(o,e),this.body.spaced=!0,this.ctor instanceof h||this.body.expressions.unshift(this.ctor),this.body.expressions.push(r),(l=this.body.expressions).unshift.apply(l,this.directives),t=c.wrap(this.body),this.parent&&(this.superClass=new x(e.scope.freeVariable("super",!1)),this.body.expressions.unshift(new d(r,this.superClass)),t.args.push(this.parent),a=t.variable.params||t.variable.base.params,a.push(new I(this.superClass))),s=new _(t,!0),this.variable&&(s=new i(this.variable,s)),s.compileToFragments(e)},n}(s),e.Assign=i=function(e){function n(e,t,n,i){var s,r,o;this.variable=e,this.value=t,this.context=n,this.param=i&&i.param,this.subpattern=i&&i.subpattern,o=r=this.variable.unwrapAll().value,s=vt.call(B,o)>=0,s&&"object"!==this.context&&this.variable.error('variable name may not be "'+r+'"')}return yt(n,e),n.prototype.children=["variable","value"],n.prototype.isStatement=function(e){return(null!=e?e.level:void 0)===N&&null!=this.context&&vt.call(this.context,"?")>=0},n.prototype.assigns=function(e){return this["object"===this.context?"value":"variable"].assigns(e)},n.prototype.unfoldSoak=function(e){return pt(e,this,"variable")},n.prototype.compileNode=function(e){var t,n,i,s,r,o,a,c,l,u,p;if(i=this.variable instanceof K){if(this.variable.isArray()||this.variable.isObject())return this.compilePatternMatch(e);if(this.variable.isSplice())return this.compileSplice(e);if("||="===(c=this.context)||"&&="===c||"?="===c)return this.compileConditional(e)}return n=this.variable.compileToFragments(e,C),r=st(n),this.context||(a=this.variable.unwrapAll(),a.isAssignable()||this.variable.error('"'+this.variable.compile(e)+'" cannot be assigned'),("function"==typeof a.hasProperties?a.hasProperties():void 0)||(this.param?e.scope.add(r,"var"):e.scope.find(r))),this.value instanceof h&&(s=E.exec(r))&&(s[1]&&(this.value.klass=s[1]),this.value.name=null!=(l=null!=(u=null!=(p=s[2])?p:s[3])?u:s[4])?l:s[5]),o=this.value.compileToFragments(e,C),"object"===this.context?n.concat(this.makeCode(": "),o):(t=n.concat(this.makeCode(" "+(this.context||"=")+" "),o),e.level<=C?t:this.wrapInBraces(t))},n.prototype.compilePatternMatch=function(e){var i,s,r,o,a,c,h,l,u,p,d,f,b,k,g,y,w,T,L,E,D,S,R,A,I,O,j,M;if(y=e.level===N,T=this.value,d=this.variable.base.objects,!(f=d.length))return r=T.compileToFragments(e),e.level>=F?this.wrapInBraces(r):r;if(h=this.variable.isObject(),y&&1===f&&!((p=d[0])instanceof U))return p instanceof n?(R=p,A=R.variable,c=A.base,p=R.value):c=h?p["this"]?p.properties[0].name:p:new x(0),i=m.test(c.unwrap().value||0),T=new K(T),T.properties.push(new(i?t:v)(c)),I=p.unwrap().value,vt.call($,I)>=0&&p.error("assignment to a reserved word: "+p.compile(e)),new n(p,T,null,{param:this.param}).compileToFragments(e,N);for(L=T.compileToFragments(e,C),E=st(L),s=[],g=!1,(!m.test(E)||this.variable.assigns(E))&&(s.push([this.makeCode(""+(b=e.scope.freeVariable("ref"))+" = ")].concat(wt.call(L))),L=[this.makeCode(b)],E=b),a=D=0,S=d.length;S>D;a=++D)p=d[a],c=a,h&&(p instanceof n?(O=p,j=O.variable,c=j.base,p=O.value):p.base instanceof _?(M=new K(p.unwrapAll()).cacheReference(e),p=M[0],c=M[1]):c=p["this"]?p.properties[0].name:p),!g&&p instanceof U?(u=p.name.unwrap().value,p=p.unwrap(),w=""+f+" <= "+E+".length ? "+dt("slice")+".call("+E+", "+a,(k=f-a-1)?(l=e.scope.freeVariable("i"),w+=", "+l+" = "+E+".length - "+k+") : ("+l+" = "+a+", [])"):w+=") : []",w=new x(w),g=""+l+"++"):(u=p.unwrap().value,p instanceof U&&p.error("multiple splats are disallowed in an assignment"),"number"==typeof c?(c=new x(g||c),i=!1):i=h&&m.test(c.unwrap().value||0),w=new K(new x(E),[new(i?t:v)(c)])),null!=u&&vt.call($,u)>=0&&p.error("assignment to a reserved word: "+p.compile(e)),s.push(new n(p,w,null,{param:this.param,subpattern:!0}).compileToFragments(e,C));
+return y||this.subpattern||s.push(L),o=this.joinFragmentArrays(s,", "),e.level=0&&(e.isExistentialEquals=!0),new A(this.context.slice(0,-1),t,new n(i,this.value,"=")).compileToFragments(e)},n.prototype.compileSplice=function(e){var t,n,i,s,r,o,a,c,h,l,u,p;return l=this.variable.properties.pop().range,i=l.from,a=l.to,n=l.exclusive,o=this.variable.compile(e),i?(u=this.cacheToCodeFragments(i.cache(e,F)),s=u[0],r=u[1]):s=r="0",a?(null!=i?i.isSimpleNumber():void 0)&&a.isSimpleNumber()?(a=+a.compile(e)-+r,n||(a+=1)):(a=a.compile(e,w)+" - "+r,n||(a+=" + 1")):a="9e9",p=this.value.cache(e,C),c=p[0],h=p[1],t=[].concat(this.makeCode("[].splice.apply("+o+", ["+s+", "+a+"].concat("),c,this.makeCode(")), "),h),e.level>N?this.wrapInBraces(t):t},n}(s),e.Code=h=function(e){function t(e,t,n){this.params=e||[],this.body=t||new r,this.bound="boundfunc"===n,this.bound&&(this.context="_this")}return yt(t,e),t.prototype.children=["params","body"],t.prototype.isStatement=function(){return!!this.ctor},t.prototype.jumps=S,t.prototype.compileNode=function(e){var t,s,r,o,a,c,h,l,u,p,d,f,m,b,k,y,v,T,C,F,L,N,E,D,S,R,I,_,$;for(e.scope=new V(e.scope,this.body,this),e.scope.shared=et(e,"sharedScope"),e.indent+=H,delete e.bare,delete e.isExistentialEquals,u=[],r=[],this.eachParamName(function(t){return e.scope.check(t)?void 0:e.scope.parameter(t)}),S=this.params,k=0,C=S.length;C>k;k++)if(l=S[k],l.splat){for(R=this.params,y=0,F=R.length;F>y;y++)h=R[y].name,h["this"]&&(h=h.properties[0].name),h.value&&e.scope.add(h.value,"var",!0);d=new i(new K(new n(function(){var t,n,i,s;for(i=this.params,s=[],t=0,n=i.length;n>t;t++)h=i[t],s.push(h.asReference(e));return s}.call(this))),new K(new x("arguments")));break}for(I=this.params,v=0,L=I.length;L>v;v++)l=I[v],l.isComplex()?(m=p=l.asReference(e),l.value&&(m=new A("?",p,l.value)),r.push(new i(new K(l.name),m,"=",{param:!0}))):(p=l,l.value&&(c=new x(p.name.value+" == null"),m=new i(new K(l.name),l.value,"="),r.push(new g(c,m)))),d||u.push(p);for(b=this.body.isEmpty(),d&&r.unshift(d),r.length&&(_=this.body.expressions).unshift.apply(_,r),o=T=0,N=u.length;N>T;o=++T)h=u[o],u[o]=h.compileToFragments(e),e.scope.parameter(st(u[o]));for(f=[],this.eachParamName(function(e,t){return vt.call(f,e)>=0&&t.error("multiple parameters named '"+e+"'"),f.push(e)}),b||this.noReturn||this.body.makeReturn(),this.bound&&((null!=($=e.scope.parent.method)?$.bound:void 0)?this.bound=this.context=e.scope.parent.method.context:this["static"]||e.scope.parent.assign("_this","this")),a=e.indent,s="function",this.ctor&&(s+=" "+this.name),s+="(",t=[this.makeCode(s)],o=D=0,E=u.length;E>D;o=++D)h=u[o],o&&t.push(this.makeCode(", ")),t.push.apply(t,h);return t.push(this.makeCode(") {")),this.body.isEmpty()||(t=t.concat(this.makeCode("\n"),this.body.compileWithDeclarations(e),this.makeCode("\n"+this.tab))),t.push(this.makeCode("}")),this.ctor?[this.makeCode(this.tab)].concat(wt.call(t)):this.front||e.level>=w?this.wrapInBraces(t):t},t.prototype.eachParamName=function(e){var t,n,i,s,r;for(s=this.params,r=[],n=0,i=s.length;i>n;n++)t=s[n],r.push(t.eachName(e));return r},t.prototype.traverseChildren=function(e,n){return e?t.__super__.traverseChildren.call(this,e,n):void 0},t}(s),e.Param=I=function(e){function t(e,t,n){var i;this.name=e,this.value=t,this.splat=n,i=e=this.name.unwrapAll().value,vt.call(B,i)>=0&&this.name.error('parameter name "'+e+'" is not allowed')}return yt(t,e),t.prototype.children=["name","value"],t.prototype.compileToFragments=function(e){return this.name.compileToFragments(e,C)},t.prototype.asReference=function(e){var t;return this.reference?this.reference:(t=this.name,t["this"]?(t=t.properties[0].name,t.value.reserved&&(t=new x(e.scope.freeVariable(t.value)))):t.isComplex()&&(t=new x(e.scope.freeVariable("arg"))),t=new K(t),this.splat&&(t=new U(t)),this.reference=t)},t.prototype.isComplex=function(){return this.name.isComplex()},t.prototype.eachName=function(e,t){var n,s,r,o,a,c;if(null==t&&(t=this.name),n=function(t){var n;return n=t.properties[0].name,n.value.reserved?void 0:e(n.value,n)},t instanceof x)return e(t.value,t);if(t instanceof K)return n(t);for(c=t.objects,o=0,a=c.length;a>o;o++)r=c[o],r instanceof i?this.eachName(e,r.value.unwrap()):r instanceof U?(s=r.name.unwrap(),e(s.value,s)):r instanceof K?r.isArray()||r.isObject()?this.eachName(e,r.base):r["this"]?n(r):e(r.base.value,r.base):r.error("illegal parameter "+r.compile())},t}(s),e.Splat=U=function(e){function t(e){this.name=e.compile?e:new x(e)}return yt(t,e),t.prototype.children=["name"],t.prototype.isAssignable=J,t.prototype.assigns=function(e){return this.name.assigns(e)},t.prototype.compileToFragments=function(e){return this.name.compileToFragments(e)},t.prototype.unwrap=function(){return this.name},t.compileSplattedArray=function(e,n,i){var s,r,o,a,c,h,l,u,p,d;for(l=-1;(u=n[++l])&&!(u instanceof t););if(l>=n.length)return[];if(1===n.length)return u=n[0],c=u.compileToFragments(e,C),i?c:[].concat(u.makeCode(""+dt("slice")+".call("),c,u.makeCode(")"));for(s=n.slice(l),h=p=0,d=s.length;d>p;h=++p)u=s[h],o=u.compileToFragments(e,C),s[h]=u instanceof t?[].concat(u.makeCode(""+dt("slice")+".call("),o,u.makeCode(")")):[].concat(u.makeCode("["),o,u.makeCode("]"));return 0===l?(u=n[0],a=u.joinFragmentArrays(s.slice(1),", "),s[0].concat(u.makeCode(".concat("),a,u.makeCode(")"))):(r=function(){var t,i,s,r;for(s=n.slice(0,l),r=[],t=0,i=s.length;i>t;t++)u=s[t],r.push(u.compileToFragments(e,C));return r}(),r=n[0].joinFragmentArrays(r,", "),a=n[l].joinFragmentArrays(s,", "),[].concat(n[0].makeCode("["),r,n[l].makeCode("].concat("),a,rt(n).makeCode(")")))},t}(s),e.While=z=function(e){function t(e,t){this.condition=(null!=t?t.invert:void 0)?e.invert():e,this.guard=null!=t?t.guard:void 0}return yt(t,e),t.prototype.children=["condition","guard","body"],t.prototype.isStatement=J,t.prototype.makeReturn=function(e){return e?t.__super__.makeReturn.apply(this,arguments):(this.returns=!this.jumps({loop:!0}),this)},t.prototype.addBody=function(e){return this.body=e,this},t.prototype.jumps=function(){var e,t,n,i;if(e=this.body.expressions,!e.length)return!1;for(n=0,i=e.length;i>n;n++)if(t=e[n],t.jumps({loop:!0}))return t;return!1},t.prototype.compileNode=function(e){var t,n,i,s;return e.indent+=H,s="",n=this.body,n.isEmpty()?n=this.makeCode(""):(this.returns&&(n.makeReturn(i=e.scope.freeVariable("results")),s=""+this.tab+i+" = [];\n"),this.guard&&(n.expressions.length>1?n.expressions.unshift(new g(new _(this.guard).invert(),new x("continue"))):this.guard&&(n=r.wrap([new g(this.guard,n)]))),n=[].concat(this.makeCode("\n"),n.compileToFragments(e,N),this.makeCode("\n"+this.tab))),t=[].concat(this.makeCode(s+this.tab+"while ("),this.condition.compileToFragments(e,L),this.makeCode(") {"),n,this.makeCode("}")),this.returns&&t.push(this.makeCode("\n"+this.tab+"return "+i+";")),t},t}(s),e.Op=A=function(e){function t(e,t,i,s){if("in"===e)return new y(t,i);if("do"===e)return this.generateDo(t);if("new"===e){if(t instanceof o&&!t["do"]&&!t.isNew)return t.newInstance();(t instanceof h&&t.bound||t["do"])&&(t=new _(t))}return this.operator=n[e]||e,this.first=t,this.second=i,this.flip=!!s,this}var n,s;return yt(t,e),n={"==":"===","!=":"!==",of:"in"},s={"!==":"===","===":"!=="},t.prototype.children=["first","second"],t.prototype.isSimpleNumber=S,t.prototype.isUnary=function(){return!this.second},t.prototype.isComplex=function(){var e;return!(this.isUnary()&&("+"===(e=this.operator)||"-"===e))||this.first.isComplex()},t.prototype.isChainable=function(){var e;return"<"===(e=this.operator)||">"===e||">="===e||"<="===e||"==="===e||"!=="===e},t.prototype.invert=function(){var e,n,i,r,o;if(this.isChainable()&&this.first.isChainable()){for(e=!0,n=this;n&&n.operator;)e&&(e=n.operator in s),n=n.first;if(!e)return new _(this).invert();for(n=this;n&&n.operator;)n.invert=!n.invert,n.operator=s[n.operator],n=n.first;return this}return(r=s[this.operator])?(this.operator=r,this.first.unwrap()instanceof t&&this.first.invert(),this):this.second?new _(this).invert():"!"===this.operator&&(i=this.first.unwrap())instanceof t&&("!"===(o=i.operator)||"in"===o||"instanceof"===o)?i:new t("!",this)},t.prototype.unfoldSoak=function(e){var t;return("++"===(t=this.operator)||"--"===t||"delete"===t)&&pt(e,this,"first")},t.prototype.generateDo=function(e){var t,n,s,r,a,c,l,u;for(r=[],n=e instanceof i&&(a=e.value.unwrap())instanceof h?a:e,u=n.params||[],c=0,l=u.length;l>c;c++)s=u[c],s.value?(r.push(s.value),delete s.value):r.push(s);return t=new o(e,r),t["do"]=!0,t},t.prototype.compileNode=function(e){var t,n,i,s;return n=this.isChainable()&&this.first.isChainable(),n||(this.first.front=this.front),"delete"===this.operator&&e.scope.check(this.first.unwrapAll().value)&&this.error("delete operand may not be argument or var"),("--"===(i=this.operator)||"++"===i)&&(s=this.first.unwrapAll().value,vt.call(B,s)>=0)&&this.error('cannot increment/decrement "'+this.first.unwrapAll().value+'"'),this.isUnary()?this.compileUnary(e):n?this.compileChain(e):"?"===this.operator?this.compileExistence(e):(t=[].concat(this.first.compileToFragments(e,F),this.makeCode(" "+this.operator+" "),this.second.compileToFragments(e,F)),e.level<=F?t:this.wrapInBraces(t))},t.prototype.compileChain=function(e){var t,n,i,s;return s=this.first.second.cache(e),this.first.second=s[0],i=s[1],n=this.first.compileToFragments(e,F),t=n.concat(this.makeCode(" "+(this.invert?"&&":"||")+" "),i.compileToFragments(e),this.makeCode(" "+this.operator+" "),this.second.compileToFragments(e,F)),this.wrapInBraces(t)},t.prototype.compileExistence=function(e){var t,n;return!e.isExistentialEquals&&this.first.isComplex()?(n=new x(e.scope.freeVariable("ref")),t=new _(new i(n,this.first))):(t=this.first,n=t),new g(new p(t),n,{type:"if"}).addElse(this.second).compileToFragments(e)},t.prototype.compileUnary=function(e){var n,i,s;return i=[],n=this.operator,i.push([this.makeCode(n)]),"!"===n&&this.first instanceof p?(this.first.negated=!this.first.negated,this.first.compileToFragments(e)):e.level>=w?new _(this).compileToFragments(e):(s="+"===n||"-"===n,("new"===n||"typeof"===n||"delete"===n||s&&this.first instanceof t&&this.first.operator===n)&&i.push([this.makeCode(" ")]),(s&&this.first instanceof t||"new"===n&&this.first.isStatement(e))&&(this.first=new _(this.first)),i.push(this.first.compileToFragments(e,F)),this.flip&&i.reverse(),this.joinFragmentArrays(i,""))},t.prototype.toString=function(e){return t.__super__.toString.call(this,e,this.constructor.name+" "+this.operator)},t}(s),e.In=y=function(e){function t(e,t){this.object=e,this.array=t}return yt(t,e),t.prototype.children=["object","array"],t.prototype.invert=D,t.prototype.compileNode=function(e){var t,n,i,s,r;if(this.array instanceof K&&this.array.isArray()){for(r=this.array.base.objects,i=0,s=r.length;s>i;i++)if(n=r[i],n instanceof U){t=!0;break}if(!t)return this.compileOrTest(e)}return this.compileLoopTest(e)},t.prototype.compileOrTest=function(e){var t,n,i,s,r,o,a,c,h,l,u,p;if(0===this.array.base.objects.length)return[this.makeCode(""+!!this.negated)];for(l=this.object.cache(e,F),o=l[0],r=l[1],u=this.negated?[" !== "," && "]:[" === "," || "],t=u[0],n=u[1],a=[],p=this.array.base.objects,i=c=0,h=p.length;h>c;i=++c)s=p[i],i&&a.push(this.makeCode(n)),a=a.concat(i?r:o,this.makeCode(t),s.compileToFragments(e,w));return e.level= 0"))),st(i)===st(n)?t:(t=i.concat(this.makeCode(", "),t),e.level+B)||(F=I.freeVariable("len")),a=""+w+y+" = 0, "+F+" = "+P+".length",c=""+w+y+" = "+P+".length - 1",s=""+y+" < "+F,o=""+y+" >= 0",this.step?(B?u&&(s=o,a=c):(s=""+V+" > 0 ? "+s+" : "+o,a="("+V+" > 0 ? ("+a+") : "+c+")"),b=""+y+" += "+V):b=""+(v!==y?"++"+y:""+y+"++"),p=[this.makeCode(""+a+"; "+s+"; "+w+b)])),this.returns&&(S=""+this.tab+A+" = [];\n",R="\n"+this.tab+"return "+A+";",t.makeReturn(A)),this.guard&&(t.expressions.length>1?t.expressions.unshift(new g(new _(this.guard).invert(),new x("continue"))):this.guard&&(t=r.wrap([new g(this.guard,t)]))),this.pattern&&t.expressions.unshift(new i(this.name,new x(""+P+"["+v+"]"))),l=[].concat(this.makeCode(h),this.pluckDirectCall(e,t)),E&&(U="\n"+f+E+";"),this.object&&(p=[this.makeCode(""+v+" in "+P)],this.own&&(d="\n"+f+"if (!"+dt("hasProp")+".call("+P+", "+v+")) continue;")),n=t.compileToFragments(at(e,{indent:f}),N),n&&n.length>0&&(n=[].concat(this.makeCode("\n"),n,this.makeCode("\n"))),[].concat(l,this.makeCode(""+(S||"")+this.tab+"for ("),p,this.makeCode(") {"+d+U),n,this.makeCode(""+this.tab+"}"+(R||"")))},t.prototype.pluckDirectCall=function(e,t){var n,s,r,a,c,l,u,p,d,f,m,b,k,g,y;for(s=[],f=t.expressions,c=p=0,d=f.length;d>p;c=++p)r=f[c],r=r.unwrapAll(),r instanceof o&&(u=r.variable.unwrapAll(),(u instanceof h||u instanceof K&&(null!=(m=u.base)?m.unwrapAll():void 0)instanceof h&&1===u.properties.length&&("call"===(b=null!=(k=u.properties[0].name)?k.value:void 0)||"apply"===b))&&(a=(null!=(g=u.base)?g.unwrapAll():void 0)||u,l=new x(e.scope.freeVariable("fn")),n=new K(l),u.base&&(y=[n,u],u.base=y[0],n=y[1]),t.expressions[c]=new o(n,r.args),s=s.concat(this.makeCode(this.tab),new i(l,a).compileToFragments(e,N),this.makeCode(";\n"))));return s},t}(z),e.Switch=q=function(e){function t(e,t,n){this.subject=e,this.cases=t,this.otherwise=n}return yt(t,e),t.prototype.children=["subject","cases","otherwise"],t.prototype.isStatement=J,t.prototype.jumps=function(e){var t,n,i,s,r,o,a;for(null==e&&(e={block:!0}),r=this.cases,i=0,s=r.length;s>i;i++)if(o=r[i],n=o[0],t=o[1],t.jumps(e))return t;return null!=(a=this.otherwise)?a.jumps(e):void 0},t.prototype.makeReturn=function(e){var t,n,i,s,o;for(s=this.cases,n=0,i=s.length;i>n;n++)t=s[n],t[1].makeReturn(e);return e&&(this.otherwise||(this.otherwise=new r([new x("void 0")]))),null!=(o=this.otherwise)&&o.makeReturn(e),this},t.prototype.compileNode=function(e){var t,n,i,s,r,o,a,c,h,l,u,p,d,f,m,b;for(c=e.indent+H,h=e.indent=c+H,o=[].concat(this.makeCode(this.tab+"switch ("),this.subject?this.subject.compileToFragments(e,L):this.makeCode("false"),this.makeCode(") {\n")),f=this.cases,a=l=0,p=f.length;p>l;a=++l){for(m=f[a],s=m[0],t=m[1],b=it([s]),u=0,d=b.length;d>u;u++)i=b[u],this.subject||(i=i.invert()),o=o.concat(this.makeCode(c+"case "),i.compileToFragments(e,L),this.makeCode(":\n"));if((n=t.compileToFragments(e,N)).length>0&&(o=o.concat(n,this.makeCode("\n"))),a===this.cases.length-1&&!this.otherwise)break;r=this.lastNonComment(t.expressions),r instanceof j||r instanceof x&&r.jumps()&&"debugger"!==r.value||o.push(i.makeCode(h+"break;\n"))}return this.otherwise&&this.otherwise.expressions.length&&o.push.apply(o,[this.makeCode(c+"default:\n")].concat(wt.call(this.otherwise.compileToFragments(e,N)),[this.makeCode("\n")])),o.push(this.makeCode(this.tab+"}")),o},t}(s),e.If=g=function(e){function t(e,t,n){this.body=t,null==n&&(n={}),this.condition="unless"===n.type?e.invert():e,this.elseBody=null,this.isChain=!1,this.soak=n.soak}return yt(t,e),t.prototype.children=["condition","body","elseBody"],t.prototype.bodyNode=function(){var e;return null!=(e=this.body)?e.unwrap():void 0},t.prototype.elseBodyNode=function(){var e;return null!=(e=this.elseBody)?e.unwrap():void 0},t.prototype.addElse=function(e){return this.isChain?this.elseBodyNode().addElse(e):(this.isChain=e instanceof t,this.elseBody=this.ensureBlock(e)),this},t.prototype.isStatement=function(e){var t;return(null!=e?e.level:void 0)===N||this.bodyNode().isStatement(e)||(null!=(t=this.elseBodyNode())?t.isStatement(e):void 0)},t.prototype.jumps=function(e){var t;return this.body.jumps(e)||(null!=(t=this.elseBody)?t.jumps(e):void 0)},t.prototype.compileNode=function(e){return this.isStatement(e)?this.compileStatement(e):this.compileExpression(e)},t.prototype.makeReturn=function(e){return e&&(this.elseBody||(this.elseBody=new r([new x("void 0")]))),this.body&&(this.body=new r([this.body.makeReturn(e)])),this.elseBody&&(this.elseBody=new r([this.elseBody.makeReturn(e)])),this},t.prototype.ensureBlock=function(e){return e instanceof r?e:new r([e])},t.prototype.compileStatement=function(e){var n,i,s,r,o,a,c;return s=et(e,"chainChild"),(o=et(e,"isExistentialEquals"))?new t(this.condition.invert(),this.elseBodyNode(),{type:"if"}).compileToFragments(e):(c=e.indent+H,r=this.condition.compileToFragments(e,L),i=this.ensureBlock(this.body).compileToFragments(at(e,{indent:c})),a=[].concat(this.makeCode("if ("),r,this.makeCode(") {\n"),i,this.makeCode("\n"+this.tab+"}")),s||a.unshift(this.makeCode(this.tab)),this.elseBody?(n=a.concat(this.makeCode(" else ")),this.isChain?(e.chainChild=!0,n=n.concat(this.elseBody.unwrap().compileToFragments(e,N))):n=n.concat(this.makeCode("{\n"),this.elseBody.compileToFragments(at(e,{indent:c}),N),this.makeCode("\n"+this.tab+"}")),n):a)},t.prototype.compileExpression=function(e){var t,n,i,s;return i=this.condition.compileToFragments(e,T),n=this.bodyNode().compileToFragments(e,C),t=this.elseBodyNode()?this.elseBodyNode().compileToFragments(e,C):[this.makeCode("void 0")],s=i.concat(this.makeCode(" ? "),n,this.makeCode(" : "),t),e.level>=T?this.wrapInBraces(s):s},t.prototype.unfoldSoak=function(){return this.soak&&this},t}(s),c={wrap:function(e,n,i){var s,a,c,l,u;return e.jumps()?e:(l=new h([],r.wrap([e])),s=[],a=e.contains(this.isLiteralArguments),a&&e.classBody&&a.error("Class bodies shouldn't reference arguments"),(a||e.contains(this.isLiteralThis))&&(u=new x(a?"apply":"call"),s=[new x("this")],a&&s.push(new x("arguments")),l=new K(l,[new t(u)])),l.noReturn=i,c=new o(l,s),n?r.wrap([c]):c)},isLiteralArguments:function(e){return e instanceof x&&"arguments"===e.value&&!e.asKey},isLiteralThis:function(e){return e instanceof x&&"this"===e.value&&!e.asKey||e instanceof h&&e.bound||e instanceof o&&e.isSuper}},pt=function(e,t,n){var i;if(i=t[n].unfoldSoak(e))return t[n]=i.body,i.body=new K(t),i},Y={"extends":function(){return"function(child, parent) { for (var key in parent) { if ("+dt("hasProp")+".call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }"},bind:function(){return"function(fn, me){ return function(){ return fn.apply(me, arguments); }; }"},indexOf:function(){return"[].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }"},hasProp:function(){return"{}.hasOwnProperty"},slice:function(){return"[].slice"}},N=1,L=2,C=3,T=4,F=5,w=6,H=" ",b="[$A-Za-z_\\x7f-\\uffff][$\\w\\x7f-\\uffff]*",m=RegExp("^"+b+"$"),M=/^[+-]?\d+$/,E=RegExp("^(?:("+b+")\\.prototype(?:\\.("+b+")|\\[(\"(?:[^\\\\\"\\r\\n]|\\\\.)*\"|'(?:[^\\\\'\\r\\n]|\\\\.)*')\\]|\\[(0x[\\da-fA-F]+|\\d*\\.?\\d+(?:[eE][+-]?\\d+)?)\\]))|("+b+")$"),k=/^['"]/,dt=function(e){var t;return t="__"+e,V.root.assign(t,Y[e]()),t},ct=function(e,t){return e=e.replace(/\n/g,"$&"+t),e.replace(/\s+$/,"")}}.call(this),t.exports}(),require["./sourcemap"]=function(){var e={},t={exports:e};return function(){var e,n;e=function(){function e(e){this.line=e,this.columns=[]}return e.prototype.add=function(e,t,n){var i,s;return s=t[0],i=t[1],null==n&&(n={}),this.columns[e]&&n.noReplace?void 0:this.columns[e]={line:this.line,column:e,sourceLine:s,sourceColumn:i}},e.prototype.sourceLocation=function(e){for(var t;!((t=this.columns[e])||0>=e);)e--;return t&&[t.sourceLine,t.sourceColumn]},e}(),n=function(){function t(){this.lines=[]}var n,i,s,r;return t.prototype.add=function(t,n,i){var s,r,o,a;return null==i&&(i={}),r=n[0],s=n[1],o=(a=this.lines)[r]||(a[r]=new e(r)),o.add(s,t,i)},t.prototype.sourceLocation=function(e){var t,n,i;for(n=e[0],t=e[1];!((i=this.lines[n])||0>=n);)n--;return i&&i.sourceLocation(t)},t.prototype.generate=function(e,t){var n,i,s,r,o,a,c,h,l,u,p,d,f,m,b,k;for(null==e&&(e={}),null==t&&(t=null),u=0,i=0,r=0,s=0,h=!1,n="",b=this.lines,a=p=0,f=b.length;f>p;a=++p)if(o=b[a])for(k=o.columns,d=0,m=k.length;m>d;d++)if(c=k[d]){for(;ue?1:0,a=(Math.abs(e)<<1)+o;a||!t;)n=a&r,a>>=s,a&&(n|=i),t+=this.encodeBase64(n);return t},n="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",t.prototype.encodeBase64=function(e){return n[e]||function(){throw new Error("Cannot Base64 encode value: "+e)}()},t}(),t.exports=n}.call(this),t.exports}(),require["./coffee-script"]=function(){var e={},t={exports:e};return function(){var t,n,i,s,r,o,a,c,h,l,u,p,d,f,m,b,k,g,y,v,w,T,C={}.hasOwnProperty;if(l=require("fs"),y=require("vm"),k=require("path"),s=require("child_process"),t=require("./lexer").Lexer,f=require("./parser").parser,u=require("./helpers"),i=require("./sourcemap"),e.VERSION="1.6.3",e.helpers=u,e.compile=r=function(e,t){var n,s,r,o,a,c,h,l,d,m,b,k;for(null==t&&(t={}),d=u.merge,t.sourceMap&&(l=new i),a=f.parse(p.tokenize(e,t)).compileToFragments(t),r=0,t.header&&(r+=1),t.shiftLine&&(r+=1),s=0,h="",b=0,k=a.length;k>b;b++)o=a[b],t.sourceMap&&(o.locationData&&l.add([o.locationData.first_line,o.locationData.first_column],[r,s],{noReplace:!0}),m=u.count(o.code,"\n"),r+=m,s=o.code.length-(m?o.code.lastIndexOf("\n"):0)),h+=o.code;return t.header&&(c="Generated by CoffeeScript "+this.VERSION,h="// "+c+"\n"+h),t.sourceMap?(n={js:h},n.sourceMap=l,n.v3SourceMap=l.generate(t,e),n):h},e.tokens=function(e,t){return p.tokenize(e,t)},e.nodes=function(e,t){return"string"==typeof e?f.parse(p.tokenize(e,t)):f.parse(e)},e.run=function(e,t){var n,i;return null==t&&(t={}),i=require.main,null==t.sourceMap&&(t.sourceMap=!0),i.filename=process.argv[1]=t.filename?l.realpathSync(t.filename):".",i.moduleCache&&(i.moduleCache={}),i.paths=require("module")._nodeModulePaths(k.dirname(l.realpathSync(t.filename||"."))),!u.isCoffee(i.filename)||require.extensions?(n=r(e,t),m(),g[i.filename]=n.sourceMap,i._compile(n.js,i.filename)):i._compile(e,i.filename)},e.eval=function(e,t){var n,i,s,o,a,c,h,l,u,p,d,f,m,b;if(null==t&&(t={}),e=e.trim()){if(i=y.Script){if(null!=t.sandbox){if(t.sandbox instanceof i.createContext().constructor)h=t.sandbox;else{h=i.createContext(),f=t.sandbox;for(o in f)C.call(f,o)&&(l=f[o],h[o]=l)}h.global=h.root=h.GLOBAL=h}else h=global;if(h.__filename=t.filename||"eval",h.__dirname=k.dirname(h.__filename),h===global&&!h.module&&!h.require){for(n=require("module"),h.module=d=new n(t.modulename||"eval"),h.require=b=function(e){return n._load(e,d,!0)},d.filename=h.__filename,m=Object.getOwnPropertyNames(require),u=0,p=m.length;p>u;u++)c=m[u],"paths"!==c&&(b[c]=require[c]);b.paths=d.paths=n._nodeModulePaths(process.cwd()),b.resolve=function(e){return n._resolveFilename(e,d)}}}a={};for(o in t)C.call(t,o)&&(l=t[o],a[o]=l);return a.bare=!0,s=r(e,a),h===global?y.runInThisContext(s):y.runInContext(s,h)}},d=function(e,t){var n,i,s;return i=l.readFileSync(t,"utf8"),s=65279===i.charCodeAt(0)?i.substring(1):i,n=r(s,{filename:t,sourceMap:!0,literate:u.isLiterate(t)}),g[t]=n.sourceMap,e._compile(n.js,t)},require.extensions){for(T=[".coffee",".litcoffee",".coffee.md"],v=0,w=T.length;w>v;v++)o=T[v],require.extensions[o]=d;n=require("module"),a=function(e){var t,i;for(i=k.basename(e).split("."),""===i[0]&&i.shift();i.shift();)if(t="."+i.join("."),n._extensions[t])return t;return".js"},n.prototype.load=function(e){var t;return this.filename=e,this.paths=n._nodeModulePaths(k.dirname(e)),t=a(e),n._extensions[t](this,e),this.loaded=!0}}s&&(c=s.fork,s.fork=function(e,t,n){var i;return null==t&&(t=[]),null==n&&(n={}),i=u.isCoffee(e)?"coffee":null,Array.isArray(t)||(t=[],n=t||{}),n.execPath||(n.execPath=i),c(e,t,n)}),p=new t,f.lexer={lex:function(){var e,t;return t=this.tokens[this.pos++],t?(e=t[0],this.yytext=t[1],this.yylloc=t[2],this.yylineno=this.yylloc.first_line):e="",e},setInput:function(e){return this.tokens=e,this.pos=0},upcomingInput:function(){return""}},f.yy=require("./nodes"),f.yy.parseError=function(e,t){var n;return n=t.token,e="unexpected "+(1===n?"end of input":n),u.throwSyntaxError(e,f.lexer.yylloc)},b=!1,g={},m=function(){var t;if(!b)return b=!0,t=require.main,Error.prepareStackTrace=function(t,n){var i,s,r,o,a;return o={},r=function(e,t,n){var i,s;return s=g[e],s&&(i=s.sourceLocation([t-1,n-1])),i?[i[0]+1,i[1]+1]:null},s=function(){var t,s,o;for(o=[],t=0,s=n.length;s>t&&(i=n[t],i.getFunction()!==e.run);t++)o.push(" at "+h(i,r));return o}(),""+t.name+": "+(null!=(a=t.message)?a:"")+"\n"+s.join("\n")+"\n"}},h=function(e,t){var n,i,s,r,o,a,c,h,l,u,p,d;return r=void 0,s="",e.isNative()?s="native":(e.isEval()?(r=e.getScriptNameOrSourceURL(),r||(s=""+e.getEvalOrigin()+", ")):r=e.getFileName(),r||(r=""),h=e.getLineNumber(),i=e.getColumnNumber(),u=t(r,h,i),s=u?""+r+":"+u[0]+":"+u[1]+", :"+h+":"+i:""+r+":"+h+":"+i),o=e.getFunctionName(),a=e.isConstructor(),c=!(e.isToplevel()||a),c?(l=e.getMethodName(),d=e.getTypeName(),o?(p=n="",d&&o.indexOf(d)&&(p=""+d+"."),l&&o.indexOf("."+l)!==o.length-l.length-1&&(n=" [as "+l+"]"),""+p+o+n+" ("+s+")"):""+d+"."+(l||"")+" ("+s+")"):a?"new "+(o||"")+" ("+s+")":o?""+o+" ("+s+")":s}}.call(this),t.exports}(),require["./browser"]=function(){var exports={},module={exports:exports};return function(){var CoffeeScript,compile,runScripts,__indexOf=[].indexOf||function(e){for(var t=0,n=this.length;n>t;t++)if(t in this&&this[t]===e)return t;return-1};CoffeeScript=require("./coffee-script"),CoffeeScript.require=require,compile=CoffeeScript.compile,CoffeeScript.eval=function(code,options){return null==options&&(options={}),null==options.bare&&(options.bare=!0),eval(compile(code,options))},CoffeeScript.run=function(e,t){return null==t&&(t={}),t.bare=!0,t.shiftLine=!0,Function(compile(e,t))()},"undefined"!=typeof window&&null!==window&&("undefined"!=typeof btoa&&null!==btoa&&"undefined"!=typeof JSON&&null!==JSON&&"undefined"!=typeof unescape&&null!==unescape&&"undefined"!=typeof encodeURIComponent&&null!==encodeURIComponent&&(compile=function(e,t){var n,i,s;return null==t&&(t={}),t.sourceMap=!0,t.inline=!0,s=CoffeeScript.compile(e,t),n=s.js,i=s.v3SourceMap,""+n+"\n//@ sourceMappingURL=data:application/json;base64,"+btoa(unescape(encodeURIComponent(i)))+"\n//@ sourceURL=coffeescript"}),CoffeeScript.load=function(e,t,n){var i;return null==n&&(n={}),n.sourceFiles=[e],i=window.ActiveXObject?new window.ActiveXObject("Microsoft.XMLHTTP"):new window.XMLHttpRequest,i.open("GET",e,!0),"overrideMimeType"in i&&i.overrideMimeType("text/plain"),i.onreadystatechange=function(){var s;if(4===i.readyState){if(0!==(s=i.status)&&200!==s)throw new Error("Could not load "+e);if(CoffeeScript.run(i.responseText,n),t)return t()}},i.send(null)},runScripts=function(){var e,t,n,i,s,r,o;return o=window.document.getElementsByTagName("script"),t=["text/coffeescript","text/literate-coffeescript"],e=function(){var e,n,i,s;
+for(s=[],e=0,n=o.length;n>e;e++)r=o[e],i=r.type,__indexOf.call(t,i)>=0&&s.push(r);return s}(),i=0,s=e.length,(n=function(){var s,r,o;return o=e[i++],s=null!=o?o.type:void 0,__indexOf.call(t,s)>=0?(r={literate:"text/literate-coffeescript"===s},o.src?CoffeeScript.load(o.src,n,r):(r.sourceFiles=["embedded"],CoffeeScript.run(o.innerHTML,r),n())):void 0})(),null},window.addEventListener?window.addEventListener("DOMContentLoaded",runScripts,!1):window.attachEvent("onload",runScripts))}.call(this),module.exports}(),require["./coffee-script"]}();"function"==typeof define&&define.amd?define(function(){return CoffeeScript}):root.CoffeeScript=CoffeeScript}(this);
diff --git a/vendor/scripts/easeljs-NEXT.combined.js b/vendor/scripts/easeljs-NEXT.combined.js
new file mode 100644
index 000000000..a6b16da20
--- /dev/null
+++ b/vendor/scripts/easeljs-NEXT.combined.js
@@ -0,0 +1,12046 @@
+/*
+* Event
+* Visit http://createjs.com/ for documentation, updates and examples.
+*
+* Copyright (c) 2010 gskinner.com, inc.
+*
+* Permission is hereby granted, free of charge, to any person
+* obtaining a copy of this software and associated documentation
+* files (the "Software"), to deal in the Software without
+* restriction, including without limitation the rights to use,
+* copy, modify, merge, publish, distribute, sublicense, and/or sell
+* copies of the Software, and to permit persons to whom the
+* Software is furnished to do so, subject to the following
+* conditions:
+*
+* The above copyright notice and this permission notice shall be
+* included in all copies or substantial portions of the Software.
+*
+* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+* OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+/**
+ * A collection of Classes that are shared across all the CreateJS libraries. The classes are included in the minified
+ * files of each library and are available on the createsjs namespace directly.
+ *
+ * Example
+ * myObject.addEventListener("change", createjs.proxy(myMethod, scope));
+ *
+ * @module CreateJS
+ * @main CreateJS
+ */
+
+// namespace:
+this.createjs = this.createjs||{};
+
+(function() {
+ "use strict";
+
+/**
+ * Contains properties and methods shared by all events for use with
+ * {{#crossLink "EventDispatcher"}}{{/crossLink}}.
+ *
+ * Note that Event objects are often reused, so you should never
+ * rely on an event object's state outside of the call stack it was received in.
+ * @class Event
+ * @param {String} type The event type.
+ * @param {Boolean} bubbles Indicates whether the event will bubble through the display list.
+ * @param {Boolean} cancelable Indicates whether the default behaviour of this event can be cancelled.
+ * @constructor
+ **/
+var Event = function(type, bubbles, cancelable) {
+ this.initialize(type, bubbles, cancelable);
+};
+var p = Event.prototype;
+
+// events:
+
+// public properties:
+
+ /**
+ * The type of event.
+ * @property type
+ * @type String
+ **/
+ p.type = null;
+
+ /**
+ * The object that generated an event.
+ * @property target
+ * @type Object
+ * @default null
+ * @readonly
+ */
+ p.target = null;
+
+ /**
+ * The current target that a bubbling event is being dispatched from. For non-bubbling events, this will
+ * always be the same as target. For example, if childObj.parent = parentObj, and a bubbling event
+ * is generated from childObj, then a listener on parentObj would receive the event with
+ * target=childObj (the original target) and currentTarget=parentObj (where the listener was added).
+ * @property currentTarget
+ * @type Object
+ * @default null
+ * @readonly
+ */
+ p.currentTarget = null;
+
+ /**
+ * For bubbling events, this indicates the current event phase:
+ *