=0&&"xmlns"!==(n=t.slice(0,e))&&(t=t.slice(e+1)),Co.hasOwnProperty(n)?{space:Co[n],local:t}:t},Ao=function(t){var n=So(t);return(n.local?v:y)(n)},Eo=function(t){return function(){return this.matches(t)}};if("undefined"!=typeof document){var Uo=document.documentElement;if(!Uo.matches){var Lo=Uo.webkitMatchesSelector||Uo.msMatchesSelector||Uo.mozMatchesSelector||Uo.oMatchesSelector;Eo=function(t){return function(){return Lo.call(this,t)}}}}var Po=Eo,Do={},Ro=null;if("undefined"!=typeof document){"onmouseenter"in document.documentElement||(Do={mouseenter:"mouseover",mouseleave:"mouseout"})}var Fo=function(t,n,e){var i,r,o=m(t+""),u=o.length;{if(!(arguments.length<2)){for(a=n?b:w,null==e&&(e=!1),i=0;i=m&&(m=x+1);!(g=y[m])&&++m =0;)(i=r[o])&&(u&&u!==i.nextSibling&&u.parentNode.insertBefore(i,u),u=i);return this},Jo=function(t){function n(n,e){return n&&e?t(n.__data__,e.__data__):!n-!e}t||(t=S);for(var e=this._groups,i=e.length,r=new Array(i),o=0;o1?this.each((null==n?R:"function"==typeof n?Y:F)(t,n,null==e?"":e)):ru(i=this.node()).getComputedStyle(i,null).getPropertyValue(t)},uu=function(t,n){return arguments.length>1?this.each((null==n?q:"function"==typeof n?z:H)(t,n)):this.node()[t]};X.prototype={add:function(t){this._names.indexOf(t)<0&&(this._names.push(t),this._node.setAttribute("class",this._names.join(" ")))},remove:function(t){var n=this._names.indexOf(t);n>=0&&(this._names.splice(n,1),this._node.setAttribute("class",this._names.join(" ")))},contains:function(t){return this._names.indexOf(t)>=0}};var au=function(t,n){var e=O(t+"");if(arguments.length<2){for(var i=j(this.node()),r=-1,o=e.length;++r=240?t-240:t+120,r,i),Mt(t,r,i),Mt(t<120?t+240:t-120,r,i),this.opacity)},displayable:function(){return(0<=this.s&&this.s<=1||isNaN(this.s))&&0<=this.l&&this.l<=1&&0<=this.opacity&&this.opacity<=1}}));var Lu=Math.PI/180,Pu=180/Math.PI,Du=.95047,Ru=1,Fu=1.08883,Yu=4/29,qu=6/29,Hu=3*qu*qu,zu=qu*qu*qu;xu(Tt,kt,lt(ft,{brighter:function(t){return new Tt(this.l+18*(null==t?1:t),this.a,this.b,this.opacity)},darker:function(t){return new Tt(this.l-18*(null==t?1:t),this.a,this.b,this.opacity)},rgb:function(){var t=(this.l+16)/116,n=isNaN(this.a)?t:t+this.a/500,e=isNaN(this.b)?t:t-this.b/200;return t=Ru*St(t),n=Du*St(n),e=Fu*St(e),new gt(At(3.2404542*n-1.5371385*t-.4985314*e),At(-.969266*n+1.8760108*t+.041556*e),At(.0556434*n-.2040259*t+1.0572252*e),this.opacity)}})),xu(Pt,Lt,lt(ft,{brighter:function(t){return new Pt(this.h,this.c,this.l+18*(null==t?1:t),this.opacity)},darker:function(t){return new Pt(this.h,this.c,this.l-18*(null==t?1:t),this.opacity)},rgb:function(){return Nt(this).rgb()}}));var Ou=-.14861,ju=1.78277,Xu=-.29227,Iu=-.90649,$u=1.97294,Bu=$u*Iu,Vu=$u*ju,Zu=ju*Xu-Iu*Ou;xu(Ft,Rt,lt(ft,{brighter:function(t){return t=null==t?1/.7:Math.pow(1/.7,t),new Ft(this.h,this.s,this.l*t,this.opacity)},darker:function(t){return t=null==t?.7:Math.pow(.7,t),new Ft(this.h,this.s,this.l*t,this.opacity)},rgb:function(){var t=isNaN(this.h)?0:(this.h+120)*Lu,n=+this.l,e=isNaN(this.s)?0:this.s*n*(1-n),i=Math.cos(t),r=Math.sin(t);return new gt(255*(n+e*(Ou*i+ju*r)),255*(n+e*(Xu*i+Iu*r)),255*(n+e*($u*i)),this.opacity)}}));var Wu,Ju,Gu,Qu,Ku=function(t){return function(){return t}},ta=function t(n){function e(t,n){var e=i((t=vt(t)).r,(n=vt(n)).r),r=i(t.g,n.g),o=i(t.b,n.b),u=Ot(t.opacity,n.opacity);return function(n){return t.r=e(n),t.g=r(n),t.b=o(n),t.opacity=u(n),t+""}}var i=zt(n);return e.gamma=t,e}(1),na=function(t,n){var e,i=n?n.length:0,r=t?Math.min(i,t.length):0,o=new Array(i),u=new Array(i);for(e=0;eo&&(r=n.slice(o,r),a[u]?a[u]+=r:a[++u]=r),(e=e[0])===(i=i[0])?a[u]?a[u]+=i:a[++u]=i:(a[++u]=null,s.push({i:u,x:ia(e,i)})),o=ua.lastIndex;return oLa&&e.state1e-6)if(Math.abs(c*a-s*h)>1e-6&&r){var f=e-o,_=i-u,p=a*a+s*s,d=f*f+_*_,y=Math.sqrt(p),v=Math.sqrt(l),g=r*Math.tan((cs-Math.acos((p+l-d)/(2*y*v)))/2),x=g/v,m=g/y;Math.abs(x-1)>1e-6&&(this._+="L"+(t+x*h)+","+(n+x*c)),this._+="A"+r+","+r+",0,0,"+ +(c*f>h*_)+","+(this._x1=t+m*a)+","+(this._y1=n+m*s)}else this._+="L"+(this._x1=t)+","+(this._y1=n);else;},arc:function(t,n,e,i,r,o){t=+t,n=+n,e=+e;var u=e*Math.cos(i),a=e*Math.sin(i),s=t+u,h=n+a,c=1^o,l=o?i-r:r-i;if(e<0)throw new Error("negative radius: "+e);null===this._x1?this._+="M"+s+","+h:(Math.abs(this._x1-s)>1e-6||Math.abs(this._y1-h)>1e-6)&&(this._+="L"+s+","+h),e&&(l>fs?this._+="A"+e+","+e+",0,1,"+c+","+(t-u)+","+(n-a)+"A"+e+","+e+",0,1,"+c+","+(this._x1=s)+","+(this._y1=h):(l<0&&(l=l%ls+ls),this._+="A"+e+","+e+",0,"+ +(l>=cs)+","+c+","+(this._x1=t+e*Math.cos(r))+","+(this._y1=n+e*Math.sin(r))))},rect:function(t,n,e,i){this._+="M"+(this._x0=this._x1=+t)+","+(this._y0=this._y1=+n)+"h"+ +e+"v"+ +i+"h"+-e+"Z"},toString:function(){return this._}};jn.prototype=Xn.prototype={constructor:jn,has:function(t){return"$"+t in this},get:function(t){return this["$"+t]},set:function(t,n){return this["$"+t]=n,this},remove:function(t){var n="$"+t;return n in this&&delete this[n]},clear:function(){for(var t in this)"$"===t[0]&&delete this[t]},keys:function(){var t=[];for(var n in this)"$"===n[0]&&t.push(n.slice(1));return t},values:function(){var t=[];for(var n in this)"$"===n[0]&&t.push(this[n]);return t},entries:function(){var t=[];for(var n in this)"$"===n[0]&&t.push({key:n.slice(1),value:this[n]});return t},size:function(){var t=0;for(var n in this)"$"===n[0]&&++t;return t},empty:function(){for(var t in this)if("$"===t[0])return!1;return!0},each:function(t){for(var n in this)"$"===n[0]&&t(this[n],n.slice(1),this)}};var _s=Xn.prototype;In.prototype=$n.prototype={constructor:In,has:_s.has,add:function(t){return t+="",this["$"+t]=t,this},remove:_s.remove,clear:_s.clear,values:_s.keys,size:_s.size,empty:_s.empty,each:_s.each};var ps=function(t){function n(t,n){var i,r,o=e(t,function(t,e){if(i)return i(t,e-1);r=t,i=n?Vn(t,n):Bn(t)});return o.columns=r,o}function e(t,n){function e(){if(c>=h)return u;if(r)return r=!1,o;var n,e=c;if(34===t.charCodeAt(e)){for(var i=e;i++t||t>r||i>n||n>o))return this;var u,a,s=r-e,h=this._root;switch(a=(n<(i+o)/2)<<1|t<(e+r)/2){case 0:do{u=new Array(4),u[a]=h,h=u}while(s*=2,r=e+s,o=i+s,t>r||n>o);break;case 1:do{u=new Array(4),u[a]=h,h=u}while(s*=2,e=r-s,o=i+s,e>t||n>o);break;case 2:do{u=new Array(4),u[a]=h,h=u}while(s*=2,r=e+s,i=o-s,t>r||i>n);break;case 3:do{u=new Array(4),u[a]=h,h=u}while(s*=2,e=r-s,i=o-s,e>t||i>n)}this._root&&this._root.length&&(this._root=h)}return this._x0=e,this._y0=i,this._x1=r,this._y1=o,this},ws=function(){var t=[];return this.visit(function(n){if(!n.length)do{t.push(n.data)}while(n=n.next)}),t},bs=function(t){return arguments.length?this.cover(+t[0][0],+t[0][1]).cover(+t[1][0],+t[1][1]):isNaN(this._x0)?void 0:[[this._x0,this._y0],[this._x1,this._y1]]},Ms=function(t,n,e,i,r){this.node=t,this.x0=n,this.y0=e,this.x1=i,this.y1=r},Ns=function(t,n,e){var i,r,o,u,a,s,h,c=this._x0,l=this._y0,f=this._x1,_=this._y1,p=[],d=this._root;for(d&&p.push(new Ms(d,c,l,f,_)),null==e?e=1/0:(c=t-e,l=n-e,f=t+e,_=n+e,e*=e);s=p.pop();)if(!(!(d=s.node)||(r=s.x0)>f||(o=s.y0)>_||(u=s.x1)=v)<<1|t>=y)&&(s=p[p.length-1],p[p.length-1]=p[p.length-1-h],p[p.length-1-h]=s)}else{var g=t-+this._x.call(null,d.data),x=n-+this._y.call(null,d.data),m=g*g+x*x;if(m=(a=(p+y)/2))?p=a:y=a,(c=u>=(s=(d+v)/2))?d=s:v=s,n=_,!(_=_[l=c<<1|h]))return this;if(!_.length)break;(n[l+1&3]||n[l+2&3]||n[l+3&3])&&(e=n,f=l)}for(;_.data!==t;)if(i=_,!(_=_.next))return this;return(r=_.next)&&delete _.next,i?(r?i.next=r:delete i.next,this):n?(r?n[l]=r:delete n[l],(_=n[0]||n[1]||n[2]||n[3])&&_===(n[3]||n[2]||n[1]||n[0])&&!_.length&&(e?e[f]=_:this._root=_),this):(this._root=r,this)},Ts=function(){return this._root},Cs=function(){var t=0;return this.visit(function(n){if(!n.length)do{++t}while(n=n.next)}),t},Ss=function(t){var n,e,i,r,o,u,a=[],s=this._root;for(s&&a.push(new Ms(s,this._x0,this._y0,this._x1,this._y1));n=a.pop();)if(!t(s=n.node,i=n.x0,r=n.y0,o=n.x1,u=n.y1)&&s.length){var h=(i+o)/2,c=(r+u)/2;(e=s[3])&&a.push(new Ms(e,h,c,o,u)),(e=s[2])&&a.push(new Ms(e,i,c,h,u)),(e=s[1])&&a.push(new Ms(e,h,r,o,c)),(e=s[0])&&a.push(new Ms(e,i,r,h,c))}return this},As=function(t){var n,e=[],i=[];for(this._root&&e.push(new Ms(this._root,this._x0,this._y0,this._x1,this._y1));n=e.pop();){var r=n.node;if(r.length){var o,u=n.x0,a=n.y0,s=n.x1,h=n.y1,c=(u+s)/2,l=(a+h)/2;(o=r[0])&&e.push(new Ms(o,u,a,c,l)),(o=r[1])&&e.push(new Ms(o,c,a,s,l)),(o=r[2])&&e.push(new Ms(o,u,l,c,h)),(o=r[3])&&e.push(new Ms(o,c,l,s,h))}i.push(n)}for(;n=i.pop();)t(n.node,n.x0,n.y0,n.x1,n.y1);return this},Es=function(t){return arguments.length?(this._x=t,this):this._x},Us=function(t){return arguments.length?(this._y=t,this):this._y},Ls=te.prototype=ne.prototype;Ls.copy=function(){var t,n,e=new ne(this._x,this._y,this._x0,this._y0,this._x1,this._y1),i=this._root;if(!i)return e;if(!i.length)return e._root=ee(i),e;for(t=[{source:i,target:e._root=new Array(4)}];i=t.pop();)for(var r=0;r<4;++r)(n=i.source[r])&&(n.length?t.push({source:n,target:i.target[r]=new Array(4)}):i.target[r]=ee(n));return e},Ls.add=xs,Ls.addAll=Jn,Ls.cover=ms,Ls.data=ws,Ls.extent=bs,Ls.find=Ns,Ls.remove=ks,Ls.removeAll=Gn,Ls.root=Ts,Ls.size=Cs,Ls.visit=Ss,Ls.visitAfter=As,Ls.x=Es,Ls.y=Us;var Ps,Ds=(Math.PI,Math.sqrt(5),function(t,n){if((e=(t=n?t.toExponential(n-1):t.toExponential()).indexOf("e"))<0)return null;var e,i=t.slice(0,e);return[i.length>1?i[0]+i.slice(2):i,+t.slice(e+1)]}),Rs=function(t){return t=Ds(Math.abs(t)),t?t[1]:NaN},Fs=function(t,n){return function(e,i){for(var r=e.length,o=[],u=0,a=t[0],s=0;r>0&&a>0&&(s+a+1>i&&(a=Math.max(1,i-s)),o.push(e.substring(r-=a,r+a)),!((s+=a+1)>i));)a=t[u=(u+1)%t.length];return o.reverse().join(n)}},Ys=function(t,n){t=t.toPrecision(n);t:for(var e,i=t.length,r=1,o=-1;r0&&(o=0)}return o>0?t.slice(0,o)+t.slice(e+1):t},qs=function(t,n){var e=Ds(t,n);if(!e)return t+"";var i=e[0],r=e[1],o=r-(Ps=3*Math.max(-8,Math.min(8,Math.floor(r/3))))+1,u=i.length;return o===u?i:o>u?i+new Array(o-u+1).join("0"):o>0?i.slice(0,o)+"."+i.slice(o):"0."+new Array(1-o).join("0")+Ds(t,Math.max(0,n+o-1))[0]},Hs=function(t,n){var e=Ds(t,n);if(!e)return t+"";var i=e[0],r=e[1];return r<0?"0."+new Array(-r).join("0")+i:i.length>r+1?i.slice(0,r+1)+"."+i.slice(r+1):i+new Array(r-i.length+2).join("0")},zs={"":Ys,"%":function(t,n){return(100*t).toFixed(n)},b:function(t){return Math.round(t).toString(2)},c:function(t){return t+""},d:function(t){return Math.round(t).toString(10)},e:function(t,n){return t.toExponential(n)},f:function(t,n){return t.toFixed(n)},g:function(t,n){return t.toPrecision(n)},o:function(t){return Math.round(t).toString(8)},p:function(t,n){return Hs(100*t,n)},r:Hs,s:qs,X:function(t){return Math.round(t).toString(16).toUpperCase()},x:function(t){return Math.round(t).toString(16)}},Os=/^(?:(.)?([<>=^]))?([+\-\( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?([a-z%])?$/i,js=function(t){return new ie(t)};ie.prototype.toString=function(){return this.fill+this.align+this.sign+this.symbol+(this.zero?"0":"")+(null==this.width?"":Math.max(1,0|this.width))+(this.comma?",":"")+(null==this.precision?"":"."+Math.max(0,0|this.precision))+this.type};var Xs,Is,$s,Bs=["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"],Vs=function(t){function n(t){function n(t){var n,r,s,g=p,x=d;if("c"===_)x=y(t)+x,t="";else{t=+t;var m=(t<0||1/t<0)&&(t*=-1,!0);if(t=y(t,f),m)for(n=-1,r=t.length,m=!1;++n(s=t.charCodeAt(n))||s>57){x=(46===s?o+t.slice(n+1):t.slice(n))+x,t=t.slice(0,n);break}}l&&!h&&(t=i(t,1/0));var w=g.length+t.length+x.length,b=w>1)+g+t+x+b.slice(w)}return b+g+t+x}t=js(t);var e=t.fill,u=t.align,a=t.sign,s=t.symbol,h=t.zero,c=t.width,l=t.comma,f=t.precision,_=t.type,p="$"===s?r[0]:"#"===s&&/[boxX]/.test(_)?"0"+_.toLowerCase():"",d="$"===s?r[1]:/[%p]/.test(_)?"%":"",y=zs[_],v=!_||/[defgprs%]/.test(_);return f=null==f?_?6:12:/[gprs]/.test(_)?Math.max(1,Math.min(21,f)):Math.max(0,Math.min(20,f)),n.toString=function(){return t+""},n}function e(t,e){var i=n((t=js(t),t.type="f",t)),r=3*Math.max(-8,Math.min(8,Math.floor(Rs(e)/3))),o=Math.pow(10,-r),u=Bs[8+r/3];return function(t){return i(o*t)+u}}var i=t.grouping&&t.thousands?Fs(t.grouping,t.thousands):re,r=t.currency,o=t.decimal;return{format:n,formatPrefix:e}};!function(t){Xs=Vs(t),Is=Xs.format,$s=Xs.formatPrefix,Xs}({decimal:".",thousands:",",grouping:[3],currency:["$",""]});var Zs=function(t){return Math.max(0,-Rs(Math.abs(t)))},Ws=function(t,n){return Math.max(0,3*Math.max(-8,Math.min(8,Math.floor(Rs(n)/3)))-Rs(Math.abs(t)))},Js=function(t,n){return t=Math.abs(t),n=Math.abs(n)-t,Math.max(0,Rs(n)-Rs(t))+1},Gs=function(){return new oe};oe.prototype={constructor:oe,reset:function(){this.s=this.t=0},add:function(t){ue(Qs,t,this.t),ue(this,Qs.s,this.s),this.s?this.t+=Qs.t:this.s=Qs.t},valueOf:function(){return this.s}};var Qs=new oe,Ks=1e-6,th=Math.PI,nh=th/2,eh=th/4,ih=2*th,rh=th/180,oh=Math.abs,uh=Math.atan,ah=Math.atan2,sh=Math.cos,hh=(Math.ceil,Math.exp),ch=Math.log,lh=(Math.pow,Math.sin),fh=(Math.sign,Math.sqrt),_h=Math.tan;Gs(),Gs(),Gs();_e.invert=_e;var ph=function(){var t,n=[];return{point:function(n,e){t.push([n,e])},lineStart:function(){n.push(t=[])},lineEnd:he,rejoin:function(){n.length>1&&n.push(n.pop().concat(n.shift()))},result:function(){var e=n;return n=[],t=null,e}}},dh=function(t,n){return oh(t[0]-n[0])=0;--o)r.point((c=h[o])[0],c[1]);else i(f.x,f.p.x,-1,r);f=f.p}f=f.o,h=f.z,_=!_}while(!f.v);r.lineEnd()}}},vh=(Gs(),Gs(),Gs(),1/0),gh=-vh;ye.prototype={_radius:4.5,pointRadius:function(t){return this._radius=t,this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._context.closePath(),this._point=NaN},point:function(t,n){switch(this._point){case 0:this._context.moveTo(t,n),this._point=1;break;case 1:this._context.lineTo(t,n);break;default:this._context.moveTo(t+this._radius,n),this._context.arc(t,n,this._radius,0,ih)}},result:he};Gs();ve.prototype={_circle:ge(4.5),pointRadius:function(t){return this._circle=ge(t),this},polygonStart:function(){this._line=0},polygonEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){0===this._line&&this._string.push("Z"),this._point=NaN},point:function(t,n){switch(this._point){case 0:this._string.push("M",t,",",n),this._point=1;break;case 1:this._string.push("L",t,",",n);break;default:this._string.push("M",t,",",n,this._circle)}},result:function(){if(this._string.length){var t=this._string.join("");return this._string=[],t}}};var xh=Gs(),mh=function(t,n){var e=n[0],i=n[1],r=[lh(e),-sh(e),0],o=0,u=0;xh.reset();for(var a=0,s=t.length;a=0?1:-1,N=M*b,k=N>th,T=p*m;if(xh.add(ah(T*M*lh(N),d*w+T*sh(N))),o+=k?b+M*ih:b,k^f>=e^g>=e){var C=le(ce(l),ce(v));fe(C);var S=le(r,C);fe(S);var A=(k^b>=0?-1:1)*se(S[2]);(i>A||i===A&&(C[0]||C[1]))&&(u+=k^b>=0?1:-1)}}return(o<-Ks||o0){for(m||(o.polygonStart(),m=!0),o.lineStart(),t=0;t1&&2&r&&u.push(u.pop().concat(u.shift())),p.push(u.filter(xe))}var _,p,d,y=n(o),v=r.invert(i[0],i[1]),g=ph(),x=n(g),m=!1,w={point:u,lineStart:s,lineEnd:h,polygonStart:function(){w.point=c,w.lineStart=l,w.lineEnd=f,p=[],_=[]},polygonEnd:function(){w.point=u,w.lineStart=s,w.lineEnd=h,p=vo(p);var t=mh(_,v);p.length?(m||(o.polygonStart(),m=!0),yh(p,me,t,e,o)):t&&(m||(o.polygonStart(),m=!0),o.lineStart(),e(null,null,1,o),o.lineEnd()),m&&(o.polygonEnd(),m=!1),p=_=null},sphere:function(){o.polygonStart(),o.lineStart(),e(null,null,1,o),o.lineEnd(),o.polygonEnd()}};return w}};wh(function(){return!0},we,Me,[-th,-nh]);ke.prototype={constructor:ke,point:function(t,n){this.stream.point(t,n)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}};sh(30*rh),Ne({point:function(t,n){this.stream.point(t*rh,n*rh)}});Te(function(t){return fh(2/(1+t))}).invert=Ce(function(t){return 2*se(t/2)}),Te(function(t){return(t=ae(t))&&t/lh(t)}).invert=Ce(function(t){return t}),Se.invert=function(t,n){return[-n,2*uh(hh(t))-nh]};var bh=function(){return this.eachAfter(Ae)},Mh=function(t){var n,e,i,r,o=this,u=[o];do{for(n=u.reverse(),u=[];o=n.pop();)if(t(o),e=o.children)for(i=0,r=e.length;i=0;--e)r.push(n[e]);return this},kh=function(t){for(var n,e,i,r=this,o=[r],u=[];r=o.pop();)if(u.push(r),n=r.children)for(e=0,i=n.length;e=0;)e+=i[r].value;n.value=e})},Ch=function(t){return this.eachBefore(function(n){n.children&&n.children.sort(t)})},Sh=function(t){for(var n=this,e=Ee(n,t),i=[n];n!==e;)n=n.parent,i.push(n);for(var r=i.length;t!==e;)i.splice(r,0,t),t=t.parent;return i},Ah=function(){for(var t=this,n=[t];t=t.parent;)n.push(t);return n},Eh=function(){var t=[];return this.each(function(n){t.push(n)}),t},Uh=function(){var t=[];return this.eachBefore(function(n){n.children||t.push(n)}),t},Lh=function(){var t=this,n=[];return t.each(function(e){e!==t&&n.push({source:e.parent,target:e})}),n};Fe.prototype=Ue.prototype={constructor:Fe,count:bh,each:Mh,eachAfter:kh,eachBefore:Nh,sum:Th,sort:Ch,path:Sh,ancestors:Ah,descendants:Eh,leaves:Uh,links:Lh,copy:Le};var Ph=function(t,n,e,i,r){for(var o,u=t.children,a=-1,s=u.length,h=t.value&&(i-n)/t.value;++a1?n:1)},e}(Rh);!function t(n){function e(t,e,i,r,o){if((u=t._squarify)&&u.ratio===n)for(var u,a,s,h,c,l=-1,f=u.length,_=t.value;++l1?n:1)},e}(Rh);var Fh=([].slice,{}),Yh=function(t,n){function e(t){var n,e=h.status;if(!e&&$e(h)||e>=200&&e<300||304===e){if(o)try{n=o.call(i,h)}catch(t){return void a.call("error",i,t)}else n=h;a.call("load",i,n)}else a.call("error",i,t)}var i,r,o,u,a=l("beforesend","progress","load","error"),s=Xn(),h=new XMLHttpRequest,c=null,f=null,_=0;if("undefined"==typeof XDomainRequest||"withCredentials"in h||!/^(http(s)?:)?\/\//.test(t)||(h=new XDomainRequest),"onload"in h?h.onload=h.onerror=h.ontimeout=e:h.onreadystatechange=function(t){h.readyState>3&&e(t)},h.onprogress=function(t){a.call("progress",i,t)},i={header:function(t,n){return t=(t+"").toLowerCase(),arguments.length<2?s.get(t):(null==n?s.remove(t):s.set(t,n+""),i)},mimeType:function(t){return arguments.length?(r=null==t?null:t+"",i):r},responseType:function(t){return arguments.length?(u=t,i):u},timeout:function(t){return arguments.length?(_=+t,i):_},user:function(t){return arguments.length<1?c:(c=null==t?null:t+"",i)},password:function(t){return arguments.length<1?f:(f=null==t?null:t+"",i)},response:function(t){return o=t,i},get:function(t,n){return i.send("GET",t,n)},post:function(t,n){return i.send("POST",t,n)},send:function(n,e,o){return h.open(n,t,!0,c,f),null==r||s.has("accept")||s.set("accept",r+",*/*"),h.setRequestHeader&&s.each(function(t,n){h.setRequestHeader(n,t)}),null!=r&&h.overrideMimeType&&h.overrideMimeType(r),null!=u&&(h.responseType=u),_>0&&(h.timeout=_),null==o&&"function"==typeof e&&(o=e,e=null),null!=o&&1===o.length&&(o=Ie(o)),null!=o&&i.on("error",o).on("load",function(t){o(null,t)}),a.call("beforesend",i,h),h.send(null==e?null:e),i},abort:function(){return h.abort(),i},on:function(){var t=a.on.apply(a,arguments);return t===a?i:t}},null!=n){if("function"!=typeof n)throw new Error("invalid callback: "+n);return i.get(n)}return i},qh=function(t,n){return function(e,i){var r=Yh(e).mimeType(t).response(n);if(null!=i){if("function"!=typeof i)throw new Error("invalid callback: "+i);return r.get(i)}return r}};qh("text/html",function(t){return document.createRange().createContextualFragment(t.responseText)}),qh("application/json",function(t){return JSON.parse(t.responseText)}),qh("text/plain",function(t){return t.responseText}),qh("application/xml",function(t){var n=t.responseXML;if(!n)throw new Error("parse error");return n});var Hh=function(t,n){return function(e,i,r){arguments.length<3&&(r=i,i=null);var o=Yh(e).mimeType(t);return o.row=function(t){return arguments.length?o.response(Be(n,i=t)):i},o.row(i),r?o.get(r):o}};Hh("text/csv",ys),Hh("text/tab-separated-values",gs);var zh=Array.prototype,Oh=zh.map,jh=zh.slice,Xh={name:"implicit"},Ih=function(t){return function(){return t}},$h=function(t){return+t},Bh=[0,1],Vh=function(t,n,i){var r,o=t[0],u=t[t.length-1],a=e(o,u,null==n?10:n);switch(i=js(null==i?",f":i),i.type){case"s":var s=Math.max(Math.abs(o),Math.abs(u));return null!=i.precision||isNaN(r=Ws(a,s))||(i.precision=r),$s(i,s);case"":case"e":case"g":case"p":case"r":null!=i.precision||isNaN(r=Js(a,Math.max(Math.abs(o),Math.abs(u))))||(i.precision=r-("e"===i.type));break;case"f":case"%":null!=i.precision||isNaN(r=Zs(a))||(i.precision=r-2*("%"===i.type))}return Is(i)},Zh=new Date,Wh=new Date,Jh=ri(function(){},function(t,n){t.setTime(+t+n)},function(t,n){return n-t});Jh.every=function(t){return t=Math.floor(t),isFinite(t)&&t>0?t>1?ri(function(n){n.setTime(Math.floor(n/t)*t)},function(n,e){n.setTime(+n+e*t)},function(n,e){return(e-n)/t}):Jh:null};var Gh=6e4,Qh=6048e5,Kh=(ri(function(t){t.setTime(1e3*Math.floor(t/1e3))},function(t,n){t.setTime(+t+1e3*n)},function(t,n){return(n-t)/1e3},function(t){return t.getUTCSeconds()}),ri(function(t){t.setTime(Math.floor(t/Gh)*Gh)},function(t,n){t.setTime(+t+n*Gh)},function(t,n){return(n-t)/Gh},function(t){return t.getMinutes()}),ri(function(t){var n=t.getTimezoneOffset()*Gh%36e5;n<0&&(n+=36e5),t.setTime(36e5*Math.floor((+t-n)/36e5)+n)},function(t,n){t.setTime(+t+36e5*n)},function(t,n){return(n-t)/36e5},function(t){return t.getHours()}),ri(function(t){t.setHours(0,0,0,0)},function(t,n){t.setDate(t.getDate()+n)},function(t,n){return(n-t-(n.getTimezoneOffset()-t.getTimezoneOffset())*Gh)/864e5},function(t){return t.getDate()-1})),tc=oi(0),nc=oi(1),ec=(oi(2),oi(3),oi(4),oi(5),oi(6),ri(function(t){t.setDate(1),t.setHours(0,0,0,0)},function(t,n){t.setMonth(t.getMonth()+n)},function(t,n){return n.getMonth()-t.getMonth()+12*(n.getFullYear()-t.getFullYear())},function(t){return t.getMonth()}),ri(function(t){t.setMonth(0,1),t.setHours(0,0,0,0)},function(t,n){t.setFullYear(t.getFullYear()+n)},function(t,n){return n.getFullYear()-t.getFullYear()},function(t){return t.getFullYear()}));ec.every=function(t){return isFinite(t=Math.floor(t))&&t>0?ri(function(n){n.setFullYear(Math.floor(n.getFullYear()/t)*t),n.setMonth(0,1),n.setHours(0,0,0,0)},function(n,e){n.setFullYear(n.getFullYear()+e*t)}):null};var ic=(ri(function(t){t.setUTCSeconds(0,0)},function(t,n){t.setTime(+t+n*Gh)},function(t,n){return(n-t)/Gh},function(t){return t.getUTCMinutes()}),ri(function(t){t.setUTCMinutes(0,0,0)},function(t,n){t.setTime(+t+36e5*n)},function(t,n){return(n-t)/36e5},function(t){return t.getUTCHours()}),ri(function(t){t.setUTCHours(0,0,0,0)},function(t,n){t.setUTCDate(t.getUTCDate()+n)},function(t,n){return(n-t)/864e5},function(t){return t.getUTCDate()-1})),rc=ui(0),oc=ui(1),uc=(ui(2),ui(3),ui(4),ui(5),ui(6),ri(function(t){t.setUTCDate(1),t.setUTCHours(0,0,0,0)},function(t,n){t.setUTCMonth(t.getUTCMonth()+n)},function(t,n){return n.getUTCMonth()-t.getUTCMonth()+12*(n.getUTCFullYear()-t.getUTCFullYear())},function(t){return t.getUTCMonth()}),ri(function(t){t.setUTCMonth(0,1),t.setUTCHours(0,0,0,0)},function(t,n){t.setUTCFullYear(t.getUTCFullYear()+n)},function(t,n){return n.getUTCFullYear()-t.getUTCFullYear()},function(t){return t.getUTCFullYear()}));uc.every=function(t){return isFinite(t=Math.floor(t))&&t>0?ri(function(n){n.setUTCFullYear(Math.floor(n.getUTCFullYear()/t)*t),n.setUTCMonth(0,1),n.setUTCHours(0,0,0,0)},function(n,e){n.setUTCFullYear(n.getUTCFullYear()+e*t)}):null};var ac,sc,hc,cc,lc,fc={"-":"",_:" ",0:"0"},_c=/^\s*\d+/,pc=/^%/,dc=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;!function(t){ac=ci(t),sc=ac.format,hc=ac.parse,cc=ac.utcFormat,lc=ac.utcParse,ac}({dateTime:"%x, %X",date:"%-m/%-d/%Y",time:"%-I:%M:%S %p",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});var yc=(Date.prototype.toISOString||cc("%Y-%m-%dT%H:%M:%S.%LZ"),+new Date("2000-01-01T00:00:00.000Z")||lc("%Y-%m-%dT%H:%M:%S.%LZ"),function(t){return t.match(/.{6}/g).map(function(t){return"#"+t})});yc("1f77b4ff7f0e2ca02cd627289467bd8c564be377c27f7f7fbcbd2217becf"),yc("393b795254a36b6ecf9c9ede6379398ca252b5cf6bcedb9c8c6d31bd9e39e7ba52e7cb94843c39ad494ad6616be7969c7b4173a55194ce6dbdde9ed6"),yc("3182bd6baed69ecae1c6dbefe6550dfd8d3cfdae6bfdd0a231a35474c476a1d99bc7e9c0756bb19e9ac8bcbddcdadaeb636363969696bdbdbdd9d9d9"),yc("1f77b4aec7e8ff7f0effbb782ca02c98df8ad62728ff98969467bdc5b0d58c564bc49c94e377c2f7b6d27f7f7fc7c7c7bcbd22dbdb8d17becf9edae5"),va(Rt(300,.5,0),Rt(-240,.5,1));var vc=(va(Rt(-100,.75,.35),Rt(80,1.5,.8)),va(Rt(260,.75,.35),Rt(80,1.5,.8)),Rt(),function(t){return function(){return t}}),gc=1e-12;Math.PI;ur.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._point=0},lineEnd:function(){(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;default:this._context.lineTo(t,n)}}};var xc=function(t){return new ur(t)},mc=function(){function t(t){var a,s,h,c=t.length,l=!1;for(null==r&&(u=o(h=On())),a=0;a<=c;++a)!(a0)for(var i,r=t[0],o=n[0],u=t[e]-r,a=n[e]-o,s=-1;++s<=e;)i=s/e,this._basis.point(this._beta*t[s]+(1-this._beta)*(r+i*u),this._beta*n[s]+(1-this._beta)*(o+i*a));this._x=this._y=null,this._basis.lineEnd()},point:function(t,n){this._x.push(+t),this._y.push(+n)}},function t(n){function e(t){return 1===n?new fr(t):new _r(t,n)}return e.beta=function(n){return t(+n)},e}(.85),dr.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:pr(this,this._x1,this._y1)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2,this._x1=t,this._y1=n;break;case 2:this._point=3;default:pr(this,t,n)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}},function t(n){function e(t){return new dr(t,n)}return e.tension=function(n){return t(+n)},e}(0),yr.prototype={areaStart:wc,areaEnd:wc,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1,this._x3=t,this._y3=n;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=n);break;case 2:this._point=3,this._x5=t,this._y5=n;break;default:pr(this,t,n)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}},function t(n){function e(t){return new yr(t,n)}return e.tension=function(n){return t(+n)},e}(0),vr.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){switch(t=+t,n=+n,this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:pr(this,t,n)}this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}},function t(n){function e(t){return new vr(t,n)}return e.tension=function(n){return t(+n)},e}(0),xr.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x2,this._y2);break;case 3:this.point(this._x2,this._y2)}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){if(t=+t,n=+n,this._point){var e=this._x2-t,i=this._y2-n;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(e*e+i*i,this._alpha))}switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;break;case 2:this._point=3;default:gr(this,t,n)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}},function t(n){function e(t){return n?new xr(t,n):new dr(t,0)}return e.alpha=function(n){return t(+n)},e}(.5),mr.prototype={areaStart:wc,areaEnd:wc,lineStart:function(){this._x0=this._x1=this._x2=this._x3=this._x4=this._x5=this._y0=this._y1=this._y2=this._y3=this._y4=this._y5=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){switch(this._point){case 1:this._context.moveTo(this._x3,this._y3),this._context.closePath();break;case 2:this._context.lineTo(this._x3,this._y3),this._context.closePath();break;case 3:this.point(this._x3,this._y3),this.point(this._x4,this._y4),this.point(this._x5,this._y5)}},point:function(t,n){if(t=+t,n=+n,this._point){var e=this._x2-t,i=this._y2-n;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(e*e+i*i,this._alpha))}switch(this._point){case 0:this._point=1,this._x3=t,this._y3=n;break;case 1:this._point=2,this._context.moveTo(this._x4=t,this._y4=n);break;case 2:this._point=3,this._x5=t,this._y5=n;break;default:gr(this,t,n)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}},function t(n){function e(t){return n?new mr(t,n):new yr(t,0)}return e.alpha=function(n){return t(+n)},e}(.5),wr.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._x2=this._y0=this._y1=this._y2=NaN,this._l01_a=this._l12_a=this._l23_a=this._l01_2a=this._l12_2a=this._l23_2a=this._point=0},lineEnd:function(){(this._line||0!==this._line&&3===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){if(t=+t,n=+n,this._point){var e=this._x2-t,i=this._y2-n;this._l23_a=Math.sqrt(this._l23_2a=Math.pow(e*e+i*i,this._alpha))}switch(this._point){case 0:this._point=1;break;case 1:this._point=2;break;case 2:this._point=3,this._line?this._context.lineTo(this._x2,this._y2):this._context.moveTo(this._x2,this._y2);break;case 3:this._point=4;default:gr(this,t,n)}this._l01_a=this._l12_a,this._l12_a=this._l23_a,this._l01_2a=this._l12_2a,this._l12_2a=this._l23_2a,this._x0=this._x1,this._x1=this._x2,this._x2=t,this._y0=this._y1,this._y1=this._y2,this._y2=n}},function t(n){function e(t){return n?new wr(t,n):new vr(t,0)}return e.alpha=function(n){return t(+n)},e}(.5),Tr.prototype={areaStart:function(){this._line=0},areaEnd:function(){this._line=NaN},lineStart:function(){this._x0=this._x1=this._y0=this._y1=this._t0=NaN,this._point=0},lineEnd:function(){switch(this._point){case 2:this._context.lineTo(this._x1,this._y1);break;case 3:kr(this,this._t0,Nr(this,this._t0))}(this._line||0!==this._line&&1===this._point)&&this._context.closePath(),this._line=1-this._line},point:function(t,n){var e=NaN;if(t=+t,n=+n,t!==this._x1||n!==this._y1){switch(this._point){case 0:this._point=1,this._line?this._context.lineTo(t,n):this._context.moveTo(t,n);break;case 1:this._point=2;break;case 2:this._point=3,kr(this,Nr(this,e=Mr(this,t,n)),e);break;default:kr(this,this._t0,e=Mr(this,t,n))}this._x0=this._x1,this._x1=t,this._y0=this._y1,this._y1=n,this._t0=e}}},(Cr.prototype=Object.create(Tr.prototype)).point=function(t,n){Tr.prototype.point.call(this,n,t)},Sr.prototype={moveTo:function(t,n){this._context.moveTo(n,t)},closePath:function(){this._context.closePath()},lineTo:function(t,n){this._context.lineTo(n,t)},bezierCurveTo:function(t,n,e,i,r,o){this._context.bezierCurveTo(n,t,i,e,o,r)}};Array.prototype.slice;Ar.prototype={constructor:Ar,insert:function(t,n){var e,i,r;if(t){if(n.P=t,n.N=t.N,t.N&&(t.N.P=n),t.N=n,t.R){for(t=t.R;t.L;)t=t.L;t.L=n}else t.R=n;e=t}else this._?(t=Pr(this._),n.P=null,n.N=t,t.P=t.L=n,e=t):(n.P=n.N=null,this._=n,e=null);for(n.L=n.R=null,n.U=e,n.C=!0,t=n;e&&e.C;)i=e.U,e===i.L?(r=i.R,r&&r.C?(e.C=r.C=!1,i.C=!0,t=i):(t===e.R&&(Ur(this,e),t=e,e=t.U),e.C=!1,i.C=!0,Lr(this,i))):(r=i.L,r&&r.C?(e.C=r.C=!1,i.C=!0,t=i):(t===e.L&&(Lr(this,e),t=e,e=t.U),e.C=!1,i.C=!0,Ur(this,i))),e=t.U;this._.C=!1},remove:function(t){t.N&&(t.N.P=t.P),t.P&&(t.P.N=t.N),t.N=t.P=null;var n,e,i,r=t.U,o=t.L,u=t.R;if(e=o?u?Pr(u):o:u,r?r.L===t?r.L=e:r.R=e:this._=e,o&&u?(i=e.C,e.C=t.C,e.L=o,o.U=e,e!==u?(r=e.U,e.U=t.U,t=e.R,r.L=t,e.R=u,u.U=e):(e.U=r,r=e,t=e.R)):(i=t.C,t=e),t&&(t.U=r),!i){if(t&&t.C)return void(t.C=!1);do{if(t===this._)break;if(t===r.L){if(n=r.R,n.C&&(n.C=!1,r.C=!0,Ur(this,r),n=r.R),n.L&&n.L.C||n.R&&n.R.C){n.R&&n.R.C||(n.L.C=!1,n.C=!0,Lr(this,n),n=r.R),n.C=r.C,r.C=n.R.C=!1,Ur(this,r),t=this._;break}}else if(n=r.L,n.C&&(n.C=!1,r.C=!0,Lr(this,r),n=r.L),n.L&&n.L.C||n.R&&n.R.C){n.L&&n.L.C||(n.R.C=!1,n.C=!0,Ur(this,n),n=r.L),n.C=r.C,r.C=n.L.C=!1,Lr(this,r),t=this._;break}n.C=!0,t=r,r=r.U}while(!t.C);t&&(t.C=!1)}}};var bc,Mc,Nc,kc,Tc,Cc=[],Sc=[],Ac=1e-6,Ec=1e-12;ro.prototype={constructor:ro,polygons:function(){var t=this.edges;return this.cells.map(function(n){var e=n.halfedges.map(function(e){return jr(n,t[e])});return e.data=n.site.data,e})},triangles:function(){var t=[],n=this.edges;return this.cells.forEach(function(e,i){if(o=(r=e.halfedges).length)for(var r,o,u,a=e.site,s=-1,h=n[r[o-1]],c=h.left===a?h.right:h.left;++s=a)return null;var s=t-r.site[0],h=n-r.site[1],c=s*s+h*h;do{r=o.cells[i=u],u=null,r.halfedges.forEach(function(e){var i=o.edges[e],a=i.left;if(a!==r.site&&a||(a=i.right)){var s=t-a[0],h=n-a[1],l=s*s+h*h;l
- {d.value}
- {d.data.label}
+ {d.value}
+ {d.data.label}
);
}
diff --git a/src/app/components/VerticalBarChart.jsx b/src/app/components/VerticalBarChart.jsx
index 34166491..50970789 100644
--- a/src/app/components/VerticalBarChart.jsx
+++ b/src/app/components/VerticalBarChart.jsx
@@ -102,8 +102,8 @@ export default class VerticalBarChart extends Component {
.attr('text-anchor', 'middle')
.attr('x', 100)
.attr('y', 100)
- .attr('stroke', '#ffffff')
- .attr('stroke-width', '1px')
+ .attr('stroke-width', '0px')
+ .attr('fill', '#ffffff')
.attr('x', d => this.x(d.label) + this.x.bandwidth() / 2)
.attr('y', d => this.y(d.value) + 15)
.text(d => d.value);
@@ -115,16 +115,17 @@ export default class VerticalBarChart extends Component {
*/
render() {
const { width } = this.state.dimensions;
-
const translate = `translate(${margin.left}, ${margin.top})`;
+ const height = width * ASPECT;
+
this._renderGraph(this.props);
return (
{ this.setState({ dimensions }); }}>
-
+
{ this.x ?
- this.svg = ref} width={width} height={width * ASPECT} transform={translate}>
+ this.svg = ref} width={width} height={height} transform={translate}>
: null }
From 23c4da55de36acc71348ef7bc9af809fae7bb25e Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Fri, 17 Mar 2017 14:04:07 +0000
Subject: [PATCH 38/87] Make ship summary numbers react to pips/fuel/cargo/etc
---
src/app/components/Movement.jsx | 8 ++--
src/app/components/OutfittingSubpages.jsx | 2 +-
src/app/components/ShipSummaryTable.jsx | 46 +++++++++++------------
src/app/pages/OutfittingPage.jsx | 3 +-
src/app/shipyard/Calculations.js | 32 ++++++++--------
src/app/shipyard/Ship.js | 28 ++------------
src/less/shippicker.less | 1 -
7 files changed, 48 insertions(+), 72 deletions(-)
diff --git a/src/app/components/Movement.jsx b/src/app/components/Movement.jsx
index 6f84a052..df993c65 100644
--- a/src/app/components/Movement.jsx
+++ b/src/app/components/Movement.jsx
@@ -58,13 +58,13 @@ export default class Movement extends TranslatedComponent {
// Speed
- {formats.int(ship.calcSpeed(eng, fuel, cargo, boost))}m/s
+ {formats.int(ship.calcSpeed(eng, fuel, cargo, boost))}m/s
// Pitch
- {formats.int(ship.calcPitch(eng, fuel, cargo, boost))}°/s
+ {formats.int(ship.calcPitch(eng, fuel, cargo, boost))}°/s
// Roll
- {formats.int(ship.calcRoll(eng, fuel, cargo, boost))}°/s
+ {formats.int(ship.calcRoll(eng, fuel, cargo, boost))}°/s
// Yaw
- {formats.int(ship.calcYaw(eng, fuel, cargo, boost))}°/s
+ {formats.int(ship.calcYaw(eng, fuel, cargo, boost))}°/s
);
}
diff --git a/src/app/components/OutfittingSubpages.jsx b/src/app/components/OutfittingSubpages.jsx
index 807bf4f7..e8b33620 100644
--- a/src/app/components/OutfittingSubpages.jsx
+++ b/src/app/components/OutfittingSubpages.jsx
@@ -138,7 +138,7 @@ export default class OutfittingSubpages extends TranslatedComponent {
}
return (
-
+
diff --git a/src/app/components/ShipSummaryTable.jsx b/src/app/components/ShipSummaryTable.jsx
index 651f9a9f..a982b1df 100644
--- a/src/app/components/ShipSummaryTable.jsx
+++ b/src/app/components/ShipSummaryTable.jsx
@@ -2,6 +2,7 @@ import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import cn from 'classnames';
import { Warning } from './SvgIcons';
+import * as Calc from '../shipyard/Calculations';
/**
* Ship Summary Table / Stats
@@ -9,7 +10,13 @@ import { Warning } from './SvgIcons';
export default class ShipSummaryTable extends TranslatedComponent {
static propTypes = {
- ship: React.PropTypes.object.isRequired
+ ship: React.PropTypes.object.isRequired,
+ sys: React.PropTypes.number.isRequired,
+ eng: React.PropTypes.number.isRequired,
+ wep: React.PropTypes.number.isRequired,
+ cargo: React.PropTypes.number.isRequired,
+ fuel: React.PropTypes.number.isRequired,
+ marker: React.PropTypes.string.isRequired,
};
/**
@@ -17,7 +24,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
* @return {React.Component} Summary table
*/
render() {
- let ship = this.props.ship;
+ const { ship, fuel, eng, cargo, boost } = this.props;
let { language, tooltip, termtip } = this.context;
let translate = language.translate;
let u = language.units;
@@ -49,8 +56,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
{translate('mass')}
{translate('cargo')}
{translate('fuel')}
- {translate('jump range')}
- {translate('fastest range')}
+ {translate('jump range')}
{translate('crew')}
{translate('MLF')}
@@ -58,36 +64,28 @@ export default class ShipSummaryTable extends TranslatedComponent {
{translate('hull')}
{translate('unladen')}
{translate('laden')}
- {translate('max')}
- {translate('full tank')}
- {translate('laden')}
- {translate('jumps')}
- {translate('unladen')}
- {translate('laden')}
+ {translate('single')}
+ {translate('total')}
- { ship.canThrust() ? {int(ship.topSpeed)} {u['m/s']} : 0 }
- { ship.canBoost() ? {int(ship.topBoost)} {u['m/s']} : 0 }
+ { ship.canThrust() ? {int(ship.calcSpeed(eng, fuel, cargo, false))}{u['m/s']} : 0 }
+ { ship.canBoost() ? {int(ship.calcSpeed(eng, fuel, cargo, true))}{u['m/s']} : 0 }
{f1(ship.totalDps)}
{f1(ship.totalEps)}
{ship.timeToDrain === Infinity ? '∞' : time(ship.timeToDrain)}
{f1(ship.totalHps)}
{int(ship.hardness)}
{int(ship.armour)}
- {int(ship.shield)} {u.MJ}
- {ship.hullMass} {u.T}
- {int(ship.unladenMass)} {u.T}
- {int(ship.ladenMass)} {u.T}
- {round(ship.cargoCapacity)} {u.T}
- {round(ship.fuelCapacity)} {u.T}
- {f2(ship.unladenRange)} {u.LY}
- {f2(ship.fullTankRange)} {u.LY}
- {f2(ship.ladenRange)} {u.LY}
- {int(ship.maxJumpCount)}
- {f2(ship.unladenFastestRange)} {u.LY}
- {f2(ship.ladenFastestRange)} {u.LY}
+ {int(ship.shield)}{u.MJ}
+ {ship.hullMass}{u.T}
+ {int(ship.unladenMass)}{u.T}
+ {int(ship.ladenMass)}{u.T}
+ {round(ship.cargoCapacity)}{u.T}
+ {round(ship.fuelCapacity)}{u.T}
+ {f2(Calc.jumpRange(ship.unladenMass + fuel + cargo, ship.standard[2].m, fuel))}{u.LY}
+ {f2(Calc.totalJumpRange(ship.unladenMass + fuel + cargo, ship.standard[2].m, fuel))}{u.LY}
{ship.crew}
{ship.masslock}
diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx
index 7ad4b5d4..eb501fb1 100644
--- a/src/app/pages/OutfittingPage.jsx
+++ b/src/app/pages/OutfittingPage.jsx
@@ -375,6 +375,7 @@ export default class OutfittingPage extends Page {
// Markers are used to propagate state changes without requiring a deep comparison of the ship, as that takes a long time
const boostMarker = `${ship.canBoost()}`;
+ const shipSummaryMarker = `${ship.toString()}:${eng}:${fuel}:${cargo}`;
return (
@@ -410,7 +411,7 @@ export default class OutfittingPage extends Page {
{/* Main tables */}
-
+
diff --git a/src/app/shipyard/Calculations.js b/src/app/shipyard/Calculations.js
index 7e74913b..732328cc 100644
--- a/src/app/shipyard/Calculations.js
+++ b/src/app/shipyard/Calculations.js
@@ -9,33 +9,33 @@ import Module from './Module';
* @return {number} Distance in Light Years
*/
export function jumpRange(mass, fsd, fuel) {
- let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
- let fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass;
+ const fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
+ const fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass;
return Math.pow(Math.min(fuel === undefined ? fsdMaxFuelPerJump : fuel, fsdMaxFuelPerJump) / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass;
}
/**
- * Calculate the fastest (total) range based on mass and a specific FSD, and all fuel available
+ * Calculate the total jump range based on mass and a specific FSD, and all fuel available
*
* @param {number} mass Mass of a ship: laden, unlanden, partially laden, etc
* @param {object} fsd The FDS object/component with maxfuel, fuelmul, fuelpower, optmass
* @param {number} fuel The total fuel available
* @return {number} Distance in Light Years
*/
-export function fastestRange(mass, fsd, fuel) {
- let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
- let fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass;
- let fuelRemaining = fuel % fsdMaxFuelPerJump; // Fuel left after making N max jumps
- let jumps = Math.floor(fuel / fsdMaxFuelPerJump);
- mass += fuelRemaining;
- // Going backwards, start with the last jump using the remaining fuel
- let fastestRange = fuelRemaining > 0 ? Math.pow(fuelRemaining / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass : 0;
- // For each max fuel jump, calculate the max jump range based on fuel mass left in the tank
- for (let j = 0; j < jumps; j++) {
- mass += fsd.maxfuel;
- fastestRange += Math.pow(fsdMaxFuelPerJump / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass;
+export function totalJumpRange(mass, fsd, fuel) {
+ const fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
+ const fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass;
+
+ let fuelRemaining = fuel;
+ let totalRange = 0;
+ while (fuelRemaining > 0) {
+ const fuelForThisJump = Math.min(fuelRemaining, fsdMaxFuelPerJump);
+ totalRange += this.jumpRange(mass, fsd, fuelForThisJump);
+ // Mass is reduced
+ mass -= fuelForThisJump;
+ fuelRemaining -= fuelForThisJump;
}
- return fastestRange;
+ return totalRange;
};
/**
diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js
index 9a591fdc..e36cba54 100755
--- a/src/app/shipyard/Ship.js
+++ b/src/app/shipyard/Ship.js
@@ -138,17 +138,6 @@ export default class Ship {
this.standard[4].m.getEnginesCapacity() > this.boostEnergy; // PD capacitor is sufficient for boost
}
- /**
- * Calculate hypothetical jump range using the installed FSD and the
- * specified mass which can be more or less than ships actual mass
- * @param {Number} fuel Fuel available in tons
- * @param {Number} cargo Cargo in tons
- * @return {Number} Jump range in Light Years
- */
- calcJumpRangeWith(fuel, cargo) {
- return Calc.jumpRange(this.unladenMass + fuel + cargo, this.standard[2].m, fuel);
- }
-
/**
* Calculate the hypothetical laden jump range based on a potential change in mass, fuel, or FSD
* @param {Number} massDelta Optional - Change in laden mass (mass + cargo + fuel)
@@ -173,17 +162,6 @@ export default class Ship {
return Calc.jumpRange(this.unladenMass + (massDelta || 0) + Math.min(fsdMaxFuelPerJump, fuel || this.fuelCapacity), fsd || this.standard[2].m, fuel);
}
- /**
- * Calculate cumulative (total) jump range when making longest jumps using the installed FSD and the
- * specified mass which can be more or less than ships actual mass
- * @param {Number} fuel Fuel available in tons
- * @param {Number} cargo Cargo in tons
- * @return {Number} Total/Cumulative Jump range in Light Years
- */
- calcFastestRangeWith(fuel, cargo) {
- return Calc.fastestRange(this.unladenMass + fuel + cargo, this.standard[2].m, fuel);
- }
-
/**
* Calculate the hypothetical top speeds at cargo and fuel tonnage
* @param {Number} fuel Fuel available in tons
@@ -1398,9 +1376,9 @@ export default class Ship {
this.unladenRange = this.calcUnladenRange(); // Includes fuel weight for jump
this.fullTankRange = Calc.jumpRange(unladenMass + fuelCapacity, fsd); // Full Tank
this.ladenRange = this.calcLadenRange(); // Includes full tank and caro
- this.unladenFastestRange = Calc.fastestRange(unladenMass, fsd, fuelCapacity);
- this.ladenFastestRange = Calc.fastestRange(unladenMass + this.cargoCapacity, fsd, fuelCapacity);
- this.maxJumpCount = Math.ceil(fuelCapacity / fsd.maxfuel);
+ this.unladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity, fsd, fuelCapacity);
+ this.ladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity + this.cargoCapacity, fsd, fuelCapacity);
+ this.maxJumpCount = Math.ceil(fuelCapacity / fsd.getMaxFuelPerJump());
return this;
}
diff --git a/src/less/shippicker.less b/src/less/shippicker.less
index 8c7658c1..982bf5ad 100755
--- a/src/less/shippicker.less
+++ b/src/less/shippicker.less
@@ -11,7 +11,6 @@
.menu {
position: relative;
- z-index: 1;
cursor: default;
&.r {
From 369d882354ad257aaa0169087fc0003b33bd7b26 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Fri, 17 Mar 2017 18:10:07 +0000
Subject: [PATCH 39/87] Tidy-ups
---
src/app/components/DamageDealt.jsx | 3 -
src/app/components/Defence.jsx | 275 +------------------
src/app/components/EngineProfile.jsx | 33 +--
src/app/components/FSDProfile.jsx | 33 +--
src/app/components/JumpRange.jsx | 2 -
src/app/components/LineChart.jsx | 131 +++++-----
src/app/components/Movement.jsx | 1 -
src/app/components/OutfittingSubpages.jsx | 25 +-
src/app/components/PieChart.jsx | 5 +
src/app/components/WeaponDamageChart.jsx | 250 ++++++++++++++++++
src/app/pages/OutfittingPage.jsx | 33 +--
src/app/shipyard/Calculations.js | 304 ++++++++++++++++++++++
src/app/shipyard/Constants.js | 1 +
src/less/charts.less | 3 +
14 files changed, 695 insertions(+), 404 deletions(-)
create mode 100644 src/app/components/WeaponDamageChart.jsx
diff --git a/src/app/components/DamageDealt.jsx b/src/app/components/DamageDealt.jsx
index c9bd714e..d1d0fbe6 100644
--- a/src/app/components/DamageDealt.jsx
+++ b/src/app/components/DamageDealt.jsx
@@ -52,7 +52,6 @@ export function weaponComparator(translate, propComparator, desc) {
export default class DamageDealt extends TranslatedComponent {
static propTypes = {
ship: React.PropTypes.object.isRequired,
- chartWidth: React.PropTypes.number.isRequired,
code: React.PropTypes.string.isRequired
};
@@ -556,7 +555,6 @@ export default class DamageDealt extends TranslatedComponent {
{translate('sustained dps against standard shields')}
{translate('sustained dps against standard armour')}
0.7 ? boosterExplDmg : 0.7 - (0.7 - boosterExplDmg) / 2;
- boosterKinDmg = boosterKinDmg > 0.7 ? boosterKinDmg : 0.7 - (0.7 - boosterKinDmg) / 2;
- boosterThermDmg = boosterThermDmg > 0.7 ? boosterThermDmg : 0.7 - (0.7 - boosterThermDmg) / 2;
-
- const generatorStrength = Calc.shieldStrength(ship.hullMass, ship.baseShieldStrength, shieldGenerator, 1);
- const boostersStrength = generatorStrength * boost;
-
- // Recover time is the time taken to go from 0 to 50%. It includes a 16-second wait before shields start to recover
- const shieldToRecover = (generatorStrength + boostersStrength) / 2;
- const powerDistributor = ship.standard[4].m;
- const sysRechargeRate = this._calcSysRechargeRate(powerDistributor, sys);
-
- // Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes
- // 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration
- let capacitorDrain = (shieldGenerator.getBrokenRegenerationRate() * 0.6) - sysRechargeRate;
- let capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain;
-
- let recover = 16;
- if (capacitorDrain <= 0 || shieldToRecover < capacitorLifetime * shieldGenerator.getBrokenRegenerationRate()) {
- // We can recover the entire shield from the capacitor store
- recover += shieldToRecover / shieldGenerator.getBrokenRegenerationRate();
- } else {
- // We can recover some of the shield from the capacitor store
- recover += capacitorLifetime;
- const remainingShieldToRecover = shieldToRecover - capacitorLifetime * shieldGenerator.getBrokenRegenerationRate();
- if (sys === 0) {
- // No system pips so will never recover shields
- recover = Math.Inf;
- } else {
- // Recover remaining shields at the rate of the power distributor's recharge
- recover += remainingShieldToRecover / (sysRechargeRate / 0.6);
- }
- }
-
- // Recharge time is the time taken to go from 50% to 100%
- const shieldToRecharge = (generatorStrength + boostersStrength) / 2;
-
- // Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes
- // 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration
- capacitorDrain = (shieldGenerator.getRegenerationRate() * 0.6) - sysRechargeRate;
- capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain;
-
- let recharge = 0;
- if (capacitorDrain <= 0 || shieldToRecharge < capacitorLifetime * shieldGenerator.getRegenerationRate()) {
- // We can recharge the entire shield from the capacitor store
- recharge += shieldToRecharge / shieldGenerator.getRegenerationRate();
- } else {
- // We can recharge some of the shield from the capacitor store
- recharge += capacitorLifetime;
- const remainingShieldToRecharge = shieldToRecharge - capacitorLifetime * shieldGenerator.getRegenerationRate();
- if (sys === 0) {
- // No system pips so will never recharge shields
- recharge = Math.Inf;
- } else {
- // Recharge remaining shields at the rate of the power distributor's recharge
- recharge += remainingShieldToRecharge / (sysRechargeRate / 0.6);
- }
- }
-
- shield = {
- generator: generatorStrength,
- boosters: boostersStrength,
- cells: ship.shieldCells,
- total: generatorStrength + boostersStrength + ship.shieldCells,
- recover,
- recharge,
- };
-
- // Shield resistances have three components: the shield generator, the shield boosters and the SYS pips.
- // We re-cast these as damage percentages
- shield.absolute = {
- generator: 1,
- boosters: 1,
- sys: 1 - sysResistance,
- total: 1 - sysResistance,
- max: 1 - maxSysResistance
- };
-
- shield.explosive = {
- generator: 1 - shieldGenerator.getExplosiveResistance(),
- boosters: boosterExplDmg,
- sys: (1 - sysResistance),
- total: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - sysResistance),
- max: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - maxSysResistance)
- };
-
- shield.kinetic = {
- generator: 1 - shieldGenerator.getKineticResistance(),
- boosters: boosterKinDmg,
- sys: (1 - sysResistance),
- total: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - sysResistance),
- max: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - maxSysResistance)
- };
-
- shield.thermal = {
- generator: 1 - shieldGenerator.getThermalResistance(),
- boosters: boosterThermDmg,
- sys: (1 - sysResistance),
- total: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - sysResistance),
- max: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - maxSysResistance)
- };
-
- shielddamage.absolutesdps = shieldsdps.absolute *= shield.absolute.total;
- shielddamage.explosivesdps = shieldsdps.explosive *= shield.explosive.total;
- shielddamage.kineticsdps = shieldsdps.kinetic *= shield.kinetic.total;
- shielddamage.thermalsdps = shieldsdps.thermal *= shield.thermal.total;
- shielddamage.totalsdps = shielddamage.absolutesdps + shielddamage.explosivesdps + shielddamage.kineticsdps + shielddamage.thermalsdps;
- }
-
- // Armour from bulkheads
- const armourBulkheads = ship.baseArmour + (ship.baseArmour * ship.bulkheads.m.getHullBoost());
- let armourReinforcement = 0;
-
- let moduleArmour = 0;
- let moduleProtection = 1;
-
- let hullExplDmg = 1;
- let hullKinDmg = 1;
- let hullThermDmg = 1;
-
- // Armour from HRPs and module armour from MRPs
- for (let slot of ship.internal) {
- if (slot.m && slot.m.grp == 'hr') {
- armourReinforcement += slot.m.getHullReinforcement();
- // Hull boost for HRPs is applied against the ship's base armour
- armourReinforcement += ship.baseArmour * slot.m.getModValue('hullboost') / 10000;
-
- hullExplDmg = hullExplDmg * (1 - slot.m.getExplosiveResistance());
- hullKinDmg = hullKinDmg * (1 - slot.m.getKineticResistance());
- hullThermDmg = hullThermDmg * (1 - slot.m.getThermalResistance());
- }
- if (slot.m && slot.m.grp == 'mrp') {
- moduleArmour += slot.m.getIntegrity();
- moduleProtection = moduleProtection * (1 - slot.m.getProtection());
- }
- }
- moduleProtection = 1 - moduleProtection;
-
- // Apply diminishing returns
- hullExplDmg = hullExplDmg > 0.7 ? hullExplDmg : 0.7 - (0.7 - hullExplDmg) / 2;
- hullKinDmg = hullKinDmg > 0.7 ? hullKinDmg : 0.7 - (0.7 - hullKinDmg) / 2;
- hullThermDmg = hullThermDmg > 0.7 ? hullThermDmg : 0.7 - (0.7 - hullThermDmg) / 2;
-
- const armour = {
- bulkheads: armourBulkheads,
- reinforcement: armourReinforcement,
- modulearmour: moduleArmour,
- moduleprotection: moduleProtection,
- total: armourBulkheads + armourReinforcement
- };
-
- // Armour resistances have two components: bulkheads and HRPs
- // We re-cast these as damage percentages
- armour.absolute = {
- bulkheads: 1,
- reinforcement: 1,
- total: 1
- };
-
- armour.explosive = {
- bulkheads: 1 - ship.bulkheads.m.getExplosiveResistance(),
- reinforcement: hullExplDmg,
- total: (1 - ship.bulkheads.m.getExplosiveResistance()) * hullExplDmg
- };
-
- armour.kinetic = {
- bulkheads: 1 - ship.bulkheads.m.getKineticResistance(),
- reinforcement: hullKinDmg,
- total: (1 - ship.bulkheads.m.getKineticResistance()) * hullKinDmg
- };
-
- armour.thermal = {
- bulkheads: 1 - ship.bulkheads.m.getThermalResistance(),
- reinforcement: hullThermDmg,
- total: (1 - ship.bulkheads.m.getThermalResistance()) * hullThermDmg
- };
-
- const armourdamage = {
- absolutesdps: armoursdps.absolute *= armour.absolute.total,
- explosivesdps: armoursdps.explosive *= armour.explosive.total,
- kineticsdps: armoursdps.kinetic *= armour.kinetic.total,
- thermalsdps: armoursdps.thermal *= armour.thermal.total
- };
- armourdamage.totalsdps = armourdamage.absolutesdps + armourdamage.explosivesdps + armourdamage.kineticsdps + armourdamage.thermalsdps;
-
- return { shield, armour, shielddamage, armourdamage };
- }
-
- /**
- * Calculate the resistance provided by SYS pips
- * @param {integer} sys the value of the SYS pips
- * @returns {integer} the resistance for the given pips
- */
- _calcSysResistance(sys) {
- return Math.pow(sys,0.85) * 0.6 / Math.pow(4,0.85);
- }
-
/**
* Render shields
* @return {React.Component} contents
@@ -371,13 +124,13 @@ export default class Defence extends TranslatedComponent {
const shieldThermalTooltipDetails = [];
let maxEffectiveShield = 0;
if (shield.total) {
- if (Math.round(shield.generator) > 0) shieldSourcesData.push({ value: Math.round(shield.generator), label: translate('generator') });
- if (Math.round(shield.boosters) > 0) shieldSourcesData.push({ value: Math.round(shield.boosters), label: translate('boosters') });
- if (Math.round(shield.cells) > 0) shieldSourcesData.push({ value: Math.round(shield.cells), label: translate('cells') });
+ shieldSourcesData.push({ value: Math.round(shield.generator), label: translate('generator') });
+ shieldSourcesData.push({ value: Math.round(shield.boosters), label: translate('boosters') });
+ shieldSourcesData.push({ value: Math.round(shield.cells), label: translate('cells') });
- if (Math.round(shield.generator) > 0) shieldTooltipDetails.push({translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}
);
- if (Math.round(shield.boosters) > 0) shieldTooltipDetails.push({translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}
);
- if (Math.round(shield.cells) > 0) shieldTooltipDetails.push({translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}
);
+ shieldTooltipDetails.push({translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}
);
+ shieldTooltipDetails.push({translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}
);
+ shieldTooltipDetails.push({translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}
);
shieldAbsoluteTooltipDetails.push({translate('generator') + ' ' + formats.pct1(shield.absolute.generator)}
);
shieldAbsoluteTooltipDetails.push({translate('boosters') + ' ' + formats.pct1(shield.absolute.boosters)}
);
@@ -413,12 +166,12 @@ export default class Defence extends TranslatedComponent {
}
const armourSourcesData = [];
- if (Math.round(armour.bulkheads) > 0) armourSourcesData.push({ value: Math.round(armour.bulkheads), label: translate('bulkheads') });
- if (Math.round(armour.reinforcement) > 0) armourSourcesData.push({ value: Math.round(armour.reinforcement), label: translate('reinforcement') });
+ armourSourcesData.push({ value: Math.round(armour.bulkheads), label: translate('bulkheads') });
+ armourSourcesData.push({ value: Math.round(armour.reinforcement), label: translate('reinforcement') });
const armourTooltipDetails = [];
- if (armour.bulkheads > 0) armourTooltipDetails.push({translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}
);
- if (armour.reinforcement > 0) armourTooltipDetails.push({translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}
);
+ armourTooltipDetails.push({translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}
);
+ armourTooltipDetails.push({translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}
);
const armourAbsoluteTooltipDetails = [];
armourAbsoluteTooltipDetails.push({translate('bulkheads') + ' ' + formats.pct1(armour.absolute.bulkheads)}
);
@@ -480,7 +233,7 @@ export default class Defence extends TranslatedComponent {
{translate('armour metrics')}
{armourTooltipDetails} )} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw armour strength')} {formats.int(armour.total)}
- {translate('PHRASE_TIME_TO_LOSE_ARMOUR')} {armourdamage.totalsdps == 0 ? translate('infinity') : formats.time(armour.total / armourdamage.totalsdps)}
+ {translate('PHRASE_TIME_TO_LOSE_ARMOUR')} {armourdamage.totalsdps == 0 ? translate('ever') : formats.time(armour.total / armourdamage.totalsdps)}
{translate('raw module armour')} {formats.int(armour.modulearmour)}
{translate('PHRASE_MODULE_PROTECTION_EXTERNAL')} {formats.pct1(armour.moduleprotection / 2)}
{translate('PHRASE_MODULE_PROTECTION_INTERNAL')} {formats.pct1(armour.moduleprotection)}
diff --git a/src/app/components/EngineProfile.jsx b/src/app/components/EngineProfile.jsx
index fef2a5f5..2faff719 100644
--- a/src/app/components/EngineProfile.jsx
+++ b/src/app/components/EngineProfile.jsx
@@ -15,7 +15,6 @@ import * as Calc from '../shipyard/Calculations';
export default class EngineProfile extends TranslatedComponent {
static propTypes = {
ship: React.PropTypes.object.isRequired,
- chartWidth: React.PropTypes.number.isRequired,
cargo: React.PropTypes.number.isRequired,
fuel: React.PropTypes.number.isRequired,
eng: React.PropTypes.number.isRequired,
@@ -87,24 +86,20 @@ export default class EngineProfile extends TranslatedComponent {
// This graph can have a precipitous fall-off so we use lots of points to make it look a little smoother
return (
-
- {translate('engine profile')}
-
-
+
);
}
}
diff --git a/src/app/components/FSDProfile.jsx b/src/app/components/FSDProfile.jsx
index 2db23b1d..c5fea602 100644
--- a/src/app/components/FSDProfile.jsx
+++ b/src/app/components/FSDProfile.jsx
@@ -15,7 +15,6 @@ import * as Calc from '../shipyard/Calculations';
export default class FSDProfile extends TranslatedComponent {
static propTypes = {
ship: React.PropTypes.object.isRequired,
- chartWidth: React.PropTypes.number.isRequired,
cargo: React.PropTypes.number.isRequired,
fuel: React.PropTypes.number.isRequired,
marker: React.PropTypes.string.isRequired
@@ -85,24 +84,20 @@ export default class FSDProfile extends TranslatedComponent {
const code = ship.name + ship.toString() + '.' + fuel;
return (
-
- {translate('fsd profile')}
-
-
+
);
}
}
diff --git a/src/app/components/JumpRange.jsx b/src/app/components/JumpRange.jsx
index e8929a58..7fc28c36 100644
--- a/src/app/components/JumpRange.jsx
+++ b/src/app/components/JumpRange.jsx
@@ -15,7 +15,6 @@ import * as Calc from '../shipyard/Calculations';
export default class JumpRange extends TranslatedComponent {
static propTypes = {
ship: React.PropTypes.object.isRequired,
- chartWidth: React.PropTypes.number.isRequired,
code: React.PropTypes.string.isRequired
};
@@ -91,7 +90,6 @@ export default class JumpRange extends TranslatedComponent {
{translate('jump range')}
0.60),
+ flip = (xPos / width > 0.60),
tipWidth = 0,
tipHeightPx = tips.selectAll('rect').node().getBoundingClientRect().height;
@@ -110,19 +115,21 @@ export default class LineChart extends TranslatedComponent {
/**
* Update dimensions based on properties and scale
- * @param {Object} props React Component properties
+ * @param {Object} props React Component properties
* @param {number} scale size ratio / scale
+ * @returns {Object} calculated dimensions
*/
_updateDimensions(props, scale) {
- let { width, xMax, xMin, yMin, yMax } = props;
- let innerWidth = width - MARGIN.left - MARGIN.right;
- let outerHeight = Math.round(width * 0.8 * scale);
- let innerHeight = outerHeight - MARGIN.top - MARGIN.bottom;
+ const { xMax, xMin, yMin, yMax } = props;
+ const { width, height } = this.state.dimensions;
+ const innerWidth = width - MARGIN.left - MARGIN.right;
+ const outerHeight = Math.round(width * 2 / 3); // TODO make this an aspect property
+ const innerHeight = outerHeight - MARGIN.top - MARGIN.bottom;
this.state.xScale.range([0, innerWidth]).domain([xMin, xMax || 1]).clamp(true);
this.state.xAxisScale.range([0, innerWidth]).domain([xMin, xMax]).clamp(true);
this.state.yScale.range([innerHeight, 0]).domain([yMin, yMax + (yMax - yMin) * 0.1]); // 10% higher than maximum value for tooltip visibility
- this.setState({ innerWidth, outerHeight, innerHeight });
+ return { innerWidth, outerHeight, innerHeight };
}
/**
@@ -183,7 +190,7 @@ export default class LineChart extends TranslatedComponent {
for (let i = 0, l = series ? series.length : 1; i < l; i++) {
const yAccessor = series ? function(d) { return state.yScale(d[1][this]); }.bind(series[i]) : (d) => state.yScale(d[1]);
seriesLines.push(d3.line().x((d, i) => this.state.xScale(d[0])).y(yAccessor));
- detailElems.push( );
+ detailElems.push( );
markerElems.push( );
}
@@ -196,7 +203,6 @@ export default class LineChart extends TranslatedComponent {
* Update dimensions and series data based on props and context.
*/
componentWillMount() {
- this._updateDimensions(this.props, this.context.sizeRatio);
this._updateSeries(this.props, this.state);
}
@@ -206,14 +212,7 @@ export default class LineChart extends TranslatedComponent {
* @param {Object} nextContext Incoming/Next conext
*/
componentWillReceiveProps(nextProps, nextContext) {
- let { func, xMin, xMax, yMin, yMax, width } = nextProps;
- let props = this.props;
-
- let domainChanged = xMax != props.xMax || xMin != props.xMin || yMax != props.yMax || yMin != props.yMin || func != props.func;
-
- if (width != props.width || domainChanged || this.context.sizeRatio != nextContext.sizeRatio) {
- this._updateDimensions(nextProps, nextContext.sizeRatio);
- }
+ const props = this.props;
if (props.code != nextProps.code) {
this._updateSeries(nextProps, this.state);
@@ -225,53 +224,57 @@ export default class LineChart extends TranslatedComponent {
* @return {React.Component} Chart SVG
*/
render() {
- if (!this.props.width) {
- return null;
- }
-
- let { xMin, xMax, xLabel, yLabel, xUnit, yUnit, xMark, colors } = this.props;
- let { innerWidth, outerHeight, innerHeight, tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state;
- let line = this.line;
- let lines = seriesLines.map((line, i) => ).reverse();
+ const { innerWidth, outerHeight, innerHeight } = this._updateDimensions(this.props, this.context.sizeRatio);
+ const { width, height } = this.state.dimensions;
+ const { xMin, xMax, xLabel, yLabel, xUnit, yUnit, xMark, colors } = this.props;
+ const { tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state;
+ const line = this.line;
+ const lines = seriesLines.map((line, i) => ).reverse();
const markX = xMark ? innerWidth * (xMark - xMin) / (xMax - xMin) : 0;
const xmark = xMark ? : '';
- return
-
- {xmark}
- {lines}
- d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}>
-
- {xLabel}
- ({xUnit})
-
-
- d3.select(elem).call(this.yAxis)}>
-
- {yLabel}
- { yUnit && ({yUnit}) }
-
-
- this.tipContainer = d3.select(g)} style={{ display: 'none' }}>
-
- {detailElems}
-
- this.markersContainer = d3.select(g)} style={{ display: 'none' }}>
- {markerElems}
-
-
-
- ;
+ return (
+ { this.setState({ dimensions }); }}>
+
+
+
+ {xmark}
+ {lines}
+ d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}>
+
+ {xLabel}
+ ({xUnit})
+
+
+ d3.select(elem).call(this.yAxis)}>
+
+ {yLabel}
+ { yUnit && ({yUnit}) }
+
+
+ this.tipContainer = d3.select(g)} style={{ display: 'none' }}>
+
+ {detailElems}
+
+ this.markersContainer = d3.select(g)} style={{ display: 'none' }}>
+ {markerElems}
+
+
+
+
+
+
+ );
}
}
diff --git a/src/app/components/Movement.jsx b/src/app/components/Movement.jsx
index df993c65..e7432969 100644
--- a/src/app/components/Movement.jsx
+++ b/src/app/components/Movement.jsx
@@ -33,7 +33,6 @@ export default class Movement extends TranslatedComponent {
return (
- {translate('movement profile')}
// Axes
diff --git a/src/app/components/OutfittingSubpages.jsx b/src/app/components/OutfittingSubpages.jsx
index e8b33620..6a760833 100644
--- a/src/app/components/OutfittingSubpages.jsx
+++ b/src/app/components/OutfittingSubpages.jsx
@@ -11,6 +11,7 @@ import EngineProfile from './EngineProfile';
import FSDProfile from './FSDProfile';
import Movement from './Movement';
import Defence from './Defence';
+import WeaponDamageChart from './WeaponDamageChart';
/**
* Outfitting subpages
@@ -21,7 +22,6 @@ export default class OutfittingSubpages extends TranslatedComponent {
ship: React.PropTypes.object.isRequired,
code: React.PropTypes.string.isRequired,
onChange: React.PropTypes.func.isRequired,
- chartWidth: React.PropTypes.number.isRequired,
buildName: React.PropTypes.string,
sys: React.PropTypes.number.isRequired,
eng: React.PropTypes.number.isRequired,
@@ -73,25 +73,40 @@ export default class OutfittingSubpages extends TranslatedComponent {
* @return {React.Component} Tab contents
*/
_profilesTab() {
- const { ship, code, chartWidth, cargo, fuel, eng, boost } = this.props;
+ const { ship, opponent, cargo, fuel, eng, boost, engagementRange } = this.props;
+ const { translate } = this.context.language;
let realBoost = boost && ship.canBoost();
const engineProfileMarker = `${ship.toString()}:${cargo}:${fuel}:${eng}:${realBoost}`;
const fsdProfileMarker = `${ship.toString()}:${cargo}:${fuel}`;
const movementMarker = `${ship.topSpeed}:${ship.pitch}:${ship.roll}:${ship.yaw}:${ship.canBoost()}`;
+ const damageMarker = `${ship.toString()}:${opponent.toString()}:${engagementRange}`;
return
-
+
{translate('engine profile')}
+
-
+
{translate('fsd profile')}
+
+
{translate('movement profile')}
+
+
+
{translate('damage to opponent\'s shields')}
+
+
+
+
+
{translate('damage to opponent\'s hull')}
+
+
;
}
@@ -114,7 +129,7 @@ export default class OutfittingSubpages extends TranslatedComponent {
_defenceTab() {
const { ship, sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild } = this.props;
- const marker = `${ship.shield}:${ship.shieldCells}:${ship.shieldExplRes}:${ship.shieldKinRes}:${ship.shieldThermRes}:${ship.armour}:${ship.standard[4].m.getSystemsCapacity()}:${ship.standard[4].m.getSystemsRechargeRate()}:${opponent.name}:${opponentBuild}:${engagementRange}`;
+ const marker = `${ship.toString()}:${opponent.name}:${opponentBuild}:${engagementRange}`;
return
diff --git a/src/app/components/PieChart.jsx b/src/app/components/PieChart.jsx
index 62f811f6..98d25800 100644
--- a/src/app/components/PieChart.jsx
+++ b/src/app/components/PieChart.jsx
@@ -43,6 +43,11 @@ export default class PieChart extends Component {
* @returns {Object} the SVG for the slice
*/
sliceGenerator(d, i) {
+ if (!d || d.value == 0) {
+ // Ignore 0 values
+ return null;
+ }
+
const { width, height } = this.state.dimensions;
const { data } = this.props;
diff --git a/src/app/components/WeaponDamageChart.jsx b/src/app/components/WeaponDamageChart.jsx
new file mode 100644
index 00000000..90121e5b
--- /dev/null
+++ b/src/app/components/WeaponDamageChart.jsx
@@ -0,0 +1,250 @@
+import React from 'react';
+import TranslatedComponent from './TranslatedComponent';
+import { Ships } from 'coriolis-data/dist';
+import ShipSelector from './ShipSelector';
+import { nameComparator } from '../utils/SlotFunctions';
+import { CollapseSection, ExpandSection, MountFixed, MountGimballed, MountTurret } from './SvgIcons';
+import LineChart from '../components/LineChart';
+import Slider from '../components/Slider';
+import * as Calc from '../shipyard/Calculations';
+import Module from '../shipyard/Module';
+
+const DAMAGE_DEALT_COLORS = ['#FFFFFF', '#FF0000', '#00FF00', '#7777FF', '#FFFF00', '#FF00FF', '#00FFFF', '#777777'];
+
+/**
+ * Weapon damage chart
+ */
+export default class WeaponDamageChart extends TranslatedComponent {
+ static propTypes = {
+ ship: React.PropTypes.object.isRequired,
+ opponent: React.PropTypes.object.isRequired,
+ hull: React.PropTypes.bool.isRequired,
+ engagementRange: React.PropTypes.number.isRequired,
+ marker: React.PropTypes.string.isRequired
+ };
+
+ /**
+ * Constructor
+ * @param {Object} props React Component properties
+ * @param {Object} context React Component context
+ */
+ constructor(props, context) {
+ super(props);
+
+ const { ship, opponent, hull } = this.props;
+
+ const maxRange = this._calcMaxRange(ship);
+ // We take whichever is the higher for shields and hull to ensure same Y axis for both
+ const maxDps = Math.max(this._calcMaxSDps(ship, opponent, true), this._calcMaxSDps(ship, opponent, false));
+
+ this.state = {
+ maxRange,
+ maxDps
+ };
+ }
+
+ /**
+ * Set the initial weapons state
+ */
+ componentWillMount() {
+ const weaponNames = this._weaponNames(this.props.ship, this.context);
+ this.setState({ weaponNames, calcSDpsFunc: this._calcSDps.bind(this, this.props.ship, weaponNames, this.props.opponent, this.props.hull) });
+ }
+
+ /**
+ * Set the updated weapons state if our ship changes
+ * @param {Object} nextProps Incoming/Next properties
+ * @param {Object} nextContext Incoming/Next conext
+ * @return {boolean} Returns true if the component should be rerendered
+ */
+ componentWillReceiveProps(nextProps, nextContext) {
+ if (nextProps.marker != this.props.marker) {
+ const weaponNames = this._weaponNames(nextProps.ship, nextContext);
+ const maxRange = this._calcMaxRange(nextProps.ship);
+ // We take whichever is the higher for shields and hull to ensure same Y axis for both
+ const maxDps = Math.max(this._calcMaxSDps(nextProps.ship, nextProps.opponent, true), this._calcMaxSDps(nextProps.ship, nextProps.opponent, false));
+ this.setState({ weaponNames,
+ maxRange,
+ maxDps,
+ calcSDpsFunc: this._calcSDps.bind(this, nextProps.ship, weaponNames, nextProps.opponent, nextProps.hull)
+ });
+ }
+ return true;
+ }
+
+ /**
+ * Calculate the maximum range of a ship's weapons
+ * @param {Object} ship The ship
+ * @returns {int} The maximum range, in metres
+ */
+ _calcMaxRange(ship) {
+ let maxRange = 1000; // Minimum
+ for (let i = 0; i < ship.hardpoints.length; i++) {
+ if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
+ const thisRange = ship.hardpoints[i].m.getRange();
+ if (thisRange > maxRange) {
+ maxRange = thisRange;
+ }
+ }
+ }
+
+ return maxRange;
+ }
+
+ /**
+ * Calculate the maximum sustained single-weapon DPS for this ship
+ * @param {Object} ship The ship
+ * @param {Object} opponent The opponent ship
+ * @param {bool} hull True if against hull
+ * @return {number} The maximum sustained single-weapon DPS
+ */
+ _calcMaxSDps(ship, opponent, hull) {
+ // Additional information to allow effectiveness calculations
+ const { shield, armour, shielddamage, armourdamage } = Calc.defenceMetrics(opponent, ship, 4, 0);
+
+ let maxSDps = 0;
+ for (let i = 0; i < ship.hardpoints.length; i++) {
+ if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
+ const m = ship.hardpoints[i].m;
+ const thisSDps = this._calcWeaponSDps(ship, m, opponent, hull, shield, armour, 0);
+ if (thisSDps > maxSDps) {
+ maxSDps = thisSDps;
+ }
+ }
+ }
+ return maxSDps;
+ }
+
+ /**
+ * Obtain the weapon names for this ship
+ * @param {Object} ship The ship
+ * @param {Object} context The context
+ * @return {array} The weapon names
+ */
+ _weaponNames(ship, context) {
+ const translate = context.language.translate;
+ let names = [];
+ let num = 1;
+ for (let i = 0; i < ship.hardpoints.length; i++) {
+ if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
+ const m = ship.hardpoints[i].m;
+ let name = '' + num++ + ': ' + m.class + m.rating + (m.missile ? '/' + m.missile : '') + ' ' + translate(m.name || m.grp);
+ let engineering;
+ if (m.blueprint && m.blueprint.name) {
+ engineering = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
+ if (m.blueprint.special && m.blueprint.special.id) {
+ engineering += ', ' + translate(m.blueprint.special.name);
+ }
+ }
+ if (engineering) {
+ name = name + ' (' + engineering + ')';
+ }
+ names.push(name);
+ }
+ }
+ return names;
+ }
+
+ /**
+ * Calculate the per-weapon sustained DPS for this ship against another ship at a given range
+ * @param {Object} ship The ship
+ * @param {Object} weaponNames The names of the weapons for which to calculate DPS
+ * @param {Object} opponent The target
+ * @param {bool} hull true if to calculate against hull, false if to calculate against shields
+ * @param {Object} engagementRange The engagement range
+ * @return {array} The array of weapon DPS
+ */
+ _calcSDps(ship, weaponNames, opponent, hull, engagementRange) {
+ // Additional information to allow effectiveness calculations
+ const { shield, armour, shielddamage, armourdamage } = Calc.defenceMetrics(opponent, ship, 4, engagementRange);
+
+ let results = {};
+ let weaponNum = 0;
+ for (let i = 0; i < ship.hardpoints.length; i++) {
+ if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
+ const m = ship.hardpoints[i].m;
+ results[weaponNames[weaponNum++]] = this._calcWeaponSDps(ship, m, opponent, hull, shield, armour, engagementRange);
+ }
+ }
+ return results;
+ }
+
+ /**
+ * Calculate the sustained DPS for a particular weapon for this ship against another ship at a given range
+ * @param {Object} ship The ship that will deal the damage
+ * @param {Object} m The weapon that will deal the damage
+ * @param {Object} opponent The ship against which damage will be dealt
+ * @param {boolean} hull True if hitting hull
+ * @param {Object} shield Shield defence metrics
+ * @param {Object} armour Armour defence metrics
+ * @param {Object} engagementRange The engagement range
+ * @return {object} Returns the sustained DPS for the weapon
+ */
+ _calcWeaponSDps(ship, m, opponent, hull, shield, armour, engagementRange) {
+ let falloff = 1;
+ if (m.getFalloff()) {
+ // Calculate the falloff % due to range
+ if (engagementRange > m.getRange()) {
+ // Weapon is out of range
+ falloff = 0;
+ } else {
+ const falloffPoint = m.getFalloff();
+ if (engagementRange > falloffPoint) {
+ const falloffRange = m.getRange() - falloffPoint;
+ // Assuming straight-line falloff
+ falloff = 1 - (engagementRange - falloffPoint) / falloffRange;
+ }
+ }
+ }
+
+ let effectiveness = 0;
+ if (m.getDamageDist().E) {
+ effectiveness += m.getDamageDist().E * (hull ? armour.explosive.total : shield.explosive.total);
+ }
+ if (m.getDamageDist().K) {
+ effectiveness += m.getDamageDist().K * (hull ? armour.kinetic.total : shield.kinetic.total);
+ }
+ if (m.getDamageDist().T) {
+ effectiveness += m.getDamageDist().T * (hull ? armour.thermal.total : shield.thermal.total);
+ }
+ if (m.getDamageDist().A) {
+ effectiveness += m.getDamageDist().A * (hull ? armour.absolute.total : shield.absolute.total);
+ }
+
+ // Return the final effective SDPS
+ return (m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getDps()) * falloff * effectiveness;
+ }
+
+ /**
+ * Render damage dealt
+ * @return {React.Component} contents
+ */
+ render() {
+ const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
+ const { formats, translate, units } = language;
+ const { maxRange } = this.state;
+ const { ship, opponent } = this.props;
+
+ const sortOrder = this._sortOrder;
+ const onCollapseExpand = this._onCollapseExpand;
+
+ const code = `${ship.toString()}:${opponent.toString()}`;
+
+ return (
+
+
+
+ );
+ }
+}
diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx
index eb501fb1..ad6e773a 100644
--- a/src/app/pages/OutfittingPage.jsx
+++ b/src/app/pages/OutfittingPage.jsx
@@ -14,9 +14,6 @@ import StandardSlotSection from '../components/StandardSlotSection';
import HardpointsSlotSection from '../components/HardpointsSlotSection';
import InternalSlotSection from '../components/InternalSlotSection';
import UtilitySlotSection from '../components/UtilitySlotSection';
-import OffenceSummary from '../components/OffenceSummary';
-import DefenceSummary from '../components/DefenceSummary';
-import MovementSummary from '../components/MovementSummary';
import Pips from '../components/Pips';
import Boost from '../components/Boost';
import Fuel from '../components/Fuel';
@@ -92,7 +89,6 @@ export default class OutfittingPage extends Page {
title: this._getTitle(buildName),
costTab: Persist.getCostTab() || 'costs',
buildName,
- thirdChartWidth: 400,
newBuildName: buildName,
shipId,
ship,
@@ -268,20 +264,6 @@ export default class OutfittingPage extends Page {
Router.replace(outfitURL(shipId, code, buildName));
}
- /**
- * Update dimenions from rendered DOM
- */
- _updateDimensions() {
- let elem = findDOMNode(this.refs.chartThird);
-
- if (elem) {
- this.setState({
- thirdChartWidth: findDOMNode(this.refs.chartThird).offsetWidth,
- halfChartWidth: findDOMNode(this.refs.chartThird).offsetWidth * 3 / 2
- });
- }
- }
-
/**
* Update state based on context changes
* @param {Object} nextProps Incoming/Next properties
@@ -297,22 +279,14 @@ export default class OutfittingPage extends Page {
* Add listeners when about to mount
*/
componentWillMount() {
- this.resizeListener = this.context.onWindowResize(this._updateDimensions);
document.addEventListener('keydown', this._keyDown);
}
- /**
- * Trigger DOM updates on mount
- */
- componentDidMount() {
- this._updateDimensions();
- }
-
/**
* Remove listeners on unmount
*/
componentWillUnmount() {
- this.resizeListener.remove();
+ document.removeEventListener('keydown', this._keyDown);
}
/**
@@ -360,7 +334,7 @@ export default class OutfittingPage extends Page {
let state = this.state,
{ language, termtip, tooltip, sizeRatio, onWindowResize } = this.context,
{ translate, units, formats } = language,
- { ship, code, savedCode, buildName, newBuildName, halfChartWidth, thirdChartWidth, sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = state,
+ { ship, code, savedCode, buildName, newBuildName, sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = state,
hide = tooltip.bind(null, null),
menu = this.props.currentMenu,
shipUpdated = this._shipUpdated,
@@ -446,6 +420,7 @@ export default class OutfittingPage extends Page {
+
{/* Tabbed subpages */}
-
);
}
}
diff --git a/src/app/shipyard/Calculations.js b/src/app/shipyard/Calculations.js
index 732328cc..8c7a5368 100644
--- a/src/app/shipyard/Calculations.js
+++ b/src/app/shipyard/Calculations.js
@@ -310,3 +310,307 @@ export function calcYaw(mass, baseYaw, thrusters, engpip, eng, boostFactor, boos
return result;
}
+ /**
+ * Calculate defence metrics
+ * @param {Object} ship The ship
+ * @param {Object} opponent The opponent ship
+ * @param {int} sys The pips to SYS
+ * @param {int} engagementrange The range between the ship and opponent
+ * @returns {Object} Defence metrics
+ */
+export function defenceMetrics(ship, opponent, sys, engagementrange) {
+ const sysResistance = this.sysResistance(sys);
+ const maxSysResistance = this.sysResistance(4);
+
+ // Obtain the opponent's sustained DPS on us for later damage calculations
+ const { shieldsdps, armoursdps } = this._sustainedDps(opponent, ship, engagementrange);
+
+ let shielddamage = {};
+ let shield = {};
+ const shieldGeneratorSlot = ship.findInternalByGroup('sg');
+ if (shieldGeneratorSlot && shieldGeneratorSlot.enabled && shieldGeneratorSlot.m) {
+ const shieldGenerator = shieldGeneratorSlot.m;
+
+ // Boosters
+ let boost = 1;
+ let boosterExplDmg = 1;
+ let boosterKinDmg = 1;
+ let boosterThermDmg = 1;
+ for (let slot of ship.hardpoints) {
+ if (slot.enabled && slot.m && slot.m.grp == 'sb') {
+ boost += slot.m.getShieldBoost();
+ boosterExplDmg = boosterExplDmg * (1 - slot.m.getExplosiveResistance());
+ boosterKinDmg = boosterKinDmg * (1 - slot.m.getKineticResistance());
+ boosterThermDmg = boosterThermDmg * (1 - slot.m.getThermalResistance());
+ }
+ }
+
+ // Calculate diminishing returns for boosters
+ boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5);
+ // Remove base shield generator strength
+ boost -= 1;
+ // Apply diminishing returns
+ boosterExplDmg = boosterExplDmg > 0.7 ? boosterExplDmg : 0.7 - (0.7 - boosterExplDmg) / 2;
+ boosterKinDmg = boosterKinDmg > 0.7 ? boosterKinDmg : 0.7 - (0.7 - boosterKinDmg) / 2;
+ boosterThermDmg = boosterThermDmg > 0.7 ? boosterThermDmg : 0.7 - (0.7 - boosterThermDmg) / 2;
+
+ const generatorStrength = this.shieldStrength(ship.hullMass, ship.baseShieldStrength, shieldGenerator, 1);
+ const boostersStrength = generatorStrength * boost;
+
+ // Recover time is the time taken to go from 0 to 50%. It includes a 16-second wait before shields start to recover
+ const shieldToRecover = (generatorStrength + boostersStrength) / 2;
+ const powerDistributor = ship.standard[4].m;
+ const sysRechargeRate = this.sysRechargeRate(powerDistributor, sys);
+
+ // Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes
+ // 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration
+ let capacitorDrain = (shieldGenerator.getBrokenRegenerationRate() * 0.6) - sysRechargeRate;
+ let capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain;
+
+ let recover = 16;
+ if (capacitorDrain <= 0 || shieldToRecover < capacitorLifetime * shieldGenerator.getBrokenRegenerationRate()) {
+ // We can recover the entire shield from the capacitor store
+ recover += shieldToRecover / shieldGenerator.getBrokenRegenerationRate();
+ } else {
+ // We can recover some of the shield from the capacitor store
+ recover += capacitorLifetime;
+ const remainingShieldToRecover = shieldToRecover - capacitorLifetime * shieldGenerator.getBrokenRegenerationRate();
+ if (sys === 0) {
+ // No system pips so will never recover shields
+ recover = Math.Inf;
+ } else {
+ // Recover remaining shields at the rate of the power distributor's recharge
+ recover += remainingShieldToRecover / (sysRechargeRate / 0.6);
+ }
+ }
+
+ // Recharge time is the time taken to go from 50% to 100%
+ const shieldToRecharge = (generatorStrength + boostersStrength) / 2;
+
+ // Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes
+ // 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration
+ capacitorDrain = (shieldGenerator.getRegenerationRate() * 0.6) - sysRechargeRate;
+ capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain;
+
+ let recharge = 0;
+ if (capacitorDrain <= 0 || shieldToRecharge < capacitorLifetime * shieldGenerator.getRegenerationRate()) {
+ // We can recharge the entire shield from the capacitor store
+ recharge += shieldToRecharge / shieldGenerator.getRegenerationRate();
+ } else {
+ // We can recharge some of the shield from the capacitor store
+ recharge += capacitorLifetime;
+ const remainingShieldToRecharge = shieldToRecharge - capacitorLifetime * shieldGenerator.getRegenerationRate();
+ if (sys === 0) {
+ // No system pips so will never recharge shields
+ recharge = Math.Inf;
+ } else {
+ // Recharge remaining shields at the rate of the power distributor's recharge
+ recharge += remainingShieldToRecharge / (sysRechargeRate / 0.6);
+ }
+ }
+
+ shield = {
+ generator: generatorStrength,
+ boosters: boostersStrength,
+ cells: ship.shieldCells,
+ total: generatorStrength + boostersStrength + ship.shieldCells,
+ recover,
+ recharge,
+ };
+
+ // Shield resistances have three components: the shield generator, the shield boosters and the SYS pips.
+ // We re-cast these as damage percentages
+ shield.absolute = {
+ generator: 1,
+ boosters: 1,
+ sys: 1 - sysResistance,
+ total: 1 - sysResistance,
+ max: 1 - maxSysResistance
+ };
+
+ shield.explosive = {
+ generator: 1 - shieldGenerator.getExplosiveResistance(),
+ boosters: boosterExplDmg,
+ sys: (1 - sysResistance),
+ total: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - sysResistance),
+ max: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - maxSysResistance)
+ };
+
+ shield.kinetic = {
+ generator: 1 - shieldGenerator.getKineticResistance(),
+ boosters: boosterKinDmg,
+ sys: (1 - sysResistance),
+ total: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - sysResistance),
+ max: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - maxSysResistance)
+ };
+
+ shield.thermal = {
+ generator: 1 - shieldGenerator.getThermalResistance(),
+ boosters: boosterThermDmg,
+ sys: (1 - sysResistance),
+ total: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - sysResistance),
+ max: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - maxSysResistance)
+ };
+
+ shielddamage.absolutesdps = shieldsdps.absolute *= shield.absolute.total;
+ shielddamage.explosivesdps = shieldsdps.explosive *= shield.explosive.total;
+ shielddamage.kineticsdps = shieldsdps.kinetic *= shield.kinetic.total;
+ shielddamage.thermalsdps = shieldsdps.thermal *= shield.thermal.total;
+ shielddamage.totalsdps = shielddamage.absolutesdps + shielddamage.explosivesdps + shielddamage.kineticsdps + shielddamage.thermalsdps;
+ }
+
+ // Armour from bulkheads
+ const armourBulkheads = ship.baseArmour + (ship.baseArmour * ship.bulkheads.m.getHullBoost());
+ let armourReinforcement = 0;
+
+ let moduleArmour = 0;
+ let moduleProtection = 1;
+
+ let hullExplDmg = 1;
+ let hullKinDmg = 1;
+ let hullThermDmg = 1;
+
+ // Armour from HRPs and module armour from MRPs
+ for (let slot of ship.internal) {
+ if (slot.m && slot.m.grp == 'hr') {
+ armourReinforcement += slot.m.getHullReinforcement();
+ // Hull boost for HRPs is applied against the ship's base armour
+ armourReinforcement += ship.baseArmour * slot.m.getModValue('hullboost') / 10000;
+
+ hullExplDmg = hullExplDmg * (1 - slot.m.getExplosiveResistance());
+ hullKinDmg = hullKinDmg * (1 - slot.m.getKineticResistance());
+ hullThermDmg = hullThermDmg * (1 - slot.m.getThermalResistance());
+ }
+ if (slot.m && slot.m.grp == 'mrp') {
+ moduleArmour += slot.m.getIntegrity();
+ moduleProtection = moduleProtection * (1 - slot.m.getProtection());
+ }
+ }
+ moduleProtection = 1 - moduleProtection;
+
+ // Apply diminishing returns
+ hullExplDmg = hullExplDmg > 0.7 ? hullExplDmg : 0.7 - (0.7 - hullExplDmg) / 2;
+ hullKinDmg = hullKinDmg > 0.7 ? hullKinDmg : 0.7 - (0.7 - hullKinDmg) / 2;
+ hullThermDmg = hullThermDmg > 0.7 ? hullThermDmg : 0.7 - (0.7 - hullThermDmg) / 2;
+
+ const armour = {
+ bulkheads: armourBulkheads,
+ reinforcement: armourReinforcement,
+ modulearmour: moduleArmour,
+ moduleprotection: moduleProtection,
+ total: armourBulkheads + armourReinforcement
+ };
+
+ // Armour resistances have two components: bulkheads and HRPs
+ // We re-cast these as damage percentages
+ armour.absolute = {
+ bulkheads: 1,
+ reinforcement: 1,
+ total: 1
+ };
+
+ armour.explosive = {
+ bulkheads: 1 - ship.bulkheads.m.getExplosiveResistance(),
+ reinforcement: hullExplDmg,
+ total: (1 - ship.bulkheads.m.getExplosiveResistance()) * hullExplDmg
+ };
+
+ armour.kinetic = {
+ bulkheads: 1 - ship.bulkheads.m.getKineticResistance(),
+ reinforcement: hullKinDmg,
+ total: (1 - ship.bulkheads.m.getKineticResistance()) * hullKinDmg
+ };
+
+ armour.thermal = {
+ bulkheads: 1 - ship.bulkheads.m.getThermalResistance(),
+ reinforcement: hullThermDmg,
+ total: (1 - ship.bulkheads.m.getThermalResistance()) * hullThermDmg
+ };
+
+ const armourdamage = {
+ absolutesdps: armoursdps.absolute *= armour.absolute.total,
+ explosivesdps: armoursdps.explosive *= armour.explosive.total,
+ kineticsdps: armoursdps.kinetic *= armour.kinetic.total,
+ thermalsdps: armoursdps.thermal *= armour.thermal.total
+ };
+ armourdamage.totalsdps = armourdamage.absolutesdps + armourdamage.explosivesdps + armourdamage.kineticsdps + armourdamage.thermalsdps;
+
+ return { shield, armour, shielddamage, armourdamage };
+}
+
+/**
+ * Calculate the resistance provided by SYS pips
+ * @param {integer} sys the value of the SYS pips
+ * @returns {integer} the resistance for the given pips
+ */
+export function sysResistance(sys) {
+ return Math.pow(sys,0.85) * 0.6 / Math.pow(4,0.85);
+}
+
+/**
+ * Obtain the recharge rate of the SYS capacitor of a power distributor given pips
+ * @param {Object} pd The power distributor
+ * @param {number} sys The number of pips to SYS
+ * @returns {number} The recharge rate in MJ/s
+ */
+export function sysRechargeRate(pd, sys) {
+ return pd.getSystemsRechargeRate() * Math.pow(sys, 1.1) / Math.pow(4, 1.1);
+}
+
+/**
+ * Calculate the sustained DPS for a ship at a given range, excluding resistances
+ * @param {Object} ship The ship
+ * @param {Object} opponent The opponent ship
+ * @param {int} engagementrange The range between the ship and opponent
+ * @returns {Object} Sustained DPS for shield and armour
+ */
+export function _sustainedDps(ship, opponent, engagementrange) {
+ const shieldsdps = {
+ absolute: 0,
+ explosive: 0,
+ kinetic: 0,
+ thermal: 0
+ };
+
+ const armoursdps = {
+ absolute: 0,
+ explosive: 0,
+ kinetic: 0,
+ thermal: 0
+ };
+
+ for (let i = 0; i < ship.hardpoints.length; i++) {
+ if (ship.hardpoints[i].m && ship.hardpoints[i].enabled && ship.hardpoints[i].maxClass > 0) {
+ const m = ship.hardpoints[i].m;
+ // Initial sustained DPS
+ let sDps = m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getDps();
+ // Take fall-off in to account
+ const falloff = m.getFalloff();
+ if (falloff && engagementrange > falloff) {
+ const dropoffRange = m.getRange() - falloff;
+ sDps *= 1 - Math.min((engagementrange - falloff) / dropoffRange, 1);
+ }
+ // Piercing/hardness modifier (for armour only)
+ const armourMultiple = m.getPiercing() >= opponent.hardness ? 1 : m.getPiercing() / opponent.hardness;
+ // Break out the damage according to type
+ if (m.getDamageDist().A) {
+ shieldsdps.absolute += sDps * m.getDamageDist().A;
+ armoursdps.absolute += sDps * m.getDamageDist().A * armourMultiple;
+ }
+ if (m.getDamageDist().E) {
+ shieldsdps.explosive += sDps * m.getDamageDist().E;
+ armoursdps.explosive += sDps * m.getDamageDist().E * armourMultiple;
+ }
+ if (m.getDamageDist().K) {
+ shieldsdps.kinetic += sDps * m.getDamageDist().K;
+ armoursdps.kinetic += sDps * m.getDamageDist().K * armourMultiple;
+ }
+ if (m.getDamageDist().T) {
+ shieldsdps.thermal += sDps * m.getDamageDist().T;
+ armoursdps.thermal += sDps * m.getDamageDist().T * armourMultiple;
+ }
+ }
+ }
+ return { shieldsdps, armoursdps };
+}
+
diff --git a/src/app/shipyard/Constants.js b/src/app/shipyard/Constants.js
index a273c578..4e2d8012 100755
--- a/src/app/shipyard/Constants.js
+++ b/src/app/shipyard/Constants.js
@@ -47,6 +47,7 @@ export const ModuleGroupToName = {
pcm: 'First Class Passenger Cabin',
pcq: 'Luxury Passenger Cabin',
cc: 'Collector Limpet Controller',
+ ss: 'Surface Scanner',
// Hard Points
bl: 'Beam Laser',
diff --git a/src/less/charts.less b/src/less/charts.less
index 556e15d8..22006184 100755
--- a/src/less/charts.less
+++ b/src/less/charts.less
@@ -44,6 +44,9 @@ svg {
.label, .text-tip {
text-transform: capitalize;
+ }
+
+ .x {
fill: @fg;
}
From eb83969015838c76caa34c2616f8a3b8f969c202 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Sat, 18 Mar 2017 13:42:32 +0000
Subject: [PATCH 40/87] Break out metric calculations
---
src/app/components/Defence.jsx | 61 +--
src/app/components/Offence.jsx | 477 ++++------------------
src/app/components/OutfittingSubpages.jsx | 5 +-
src/app/components/WeaponDamageChart.jsx | 23 +-
src/app/shipyard/Calculations.js | 96 ++++-
5 files changed, 181 insertions(+), 481 deletions(-)
diff --git a/src/app/components/Defence.jsx b/src/app/components/Defence.jsx
index 39b4a22f..0a1ba4fe 100644
--- a/src/app/components/Defence.jsx
+++ b/src/app/components/Defence.jsx
@@ -1,8 +1,6 @@
import React from 'react';
-import cn from 'classnames';
import TranslatedComponent from './TranslatedComponent';
import * as Calc from '../shipyard/Calculations';
-import { DamageAbsolute, DamageExplosive, DamageKinetic, DamageThermal } from './SvgIcons';
import PieChart from './PieChart';
import VerticalBarChart from './VerticalBarChart';
@@ -48,64 +46,7 @@ export default class Defence extends TranslatedComponent {
}
/**
- * Calculate the sustained DPS for a ship at a given range, excluding resistances
- * @param {Object} ship The ship
- * @param {Object} opponent The opponent ship
- * @param {int} engagementrange The range between the ship and opponent
- * @returns {Object} Sustained DPS for shield and armour
- */
- _calcSustainedDps(ship, opponent, engagementrange) {
- const shieldsdps = {
- absolute: 0,
- explosive: 0,
- kinetic: 0,
- thermal: 0
- };
-
- const armoursdps = {
- absolute: 0,
- explosive: 0,
- kinetic: 0,
- thermal: 0
- };
-
- for (let i = 0; i < ship.hardpoints.length; i++) {
- if (ship.hardpoints[i].m && ship.hardpoints[i].enabled && ship.hardpoints[i].maxClass > 0) {
- const m = ship.hardpoints[i].m;
- // Initial sustained DPS
- let sDps = m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getDps();
- // Take fall-off in to account
- const falloff = m.getFalloff();
- if (falloff && engagementrange > falloff) {
- const dropoffRange = m.getRange() - falloff;
- sDps *= 1 - Math.min((engagementrange - falloff) / dropoffRange, 1);
- }
- // Piercing/hardness modifier (for armour only)
- const armourMultiple = m.getPiercing() >= opponent.hardness ? 1 : m.getPiercing() / opponent.hardness;
- // Break out the damage according to type
- if (m.getDamageDist().A) {
- shieldsdps.absolute += sDps * m.getDamageDist().A;
- armoursdps.absolute += sDps * m.getDamageDist().A * armourMultiple;
- }
- if (m.getDamageDist().E) {
- shieldsdps.explosive += sDps * m.getDamageDist().E;
- armoursdps.explosive += sDps * m.getDamageDist().E * armourMultiple;
- }
- if (m.getDamageDist().K) {
- shieldsdps.kinetic += sDps * m.getDamageDist().K;
- armoursdps.kinetic += sDps * m.getDamageDist().K * armourMultiple;
- }
- if (m.getDamageDist().T) {
- shieldsdps.thermal += sDps * m.getDamageDist().T;
- armoursdps.thermal += sDps * m.getDamageDist().T * armourMultiple;
- }
- }
- }
- return { shieldsdps, armoursdps };
- }
-
- /**
- * Render shields
+ * Render defence
* @return {React.Component} contents
*/
render() {
diff --git a/src/app/components/Offence.jsx b/src/app/components/Offence.jsx
index 86307353..506b6fcd 100644
--- a/src/app/components/Offence.jsx
+++ b/src/app/components/Offence.jsx
@@ -1,13 +1,16 @@
import React from 'react';
-import cn from 'classnames';
import TranslatedComponent from './TranslatedComponent';
import * as Calc from '../shipyard/Calculations';
-import { DamageAbsolute, DamageExplosive, DamageKinetic, DamageThermal } from './SvgIcons';
import PieChart from './PieChart';
import VerticalBarChart from './VerticalBarChart';
/**
* Offence information
+ * Offence information consists of four panels:
+ * - textual information (time to drain cap, time to take down shields etc.)
+ * - breakdown of damage sources (pie chart)
+ * - comparison of shield resistances (table chart)
+ * - effective sustained DPS of weapons (bar chart)
*/
export default class Offence extends TranslatedComponent {
static propTypes = {
@@ -25,7 +28,7 @@ export default class Offence extends TranslatedComponent {
constructor(props) {
super(props);
- const { shield, armour, shielddamage, armourdamage } = this._calcMetrics(props.ship, props.opponent, props.sys, props.engagementrange);
+ const { shield, armour, shielddamage, armourdamage } = Calc.offenceMetrics(props.ship, props.opponent, props.eng, props.engagementrange);
this.state = { shield, armour, shielddamage, armourdamage };
}
@@ -36,419 +39,116 @@ export default class Offence extends TranslatedComponent {
*/
componentWillReceiveProps(nextProps) {
if (this.props.marker != nextProps.marker || this.props.sys != nextProps.sys) {
- const { shield, armour, shielddamage, armourdamage } = this._calcMetrics(nextProps.ship, nextProps.opponent, nextProps.sys, nextProps.engagementrange);
+ const { shield, armour, shielddamage, armourdamage } = Calc.offenceMetrics(nextProps.ship, nextProps.opponent, nextProps.wep, nextProps.engagementrange);
this.setState({ shield, armour, shielddamage, armourdamage });
- return true;
}
+ return true;
}
/**
- * Calculate the sustained DPS for a ship at a given range, excluding resistances
- * @param {Object} ship The ship
- * @param {Object} opponent The opponent ship
- * @param {int} engagementrange The range between the ship and opponent
- * @returns {Object} Sustained DPS for shield and armour
- */
- _calcSustainedDps(ship, opponent, engagementrange) {
- const shieldsdps = {
- absolute: 0,
- explosive: 0,
- kinetic: 0,
- thermal: 0
- };
-
- const armoursdps = {
- absolute: 0,
- explosive: 0,
- kinetic: 0,
- thermal: 0
- };
-
- for (let i = 0; i < ship.hardpoints.length; i++) {
- if (ship.hardpoints[i].m && ship.hardpoints[i].enabled && ship.hardpoints[i].maxClass > 0) {
- const m = ship.hardpoints[i].m;
- // Initial sustained DPS
- let sDps = m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getDps();
- // Take fall-off in to account
- const falloff = m.getFalloff();
- if (falloff && engagementrange > falloff) {
- const dropoffRange = m.getRange() - falloff;
- sDps *= 1 - Math.min((engagementrange - falloff) / dropoffRange, 1);
- }
- // Piercing/hardness modifier (for armour only)
- const armourMultiple = m.getPiercing() >= opponent.hardness ? 1 : m.getPiercing() / opponent.hardness;
- // Break out the damage according to type
- if (m.getDamageDist().A) {
- shieldsdps.absolute += sDps * m.getDamageDist().A;
- armoursdps.absolute += sDps * m.getDamageDist().A * armourMultiple;
- }
- if (m.getDamageDist().E) {
- shieldsdps.explosive += sDps * m.getDamageDist().E;
- armoursdps.explosive += sDps * m.getDamageDist().E * armourMultiple;
- }
- if (m.getDamageDist().K) {
- shieldsdps.kinetic += sDps * m.getDamageDist().K;
- armoursdps.kinetic += sDps * m.getDamageDist().K * armourMultiple;
- }
- if (m.getDamageDist().T) {
- shieldsdps.thermal += sDps * m.getDamageDist().T;
- armoursdps.thermal += sDps * m.getDamageDist().T * armourMultiple;
- }
- }
- }
- return { shieldsdps, armoursdps };
- }
-
- /**
- * Obtain the recharge rate of the SYS capacitor of a power distributor given pips
- * @param {Object} pd The power distributor
- * @param {number} sys The number of pips to SYS
- * @returns {number} The recharge rate in MJ/s
- */
- _calcSysRechargeRate(pd, sys) {
- return pd.getSystemsRechargeRate() * Math.pow(sys, 1.1) / Math.pow(4, 1.1);
- }
-
- /**
- * Calculate shield metrics
- * @param {Object} ship The ship
- * @param {Object} opponent The opponent ship
- * @param {int} sys The opponent ship
- * @param {int} engagementrange The range between the ship and opponent
- * @returns {Object} Shield metrics
- */
- _calcMetrics(ship, opponent, sys, engagementrange) {
- const sysResistance = this._calcSysResistance(sys);
- const maxSysResistance = this._calcSysResistance(4);
-
- // Obtain the opponent's sustained DPS on us for later damage calculations
- const { shieldsdps, armoursdps } = this._calcSustainedDps(opponent, ship, engagementrange);
-
- let shielddamage = {};
- let shield = {};
- const shieldGeneratorSlot = ship.findInternalByGroup('sg');
- if (shieldGeneratorSlot && shieldGeneratorSlot.enabled && shieldGeneratorSlot.m) {
- const shieldGenerator = shieldGeneratorSlot.m;
-
- // Boosters
- let boost = 1;
- let boosterExplDmg = 1;
- let boosterKinDmg = 1;
- let boosterThermDmg = 1;
- for (let slot of ship.hardpoints) {
- if (slot.enabled && slot.m && slot.m.grp == 'sb') {
- boost += slot.m.getShieldBoost();
- boosterExplDmg = boosterExplDmg * (1 - slot.m.getExplosiveResistance());
- boosterKinDmg = boosterKinDmg * (1 - slot.m.getKineticResistance());
- boosterThermDmg = boosterThermDmg * (1 - slot.m.getThermalResistance());
- }
- }
-
- // Calculate diminishing returns for boosters
- boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5);
- // Remove base shield generator strength
- boost -= 1;
- // Apply diminishing returns
- boosterExplDmg = boosterExplDmg > 0.7 ? boosterExplDmg : 0.7 - (0.7 - boosterExplDmg) / 2;
- boosterKinDmg = boosterKinDmg > 0.7 ? boosterKinDmg : 0.7 - (0.7 - boosterKinDmg) / 2;
- boosterThermDmg = boosterThermDmg > 0.7 ? boosterThermDmg : 0.7 - (0.7 - boosterThermDmg) / 2;
-
- const generatorStrength = Calc.shieldStrength(ship.hullMass, ship.baseShieldStrength, shieldGenerator, 1);
- const boostersStrength = generatorStrength * boost;
-
- // Recover time is the time taken to go from 0 to 50%. It includes a 16-second wait before shields start to recover
- const shieldToRecover = (generatorStrength + boostersStrength) / 2;
- const powerDistributor = ship.standard[4].m;
- const sysRechargeRate = this._calcSysRechargeRate(powerDistributor, sys);
-
- // Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes
- // 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration
- let capacitorDrain = (shieldGenerator.getBrokenRegenerationRate() * 0.6) - sysRechargeRate;
- let capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain;
-
- let recover = 16;
- if (capacitorDrain <= 0 || shieldToRecover < capacitorLifetime * shieldGenerator.getBrokenRegenerationRate()) {
- // We can recover the entire shield from the capacitor store
- recover += shieldToRecover / shieldGenerator.getBrokenRegenerationRate();
- } else {
- // We can recover some of the shield from the capacitor store
- recover += capacitorLifetime;
- const remainingShieldToRecover = shieldToRecover - capacitorLifetime * shieldGenerator.getBrokenRegenerationRate();
- if (sys === 0) {
- // No system pips so will never recover shields
- recover = Math.Inf;
- } else {
- // Recover remaining shields at the rate of the power distributor's recharge
- recover += remainingShieldToRecover / (sysRechargeRate / 0.6);
- }
- }
-
- // Recharge time is the time taken to go from 50% to 100%
- const shieldToRecharge = (generatorStrength + boostersStrength) / 2;
-
- // Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes
- // 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration
- capacitorDrain = (shieldGenerator.getRegenerationRate() * 0.6) - sysRechargeRate;
- capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain;
-
- let recharge = 0;
- if (capacitorDrain <= 0 || shieldToRecharge < capacitorLifetime * shieldGenerator.getRegenerationRate()) {
- // We can recharge the entire shield from the capacitor store
- recharge += shieldToRecharge / shieldGenerator.getRegenerationRate();
- } else {
- // We can recharge some of the shield from the capacitor store
- recharge += capacitorLifetime;
- const remainingShieldToRecharge = shieldToRecharge - capacitorLifetime * shieldGenerator.getRegenerationRate();
- if (sys === 0) {
- // No system pips so will never recharge shields
- recharge = Math.Inf;
- } else {
- // Recharge remaining shields at the rate of the power distributor's recharge
- recharge += remainingShieldToRecharge / (sysRechargeRate / 0.6);
- }
- }
-
- shield = {
- generator: generatorStrength,
- boosters: boostersStrength,
- cells: ship.shieldCells,
- total: generatorStrength + boostersStrength + ship.shieldCells,
- recover,
- recharge,
- };
-
- // Shield resistances have three components: the shield generator, the shield boosters and the SYS pips.
- // We re-cast these as damage percentages
- shield.absolute = {
- generator: 1,
- boosters: 1,
- sys: 1 - sysResistance,
- total: 1 - sysResistance,
- max: 1 - maxSysResistance
- };
-
- shield.explosive = {
- generator: 1 - shieldGenerator.getExplosiveResistance(),
- boosters: boosterExplDmg,
- sys: (1 - sysResistance),
- total: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - sysResistance),
- max: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - maxSysResistance)
- };
-
- shield.kinetic = {
- generator: 1 - shieldGenerator.getKineticResistance(),
- boosters: boosterKinDmg,
- sys: (1 - sysResistance),
- total: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - sysResistance),
- max: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - maxSysResistance)
- };
-
- shield.thermal = {
- generator: 1 - shieldGenerator.getThermalResistance(),
- boosters: boosterThermDmg,
- sys: (1 - sysResistance),
- total: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - sysResistance),
- max: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - maxSysResistance)
- };
-
- shielddamage.absolutesdps = shieldsdps.absolute *= shield.absolute.total;
- shielddamage.explosivesdps = shieldsdps.explosive *= shield.explosive.total;
- shielddamage.kineticsdps = shieldsdps.kinetic *= shield.kinetic.total;
- shielddamage.thermalsdps = shieldsdps.thermal *= shield.thermal.total;
- shielddamage.totalsdps = shielddamage.absolutesdps + shielddamage.explosivesdps + shielddamage.kineticsdps + shielddamage.thermalsdps;
- }
-
- // Armour from bulkheads
- const armourBulkheads = ship.baseArmour + (ship.baseArmour * ship.bulkheads.m.getHullBoost());
- let armourReinforcement = 0;
-
- let moduleArmour = 0;
- let moduleProtection = 1;
-
- let hullExplDmg = 1;
- let hullKinDmg = 1;
- let hullThermDmg = 1;
-
- // Armour from HRPs and module armour from MRPs
- for (let slot of ship.internal) {
- if (slot.m && slot.m.grp == 'hr') {
- armourReinforcement += slot.m.getHullReinforcement();
- // Hull boost for HRPs is applied against the ship's base armour
- armourReinforcement += ship.baseArmour * slot.m.getModValue('hullboost') / 10000;
-
- hullExplDmg = hullExplDmg * (1 - slot.m.getExplosiveResistance());
- hullKinDmg = hullKinDmg * (1 - slot.m.getKineticResistance());
- hullThermDmg = hullThermDmg * (1 - slot.m.getThermalResistance());
- }
- if (slot.m && slot.m.grp == 'mrp') {
- moduleArmour += slot.m.getIntegrity();
- moduleProtection = moduleProtection * (1 - slot.m.getProtection());
- }
- }
- moduleProtection = 1 - moduleProtection;
-
- // Apply diminishing returns
- hullExplDmg = hullExplDmg > 0.7 ? hullExplDmg : 0.7 - (0.7 - hullExplDmg) / 2;
- hullKinDmg = hullKinDmg > 0.7 ? hullKinDmg : 0.7 - (0.7 - hullKinDmg) / 2;
- hullThermDmg = hullThermDmg > 0.7 ? hullThermDmg : 0.7 - (0.7 - hullThermDmg) / 2;
-
- const armour = {
- bulkheads: armourBulkheads,
- reinforcement: armourReinforcement,
- modulearmour: moduleArmour,
- moduleprotection: moduleProtection,
- total: armourBulkheads + armourReinforcement
- };
-
- // Armour resistances have two components: bulkheads and HRPs
- // We re-cast these as damage percentages
- armour.absolute = {
- bulkheads: 1,
- reinforcement: 1,
- total: 1
- };
-
- armour.explosive = {
- bulkheads: 1 - ship.bulkheads.m.getExplosiveResistance(),
- reinforcement: hullExplDmg,
- total: (1 - ship.bulkheads.m.getExplosiveResistance()) * hullExplDmg
- };
-
- armour.kinetic = {
- bulkheads: 1 - ship.bulkheads.m.getKineticResistance(),
- reinforcement: hullKinDmg,
- total: (1 - ship.bulkheads.m.getKineticResistance()) * hullKinDmg
- };
-
- armour.thermal = {
- bulkheads: 1 - ship.bulkheads.m.getThermalResistance(),
- reinforcement: hullThermDmg,
- total: (1 - ship.bulkheads.m.getThermalResistance()) * hullThermDmg
- };
-
- const armourdamage = {
- absolutesdps: armoursdps.absolute *= armour.absolute.total,
- explosivesdps: armoursdps.explosive *= armour.explosive.total,
- kineticsdps: armoursdps.kinetic *= armour.kinetic.total,
- thermalsdps: armoursdps.thermal *= armour.thermal.total
- };
- armourdamage.totalsdps = armourdamage.absolutesdps + armourdamage.explosivesdps + armourdamage.kineticsdps + armourdamage.thermalsdps;
-
- return { shield, armour, shielddamage, armourdamage };
- }
-
- /**
- * Calculate the resistance provided by SYS pips
- * @param {integer} sys the value of the SYS pips
- * @returns {integer} the resistance for the given pips
- */
- _calcSysResistance(sys) {
- return Math.pow(sys,0.85) * 0.6 / Math.pow(4,0.85);
- }
-
- /**
- * Render shields
+ * Render offence
* @return {React.Component} contents
*/
render() {
- const { ship, sys } = this.props;
+ const { ship, wep } = this.props;
const { language, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
- const { shield, armour, shielddamage, armourdamage } = this.state;
+// const { shield, armour, shielddamage, armourdamage } = this.state;
- const shieldSourcesData = [];
- const effectiveShieldData = [];
- const shieldDamageTakenData = [];
- const shieldTooltipDetails = [];
- const shieldAbsoluteTooltipDetails = [];
- const shieldExplosiveTooltipDetails = [];
- const shieldKineticTooltipDetails = [];
- const shieldThermalTooltipDetails = [];
- let maxEffectiveShield = 0;
- if (shield.total) {
- if (Math.round(shield.generator) > 0) shieldSourcesData.push({ value: Math.round(shield.generator), label: translate('generator') });
- if (Math.round(shield.boosters) > 0) shieldSourcesData.push({ value: Math.round(shield.boosters), label: translate('boosters') });
- if (Math.round(shield.cells) > 0) shieldSourcesData.push({ value: Math.round(shield.cells), label: translate('cells') });
+// const shieldSourcesData = [];
+// const effectiveShieldData = [];
+// const shieldDamageTakenData = [];
+// const shieldTooltipDetails = [];
+// const shieldAbsoluteTooltipDetails = [];
+// const shieldExplosiveTooltipDetails = [];
+// const shieldKineticTooltipDetails = [];
+// const shieldThermalTooltipDetails = [];
+// let maxEffectiveShield = 0;
+// if (shield.total) {
+// shieldSourcesData.push({ value: Math.round(shield.generator), label: translate('generator') });
+// shieldSourcesData.push({ value: Math.round(shield.boosters), label: translate('boosters') });
+// shieldSourcesData.push({ value: Math.round(shield.cells), label: translate('cells') });
- if (Math.round(shield.generator) > 0) shieldTooltipDetails.push({translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}
);
- if (Math.round(shield.boosters) > 0) shieldTooltipDetails.push({translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}
);
- if (Math.round(shield.cells) > 0) shieldTooltipDetails.push({translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}
);
+// shieldTooltipDetails.push({translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}
);
+// shieldTooltipDetails.push({translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}
);
+// shieldTooltipDetails.push({translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}
);
- shieldAbsoluteTooltipDetails.push({translate('generator') + ' ' + formats.pct1(shield.absolute.generator)}
);
- shieldAbsoluteTooltipDetails.push({translate('boosters') + ' ' + formats.pct1(shield.absolute.boosters)}
);
- shieldAbsoluteTooltipDetails.push({translate('power distributor') + ' ' + formats.pct1(shield.absolute.sys)}
);
+// shieldAbsoluteTooltipDetails.push({translate('generator') + ' ' + formats.pct1(shield.absolute.generator)}
);
+// shieldAbsoluteTooltipDetails.push({translate('boosters') + ' ' + formats.pct1(shield.absolute.boosters)}
);
+// shieldAbsoluteTooltipDetails.push({translate('power distributor') + ' ' + formats.pct1(shield.absolute.sys)}
);
- shieldExplosiveTooltipDetails.push({translate('generator') + ' ' + formats.pct1(shield.explosive.generator)}
);
- shieldExplosiveTooltipDetails.push({translate('boosters') + ' ' + formats.pct1(shield.explosive.boosters)}
);
- shieldExplosiveTooltipDetails.push({translate('power distributor') + ' ' + formats.pct1(shield.explosive.sys)}
);
+// shieldExplosiveTooltipDetails.push({translate('generator') + ' ' + formats.pct1(shield.explosive.generator)}
);
+// shieldExplosiveTooltipDetails.push({translate('boosters') + ' ' + formats.pct1(shield.explosive.boosters)}
);
+// shieldExplosiveTooltipDetails.push({translate('power distributor') + ' ' + formats.pct1(shield.explosive.sys)}
);
- shieldKineticTooltipDetails.push({translate('generator') + ' ' + formats.pct1(shield.kinetic.generator)}
);
- shieldKineticTooltipDetails.push({translate('boosters') + ' ' + formats.pct1(shield.kinetic.boosters)}
);
- shieldKineticTooltipDetails.push({translate('power distributor') + ' ' + formats.pct1(shield.kinetic.sys)}
);
+// shieldKineticTooltipDetails.push({translate('generator') + ' ' + formats.pct1(shield.kinetic.generator)}
);
+// shieldKineticTooltipDetails.push({translate('boosters') + ' ' + formats.pct1(shield.kinetic.boosters)}
);
+// shieldKineticTooltipDetails.push({translate('power distributor') + ' ' + formats.pct1(shield.kinetic.sys)}
);
- shieldThermalTooltipDetails.push({translate('generator') + ' ' + formats.pct1(shield.thermal.generator)}
);
- shieldThermalTooltipDetails.push({translate('boosters') + ' ' + formats.pct1(shield.thermal.boosters)}
);
- shieldThermalTooltipDetails.push({translate('power distributor') + ' ' + formats.pct1(shield.thermal.sys)}
);
+// shieldThermalTooltipDetails.push({translate('generator') + ' ' + formats.pct1(shield.thermal.generator)}
);
+// shieldThermalTooltipDetails.push({translate('boosters') + ' ' + formats.pct1(shield.thermal.boosters)}
);
+// shieldThermalTooltipDetails.push({translate('power distributor') + ' ' + formats.pct1(shield.thermal.sys)}
);
- const effectiveAbsoluteShield = shield.total / shield.absolute.total;
- effectiveShieldData.push({ value: Math.round(effectiveAbsoluteShield), label: translate('absolute') });
- const effectiveExplosiveShield = shield.total / shield.explosive.total;
- effectiveShieldData.push({ value: Math.round(effectiveExplosiveShield), label: translate('explosive') });
- const effectiveKineticShield = shield.total / shield.kinetic.total;
- effectiveShieldData.push({ value: Math.round(effectiveKineticShield), label: translate('kinetic') });
- const effectiveThermalShield = shield.total / shield.thermal.total;
- effectiveShieldData.push({ value: Math.round(effectiveThermalShield), label: translate('thermal') });
+// const effectiveAbsoluteShield = shield.total / shield.absolute.total;
+// effectiveShieldData.push({ value: Math.round(effectiveAbsoluteShield), label: translate('absolute') });
+// const effectiveExplosiveShield = shield.total / shield.explosive.total;
+// effectiveShieldData.push({ value: Math.round(effectiveExplosiveShield), label: translate('explosive') });
+// const effectiveKineticShield = shield.total / shield.kinetic.total;
+// effectiveShieldData.push({ value: Math.round(effectiveKineticShield), label: translate('kinetic') });
+// const effectiveThermalShield = shield.total / shield.thermal.total;
+// effectiveShieldData.push({ value: Math.round(effectiveThermalShield), label: translate('thermal') });
- shieldDamageTakenData.push({ value: Math.round(shield.absolute.total * 100), label: translate('absolute') });
- shieldDamageTakenData.push({ value: Math.round(shield.explosive.total * 100), label: translate('explosive') });
- shieldDamageTakenData.push({ value: Math.round(shield.kinetic.total * 100), label: translate('kinetic') });
- shieldDamageTakenData.push({ value: Math.round(shield.thermal.total * 100), label: translate('thermal') });
+// shieldDamageTakenData.push({ value: Math.round(shield.absolute.total * 100), label: translate('absolute') });
+// shieldDamageTakenData.push({ value: Math.round(shield.explosive.total * 100), label: translate('explosive') });
+// shieldDamageTakenData.push({ value: Math.round(shield.kinetic.total * 100), label: translate('kinetic') });
+// shieldDamageTakenData.push({ value: Math.round(shield.thermal.total * 100), label: translate('thermal') });
- maxEffectiveShield = Math.max(shield.total / shield.absolute.max, shield.total / shield.explosive.max, shield.total / shield.kinetic.max, shield.total / shield.thermal.max);
- }
+// maxEffectiveShield = Math.max(shield.total / shield.absolute.max, shield.total / shield.explosive.max, shield.total / shield.kinetic.max, shield.total / shield.thermal.max);
+// }
- const armourSourcesData = [];
- if (Math.round(armour.bulkheads) > 0) armourSourcesData.push({ value: Math.round(armour.bulkheads), label: translate('bulkheads') });
- if (Math.round(armour.reinforcement) > 0) armourSourcesData.push({ value: Math.round(armour.reinforcement), label: translate('reinforcement') });
+// const armourSourcesData = [];
+// armourSourcesData.push({ value: Math.round(armour.bulkheads), label: translate('bulkheads') });
+// armourSourcesData.push({ value: Math.round(armour.reinforcement), label: translate('reinforcement') });
- const armourTooltipDetails = [];
- if (armour.bulkheads > 0) armourTooltipDetails.push({translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}
);
- if (armour.reinforcement > 0) armourTooltipDetails.push({translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}
);
+// const armourTooltipDetails = [];
+// armourTooltipDetails.push({translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}
);
+// armourTooltipDetails.push({translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}
);
- const armourAbsoluteTooltipDetails = [];
- armourAbsoluteTooltipDetails.push({translate('bulkheads') + ' ' + formats.pct1(armour.absolute.bulkheads)}
);
- armourAbsoluteTooltipDetails.push({translate('reinforcement') + ' ' + formats.pct1(armour.absolute.reinforcement)}
);
+// const armourAbsoluteTooltipDetails = [];
+// armourAbsoluteTooltipDetails.push({translate('bulkheads') + ' ' + formats.pct1(armour.absolute.bulkheads)}
);
+// armourAbsoluteTooltipDetails.push({translate('reinforcement') + ' ' + formats.pct1(armour.absolute.reinforcement)}
);
- const armourExplosiveTooltipDetails = [];
- armourExplosiveTooltipDetails.push({translate('bulkheads') + ' ' + formats.pct1(armour.explosive.bulkheads)}
);
- armourExplosiveTooltipDetails.push({translate('reinforcement') + ' ' + formats.pct1(armour.explosive.reinforcement)}
);
+// const armourExplosiveTooltipDetails = [];
+// armourExplosiveTooltipDetails.push({translate('bulkheads') + ' ' + formats.pct1(armour.explosive.bulkheads)}
);
+// armourExplosiveTooltipDetails.push({translate('reinforcement') + ' ' + formats.pct1(armour.explosive.reinforcement)}
);
- const armourKineticTooltipDetails = [];
- armourKineticTooltipDetails.push({translate('bulkheads') + ' ' + formats.pct1(armour.kinetic.bulkheads)}
);
- armourKineticTooltipDetails.push({translate('reinforcement') + ' ' + formats.pct1(armour.kinetic.reinforcement)}
);
+// const armourKineticTooltipDetails = [];
+// armourKineticTooltipDetails.push({translate('bulkheads') + ' ' + formats.pct1(armour.kinetic.bulkheads)}
);
+// armourKineticTooltipDetails.push({translate('reinforcement') + ' ' + formats.pct1(armour.kinetic.reinforcement)}
);
- const armourThermalTooltipDetails = [];
- armourThermalTooltipDetails.push({translate('bulkheads') + ' ' + formats.pct1(armour.thermal.bulkheads)}
);
- armourThermalTooltipDetails.push({translate('reinforcement') + ' ' + formats.pct1(armour.thermal.reinforcement)}
);
+// const armourThermalTooltipDetails = [];
+// armourThermalTooltipDetails.push({translate('bulkheads') + ' ' + formats.pct1(armour.thermal.bulkheads)}
);
+// armourThermalTooltipDetails.push({translate('reinforcement') + ' ' + formats.pct1(armour.thermal.reinforcement)}
);
- const effectiveArmourData = [];
- const effectiveAbsoluteArmour = armour.total / armour.absolute.total;
- effectiveArmourData.push({ value: Math.round(effectiveAbsoluteArmour), label: translate('absolute') });
- const effectiveExplosiveArmour = armour.total / armour.explosive.total;
- effectiveArmourData.push({ value: Math.round(effectiveExplosiveArmour), label: translate('explosive') });
- const effectiveKineticArmour = armour.total / armour.kinetic.total;
- effectiveArmourData.push({ value: Math.round(effectiveKineticArmour), label: translate('kinetic') });
- const effectiveThermalArmour = armour.total / armour.thermal.total;
- effectiveArmourData.push({ value: Math.round(effectiveThermalArmour), label: translate('thermal') });
+// const effectiveArmourData = [];
+// const effectiveAbsoluteArmour = armour.total / armour.absolute.total;
+// effectiveArmourData.push({ value: Math.round(effectiveAbsoluteArmour), label: translate('absolute') });
+// const effectiveExplosiveArmour = armour.total / armour.explosive.total;
+// effectiveArmourData.push({ value: Math.round(effectiveExplosiveArmour), label: translate('explosive') });
+// const effectiveKineticArmour = armour.total / armour.kinetic.total;
+// effectiveArmourData.push({ value: Math.round(effectiveKineticArmour), label: translate('kinetic') });
+// const effectiveThermalArmour = armour.total / armour.thermal.total;
+// effectiveArmourData.push({ value: Math.round(effectiveThermalArmour), label: translate('thermal') });
- const armourDamageTakenData = [];
- armourDamageTakenData.push({ value: Math.round(armour.absolute.total * 100), label: translate('absolute') });
- armourDamageTakenData.push({ value: Math.round(armour.explosive.total * 100), label: translate('explosive') });
- armourDamageTakenData.push({ value: Math.round(armour.kinetic.total * 100), label: translate('kinetic') });
- armourDamageTakenData.push({ value: Math.round(armour.thermal.total * 100), label: translate('thermal') });
+// const armourDamageTakenData = [];
+// armourDamageTakenData.push({ value: Math.round(armour.absolute.total * 100), label: translate('absolute') });
+// armourDamageTakenData.push({ value: Math.round(armour.explosive.total * 100), label: translate('explosive') });
+// armourDamageTakenData.push({ value: Math.round(armour.kinetic.total * 100), label: translate('kinetic') });
+// armourDamageTakenData.push({ value: Math.round(armour.thermal.total * 100), label: translate('thermal') });
return (
-
+
+ {/*
{shield.total ?
{translate('shield metrics')}
@@ -475,7 +175,7 @@ export default class Offence extends TranslatedComponent {
{translate('armour metrics')}
{armourTooltipDetails} )} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw armour strength')}
{formats.int(armour.total)}
-
{translate('PHRASE_TIME_TO_LOSE_ARMOUR')} {armourdamage.totalsdps == 0 ? translate('infinity') : formats.time(armour.total / armourdamage.totalsdps)}
+
{translate('PHRASE_TIME_TO_LOSE_ARMOUR')} {armourdamage.totalsdps == 0 ? translate('ever') : formats.time(armour.total / armourdamage.totalsdps)}
{translate('raw module armour')} {formats.int(armour.modulearmour)}
{translate('PHRASE_MODULE_PROTECTION_EXTERNAL')} {formats.pct1(armour.moduleprotection / 2)}
{translate('PHRASE_MODULE_PROTECTION_INTERNAL')} {formats.pct1(armour.moduleprotection)}
@@ -493,6 +193,7 @@ export default class Offence extends TranslatedComponent {
{translate('effective armour')}
+ */}
);
}
}
diff --git a/src/app/components/OutfittingSubpages.jsx b/src/app/components/OutfittingSubpages.jsx
index 6a760833..18f7ac47 100644
--- a/src/app/components/OutfittingSubpages.jsx
+++ b/src/app/components/OutfittingSubpages.jsx
@@ -10,6 +10,7 @@ import CostSection from './CostSection';
import EngineProfile from './EngineProfile';
import FSDProfile from './FSDProfile';
import Movement from './Movement';
+import Offence from './Offence';
import Defence from './Defence';
import WeaponDamageChart from './WeaponDamageChart';
@@ -117,8 +118,10 @@ export default class OutfittingSubpages extends TranslatedComponent {
_offenceTab() {
const { ship, sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild } = this.props;
+ const marker = `${ship.toString()}:${opponent.name}:${opponentBuild}:${engagementRange}`;
+
return
-
Offence goes here
+
;
}
diff --git a/src/app/components/WeaponDamageChart.jsx b/src/app/components/WeaponDamageChart.jsx
index 90121e5b..990225aa 100644
--- a/src/app/components/WeaponDamageChart.jsx
+++ b/src/app/components/WeaponDamageChart.jsx
@@ -100,13 +100,12 @@ export default class WeaponDamageChart extends TranslatedComponent {
*/
_calcMaxSDps(ship, opponent, hull) {
// Additional information to allow effectiveness calculations
- const { shield, armour, shielddamage, armourdamage } = Calc.defenceMetrics(opponent, ship, 4, 0);
-
+ const defence = hull ? Calc.armourMetrics(opponent) : Calc.shieldMetrics(opponent, 4);
let maxSDps = 0;
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
const m = ship.hardpoints[i].m;
- const thisSDps = this._calcWeaponSDps(ship, m, opponent, hull, shield, armour, 0);
+ const thisSDps = this._calcWeaponSDps(ship, m, opponent, defence, 0);
if (thisSDps > maxSDps) {
maxSDps = thisSDps;
}
@@ -156,14 +155,14 @@ export default class WeaponDamageChart extends TranslatedComponent {
*/
_calcSDps(ship, weaponNames, opponent, hull, engagementRange) {
// Additional information to allow effectiveness calculations
- const { shield, armour, shielddamage, armourdamage } = Calc.defenceMetrics(opponent, ship, 4, engagementRange);
+ const defence = hull ? Calc.armourMetrics(opponent) : Calc.shieldMetrics(opponent, 4);
let results = {};
let weaponNum = 0;
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
const m = ship.hardpoints[i].m;
- results[weaponNames[weaponNum++]] = this._calcWeaponSDps(ship, m, opponent, hull, shield, armour, engagementRange);
+ results[weaponNames[weaponNum++]] = this._calcWeaponSDps(ship, m, opponent, defence, engagementRange);
}
}
return results;
@@ -174,13 +173,11 @@ export default class WeaponDamageChart extends TranslatedComponent {
* @param {Object} ship The ship that will deal the damage
* @param {Object} m The weapon that will deal the damage
* @param {Object} opponent The ship against which damage will be dealt
- * @param {boolean} hull True if hitting hull
- * @param {Object} shield Shield defence metrics
- * @param {Object} armour Armour defence metrics
+ * @param {Object} defence defence metrics (either shield or hull)
* @param {Object} engagementRange The engagement range
* @return {object} Returns the sustained DPS for the weapon
*/
- _calcWeaponSDps(ship, m, opponent, hull, shield, armour, engagementRange) {
+ _calcWeaponSDps(ship, m, opponent, defence, engagementRange) {
let falloff = 1;
if (m.getFalloff()) {
// Calculate the falloff % due to range
@@ -199,16 +196,16 @@ export default class WeaponDamageChart extends TranslatedComponent {
let effectiveness = 0;
if (m.getDamageDist().E) {
- effectiveness += m.getDamageDist().E * (hull ? armour.explosive.total : shield.explosive.total);
+ effectiveness += m.getDamageDist().E * defence.explosive.total;
}
if (m.getDamageDist().K) {
- effectiveness += m.getDamageDist().K * (hull ? armour.kinetic.total : shield.kinetic.total);
+ effectiveness += m.getDamageDist().K * defence.kinetic.total;
}
if (m.getDamageDist().T) {
- effectiveness += m.getDamageDist().T * (hull ? armour.thermal.total : shield.thermal.total);
+ effectiveness += m.getDamageDist().T * defence.thermal.total;
}
if (m.getDamageDist().A) {
- effectiveness += m.getDamageDist().A * (hull ? armour.absolute.total : shield.absolute.total);
+ effectiveness += m.getDamageDist().A * defence.absolute.total;
}
// Return the final effective SDPS
diff --git a/src/app/shipyard/Calculations.js b/src/app/shipyard/Calculations.js
index 8c7a5368..b27be63e 100644
--- a/src/app/shipyard/Calculations.js
+++ b/src/app/shipyard/Calculations.js
@@ -310,23 +310,18 @@ export function calcYaw(mass, baseYaw, thrusters, engpip, eng, boostFactor, boos
return result;
}
- /**
- * Calculate defence metrics
- * @param {Object} ship The ship
- * @param {Object} opponent The opponent ship
- * @param {int} sys The pips to SYS
- * @param {int} engagementrange The range between the ship and opponent
- * @returns {Object} Defence metrics
- */
-export function defenceMetrics(ship, opponent, sys, engagementrange) {
+/**
+ * Calculate shield metrics
+ * @param {Object} ship The ship
+ * @param {int} sys The pips to SYS
+ * @returns {Object} Shield metrics
+ */
+export function shieldMetrics(ship, sys) {
const sysResistance = this.sysResistance(sys);
const maxSysResistance = this.sysResistance(4);
- // Obtain the opponent's sustained DPS on us for later damage calculations
- const { shieldsdps, armoursdps } = this._sustainedDps(opponent, ship, engagementrange);
-
- let shielddamage = {};
let shield = {};
+
const shieldGeneratorSlot = ship.findInternalByGroup('sg');
if (shieldGeneratorSlot && shieldGeneratorSlot.enabled && shieldGeneratorSlot.m) {
const shieldGenerator = shieldGeneratorSlot.m;
@@ -451,14 +446,17 @@ export function defenceMetrics(ship, opponent, sys, engagementrange) {
total: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - sysResistance),
max: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - maxSysResistance)
};
-
- shielddamage.absolutesdps = shieldsdps.absolute *= shield.absolute.total;
- shielddamage.explosivesdps = shieldsdps.explosive *= shield.explosive.total;
- shielddamage.kineticsdps = shieldsdps.kinetic *= shield.kinetic.total;
- shielddamage.thermalsdps = shieldsdps.thermal *= shield.thermal.total;
- shielddamage.totalsdps = shielddamage.absolutesdps + shielddamage.explosivesdps + shielddamage.kineticsdps + shielddamage.thermalsdps;
}
+ return shield;
+}
+
+/**
+ * Calculate armour metrics
+ * @param {Object} ship The ship
+ * @returns {Object} Armour metrics
+ */
+export function armourMetrics(ship) {
// Armour from bulkheads
const armourBulkheads = ship.baseArmour + (ship.baseArmour * ship.bulkheads.m.getHullBoost());
let armourReinforcement = 0;
@@ -527,6 +525,35 @@ export function defenceMetrics(ship, opponent, sys, engagementrange) {
total: (1 - ship.bulkheads.m.getThermalResistance()) * hullThermDmg
};
+ return armour;
+}
+
+/**
+ * Calculate defence metrics for a ship
+ * @param {Object} ship The ship
+ * @param {Object} opponent The opponent ship
+ * @param {int} sys The pips to SYS
+ * @param {int} engagementrange The range between the ship and opponent
+ * @returns {Object} Defence metrics
+ */
+export function defenceMetrics(ship, opponent, sys, engagementrange) {
+ // Obtain the shield metrics
+ const shield = this.shieldMetrics(ship, sys);
+
+ // Obtain the armour metrics
+ const armour = this.armourMetrics(ship);
+
+ // Obtain the opponent's sustained DPS on us
+ const { shieldsdps, armoursdps } = this._sustainedDps(opponent, ship, engagementrange);
+
+ const shielddamage = shield.generatorStrength ? {
+ absolutesdps: shieldsdps.absolute *= shield.absolute.total,
+ explosivesdps: shieldsdps.explosive *= shield.explosive.total,
+ kineticsdps: shieldsdps.kinetic *= shield.kinetic.total,
+ thermalsdps: shieldsdps.thermal *= shield.thermal.total,
+ totalsdps: shielddamage.absolutesdps + shielddamage.explosivesdps + shielddamage.kineticsdps + shielddamage.thermalsdps
+ } : {} ;
+
const armourdamage = {
absolutesdps: armoursdps.absolute *= armour.absolute.total,
explosivesdps: armoursdps.explosive *= armour.explosive.total,
@@ -538,6 +565,37 @@ export function defenceMetrics(ship, opponent, sys, engagementrange) {
return { shield, armour, shielddamage, armourdamage };
}
+/**
+ * Calculate offence metrics for a ship
+ * @param {Object} ship The ship
+ * @param {Object} opponent The opponent ship
+ * @param {int} wep The pips to WEP
+ * @param {int} engagementrange The range between the ship and opponent
+ * @returns {Object} Offence metrics
+ */
+export function offenceMetrics(ship, opponent, wep, engagementrange) {
+
+ // Per-weapon and total damage to armour
+ const armourdamage = {};
+
+ // Obtain the opponent's shield and armour metrics
+ const opponentShields = this.shieldMetrics(opponent, 4);
+ const opponentArmour = this.armourMetrics(opponent);
+
+ // Per-weapon and total damage to shields
+ const shielddamage = opponentShields.generatorStrength ? {
+ absolute: {
+ weapon1: 10,
+ weapon2: 10,
+ weapon3: 10,
+ weapon4: 10,
+ total: 40
+ }
+ } : {};
+
+ return { shielddamage, armourdamage };
+}
+
/**
* Calculate the resistance provided by SYS pips
* @param {integer} sys the value of the SYS pips
From 49a076fd9eb3a9913a030f9d28b97129cac70b72 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Sat, 18 Mar 2017 23:46:46 +0000
Subject: [PATCH 41/87] Updates
---
src/app/components/Defence.jsx | 26 +--
src/app/components/LineChart.jsx | 2 +-
src/app/components/Offence.jsx | 174 ++++++++++++------
src/app/components/VerticalBarChart.jsx | 12 +-
src/app/components/WeaponDamageChart.jsx | 97 +++-------
src/app/shipyard/Calculations.js | 220 ++++++++++++++++++-----
6 files changed, 342 insertions(+), 189 deletions(-)
diff --git a/src/app/components/Defence.jsx b/src/app/components/Defence.jsx
index 0a1ba4fe..902e1bea 100644
--- a/src/app/components/Defence.jsx
+++ b/src/app/components/Defence.jsx
@@ -69,9 +69,9 @@ export default class Defence extends TranslatedComponent {
shieldSourcesData.push({ value: Math.round(shield.boosters), label: translate('boosters') });
shieldSourcesData.push({ value: Math.round(shield.cells), label: translate('cells') });
- shieldTooltipDetails.push({translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}
);
- shieldTooltipDetails.push({translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}
);
- shieldTooltipDetails.push({translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}
);
+ if (shield.generator > 0) shieldTooltipDetails.push({translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}
);
+ if (shield.boosters > 0) shieldTooltipDetails.push({translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}
);
+ if (shield.cells > 0) shieldTooltipDetails.push({translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}
);
shieldAbsoluteTooltipDetails.push({translate('generator') + ' ' + formats.pct1(shield.absolute.generator)}
);
shieldAbsoluteTooltipDetails.push({translate('boosters') + ' ' + formats.pct1(shield.absolute.boosters)}
);
@@ -98,10 +98,10 @@ export default class Defence extends TranslatedComponent {
const effectiveThermalShield = shield.total / shield.thermal.total;
effectiveShieldData.push({ value: Math.round(effectiveThermalShield), label: translate('thermal') });
- shieldDamageTakenData.push({ value: Math.round(shield.absolute.total * 100), label: translate('absolute') });
- shieldDamageTakenData.push({ value: Math.round(shield.explosive.total * 100), label: translate('explosive') });
- shieldDamageTakenData.push({ value: Math.round(shield.kinetic.total * 100), label: translate('kinetic') });
- shieldDamageTakenData.push({ value: Math.round(shield.thermal.total * 100), label: translate('thermal') });
+ shieldDamageTakenData.push({ value: Math.round(shield.absolute.total * 100), label: translate('absolute'), tooltip: shieldAbsoluteTooltipDetails });
+ shieldDamageTakenData.push({ value: Math.round(shield.explosive.total * 100), label: translate('explosive'), tooltip: shieldExplosiveTooltipDetails });
+ shieldDamageTakenData.push({ value: Math.round(shield.kinetic.total * 100), label: translate('kinetic'), tooltip: shieldKineticTooltipDetails });
+ shieldDamageTakenData.push({ value: Math.round(shield.thermal.total * 100), label: translate('thermal'), tooltip: shieldThermalTooltipDetails });
maxEffectiveShield = Math.max(shield.total / shield.absolute.max, shield.total / shield.explosive.max, shield.total / shield.kinetic.max, shield.total / shield.thermal.max);
}
@@ -111,8 +111,8 @@ export default class Defence extends TranslatedComponent {
armourSourcesData.push({ value: Math.round(armour.reinforcement), label: translate('reinforcement') });
const armourTooltipDetails = [];
- armourTooltipDetails.push({translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}
);
- armourTooltipDetails.push({translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}
);
+ if (armour.bulkheads > 0) armourTooltipDetails.push({translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}
);
+ if (armour.reinforcement > 0) armourTooltipDetails.push({translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}
);
const armourAbsoluteTooltipDetails = [];
armourAbsoluteTooltipDetails.push({translate('bulkheads') + ' ' + formats.pct1(armour.absolute.bulkheads)}
);
@@ -141,10 +141,10 @@ export default class Defence extends TranslatedComponent {
effectiveArmourData.push({ value: Math.round(effectiveThermalArmour), label: translate('thermal') });
const armourDamageTakenData = [];
- armourDamageTakenData.push({ value: Math.round(armour.absolute.total * 100), label: translate('absolute') });
- armourDamageTakenData.push({ value: Math.round(armour.explosive.total * 100), label: translate('explosive') });
- armourDamageTakenData.push({ value: Math.round(armour.kinetic.total * 100), label: translate('kinetic') });
- armourDamageTakenData.push({ value: Math.round(armour.thermal.total * 100), label: translate('thermal') });
+ armourDamageTakenData.push({ value: Math.round(armour.absolute.total * 100), label: translate('absolute'), tooltip: armourAbsoluteTooltipDetails });
+ armourDamageTakenData.push({ value: Math.round(armour.explosive.total * 100), label: translate('explosive'), tooltip: armourExplosiveTooltipDetails });
+ armourDamageTakenData.push({ value: Math.round(armour.kinetic.total * 100), label: translate('kinetic'), tooltip: armourKineticTooltipDetails });
+ armourDamageTakenData.push({ value: Math.round(armour.thermal.total * 100), label: translate('thermal'), tooltip: armourThermalTooltipDetails });
return (
diff --git a/src/app/components/LineChart.jsx b/src/app/components/LineChart.jsx
index 696cb82b..24edc448 100644
--- a/src/app/components/LineChart.jsx
+++ b/src/app/components/LineChart.jsx
@@ -84,7 +84,7 @@ export default class LineChart extends TranslatedComponent {
y0 = func(x0),
tips = this.tipContainer,
yTotal = 0,
- flip = (xPos / width > 0.60),
+ flip = (xPos / width > 0.50),
tipWidth = 0,
tipHeightPx = tips.selectAll('rect').node().getBoundingClientRect().height;
diff --git a/src/app/components/Offence.jsx b/src/app/components/Offence.jsx
index 506b6fcd..45a1841e 100644
--- a/src/app/components/Offence.jsx
+++ b/src/app/components/Offence.jsx
@@ -2,8 +2,45 @@ import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import * as Calc from '../shipyard/Calculations';
import PieChart from './PieChart';
+import { nameComparator } from '../utils/SlotFunctions';
+import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
import VerticalBarChart from './VerticalBarChart';
+/**
+ * Generates an internationalization friendly weapon comparator that will
+ * sort by specified property (if provided) then by name/group, class, rating
+ * @param {function} translate Translation function
+ * @param {function} propComparator Optional property comparator
+ * @param {boolean} desc Use descending order
+ * @return {function} Comparator function for names
+ */
+export function weaponComparator(translate, propComparator, desc) {
+ return (a, b) => {
+ if (!desc) { // Flip A and B if ascending order
+ let t = a;
+ a = b;
+ b = t;
+ }
+
+ // If a property comparator is provided use it first
+ let diff = propComparator ? propComparator(a, b) : nameComparator(translate, a, b);
+
+ if (diff) {
+ return diff;
+ }
+
+ // Property matches so sort by name / group, then class, rating
+ if (a.name === b.name && a.grp === b.grp) {
+ if(a.class == b.class) {
+ return a.rating > b.rating ? 1 : -1;
+ }
+ return a.class - b.class;
+ }
+
+ return nameComparator(translate, a, b);
+ };
+}
+
/**
* Offence information
* Offence information consists of four panels:
@@ -28,8 +65,14 @@ export default class Offence extends TranslatedComponent {
constructor(props) {
super(props);
- const { shield, armour, shielddamage, armourdamage } = Calc.offenceMetrics(props.ship, props.opponent, props.eng, props.engagementrange);
- this.state = { shield, armour, shielddamage, armourdamage };
+ this._sort = this._sort.bind(this);
+
+ const damage = Calc.offenceMetrics(props.ship, props.opponent, props.eng, props.engagementrange);
+ this.state = {
+ predicate: 'n',
+ desc: true,
+ damage
+ };
}
/**
@@ -39,12 +82,51 @@ export default class Offence extends TranslatedComponent {
*/
componentWillReceiveProps(nextProps) {
if (this.props.marker != nextProps.marker || this.props.sys != nextProps.sys) {
- const { shield, armour, shielddamage, armourdamage } = Calc.offenceMetrics(nextProps.ship, nextProps.opponent, nextProps.wep, nextProps.engagementrange);
- this.setState({ shield, armour, shielddamage, armourdamage });
+ const damage = Calc.offenceMetrics(nextProps.ship, nextProps.opponent, nextProps.wep, nextProps.engagementrange);
+ this.setState({ damage });
}
return true;
}
+ /**
+ * Set the sort order and sort
+ * @param {string} predicate Sort predicate
+ */
+ _sortOrder(predicate) {
+ let desc = this.state.desc;
+
+ if (predicate == this.state.predicate) {
+ desc = !desc;
+ } else {
+ desc = true;
+ }
+
+ this._sort(this.props.ship, predicate, desc);
+ this.setState({ predicate, desc });
+ }
+
+ /**
+ * Sorts the weapon list
+ * @param {Ship} ship Ship instance
+ * @param {string} predicate Sort predicate
+ * @param {Boolean} desc Sort order descending
+ */
+ _sort(ship, predicate, desc) {
+ let comp = weaponComparator.bind(null, this.context.language.translate);
+
+ switch (predicate) {
+ case 'n': comp = comp(null, desc); break;
+ case 'edpss': comp = comp((a, b) => a.effectiveDpsShields - b.effectiveDpsShields, desc); break;
+ case 'esdpss': comp = comp((a, b) => a.effectiveSDpsShields - b.effectiveSDpsShields, desc); break;
+ case 'es': comp = comp((a, b) => a.effectivenessShields - b.effectivenessShields, desc); break;
+ case 'edpsh': comp = comp((a, b) => a.effectiveDpsHull - b.effectiveDpsHull, desc); break;
+ case 'esdpsh': comp = comp((a, b) => a.effectiveSDpsHull - b.effectiveSDpsHull, desc); break;
+ case 'eh': comp = comp((a, b) => a.effectivenessHull - b.effectivenessHull, desc); break;
+ }
+
+ this.state.damage.sort(comp);
+ }
+
/**
* Render offence
* @return {React.Component} contents
@@ -53,8 +135,26 @@ export default class Offence extends TranslatedComponent {
const { ship, wep } = this.props;
const { language, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
-// const { shield, armour, shielddamage, armourdamage } = this.state;
+ const { damage } = this.state;
+ const sortOrder = this._sortOrder;
+ const rows = [];
+ for (let i = 0; i < damage.length; i++) {
+ const weapon = damage[i];
+ rows.push(
+
+ {weapon.mount == 'F' ? : null}
+ {weapon.mount == 'G' ? : null}
+ {weapon.mount == 'T' ? : null}
+ {weapon.classRating} {translate(weapon.name)}
+ {weapon.engineering ? ' (' + weapon.engineering + ')' : null }
+
+ {formats.f1(weapon.sdpsShields)}
+ {formats.pct1(weapon.effectivenessShields)}
+ {formats.f1(weapon.sdpsArmour)}
+ {formats.pct1(weapon.effectivenessArmour)}
+ );
+ }
// const shieldSourcesData = [];
// const effectiveShieldData = [];
// const shieldDamageTakenData = [];
@@ -148,52 +248,24 @@ export default class Offence extends TranslatedComponent {
return (
- {/*
- {shield.total ?
-
-
{translate('shield metrics')}
-
- {shieldTooltipDetails} )} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw shield strength')} {formats.int(shield.total)}{units.MJ}
- {translate('PHRASE_TIME_TO_LOSE_SHIELDS')} {shielddamage.totalsdps == 0 ? translate('ever') : formats.time(shield.total / shielddamage.totalsdps)}
- {translate('PHRASE_TIME_TO_RECOVER_SHIELDS')} {shield.recover === Math.Inf ? translate('never') : formats.time(shield.recover)}
- {translate('PHRASE_TIME_TO_RECHARGE_SHIELDS')} {shield.recharge === Math.Inf ? translate('never') : formats.time(shield.recharge)}
-
-
-
{translate('shield sources')}
-
-
-
-
{translate('damage taken')}(%)
-
-
-
-
{translate('effective shield')}(MJ)
-
-
- : null }
-
-
-
{translate('armour metrics')}
- {armourTooltipDetails} )} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw armour strength')} {formats.int(armour.total)}
- {translate('PHRASE_TIME_TO_LOSE_ARMOUR')} {armourdamage.totalsdps == 0 ? translate('ever') : formats.time(armour.total / armourdamage.totalsdps)}
- {translate('raw module armour')} {formats.int(armour.modulearmour)}
- {translate('PHRASE_MODULE_PROTECTION_EXTERNAL')} {formats.pct1(armour.moduleprotection / 2)}
- {translate('PHRASE_MODULE_PROTECTION_INTERNAL')} {formats.pct1(armour.moduleprotection)}
-
-
-
-
{translate('armour sources')}
-
-
-
-
{translate('damage taken')}(%)
-
-
-
-
{translate('effective armour')}
-
-
- */}
+
+
+
+ {translate('weapon')}
+ {translate('opponent shields')}
+ {translate('opponent armour')}
+
+
+ {translate('effective sdps')}
+ {translate('effectiveness')}
+ {translate('effective sdps')}
+ {translate('effectiveness')}
+
+
+
+ {rows}
+
+
);
}
}
diff --git a/src/app/components/VerticalBarChart.jsx b/src/app/components/VerticalBarChart.jsx
index 50970789..e6fa37cc 100644
--- a/src/app/components/VerticalBarChart.jsx
+++ b/src/app/components/VerticalBarChart.jsx
@@ -1,6 +1,7 @@
import React, { Component } from 'react';
import Measure from 'react-measure';
import * as d3 from 'd3';
+import TranslatedComponent from './TranslatedComponent';
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#519032', '#D5420D'];
const LABEL_COLOUR = '#FFFFFF';
@@ -16,7 +17,7 @@ const merge = function(one, two) {
/**
* A vertical bar chart
*/
-export default class VerticalBarChart extends Component {
+export default class VerticalBarChart extends TranslatedComponent {
static propTypes = {
data : React.PropTypes.array.isRequired,
@@ -45,6 +46,7 @@ export default class VerticalBarChart extends Component {
*/
_renderGraph(props) {
let { width, height } = this.state.dimensions;
+ const { tooltip, termtip } = this.context;
width = width - margin.left - margin.right,
height = width * ASPECT - margin.top - margin.bottom;
@@ -104,9 +106,9 @@ export default class VerticalBarChart extends Component {
.attr('y', 100)
.attr('stroke-width', '0px')
.attr('fill', '#ffffff')
- .attr('x', d => this.x(d.label) + this.x.bandwidth() / 2)
- .attr('y', d => this.y(d.value) + 15)
- .text(d => d.value);
+ .attr('x', d => this.x(d.label) + this.x.bandwidth() / 2)
+ .attr('y', d => this.y(d.value) + 15)
+ .text(d => d.value);
}
/**
@@ -125,7 +127,7 @@ export default class VerticalBarChart extends Component {
{ this.setState({ dimensions }); }}>
{ this.x ?
- this.svg = ref} width={width} height={height} transform={translate}>
+ this.svg = ref} width={width} height={height}>
: null }
diff --git a/src/app/components/WeaponDamageChart.jsx b/src/app/components/WeaponDamageChart.jsx
index 990225aa..dfb7f237 100644
--- a/src/app/components/WeaponDamageChart.jsx
+++ b/src/app/components/WeaponDamageChart.jsx
@@ -30,17 +30,6 @@ export default class WeaponDamageChart extends TranslatedComponent {
*/
constructor(props, context) {
super(props);
-
- const { ship, opponent, hull } = this.props;
-
- const maxRange = this._calcMaxRange(ship);
- // We take whichever is the higher for shields and hull to ensure same Y axis for both
- const maxDps = Math.max(this._calcMaxSDps(ship, opponent, true), this._calcMaxSDps(ship, opponent, false));
-
- this.state = {
- maxRange,
- maxDps
- };
}
/**
@@ -48,7 +37,12 @@ export default class WeaponDamageChart extends TranslatedComponent {
*/
componentWillMount() {
const weaponNames = this._weaponNames(this.props.ship, this.context);
- this.setState({ weaponNames, calcSDpsFunc: this._calcSDps.bind(this, this.props.ship, weaponNames, this.props.opponent, this.props.hull) });
+ const opponentShields = Calc.shieldMetrics(this.props.opponent, 4);
+ const opponentArmour = Calc.armourMetrics(this.props.opponent);
+ const maxRange = this._calcMaxRange(this.props.ship);
+ const maxDps = this._calcMaxSDps(this.props.ship, this.props.opponent, opponentShields, opponentArmour);
+
+ this.setState({ maxRange, maxDps, weaponNames, opponentShields, opponentArmour, calcSDpsFunc: this._calcSDps.bind(this, this.props.ship, weaponNames, this.props.opponent, opponentShields, opponentArmour, this.props.hull) });
}
/**
@@ -60,13 +54,16 @@ export default class WeaponDamageChart extends TranslatedComponent {
componentWillReceiveProps(nextProps, nextContext) {
if (nextProps.marker != this.props.marker) {
const weaponNames = this._weaponNames(nextProps.ship, nextContext);
+ const opponentShields = Calc.shieldMetrics(nextProps.opponent, 4);
+ const opponentArmour = Calc.armourMetrics(nextProps.opponent);
const maxRange = this._calcMaxRange(nextProps.ship);
- // We take whichever is the higher for shields and hull to ensure same Y axis for both
- const maxDps = Math.max(this._calcMaxSDps(nextProps.ship, nextProps.opponent, true), this._calcMaxSDps(nextProps.ship, nextProps.opponent, false));
+ const maxDps = this._calcMaxSDps(nextProps.ship, nextProps.opponent, opponentShields, opponentArmour);
this.setState({ weaponNames,
+ opponentShields,
+ opponentArmour,
maxRange,
maxDps,
- calcSDpsFunc: this._calcSDps.bind(this, nextProps.ship, weaponNames, nextProps.opponent, nextProps.hull)
+ calcSDpsFunc: this._calcSDps.bind(this, nextProps.ship, weaponNames, nextProps.opponent, opponentShields, opponentArmour, nextProps.hull)
});
}
return true;
@@ -93,19 +90,21 @@ export default class WeaponDamageChart extends TranslatedComponent {
/**
* Calculate the maximum sustained single-weapon DPS for this ship
- * @param {Object} ship The ship
- * @param {Object} opponent The opponent ship
- * @param {bool} hull True if against hull
- * @return {number} The maximum sustained single-weapon DPS
+ * @param {Object} ship The ship
+ * @param {Object} opponent The opponent ship
+ * @param {Object} opponentShields The opponent's shields
+ * @param {Object} opponentArmour The opponent's armour
+ * @return {number} The maximum sustained single-weapon DPS
*/
- _calcMaxSDps(ship, opponent, hull) {
+ _calcMaxSDps(ship, opponent, opponentShields, opponentArmour) {
// Additional information to allow effectiveness calculations
- const defence = hull ? Calc.armourMetrics(opponent) : Calc.shieldMetrics(opponent, 4);
let maxSDps = 0;
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
const m = ship.hardpoints[i].m;
- const thisSDps = this._calcWeaponSDps(ship, m, opponent, defence, 0);
+
+ const sustainedDps = Calc._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, 0);
+ const thisSDps = sustainedDps.damage.armour.total > sustainedDps.damage.shields.total ? sustainedDps.damage.armour.total : sustainedDps.damage.shields.total;
if (thisSDps > maxSDps) {
maxSDps = thisSDps;
}
@@ -149,69 +148,25 @@ export default class WeaponDamageChart extends TranslatedComponent {
* @param {Object} ship The ship
* @param {Object} weaponNames The names of the weapons for which to calculate DPS
* @param {Object} opponent The target
+ * @param {Object} opponentShields The opponent's shields
+ * @param {Object} opponentArmour The opponent's armour
* @param {bool} hull true if to calculate against hull, false if to calculate against shields
* @param {Object} engagementRange The engagement range
* @return {array} The array of weapon DPS
*/
- _calcSDps(ship, weaponNames, opponent, hull, engagementRange) {
- // Additional information to allow effectiveness calculations
- const defence = hull ? Calc.armourMetrics(opponent) : Calc.shieldMetrics(opponent, 4);
-
+ _calcSDps(ship, weaponNames, opponent, opponentShields, opponentArmour, hull, engagementRange) {
let results = {};
let weaponNum = 0;
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
const m = ship.hardpoints[i].m;
- results[weaponNames[weaponNum++]] = this._calcWeaponSDps(ship, m, opponent, defence, engagementRange);
+ const sustainedDps = Calc._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementRange);
+ results[weaponNames[weaponNum++]] = hull ? sustainedDps.damage.armour.total : sustainedDps.damage.shields.total;
}
}
return results;
}
- /**
- * Calculate the sustained DPS for a particular weapon for this ship against another ship at a given range
- * @param {Object} ship The ship that will deal the damage
- * @param {Object} m The weapon that will deal the damage
- * @param {Object} opponent The ship against which damage will be dealt
- * @param {Object} defence defence metrics (either shield or hull)
- * @param {Object} engagementRange The engagement range
- * @return {object} Returns the sustained DPS for the weapon
- */
- _calcWeaponSDps(ship, m, opponent, defence, engagementRange) {
- let falloff = 1;
- if (m.getFalloff()) {
- // Calculate the falloff % due to range
- if (engagementRange > m.getRange()) {
- // Weapon is out of range
- falloff = 0;
- } else {
- const falloffPoint = m.getFalloff();
- if (engagementRange > falloffPoint) {
- const falloffRange = m.getRange() - falloffPoint;
- // Assuming straight-line falloff
- falloff = 1 - (engagementRange - falloffPoint) / falloffRange;
- }
- }
- }
-
- let effectiveness = 0;
- if (m.getDamageDist().E) {
- effectiveness += m.getDamageDist().E * defence.explosive.total;
- }
- if (m.getDamageDist().K) {
- effectiveness += m.getDamageDist().K * defence.kinetic.total;
- }
- if (m.getDamageDist().T) {
- effectiveness += m.getDamageDist().T * defence.thermal.total;
- }
- if (m.getDamageDist().A) {
- effectiveness += m.getDamageDist().A * defence.absolute.total;
- }
-
- // Return the final effective SDPS
- return (m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getDps()) * falloff * effectiveness;
- }
-
/**
* Render damage dealt
* @return {React.Component} contents
diff --git a/src/app/shipyard/Calculations.js b/src/app/shipyard/Calculations.js
index b27be63e..549fcf2a 100644
--- a/src/app/shipyard/Calculations.js
+++ b/src/app/shipyard/Calculations.js
@@ -544,23 +544,23 @@ export function defenceMetrics(ship, opponent, sys, engagementrange) {
const armour = this.armourMetrics(ship);
// Obtain the opponent's sustained DPS on us
- const { shieldsdps, armoursdps } = this._sustainedDps(opponent, ship, engagementrange);
+ const sustainedDps = this.sustainedDps(opponent, ship, sys, engagementrange);
- const shielddamage = shield.generatorStrength ? {
- absolutesdps: shieldsdps.absolute *= shield.absolute.total,
- explosivesdps: shieldsdps.explosive *= shield.explosive.total,
- kineticsdps: shieldsdps.kinetic *= shield.kinetic.total,
- thermalsdps: shieldsdps.thermal *= shield.thermal.total,
- totalsdps: shielddamage.absolutesdps + shielddamage.explosivesdps + shielddamage.kineticsdps + shielddamage.thermalsdps
- } : {} ;
+ const shielddamage = shield.generator ? {
+ absolutesdps: sustainedDps.shieldsdps.absolute,
+ explosivesdps: sustainedDps.shieldsdps.explosive,
+ kineticsdps: sustainedDps.shieldsdps.kinetic,
+ thermalsdps: sustainedDps.shieldsdps.thermal,
+ totalsdps: sustainedDps.shieldsdps.absolute + sustainedDps.shieldsdps.explosive + sustainedDps.shieldsdps.kinetic + sustainedDps.shieldsdps.thermal
+ } : {};
const armourdamage = {
- absolutesdps: armoursdps.absolute *= armour.absolute.total,
- explosivesdps: armoursdps.explosive *= armour.explosive.total,
- kineticsdps: armoursdps.kinetic *= armour.kinetic.total,
- thermalsdps: armoursdps.thermal *= armour.thermal.total
+ absolutesdps: sustainedDps.armoursdps.absolute,
+ explosivesdps: sustainedDps.armoursdps.explosive,
+ kineticsdps: sustainedDps.armoursdps.kinetic,
+ thermalsdps: sustainedDps.armoursdps.thermal,
+ totalsdps: sustainedDps.armoursdps.absolute + sustainedDps.armoursdps.explosive + sustainedDps.armoursdps.kinetic + sustainedDps.armoursdps.thermal
};
- armourdamage.totalsdps = armourdamage.absolutesdps + armourdamage.explosivesdps + armourdamage.kineticsdps + armourdamage.thermalsdps;
return { shield, armour, shielddamage, armourdamage };
}
@@ -571,19 +571,48 @@ export function defenceMetrics(ship, opponent, sys, engagementrange) {
* @param {Object} opponent The opponent ship
* @param {int} wep The pips to WEP
* @param {int} engagementrange The range between the ship and opponent
- * @returns {Object} Offence metrics
+ * @returns {array} Offence metrics
*/
export function offenceMetrics(ship, opponent, wep, engagementrange) {
-
- // Per-weapon and total damage to armour
- const armourdamage = {};
+ // Per-weapon and total damage
+ const damage = [];
// Obtain the opponent's shield and armour metrics
const opponentShields = this.shieldMetrics(opponent, 4);
const opponentArmour = this.armourMetrics(opponent);
// Per-weapon and total damage to shields
- const shielddamage = opponentShields.generatorStrength ? {
+ for (let i = 0; i < ship.hardpoints.length; i++) {
+ if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
+ const m = ship.hardpoints[i].m;
+
+ const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
+ let engineering;
+ if (m.blueprint && m.blueprint.name) {
+ engineering = m.blueprint.name + ' ' + 'grade' + ' ' + m.blueprint.grade;
+ if (m.blueprint.special && m.blueprint.special.id >= 0) {
+ engineering += ', ' + m.blueprint.special.name;
+ }
+ }
+
+ const weaponSustainedDps = this._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementrange);
+ damage.push({
+ id: i,
+ mount: m.mount,
+ name: m.name || m.grp,
+ classRating,
+ engineering,
+ sdpsShields: weaponSustainedDps.damage.shields.total,
+ sdpsArmour: weaponSustainedDps.damage.armour.total,
+ effectivenessShields: weaponSustainedDps.effectiveness.shields.total,
+ effectivenessArmour: weaponSustainedDps.effectiveness.armour.total
+ });
+ }
+ }
+
+ return damage;
+
+ const shielddamage = opponentShields.generator ? {
absolute: {
weapon1: 10,
weapon2: 10,
@@ -593,7 +622,7 @@ export function offenceMetrics(ship, opponent, wep, engagementrange) {
}
} : {};
- return { shielddamage, armourdamage };
+ return damage;
}
/**
@@ -616,13 +645,31 @@ export function sysRechargeRate(pd, sys) {
}
/**
- * Calculate the sustained DPS for a ship at a given range, excluding resistances
+ * Calculate the sustained DPS for a ship against an opponent at a given range
* @param {Object} ship The ship
* @param {Object} opponent The opponent ship
+ * @param {number} sys Pips to opponent's SYS
* @param {int} engagementrange The range between the ship and opponent
* @returns {Object} Sustained DPS for shield and armour
*/
-export function _sustainedDps(ship, opponent, engagementrange) {
+export function sustainedDps(ship, opponent, sys, engagementrange) {
+ // Obtain the opponent's shield and armour metrics
+ const opponentShields = this.shieldMetrics(opponent, sys);
+ const opponentArmour = this.armourMetrics(opponent);
+
+ return this._sustainedDps(ship, opponent, opponentShields, opponentArmour, engagementrange);
+}
+
+/**
+ * Calculate the sustained DPS for a ship against an opponent at a given range
+ * @param {Object} ship The ship
+ * @param {Object} opponent The opponent ship
+ * @param {Object} opponentShields The opponent's shield resistances
+ * @param {Object} opponentArmour The opponent's armour resistances
+ * @param {int} engagementrange The range between the ship and opponent
+ * @returns {Object} Sustained DPS for shield and armour
+ */
+export function _sustainedDps(ship, opponent, opponentShields, opponentArmour, engagementrange) {
const shieldsdps = {
absolute: 0,
explosive: 0,
@@ -640,35 +687,112 @@ export function _sustainedDps(ship, opponent, engagementrange) {
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].m && ship.hardpoints[i].enabled && ship.hardpoints[i].maxClass > 0) {
const m = ship.hardpoints[i].m;
- // Initial sustained DPS
- let sDps = m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getDps();
- // Take fall-off in to account
- const falloff = m.getFalloff();
- if (falloff && engagementrange > falloff) {
- const dropoffRange = m.getRange() - falloff;
- sDps *= 1 - Math.min((engagementrange - falloff) / dropoffRange, 1);
- }
- // Piercing/hardness modifier (for armour only)
- const armourMultiple = m.getPiercing() >= opponent.hardness ? 1 : m.getPiercing() / opponent.hardness;
- // Break out the damage according to type
- if (m.getDamageDist().A) {
- shieldsdps.absolute += sDps * m.getDamageDist().A;
- armoursdps.absolute += sDps * m.getDamageDist().A * armourMultiple;
- }
- if (m.getDamageDist().E) {
- shieldsdps.explosive += sDps * m.getDamageDist().E;
- armoursdps.explosive += sDps * m.getDamageDist().E * armourMultiple;
- }
- if (m.getDamageDist().K) {
- shieldsdps.kinetic += sDps * m.getDamageDist().K;
- armoursdps.kinetic += sDps * m.getDamageDist().K * armourMultiple;
- }
- if (m.getDamageDist().T) {
- shieldsdps.thermal += sDps * m.getDamageDist().T;
- armoursdps.thermal += sDps * m.getDamageDist().T * armourMultiple;
- }
+ const sustainedDps = this._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementrange);
+ shieldsdps.absolute += sustainedDps.damage.shields.absolute;
+ shieldsdps.explosive += sustainedDps.damage.shields.explosive;
+ shieldsdps.kinetic += sustainedDps.damage.shields.kinetic;
+ shieldsdps.thermal += sustainedDps.damage.shields.thermal;
+ armoursdps.absolute += sustainedDps.damage.armour.absolute;
+ armoursdps.explosive += sustainedDps.damage.armour.explosive;
+ armoursdps.kinetic += sustainedDps.damage.armour.kinetic;
+ armoursdps.thermal += sustainedDps.damage.armour.thermal;
}
}
+
return { shieldsdps, armoursdps };
}
+/**
+ * Calculate the sustained DPS for a weapon at a given range
+ * @param {Object} m The weapon
+ * @param {Object} opponent The opponent ship
+ * @param {Object} opponentShields The opponent's shield resistances
+ * @param {Object} opponentArmour The opponent's armour resistances
+ * @param {int} engagementrange The range between the ship and opponent
+ * @returns {Object} Sustained DPS for shield and armour
+ */
+export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementrange) {
+ const weapon = {
+ damage: {
+ shields: {
+ absolute: 0,
+ explosive: 0,
+ kinetic: 0,
+ thermal: 0,
+ total: 0
+ },
+ armour: {
+ absolute: 0,
+ explosive: 0,
+ kinetic: 0,
+ thermal: 0,
+ total: 0
+ },
+ },
+ effectiveness: {
+ shields: {
+ range: 1,
+ sys: opponentShields.absolute.sys,
+ resistance: 1
+ },
+ armour: {
+ range: 1,
+ hardness: 1,
+ resistance: 1
+ }
+ }
+ };
+
+ // Initial sustained DPS
+ let sDps = m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getDps();
+
+ // Take fall-off in to account
+ const falloff = m.getFalloff();
+ if (falloff && engagementrange > falloff) {
+ const dropoffRange = m.getRange() - falloff;
+ const dropoff = 1 - Math.min((engagementrange - falloff) / dropoffRange, 1);
+ weapon.effectiveness.shields.range = weapon.effectiveness.armour.range = dropoff;
+ sDps *= dropoff;
+ }
+
+ // Piercing/hardness modifier (for armour only)
+ const armourMultiple = m.getPiercing() >= opponent.hardness ? 1 : m.getPiercing() / opponent.hardness;
+ weapon.effectiveness.armour.hardness = armourMultiple;
+
+ // Break out the damage according to type
+ let shieldsResistance = 0;
+ let armourResistance = 0;
+ if (m.getDamageDist().A) {
+ weapon.damage.shields.absolute += sDps * m.getDamageDist().A * opponentShields.absolute.total;
+ weapon.damage.armour.absolute += sDps * m.getDamageDist().A * armourMultiple * opponentArmour.absolute.total;
+ shieldsResistance += m.getDamageDist().A * opponentShields.absolute.generator * opponentShields.absolute.boosters;
+ armourResistance += m.getDamageDist().A * opponentArmour.absolute.bulkheads * opponentArmour.absolute.reinforcement;
+ }
+ if (m.getDamageDist().E) {
+ weapon.damage.shields.explosive += sDps * m.getDamageDist().E * opponentShields.explosive.total;
+ weapon.damage.armour.explosive += sDps * m.getDamageDist().E * armourMultiple * opponentArmour.explosive.total;
+ shieldsResistance += m.getDamageDist().E * opponentShields.explosive.generator * opponentShields.explosive.boosters;
+ armourResistance += m.getDamageDist().E * opponentArmour.explosive.bulkheads * opponentArmour.explosive.reinforcement;
+ }
+ if (m.getDamageDist().K) {
+ weapon.damage.shields.kinetic += sDps * m.getDamageDist().K * opponentShields.kinetic.total;
+ weapon.damage.armour.kinetic += sDps * m.getDamageDist().K * armourMultiple * opponentArmour.kinetic.total;
+ shieldsResistance += m.getDamageDist().K * opponentShields.kinetic.generator * opponentShields.kinetic.boosters;
+ armourResistance += m.getDamageDist().K * opponentArmour.kinetic.bulkheads * opponentArmour.kinetic.reinforcement;
+ }
+ if (m.getDamageDist().T) {
+ weapon.damage.shields.thermal += sDps * m.getDamageDist().T * opponentShields.thermal.total;
+ weapon.damage.armour.thermal += sDps * m.getDamageDist().T * armourMultiple * opponentArmour.thermal.total;
+ shieldsResistance += m.getDamageDist().T * opponentShields.thermal.generator * opponentShields.thermal.boosters;
+ armourResistance += m.getDamageDist().T * opponentArmour.thermal.bulkheads * opponentArmour.thermal.reinforcement;
+ }
+ weapon.damage.shields.total = weapon.damage.shields.absolute + weapon.damage.shields.explosive + weapon.damage.shields.kinetic + weapon.damage.shields.thermal;
+ weapon.damage.armour.total = weapon.damage.armour.absolute + weapon.damage.armour.explosive + weapon.damage.armour.kinetic + weapon.damage.armour.thermal;
+
+ weapon.effectiveness.shields.resistance *= shieldsResistance;
+ weapon.effectiveness.armour.resistance *= armourResistance;
+
+ weapon.effectiveness.shields.total = weapon.effectiveness.shields.range * weapon.effectiveness.shields.sys * weapon.effectiveness.shields.resistance;
+ weapon.effectiveness.armour.total = weapon.effectiveness.armour.range * weapon.effectiveness.armour.resistance * weapon.effectiveness.armour.hardness;
+ return weapon;
+}
From fcb8980a388d92723bcad78c34249544c216268a Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Sun, 19 Mar 2017 08:41:36 +0000
Subject: [PATCH 42/87] Tidy-ups
---
src/app/components/EngagementRange.jsx | 2 +-
src/app/components/Offence.jsx | 164 ++++++++-----------------
src/app/i18n/en.js | 5 +
src/app/pages/OutfittingPage.jsx | 2 +-
src/app/shipyard/Calculations.js | 6 +-
src/less/app.less | 1 +
src/less/offence.less | 14 +++
7 files changed, 75 insertions(+), 119 deletions(-)
create mode 100755 src/less/offence.less
diff --git a/src/app/components/EngagementRange.jsx b/src/app/components/EngagementRange.jsx
index ef851b01..724a197f 100644
--- a/src/app/components/EngagementRange.jsx
+++ b/src/app/components/EngagementRange.jsx
@@ -27,7 +27,7 @@ export default class Range extends TranslatedComponent {
this.state = {
maxRange,
- rangeLevel: 0.5,
+ rangeLevel: 1000 / maxRange,
};
}
diff --git a/src/app/components/Offence.jsx b/src/app/components/Offence.jsx
index 45a1841e..65327fe1 100644
--- a/src/app/components/Offence.jsx
+++ b/src/app/components/Offence.jsx
@@ -81,7 +81,7 @@ export default class Offence extends TranslatedComponent {
* @return {boolean} Returns true if the component should be rerendered
*/
componentWillReceiveProps(nextProps) {
- if (this.props.marker != nextProps.marker || this.props.sys != nextProps.sys) {
+ if (this.props.marker != nextProps.marker || this.props.eng != nextProps.eng) {
const damage = Calc.offenceMetrics(nextProps.ship, nextProps.opponent, nextProps.wep, nextProps.engagementrange);
this.setState({ damage });
}
@@ -101,27 +101,24 @@ export default class Offence extends TranslatedComponent {
desc = true;
}
- this._sort(this.props.ship, predicate, desc);
+ this._sort(predicate, desc);
this.setState({ predicate, desc });
}
/**
* Sorts the weapon list
- * @param {Ship} ship Ship instance
* @param {string} predicate Sort predicate
* @param {Boolean} desc Sort order descending
*/
- _sort(ship, predicate, desc) {
+ _sort(predicate, desc) {
let comp = weaponComparator.bind(null, this.context.language.translate);
switch (predicate) {
case 'n': comp = comp(null, desc); break;
- case 'edpss': comp = comp((a, b) => a.effectiveDpsShields - b.effectiveDpsShields, desc); break;
- case 'esdpss': comp = comp((a, b) => a.effectiveSDpsShields - b.effectiveSDpsShields, desc); break;
- case 'es': comp = comp((a, b) => a.effectivenessShields - b.effectivenessShields, desc); break;
- case 'edpsh': comp = comp((a, b) => a.effectiveDpsHull - b.effectiveDpsHull, desc); break;
- case 'esdpsh': comp = comp((a, b) => a.effectiveSDpsHull - b.effectiveSDpsHull, desc); break;
- case 'eh': comp = comp((a, b) => a.effectivenessHull - b.effectivenessHull, desc); break;
+ case 'esdpss': comp = comp((a, b) => a.sdps.shields.total - b.sdps.shields.total, desc); break;
+ case 'es': comp = comp((a, b) => a.effectiveness.shields.total - b.effectiveness.shields.total, desc); break;
+ case 'esdpsh': comp = comp((a, b) => a.sdps.armour.total - b.sdps.armour.total, desc); break;
+ case 'eh': comp = comp((a, b) => a.effectiveness.armour.total - b.effectiveness.armour.total, desc); break;
}
this.state.damage.sort(comp);
@@ -141,104 +138,43 @@ export default class Offence extends TranslatedComponent {
const rows = [];
for (let i = 0; i < damage.length; i++) {
const weapon = damage[i];
- rows.push(
-
- {weapon.mount == 'F' ? : null}
- {weapon.mount == 'G' ? : null}
- {weapon.mount == 'T' ? : null}
- {weapon.classRating} {translate(weapon.name)}
- {weapon.engineering ? ' (' + weapon.engineering + ')' : null }
-
- {formats.f1(weapon.sdpsShields)}
- {formats.pct1(weapon.effectivenessShields)}
- {formats.f1(weapon.sdpsArmour)}
- {formats.pct1(weapon.effectivenessArmour)}
- );
+
+ const effectivenessShieldsTooltipDetails = [];
+ effectivenessShieldsTooltipDetails.push({translate('range') + ' ' + formats.pct1(weapon.effectiveness.shields.range)}
);
+ effectivenessShieldsTooltipDetails.push({translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.shields.resistance)}
);
+ effectivenessShieldsTooltipDetails.push({translate('sys') + ' ' + formats.pct1(weapon.effectiveness.shields.sys)}
);
+
+ const effectiveShieldsSDpsTooltipDetails = [];
+ if (weapon.sdps.shields.absolute) effectiveShieldsSDpsTooltipDetails.push({translate('absolute') + ' ' + formats.f1(weapon.sdps.shields.absolute)}
);
+ if (weapon.sdps.shields.explosive) effectiveShieldsSDpsTooltipDetails.push({translate('explosive') + ' ' + formats.f1(weapon.sdps.shields.explosive)}
);
+ if (weapon.sdps.shields.kinetic) effectiveShieldsSDpsTooltipDetails.push({translate('kinetic') + ' ' + formats.f1(weapon.sdps.shields.kinetic)}
);
+ if (weapon.sdps.shields.thermal) effectiveShieldsSDpsTooltipDetails.push({translate('thermal') + ' ' + formats.f1(weapon.sdps.shields.thermal)}
);
+
+ const effectivenessArmourTooltipDetails = [];
+ effectivenessArmourTooltipDetails.push({translate('range') + ' ' + formats.pct1(weapon.effectiveness.armour.range)}
);
+ effectivenessArmourTooltipDetails.push({translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.armour.resistance)}
);
+ effectivenessArmourTooltipDetails.push({translate('hardness') + ' ' + formats.pct1(weapon.effectiveness.armour.hardness)}
);
+ const effectiveArmourSDpsTooltipDetails = [];
+ if (weapon.sdps.armour.absolute) effectiveArmourSDpsTooltipDetails.push({translate('absolute') + ' ' + formats.f1(weapon.sdps.armour.absolute)}
);
+ if (weapon.sdps.armour.explosive) effectiveArmourSDpsTooltipDetails.push({translate('explosive') + ' ' + formats.f1(weapon.sdps.armour.explosive)}
);
+ if (weapon.sdps.armour.kinetic) effectiveArmourSDpsTooltipDetails.push({translate('kinetic') + ' ' + formats.f1(weapon.sdps.armour.kinetic)}
);
+ if (weapon.sdps.armour.thermal) effectiveArmourSDpsTooltipDetails.push({translate('thermal') + ' ' + formats.f1(weapon.sdps.armour.thermal)}
);
+
+ rows.push(
+
+
+ {weapon.mount == 'F' ? : null}
+ {weapon.mount == 'G' ? : null}
+ {weapon.mount == 'T' ? : null}
+ {weapon.classRating} {translate(weapon.name)}
+ {weapon.engineering ? ' (' + weapon.engineering + ')' : null }
+
+ {formats.f1(weapon.sdps.shields.total)}
+ {formats.pct1(weapon.effectiveness.shields.total)}
+ {formats.f1(weapon.sdps.armour.total)}
+ {formats.pct1(weapon.effectiveness.armour.total)}
+ );
}
-// const shieldSourcesData = [];
-// const effectiveShieldData = [];
-// const shieldDamageTakenData = [];
-// const shieldTooltipDetails = [];
-// const shieldAbsoluteTooltipDetails = [];
-// const shieldExplosiveTooltipDetails = [];
-// const shieldKineticTooltipDetails = [];
-// const shieldThermalTooltipDetails = [];
-// let maxEffectiveShield = 0;
-// if (shield.total) {
-// shieldSourcesData.push({ value: Math.round(shield.generator), label: translate('generator') });
-// shieldSourcesData.push({ value: Math.round(shield.boosters), label: translate('boosters') });
-// shieldSourcesData.push({ value: Math.round(shield.cells), label: translate('cells') });
-
-// shieldTooltipDetails.push({translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}
);
-// shieldTooltipDetails.push({translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}
);
-// shieldTooltipDetails.push({translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}
);
-
-// shieldAbsoluteTooltipDetails.push({translate('generator') + ' ' + formats.pct1(shield.absolute.generator)}
);
-// shieldAbsoluteTooltipDetails.push({translate('boosters') + ' ' + formats.pct1(shield.absolute.boosters)}
);
-// shieldAbsoluteTooltipDetails.push({translate('power distributor') + ' ' + formats.pct1(shield.absolute.sys)}
);
-
-// shieldExplosiveTooltipDetails.push({translate('generator') + ' ' + formats.pct1(shield.explosive.generator)}
);
-// shieldExplosiveTooltipDetails.push({translate('boosters') + ' ' + formats.pct1(shield.explosive.boosters)}
);
-// shieldExplosiveTooltipDetails.push({translate('power distributor') + ' ' + formats.pct1(shield.explosive.sys)}
);
-
-// shieldKineticTooltipDetails.push({translate('generator') + ' ' + formats.pct1(shield.kinetic.generator)}
);
-// shieldKineticTooltipDetails.push({translate('boosters') + ' ' + formats.pct1(shield.kinetic.boosters)}
);
-// shieldKineticTooltipDetails.push({translate('power distributor') + ' ' + formats.pct1(shield.kinetic.sys)}
);
-
-// shieldThermalTooltipDetails.push({translate('generator') + ' ' + formats.pct1(shield.thermal.generator)}
);
-// shieldThermalTooltipDetails.push({translate('boosters') + ' ' + formats.pct1(shield.thermal.boosters)}
);
-// shieldThermalTooltipDetails.push({translate('power distributor') + ' ' + formats.pct1(shield.thermal.sys)}
);
-
-// const effectiveAbsoluteShield = shield.total / shield.absolute.total;
-// effectiveShieldData.push({ value: Math.round(effectiveAbsoluteShield), label: translate('absolute') });
-// const effectiveExplosiveShield = shield.total / shield.explosive.total;
-// effectiveShieldData.push({ value: Math.round(effectiveExplosiveShield), label: translate('explosive') });
-// const effectiveKineticShield = shield.total / shield.kinetic.total;
-// effectiveShieldData.push({ value: Math.round(effectiveKineticShield), label: translate('kinetic') });
-// const effectiveThermalShield = shield.total / shield.thermal.total;
-// effectiveShieldData.push({ value: Math.round(effectiveThermalShield), label: translate('thermal') });
-
-// shieldDamageTakenData.push({ value: Math.round(shield.absolute.total * 100), label: translate('absolute') });
-// shieldDamageTakenData.push({ value: Math.round(shield.explosive.total * 100), label: translate('explosive') });
-// shieldDamageTakenData.push({ value: Math.round(shield.kinetic.total * 100), label: translate('kinetic') });
-// shieldDamageTakenData.push({ value: Math.round(shield.thermal.total * 100), label: translate('thermal') });
-
-// maxEffectiveShield = Math.max(shield.total / shield.absolute.max, shield.total / shield.explosive.max, shield.total / shield.kinetic.max, shield.total / shield.thermal.max);
-// }
-
-// const armourSourcesData = [];
-// armourSourcesData.push({ value: Math.round(armour.bulkheads), label: translate('bulkheads') });
-// armourSourcesData.push({ value: Math.round(armour.reinforcement), label: translate('reinforcement') });
-
-// const armourTooltipDetails = [];
-// armourTooltipDetails.push({translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}
);
-// armourTooltipDetails.push({translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}
);
-
-// const armourAbsoluteTooltipDetails = [];
-// armourAbsoluteTooltipDetails.push({translate('bulkheads') + ' ' + formats.pct1(armour.absolute.bulkheads)}
);
-// armourAbsoluteTooltipDetails.push({translate('reinforcement') + ' ' + formats.pct1(armour.absolute.reinforcement)}
);
-
-// const armourExplosiveTooltipDetails = [];
-// armourExplosiveTooltipDetails.push({translate('bulkheads') + ' ' + formats.pct1(armour.explosive.bulkheads)}
);
-// armourExplosiveTooltipDetails.push({translate('reinforcement') + ' ' + formats.pct1(armour.explosive.reinforcement)}
);
-
-// const armourKineticTooltipDetails = [];
-// armourKineticTooltipDetails.push({translate('bulkheads') + ' ' + formats.pct1(armour.kinetic.bulkheads)}
);
-// armourKineticTooltipDetails.push({translate('reinforcement') + ' ' + formats.pct1(armour.kinetic.reinforcement)}
);
-
-// const armourThermalTooltipDetails = [];
-// armourThermalTooltipDetails.push({translate('bulkheads') + ' ' + formats.pct1(armour.thermal.bulkheads)}
);
-// armourThermalTooltipDetails.push({translate('reinforcement') + ' ' + formats.pct1(armour.thermal.reinforcement)}
);
-
-// const effectiveArmourData = [];
-// const effectiveAbsoluteArmour = armour.total / armour.absolute.total;
-// effectiveArmourData.push({ value: Math.round(effectiveAbsoluteArmour), label: translate('absolute') });
-// const effectiveExplosiveArmour = armour.total / armour.explosive.total;
-// effectiveArmourData.push({ value: Math.round(effectiveExplosiveArmour), label: translate('explosive') });
-// const effectiveKineticArmour = armour.total / armour.kinetic.total;
-// effectiveArmourData.push({ value: Math.round(effectiveKineticArmour), label: translate('kinetic') });
-// const effectiveThermalArmour = armour.total / armour.thermal.total;
-// effectiveArmourData.push({ value: Math.round(effectiveThermalArmour), label: translate('thermal') });
// const armourDamageTakenData = [];
// armourDamageTakenData.push({ value: Math.round(armour.absolute.total * 100), label: translate('absolute') });
@@ -248,24 +184,26 @@ export default class Offence extends TranslatedComponent {
return (
+
{translate('weapon')}
- {translate('opponent shields')}
- {translate('opponent armour')}
+ {translate('opponent\`s shields')}
+ {translate('opponent\`s armour')}
- {translate('effective sdps')}
- {translate('effectiveness')}
- {translate('effective sdps')}
- {translate('effectiveness')}
+ {'sdps'}
+ {'eft'}
+ {'sdps'}
+ {'eft'}
{rows}
+
);
}
}
diff --git a/src/app/i18n/en.js b/src/app/i18n/en.js
index 1f7d8cb5..37683424 100644
--- a/src/app/i18n/en.js
+++ b/src/app/i18n/en.js
@@ -55,6 +55,11 @@ export const terms = {
TT_MODULE_PROTECTION_EXTERNAL: 'Percentage of damage diverted from hardpoints to module reinforcement packages',
TT_MODULE_PROTECTION_INTERNAL: 'Percentage of damage diverted from non-hardpoint modules to module reinforcement packages',
+ TT_EFFECTIVE_SDPS_SHIELDS: 'Actual sustained DPS whilst WEP capacitor is not empty',
+ TT_EFFECTIVENESS_SHIELDS: 'Effectivness compared to hitting a 0-resistance target with 0 pips to SYS at 0m',
+ TT_EFFECTIVE_SDPS_ARMOUR: 'Actual sustained DPS whilst WEP capacitor is not empty',
+ TT_EFFECTIVENESS_ARMOUR: 'Effectivness compared to hitting a 0-resistance target at 0m',
+
HELP_MODIFICATIONS_MENU: 'Click on a number to enter a new value, or drag along the bar for small changes',
// Other languages fallback to these values
diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx
index ad6e773a..b96c2ef3 100644
--- a/src/app/pages/OutfittingPage.jsx
+++ b/src/app/pages/OutfittingPage.jsx
@@ -100,7 +100,7 @@ export default class OutfittingPage extends Page {
fuel: ship.fuelCapacity,
cargo: 0,
boost: false,
- engagementRange: 1500,
+ engagementRange: 1000,
opponent: new Ship('anaconda', Ships['anaconda'].properties, Ships['anaconda'].slots).buildWith(Ships['anaconda'].defaults)
};
}
diff --git a/src/app/shipyard/Calculations.js b/src/app/shipyard/Calculations.js
index 549fcf2a..597b279d 100644
--- a/src/app/shipyard/Calculations.js
+++ b/src/app/shipyard/Calculations.js
@@ -602,10 +602,8 @@ export function offenceMetrics(ship, opponent, wep, engagementrange) {
name: m.name || m.grp,
classRating,
engineering,
- sdpsShields: weaponSustainedDps.damage.shields.total,
- sdpsArmour: weaponSustainedDps.damage.armour.total,
- effectivenessShields: weaponSustainedDps.effectiveness.shields.total,
- effectivenessArmour: weaponSustainedDps.effectiveness.armour.total
+ sdps: weaponSustainedDps.damage,
+ effectiveness: weaponSustainedDps.effectiveness
});
}
}
diff --git a/src/less/app.less b/src/less/app.less
index f1234db6..5c015eff 100755
--- a/src/less/app.less
+++ b/src/less/app.less
@@ -24,6 +24,7 @@
@import 'movement';
@import 'shippicker';
@import 'defence';
+@import 'offence';
html, body {
height: 100%;
diff --git a/src/less/offence.less b/src/less/offence.less
new file mode 100755
index 00000000..39679f27
--- /dev/null
+++ b/src/less/offence.less
@@ -0,0 +1,14 @@
+#offence {
+ table {
+ background-color: @bgBlack;
+ color: @fg;
+ margin: 0 auto;
+ }
+
+ .icon {
+ stroke: @fg;
+ stroke-width: 20;
+ fill: transparent;
+ }
+}
+
From 0ff95ed1f1a1919cee2fccbfac7005ca29b34d36 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Sun, 19 Mar 2017 08:49:38 +0000
Subject: [PATCH 43/87] Shrink movement profile
---
src/app/components/OutfittingSubpages.jsx | 2 +-
src/less/movement.less | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/app/components/OutfittingSubpages.jsx b/src/app/components/OutfittingSubpages.jsx
index 18f7ac47..d381ec72 100644
--- a/src/app/components/OutfittingSubpages.jsx
+++ b/src/app/components/OutfittingSubpages.jsx
@@ -96,7 +96,7 @@ export default class OutfittingSubpages extends TranslatedComponent {
{translate('movement profile')}
-
+
diff --git a/src/less/movement.less b/src/less/movement.less
index 30eb083a..796468f5 100644
--- a/src/less/movement.less
+++ b/src/less/movement.less
@@ -1,8 +1,8 @@
#movement {
svg {
- width: 100%;
- height: 100%;
+ width: 75%;
+ height: 75%;
stroke: @primary-disabled;
fill: @primary-disabled;
From 2736e1df79d75bc93b64e76b9bafe02272682ebc Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Sun, 19 Mar 2017 16:16:00 +0000
Subject: [PATCH 44/87] More metrics
---
src/app/components/Offence.jsx | 59 +++++++++++++++++++++----
src/app/components/PieChart.jsx | 4 +-
src/app/components/ShipSummaryTable.jsx | 6 ++-
src/app/i18n/en.js | 2 +
src/app/shipyard/Calculations.js | 46 +++++++++++++++----
src/app/shipyard/Ship.js | 38 ----------------
6 files changed, 97 insertions(+), 58 deletions(-)
diff --git a/src/app/components/Offence.jsx b/src/app/components/Offence.jsx
index 65327fe1..d3272c53 100644
--- a/src/app/components/Offence.jsx
+++ b/src/app/components/Offence.jsx
@@ -129,16 +129,37 @@ export default class Offence extends TranslatedComponent {
* @return {React.Component} contents
*/
render() {
- const { ship, wep } = this.props;
+ const { ship, opponent, wep, engagementrange } = this.props;
const { language, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { damage } = this.state;
const sortOrder = this._sortOrder;
+ const opponentShields = Calc.shieldMetrics(opponent, 4);
+ const opponentArmour = Calc.armourMetrics(opponent);
+
+ let absoluteShieldsSDps = 0;
+ let explosiveShieldsSDps = 0;
+ let kineticShieldsSDps = 0;
+ let thermalShieldsSDps = 0;
+ let absoluteArmourSDps = 0;
+ let explosiveArmourSDps = 0;
+ let kineticArmourSDps = 0;
+ let thermalArmourSDps = 0;
+
const rows = [];
for (let i = 0; i < damage.length; i++) {
const weapon = damage[i];
+ absoluteShieldsSDps += weapon.sdps.shields.absolute;
+ explosiveShieldsSDps += weapon.sdps.shields.explosive;
+ kineticShieldsSDps += weapon.sdps.shields.kinetic;
+ thermalShieldsSDps += weapon.sdps.shields.thermal;
+ absoluteArmourSDps += weapon.sdps.armour.absolute;
+ explosiveArmourSDps += weapon.sdps.armour.explosive;
+ kineticArmourSDps += weapon.sdps.armour.kinetic;
+ thermalArmourSDps += weapon.sdps.armour.thermal;
+
const effectivenessShieldsTooltipDetails = [];
effectivenessShieldsTooltipDetails.push({translate('range') + ' ' + formats.pct1(weapon.effectiveness.shields.range)}
);
effectivenessShieldsTooltipDetails.push({translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.shields.resistance)}
);
@@ -176,16 +197,25 @@ export default class Offence extends TranslatedComponent {
);
}
-// const armourDamageTakenData = [];
-// armourDamageTakenData.push({ value: Math.round(armour.absolute.total * 100), label: translate('absolute') });
-// armourDamageTakenData.push({ value: Math.round(armour.explosive.total * 100), label: translate('explosive') });
-// armourDamageTakenData.push({ value: Math.round(armour.kinetic.total * 100), label: translate('kinetic') });
-// armourDamageTakenData.push({ value: Math.round(armour.thermal.total * 100), label: translate('thermal') });
+ const totalShieldsSDps = absoluteShieldsSDps + explosiveShieldsSDps + kineticShieldsSDps + thermalShieldsSDps;
+ const totalArmourSDps = absoluteArmourSDps + explosiveArmourSDps + kineticArmourSDps + thermalArmourSDps;
+
+ const shieldsSDpsData = [];
+ shieldsSDpsData.push({ value: Math.round(absoluteShieldsSDps), label: translate('absolute') });
+ shieldsSDpsData.push({ value: Math.round(explosiveShieldsSDps), label: translate('explosive') });
+ shieldsSDpsData.push({ value: Math.round(kineticShieldsSDps), label: translate('kinetic') });
+ shieldsSDpsData.push({ value: Math.round(thermalShieldsSDps), label: translate('thermal') });
+
+ const armourSDpsData = [];
+ armourSDpsData.push({ value: Math.round(absoluteArmourSDps), label: translate('absolute') });
+ armourSDpsData.push({ value: Math.round(explosiveArmourSDps), label: translate('explosive') });
+ armourSDpsData.push({ value: Math.round(kineticArmourSDps), label: translate('kinetic') });
+ armourSDpsData.push({ value: Math.round(thermalArmourSDps), label: translate('thermal') });
return (
-
-
+
+
{translate('weapon')}
@@ -204,6 +234,19 @@ export default class Offence extends TranslatedComponent {
+
+
{translate('shield damage')}
+ {translate('PHRASE_TIME_TO_REMOVE_SHIELDS')} {totalShieldsSDps == 0 ? translate('never') : formats.time(opponentShields.total / totalShieldsSDps)}
+ {translate('PHRASE_TIME_TO_REMOVE_ARMOUR')} {totalArmourSDps == 0 ? translate('never') : formats.time(opponentArmour.total / totalArmourSDps)}
+
+
+
{translate('shield damage sources')}
+
+
+
+
{translate('armour damage sources')}
+
+
);
}
}
diff --git a/src/app/components/PieChart.jsx b/src/app/components/PieChart.jsx
index 98d25800..a6ef2e68 100644
--- a/src/app/components/PieChart.jsx
+++ b/src/app/components/PieChart.jsx
@@ -56,7 +56,9 @@ export default class PieChart extends Component {
const labelTranslate = `translate(${labelX * 1.5}, ${labelY * 1.5})`;
// Put the keys in a line with equal spacing
- const keyX = -width / 2 + (width / data.length) * (i + 0.5);
+ const nonZeroItems = data.filter(d => d.value != 0).length;
+ const thisItemIndex = data.slice(0, i + 1).filter(d => d.value != 0).length - 1;
+ const keyX = -width / 2 + (width / nonZeroItems) * (thisItemIndex + 0.5);
const keyTranslate = `translate(${keyX}, ${width * 0.45})`;
return (
diff --git a/src/app/components/ShipSummaryTable.jsx b/src/app/components/ShipSummaryTable.jsx
index a982b1df..370cb03b 100644
--- a/src/app/components/ShipSummaryTable.jsx
+++ b/src/app/components/ShipSummaryTable.jsx
@@ -24,7 +24,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
* @return {React.Component} Summary table
*/
render() {
- const { ship, fuel, eng, cargo, boost } = this.props;
+ const { ship, fuel, eng, wep, cargo, boost } = this.props;
let { language, tooltip, termtip } = this.context;
let translate = language.translate;
let u = language.units;
@@ -40,6 +40,8 @@ export default class ShipSummaryTable extends TranslatedComponent {
sgRecharge = time(ship.calcShieldRecharge());
}
+ const timeToDrain = Calc.timeToDrainWep(ship, wep);
+
return
@@ -74,7 +76,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
{ ship.canBoost() ? {int(ship.calcSpeed(eng, fuel, cargo, true))}{u['m/s']} : 0 }
{f1(ship.totalDps)}
{f1(ship.totalEps)}
- {ship.timeToDrain === Infinity ? '∞' : time(ship.timeToDrain)}
+ {timeToDrain === Infinity ? '∞' : time(timeToDrain)}
{f1(ship.totalHps)}
{int(ship.hardness)}
{int(ship.armour)}
diff --git a/src/app/i18n/en.js b/src/app/i18n/en.js
index 37683424..ad969a1b 100644
--- a/src/app/i18n/en.js
+++ b/src/app/i18n/en.js
@@ -48,6 +48,8 @@ export const terms = {
PHRASE_TIME_TO_LOSE_ARMOUR: 'Armour will hold for',
PHRASE_MODULE_PROTECTION_EXTERNAL: 'Protection for hardpoints',
PHRASE_MODULE_PROTECTION_INTERNAL: 'Protection for all other modules',
+ PHRASE_SHIELD_DAMAGE: 'Breakdown of sources for sustained DPS against shields',
+ PHRASE_ARMOUR_DAMAGE: 'Breakdown of sources for sustained DPS against armour',
TT_TIME_TO_LOSE_SHIELDS: 'Against sustained fire from all opponent\'s weapons',
TT_TIME_TO_LOSE_ARMOUR: 'Against sustained fire from all opponent\'s weapons',
diff --git a/src/app/shipyard/Calculations.js b/src/app/shipyard/Calculations.js
index 597b279d..ea77c8a0 100644
--- a/src/app/shipyard/Calculations.js
+++ b/src/app/shipyard/Calculations.js
@@ -710,6 +710,7 @@ export function _sustainedDps(ship, opponent, opponentShields, opponentArmour, e
* @returns {Object} Sustained DPS for shield and armour
*/
export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementrange) {
+ const opponentHasShields = opponentShields.generator ? true : false;
const weapon = {
damage: {
shields: {
@@ -730,7 +731,7 @@ export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour
effectiveness: {
shields: {
range: 1,
- sys: opponentShields.absolute.sys,
+ sys: opponentHasShields ? opponentShields.absolute.sys : 1,
resistance: 1
},
armour: {
@@ -761,27 +762,27 @@ export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour
let shieldsResistance = 0;
let armourResistance = 0;
if (m.getDamageDist().A) {
- weapon.damage.shields.absolute += sDps * m.getDamageDist().A * opponentShields.absolute.total;
+ weapon.damage.shields.absolute += sDps * m.getDamageDist().A * (opponentHasShields ? opponentShields.absolute.total : 1);
weapon.damage.armour.absolute += sDps * m.getDamageDist().A * armourMultiple * opponentArmour.absolute.total;
- shieldsResistance += m.getDamageDist().A * opponentShields.absolute.generator * opponentShields.absolute.boosters;
+ shieldsResistance += m.getDamageDist().A * (opponentHasShields ? opponentShields.absolute.generator * opponentShields.absolute.boosters : 1);
armourResistance += m.getDamageDist().A * opponentArmour.absolute.bulkheads * opponentArmour.absolute.reinforcement;
}
if (m.getDamageDist().E) {
- weapon.damage.shields.explosive += sDps * m.getDamageDist().E * opponentShields.explosive.total;
+ weapon.damage.shields.explosive += sDps * m.getDamageDist().E * (opponentHasShields ? opponentShields.explosive.total : 1);
weapon.damage.armour.explosive += sDps * m.getDamageDist().E * armourMultiple * opponentArmour.explosive.total;
- shieldsResistance += m.getDamageDist().E * opponentShields.explosive.generator * opponentShields.explosive.boosters;
+ shieldsResistance += m.getDamageDist().E * (opponentHasShields ? opponentShields.explosive.generator * opponentShields.explosive.boosters : 1);
armourResistance += m.getDamageDist().E * opponentArmour.explosive.bulkheads * opponentArmour.explosive.reinforcement;
}
if (m.getDamageDist().K) {
- weapon.damage.shields.kinetic += sDps * m.getDamageDist().K * opponentShields.kinetic.total;
+ weapon.damage.shields.kinetic += sDps * m.getDamageDist().K * (opponentHasShields ? opponentShields.kinetic.total : 1);
weapon.damage.armour.kinetic += sDps * m.getDamageDist().K * armourMultiple * opponentArmour.kinetic.total;
- shieldsResistance += m.getDamageDist().K * opponentShields.kinetic.generator * opponentShields.kinetic.boosters;
+ shieldsResistance += m.getDamageDist().K * (opponentHasShields ? opponentShields.kinetic.generator * opponentShields.kinetic.boosters : 1);
armourResistance += m.getDamageDist().K * opponentArmour.kinetic.bulkheads * opponentArmour.kinetic.reinforcement;
}
if (m.getDamageDist().T) {
- weapon.damage.shields.thermal += sDps * m.getDamageDist().T * opponentShields.thermal.total;
+ weapon.damage.shields.thermal += sDps * m.getDamageDist().T * (opponentHasShields ? opponentShields.thermal.total : 1);
weapon.damage.armour.thermal += sDps * m.getDamageDist().T * armourMultiple * opponentArmour.thermal.total;
- shieldsResistance += m.getDamageDist().T * opponentShields.thermal.generator * opponentShields.thermal.boosters;
+ shieldsResistance += m.getDamageDist().T * (opponentHasShields ? opponentShields.thermal.generator * opponentShields.thermal.boosters : 1);
armourResistance += m.getDamageDist().T * opponentArmour.thermal.bulkheads * opponentArmour.thermal.reinforcement;
}
weapon.damage.shields.total = weapon.damage.shields.absolute + weapon.damage.shields.explosive + weapon.damage.shields.kinetic + weapon.damage.shields.thermal;
@@ -794,3 +795,30 @@ export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour
weapon.effectiveness.armour.total = weapon.effectiveness.armour.range * weapon.effectiveness.armour.resistance * weapon.effectiveness.armour.hardness;
return weapon;
}
+
+/**
+ * Calculate time to drain WEP capacitor
+ * @param {object} ship The ship
+ * @param {number} wep Pips to WEP
+ * @return The time to drain the WEP capacitor, in seconds
+ */
+export function timeToDrainWep(ship, wep) {
+ let totalSEps = 0;
+
+ for (let slotNum in ship.hardpoints) {
+ const slot = ship.hardpoints[slotNum];
+ if (slot.maxClass > 0 && slot.m && slot.enabled && slot.type === 'WEP' && slot.m.getDps()) {
+ totalSEps += slot.m.getClip() ? (slot.m.getClip() * slot.m.getEps() / slot.m.getRoF()) / ((slot.m.getClip() / slot.m.getRoF()) + slot.m.getReload()) : slot.m.getEps();
+ }
+ }
+
+ // Calculate the drain time
+ const drainPerSecond = totalSEps - ship.standard[4].m.getWeaponsRechargeRate() * wep / 4;
+ if (drainPerSecond <= 0) {
+ // Can fire forever
+ return Infinity;
+ } else {
+ const initialCharge = ship.standard[4].m.getWeaponsCapacity();
+ return initialCharge / drainPerSecond;
+ }
+}
diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js
index e36cba54..a1c11650 100755
--- a/src/app/shipyard/Ship.js
+++ b/src/app/shipyard/Ship.js
@@ -453,7 +453,6 @@ export default class Ship {
.recalculateDps()
.recalculateEps()
.recalculateHps()
- .recalculateTtd()
.updateMovement();
}
@@ -522,15 +521,11 @@ export default class Ship {
this.recalculateDps();
this.recalculateHps();
this.recalculateEps();
- this.recalculateTtd();
} else if (name === 'explres' || name === 'kinres' || name === 'thermres') {
m.setModValue(name, value, sentfromui);
// Could be for shields or armour
this.recalculateArmour();
this.recalculateShield();
- } else if (name === 'wepcap' || name === 'weprate') {
- m.setModValue(name, value, sentfromui);
- this.recalculateTtd();
} else if (name === 'engcap') {
m.setModValue(name, value, sentfromui);
// Might have resulted in a change in boostability
@@ -668,7 +663,6 @@ export default class Ship {
.recalculateDps()
.recalculateEps()
.recalculateHps()
- .recalculateTtd()
.updateMovement();
}
@@ -850,7 +844,6 @@ export default class Ship {
if (slot.m.getEps()) {
this.recalculateEps();
- this.recalculateTtd();
}
}
}
@@ -926,7 +919,6 @@ export default class Ship {
}
if (epsChanged) {
this.recalculateEps();
- this.recalculateTtd();
}
if (hpsChanged) {
this.recalculateHps();
@@ -934,9 +926,6 @@ export default class Ship {
if (powerGeneratedChange) {
this.updatePowerGenerated();
}
- if (powerDistributorChange) {
- this.recalculateTtd();
- }
if (powerUsedChange) {
this.updatePowerUsed();
}
@@ -974,33 +963,6 @@ export default class Ship {
return val;
}
- /**
- * Calculate time to drain WEP capacitor
- * @return {this} The ship instance (for chaining operations)
- */
- recalculateTtd() {
- let totalSEps = 0;
-
- for (let slotNum in this.hardpoints) {
- const slot = this.hardpoints[slotNum];
- if (slot.m && slot.enabled && slot.type === 'WEP' && slot.m.getDps()) {
- totalSEps += slot.m.getClip() ? (slot.m.getClip() * slot.m.getEps() / slot.m.getRoF()) / ((slot.m.getClip() / slot.m.getRoF()) + slot.m.getReload()) : slot.m.getEps();
- }
- }
-
- // Calculate the drain time
- const drainPerSecond = totalSEps - this.standard[4].m.getWeaponsRechargeRate();
- if (drainPerSecond <= 0) {
- // Can fire forever
- this.timeToDrain = Infinity;
- } else {
- const initialCharge = this.standard[4].m.getWeaponsCapacity();
- this.timeToDrain = initialCharge / drainPerSecond;
- }
-
- return this;
- }
-
/**
* Calculate damage per second and related items for weapons
* @return {this} The ship instance (for chaining operations)
From 6afd80002c0347f1e82ab9ee287c835b9af1af86 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Sun, 19 Mar 2017 17:40:41 +0000
Subject: [PATCH 45/87] Tidy-ups
---
src/app/components/Defence.jsx | 6 +++--
src/app/components/Offence.jsx | 12 ++++++++--
src/app/i18n/en.js | 6 +++++
src/app/shipyard/Calculations.js | 39 +++++++++++++++++++++++++++++---
4 files changed, 56 insertions(+), 7 deletions(-)
diff --git a/src/app/components/Defence.jsx b/src/app/components/Defence.jsx
index 902e1bea..56a1bafc 100644
--- a/src/app/components/Defence.jsx
+++ b/src/app/components/Defence.jsx
@@ -55,6 +55,8 @@ export default class Defence extends TranslatedComponent {
const { formats, translate, units } = language;
const { shield, armour, shielddamage, armourdamage } = this.state;
+ const pd = ship.standard[4].m;
+
const shieldSourcesData = [];
const effectiveShieldData = [];
const shieldDamageTakenData = [];
@@ -153,7 +155,7 @@ export default class Defence extends TranslatedComponent {
{translate('shield metrics')}
{shieldTooltipDetails})} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw shield strength')} {formats.int(shield.total)}{units.MJ}
- {translate('PHRASE_TIME_TO_LOSE_SHIELDS')} {shielddamage.totalsdps == 0 ? translate('ever') : formats.time(shield.total / shielddamage.totalsdps)}
+ {translate('PHRASE_TIME_TO_LOSE_SHIELDS')} {shielddamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(shield.total, shielddamage.totalsdps, shielddamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate()))}
{translate('PHRASE_TIME_TO_RECOVER_SHIELDS')} {shield.recover === Math.Inf ? translate('never') : formats.time(shield.recover)}
{translate('PHRASE_TIME_TO_RECHARGE_SHIELDS')} {shield.recharge === Math.Inf ? translate('never') : formats.time(shield.recharge)}
@@ -174,7 +176,7 @@ export default class Defence extends TranslatedComponent {
{translate('armour metrics')}
{armourTooltipDetails} )} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw armour strength')} {formats.int(armour.total)}
- {translate('PHRASE_TIME_TO_LOSE_ARMOUR')} {armourdamage.totalsdps == 0 ? translate('ever') : formats.time(armour.total / armourdamage.totalsdps)}
+ {translate('PHRASE_TIME_TO_LOSE_ARMOUR')} {armourdamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(armour.total, armourdamage.totalsdps, armourdamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate()))}
{translate('raw module armour')} {formats.int(armour.modulearmour)}
{translate('PHRASE_MODULE_PROTECTION_EXTERNAL')} {formats.pct1(armour.moduleprotection / 2)}
{translate('PHRASE_MODULE_PROTECTION_INTERNAL')} {formats.pct1(armour.moduleprotection)}
diff --git a/src/app/components/Offence.jsx b/src/app/components/Offence.jsx
index d3272c53..52e0791c 100644
--- a/src/app/components/Offence.jsx
+++ b/src/app/components/Offence.jsx
@@ -135,6 +135,8 @@ export default class Offence extends TranslatedComponent {
const { damage } = this.state;
const sortOrder = this._sortOrder;
+ const pd = ship.standard[4].m;
+
const opponentShields = Calc.shieldMetrics(opponent, 4);
const opponentArmour = Calc.armourMetrics(opponent);
@@ -147,10 +149,13 @@ export default class Offence extends TranslatedComponent {
let kineticArmourSDps = 0;
let thermalArmourSDps = 0;
+ let totalSEps = 0;
+
const rows = [];
for (let i = 0; i < damage.length; i++) {
const weapon = damage[i];
+ totalSEps += weapon.seps;
absoluteShieldsSDps += weapon.sdps.shields.absolute;
explosiveShieldsSDps += weapon.sdps.shields.explosive;
kineticShieldsSDps += weapon.sdps.shields.kinetic;
@@ -212,6 +217,9 @@ export default class Offence extends TranslatedComponent {
armourSDpsData.push({ value: Math.round(kineticArmourSDps), label: translate('kinetic') });
armourSDpsData.push({ value: Math.round(thermalArmourSDps), label: translate('thermal') });
+ const timeToDepleteShields = Calc.timeToDeplete(opponentShields.total, totalShieldsSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4));
+ const timeToDepleteArmour = Calc.timeToDeplete(opponentArmour.total, totalArmourSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4));
+
return (
@@ -236,8 +244,8 @@ export default class Offence extends TranslatedComponent {
{translate('shield damage')}
- {translate('PHRASE_TIME_TO_REMOVE_SHIELDS')} {totalShieldsSDps == 0 ? translate('never') : formats.time(opponentShields.total / totalShieldsSDps)}
- {translate('PHRASE_TIME_TO_REMOVE_ARMOUR')} {totalArmourSDps == 0 ? translate('never') : formats.time(opponentArmour.total / totalArmourSDps)}
+ {translate('PHRASE_TIME_TO_REMOVE_SHIELDS')} {timeToDepleteShields === Infinity ? translate('never') : formats.time(timeToDepleteShields)}
+ {translate('PHRASE_TIME_TO_REMOVE_ARMOUR')} {timeToDepleteArmour === Infinity ? translate('never') : formats.time(timeToDepleteArmour)}
{translate('shield damage sources')}
diff --git a/src/app/i18n/en.js b/src/app/i18n/en.js
index ad969a1b..cf986c7b 100644
--- a/src/app/i18n/en.js
+++ b/src/app/i18n/en.js
@@ -44,6 +44,8 @@ export const terms = {
PHRASE_TIME_TO_RECHARGE_SHIELDS: 'Shields will recharge in',
PHRASE_SHIELD_SOURCES: 'Breakdown of the supply of shield energy',
PHRASE_EFFECTIVE_SHIELD: 'Effective shield strength against different damage types',
+ PHRASE_ARMOUR_SOURCES: 'Breakdown of the supply of armour',
+ PHRASE_EFFECTIVE_ARMOUR: 'Effective armour strength against different damage types',
PHRASE_DAMAGE_TAKEN: '% of raw damage taken for different damage types',
PHRASE_TIME_TO_LOSE_ARMOUR: 'Armour will hold for',
PHRASE_MODULE_PROTECTION_EXTERNAL: 'Protection for hardpoints',
@@ -51,6 +53,10 @@ export const terms = {
PHRASE_SHIELD_DAMAGE: 'Breakdown of sources for sustained DPS against shields',
PHRASE_ARMOUR_DAMAGE: 'Breakdown of sources for sustained DPS against armour',
+ PHRASE_TIME_TO_REMOVE_SHIELDS: 'Will remove shields in',
+ TT_TIME_TO_REMOVE_SHIELDS: 'With sustained fire by all weapons',
+ PHRASE_TIME_TO_REMOVE_ARMOUR: 'Will remove armour in',
+ TT_TIME_TO_REMOVE_ARMOUR: 'With sustained fire by all weapons',
TT_TIME_TO_LOSE_SHIELDS: 'Against sustained fire from all opponent\'s weapons',
TT_TIME_TO_LOSE_ARMOUR: 'Against sustained fire from all opponent\'s weapons',
TT_MODULE_ARMOUR: 'Armour protecting against module damage',
diff --git a/src/app/shipyard/Calculations.js b/src/app/shipyard/Calculations.js
index ea77c8a0..183ec30e 100644
--- a/src/app/shipyard/Calculations.js
+++ b/src/app/shipyard/Calculations.js
@@ -551,7 +551,8 @@ export function defenceMetrics(ship, opponent, sys, engagementrange) {
explosivesdps: sustainedDps.shieldsdps.explosive,
kineticsdps: sustainedDps.shieldsdps.kinetic,
thermalsdps: sustainedDps.shieldsdps.thermal,
- totalsdps: sustainedDps.shieldsdps.absolute + sustainedDps.shieldsdps.explosive + sustainedDps.shieldsdps.kinetic + sustainedDps.shieldsdps.thermal
+ totalsdps: sustainedDps.shieldsdps.absolute + sustainedDps.shieldsdps.explosive + sustainedDps.shieldsdps.kinetic + sustainedDps.shieldsdps.thermal,
+ totalseps: sustainedDps.eps
} : {};
const armourdamage = {
@@ -559,7 +560,8 @@ export function defenceMetrics(ship, opponent, sys, engagementrange) {
explosivesdps: sustainedDps.armoursdps.explosive,
kineticsdps: sustainedDps.armoursdps.kinetic,
thermalsdps: sustainedDps.armoursdps.thermal,
- totalsdps: sustainedDps.armoursdps.absolute + sustainedDps.armoursdps.explosive + sustainedDps.armoursdps.kinetic + sustainedDps.armoursdps.thermal
+ totalsdps: sustainedDps.armoursdps.absolute + sustainedDps.armoursdps.explosive + sustainedDps.armoursdps.kinetic + sustainedDps.armoursdps.thermal,
+ totalseps: sustainedDps.eps
};
return { shield, armour, shielddamage, armourdamage };
@@ -603,6 +605,7 @@ export function offenceMetrics(ship, opponent, wep, engagementrange) {
classRating,
engineering,
sdps: weaponSustainedDps.damage,
+ seps: weaponSustainedDps.eps,
effectiveness: weaponSustainedDps.effectiveness
});
}
@@ -682,6 +685,8 @@ export function _sustainedDps(ship, opponent, opponentShields, opponentArmour, e
thermal: 0
};
+ let eps = 0;
+
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].m && ship.hardpoints[i].enabled && ship.hardpoints[i].maxClass > 0) {
const m = ship.hardpoints[i].m;
@@ -694,10 +699,11 @@ export function _sustainedDps(ship, opponent, opponentShields, opponentArmour, e
armoursdps.explosive += sustainedDps.damage.armour.explosive;
armoursdps.kinetic += sustainedDps.damage.armour.kinetic;
armoursdps.thermal += sustainedDps.damage.armour.thermal;
+ eps += sustainedDps.eps;
}
}
- return { shieldsdps, armoursdps };
+ return { shieldsdps, armoursdps, eps };
}
/**
@@ -712,6 +718,7 @@ export function _sustainedDps(ship, opponent, opponentShields, opponentArmour, e
export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementrange) {
const opponentHasShields = opponentShields.generator ? true : false;
const weapon = {
+ eps: 0,
damage: {
shields: {
absolute: 0,
@@ -742,6 +749,9 @@ export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour
}
};
+ // EPS
+ weapon.eps = m.getClip() ? (m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getEps();
+
// Initial sustained DPS
let sDps = m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getDps();
@@ -822,3 +832,26 @@ export function timeToDrainWep(ship, wep) {
return initialCharge / drainPerSecond;
}
}
+
+/**
+ * Calculate the time to deplete an amount of shields or armour
+ */
+export function timeToDeplete(amount, dps, eps, capacity, recharge) {
+ const drainPerSecond = eps - recharge;
+ if (drainPerSecond <= 0) {
+ // Simple result
+ return amount / dps;
+ } else {
+ // We are draining the capacitor, but can we deplete before we run out
+ const timeToDrain = capacity / drainPerSecond;
+ const depletedBeforeDrained = dps * timeToDrain;
+ if (depletedBeforeDrained >= amount) {
+ return amount / dps;
+ } else {
+ const restToDeplete = amount - depletedBeforeDrained;
+ // We delete the rest at the reduced rate
+ const reducedDps = dps * (recharge / eps);
+ return timeToDrain + (restToDeplete / reducedDps);
+ }
+ }
+}
From cadd699bdfe3f802308896cbe6273ca7b9df377c Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Sun, 19 Mar 2017 18:53:22 +0000
Subject: [PATCH 46/87] Manual merge of #90
---
src/app/components/Header.jsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/app/components/Header.jsx b/src/app/components/Header.jsx
index ff880f1b..c36660da 100644
--- a/src/app/components/Header.jsx
+++ b/src/app/components/Header.jsx
@@ -356,7 +356,7 @@ export default class Header extends TranslatedComponent {
let comps = Object.keys(Persist.getComparisons()).sort();
for (let name of comps) {
- comparisons.push({name} );
+ comparisons.push({name} );
}
} else {
comparisons = {translate('none created')} ;
From 117028875fe4f3912c7dca1cf786a9c0d712d539 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Sun, 19 Mar 2017 18:54:38 +0000
Subject: [PATCH 47/87] Name change
---
src/app/components/Offence.jsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/app/components/Offence.jsx b/src/app/components/Offence.jsx
index 52e0791c..1c823c3c 100644
--- a/src/app/components/Offence.jsx
+++ b/src/app/components/Offence.jsx
@@ -243,7 +243,7 @@ export default class Offence extends TranslatedComponent {
-
{translate('shield damage')}
+ {translate('damage to opponent')}
{translate('PHRASE_TIME_TO_REMOVE_SHIELDS')} {timeToDepleteShields === Infinity ? translate('never') : formats.time(timeToDepleteShields)}
{translate('PHRASE_TIME_TO_REMOVE_ARMOUR')} {timeToDepleteArmour === Infinity ? translate('never') : formats.time(timeToDepleteArmour)}
From 86df6f20f6c213620d17e49893a555c8e79a6826 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Sun, 19 Mar 2017 20:18:31 +0000
Subject: [PATCH 48/87] Ensure ship picker z index is correct
---
src/less/shippicker.less | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/less/shippicker.less b/src/less/shippicker.less
index 982bf5ad..bd5c4c17 100755
--- a/src/less/shippicker.less
+++ b/src/less/shippicker.less
@@ -61,7 +61,7 @@
background-color: @bgBlack;
font-size: 0.9em;
overflow-y: auto;
- z-index: 0;
+ z-index: 1;
-webkit-overflow-scrolling: touch;
max-height: 500px;
From e278babee8c78163aa94f96d778ec343ebbe62ca Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Sun, 19 Mar 2017 22:34:19 +0000
Subject: [PATCH 49/87] Use correct values for boost/speed when working out
boost factor
---
src/app/shipyard/Ship.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js
index a1c11650..ff564bbb 100755
--- a/src/app/shipyard/Ship.js
+++ b/src/app/shipyard/Ship.js
@@ -181,7 +181,7 @@ export default class Ship {
* @return {Number} Speed
*/
calcSpeed(eng, fuel, cargo, boost) {
- return Calc.calcSpeed(this.unladenMass + fuel + cargo, this.speed, this.standard[1].m, this.pipSpeed, eng, this.topBoost / this.topSpeed, boost);
+ return Calc.calcSpeed(this.unladenMass + fuel + cargo, this.speed, this.standard[1].m, this.pipSpeed, eng, this.boost / this.speed, boost);
}
/**
From 2f5d123f029f8933b863b7056968ee53e616f3ed Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Mon, 20 Mar 2017 07:48:44 +0000
Subject: [PATCH 50/87] Change chart label colour to black; persist select
outfitting subpage
---
src/app/components/OutfittingSubpages.jsx | 14 ++++++++++----
src/app/components/PieChart.jsx | 4 ++--
src/app/components/VerticalBarChart.jsx | 6 +++---
src/app/stores/Persist.js | 18 ++++++++++++++++++
4 files changed, 33 insertions(+), 9 deletions(-)
diff --git a/src/app/components/OutfittingSubpages.jsx b/src/app/components/OutfittingSubpages.jsx
index d381ec72..e6d5f663 100644
--- a/src/app/components/OutfittingSubpages.jsx
+++ b/src/app/components/OutfittingSubpages.jsx
@@ -2,8 +2,7 @@ import React from 'react';
import cn from 'classnames';
import { Ships } from 'coriolis-data/dist';
import Ship from '../shipyard/Ship';
-import { Insurance } from '../shipyard/Constants';
-import { slotName, slotComparator } from '../utils/SlotFunctions';
+import Persist from '../stores/Persist';
import TranslatedComponent from './TranslatedComponent';
import PowerManagement from './PowerManagement';
import CostSection from './CostSection';
@@ -42,9 +41,12 @@ export default class OutfittingSubpages extends TranslatedComponent {
constructor(props) {
super(props);
this._powerTab = this._powerTab.bind(this);
+ this._profilesTab = this._profilesTab.bind(this);
+ this._offenceTab = this._offenceTab.bind(this);
+ this._defenceTab = this._defenceTab.bind(this);
this.state = {
- tab: 'power'
+ tab: Persist.getOutfittingTab() || 'power',
};
}
@@ -62,6 +64,7 @@ export default class OutfittingSubpages extends TranslatedComponent {
*/
_powerTab() {
let { ship, buildName, code, onChange } = this.props;
+ Persist.setOutfittingTab('power');
return
@@ -77,6 +80,7 @@ export default class OutfittingSubpages extends TranslatedComponent {
const { ship, opponent, cargo, fuel, eng, boost, engagementRange } = this.props;
const { translate } = this.context.language;
let realBoost = boost && ship.canBoost();
+ Persist.setOutfittingTab('profiles');
const engineProfileMarker = `${ship.toString()}:${cargo}:${fuel}:${eng}:${realBoost}`;
const fsdProfileMarker = `${ship.toString()}:${cargo}:${fuel}`;
@@ -117,6 +121,7 @@ export default class OutfittingSubpages extends TranslatedComponent {
*/
_offenceTab() {
const { ship, sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild } = this.props;
+ Persist.setOutfittingTab('offence');
const marker = `${ship.toString()}:${opponent.name}:${opponentBuild}:${engagementRange}`;
@@ -131,6 +136,7 @@ export default class OutfittingSubpages extends TranslatedComponent {
*/
_defenceTab() {
const { ship, sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild } = this.props;
+ Persist.setOutfittingTab('defence');
const marker = `${ship.toString()}:${opponent.name}:${opponentBuild}:${engagementRange}`;
@@ -144,7 +150,7 @@ export default class OutfittingSubpages extends TranslatedComponent {
* @return {React.Component} Contents
*/
render() {
- const tab = this.state.tab || 'power';
+ const tab = this.state.tab;
const translate = this.context.language.translate;
let tabSection;
diff --git a/src/app/components/PieChart.jsx b/src/app/components/PieChart.jsx
index a6ef2e68..c5e5435e 100644
--- a/src/app/components/PieChart.jsx
+++ b/src/app/components/PieChart.jsx
@@ -2,8 +2,8 @@ import React, { Component } from 'react';
import Measure from 'react-measure';
import * as d3 from 'd3';
-const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#519032', '#D5420D'];
-const LABEL_COLOUR = '#FFFFFF';
+const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
+const LABEL_COLOUR = '#000000';
/**
* A pie chart
diff --git a/src/app/components/VerticalBarChart.jsx b/src/app/components/VerticalBarChart.jsx
index e6fa37cc..617b1b4b 100644
--- a/src/app/components/VerticalBarChart.jsx
+++ b/src/app/components/VerticalBarChart.jsx
@@ -3,8 +3,8 @@ import Measure from 'react-measure';
import * as d3 from 'd3';
import TranslatedComponent from './TranslatedComponent';
-const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#519032', '#D5420D'];
-const LABEL_COLOUR = '#FFFFFF';
+const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
+const LABEL_COLOUR = '#000000';
const margin = { top: 10, right: 0, bottom: 0, left: 55 };
@@ -105,7 +105,7 @@ export default class VerticalBarChart extends TranslatedComponent {
.attr('x', 100)
.attr('y', 100)
.attr('stroke-width', '0px')
- .attr('fill', '#ffffff')
+ .attr('fill', LABEL_COLOUR)
.attr('x', d => this.x(d.label) + this.x.bandwidth() / 2)
.attr('y', d => this.y(d.value) + 15)
.text(d => d.value);
diff --git a/src/app/stores/Persist.js b/src/app/stores/Persist.js
index 4ef740f6..e1ea7b82 100644
--- a/src/app/stores/Persist.js
+++ b/src/app/stores/Persist.js
@@ -5,6 +5,7 @@ const LS_KEY_BUILDS = 'builds';
const LS_KEY_COMPARISONS = 'comparisons';
const LS_KEY_LANG = 'NG_TRANSLATE_LANG_KEY';
const LS_KEY_COST_TAB = 'costTab';
+const LS_KEY_OUTFITTING_TAB = 'outfittingTab';
const LS_KEY_INSURANCE = 'insurance';
const LS_KEY_SHIP_DISCOUNT = 'shipDiscount';
const LS_KEY_MOD_DISCOUNT = 'moduleDiscount';
@@ -98,6 +99,7 @@ export class Persist extends EventEmitter {
this.builds = buildJson && typeof buildJson == 'object' ? buildJson : {};
this.comparisons = comparisonJson && typeof comparisonJson == 'object' ? comparisonJson : {};
this.costTab = _getString(LS_KEY_COST_TAB);
+ this.outfittingTab = _getString(LS_KEY_OUTFITTING_TAB);
this.state = _get(LS_KEY_STATE);
this.sizeRatio = _get(LS_KEY_SIZE_RATIO) || 1;
this.tooltipsEnabled = tips === null ? true : tips;
@@ -472,6 +474,22 @@ export class Persist extends EventEmitter {
return this.costTab;
}
+ /**
+ * Persist selected outfitting tab
+ * @param {string} tabName Cost tab name
+ */
+ setOutfittingTab(tabName) {
+ this.outfittingTab = tabName;
+ _put(LS_KEY_OUTFITTING_TAB, tabName);
+ }
+ /**
+ * Get the current outfitting tab
+ * @return {string} the current outfitting tab
+ */
+ getOutfittingTab() {
+ return this.outfittingTab;
+ }
+
/**
* Retrieve the last router state from local storage
* @return {Object} state State object containing state name and params
From 73a75c69a3858bc88fc22dc7e2f6230d1e48efe6 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Mon, 20 Mar 2017 13:52:24 +0000
Subject: [PATCH 51/87] Make various components stateless
---
.../anaconda-test-detailed-export-v4.json | 1 -
package.json | 1 +
src/app/components/Boost.jsx | 34 +--
src/app/components/Cargo.jsx | 53 +---
src/app/components/EngagementRange.jsx | 56 +---
src/app/components/Fuel.jsx | 50 +---
src/app/components/OutfittingSubpages.jsx | 7 +-
src/app/components/Pips.jsx | 84 +-----
src/app/components/ShipPicker.jsx | 52 ++--
src/app/components/ShipSummaryTable.jsx | 20 +-
src/app/pages/OutfittingPage.jsx | 242 ++++++++++++++----
src/app/shipyard/Calculations.js | 12 +-
12 files changed, 286 insertions(+), 326 deletions(-)
diff --git a/__tests__/fixtures/anaconda-test-detailed-export-v4.json b/__tests__/fixtures/anaconda-test-detailed-export-v4.json
index 93aa7a93..65893b57 100644
--- a/__tests__/fixtures/anaconda-test-detailed-export-v4.json
+++ b/__tests__/fixtures/anaconda-test-detailed-export-v4.json
@@ -320,7 +320,6 @@
"shieldExplRes": 0.5,
"shieldKinRes": 0.4,
"shieldThermRes": -0.2,
- "timeToDrain": 7.04,
"crew": 3
}
}
diff --git a/package.json b/package.json
index efc71869..c934693c 100644
--- a/package.json
+++ b/package.json
@@ -76,6 +76,7 @@
"json-loader": "^0.5.3",
"less": "^2.5.3",
"less-loader": "^2.2.1",
+ "react-addons-perf": "^15.4.2",
"react-addons-test-utils": "^15.0.1",
"react-measure": "^1.4.6",
"react-testutils-additions": "^15.1.0",
diff --git a/src/app/components/Boost.jsx b/src/app/components/Boost.jsx
index cc39d28e..10e60927 100644
--- a/src/app/components/Boost.jsx
+++ b/src/app/components/Boost.jsx
@@ -17,6 +17,7 @@ export default class Boost extends TranslatedComponent {
static propTypes = {
marker: React.PropTypes.string.isRequired,
ship: React.PropTypes.object.isRequired,
+ boost: React.PropTypes.bool.isRequired,
onChange: React.PropTypes.func.isRequired
};
@@ -27,14 +28,10 @@ export default class Boost extends TranslatedComponent {
*/
constructor(props, context) {
super(props);
- const ship = props.ship;
+ const { ship, boost } = props;
this._keyDown = this._keyDown.bind(this);
this._toggleBoost = this._toggleBoost.bind(this);
-
- this.state = {
- boost: false
- };
}
/**
@@ -51,25 +48,6 @@ export default class Boost extends TranslatedComponent {
document.removeEventListener('keydown', this._keyDown);
}
- /**
- * Update values if we change ship
- * @param {Object} nextProps Incoming/Next properties
- * @returns {boolean} Returns true if the component should be rerendered
- */
- componentWillReceiveProps(nextProps) {
- const { boost } = this.state;
- const nextShip = nextProps.ship;
-
- const nextBoost = nextShip.canBoost() ? boost : false;
- if (nextBoost != boost) {
- this.setState({
- boost: nextBoost
- });
- }
-
- return true;
- }
-
/**
* Handle Key Down
* @param {Event} e Keyboard Event
@@ -91,10 +69,7 @@ export default class Boost extends TranslatedComponent {
* Toggle the boost feature
*/
_toggleBoost() {
- let { boost } = this.state;
- boost = !boost;
- this.setState({ boost });
- this.props.onChange(boost);
+ this.props.onChange(!this.props.boost);
}
/**
@@ -103,8 +78,7 @@ export default class Boost extends TranslatedComponent {
*/
render() {
const { formats, translate, units } = this.context.language;
- const { ship } = this.props;
- const { boost } = this.state;
+ const { ship, boost } = this.props;
// TODO disable if ship cannot boost
return (
diff --git a/src/app/components/Cargo.jsx b/src/app/components/Cargo.jsx
index bad1e2cf..96bbac83 100644
--- a/src/app/components/Cargo.jsx
+++ b/src/app/components/Cargo.jsx
@@ -9,7 +9,8 @@ import Slider from '../components/Slider';
*/
export default class Cargo extends TranslatedComponent {
static propTypes = {
- ship: React.PropTypes.object.isRequired,
+ cargo: React.PropTypes.number.isRequired,
+ cargoCapacity: React.PropTypes.number.isRequired,
onChange: React.PropTypes.func.isRequired
};
@@ -21,35 +22,7 @@ export default class Cargo extends TranslatedComponent {
constructor(props, context) {
super(props);
- const ship = this.props.ship;
-
- this.state = {
- cargoCapacity: ship.cargoCapacity,
- cargoLevel: 0,
- };
- }
-
- /**
- * Update the state if our ship changes
- * @param {Object} nextProps Incoming/Next properties
- * @return {boolean} Returns true if the component should be rerendered
- */
- componentWillReceiveProps(nextProps) {
- const { cargoLevel, cargoCapacity } = this.state;
- const nextCargoCapacity = nextProps.ship.cargoCapacity;
-
- if (nextCargoCapacity != cargoCapacity) {
- // We keep the absolute cargo amount the same if possible so recalculate the relative level
- const nextCargoLevel = Math.min((cargoLevel * cargoCapacity) / nextCargoCapacity, 1);
-
- this.setState({ cargoLevel: nextCargoLevel, cargoCapacity: nextCargoCapacity });
-
- // Notify if appropriate
- if (nextCargoLevel * nextCargoCapacity != cargoLevel * cargoCapacity) {
- this.props.onChange(Math.round(nextCargoLevel * nextCargoCapacity));
- }
- }
- return true;
+ this._cargoChange = this._cargoChange.bind(this);
}
/**
@@ -57,14 +30,12 @@ export default class Cargo extends TranslatedComponent {
* @param {number} cargoLevel percentage level from 0 to 1
*/
_cargoChange(cargoLevel) {
- const { cargoCapacity } = this.state;
+ const { cargo, cargoCapacity } = this.props;
if (cargoCapacity > 0) {
- // We round the cargo level to a suitable value given the capacity
- cargoLevel = Math.round(cargoLevel * cargoCapacity) / cargoCapacity;
-
- if (cargoLevel != this.state.cargoLevel) {
- this.setState({ cargoLevel });
- this.props.onChange(Math.round(cargoLevel * cargoCapacity));
+ // We round the cargo to whole number of tonnes
+ const newCargo = Math.round(cargoLevel * cargoCapacity);
+ if (newCargo != cargo) {
+ this.props.onChange(newCargo);
}
}
}
@@ -76,20 +47,20 @@ export default class Cargo extends TranslatedComponent {
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
- const { cargoLevel, cargoCapacity } = this.state;
+ const { cargo, cargoCapacity } = this.props;
return (
- {translate('cargo carried')}: {formats.int(cargoLevel * cargoCapacity)}{units.T}
+ {translate('cargo carried')}: {formats.int(cargo)}{units.T}
- {translate('engagement range')}: {formats.int(rangeLevel * maxRange)}{translate('m')}
+ {translate('engagement range')}: {formats.int(engagementRange)}{translate('m')}
@@ -116,7 +86,7 @@ export default class Range extends TranslatedComponent {
axis={true}
onChange={this._rangeChange.bind(this)}
axisUnit={translate('m')}
- percent={rangeLevel}
+ percent={engagementRange / maxRange}
max={maxRange}
scale={sizeRatio}
onResize={onWindowResize}
diff --git a/src/app/components/Fuel.jsx b/src/app/components/Fuel.jsx
index c3f9cc6b..8c8ea8d7 100644
--- a/src/app/components/Fuel.jsx
+++ b/src/app/components/Fuel.jsx
@@ -9,7 +9,8 @@ import Slider from '../components/Slider';
*/
export default class Fuel extends TranslatedComponent {
static propTypes = {
- ship: React.PropTypes.object.isRequired,
+ fuel: React.PropTypes.number.isRequired,
+ fuelCapacity: React.PropTypes.number.isRequired,
onChange: React.PropTypes.func.isRequired
};
@@ -21,35 +22,7 @@ export default class Fuel extends TranslatedComponent {
constructor(props, context) {
super(props);
- const ship = this.props.ship;
-
- this.state = {
- fuelCapacity: ship.fuelCapacity,
- fuelLevel: 1,
- };
- }
-
- /**
- * Update the state if our ship changes
- * @param {Object} nextProps Incoming/Next properties
- * @return {boolean} Returns true if the component should be rerendered
- */
- componentWillReceiveProps(nextProps) {
- const { fuelLevel, fuelCapacity } = this.state;
- const nextFuelCapacity = nextProps.ship.fuelCapacity;
-
- if (nextFuelCapacity != fuelCapacity) {
- // We keep the absolute fuel amount the same if possible so recalculate the relative level
- const nextFuelLevel = Math.min((fuelLevel * fuelCapacity) / nextFuelCapacity, 1);
-
- this.setState({ fuelLevel: nextFuelLevel, fuelCapacity: nextFuelCapacity });
-
- // Notify if appropriate
- if (nextFuelLevel * nextFuelCapacity != fuelLevel * fuelCapacity) {
- this.props.onChange(nextFuelLevel * nextFuelCapacity);
- }
- }
- return true;
+ this._fuelChange = this._fuelChange.bind(this);
}
/**
@@ -57,8 +30,13 @@ export default class Fuel extends TranslatedComponent {
* @param {number} fuelLevel percentage level from 0 to 1
*/
_fuelChange(fuelLevel) {
- this.setState({ fuelLevel });
- this.props.onChange(fuelLevel * this.state.fuelCapacity);
+ const { fuel, fuelCapacity } = this.props;
+
+ const newFuel = fuelLevel * fuelCapacity;
+ // Only send an update if the fuel has changed significantly
+ if (Math.round(fuel * 10) != Math.round(newFuel * 10)) {
+ this.props.onChange(Math.round(newFuel * 10) / 10);
+ }
}
/**
@@ -68,20 +46,20 @@ export default class Fuel extends TranslatedComponent {
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
- const { fuelLevel, fuelCapacity } = this.state;
+ const { fuel, fuelCapacity } = this.props;
return (
- {translate('fuel carried')}: {formats.f2(fuelLevel * fuelCapacity)}{units.T}
+ {translate('fuel carried')}: {formats.f1(fuel)}{units.T}
-
-
+
+
;
}
diff --git a/src/app/components/Pips.jsx b/src/app/components/Pips.jsx
index 7c9f86ec..29051318 100644
--- a/src/app/components/Pips.jsx
+++ b/src/app/components/Pips.jsx
@@ -15,7 +15,9 @@ import Module from '../shipyard/Module';
*/
export default class Pips extends TranslatedComponent {
static propTypes = {
- ship: React.PropTypes.object.isRequired,
+ sys: React.PropTypes.number.isRequired,
+ eng: React.PropTypes.number.isRequired,
+ wep: React.PropTypes.number.isRequired,
onChange: React.PropTypes.func.isRequired
};
@@ -26,24 +28,9 @@ export default class Pips extends TranslatedComponent {
*/
constructor(props, context) {
super(props);
- const ship = props.ship;
- const pd = ship.standard[4].m;
+ const { sys, eng, wep } = props;
this._keyDown = this._keyDown.bind(this);
-
- let pipsSvg = this._renderPips(2, 2, 2);
- this.state = {
- sys: 2,
- eng: 2,
- wep: 2,
- sysCap: pd.getSystemsCapacity(),
- engCap: pd.getEnginesCapacity(),
- wepCap: pd.getWeaponsCapacity(),
- sysRate: pd.getSystemsRechargeRate(),
- engRate: pd.getEnginesRechargeRate(),
- wepRate: pd.getWeaponsRechargeRate(),
- pipsSvg
- };
}
/**
@@ -60,41 +47,6 @@ export default class Pips extends TranslatedComponent {
document.removeEventListener('keydown', this._keyDown);
}
- /**
- * Update values if we change ship
- * @param {Object} nextProps Incoming/Next properties
- * @returns {boolean} Returns true if the component should be rerendered
- */
- componentWillReceiveProps(nextProps) {
- const { sysCap, engCap, wepCap, sysRate, engRate, wepRate } = this.state;
- const nextShip = nextProps.ship;
- const pd = nextShip.standard[4].m;
-
- const nextSysCap = pd.getSystemsCapacity();
- const nextEngCap = pd.getEnginesCapacity();
- const nextWepCap = pd.getWeaponsCapacity();
- const nextSysRate = pd.getSystemsRechargeRate();
- const nextEngRate = pd.getEnginesRechargeRate();
- const nextWepRate = pd.getWeaponsRechargeRate();
- if (nextSysCap != sysCap ||
- nextEngCap != engCap ||
- nextWepCap != wepCap ||
- nextSysRate != sysRate ||
- nextEngRate != engRate ||
- nextWepRate != wepRate) {
- this.setState({
- sysCap: nextSysCap,
- engCap: nextEngCap,
- wepCap: nextWepCap,
- sysRate: nextSysRate,
- engRate: nextEngRate,
- wepRate: nextWepRate
- });
- }
-
- return true;
- }
-
/**
* Handle Key Down
* @param {Event} e Keyboard Event
@@ -142,10 +94,9 @@ export default class Pips extends TranslatedComponent {
* Reset the capacitor
*/
_reset() {
- let { sys, eng, wep } = this.state;
+ let { sys, eng, wep } = this.props;
if (sys != 2 || eng != 2 || wep != 2) {
sys = eng = wep = 2;
- this.setState({ sys, eng, wep, pipsSvg: this._renderPips(sys, eng, wep) });
this.props.onChange(sys, eng, wep);
}
}
@@ -154,7 +105,7 @@ export default class Pips extends TranslatedComponent {
* Increment the SYS capacitor
*/
_incSys() {
- let { sys, eng, wep } = this.state;
+ let { sys, eng, wep } = this.props;
const required = Math.min(1, 4 - sys);
if (required > 0) {
@@ -181,7 +132,6 @@ export default class Pips extends TranslatedComponent {
sys += 1;
}
}
- this.setState({ sys, eng, wep, pipsSvg: this._renderPips(sys, eng, wep) });
this.props.onChange(sys, eng, wep);
}
}
@@ -190,7 +140,7 @@ export default class Pips extends TranslatedComponent {
* Increment the ENG capacitor
*/
_incEng() {
- let { sys, eng, wep } = this.state;
+ let { sys, eng, wep } = this.props;
const required = Math.min(1, 4 - eng);
if (required > 0) {
@@ -217,7 +167,6 @@ export default class Pips extends TranslatedComponent {
eng += 1;
}
}
- this.setState({ sys, eng, wep, pipsSvg: this._renderPips(sys, eng, wep) });
this.props.onChange(sys, eng, wep);
}
}
@@ -226,7 +175,7 @@ export default class Pips extends TranslatedComponent {
* Increment the WEP capacitor
*/
_incWep() {
- let { sys, eng, wep } = this.state;
+ let { sys, eng, wep } = this.props;
const required = Math.min(1, 4 - wep);
if (required > 0) {
@@ -253,7 +202,6 @@ export default class Pips extends TranslatedComponent {
wep += 1;
}
}
- this.setState({ sys, eng, wep, pipsSvg: this._renderPips(sys, eng, wep) });
this.props.onChange(sys, eng, wep);
}
}
@@ -313,14 +261,14 @@ export default class Pips extends TranslatedComponent {
*/
render() {
const { formats, translate, units } = this.context.language;
- const { ship } = this.props;
- const { sys, eng, wep, sysCap, engCap, wepCap, sysRate, engRate, wepRate, pipsSvg } = this.state;
+ const { sys, eng, wep } = this.props;
const onSysClicked = this.onClick.bind(this, 'SYS');
const onEngClicked = this.onClick.bind(this, 'ENG');
const onWepClicked = this.onClick.bind(this, 'WEP');
const onRstClicked = this.onClick.bind(this, 'RST');
+ const pipsSvg = this._renderPips(sys, eng, wep);
return (
@@ -349,15 +297,3 @@ export default class Pips extends TranslatedComponent {
);
}
}
-//
-// {translate('capacity')} ({units.MJ})
-// {formats.f1(sysCap)}
-// {formats.f1(engCap)}
-// {formats.f1(wepCap)}
-//
-//
-// {translate('recharge')} ({units.MW})
-// {formats.f1(sysRate * (sys / 4))}
-// {formats.f1(engRate * (eng / 4))}
-// {formats.f1(wepRate * (wep / 4))}
-//
diff --git a/src/app/components/ShipPicker.jsx b/src/app/components/ShipPicker.jsx
index cebfa173..3b048b86 100644
--- a/src/app/components/ShipPicker.jsx
+++ b/src/app/components/ShipPicker.jsx
@@ -13,12 +13,12 @@ import cn from 'classnames';
export default class ShipPicker extends TranslatedComponent {
static propTypes = {
onChange: React.PropTypes.func.isRequired,
- ship: React.PropTypes.object,
+ ship: React.PropTypes.string.isRequired,
build: React.PropTypes.string
};
static defaultProps = {
- ship: new Ship('anaconda', Ships['anaconda'].properties, Ships['anaconda'].slots).buildWith(Ships['anaconda'].defaults)
+ ship: 'eagle'
}
/**
@@ -33,44 +33,21 @@ export default class ShipPicker extends TranslatedComponent {
this._toggleMenu = this._toggleMenu.bind(this);
this._closeMenu = this._closeMenu.bind(this);
- this.state = {
- ship: props.ship,
- build: props.build
- };
- }
-
- /**
- * Update the state if our ship changes
- * @param {Object} nextProps Incoming/Next properties
- * @return {boolean} Returns true if the component should be rerendered
- */
- componentWillReceiveProps(nextProps) {
- const { ship, build } = this.state;
- const { nextShip, nextBuild } = nextProps;
-
- if (nextShip != undefined && nextShip != ship && nextBuild != build) {
- this.setState({ ship: nextShip, build: nextBuild });
- }
- return true;
+ this.state = { menuOpen: false };
}
/**
* Update ship
- * @param {object} shipId the ship
+ * @param {object} ship the ship
* @param {string} build the build, if present
*/
- _shipChange(shipId, build) {
- const ship = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots);
- if (build) {
- // Ship is a particular build
- ship.buildFrom(Persist.getBuild(shipId, build));
- } else {
- // Ship is a stock build
- ship.buildWith(Ships[shipId].defaults);
- }
+ _shipChange(ship, build) {
this._closeMenu();
- this.setState({ ship, build });
- this.props.onChange(ship, build);
+
+ // Ensure that the ship has changed
+ if (ship !== this.props.ship || this.build !== this.props.build) {
+ this.props.onChange(ship, build);
+ }
}
/**
@@ -78,7 +55,7 @@ export default class ShipPicker extends TranslatedComponent {
* @returns {object} the picker menu
*/
_renderPickerMenu() {
- const { ship, build } = this.state;
+ const { ship, build } = this.props;
const _shipChange = this._shipChange;
const builds = Persist.getBuilds();
@@ -86,7 +63,7 @@ export default class ShipPicker extends TranslatedComponent {
for (let shipId of this.shipOrder) {
const shipBuilds = [];
// Add stock build
- const stockSelected = (ship.id == shipId && !build);
+ const stockSelected = (ship == shipId && !build);
shipBuilds.push(Stock );
if (builds[shipId]) {
let buildNameOrder = Object.keys(builds[shipId]).sort();
@@ -126,9 +103,10 @@ export default class ShipPicker extends TranslatedComponent {
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
- const { menuOpen, ship, build } = this.state;
+ const { ship, build } = this.props;
+ const { menuOpen } = this.state;
- const shipString = ship.name + ': ' + (build ? build : translate('stock'));
+ const shipString = ship + ': ' + (build ? build : translate('stock'));
return (
e.stopPropagation() }>
diff --git a/src/app/components/ShipSummaryTable.jsx b/src/app/components/ShipSummaryTable.jsx
index 370cb03b..e4fb8edd 100644
--- a/src/app/components/ShipSummaryTable.jsx
+++ b/src/app/components/ShipSummaryTable.jsx
@@ -30,24 +30,20 @@ export default class ShipSummaryTable extends TranslatedComponent {
let u = language.units;
let formats = language.formats;
let { time, int, round, f1, f2 } = formats;
- let sgClassNames = cn({ warning: ship.findInternalByGroup('sg') && !ship.shield, muted: !ship.findInternalByGroup('sg') });
- let sgRecover = '-';
- let sgRecharge = '-';
let hide = tooltip.bind(null, null);
- if (ship.shield) {
- sgRecover = time(ship.calcShieldRecovery());
- sgRecharge = time(ship.calcShieldRecharge());
- }
-
+ const shieldGenerator = ship.findInternalByGroup('sg');
+ const sgClassNames = cn({ warning: shieldGenerator && !ship.shield, muted: !shieldGenerator });
const timeToDrain = Calc.timeToDrainWep(ship, wep);
+ const canThrust = ship.canThrust();
+ const canBoost = ship.canBoost();
return
- {translate('speed')}
- {translate('boost')}
+ {translate('speed')}
+ {translate('boost')}
{translate('DPS')}
{translate('EPS')}
{translate('TTD')}
@@ -72,8 +68,8 @@ export default class ShipSummaryTable extends TranslatedComponent {
- { ship.canThrust() ? {int(ship.calcSpeed(eng, fuel, cargo, false))}{u['m/s']} : 0 }
- { ship.canBoost() ? {int(ship.calcSpeed(eng, fuel, cargo, true))}{u['m/s']} : 0 }
+ { canThrust ? {int(ship.calcSpeed(eng, fuel, cargo, false))}{u['m/s']} : 0 }
+ { canBoost ? {int(ship.calcSpeed(eng, fuel, cargo, true))}{u['m/s']} : 0 }
{f1(ship.totalDps)}
{f1(ship.totalEps)}
{timeToDrain === Infinity ? '∞' : time(timeToDrain)}
diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx
index b96c2ef3..583a5485 100644
--- a/src/app/pages/OutfittingPage.jsx
+++ b/src/app/pages/OutfittingPage.jsx
@@ -1,14 +1,17 @@
import React from 'react';
+// import Perf from 'react-addons-perf';
import { findDOMNode } from 'react-dom';
import { Ships } from 'coriolis-data/dist';
import cn from 'classnames';
import Page from './Page';
import Router from '../Router';
import Persist from '../stores/Persist';
+import * as Utils from '../utils/UtilityFunctions';
import Ship from '../shipyard/Ship';
import { toDetailedBuild } from '../shipyard/Serializer';
import { outfitURL } from '../utils/UrlGenerators';
import { FloppyDisk, Bin, Switch, Download, Reload, LinkIcon, ShoppingIcon } from '../components/SvgIcons';
+import LZString from 'lz-string';
import ShipSummaryTable from '../components/ShipSummaryTable';
import StandardSlotSection from '../components/StandardSlotSection';
import HardpointsSlotSection from '../components/HardpointsSlotSection';
@@ -46,7 +49,8 @@ export default class OutfittingPage extends Page {
*/
constructor(props, context) {
super(props, context);
- this.state = this._initState(context);
+ // window.Perf = Perf;
+ this.state = this._initState(props, context);
this._keyDown = this._keyDown.bind(this);
this._exportBuild = this._exportBuild.bind(this);
this._pipsUpdated = this._pipsUpdated.bind(this);
@@ -59,10 +63,11 @@ export default class OutfittingPage extends Page {
/**
* [Re]Create initial state from context
+ * @param {Object} props React component properties
* @param {context} context React component context
* @return {Object} New state object
*/
- _initState(context) {
+ _initState(props, context) {
let params = context.route.params;
let shipId = params.ship;
let code = params.code;
@@ -84,6 +89,8 @@ export default class OutfittingPage extends Page {
this._getTitle = getTitle.bind(this, data.properties.name);
+ // Obtain ship control from code
+ const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = this._obtainControlFromCode(ship, code);
return {
error: null,
title: this._getTitle(buildName),
@@ -94,14 +101,15 @@ export default class OutfittingPage extends Page {
ship,
code,
savedCode,
- sys: 2,
- eng: 2,
- wep: 2,
- fuel: ship.fuelCapacity,
- cargo: 0,
- boost: false,
- engagementRange: 1000,
- opponent: new Ship('anaconda', Ships['anaconda'].properties, Ships['anaconda'].slots).buildWith(Ships['anaconda'].defaults)
+ sys,
+ eng,
+ wep,
+ boost,
+ fuel,
+ cargo,
+ opponent,
+ opponentBuild,
+ engagementRange
};
}
@@ -123,6 +131,76 @@ export default class OutfittingPage extends Page {
this.setState(stateChanges);
}
+ /**
+ * Update the control part of the route
+ */
+ _updateRouteOnControlChange() {
+ const { ship, shipId, buildName } = this.state;
+ const code = this._fullCode(ship);
+ this._updateRoute(shipId, buildName, code);
+ this.setState({ code });
+ }
+
+ /**
+ * Provide a full code for this ship, including any additions due to the outfitting page
+ * @param {Object} ship the ship
+ * @param {number} fuel the fuel carried by the ship (if different from that in state)
+ * @param {number} cargo the cargo carried by the ship (if different from that in state)
+ * @returns {string} the code for this ship
+ */
+ _fullCode(ship, fuel, cargo) {
+ return `${ship.toString()}.${LZString.compressToBase64(this._controlCode(fuel, cargo))}`;
+ }
+
+ /**
+ * Obtain the control information from the build code
+ * @param {Object} ship The ship
+ * @param {string} code The build code
+ * @returns {Object} The control information
+ */
+ _obtainControlFromCode(ship, code) {
+ // Defaults
+ let sys = 2;
+ let eng = 2;
+ let wep = 2;
+ let boost = false;
+ let fuel = ship.fuelCapacity;
+ let cargo = ship.cargoCapacity;
+ let opponent = new Ship('eagle', Ships['eagle'].properties, Ships['eagle'].slots).buildWith(Ships['eagle'].defaults);
+ let opponentBuild = undefined;
+ let engagementRange = 1000;
+
+ // Obtain updates from code, if available
+ if (code) {
+ const parts = code.split('.');
+ if (parts.length >= 5) {
+ // We have control information in the code
+ const control = LZString.decompressFromBase64(Utils.fromUrlSafe(parts[4])).split('/');
+ sys = parseFloat(control[0]);
+ eng = parseFloat(control[1]);
+ wep = parseFloat(control[2]);
+ boost = control[3] == 1 ? true : false;
+ fuel = parseFloat(control[4]);
+ cargo = parseInt(control[5]);
+ if (control[6]) {
+ const shipId = control[6];
+ opponent = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots);
+ if (control[7] && Persist.getBuild(shipId, control[7])) {
+ // Ship is a particular build
+ opponent.buildFrom(Persist.getBuild(shipId, control[7]));
+ opponentBuild = control[7];
+ } else {
+ // Ship is a stock build
+ opponent.buildWith(Ships[shipId].defaults);
+ }
+ }
+ engagementRange = parseInt(control[8]);
+ }
+ }
+
+ return { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange };
+ }
+
/**
* Triggered when pips have been updated
* @param {number} sys SYS pips
@@ -130,7 +208,7 @@ export default class OutfittingPage extends Page {
* @param {number} wep WEP pips
*/
_pipsUpdated(sys, eng, wep) {
- this.setState({ sys, eng, wep });
+ this.setState({ sys, eng, wep }, () => this._updateRouteOnControlChange());
}
/**
@@ -138,7 +216,7 @@ export default class OutfittingPage extends Page {
* @param {boolean} boost true if boosting
*/
_boostUpdated(boost) {
- this.setState({ boost });
+ this.setState({ boost }, () => this._updateRouteOnControlChange());
}
/**
@@ -146,7 +224,7 @@ export default class OutfittingPage extends Page {
* @param {number} fuel the amount of fuel, in T
*/
_fuelUpdated(fuel) {
- this.setState({ fuel });
+ this.setState({ fuel }, () => this._updateRouteOnControlChange());
}
/**
@@ -154,7 +232,7 @@ export default class OutfittingPage extends Page {
* @param {number} cargo the amount of cargo, in T
*/
_cargoUpdated(cargo) {
- this.setState({ cargo });
+ this.setState({ cargo }, () => this._updateRouteOnControlChange());
}
/**
@@ -162,24 +240,44 @@ export default class OutfittingPage extends Page {
* @param {number} engagementRange the engagement range, in m
*/
_engagementRangeUpdated(engagementRange) {
- this.setState({ engagementRange });
+ this.setState({ engagementRange }, () => this._updateRouteOnControlChange());
}
/**
* Triggered when target ship has been updated
- * @param {object} opponent the opponent's ship
- * @param {string} opponentBuild the name of the opponent's build
+ * @param {string} opponent the opponent's ship model
+ * @param {string} opponentBuild the name of the opponent's build
*/
_opponentUpdated(opponent, opponentBuild) {
- this.setState({ opponent, opponentBuild });
+ const opponentShip = new Ship(opponent, Ships[opponent].properties, Ships[opponent].slots);
+ if (opponentBuild && Persist.getBuild(opponent, opponentBuild)) {
+ // Ship is a particular build
+ opponentShip.buildFrom(Persist.getBuild(opponent, opponentBuild));
+ } else {
+ // Ship is a stock build
+ opponentShip.buildWith(Ships[opponent].defaults);
+ }
+
+ this.setState({ opponent: opponentShip, opponentBuild }, () => this._updateRouteOnControlChange());
+ }
+
+ /**
+ * Set the control code for this outfitting page
+ * @param {number} fuel the fuel carried by the ship (if different from that in state)
+ * @param {number} cargo the cargo carried by the ship (if different from that in state)
+ * @returns {string} The control code
+ */
+ _controlCode(fuel, cargo) {
+ const { sys, eng, wep, boost, opponent, opponentBuild, engagementRange } = this.state;
+ const code = `${sys}/${eng}/${wep}/${boost ? 1 : 0}/${fuel || this.state.fuel}/${cargo || this.state.cargo}/${opponent.id}/${opponentBuild ? opponentBuild : ''}/${engagementRange}`;
+ return code;
}
/**
* Save the current build
*/
_saveBuild() {
- let code = this.state.ship.toString();
- let { buildName, newBuildName, shipId } = this.state;
+ const { code, buildName, newBuildName, shipId } = this.state;
if (buildName === newBuildName) {
Persist.saveBuild(shipId, buildName, code);
@@ -196,9 +294,8 @@ export default class OutfittingPage extends Page {
* Rename the current build
*/
_renameBuild() {
- let { buildName, newBuildName, shipId, ship } = this.state;
+ const { code, buildName, newBuildName, shipId, ship } = this.state;
if (buildName != newBuildName && newBuildName.length) {
- let code = ship.toString();
Persist.deleteBuild(shipId, buildName);
Persist.saveBuild(shipId, newBuildName, code);
this._updateRoute(shipId, newBuildName, code);
@@ -210,16 +307,31 @@ export default class OutfittingPage extends Page {
* Reload build from last save
*/
_reloadBuild() {
- this.state.ship.buildFrom(this.state.savedCode);
- this._shipUpdated();
+ this.setState({ code: this.state.savedCode }, () => this._codeUpdated());
}
/**
* Reset build to Stock/Factory defaults
*/
_resetBuild() {
- this.state.ship.buildWith(Ships[this.state.shipId].defaults);
- this._shipUpdated();
+ const { ship, shipId, buildName } = this.state;
+ // Rebuild ship
+ ship.buildWith(Ships[shipId].defaults);
+ // Reset controls
+ const code = ship.toString();
+ const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = this._obtainControlFromCode(ship, code);
+ // Update state, and refresh the ship
+ this.setState({
+ sys,
+ eng,
+ wep,
+ boost,
+ fuel,
+ cargo,
+ opponent,
+ opponentBuild,
+ engagementRange
+ }, () => this._updateRoute(shipId, buildName, code));
}
/**
@@ -244,14 +356,43 @@ export default class OutfittingPage extends Page {
}
/**
- * Trigger render on ship model change
+ * Called when the code for the ship has been updated, to synchronise the rest of the data
+ */
+ _codeUpdated() {
+ const { code, ship, shipId, buildName } = this.state;
+
+ // Rebuild ship from the code
+ this.state.ship.buildFrom(code);
+
+ // Obtain controls from the code
+ const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = this._obtainControlFromCode(ship, code);
+ // Update state, and refresh the route when complete
+ this.setState({
+ sys,
+ eng,
+ wep,
+ boost,
+ fuel,
+ cargo,
+ opponent,
+ opponentBuild,
+ engagementRange
+ }, () => this._updateRoute(shipId, buildName, code));
+ }
+
+ /**
+ * Called when the ship has been updated, to set the code and then update accordingly
*/
_shipUpdated() {
- let { shipId, buildName, ship } = this.state;
- let code = ship.toString();
-
- this._updateRoute(shipId, buildName, code);
- this.setState({ code });
+ let { ship, shipId, buildName, cargo, fuel } = this.state;
+ if (cargo > ship.cargoCapacity) {
+ cargo = ship.cargoCapacity;
+ }
+ if (fuel > ship.fuelCapacity) {
+ fuel = ship.fuelCapacity;
+ }
+ const code = this._fullCode(ship, fuel, cargo);
+ this.setState({ code, cargo, fuel }, () => this._updateRoute(shipId, buildName, code));
}
/**
@@ -271,7 +412,7 @@ export default class OutfittingPage extends Page {
*/
componentWillReceiveProps(nextProps, nextContext) {
if (this.context.route !== nextContext.route) { // Only reinit state if the route has changed
- this.setState(this._initState(nextContext));
+ this.setState(this._initState(nextProps, nextContext));
}
}
@@ -340,16 +481,23 @@ export default class OutfittingPage extends Page {
shipUpdated = this._shipUpdated,
canSave = (newBuildName || buildName) && code !== savedCode,
canRename = buildName && newBuildName && buildName != newBuildName,
- canReload = savedCode && canSave,
- hStr = ship.getHardpointsString() + '.' + ship.getModificationsString(),
- iStr = ship.getInternalString() + '.' + ship.getModificationsString();
+ canReload = savedCode && canSave;
// Code can be blank for a default loadout. Prefix it with the ship name to ensure that changes in default ships is picked up
code = ship.name + (code || '');
// Markers are used to propagate state changes without requiring a deep comparison of the ship, as that takes a long time
+ const _sStr = ship.getStandardString();
+ const _iStr = ship.getInternalString();
+ const _hStr = ship.getHardpointsString();
+ const _pStr = `${ship.getPowerEnabledString()}${ship.getPowerPrioritiesString()}`;
+ const _mStr = ship.getModificationsString();
+
+ const standardSlotMarker = `${ship.name}${_sStr}${_pStr}${_mStr}`;
+ const internalSlotMarker = `${ship.name}${_iStr}${_pStr}${_mStr}`;
+ const hardpointsSlotMarker = `${ship.name}${_hStr}${_pStr}${_mStr}`;
const boostMarker = `${ship.canBoost()}`;
- const shipSummaryMarker = `${ship.toString()}:${eng}:${fuel}:${cargo}`;
+ const shipSummaryMarker = `${ship.toString()}${eng}${fuel}${cargo}`;
return (
@@ -386,10 +534,10 @@ export default class OutfittingPage extends Page {
{/* Main tables */}
-
-
-
-
+
+
+
+
{/* Control of ship and opponent */}
@@ -397,28 +545,28 @@ export default class OutfittingPage extends Page {
{translate('ship control')}
-
+
-
+
- { ship.cargoCapacity > 0 ? : null }
+ { ship.cargoCapacity > 0 ? : null }
{translate('opponent')}
-
+
-
+
{/* Tabbed subpages */}
diff --git a/src/app/shipyard/Calculations.js b/src/app/shipyard/Calculations.js
index 183ec30e..30432949 100644
--- a/src/app/shipyard/Calculations.js
+++ b/src/app/shipyard/Calculations.js
@@ -808,9 +808,9 @@ export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour
/**
* Calculate time to drain WEP capacitor
- * @param {object} ship The ship
- * @param {number} wep Pips to WEP
- * @return The time to drain the WEP capacitor, in seconds
+ * @param {object} ship The ship
+ * @param {number} wep Pips to WEP
+ * @returns {number} The time to drain the WEP capacitor, in seconds
*/
export function timeToDrainWep(ship, wep) {
let totalSEps = 0;
@@ -835,6 +835,12 @@ export function timeToDrainWep(ship, wep) {
/**
* Calculate the time to deplete an amount of shields or armour
+ * @param {number} amount The amount to be depleted
+ * @param {number} dps The depletion per second
+ * @param {number} eps The energy drained per second
+ * @param {number} capacity The initial energy capacity
+ * @param {number} recharge The energy recharged per second
+ * @returns {number} The number of seconds to deplete to 0
*/
export function timeToDeplete(amount, dps, eps, capacity, recharge) {
const drainPerSecond = eps - recharge;
From 85f108556ada33fd71e76d6ae7112e9e9adfeb78 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Tue, 21 Mar 2017 00:41:33 +0000
Subject: [PATCH 52/87] Fix for ship summary
---
src/app/components/Offence.jsx | 5 +-
src/app/components/ShipSummaryTable.jsx | 74 +++++++++++++------------
src/app/i18n/en.js | 21 +++++++
src/app/pages/OutfittingPage.jsx | 4 +-
4 files changed, 66 insertions(+), 38 deletions(-)
diff --git a/src/app/components/Offence.jsx b/src/app/components/Offence.jsx
index 1c823c3c..bb880d4d 100644
--- a/src/app/components/Offence.jsx
+++ b/src/app/components/Offence.jsx
@@ -140,6 +140,8 @@ export default class Offence extends TranslatedComponent {
const opponentShields = Calc.shieldMetrics(opponent, 4);
const opponentArmour = Calc.armourMetrics(opponent);
+ const timeToDrain = Calc.timeToDrainWep(ship, wep);
+
let absoluteShieldsSDps = 0;
let explosiveShieldsSDps = 0;
let kineticShieldsSDps = 0;
@@ -243,7 +245,8 @@ export default class Offence extends TranslatedComponent {
-
{translate('damage to opponent')}
+ {translate('offence metrics')}
+ {translate('PHRASE_TIME_TO_DRAIN_WEP')} {timeToDrain === Infinity ? translate('never') : formats.time(timeToDrain)}
{translate('PHRASE_TIME_TO_REMOVE_SHIELDS')} {timeToDepleteShields === Infinity ? translate('never') : formats.time(timeToDepleteShields)}
{translate('PHRASE_TIME_TO_REMOVE_ARMOUR')} {timeToDepleteArmour === Infinity ? translate('never') : formats.time(timeToDepleteArmour)}
diff --git a/src/app/components/ShipSummaryTable.jsx b/src/app/components/ShipSummaryTable.jsx
index e4fb8edd..9212bb1b 100644
--- a/src/app/components/ShipSummaryTable.jsx
+++ b/src/app/components/ShipSummaryTable.jsx
@@ -11,11 +11,6 @@ export default class ShipSummaryTable extends TranslatedComponent {
static propTypes = {
ship: React.PropTypes.object.isRequired,
- sys: React.PropTypes.number.isRequired,
- eng: React.PropTypes.number.isRequired,
- wep: React.PropTypes.number.isRequired,
- cargo: React.PropTypes.number.isRequired,
- fuel: React.PropTypes.number.isRequired,
marker: React.PropTypes.string.isRequired,
};
@@ -24,7 +19,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
* @return {React.Component} Summary table
*/
render() {
- const { ship, fuel, eng, wep, cargo, boost } = this.props;
+ const { ship } = this.props;
let { language, tooltip, termtip } = this.context;
let translate = language.translate;
let u = language.units;
@@ -34,9 +29,12 @@ export default class ShipSummaryTable extends TranslatedComponent {
const shieldGenerator = ship.findInternalByGroup('sg');
const sgClassNames = cn({ warning: shieldGenerator && !ship.shield, muted: !shieldGenerator });
- const timeToDrain = Calc.timeToDrainWep(ship, wep);
+ const sgTooltip = shieldGenerator ? 'TT_SUMMARY_SHIELDS' : 'TT_SUMMARY_SHIELDS_NONFUNCTIONAL';
+ const timeToDrain = Calc.timeToDrainWep(ship, 4);
const canThrust = ship.canThrust();
+ const speedTooltip = canThrust ? 'TT_SUMMARY_SPEED' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
const canBoost = ship.canBoost();
+ const boostTooltip = canBoost ? 'TT_SUMMARY_BOOST' : canThrust ? 'TT_SUMMARY_BOOST_NONFUNCTIONAL' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
return
@@ -44,46 +42,52 @@ export default class ShipSummaryTable extends TranslatedComponent {
{translate('speed')}
{translate('boost')}
- {translate('DPS')}
- {translate('EPS')}
- {translate('TTD')}
- {translate('HPS')}
- {translate('hrd')}
- {translate('arm')}
- {translate('shld')}
- {translate('mass')}
+ {translate('jump range')}
+ {translate('shield')}
+ {translate('integrity')}
+ {translate('DPS')}
+ {translate('EPS')}
+ {translate('TTD')}
+ {/* {translate('HPS')} */}
{translate('cargo')}
{translate('fuel')}
- {translate('jump range')}
+ {translate('mass')}
+ {translate('hrd')}
{translate('crew')}
- {translate('MLF')}
+ {translate('MLF')}
+ {translate('max')}
+ {translate('unladen')}
+ {translate('laden')}
+ {translate('total unladen')}
+ {translate('total laden')}
{translate('hull')}
- {translate('unladen')}
- {translate('laden')}
- {translate('single')}
- {translate('total')}
+ {translate('unladen')}
+ {translate('laden')}
- { canThrust ? {int(ship.calcSpeed(eng, fuel, cargo, false))}{u['m/s']} : 0 }
- { canBoost ? {int(ship.calcSpeed(eng, fuel, cargo, true))}{u['m/s']} : 0 }
- {f1(ship.totalDps)}
- {f1(ship.totalEps)}
- {timeToDrain === Infinity ? '∞' : time(timeToDrain)}
- {f1(ship.totalHps)}
- {int(ship.hardness)}
- {int(ship.armour)}
- {int(ship.shield)}{u.MJ}
- {ship.hullMass}{u.T}
- {int(ship.unladenMass)}{u.T}
- {int(ship.ladenMass)}{u.T}
+ { canThrust ? {int(ship.calcSpeed(4, ship.fuelCapacity, 0, false))}{u['m/s']} : 0 }
+ { canBoost ? {int(ship.calcSpeed(4, ship.fuelCapacity, 0, true))}{u['m/s']} : 0 }
+ {f2(Calc.jumpRange(ship.unladenMass + ship.standard[2].m.getMaxFuelPerJump(), ship.standard[2].m, ship.standard[2].m.getMaxFuelPerJump()))}{u.LY}
+ {f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}
+ {f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}
+ {f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}
+ {f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}
+ {int(ship.shield)}{u.MJ}
+ {int(ship.armour)}
+ {f1(ship.totalDps)}
+ {f1(ship.totalEps)}
+ {timeToDrain === Infinity ? '∞' : time(timeToDrain)}
+ {/* {f1(ship.totalHps)} */}
{round(ship.cargoCapacity)}{u.T}
{round(ship.fuelCapacity)}{u.T}
- {f2(Calc.jumpRange(ship.unladenMass + fuel + cargo, ship.standard[2].m, fuel))}{u.LY}
- {f2(Calc.totalJumpRange(ship.unladenMass + fuel + cargo, ship.standard[2].m, fuel))}{u.LY}
+ {ship.hullMass}{u.T}
+ {int(ship.unladenMass)}{u.T}
+ {int(ship.ladenMass)}{u.T}
+ {int(ship.hardness)}
{ship.crew}
{ship.masslock}
diff --git a/src/app/i18n/en.js b/src/app/i18n/en.js
index cf986c7b..6ed56ba4 100644
--- a/src/app/i18n/en.js
+++ b/src/app/i18n/en.js
@@ -57,6 +57,8 @@ export const terms = {
TT_TIME_TO_REMOVE_SHIELDS: 'With sustained fire by all weapons',
PHRASE_TIME_TO_REMOVE_ARMOUR: 'Will remove armour in',
TT_TIME_TO_REMOVE_ARMOUR: 'With sustained fire by all weapons',
+ PHRASE_TIME_TO_DRAIN_WEP: 'Will drain WEP in',
+ TT_TIME_TO_DRAIN_WEP: 'Time to drain WEP capacitor with all weapons firing',
TT_TIME_TO_LOSE_SHIELDS: 'Against sustained fire from all opponent\'s weapons',
TT_TIME_TO_LOSE_ARMOUR: 'Against sustained fire from all opponent\'s weapons',
TT_MODULE_ARMOUR: 'Armour protecting against module damage',
@@ -68,6 +70,25 @@ export const terms = {
TT_EFFECTIVE_SDPS_ARMOUR: 'Actual sustained DPS whilst WEP capacitor is not empty',
TT_EFFECTIVENESS_ARMOUR: 'Effectivness compared to hitting a 0-resistance target at 0m',
+ TT_SUMMARY_SPEED: 'With full fuel tank and 4 pips to ENG',
+ TT_SUMMARY_SPEED_NONFUNCTIONAL: 'Thrusters powered off or over maximum mass',
+ TT_SUMMARY_BOOST: 'With full fuel tank and 4 pips to ENG',
+ TT_SUMMARY_BOOST_NONFUNCTIONAL: 'Power distributor not able to supply enough power to boost',
+ TT_SUMMARY_SHIELDS: 'Raw shield strength, including boosters',
+ TT_SUMMARY_SHIELDS_NONFUNCTIONAL: 'No shield generator or shield generator powered off',
+ TT_SUMMARY_INTEGRITY: 'Ship integrity, including bulkheads and hull reinforcement packages',
+ TT_SUMMARY_HULL_MASS: 'Mass of the hull prior to any modules being installed',
+ TT_SUMMARY_UNLADEN_MASS: 'Mass of the hull and modules prior to any fuel or cargo',
+ TT_SUMMARY_LADEN_MASS: 'Mass of the hull and modules with full fuel and cargo',
+ TT_SUMMARY_DPS: 'Damage per second with all weapons firing',
+ TT_SUMMARY_EPS: 'WEP capacitor consumed per second with all weapons firing',
+ TT_SUMMARY_TTD: 'Time to drain WEP capacitor with all weapons firing and 4 pips to WEP',
+ TT_SUMMARY_MAX_SINGLE_JUMP: 'Farthest possible jump range with no cargo and only enough fuel for the jump itself',
+ TT_SUMMARY_UNLADEN_SINGLE_JUMP: 'Farthest possible jump range with no cargo and a full fuel tank',
+ TT_SUMMARY_LADEN_SINGLE_JUMP: 'Farthest possible jump range with full cargo and a full fuel tank',
+ TT_SUMMARY_UNLADEN_TOTAL_JUMP: 'Farthest possible range with no cargo, a full fuel tank, and jumping as far as possible each time',
+ TT_SUMMARY_LADEN_TOTAL_JUMP: 'Farthest possible range with full cargo, a full fuel tank, and jumping as far as possible each time',
+
HELP_MODIFICATIONS_MENU: 'Click on a number to enter a new value, or drag along the bar for small changes',
// Other languages fallback to these values
diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx
index 583a5485..a4521982 100644
--- a/src/app/pages/OutfittingPage.jsx
+++ b/src/app/pages/OutfittingPage.jsx
@@ -497,7 +497,7 @@ export default class OutfittingPage extends Page {
const internalSlotMarker = `${ship.name}${_iStr}${_pStr}${_mStr}`;
const hardpointsSlotMarker = `${ship.name}${_hStr}${_pStr}${_mStr}`;
const boostMarker = `${ship.canBoost()}`;
- const shipSummaryMarker = `${ship.toString()}${eng}${fuel}${cargo}`;
+ const shipSummaryMarker = `${ship.name}${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}`;
return (
@@ -533,7 +533,7 @@ export default class OutfittingPage extends Page {
{/* Main tables */}
-
+
From eb042b2778c42e955dd19bfcbac9f638ec41e153 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Tue, 21 Mar 2017 12:19:25 +0000
Subject: [PATCH 53/87] Tidy-ups for build changes
---
src/app/components/OutfittingSubpages.jsx | 4 +--
src/app/components/ShipPicker.jsx | 3 +-
src/app/pages/OutfittingPage.jsx | 35 ++++++++++++++++-------
3 files changed, 28 insertions(+), 14 deletions(-)
diff --git a/src/app/components/OutfittingSubpages.jsx b/src/app/components/OutfittingSubpages.jsx
index 665d2697..6a8bf2bd 100644
--- a/src/app/components/OutfittingSubpages.jsx
+++ b/src/app/components/OutfittingSubpages.jsx
@@ -126,7 +126,7 @@ export default class OutfittingSubpages extends TranslatedComponent {
const { ship, sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild } = this.props;
Persist.setOutfittingTab('offence');
- const marker = `${ship.toString()}:${opponent.name}:${opponentBuild}:${engagementRange}`;
+ const marker = `${ship.toString()}:${opponent.toString()}:${opponentBuild}:${engagementRange}`;
return
@@ -141,7 +141,7 @@ export default class OutfittingSubpages extends TranslatedComponent {
const { ship, sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild } = this.props;
Persist.setOutfittingTab('defence');
- const marker = `${ship.toString()}:${opponent.name}:${opponentBuild}:${engagementRange}`;
+ const marker = `${ship.toString()}:${opponent.toString()}:${opponentBuild}:${engagementRange}`;
return
diff --git a/src/app/components/ShipPicker.jsx b/src/app/components/ShipPicker.jsx
index 3b048b86..c2bcb9a4 100644
--- a/src/app/components/ShipPicker.jsx
+++ b/src/app/components/ShipPicker.jsx
@@ -57,7 +57,6 @@ export default class ShipPicker extends TranslatedComponent {
_renderPickerMenu() {
const { ship, build } = this.props;
const _shipChange = this._shipChange;
-
const builds = Persist.getBuilds();
const buildList = [];
for (let shipId of this.shipOrder) {
@@ -68,7 +67,7 @@ export default class ShipPicker extends TranslatedComponent {
if (builds[shipId]) {
let buildNameOrder = Object.keys(builds[shipId]).sort();
for (let buildName of buildNameOrder) {
- const buildSelected = ship.id == shipId && build == buildName;
+ const buildSelected = ship === shipId && build === buildName;
shipBuilds.push({buildName} );
}
}
diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx
index a4521982..d9452d74 100644
--- a/src/app/pages/OutfittingPage.jsx
+++ b/src/app/pages/OutfittingPage.jsx
@@ -279,15 +279,19 @@ export default class OutfittingPage extends Page {
_saveBuild() {
const { code, buildName, newBuildName, shipId } = this.state;
- if (buildName === newBuildName) {
- Persist.saveBuild(shipId, buildName, code);
- this._updateRoute(shipId, buildName, code);
- } else {
- Persist.saveBuild(shipId, newBuildName, code);
- this._updateRoute(shipId, newBuildName, code);
- }
+ Persist.saveBuild(shipId, newBuildName, code);
+ this._updateRoute(shipId, newBuildName, code);
- this.setState({ buildName: newBuildName, code, savedCode: code, title: this._getTitle(newBuildName) });
+ let opponent, opponentBuild;
+ if (shipId === this.state.opponent.id && buildName === this.state.opponentBuild) {
+ // This is a save of our current opponent build; update it
+ opponentBuild = newBuildName;
+ opponent = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots).buildFrom(code);
+ } else {
+ opponentBuild = this.state.opponentBuild;
+ opponent = this.state.opponent;
+ }
+ this.setState({ buildName: newBuildName, code, savedCode: code, opponent, opponentBuild, title: this._getTitle(newBuildName) });
}
/**
@@ -299,7 +303,7 @@ export default class OutfittingPage extends Page {
Persist.deleteBuild(shipId, buildName);
Persist.saveBuild(shipId, newBuildName, code);
this._updateRoute(shipId, newBuildName, code);
- this.setState({ buildName: newBuildName, code, savedCode: code });
+ this.setState({ buildName: newBuildName, code, savedCode: code, opponentBuild: newBuildName });
}
}
@@ -338,8 +342,19 @@ export default class OutfittingPage extends Page {
* Delete the build
*/
_deleteBuild() {
- Persist.deleteBuild(this.state.shipId, this.state.buildName);
+ const { shipId, buildName } = this.state;
+ Persist.deleteBuild(shipId, buildName);
+
+ let opponentBuild;
+ if (shipId === this.state.opponent.id && buildName === this.state.opponentBuild) {
+ // Our current opponent has been deleted; revert to stock
+ opponentBuild = null;
+ } else {
+ opponentBuild = this.state.opponentBuild;
+ }
Router.go(outfitURL(this.state.shipId));
+
+ this.setState({ opponentBuild });
}
/**
From 3f18987007b4d965378e4777fdec5c0aea8f57be Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Tue, 21 Mar 2017 14:46:35 +0000
Subject: [PATCH 54/87] Rewrite vertical bar chart to use recharts
---
package.json | 5 +-
src/app/Coriolis.jsx | 11 +-
src/app/components/Defence.jsx | 2 +-
src/app/components/VerticalBarChart.jsx | 150 ++++++++++--------------
4 files changed, 71 insertions(+), 97 deletions(-)
diff --git a/package.json b/package.json
index c934693c..e018f952 100644
--- a/package.json
+++ b/package.json
@@ -90,16 +90,17 @@
},
"dependencies": {
"babel-polyfill": "*",
- "classnames": "^2.2.0",
"browserify-zlib": "ipfs/browserify-zlib",
+ "classnames": "^2.2.0",
"coriolis-data": "EDCD/coriolis-data",
"d3": "4.6.0",
"fbemitter": "^2.0.0",
"lodash": "^4.15.0",
"lz-string": "^1.4.4",
- "react-number-editor": "Athanasius/react-number-editor.git#miggy",
"react": "^15.0.1",
"react-dom": "^15.0.1",
+ "react-number-editor": "Athanasius/react-number-editor.git#miggy",
+ "recharts": "^0.21.2",
"superagent": "^1.4.0"
}
}
diff --git a/src/app/Coriolis.jsx b/src/app/Coriolis.jsx
index 5d676b5e..c0e3ea89 100644
--- a/src/app/Coriolis.jsx
+++ b/src/app/Coriolis.jsx
@@ -241,14 +241,19 @@ export default class Coriolis extends React.Component {
/**
* Show the term tip
* @param {string} term Term or Phrase
- * @param {Object} opts Options - dontCap, orientation (n,e,s,w)
+ * @param {Object} opts Options - dontCap, orientation (n,e,s,w) (can also be the event if no options supplied)
* @param {SyntheticEvent} event Event
+ * @param {SyntheticEvent} e2 Alternative location for synthetic event from charts (where 'Event' is actually a chart index)
*/
- _termtip(term, opts, event) {
- if (opts && opts.nativeEvent) { // Opts is a SyntheticEvent
+ _termtip(term, opts, event, e2) {
+ if (opts && opts.nativeEvent) { // Opts is the SyntheticEvent
event = opts;
opts = { cap: true };
}
+ if (e2 instanceof Object && e2.nativeEvent) { // E2 is the SyntheticEvent
+ event = e2;
+ }
+
this._tooltip(
{this.state.language.translate(term)}
,
event.currentTarget.getBoundingClientRect(),
diff --git a/src/app/components/Defence.jsx b/src/app/components/Defence.jsx
index 56a1bafc..d365206c 100644
--- a/src/app/components/Defence.jsx
+++ b/src/app/components/Defence.jsx
@@ -165,7 +165,7 @@ export default class Defence extends TranslatedComponent {
{translate('damage taken')}(%)
-
+
{translate('effective shield')}(MJ)
diff --git a/src/app/components/VerticalBarChart.jsx b/src/app/components/VerticalBarChart.jsx
index 617b1b4b..b8a709cf 100644
--- a/src/app/components/VerticalBarChart.jsx
+++ b/src/app/components/VerticalBarChart.jsx
@@ -1,12 +1,11 @@
-import React, { Component } from 'react';
-import Measure from 'react-measure';
-import * as d3 from 'd3';
import TranslatedComponent from './TranslatedComponent';
+import React, { PropTypes } from 'react';
+import Measure from 'react-measure';
+import { BarChart, Bar, XAxis, YAxis } from 'recharts';
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
const LABEL_COLOUR = '#000000';
-
-const margin = { top: 10, right: 0, bottom: 0, left: 55 };
+const AXIS_COLOUR = '#C06400';
const ASPECT = 1;
@@ -20,8 +19,8 @@ const merge = function(one, two) {
export default class VerticalBarChart extends TranslatedComponent {
static propTypes = {
- data : React.PropTypes.array.isRequired,
- yMax : React.PropTypes.number
+ data : PropTypes.array.isRequired,
+ yMax : PropTypes.number
};
/**
@@ -32,6 +31,8 @@ export default class VerticalBarChart extends TranslatedComponent {
constructor(props, context) {
super(props);
+ this._termtip = this._termtip.bind(this);
+
this.state = {
dimensions: {
width: 300,
@@ -41,97 +42,64 @@ export default class VerticalBarChart extends TranslatedComponent {
}
/**
- * Render the graph
- * @param {Object} props React Component properties
- */
- _renderGraph(props) {
- let { width, height } = this.state.dimensions;
- const { tooltip, termtip } = this.context;
-
- width = width - margin.left - margin.right,
- height = width * ASPECT - margin.top - margin.bottom;
-
- // X axis is a band scale with values being 'label'
- this.x = d3.scaleBand();
- this.x.domain(this.props.data.map(d => d.label)).padding(0.2);
- this.xAxis = d3.axisBottom(this.x).tickValues(this.props.data.map(d => d.label));
- this.x.range([0, width]);
-
- // Y axis is a numeric scale with values being 'value'
- this.y = d3.scaleLinear();
- if (props.yMax) {
- // Fixed maximum value (unless we go off the scale)
- const localMax = d3.max(this.props.data, d => d.value);
- this.y.domain([0, localMax > props.yMax ? localMax : props.yMax]);
- } else {
- this.y.domain([0, d3.max(this.props.data, d => d.value)]);
- }
- this.yAxis = d3.axisLeft(this.y);
- this.y.range([height, 0]);
-
- let svg = d3.select(this.svg).select('g');
-
- svg.selectAll('rect').remove();
- svg.selectAll('text').remove();
-
- svg.select('.x.axis').remove();
- svg.select('.y.axis').remove();
-
- svg.append('g')
- .attr('class', 'x axis')
- .attr('transform', `translate(0, ${height})`)
- .call(this.xAxis);
-
- svg.append('g')
- .attr('class', 'y axis')
- .call(this.yAxis)
- .attr('fill', CORIOLIS_COLOURS[0]);
-
- svg.selectAll('rect.bar')
- .data(props.data)
- .enter().append('rect')
- .attr('class', 'bar')
- .attr('x', d => this.x(d.label))
- .attr('width', this.x.bandwidth())
- .attr('y', d => this.y(d.value))
- .attr('height', d => height - this.y(d.value))
- .attr('fill', CORIOLIS_COLOURS[0]);
-
- svg.selectAll('text.bar')
- .data(props.data)
- .enter().append('text')
- .attr('class', 'bar')
- .attr('text-anchor', 'middle')
- .attr('x', 100)
- .attr('y', 100)
- .attr('stroke-width', '0px')
- .attr('fill', LABEL_COLOUR)
- .attr('x', d => this.x(d.label) + this.x.bandwidth() / 2)
- .attr('y', d => this.y(d.value) + 15)
- .text(d => d.value);
- }
-
- /**
- * Render the component
- * @returns {object} Markup
+ * Render the bar chart
+ * @returns {Object} the markup
*/
render() {
- const { width } = this.state.dimensions;
- const translate = `translate(${margin.left}, ${margin.top})`;
+ const { width, height } = this.state.dimensions;
+ const { tooltip, termtip } = this.context;
- const height = width * ASPECT;
-
- this._renderGraph(this.props);
+ // Calculate maximum for Y
+ let dataMax = Math.max(...this.props.data.map(d => d.value));
+ if (dataMax == -Infinity) dataMax = 0;
+ let yMax = this.props.yMax ? Math.round(this.props.yMax) : 0;
+ const localMax = Math.max(dataMax, yMax);
return (
-
{ this.setState({ dimensions }); }}>
-
- { this.x ?
-
this.svg = ref} width={width} height={height}>
-
- : null }
+
this.setState({ dimensions }) }>
+
+
+
+
+ } fill={CORIOLIS_COLOURS[0]} isAnimationActive={false} onMouseOver={this._termtip} onMouseOut={tooltip.bind(null, null)}/>
+
);
}
+
+ /**
+ * Generate a term tip
+ * @param {Object} d the data
+ * @param {number} i the index
+ * @param {Object} e the event
+ * @returns {Object} termtip markup
+ */
+ _termtip(d, i, e) {
+ if (this.props.data[i].tooltip) {
+ return this.context.termtip(this.props.data[i].tooltip, e);
+ } else {
+ return null;
+ }
+ }
}
+
+/**
+ * A label that displays the value within the bar of the chart
+ */
+const ValueLabel = React.createClass({
+ propTypes: {
+ x: PropTypes.number,
+ y: PropTypes.number,
+ payload: PropTypes.object,
+ value: PropTypes.number
+ },
+
+ render() {
+ const { x, y, payload, value } = this.props;
+
+ return (
+
{value}
+ );
+ }
+});
From 32fb66139a42e314e98bf795aedb32617a98c656 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Wed, 22 Mar 2017 09:20:51 +0000
Subject: [PATCH 55/87] Take base hull reinforcement resistances in to account
when calculating modifiers
---
ChangeLog.md | 1 +
src/app/utils/CompanionApiUtils.js | 7 ++++---
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/ChangeLog.md b/ChangeLog.md
index eff8db7f..c03a9d5b 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -8,6 +8,7 @@
* Show integrity value for relevant modules
* Reset old modification values when a new roll is applied
* Ensure that boost value is set correctly when modifications to power distributor enable/disable boost
+ * Ensure that hull reinforcement modifications take the inherent resistance in to account when calculating modification percentages
#2.2.19
* Power management panel now displays modules in descending order of power usage by default
diff --git a/src/app/utils/CompanionApiUtils.js b/src/app/utils/CompanionApiUtils.js
index 461886cb..6c6a2272 100644
--- a/src/app/utils/CompanionApiUtils.js
+++ b/src/app/utils/CompanionApiUtils.js
@@ -384,15 +384,16 @@ function _addModifications(module, modifiers, blueprint, grade) {
}
// Hull reinforcement package resistance is actually a damage modifier, so needs to be inverted.
+ // In addition, the modification is based off the inherent resistance of the module
if (module.grp === 'hr') {
if (module.getModValue('explres')) {
- module.setModValue('explres', ((module.getModValue('explres') / 10000) * -1) * 10000);
+ module.setModValue('explres', ((1 - (1 - module.explres) * (1 + module.getModValue('explres') / 10000)) - module.explres) * 10000);
}
if (module.getModValue('kinres')) {
- module.setModValue('kinres', ((module.getModValue('kinres') / 10000) * -1) * 10000);
+ module.setModValue('kinres', ((1 - (1 - module.kinres) * (1 + module.getModValue('kinres') / 10000)) - module.kinres) * 10000);
}
if (module.getModValue('thermres')) {
- module.setModValue('thermres', ((module.getModValue('thermres') / 10000) * -1) * 10000);
+ module.setModValue('thermres', ((1 - (1 - module.thermres) * (1 + module.getModValue('thermres') / 10000)) - module.thermres) * 10000);
}
}
From 75a4e54453010e16293c15884d7d087b75ec66e3 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Wed, 22 Mar 2017 10:51:23 +0000
Subject: [PATCH 56/87] Add tooltips for blueprints
---
ChangeLog.md | 1 +
src/app/components/ModificationsMenu.jsx | 74 ++++++++++++++++++++++--
2 files changed, 69 insertions(+), 6 deletions(-)
diff --git a/ChangeLog.md b/ChangeLog.md
index c03a9d5b..08bceac1 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -9,6 +9,7 @@
* Reset old modification values when a new roll is applied
* Ensure that boost value is set correctly when modifications to power distributor enable/disable boost
* Ensure that hull reinforcement modifications take the inherent resistance in to account when calculating modification percentages
+ * Add tooltip for blueprints providing details of the features they alter
#2.2.19
* Power management panel now displays modules in descending order of power usage by default
diff --git a/src/app/components/ModificationsMenu.jsx b/src/app/components/ModificationsMenu.jsx
index ee961d29..52840fdc 100644
--- a/src/app/components/ModificationsMenu.jsx
+++ b/src/app/components/ModificationsMenu.jsx
@@ -43,7 +43,7 @@ export default class ModificationsMenu extends TranslatedComponent {
*/
_initState(props, context) {
let { m } = props;
- const { language } = context;
+ const { language, tooltip, termtip } = context;
const translate = language.translate;
// Set up the blueprints
@@ -52,7 +52,8 @@ export default class ModificationsMenu extends TranslatedComponent {
for (const grade of Modifications.modules[m.grp].blueprints[blueprintName]) {
const close = this._blueprintSelected.bind(this, Modifications.blueprints[blueprintName].id, grade);
const key = blueprintName + ':' + grade;
- blueprints.push({translate(Modifications.blueprints[blueprintName].name + ' grade ' + grade)}
);
+ const tooltipContent = this._blueprintTooltip(translate, Modifications.blueprints[blueprintName].grades[grade].features);
+ blueprints.push({translate(Modifications.blueprints[blueprintName].name + ' grade ' + grade)}
);
}
}
@@ -76,6 +77,51 @@ export default class ModificationsMenu extends TranslatedComponent {
return { blueprintMenuOpened, blueprints, modifications, specialMenuOpened, specials };
}
+ /**
+ * Generate a tooltip with details of a blueprint's effects
+ * @param {Object} features The features of the blueprint
+ * @returns {Object} The react components
+ */
+ _blueprintTooltip(translate, features)
+ {
+ const results = [];
+ for (const feature in features) {
+ const featureIsBeneficial = this._isBeneficial(feature, features[feature]);
+ const featureDef = Modifications.modifications[feature];
+ if (!featureDef.hidden) {
+ let symbol = '';
+ if (feature === 'jitter') {
+ symbol = '°';
+ } else if (featureDef.type === 'percentage') {
+ symbol = '%';
+ }
+ let lowerBound = features[feature][0];
+ let upperBound = features[feature][1];
+ if (featureDef.type === 'percentage') {
+ lowerBound = Math.round(lowerBound * 1000) / 10;
+ upperBound = Math.round(upperBound * 1000) / 10;
+ }
+ const range = `${lowerBound}${symbol} - ${upperBound}${symbol}`;
+ results.push({translate(feature)} {lowerBound}{symbol} {upperBound}{symbol} );
+ }
+ }
+
+ return (
+
+
+
+ {translate('effect')}
+ {translate('worst')}
+ {translate('best')}
+
+
+
+ {results}
+
+
+ );
+ }
+
/**
* Initialise the modifications
* @param {Object} props React Component properties
@@ -244,6 +290,19 @@ export default class ModificationsMenu extends TranslatedComponent {
this.props.onChange();
}
+ /**
+ * Is this feature beneficial?
+ *
+ */
+ _isBeneficial(feature, values) {
+ const fact = (values[0] < 0 || (values[0] === 0 && values[1] < 0));
+ if (Modifications.modifications[feature].higherbetter) {
+ return !fact;
+ } else {
+ return fact;
+ }
+ }
+
/**
* Reset modification information
*/
@@ -276,11 +335,11 @@ export default class ModificationsMenu extends TranslatedComponent {
let blueprintLabel;
let haveBlueprint = false;
+ let blueprintTooltip;
if (m.blueprint && !isEmpty(m.blueprint)) {
blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
haveBlueprint = true;
- } else {
- blueprintLabel = translate('PHRASE_SELECT_BLUEPRINT');
+ blueprintTooltip = this._blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade].features);
}
let specialLabel;
@@ -291,7 +350,7 @@ export default class ModificationsMenu extends TranslatedComponent {
}
const showBlueprintsMenu = blueprintMenuOpened;
- const showSpecial = haveBlueprint && this.state.specials.length > 0;
+ const showSpecial = haveBlueprint && this.state.specials.length > 0 && !blueprintMenuOpened;
const showSpecialsMenu = specialMenuOpened;
const showRolls = haveBlueprint && !blueprintMenuOpened && !specialMenuOpened;
const showReset = !blueprintMenuOpened && !specialMenuOpened;
@@ -303,7 +362,10 @@ export default class ModificationsMenu extends TranslatedComponent {
onClick={(e) => e.stopPropagation() }
onContextMenu={stopCtxPropagation}
>
- {blueprintLabel}
+ { haveBlueprint ?
+ {blueprintLabel}
+ :
+ {translate('PHRASE_SELECT_BLUEPRINT')}
}
{ showBlueprintsMenu ? this.state.blueprints : null }
{ showSpecial ? {specialLabel}
: null }
{ showSpecialsMenu ? this.state.specials : null }
From a1a17bc8369bae42618f7904ecad10f4a66c81c5 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Wed, 22 Mar 2017 11:44:08 +0000
Subject: [PATCH 57/87] Add blueprint info to modification tooltip
---
src/app/components/HardpointSlot.jsx | 7 +++
src/app/components/InternalSlot.jsx | 7 +++
src/app/components/ModificationsMenu.jsx | 67 ++--------------------
src/app/components/StandardSlot.jsx | 7 +++
src/app/utils/BlueprintFunctions.js | 72 ++++++++++++++++++++++++
5 files changed, 98 insertions(+), 62 deletions(-)
create mode 100644 src/app/utils/BlueprintFunctions.js
diff --git a/src/app/components/HardpointSlot.jsx b/src/app/components/HardpointSlot.jsx
index 4726f526..1a48a3a5 100644
--- a/src/app/components/HardpointSlot.jsx
+++ b/src/app/components/HardpointSlot.jsx
@@ -4,6 +4,7 @@ import Persist from '../stores/Persist';
import { DamageAbsolute, DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret, ListModifications, Modified } from './SvgIcons';
import { Modifications } from 'coriolis-data/dist';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
+import { blueprintTooltip } from '../utils/BlueprintFunctions';
/**
@@ -51,6 +52,12 @@ export default class HardpointSlot extends Slot {
if (m.blueprint.special && m.blueprint.special.id >= 0) {
modTT += ', ' + translate(m.blueprint.special.name);
}
+ modTT = (
+
+
{modTT}
+ {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade].features, m)}
+
+ );
}
return
diff --git a/src/app/components/InternalSlot.jsx b/src/app/components/InternalSlot.jsx
index ec580311..562f7555 100644
--- a/src/app/components/InternalSlot.jsx
+++ b/src/app/components/InternalSlot.jsx
@@ -4,6 +4,7 @@ import Persist from '../stores/Persist';
import { ListModifications, Modified } from './SvgIcons';
import { Modifications } from 'coriolis-data/dist';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
+import { blueprintTooltip } from '../utils/BlueprintFunctions';
/**
* Internal Slot
@@ -30,6 +31,12 @@ export default class InternalSlot extends Slot {
let modTT = translate('modified');
if (m && m.blueprint && m.blueprint.name) {
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
+ modTT = (
+
+
{modTT}
+ {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade].features, m)}
+
+ );
}
let mass = m.getMass() || m.cargo || m.fuel || 0;
diff --git a/src/app/components/ModificationsMenu.jsx b/src/app/components/ModificationsMenu.jsx
index 52840fdc..8faea959 100644
--- a/src/app/components/ModificationsMenu.jsx
+++ b/src/app/components/ModificationsMenu.jsx
@@ -5,6 +5,7 @@ import { isEmpty, stopCtxPropagation } from '../utils/UtilityFunctions';
import cn from 'classnames';
import { Modifications } from 'coriolis-data/dist';
import Modification from './Modification';
+import { blueprintTooltip } from '../utils/BlueprintFunctions';
/**
* Modifications menu
@@ -52,7 +53,7 @@ export default class ModificationsMenu extends TranslatedComponent {
for (const grade of Modifications.modules[m.grp].blueprints[blueprintName]) {
const close = this._blueprintSelected.bind(this, Modifications.blueprints[blueprintName].id, grade);
const key = blueprintName + ':' + grade;
- const tooltipContent = this._blueprintTooltip(translate, Modifications.blueprints[blueprintName].grades[grade].features);
+ const tooltipContent = blueprintTooltip(translate, Modifications.blueprints[blueprintName].grades[grade].features);
blueprints.push(
{translate(Modifications.blueprints[blueprintName].name + ' grade ' + grade)}
);
}
}
@@ -77,51 +78,6 @@ export default class ModificationsMenu extends TranslatedComponent {
return { blueprintMenuOpened, blueprints, modifications, specialMenuOpened, specials };
}
- /**
- * Generate a tooltip with details of a blueprint's effects
- * @param {Object} features The features of the blueprint
- * @returns {Object} The react components
- */
- _blueprintTooltip(translate, features)
- {
- const results = [];
- for (const feature in features) {
- const featureIsBeneficial = this._isBeneficial(feature, features[feature]);
- const featureDef = Modifications.modifications[feature];
- if (!featureDef.hidden) {
- let symbol = '';
- if (feature === 'jitter') {
- symbol = '°';
- } else if (featureDef.type === 'percentage') {
- symbol = '%';
- }
- let lowerBound = features[feature][0];
- let upperBound = features[feature][1];
- if (featureDef.type === 'percentage') {
- lowerBound = Math.round(lowerBound * 1000) / 10;
- upperBound = Math.round(upperBound * 1000) / 10;
- }
- const range = `${lowerBound}${symbol} - ${upperBound}${symbol}`;
- results.push(
{translate(feature)} {lowerBound}{symbol} {upperBound}{symbol} );
- }
- }
-
- return (
-
-
-
- {translate('effect')}
- {translate('worst')}
- {translate('best')}
-
-
-
- {results}
-
-
- );
- }
-
/**
* Initialise the modifications
* @param {Object} props React Component properties
@@ -290,19 +246,6 @@ export default class ModificationsMenu extends TranslatedComponent {
this.props.onChange();
}
- /**
- * Is this feature beneficial?
- *
- */
- _isBeneficial(feature, values) {
- const fact = (values[0] < 0 || (values[0] === 0 && values[1] < 0));
- if (Modifications.modifications[feature].higherbetter) {
- return !fact;
- } else {
- return fact;
- }
- }
-
/**
* Reset modification information
*/
@@ -335,11 +278,11 @@ export default class ModificationsMenu extends TranslatedComponent {
let blueprintLabel;
let haveBlueprint = false;
- let blueprintTooltip;
+ let blueprintTt;
if (m.blueprint && !isEmpty(m.blueprint)) {
blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
haveBlueprint = true;
- blueprintTooltip = this._blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade].features);
+ blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade].features);
}
let specialLabel;
@@ -363,7 +306,7 @@ export default class ModificationsMenu extends TranslatedComponent {
onContextMenu={stopCtxPropagation}
>
{ haveBlueprint ?
-
{blueprintLabel}
+
{blueprintLabel}
:
{translate('PHRASE_SELECT_BLUEPRINT')}
}
{ showBlueprintsMenu ? this.state.blueprints : null }
diff --git a/src/app/components/StandardSlot.jsx b/src/app/components/StandardSlot.jsx
index 2dade7e1..8ec68235 100644
--- a/src/app/components/StandardSlot.jsx
+++ b/src/app/components/StandardSlot.jsx
@@ -8,6 +8,7 @@ import ModificationsMenu from './ModificationsMenu';
import { ListModifications, Modified } from './SvgIcons';
import { Modifications } from 'coriolis-data/dist';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
+import { blueprintTooltip } from '../utils/BlueprintFunctions';
/**
* Standard Slot
@@ -53,6 +54,12 @@ export default class StandardSlot extends TranslatedComponent {
let modTT = translate('modified');
if (m && m.blueprint && m.blueprint.name) {
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
+ modTT = (
+
+
{modTT}
+ {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade].features, m)}
+
+ );
}
if (!selected) {
diff --git a/src/app/utils/BlueprintFunctions.js b/src/app/utils/BlueprintFunctions.js
new file mode 100644
index 00000000..ece22eed
--- /dev/null
+++ b/src/app/utils/BlueprintFunctions.js
@@ -0,0 +1,72 @@
+import React from 'react';
+import { Modifications } from 'coriolis-data/dist';
+
+/**
+ * Generate a tooltip with details of a blueprint's effects
+ * @param {Object} features The features of the blueprint
+ * @param {Object} m The module to compare with
+ * @returns {Object} The react components
+ */
+export function blueprintTooltip(translate, features, m)
+{
+ const results = [];
+ for (const feature in features) {
+ const featureIsBeneficial = isBeneficial(feature, features[feature]);
+ const featureDef = Modifications.modifications[feature];
+ if (!featureDef.hidden) {
+ let symbol = '';
+ if (feature === 'jitter') {
+ symbol = '°';
+ } else if (featureDef.type === 'percentage') {
+ symbol = '%';
+ }
+ let lowerBound = features[feature][0];
+ let upperBound = features[feature][1];
+ if (featureDef.type === 'percentage') {
+ lowerBound = Math.round(lowerBound * 1000) / 10;
+ upperBound = Math.round(upperBound * 1000) / 10;
+ }
+ const range = `${lowerBound}${symbol} - ${upperBound}${symbol}`;
+ if (m) {
+ // We have a module - add in the current value
+ let current = m.getModValue(feature);
+ if (featureDef.type === 'percentage') {
+ current = Math.round(current / 10) / 10;
+ }
+ results.push(
{translate(feature)} {lowerBound}{symbol} {current}{symbol} {upperBound}{symbol} );
+ } else {
+ // We do not have a module, no value
+ results.push(
{translate(feature)} {lowerBound}{symbol} {upperBound}{symbol} );
+ }
+ }
+ }
+
+ return (
+
+
+
+ {translate('feature')}
+ {translate('worst')}
+ {m ? {translate('current')} : null }
+ {translate('best')}
+
+
+
+ {results}
+
+
+ );
+}
+
+/**
+ * Is this blueprint feature beneficial?
+ *
+ */
+export function isBeneficial(feature, values) {
+ const fact = (values[0] < 0 || (values[0] === 0 && values[1] < 0));
+ if (Modifications.modifications[feature].higherbetter) {
+ return !fact;
+ } else {
+ return fact;
+ }
+}
From 82ce86a37419811e4daf8f5a74c88a14ca84726d Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Wed, 22 Mar 2017 13:55:45 +0000
Subject: [PATCH 58/87] Updates
---
ChangeLog.md | 1 +
src/app/components/HardpointSlot.jsx | 2 +-
src/app/components/StandardSlot.jsx | 2 +-
src/app/i18n/en.js | 5 +++--
src/app/pages/OutfittingPage.jsx | 25 +++++++++++++++++++++----
5 files changed, 27 insertions(+), 8 deletions(-)
diff --git a/ChangeLog.md b/ChangeLog.md
index 08bceac1..ec2992c6 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -10,6 +10,7 @@
* Ensure that boost value is set correctly when modifications to power distributor enable/disable boost
* Ensure that hull reinforcement modifications take the inherent resistance in to account when calculating modification percentages
* Add tooltip for blueprints providing details of the features they alter
+ * Use opponent's saved pips if available
#2.2.19
* Power management panel now displays modules in descending order of power usage by default
diff --git a/src/app/components/HardpointSlot.jsx b/src/app/components/HardpointSlot.jsx
index 1a48a3a5..bca5ecbc 100644
--- a/src/app/components/HardpointSlot.jsx
+++ b/src/app/components/HardpointSlot.jsx
@@ -81,7 +81,7 @@ export default class HardpointSlot extends Slot {
{ m.getHps() ? {translate('HPS')}: {formats.round1(m.getHps())} { m.getClip() ? ({formats.round1((m.getClip() * m.getHps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) }) : null }
: null }
{ m.getDps() && m.getEps() ? {translate('DPE')}: {formats.f1(m.getDps() / m.getEps())}
: null }
{ m.getRoF() ? {translate('ROF')}: {formats.f1(m.getRoF())}{u.ps}
: null }
- { m.getRange() ? {translate('range')} {formats.f1(m.getRange() / 1000)}{u.km}
: null }
+ { m.getRange() ? {translate('range', m.grp)} {formats.f1(m.getRange() / 1000)}{u.km}
: null }
{ m.getScanTime() ? {translate('scantime')} {formats.f1(m.getScanTime())}{u.s}
: null }
{ m.getFalloff() ? {translate('falloff')} {formats.round(m.getFalloff() / 1000)}{u.km}
: null }
{ m.getShieldBoost() ? +{formats.pct1(m.getShieldBoost())}
: null }
diff --git a/src/app/components/StandardSlot.jsx b/src/app/components/StandardSlot.jsx
index 8ec68235..30d5502f 100644
--- a/src/app/components/StandardSlot.jsx
+++ b/src/app/components/StandardSlot.jsx
@@ -100,7 +100,7 @@ export default class StandardSlot extends TranslatedComponent {
{ m.getMinMass() ? {translate('minimum mass')}: {formats.int(m.getMinMass())}{units.T}
: null }
{ m.getOptMass() ? {translate('optimal mass')}: {formats.int(m.getOptMass())}{units.T}
: null }
{ m.getMaxMass() ? {translate('max mass')}: {formats.int(m.getMaxMass())}{units.T}
: null }
- { m.getRange() ? {translate('range')}: {formats.f2(m.getRange())}{units.km}
: null }
+ { m.getRange() ? {translate('range', m.grp)}: {formats.f2(m.getRange())}{units.km}
: null }
{ m.time ? {translate('time')}: {formats.time(m.time)}
: null }
{ m.getThermalEfficiency() ? {translate('efficiency')}: {formats.f2(m.getThermalEfficiency())}
: null }
{ m.getPowerGeneration() > 0 ? {translate('pgen')}: {formats.f1(m.getPowerGeneration())}{units.MW}
: null }
diff --git a/src/app/i18n/en.js b/src/app/i18n/en.js
index 6ed56ba4..36fd123e 100644
--- a/src/app/i18n/en.js
+++ b/src/app/i18n/en.js
@@ -230,8 +230,7 @@ export const terms = {
regen: 'Regeneration rate',
reload: 'Reload',
rof: 'Rate of fire',
- scanangle: 'Scan angle',
- scanrange: 'Scan range',
+ angle: 'Scan angle',
scantime: 'Scan time',
shield: 'Shield',
shieldboost: 'Shield boost',
@@ -253,6 +252,8 @@ export const terms = {
optmul_sg: 'Optimal strength',
maxmul_sg: 'Minimum strength',
+ range_s: 'Typical emission range',
+
// Damage types
absolute: 'Absolute',
explosive: 'Explosive',
diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx
index d9452d74..11b4b71a 100644
--- a/src/app/pages/OutfittingPage.jsx
+++ b/src/app/pages/OutfittingPage.jsx
@@ -90,7 +90,7 @@ export default class OutfittingPage extends Page {
this._getTitle = getTitle.bind(this, data.properties.name);
// Obtain ship control from code
- const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = this._obtainControlFromCode(ship, code);
+ const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange } = this._obtainControlFromCode(ship, code);
return {
error: null,
title: this._getTitle(buildName),
@@ -109,6 +109,9 @@ export default class OutfittingPage extends Page {
cargo,
opponent,
opponentBuild,
+ opponentSys,
+ opponentEng,
+ opponentWep,
engagementRange
};
}
@@ -167,7 +170,10 @@ export default class OutfittingPage extends Page {
let fuel = ship.fuelCapacity;
let cargo = ship.cargoCapacity;
let opponent = new Ship('eagle', Ships['eagle'].properties, Ships['eagle'].slots).buildWith(Ships['eagle'].defaults);
- let opponentBuild = undefined;
+ let opponentSys = 2;
+ let opponentEng = 2;
+ let opponentWep = 2;
+ let opponentBuild;
let engagementRange = 1000;
// Obtain updates from code, if available
@@ -187,8 +193,19 @@ export default class OutfittingPage extends Page {
opponent = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots);
if (control[7] && Persist.getBuild(shipId, control[7])) {
// Ship is a particular build
- opponent.buildFrom(Persist.getBuild(shipId, control[7]));
+ const opponentCode = Persist.getBuild(shipId, control[7]);
+ opponent.buildFrom(opponentCode);
opponentBuild = control[7];
+ if (opponentBuild) {
+ // Obtain opponent's sys/eng/wep pips from their code
+ const opponentParts = opponentCode.split('.');
+ if (opponentParts.length >= 5) {
+ const opponentControl = LZString.decompressFromBase64(Utils.fromUrlSafe(opponentParts[4])).split('/');
+ opponentSys = parseFloat(opponentControl[0]);
+ opponentEng = parseFloat(opponentControl[1]);
+ opponentWep = parseFloat(opponentControl[2]);
+ }
+ }
} else {
// Ship is a stock build
opponent.buildWith(Ships[shipId].defaults);
@@ -198,7 +215,7 @@ export default class OutfittingPage extends Page {
}
}
- return { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange };
+ return { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange };
}
/**
From c7ea1eb95aa7015fd180f55e914b4212633aa30b Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Wed, 22 Mar 2017 14:19:21 +0000
Subject: [PATCH 59/87] Use opponent's pip info
---
src/app/components/Defence.jsx | 13 +++++++------
src/app/components/Offence.jsx | 7 ++++---
src/app/components/OutfittingSubpages.jsx | 21 ++++++++++++---------
src/app/components/ShipPicker.jsx | 2 +-
src/app/pages/OutfittingPage.jsx | 15 ++++++++++++---
src/app/shipyard/Calculations.js | 22 ++++++----------------
6 files changed, 42 insertions(+), 38 deletions(-)
diff --git a/src/app/components/Defence.jsx b/src/app/components/Defence.jsx
index d365206c..7d6f5e78 100644
--- a/src/app/components/Defence.jsx
+++ b/src/app/components/Defence.jsx
@@ -18,7 +18,8 @@ export default class Defence extends TranslatedComponent {
ship: React.PropTypes.object.isRequired,
opponent: React.PropTypes.object.isRequired,
engagementrange: React.PropTypes.number.isRequired,
- sys: React.PropTypes.number.isRequired
+ sys: React.PropTypes.number.isRequired,
+ opponentWep: React.PropTypes.number.isRequired
};
/**
@@ -28,7 +29,7 @@ export default class Defence extends TranslatedComponent {
constructor(props) {
super(props);
- const { shield, armour, shielddamage, armourdamage } = Calc.defenceMetrics(props.ship, props.opponent, props.sys, props.engagementrange);
+ const { shield, armour, shielddamage, armourdamage } = Calc.defenceMetrics(props.ship, props.opponent, props.sys, props.opponentWep, props.engagementrange);
this.state = { shield, armour, shielddamage, armourdamage };
}
@@ -39,7 +40,7 @@ export default class Defence extends TranslatedComponent {
*/
componentWillReceiveProps(nextProps) {
if (this.props.marker != nextProps.marker || this.props.sys != nextProps.sys) {
- const { shield, armour, shielddamage, armourdamage } = Calc.defenceMetrics(nextProps.ship, nextProps.opponent, nextProps.sys, nextProps.engagementrange);
+ const { shield, armour, shielddamage, armourdamage } = Calc.defenceMetrics(nextProps.ship, nextProps.opponent, nextProps.sys, nextProps.opponentWep, nextProps.engagementrange);
this.setState({ shield, armour, shielddamage, armourdamage });
}
return true;
@@ -50,7 +51,7 @@ export default class Defence extends TranslatedComponent {
* @return {React.Component} contents
*/
render() {
- const { ship, sys } = this.props;
+ const { ship, sys, opponentWep } = this.props;
const { language, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { shield, armour, shielddamage, armourdamage } = this.state;
@@ -155,7 +156,7 @@ export default class Defence extends TranslatedComponent {
{translate('shield metrics')}
{shieldTooltipDetails} )} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw shield strength')} {formats.int(shield.total)}{units.MJ}
- {translate('PHRASE_TIME_TO_LOSE_SHIELDS')} {shielddamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(shield.total, shielddamage.totalsdps, shielddamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate()))}
+ {translate('PHRASE_TIME_TO_LOSE_SHIELDS')} {shielddamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(shield.total, shielddamage.totalsdps, shielddamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * opponentWep / 4))}
{translate('PHRASE_TIME_TO_RECOVER_SHIELDS')} {shield.recover === Math.Inf ? translate('never') : formats.time(shield.recover)}
{translate('PHRASE_TIME_TO_RECHARGE_SHIELDS')} {shield.recharge === Math.Inf ? translate('never') : formats.time(shield.recharge)}
@@ -176,7 +177,7 @@ export default class Defence extends TranslatedComponent {
{translate('armour metrics')}
{armourTooltipDetails} )} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw armour strength')} {formats.int(armour.total)}
- {translate('PHRASE_TIME_TO_LOSE_ARMOUR')} {armourdamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(armour.total, armourdamage.totalsdps, armourdamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate()))}
+ {translate('PHRASE_TIME_TO_LOSE_ARMOUR')} {armourdamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(armour.total, armourdamage.totalsdps, armourdamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * opponentWep / 4))}
{translate('raw module armour')} {formats.int(armour.modulearmour)}
{translate('PHRASE_MODULE_PROTECTION_EXTERNAL')} {formats.pct1(armour.moduleprotection / 2)}
{translate('PHRASE_MODULE_PROTECTION_INTERNAL')} {formats.pct1(armour.moduleprotection)}
diff --git a/src/app/components/Offence.jsx b/src/app/components/Offence.jsx
index bb880d4d..0faa5c82 100644
--- a/src/app/components/Offence.jsx
+++ b/src/app/components/Offence.jsx
@@ -55,7 +55,8 @@ export default class Offence extends TranslatedComponent {
ship: React.PropTypes.object.isRequired,
opponent: React.PropTypes.object.isRequired,
engagementrange: React.PropTypes.number.isRequired,
- wep: React.PropTypes.number.isRequired
+ wep: React.PropTypes.number.isRequired,
+ opponentSys: React.PropTypes.number.isRequired
};
/**
@@ -67,7 +68,7 @@ export default class Offence extends TranslatedComponent {
this._sort = this._sort.bind(this);
- const damage = Calc.offenceMetrics(props.ship, props.opponent, props.eng, props.engagementrange);
+ const damage = Calc.offenceMetrics(props.ship, props.opponent, props.wep, props.opponentSys, props.engagementrange);
this.state = {
predicate: 'n',
desc: true,
@@ -82,7 +83,7 @@ export default class Offence extends TranslatedComponent {
*/
componentWillReceiveProps(nextProps) {
if (this.props.marker != nextProps.marker || this.props.eng != nextProps.eng) {
- const damage = Calc.offenceMetrics(nextProps.ship, nextProps.opponent, nextProps.wep, nextProps.engagementrange);
+ const damage = Calc.offenceMetrics(nextProps.ship, nextProps.opponent, nextProps.wep, nextProps.opponentSys, nextProps.engagementrange);
this.setState({ damage });
}
return true;
diff --git a/src/app/components/OutfittingSubpages.jsx b/src/app/components/OutfittingSubpages.jsx
index 6a8bf2bd..5eb2abb1 100644
--- a/src/app/components/OutfittingSubpages.jsx
+++ b/src/app/components/OutfittingSubpages.jsx
@@ -31,7 +31,10 @@ export default class OutfittingSubpages extends TranslatedComponent {
boost: React.PropTypes.bool.isRequired,
engagementRange: React.PropTypes.number.isRequired,
opponent: React.PropTypes.object.isRequired,
- opponentBuild: React.PropTypes.string
+ opponentBuild: React.PropTypes.string,
+ opponentSys: React.PropTypes.number.isRequired,
+ opponentEng: React.PropTypes.number.isRequired,
+ opponentWep: React.PropTypes.number.isRequired,
};
/**
@@ -80,7 +83,7 @@ export default class OutfittingSubpages extends TranslatedComponent {
* @return {React.Component} Tab contents
*/
_profilesTab() {
- const { ship, opponent, cargo, fuel, eng, boost, engagementRange } = this.props;
+ const { ship, opponent, cargo, fuel, eng, boost, engagementRange, opponentSys } = this.props;
const { translate } = this.context.language;
let realBoost = boost && ship.canBoost();
Persist.setOutfittingTab('profiles');
@@ -88,7 +91,7 @@ export default class OutfittingSubpages extends TranslatedComponent {
const engineProfileMarker = `${ship.toString()}:${cargo}:${fuel}:${eng}:${realBoost}`;
const fsdProfileMarker = `${ship.toString()}:${cargo}:${fuel}`;
const movementMarker = `${ship.topSpeed}:${ship.pitch}:${ship.roll}:${ship.yaw}:${ship.canBoost()}`;
- const damageMarker = `${ship.toString()}:${opponent.toString()}:${engagementRange}`;
+ const damageMarker = `${ship.toString()}:${opponent.toString()}:${engagementRange}:${opponentSys}`;
return
@@ -123,13 +126,13 @@ export default class OutfittingSubpages extends TranslatedComponent {
* @return {React.Component} Tab contents
*/
_offenceTab() {
- const { ship, sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild } = this.props;
+ const { ship, sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild, opponentSys } = this.props;
Persist.setOutfittingTab('offence');
- const marker = `${ship.toString()}:${opponent.toString()}:${opponentBuild}:${engagementRange}`;
+ const marker = `${ship.toString()}${opponent.toString()}${opponentBuild}${engagementRange}${opponentSys}`;
return
-
+
;
}
@@ -138,13 +141,13 @@ export default class OutfittingSubpages extends TranslatedComponent {
* @return {React.Component} Tab contents
*/
_defenceTab() {
- const { ship, sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild } = this.props;
+ const { ship, sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild, opponentWep } = this.props;
Persist.setOutfittingTab('defence');
- const marker = `${ship.toString()}:${opponent.toString()}:${opponentBuild}:${engagementRange}`;
+ const marker = `${ship.toString()}${opponent.toString()}{opponentBuild}${engagementRange}${opponentWep}`;
return
-
+
;
}
diff --git a/src/app/components/ShipPicker.jsx b/src/app/components/ShipPicker.jsx
index c2bcb9a4..93505b8d 100644
--- a/src/app/components/ShipPicker.jsx
+++ b/src/app/components/ShipPicker.jsx
@@ -45,7 +45,7 @@ export default class ShipPicker extends TranslatedComponent {
this._closeMenu();
// Ensure that the ship has changed
- if (ship !== this.props.ship || this.build !== this.props.build) {
+ if (ship !== this.props.ship || build !== this.props.build) {
this.props.onChange(ship, build);
}
}
diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx
index 11b4b71a..81372ec8 100644
--- a/src/app/pages/OutfittingPage.jsx
+++ b/src/app/pages/OutfittingPage.jsx
@@ -299,16 +299,22 @@ export default class OutfittingPage extends Page {
Persist.saveBuild(shipId, newBuildName, code);
this._updateRoute(shipId, newBuildName, code);
- let opponent, opponentBuild;
+ let opponent, opponentBuild, opponentSys, opponentEng, opponentWep;
if (shipId === this.state.opponent.id && buildName === this.state.opponentBuild) {
// This is a save of our current opponent build; update it
opponentBuild = newBuildName;
opponent = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots).buildFrom(code);
+ opponentSys = this.state.sys;
+ opponentEng = this.state.eng;
+ opponentWep = this.state.wep;
} else {
opponentBuild = this.state.opponentBuild;
opponent = this.state.opponent;
+ opponentSys = this.state.opponentSys;
+ opponentEng = this.state.opponentEng;
+ opponentWep = this.state.opponentWep;
}
- this.setState({ buildName: newBuildName, code, savedCode: code, opponent, opponentBuild, title: this._getTitle(newBuildName) });
+ this.setState({ buildName: newBuildName, code, savedCode: code, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, title: this._getTitle(newBuildName) });
}
/**
@@ -507,7 +513,7 @@ export default class OutfittingPage extends Page {
let state = this.state,
{ language, termtip, tooltip, sizeRatio, onWindowResize } = this.context,
{ translate, units, formats } = language,
- { ship, code, savedCode, buildName, newBuildName, sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = state,
+ { ship, code, savedCode, buildName, newBuildName, sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange } = state,
hide = tooltip.bind(null, null),
menu = this.props.currentMenu,
shipUpdated = this._shipUpdated,
@@ -616,6 +622,9 @@ export default class OutfittingPage extends Page {
engagementRange={engagementRange}
opponent={opponent}
opponentBuild={opponentBuild}
+ opponentSys={opponentSys}
+ opponentEng={opponentEng}
+ opponentWep={opponentWep}
/>
);
diff --git a/src/app/shipyard/Calculations.js b/src/app/shipyard/Calculations.js
index 30432949..3b806710 100644
--- a/src/app/shipyard/Calculations.js
+++ b/src/app/shipyard/Calculations.js
@@ -533,10 +533,11 @@ export function armourMetrics(ship) {
* @param {Object} ship The ship
* @param {Object} opponent The opponent ship
* @param {int} sys The pips to SYS
+ * @param {int} opponentWep The pips to pponent's WEP
* @param {int} engagementrange The range between the ship and opponent
* @returns {Object} Defence metrics
*/
-export function defenceMetrics(ship, opponent, sys, engagementrange) {
+export function defenceMetrics(ship, opponent, sys, opponentWep, engagementrange) {
// Obtain the shield metrics
const shield = this.shieldMetrics(ship, sys);
@@ -572,15 +573,16 @@ export function defenceMetrics(ship, opponent, sys, engagementrange) {
* @param {Object} ship The ship
* @param {Object} opponent The opponent ship
* @param {int} wep The pips to WEP
+ * @param {int} opponentSys The pips to opponent's SYS
* @param {int} engagementrange The range between the ship and opponent
* @returns {array} Offence metrics
*/
-export function offenceMetrics(ship, opponent, wep, engagementrange) {
+export function offenceMetrics(ship, opponent, wep, opponentSys, engagementrange) {
// Per-weapon and total damage
const damage = [];
// Obtain the opponent's shield and armour metrics
- const opponentShields = this.shieldMetrics(opponent, 4);
+ const opponentShields = this.shieldMetrics(opponent, opponentSys);
const opponentArmour = this.armourMetrics(opponent);
// Per-weapon and total damage to shields
@@ -612,18 +614,6 @@ export function offenceMetrics(ship, opponent, wep, engagementrange) {
}
return damage;
-
- const shielddamage = opponentShields.generator ? {
- absolute: {
- weapon1: 10,
- weapon2: 10,
- weapon3: 10,
- weapon4: 10,
- total: 40
- }
- } : {};
-
- return damage;
}
/**
@@ -632,7 +622,7 @@ export function offenceMetrics(ship, opponent, wep, engagementrange) {
* @returns {integer} the resistance for the given pips
*/
export function sysResistance(sys) {
- return Math.pow(sys,0.85) * 0.6 / Math.pow(4,0.85);
+ return Math.pow(sys, 0.85) * 0.6 / Math.pow(4, 0.85);
}
/**
From d15e49f315827623d227b8d7148d95a74839c7bb Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Wed, 22 Mar 2017 17:53:26 +0000
Subject: [PATCH 60/87] Ignore rpshot for eps and hps
---
ChangeLog.md | 1 +
src/app/components/ModificationsMenu.jsx | 24 +++++--------
src/app/shipyard/Module.js | 8 ++---
src/app/shipyard/Ship.js | 33 ++++++++++++++---
src/app/utils/BlueprintFunctions.js | 46 ++++++++++++++++++++++++
src/app/utils/CompanionApiUtils.js | 5 +--
6 files changed, 92 insertions(+), 25 deletions(-)
diff --git a/ChangeLog.md b/ChangeLog.md
index ec2992c6..0158383b 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -11,6 +11,7 @@
* Ensure that hull reinforcement modifications take the inherent resistance in to account when calculating modification percentages
* Add tooltip for blueprints providing details of the features they alter
* Use opponent's saved pips if available
+ * Ignore rounds per shot for EPS and HPS calculations; it's already factored in to the numbers
#2.2.19
* Power management panel now displays modules in descending order of power usage by default
diff --git a/src/app/components/ModificationsMenu.jsx b/src/app/components/ModificationsMenu.jsx
index 8faea959..ee950579 100644
--- a/src/app/components/ModificationsMenu.jsx
+++ b/src/app/components/ModificationsMenu.jsx
@@ -5,7 +5,7 @@ import { isEmpty, stopCtxPropagation } from '../utils/UtilityFunctions';
import cn from 'classnames';
import { Modifications } from 'coriolis-data/dist';
import Modification from './Modification';
-import { blueprintTooltip } from '../utils/BlueprintFunctions';
+import { getBlueprint, blueprintTooltip } from '../utils/BlueprintFunctions';
/**
* Modifications menu
@@ -51,10 +51,11 @@ export default class ModificationsMenu extends TranslatedComponent {
let blueprints = [];
for (const blueprintName in Modifications.modules[m.grp].blueprints) {
for (const grade of Modifications.modules[m.grp].blueprints[blueprintName]) {
- const close = this._blueprintSelected.bind(this, Modifications.blueprints[blueprintName].id, grade);
+ const close = this._blueprintSelected.bind(this, blueprintName, grade);
const key = blueprintName + ':' + grade;
- const tooltipContent = blueprintTooltip(translate, Modifications.blueprints[blueprintName].grades[grade].features);
- blueprints.push({translate(Modifications.blueprints[blueprintName].name + ' grade ' + grade)}
);
+ const blueprint = getBlueprint(blueprintName, m);
+ const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade].features);
+ blueprints.push({translate(blueprint.name + ' grade ' + grade)}
);
}
}
@@ -105,12 +106,12 @@ export default class ModificationsMenu extends TranslatedComponent {
/**
* Activated when a blueprint is selected
- * @param {int} blueprintId The ID of the selected blueprint
- * @param {int} grade The grade of the selected blueprint
+ * @param {int} fdname The Frontier name of the blueprint
+ * @param {int} grade The grade of the selected blueprint
*/
- _blueprintSelected(blueprintId, grade) {
+ _blueprintSelected(fdname, grade) {
const { m } = this.props;
- const blueprint = Object.assign({}, _.find(Modifications.blueprints, function(o) { return o.id === blueprintId; }));
+ const blueprint = getBlueprint(fdname, m);
blueprint.grade = grade;
m.blueprint = blueprint;
@@ -155,13 +156,6 @@ export default class ModificationsMenu extends TranslatedComponent {
* @param {number} value The value of the roll
*/
_setRollResult(ship, m, featureName, value) {
- if (Modifications.modifications[featureName].method !== 'overwrite') {
- if (m.grp == 'sb' && featureName == 'shieldboost') {
- // Shield boosters are a special case. Their boost is dependent on their base so we need to calculate the value here
- value = ((1 + m.shieldboost) * (1 + value) - 1) / m.shieldboost - 1;
- }
- }
-
if (Modifications.modifications[featureName].type == 'percentage') {
ship.setModification(m, featureName, value * 10000);
} else if (Modifications.modifications[featureName].type == 'numeric') {
diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js
index e013609b..2ea29321 100755
--- a/src/app/shipyard/Module.js
+++ b/src/app/shipyard/Module.js
@@ -554,10 +554,10 @@ export default class Module {
getEps() {
// EPS is a synthetic value
let distdraw = this.getDistDraw();
- let rpshot = this.roundspershot || 1;
+ // We don't use rpshot here as dist draw is per combined shot
let rof = this.getRoF() || 1;
- return distdraw * rpshot * rof;
+ return distdraw * rof;
}
/**
@@ -567,10 +567,10 @@ export default class Module {
getHps() {
// HPS is a synthetic value
let heat = this.getThermalLoad();
- let rpshot = this.roundspershot || 1;
+ // We don't use rpshot here as dist draw is per combined shot
let rof = this.getRoF() || 1;
- return heat * rpshot * rof;
+ return heat * rof;
}
/**
diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js
index ff564bbb..d207279e 100755
--- a/src/app/shipyard/Ship.js
+++ b/src/app/shipyard/Ship.js
@@ -1,6 +1,7 @@
import * as Calc from './Calculations';
import * as ModuleUtils from './ModuleUtils';
import * as Utils from '../utils/UtilityFunctions';
+import { getBlueprint } from '../utils/BlueprintFunctions';
import Module from './Module';
import LZString from 'lz-string';
import * as _ from 'lodash';
@@ -588,7 +589,13 @@ export default class Ship {
this.bulkheads.m = null;
this.useBulkhead(comps && comps.bulkheads ? comps.bulkheads : 0, true);
this.bulkheads.m.mods = mods && mods[0] ? mods[0] : {};
- this.bulkheads.m.blueprint = blueprints && blueprints[0] ? blueprints[0] : {};
+ if (blueprints && blueprints[0]) {
+ this.bulkheads.m.blueprint = getBlueprint(blueprints[0].fdname, this.bulkheads.m);
+ this.bulkheads.m.blueprint.grade = blueprints[0].grade;
+ this.bulkheads.m.blueprint.special = blueprints[0].special;
+ } else {
+ this.bulkheads.m.blueprint = {};
+ }
this.cargoHatch.priority = priorities ? priorities[0] * 1 : 0;
this.cargoHatch.enabled = enabled ? enabled[0] * 1 : true;
@@ -602,7 +609,13 @@ export default class Ship {
let module = ModuleUtils.standard(i, comps.standard[i]);
if (module != null) {
module.mods = mods && mods[i + 1] ? mods[i + 1] : {};
- module.blueprint = blueprints && blueprints[i + 1] ? blueprints[i + 1] : {};
+ if (blueprints && blueprints[i + 1]) {
+ module.blueprint = getBlueprint(blueprints[i + 1].fdname, module);
+ module.blueprint.grade = blueprints[i + 1].grade;
+ module.blueprint.special = blueprints[i + 1].special;
+ } else {
+ module.blueprint = {};
+ }
}
this.use(standard[i], module, true);
}
@@ -624,7 +637,13 @@ export default class Ship {
let module = ModuleUtils.hardpoints(comps.hardpoints[i]);
if (module != null) {
module.mods = mods && mods[cl + i] ? mods[cl + i] : {};
- module.blueprint = blueprints && blueprints[cl + i] ? blueprints[cl + i] : {};
+ if (blueprints && blueprints[cl + i]) {
+ module.blueprint = getBlueprint(blueprints[cl + i].fdname, module);
+ module.blueprint.grade = blueprints[cl + i].grade;
+ module.blueprint.special = blueprints[cl + i].special;
+ } else {
+ module.blueprint = {};
+ }
}
this.use(hps[i], module, true);
}
@@ -644,7 +663,13 @@ export default class Ship {
let module = ModuleUtils.internal(comps.internal[i]);
if (module != null) {
module.mods = mods && mods[cl + i] ? mods[cl + i] : {};
- module.blueprint = blueprints && blueprints[cl + i] ? blueprints[cl + i] : {};
+ if (blueprints && blueprints[cl + i]) {
+ module.blueprint = getBlueprint(blueprints[cl + i].fdname, module);
+ module.blueprint.grade = blueprints[cl + i].grade;
+ module.blueprint.special = blueprints[cl + i].special;
+ } else {
+ module.blueprint = {};
+ }
}
this.use(internal[i], module, true);
}
diff --git a/src/app/utils/BlueprintFunctions.js b/src/app/utils/BlueprintFunctions.js
index ece22eed..0389f2cf 100644
--- a/src/app/utils/BlueprintFunctions.js
+++ b/src/app/utils/BlueprintFunctions.js
@@ -70,3 +70,49 @@ export function isBeneficial(feature, values) {
return fact;
}
}
+
+/**
+ * Get a blueprint with a given name and an optional module
+ * @param {string} name The name of the blueprint
+ * @param {Object} module The module for which to obtain this blueprint
+ * @returns {Object} The matching blueprint
+ */
+export function getBlueprint(name, module) {
+ // Start with a copy of the blueprint
+ const blueprint = JSON.parse(JSON.stringify(Modifications.blueprints[name]));
+ if (module) {
+ if (module.grp === 'bh') {
+ // Bulkheads need to have their resistances altered
+ for (const grade in blueprint.grades) {
+ for (const feature in blueprint.grades[grade].features) {
+ if (feature === 'explres') {
+ blueprint.grades[grade].features[feature][0] *= (1 - module.explres);
+ blueprint.grades[grade].features[feature][1] *= (1 - module.explres);
+ }
+ if (feature === 'kinres') {
+ blueprint.grades[grade].features[feature][0] *= (1 - module.kinres);
+ blueprint.grades[grade].features[feature][1] *= (1 - module.kinres);
+ }
+ if (feature === 'thermres') {
+ blueprint.grades[grade].features[feature][0] *= (1 - module.thermres);
+ blueprint.grades[grade].features[feature][1] *= (1 - module.thermres);
+ }
+ }
+ }
+ }
+ if (module.grp === 'sb') {
+ // Shield boosters are treated internally as straight modifiers, so rather than (for example)
+ // being a 4% boost they are a 104% multiplier. We need to fix the values here so that they look
+ // accurate as per the information in Elite
+ for (const grade in blueprint.grades) {
+ for (const feature in blueprint.grades[grade].features) {
+ if (feature === 'shieldboost') {
+ blueprint.grades[grade].features[feature][0] = ((1 + blueprint.grades[grade].features[feature][0]) * (1 + module.shieldboost) - 1)/ module.shieldboost - 1;
+ blueprint.grades[grade].features[feature][1] = ((1 + blueprint.grades[grade].features[feature][1]) * (1 + module.shieldboost) - 1)/ module.shieldboost - 1;
+ }
+ }
+ }
+ }
+ }
+ return blueprint;
+}
diff --git a/src/app/utils/CompanionApiUtils.js b/src/app/utils/CompanionApiUtils.js
index 6c6a2272..47a1db01 100644
--- a/src/app/utils/CompanionApiUtils.js
+++ b/src/app/utils/CompanionApiUtils.js
@@ -2,6 +2,7 @@ import React from 'react';
import { Modifications, Modules, Ships } from 'coriolis-data/dist';
import Module from '../shipyard/Module';
import Ship from '../shipyard/Ship';
+import { getBlueprint } from '../utils/BlueprintFunctions';
// mapping from fd's ship model names to coriolis'
const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
@@ -335,9 +336,9 @@ function _addModifications(module, modifiers, blueprint, grade) {
}
}
- // Add the blueprint ID, grade and special
+ // Add the blueprint definition, grade and special
if (blueprint) {
- module.blueprint = Object.assign({}, Modifications.blueprints[blueprint]);
+ module.blueprint = getBlueprint(blueprint, module);
if (grade) {
module.blueprint.grade = Number(grade);
}
From 8ad9472d5612679b8466159a4e31af13add26a53 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Wed, 22 Mar 2017 18:24:22 +0000
Subject: [PATCH 61/87] Provide correct blueprints limits for HRPs
---
src/app/utils/BlueprintFunctions.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/app/utils/BlueprintFunctions.js b/src/app/utils/BlueprintFunctions.js
index 0389f2cf..8c9f117d 100644
--- a/src/app/utils/BlueprintFunctions.js
+++ b/src/app/utils/BlueprintFunctions.js
@@ -81,8 +81,8 @@ export function getBlueprint(name, module) {
// Start with a copy of the blueprint
const blueprint = JSON.parse(JSON.stringify(Modifications.blueprints[name]));
if (module) {
- if (module.grp === 'bh') {
- // Bulkheads need to have their resistances altered
+ if (module.grp === 'bh' || module.grp === 'hr') {
+ // Bulkheads and hull reinforcements need to have their resistances altered by the base values
for (const grade in blueprint.grades) {
for (const feature in blueprint.grades[grade].features) {
if (feature === 'explres') {
From b055963fe095a29dccb58d4aac03121caeb07dff Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Wed, 22 Mar 2017 18:34:57 +0000
Subject: [PATCH 62/87] Fixes
---
src/app/utils/BlueprintFunctions.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/app/utils/BlueprintFunctions.js b/src/app/utils/BlueprintFunctions.js
index 8c9f117d..dcaf9555 100644
--- a/src/app/utils/BlueprintFunctions.js
+++ b/src/app/utils/BlueprintFunctions.js
@@ -30,7 +30,7 @@ export function blueprintTooltip(translate, features, m)
if (m) {
// We have a module - add in the current value
let current = m.getModValue(feature);
- if (featureDef.type === 'percentage') {
+ if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
}
results.push({translate(feature)} {lowerBound}{symbol} {current}{symbol} {upperBound}{symbol} );
From 94d876e934de89ae197bd3eafb2bc731d7c18246 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Wed, 22 Mar 2017 21:38:33 +0000
Subject: [PATCH 63/87] Per-cell colour coding for blueprint tooltips
---
src/app/utils/BlueprintFunctions.js | 33 ++++++++++++++++++++++++++---
1 file changed, 30 insertions(+), 3 deletions(-)
diff --git a/src/app/utils/BlueprintFunctions.js b/src/app/utils/BlueprintFunctions.js
index dcaf9555..7dcddf04 100644
--- a/src/app/utils/BlueprintFunctions.js
+++ b/src/app/utils/BlueprintFunctions.js
@@ -26,17 +26,32 @@ export function blueprintTooltip(translate, features, m)
lowerBound = Math.round(lowerBound * 1000) / 10;
upperBound = Math.round(upperBound * 1000) / 10;
}
- const range = `${lowerBound}${symbol} - ${upperBound}${symbol}`;
+ const lowerIsBeneficial = isValueBeneficial(feature, lowerBound);
+ const upperIsBeneficial = isValueBeneficial(feature, upperBound);
if (m) {
// We have a module - add in the current value
let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
}
- results.push({translate(feature)} {lowerBound}{symbol} {current}{symbol} {upperBound}{symbol} );
+ const currentIsBeneficial = isValueBeneficial(feature, current);
+ results.push(
+
+ {translate(feature)}
+ {lowerBound}{symbol}
+ {current}{symbol}
+ {upperBound}{symbol}
+
+ );
} else {
// We do not have a module, no value
- results.push({translate(feature)} {lowerBound}{symbol} {upperBound}{symbol} );
+ results.push(
+
+ {translate(feature)}
+ {lowerBound}{symbol}
+ {upperBound}{symbol}
+
+ );
}
}
}
@@ -71,6 +86,18 @@ export function isBeneficial(feature, values) {
}
}
+/**
+ * Is this feature value beneficial?
+ *
+ */
+export function isValueBeneficial(feature, value) {
+ if (Modifications.modifications[feature].higherbetter) {
+ return value > 0;
+ } else {
+ return value < 0;
+ }
+}
+
/**
* Get a blueprint with a given name and an optional module
* @param {string} name The name of the blueprint
From 873935c1d497dea3b838c839a48e0f27a535807b Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Thu, 23 Mar 2017 08:17:07 +0000
Subject: [PATCH 64/87] Update to documentation
---
src/app/components/EngineProfile.jsx | 1 +
src/app/components/FSDProfile.jsx | 1 +
src/app/components/LineChart.jsx | 8 +-
src/app/components/Offence.jsx | 2 +-
src/app/components/OutfittingSubpages.jsx | 4 +-
src/app/components/WeaponDamageChart.jsx | 8 +-
src/app/i18n/en.js | 113 ++++++++++++++--------
7 files changed, 90 insertions(+), 47 deletions(-)
diff --git a/src/app/components/EngineProfile.jsx b/src/app/components/EngineProfile.jsx
index 2faff719..8c9ddf0f 100644
--- a/src/app/components/EngineProfile.jsx
+++ b/src/app/components/EngineProfile.jsx
@@ -99,6 +99,7 @@ export default class EngineProfile extends TranslatedComponent {
func={this.state.calcMaxSpeedFunc}
points={1000}
code={code}
+ aspect={0.7}
/>
);
}
diff --git a/src/app/components/FSDProfile.jsx b/src/app/components/FSDProfile.jsx
index c5fea602..81548632 100644
--- a/src/app/components/FSDProfile.jsx
+++ b/src/app/components/FSDProfile.jsx
@@ -97,6 +97,7 @@ export default class FSDProfile extends TranslatedComponent {
func={this.state.calcMaxRangeFunc}
points={200}
code={code}
+ aspect={0.7}
/>
);
}
diff --git a/src/app/components/LineChart.jsx b/src/app/components/LineChart.jsx
index 24edc448..33550cc6 100644
--- a/src/app/components/LineChart.jsx
+++ b/src/app/components/LineChart.jsx
@@ -15,7 +15,8 @@ export default class LineChart extends TranslatedComponent {
xMin: 0,
yMin: 0,
points: 20,
- colors: ['#ff8c0d']
+ colors: ['#ff8c0d'],
+ aspect: 0.5
};
static propTypes = {
@@ -32,6 +33,7 @@ export default class LineChart extends TranslatedComponent {
series: React.PropTypes.array,
colors: React.PropTypes.array,
points: React.PropTypes.number,
+ aspect: React.PropTypes.number,
code: React.PropTypes.string,
};
@@ -123,7 +125,7 @@ export default class LineChart extends TranslatedComponent {
const { xMax, xMin, yMin, yMax } = props;
const { width, height } = this.state.dimensions;
const innerWidth = width - MARGIN.left - MARGIN.right;
- const outerHeight = Math.round(width * 2 / 3); // TODO make this an aspect property
+ const outerHeight = Math.round(width * props.aspect);
const innerHeight = outerHeight - MARGIN.top - MARGIN.bottom;
this.state.xScale.range([0, innerWidth]).domain([xMin, xMax || 1]).clamp(true);
@@ -232,7 +234,7 @@ export default class LineChart extends TranslatedComponent {
const lines = seriesLines.map((line, i) => ).reverse();
const markX = xMark ? innerWidth * (xMark - xMin) / (xMax - xMin) : 0;
- const xmark = xMark ? : '';
+ const xmark = xMark ? : '';
return (
{ this.setState({ dimensions }); }}>
diff --git a/src/app/components/Offence.jsx b/src/app/components/Offence.jsx
index 0faa5c82..4f502c0f 100644
--- a/src/app/components/Offence.jsx
+++ b/src/app/components/Offence.jsx
@@ -171,7 +171,7 @@ export default class Offence extends TranslatedComponent {
const effectivenessShieldsTooltipDetails = [];
effectivenessShieldsTooltipDetails.push({translate('range') + ' ' + formats.pct1(weapon.effectiveness.shields.range)}
);
effectivenessShieldsTooltipDetails.push({translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.shields.resistance)}
);
- effectivenessShieldsTooltipDetails.push({translate('sys') + ' ' + formats.pct1(weapon.effectiveness.shields.sys)}
);
+ effectivenessShieldsTooltipDetails.push({translate('power distributor') + ' ' + formats.pct1(weapon.effectiveness.shields.sys)}
);
const effectiveShieldsSDpsTooltipDetails = [];
if (weapon.sdps.shields.absolute) effectiveShieldsSDpsTooltipDetails.push({translate('absolute') + ' ' + formats.f1(weapon.sdps.shields.absolute)}
);
diff --git a/src/app/components/OutfittingSubpages.jsx b/src/app/components/OutfittingSubpages.jsx
index 5eb2abb1..2d25037c 100644
--- a/src/app/components/OutfittingSubpages.jsx
+++ b/src/app/components/OutfittingSubpages.jsx
@@ -111,12 +111,12 @@ export default class OutfittingSubpages extends TranslatedComponent {
{translate('damage to opponent\'s shields')}
-
+
{translate('damage to opponent\'s hull')}
-
+
;
}
diff --git a/src/app/components/WeaponDamageChart.jsx b/src/app/components/WeaponDamageChart.jsx
index dfb7f237..9c04fbef 100644
--- a/src/app/components/WeaponDamageChart.jsx
+++ b/src/app/components/WeaponDamageChart.jsx
@@ -20,6 +20,7 @@ export default class WeaponDamageChart extends TranslatedComponent {
opponent: React.PropTypes.object.isRequired,
hull: React.PropTypes.bool.isRequired,
engagementRange: React.PropTypes.number.isRequired,
+ opponentSys: React.PropTypes.number.isRequired,
marker: React.PropTypes.string.isRequired
};
@@ -37,7 +38,7 @@ export default class WeaponDamageChart extends TranslatedComponent {
*/
componentWillMount() {
const weaponNames = this._weaponNames(this.props.ship, this.context);
- const opponentShields = Calc.shieldMetrics(this.props.opponent, 4);
+ const opponentShields = Calc.shieldMetrics(this.props.opponent, this.props.opponentSys);
const opponentArmour = Calc.armourMetrics(this.props.opponent);
const maxRange = this._calcMaxRange(this.props.ship);
const maxDps = this._calcMaxSDps(this.props.ship, this.props.opponent, opponentShields, opponentArmour);
@@ -54,7 +55,7 @@ export default class WeaponDamageChart extends TranslatedComponent {
componentWillReceiveProps(nextProps, nextContext) {
if (nextProps.marker != this.props.marker) {
const weaponNames = this._weaponNames(nextProps.ship, nextContext);
- const opponentShields = Calc.shieldMetrics(nextProps.opponent, 4);
+ const opponentShields = Calc.shieldMetrics(nextProps.opponent, nextProps.opponentSys);
const opponentArmour = Calc.armourMetrics(nextProps.opponent);
const maxRange = this._calcMaxRange(nextProps.ship);
const maxDps = this._calcMaxSDps(nextProps.ship, nextProps.opponent, opponentShields, opponentArmour);
@@ -175,7 +176,7 @@ export default class WeaponDamageChart extends TranslatedComponent {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { maxRange } = this.state;
- const { ship, opponent } = this.props;
+ const { ship, opponent, engagementRange } = this.props;
const sortOrder = this._sortOrder;
const onCollapseExpand = this._onCollapseExpand;
@@ -191,6 +192,7 @@ export default class WeaponDamageChart extends TranslatedComponent {
xUnit={translate('m')}
yLabel={translate('sdps')}
series={this.state.weaponNames}
+ xMark={this.props.engagementRange}
colors={DAMAGE_DEALT_COLORS}
func={this.state.calcSDpsFunc}
points={200}
diff --git a/src/app/i18n/en.js b/src/app/i18n/en.js
index 36fd123e..5c68a1f8 100644
--- a/src/app/i18n/en.js
+++ b/src/app/i18n/en.js
@@ -299,7 +299,7 @@ Along the top of the screen are some of the key values for your build. This is
Here, along with most places in Coriolis, acronyms will have tooltips explaining what they mean. Hover over the acronym to obtain more detail, or look in the glossary at the end of this help.
-All values are the highest possible, assuming that you have maximum pips in the relevant capacitor (ENG for speed, WEP for time to drain, etc.).
+All values are the highest possible, assuming that you an optimal setup for that particular value (maximum pips in ENG for speed, minimum fuel for jump range, etc.). Details of the specific setup for each value are listed in the associated tootip.
Modules
The next set of panels laid out horizontally across the screen contain the modules you have put in your build. From left to right these are the core modules, the internal modules, the hardpoints and the utility mounts. These represent the available slots in your ship and cannot be altered. Each slot has a class, or size, and in general any module up to a given size can fit in a given slot (exceptions being bulkheads, life support and sensors in core modules and restricted internal slots, which can only take a subset of module depending on their restrictions).
@@ -312,30 +312,39 @@ To move a module from one slot to another drag it. If you instead want to copy
Clicking on the headings for each set of modules gives you the ability to either select an overall role for your ship (when clicking the core internal header) or a specific module with which you want to fill all applicable slots (when clicking the other headers).
-Offence Summary
-The offence summary panel provides information about the damage that you deal with your weapons.
+Ship Controls
+The ship controls allow you to set your pips, boost, and amount of fuel and cargo that your build carries. The changes made here will effect the information supplied in the subsequent panels, giving you a clearer view of what effect different changing these items will have.
-The first headline gives an overall damage per second rating: this is the optimum amount of damage the build will do per second according to weapon statistics. After that is a breakdown of the damage per second the build will do for each type of damage: absolute, explosive, kinetic, and thermal.
+Ship control settings are saved as part of a build.
-The next headline gives an overall sustained damage per second rating: this is the optimum amount of damage the build will do per second over a longer period of time, taking in to account ammunition clip capacities and reload times. After that is a breakdown of the sustained damage per second the build will do for each type of damage: absolute, explosive, kinetic, and thermal.
+Opponent
+The opponet selection allows you to choose your opponent. The opponent can be either a stock build of a ship or one of your own saved builds. You can also set the engagement range between you and your opponent. Your selection here will effect the information supplied in the subsequent panels, specifically the Offence and Defence panels.
-The final headline gives an overall damage per energy rating: this is the amount of damage the build will do per unit of weapon capacitor energy expended. After that is a breakdown of the damage per energy the build will do for each type of damage: absolute, explosive, kinetic, and thermal.
+Opponent settings are saved as part of a build.
-Defence Summary
-The defence summary panel provides information about the strength of your defences and the damage that you receive from opponents.
+Power and Costs Sub-panels
+Power
+The power management panel provides information about power usage and priorities. It allows you to enable and disable individual modules, as well as set power priorities for each module. Disabled modules will not be included in the build's statistics, with the exception of Shield Cell Banks as they are usually disabled when not in use and only enabled when required.
-The first headline gives your total shield strength (if you have shields), taking in to account your base shield plus boosters. After that are the details of how long it will take for your shields to recover from 0 to 50% (recovery time) and from 50% to 100% (recharge time). The next line provides a breakdown of the shield damage taken from different damage types. For example, if you damage from kinetic is 60% then it means that a weapon usually dealing 10 points of damage will only deal 6, the rest being resisted by the shield. Note that this does not include any resistance alterations due to pips in your SYS capacitor.
+Costs
+The costs panel provides information about the costs for each of your modules, and the total cost and insurance for your build. By default Coriolis uses the standard costs, however discounts for your ship, modules and insurance can be altered in the 'Settings' at the top-right of the page.
-The second headline gives your total shield cell strength (if you have shield cell banks). This is the sum of the recharge of all of equipped shield cell banks.
+The retrofit costs provides information about the costs of changing the base build for your ship, or your saved build, to the current build.
-The third headline gives your total armour strength, taking in to account your base armour plus hull reinforcement packages. The next line provides a breakdown of the hull damage taken from different damage types. For example, if you damage from kinetic is 120% then it means that a weapon usually dealing 10 points of damage will deal 12.
+The reload costs provides information about the costs of reloading your current build.
-The fourth headline gives your total module protection strength from module reinforcement packages. The next line provides a breakdown of the protection for both internal and external modules whilst all module reinforcement packages are functioning. For example, if external module protection is 20% then 10 points of damage will 2 points of damage to the module reinforcement packages and 8 points of damage to the module
+Profiles
+Profiles provide graphs that show the general performance of modules in your build
-Movement Summary
-The movement summary panel provides information about the build's speed and agility.
+Engine Profile
+The engine profile panel provides information about the capabilities of your current thrusters. The graph shows you how the maximum speed alters with the overall mass of your build. The vertical dashed line on the graph shows your current mass. Your engine profile can be altered by obtaining different thrusters or engineering your existing thrusters, and you can increase your maximum speed by adding pips to the ENG capacitor as well as reducing the amount of fuel and cargo you are carrying as well as reducing the overall weight of the build. You can also temporarily increase your speed by hitting the boost button.
+
+FSD Profile
+The FSD profile panel provides information about the capabilities of your current frame shift drive. The graph shows you how the maximum jump range alters with the overall mass of your build. The vertical dashed line on the graph shows your current maximum single jump range. Your FSD profile can be altered by obtaining a different FSD or engineering your existing FSD, and you can increase your maximum jump range by reducing the amount of fuel and cargo you are carrying as well as reducing the overall weight of the build,
+
+Movement Profile
+The movement profile panel provides information about the capabilities of your current thrusters with your current overall mass and ENG pips settings. The diagram shows your ability to move and rotate in the different axes:
-Along the top of this panel are the number of pips you put in to your ENG capacitor, from 0 to 4 and also include 4 pips and boost (4b). Along the side of this panel are the names of the metrics. These are:
Speed The fastest the ship can move, in metres per second
Pitch The fastest the ship can raise or lower its nose, in degrees per second
@@ -343,44 +352,72 @@ Along the top of this panel are the number of pips you put in to your ENG capaci
Yaw The fastest the ship can turn its nose left or right, in degrees per second
-Power Management
-The power management panel provides information about power usage and priorities. It allows you to enable and disable individual modules, as well as set power priorities for each module.
+Your movement profile can be altered by obtaining different thrusters or engineering your existing thrusters, and you can increase your movement values by adding pips to the ENG capacitor as well as reducing the amount of fuel and cargo you are carrying as well as reducing the overall weight of the build. You can also temporarily increase your movement profile by hitting the boost button.
-Costs
-The costs panel provides information about the costs for each of your modules, and the total cost and insurance for your build. By default Coriolis uses the standard costs, however discounts for your ship, modules and insurance can be altered in the 'Settings' at the top-right of the page.
+Damage Profile
+The damage profile provides two graphs showing how the the build's damage to the opponent's shields and hull change with engagement range. The vertical dashed line on the graph shows your current engagement range. This combines information about the build's weapons with the opponent's shields and hull to provide an accurate picture of sustained damage that can be inflicted on the opponent.
-The retrofit costs provides information about the costs of changing the base build for your ship, or your saved build, to the current build.
+Offence
+Summary
+The offence summary provides per-weapon information about sustained damage per second inflicted to shields and hull, along with a measure of effectiveness of that weapon. The effectiveness value has a tooltip that provides a breakdown of the effectiveness, and can include reductions or increases due to range, resistance, and either power distributor (for shields) or hardness (for hull). The final effectiveness value is calculated by multiplying these percentages together.
-The reload costs provides information about the costs of reloading your current build.
+Offence Metrics
+The offence metrics panel provides information about your offence.
-Engine Profile
-The engine profile panel provides information about the capabilities of your current thrusters. The graph shows you how the maximum speed (with a full fuel tank and 4 pips to engines) alters with the overall mass of your build. The slider can be altered to change the amount of cargo you have on-board. Your engine profile can be altered by obtaining different thrusters or engineering your existing thrusters.
+Time to drain is a measure of how quickly your WEP capacitor will drain when firing all weapons. It is affected by the number of pips you have in your WEP capacitor, with more pips resulting in a higher WEP recharge rate and hence a longer time to drain.
-FSD Profile
-The FSD profile panel provides information about the capabilities of your current frame shift drive. The graph shows you how the maximum jump range (with a full fuel tank) alters with the overall mass of your build. The slider can be altered to change the amount of cargo you have on-board. Your FSD profile can be altered by obtaining a different FSD or engineering your existing FSD.
+The next value is the time it will take you to remove your opponent's shields. This assumes that you have 100% time on target and that your engagement range stays constant. Note that if your time to remove shields is longer than your time to drain this assumes that you continue firing throughout, inflicting lower damage due to the reduced energy in your WEP capacitor.
-Jump Range
-The jump range panel provides information about the build' jump range. The graph shows how the build's jump range changes with the amount of cargo on-board. The slider can be altered to change the amount of fuel you have on-board.
+The next value is the time it will take you to remove your opponent's armour. This follows the same logic as the time to remove shields.
-Damage Dealt
-The damage dealt panel provides information about the effectiveness of your build's weapons against opponents' shields and hull at different engagement distances.
+Shield Damage Sources
+The shield damage sources provides information about the sources of damage to your opponent by damage type. For each applicable type of damage (absolute explosive, kinetic, thermal) a sustained damage per second value is provided.
-The ship against which you want to check damage dealt can be selected by clicking on the red ship icon or the red ship name at the top of this panel.
+Hull Damage Sources
+The hull damage sources provides information about the sources of damage to your opponent by damage type. For each applicable type of damage (absolute explosive, kinetic, thermal) a sustained damage per second value is provided.
-The main section of this panel is a table showing your weapons and their effectiveness. Effectiveness against shields takes in to account the weapon and its engagement range, and assumes standard shield resistances. Effectiveness against hull takes in to account the weapon and, its engagement range and the target's hardness, and assumes military grade armour resistances.
+Defence
+Shield Metrics
+The shield metrics provides information about your shield defence.
-Effective DPS and effective SDPS are the equivalent of DPS and SDPS for the weapon. Effectiveness is a percentage value that shows how effective the DPS of the weapon is compared in reality against the given target compared to the weapon's stated DPS. Effectiveness can never go above 100%.
+Raw shield strength is the sum of the shield from your generator, boosters and shield cell banks. A tooltip provides a breakdown of these values.
-Total effective DPS, SDPS and effectiveness against both shields and hull are provided at the bottom of the table.
+The time the shields will hold for is the time it will take your opponent' to remove your shields. This assumes that they have 100% time on target and that the engagement range stays constant. It also assumes that you fire all of your shield cell banks prior to your shields being lost.
-At the bottom of this panel you can change your engagement range. The engagement range is the distance between your ship and your target. Many weapons suffer from what is known as damage falloff, where their effectiveness decreases the further the distance between your ship and your target. This allows you to model the effect of engaging at different ranges.
+The time the shields will recover in is the time it will take your shields to go from collapsed (0%) to recovered (50%). This is affected by the number of pips you have in your SYS capacitor.
-Note that this panel only shows enabled weapons, so if you want to see your overall effectiveness for a subset of your weapons you can disable the undesired weapons in the power management panel.
+The time the shields will recharge in is the time it will take your shields to go from recovered (50%) to full (100%). This is affected by the number of pips you have in your SYS capacitor.
-At the bottom of this panel are two graphs showing how your sustained DPS changes with engagement range. This shows at a glance how effective each weapon is at different distances.
+Shield Sources
+This chart provides information about the sources of your shields. For each applicable source of shields (generator, boosters, shield cell banks) a value is provided.
-Damage Received
-The damage received panel provides information about the effectiveness of your build's defences against opponent's weapons at different engagement range. Features and functions are the same as the damage dealt panel, except that it does take in to account your build's resistances.
+Damage Taken
+This graph shows how the initial damage from the weapons of each type are reduced before their damage is applied to the shields. For each type of damage (absolute, explosive, kinetic, thermal) a percentage of the initial damage is provided. A tooltip provides a breakdown of these values.
+
+Effective Shield
+This graph shows the effective shield for each damage type, found by dividing the raw shield value by the damage taken for that type.
+
+Amour Metrics
+The armour metrics provides information about your armour defence.
+
+Raw armour strength is the sum of the armour from your bulkheads and hull reinforcement packages. A tooltip provides a breakdown of these values.
+
+The time the armour will hold for is the time it will take your opponent' to take your armour to 0. This assumes that they have 100% time on target, the engagement range stays constant, and that all damage is dealt to the armour rather than modules.
+
+Raw module armour is the sum of the protection from your module reinforcement packages.
+
+Protection for hardpoints is the amount of protection that your module reinforcement packages provide to hardpoints. This percentage of damage to the hardpoints will be diverted to the module reinforcement packages.
+
+Protection for all other modules is the amount of protection that your module reinforcement packages provide to everything other than hardpoints. This percentage of damage to the modules will be diverted to the module reinforcement packages.
+
+Armour Sources
+This chart provides information about the sources of your armour. For each applicable source of shields (bulkheads, hull reinforcement packages) a value is provided.
+
+Damage Taken
+This graph shows how the initial damage from the weapons of each type are reduced before their damage is applied to the armour. For each type of damage (absolute, explosive, kinetic, thermal) a percentage of the initial damage is provided. A tooltip provides a breakdown of these values.
+
+Effective Armour
+This graph shows the effective armour for each damage type, found by dividing the raw armour value by the damage taken for that type.
Keyboard Shortcuts
From 8b0e822ea96e8f7b8d2a11832050463cdbf2425c Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Thu, 23 Mar 2017 10:14:43 +0000
Subject: [PATCH 65/87] Fixes
---
src/app/components/ModificationsMenu.jsx | 8 +++++---
src/app/pages/OutfittingPage.jsx | 4 ++--
src/app/shipyard/Ship.js | 12 ++++++------
src/app/utils/BlueprintFunctions.js | 16 ++++++++++------
4 files changed, 23 insertions(+), 17 deletions(-)
diff --git a/src/app/components/ModificationsMenu.jsx b/src/app/components/ModificationsMenu.jsx
index ee950579..6486d951 100644
--- a/src/app/components/ModificationsMenu.jsx
+++ b/src/app/components/ModificationsMenu.jsx
@@ -133,7 +133,7 @@ export default class ModificationsMenu extends TranslatedComponent {
* @param {int} special The name of the selected special
*/
_specialSelected(special) {
- const { m } = this.props;
+ const { m, ship } = this.props;
if (m.blueprint) {
if (special === null) {
@@ -141,6 +141,9 @@ export default class ModificationsMenu extends TranslatedComponent {
} else {
m.blueprint.special = Modifications.specials[special];
}
+ ship.recalculateDps();
+ ship.recalculateHps();
+ ship.recalculateEps();
}
const specialMenuOpened = false;
@@ -300,8 +303,7 @@ export default class ModificationsMenu extends TranslatedComponent {
onContextMenu={stopCtxPropagation}
>
{ haveBlueprint ?
- {blueprintLabel}
- :
+ {blueprintLabel}
:
{translate('PHRASE_SELECT_BLUEPRINT')}
}
{ showBlueprintsMenu ? this.state.blueprints : null }
{ showSpecial ? {specialLabel}
: null }
diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx
index 81372ec8..18dae989 100644
--- a/src/app/pages/OutfittingPage.jsx
+++ b/src/app/pages/OutfittingPage.jsx
@@ -204,8 +204,8 @@ export default class OutfittingPage extends Page {
opponentSys = parseFloat(opponentControl[0]);
opponentEng = parseFloat(opponentControl[1]);
opponentWep = parseFloat(opponentControl[2]);
- }
- }
+ }
+ }
} else {
// Ship is a stock build
opponent.buildWith(Ships[shipId].defaults);
diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js
index d207279e..cfa9a1f4 100755
--- a/src/app/shipyard/Ship.js
+++ b/src/app/shipyard/Ship.js
@@ -613,9 +613,9 @@ export default class Ship {
module.blueprint = getBlueprint(blueprints[i + 1].fdname, module);
module.blueprint.grade = blueprints[i + 1].grade;
module.blueprint.special = blueprints[i + 1].special;
- } else {
+ } else {
module.blueprint = {};
- }
+ }
}
this.use(standard[i], module, true);
}
@@ -641,9 +641,9 @@ export default class Ship {
module.blueprint = getBlueprint(blueprints[cl + i].fdname, module);
module.blueprint.grade = blueprints[cl + i].grade;
module.blueprint.special = blueprints[cl + i].special;
- } else {
+ } else {
module.blueprint = {};
- }
+ }
}
this.use(hps[i], module, true);
}
@@ -667,9 +667,9 @@ export default class Ship {
module.blueprint = getBlueprint(blueprints[cl + i].fdname, module);
module.blueprint.grade = blueprints[cl + i].grade;
module.blueprint.special = blueprints[cl + i].special;
- } else {
+ } else {
module.blueprint = {};
- }
+ }
}
this.use(internal[i], module, true);
}
diff --git a/src/app/utils/BlueprintFunctions.js b/src/app/utils/BlueprintFunctions.js
index 7dcddf04..2a55a56d 100644
--- a/src/app/utils/BlueprintFunctions.js
+++ b/src/app/utils/BlueprintFunctions.js
@@ -3,12 +3,12 @@ import { Modifications } from 'coriolis-data/dist';
/**
* Generate a tooltip with details of a blueprint's effects
+ * @param {Object} translate The translate object
* @param {Object} features The features of the blueprint
* @param {Object} m The module to compare with
* @returns {Object} The react components
*/
-export function blueprintTooltip(translate, features, m)
-{
+export function blueprintTooltip(translate, features, m) {
const results = [];
for (const feature in features) {
const featureIsBeneficial = isBeneficial(feature, features[feature]);
@@ -75,7 +75,9 @@ export function blueprintTooltip(translate, features, m)
/**
* Is this blueprint feature beneficial?
- *
+ * @param {string} feature The name of the feature
+ * @param {array} values The value of the feature
+ * @returns {boolean} True if this feature is beneficial
*/
export function isBeneficial(feature, values) {
const fact = (values[0] < 0 || (values[0] === 0 && values[1] < 0));
@@ -88,7 +90,9 @@ export function isBeneficial(feature, values) {
/**
* Is this feature value beneficial?
- *
+ * @param {string} feature The name of the feature
+ * @param {number} value The value of the feature
+ * @returns {boolean} True if this value is beneficial
*/
export function isValueBeneficial(feature, value) {
if (Modifications.modifications[feature].higherbetter) {
@@ -134,8 +138,8 @@ export function getBlueprint(name, module) {
for (const grade in blueprint.grades) {
for (const feature in blueprint.grades[grade].features) {
if (feature === 'shieldboost') {
- blueprint.grades[grade].features[feature][0] = ((1 + blueprint.grades[grade].features[feature][0]) * (1 + module.shieldboost) - 1)/ module.shieldboost - 1;
- blueprint.grades[grade].features[feature][1] = ((1 + blueprint.grades[grade].features[feature][1]) * (1 + module.shieldboost) - 1)/ module.shieldboost - 1;
+ blueprint.grades[grade].features[feature][0] = ((1 + blueprint.grades[grade].features[feature][0]) * (1 + module.shieldboost) - 1) / module.shieldboost - 1;
+ blueprint.grades[grade].features[feature][1] = ((1 + blueprint.grades[grade].features[feature][1]) * (1 + module.shieldboost) - 1) / module.shieldboost - 1;
}
}
}
From b0dc4d78640ef90ff9968351da1e380df2b0b508 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Fri, 24 Mar 2017 12:04:41 +0000
Subject: [PATCH 66/87] Shirnk large labels on vertical bar chart
---
src/app/components/VerticalBarChart.jsx | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/app/components/VerticalBarChart.jsx b/src/app/components/VerticalBarChart.jsx
index b8a709cf..1f2ecb94 100644
--- a/src/app/components/VerticalBarChart.jsx
+++ b/src/app/components/VerticalBarChart.jsx
@@ -98,8 +98,10 @@ const ValueLabel = React.createClass({
render() {
const { x, y, payload, value } = this.props;
+ const em = value < 1000 ? '1em' : value < 1000 ? '0.8em' : '0.7em';
+
return (
- {value}
+ {value}
);
}
});
From 33a678a2009a5021f88dad8af9181587b6a625f0 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Fri, 24 Mar 2017 22:30:55 +0000
Subject: [PATCH 67/87] Add SDPS totals
---
src/app/components/Offence.jsx | 4 +++-
src/app/i18n/en.js | 3 +++
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/src/app/components/Offence.jsx b/src/app/components/Offence.jsx
index 4f502c0f..f67f721f 100644
--- a/src/app/components/Offence.jsx
+++ b/src/app/components/Offence.jsx
@@ -248,11 +248,13 @@ export default class Offence extends TranslatedComponent {
{translate('offence metrics')}
{translate('PHRASE_TIME_TO_DRAIN_WEP')} {timeToDrain === Infinity ? translate('never') : formats.time(timeToDrain)}
+ {translate('PHRASE_EFFECTIVE_SDPS_SHIELDS')} {formats.f1(totalShieldsSDps)}
{translate('PHRASE_TIME_TO_REMOVE_SHIELDS')} {timeToDepleteShields === Infinity ? translate('never') : formats.time(timeToDepleteShields)}
+ {translate('PHRASE_EFFECTIVE_SDPS_ARMOUR')} {formats.f1(totalArmourSDps)}
{translate('PHRASE_TIME_TO_REMOVE_ARMOUR')} {timeToDepleteArmour === Infinity ? translate('never') : formats.time(timeToDepleteArmour)}
-
{translate('shield damage sources')}
+
{translate('shield damage sources')}
diff --git a/src/app/i18n/en.js b/src/app/i18n/en.js
index 5c68a1f8..b0a25be5 100644
--- a/src/app/i18n/en.js
+++ b/src/app/i18n/en.js
@@ -70,6 +70,9 @@ export const terms = {
TT_EFFECTIVE_SDPS_ARMOUR: 'Actual sustained DPS whilst WEP capacitor is not empty',
TT_EFFECTIVENESS_ARMOUR: 'Effectivness compared to hitting a 0-resistance target at 0m',
+ PHRASE_EFFECTIVE_SDPS_SHIELDS: 'SDPS against shields',
+ PHRASE_EFFECTIVE_SDPS_ARMOUR: 'SDPS against armour',
+
TT_SUMMARY_SPEED: 'With full fuel tank and 4 pips to ENG',
TT_SUMMARY_SPEED_NONFUNCTIONAL: 'Thrusters powered off or over maximum mass',
TT_SUMMARY_BOOST: 'With full fuel tank and 4 pips to ENG',
From f536c037b182f4f40030e1f152e1f49e4e917f9d Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Sat, 25 Mar 2017 16:24:13 +0000
Subject: [PATCH 68/87] Updates
---
ChangeLog.md | 2 ++
src/app/components/CostSection.jsx | 2 +-
src/app/components/InternalSlot.jsx | 10 ++++++----
src/app/shipyard/Module.js | 8 --------
src/app/shipyard/Ship.js | 4 +++-
src/app/utils/CompanionApiUtils.js | 6 ++++++
6 files changed, 18 insertions(+), 14 deletions(-)
diff --git a/ChangeLog.md b/ChangeLog.md
index cd9a709b..ede897fe 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -13,6 +13,8 @@
* Add tooltip for blueprints providing details of the features they alter
* Use opponent's saved pips if available
* Ignore rounds per shot for EPS and HPS calculations; it's already factored in to the numbers
+ * Ensure that clip size modification imports result in whole numbers
+ * Rework of separate offence/defence/movement sections to a unified interface
#2.2.19
* Power management panel now displays modules in descending order of power usage by default
diff --git a/src/app/components/CostSection.jsx b/src/app/components/CostSection.jsx
index f0dc5bfd..302dd5c8 100644
--- a/src/app/components/CostSection.jsx
+++ b/src/app/components/CostSection.jsx
@@ -507,7 +507,7 @@ export default class CostSection extends TranslatedComponent {
scoop = true;
break;
case 'scb':
- q = slotGroup[i].m.getCells();
+ q = slotGroup[i].m.getAmmo() + 1;
break;
case 'am':
q = slotGroup[i].m.getAmmo();
diff --git a/src/app/components/InternalSlot.jsx b/src/app/components/InternalSlot.jsx
index 562f7555..7c8c4063 100644
--- a/src/app/components/InternalSlot.jsx
+++ b/src/app/components/InternalSlot.jsx
@@ -52,14 +52,16 @@ export default class InternalSlot extends Slot {
{ m.bays ? {translate('bays')}: {m.bays}
: null }
{ m.rebuildsperbay ? {translate('rebuildsperbay')}: {m.rebuildsperbay}
: null }
{ m.rate ? {translate('rate')}: {m.rate}{u.kgs} {translate('refuel time')}: {formats.time(this.props.fuel * 1000 / m.rate)}
: null }
- { m.getAmmo() ? {translate('ammunition')}: {formats.gen(m.getAmmo())}
: null }
- { m.cells ? {translate('cells')}: {m.cells}
: null }
- { m.getShieldReinforcement() ? {translate('shieldreinforcement')}: {formats.int(m.getShieldReinforcement())} MJ {translate('total')}: {formats.int(m.cells * m.getShieldReinforcement())}{u.MJ}
: null }
+ { m.getAmmo() && m.grp !== 'scb' ? {translate('ammunition')}: {formats.gen(m.getAmmo())}
: null }
+ { m.getSpinup() ? {translate('spinup')}: {formats.f1(m.getSpinup())}{u.s}
: null }
+ { m.getDuration() ? {translate('duration')}: {formats.f1(m.getDuration())}{u.s}
: null }
+ { m.grp === 'scb' ? {translate('cells')}: {formats.int(m.getAmmo() + 1)}
: null }
+ { m.getShieldReinforcement() ? {translate('shieldreinforcement')}: {formats.f1(m.getDuration() * m.getShieldReinforcement())}{u.MJ}
: null }
+ { m.getShieldReinforcement() ? {translate('total')}: {formats.int((m.getAmmo() + 1) * (m.getDuration() * m.getShieldReinforcement()))}{u.MJ}
: null }
{ m.repair ? {translate('repair')}: {m.repair}
: null }
{ m.getFacingLimit() ? {translate('facinglimit')} {formats.f1(m.getFacingLimit())}°
: null }
{ m.getRange() ? {translate('range')} {formats.f2(m.getRange())}{u.km}
: null }
{ m.getRangeT() ? {translate('ranget')} {formats.f1(m.getRangeT())}{u.s}
: null }
- { m.getSpinup() ? {translate('spinup')}: {formats.f1(m.getSpinup())}{u.s}
: null }
{ m.getTime() ? {translate('time')}: {formats.time(m.getTime())}
: null }
{ m.maximum ? {translate('max')}: {(m.maximum)}
: null }
{ m.rangeLS ? {translate('range')}: {m.rangeLS}{u.Ls}
: null }
diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js
index 2ea29321..710c1528 100755
--- a/src/app/shipyard/Module.js
+++ b/src/app/shipyard/Module.js
@@ -680,14 +680,6 @@ export default class Module {
return this._getModifiedValue('rebuildsperbay');
}
- /**
- * Get the cells for this module, taking in to account modifications
- * @return {Number} the cells for this module
- */
- getCells() {
- return this._getModifiedValue('cells');
- }
-
/**
* Get the jitter for this module, taking in to account modifications
* @return {Number} the jitter for this module
diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js
index cfa9a1f4..40a80237 100755
--- a/src/app/shipyard/Ship.js
+++ b/src/app/shipyard/Ship.js
@@ -1296,7 +1296,9 @@ export default class Ship {
for (let slot of this.internal) {
if (slot.m && slot.m.grp == 'scb') {
- shieldCells += slot.m.getShieldReinforcement() * slot.m.getCells();
+ // There is currently a bug with Elite where you can have a clip > 1 thanks to engineering but it doesn't do anything,
+ // so we need to hard-code clip to 1
+ shieldCells += slot.m.getShieldReinforcement() * slot.m.getDuration() * (slot.m.getAmmo() + 1);
}
}
diff --git a/src/app/utils/CompanionApiUtils.js b/src/app/utils/CompanionApiUtils.js
index 47a1db01..a3fda30c 100644
--- a/src/app/utils/CompanionApiUtils.js
+++ b/src/app/utils/CompanionApiUtils.js
@@ -427,4 +427,10 @@ function _addModifications(module, modifiers, blueprint, grade) {
if (module.getModValue('rof')) {
module.setModValue('rof', ((1 / (1 + module.getModValue('rof') / 10000)) - 1) * 10000);
}
+
+ // Clip size is rounded up so that the result is a whole number
+ if (module.getModValue('clip')) {
+ const individual = 1 / (module.clip || 1);
+ module.setModValue('clip', Math.ceil((module.getModValue('clip') / 10000) / individual) * individual * 10000);
+ }
}
From 442da6f05b1cf6b8178ace117cd6c5229e26aad3 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Sat, 25 Mar 2017 18:19:31 +0000
Subject: [PATCH 69/87] Use cargo hatch info on import if available
---
ChangeLog.md | 1 +
src/app/utils/CompanionApiUtils.js | 12 +++++++++---
2 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/ChangeLog.md b/ChangeLog.md
index ede897fe..e33785d2 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -15,6 +15,7 @@
* Ignore rounds per shot for EPS and HPS calculations; it's already factored in to the numbers
* Ensure that clip size modification imports result in whole numbers
* Rework of separate offence/defence/movement sections to a unified interface
+ * Use cargo hatch information on import if available
#2.2.19
* Power management panel now displays modules in descending order of power usage by default
diff --git a/src/app/utils/CompanionApiUtils.js b/src/app/utils/CompanionApiUtils.js
index a3fda30c..5f6df7c5 100644
--- a/src/app/utils/CompanionApiUtils.js
+++ b/src/app/utils/CompanionApiUtils.js
@@ -130,9 +130,15 @@ export function shipFromJson(json) {
let ship = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots);
ship.buildWith(null);
- // Set the cargo hatch. We don't have any information on it so guess it's priority 5 and disabled
- ship.cargoHatch.enabled = false;
- ship.cargoHatch.priority = 4;
+ // Set the cargo hatch
+ if (json.modules.CargoHatch) {
+ ship.cargoHatch.enabled = json.modules.CargoHatch.module.on == true;
+ ship.cargoHatch.priority = json.modules.CargoHatch.module.priority;
+ } else {
+ // We don't have any information on it so guess it's priority 5 and disabled
+ ship.cargoHatch.enabled = false;
+ ship.cargoHatch.priority = 4;
+ }
// Add the bulkheads
const armourJson = json.modules.Armour.module;
From 8a115f83237dcad23062593264f859c9716b1d95 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Sun, 26 Mar 2017 20:48:21 +0100
Subject: [PATCH 70/87] Add component list to blueprint display tooltip
---
ChangeLog.md | 8 ++++-
src/app/components/HardpointSlot.jsx | 2 +-
src/app/components/InternalSlot.jsx | 2 +-
src/app/components/ModificationsMenu.jsx | 4 +--
src/app/components/StandardSlot.jsx | 2 +-
src/app/utils/BlueprintFunctions.js | 45 ++++++++++++++++++------
6 files changed, 46 insertions(+), 17 deletions(-)
diff --git a/ChangeLog.md b/ChangeLog.md
index e33785d2..4841ab12 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -3,7 +3,6 @@
* Make scan time visible on scanners where available
* Update power distributor able-to-boost calculation to take fractional MJ values in to account
* Revert to floating header due to issues on iOS
- * Add effective total shield value to defence summary
* Fix issue where new module added to a slot did not reset its enabled status
* Show integrity value for relevant modules
* Reset old modification values when a new roll is applied
@@ -16,6 +15,13 @@
* Ensure that clip size modification imports result in whole numbers
* Rework of separate offence/defence/movement sections to a unified interface
* Use cargo hatch information on import if available
+ * Additional information of power distributor pips, boost, cargo and fuel loads added to build
+ * Additional information of opponent and engagement range added to build
+ * Reworking of offence, defence and movement information in to separate tabs as part of the outfitting screen:
+ * Power and costs section provides the existing 'Power' and 'Costs' sections
+ * Profiles section provides a number of graphs that show how various components of the build (top speed, sustained DPS against opponent's shields and armour etc) are affected by mass, range, etc.
+ * Offence section provides details of your build's damage distribution and per-weapon effectiveness. It also gives summary information for how long it will take for your build to wear down your opponent's shields and armour
+ * Defence section provides details of your build's defences against your selected opponent. It provides details of the effectiveness of your resistances of both shields and armour, and effective strength of each as a result. It also provides key metrics around shield longevity and recovery times, as well as module protection
#2.2.19
* Power management panel now displays modules in descending order of power usage by default
diff --git a/src/app/components/HardpointSlot.jsx b/src/app/components/HardpointSlot.jsx
index bca5ecbc..ca76bd80 100644
--- a/src/app/components/HardpointSlot.jsx
+++ b/src/app/components/HardpointSlot.jsx
@@ -55,7 +55,7 @@ export default class HardpointSlot extends Slot {
modTT = (
{modTT}
- {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade].features, m)}
+ {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], m)}
);
}
diff --git a/src/app/components/InternalSlot.jsx b/src/app/components/InternalSlot.jsx
index 7c8c4063..fad3e2cf 100644
--- a/src/app/components/InternalSlot.jsx
+++ b/src/app/components/InternalSlot.jsx
@@ -34,7 +34,7 @@ export default class InternalSlot extends Slot {
modTT = (
{modTT}
- {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade].features, m)}
+ {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], m)}
);
}
diff --git a/src/app/components/ModificationsMenu.jsx b/src/app/components/ModificationsMenu.jsx
index 6486d951..108bf578 100644
--- a/src/app/components/ModificationsMenu.jsx
+++ b/src/app/components/ModificationsMenu.jsx
@@ -54,7 +54,7 @@ export default class ModificationsMenu extends TranslatedComponent {
const close = this._blueprintSelected.bind(this, blueprintName, grade);
const key = blueprintName + ':' + grade;
const blueprint = getBlueprint(blueprintName, m);
- const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade].features);
+ const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade]);
blueprints.push({translate(blueprint.name + ' grade ' + grade)}
);
}
}
@@ -279,7 +279,7 @@ export default class ModificationsMenu extends TranslatedComponent {
if (m.blueprint && !isEmpty(m.blueprint)) {
blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
haveBlueprint = true;
- blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade].features);
+ blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade]);
}
let specialLabel;
diff --git a/src/app/components/StandardSlot.jsx b/src/app/components/StandardSlot.jsx
index 30d5502f..effc1480 100644
--- a/src/app/components/StandardSlot.jsx
+++ b/src/app/components/StandardSlot.jsx
@@ -57,7 +57,7 @@ export default class StandardSlot extends TranslatedComponent {
modTT = (
{modTT}
- {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade].features, m)}
+ {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], m)}
);
}
diff --git a/src/app/utils/BlueprintFunctions.js b/src/app/utils/BlueprintFunctions.js
index 2a55a56d..f5f42dc6 100644
--- a/src/app/utils/BlueprintFunctions.js
+++ b/src/app/utils/BlueprintFunctions.js
@@ -4,14 +4,14 @@ import { Modifications } from 'coriolis-data/dist';
/**
* Generate a tooltip with details of a blueprint's effects
* @param {Object} translate The translate object
- * @param {Object} features The features of the blueprint
+ * @param {Object} blueprint The blueprint at the required grade
* @param {Object} m The module to compare with
* @returns {Object} The react components
*/
-export function blueprintTooltip(translate, features, m) {
- const results = [];
- for (const feature in features) {
- const featureIsBeneficial = isBeneficial(feature, features[feature]);
+export function blueprintTooltip(translate, blueprint, m) {
+ const effects = [];
+ for (const feature in blueprint.features) {
+ const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]);
const featureDef = Modifications.modifications[feature];
if (!featureDef.hidden) {
let symbol = '';
@@ -20,8 +20,8 @@ export function blueprintTooltip(translate, features, m) {
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
- let lowerBound = features[feature][0];
- let upperBound = features[feature][1];
+ let lowerBound = blueprint.features[feature][0];
+ let upperBound = blueprint.features[feature][1];
if (featureDef.type === 'percentage') {
lowerBound = Math.round(lowerBound * 1000) / 10;
upperBound = Math.round(upperBound * 1000) / 10;
@@ -35,7 +35,7 @@ export function blueprintTooltip(translate, features, m) {
current = Math.round(current / 10) / 10;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
- results.push(
+ effects.push(
{translate(feature)}
{lowerBound}{symbol}
@@ -45,7 +45,7 @@ export function blueprintTooltip(translate, features, m) {
);
} else {
// We do not have a module, no value
- results.push(
+ effects.push(
{translate(feature)}
{lowerBound}{symbol}
@@ -56,8 +56,19 @@ export function blueprintTooltip(translate, features, m) {
}
}
+ const components = [];
+ for (const component in blueprint.components) {
+ components.push(
+
+ {translate(component)}
+ {blueprint.components[component]}
+
+ );
+ }
+
return (
-
+
+
{translate('feature')}
@@ -67,9 +78,21 @@ export function blueprintTooltip(translate, features, m) {
- {results}
+ {effects}
+ { m ? null :
+
+
+ {translate('component')}
+ {translate('amount')}
+
+
+
+ {components}
+
+
}
+
);
}
From b66baef998c003a1931beebf981fd92910b8bc3c Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Mon, 27 Mar 2017 10:17:30 +0100
Subject: [PATCH 71/87] Add engineer info to blueprint tooltip
---
ChangeLog.md | 2 +-
src/app/components/HardpointSlot.jsx | 2 +-
src/app/components/InternalSlot.jsx | 2 +-
src/app/components/ModificationsMenu.jsx | 6 +-
src/app/components/StandardSlot.jsx | 2 +-
src/app/utils/BlueprintFunctions.js | 124 ++++++++++++++++-------
src/app/utils/SlotFunctions.js | 2 +-
7 files changed, 98 insertions(+), 42 deletions(-)
diff --git a/ChangeLog.md b/ChangeLog.md
index 4841ab12..0b03097a 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -9,7 +9,7 @@
* Fix issue with miner role where refinery would not be present in ships with class 5 slots but no class 4
* Ensure that boost value is set correctly when modifications to power distributor enable/disable boost
* Ensure that hull reinforcement modifications take the inherent resistance in to account when calculating modification percentages
- * Add tooltip for blueprints providing details of the features they alter
+ * Add tooltip for blueprints providing details of the features they alter, the components required for the blueprint and the engineer(s) who cam craft them
* Use opponent's saved pips if available
* Ignore rounds per shot for EPS and HPS calculations; it's already factored in to the numbers
* Ensure that clip size modification imports result in whole numbers
diff --git a/src/app/components/HardpointSlot.jsx b/src/app/components/HardpointSlot.jsx
index ca76bd80..2dd96bb3 100644
--- a/src/app/components/HardpointSlot.jsx
+++ b/src/app/components/HardpointSlot.jsx
@@ -55,7 +55,7 @@ export default class HardpointSlot extends Slot {
modTT = (
{modTT}
- {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], m)}
+ {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m)}
);
}
diff --git a/src/app/components/InternalSlot.jsx b/src/app/components/InternalSlot.jsx
index fad3e2cf..12cfe0aa 100644
--- a/src/app/components/InternalSlot.jsx
+++ b/src/app/components/InternalSlot.jsx
@@ -34,7 +34,7 @@ export default class InternalSlot extends Slot {
modTT = (
{modTT}
- {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], m)}
+ {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m)}
);
}
diff --git a/src/app/components/ModificationsMenu.jsx b/src/app/components/ModificationsMenu.jsx
index 108bf578..3a8a19ed 100644
--- a/src/app/components/ModificationsMenu.jsx
+++ b/src/app/components/ModificationsMenu.jsx
@@ -50,11 +50,11 @@ export default class ModificationsMenu extends TranslatedComponent {
// Set up the blueprints
let blueprints = [];
for (const blueprintName in Modifications.modules[m.grp].blueprints) {
- for (const grade of Modifications.modules[m.grp].blueprints[blueprintName]) {
+ for (const grade in Modifications.modules[m.grp].blueprints[blueprintName].grades) {
const close = this._blueprintSelected.bind(this, blueprintName, grade);
const key = blueprintName + ':' + grade;
const blueprint = getBlueprint(blueprintName, m);
- const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade]);
+ const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers);
blueprints.push({translate(blueprint.name + ' grade ' + grade)}
);
}
}
@@ -279,7 +279,7 @@ export default class ModificationsMenu extends TranslatedComponent {
if (m.blueprint && !isEmpty(m.blueprint)) {
blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
haveBlueprint = true;
- blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade]);
+ blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade].engineers);
}
let specialLabel;
diff --git a/src/app/components/StandardSlot.jsx b/src/app/components/StandardSlot.jsx
index effc1480..eabaa353 100644
--- a/src/app/components/StandardSlot.jsx
+++ b/src/app/components/StandardSlot.jsx
@@ -57,7 +57,7 @@ export default class StandardSlot extends TranslatedComponent {
modTT = (
{modTT}
- {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], m)}
+ {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m)}
);
}
diff --git a/src/app/utils/BlueprintFunctions.js b/src/app/utils/BlueprintFunctions.js
index f5f42dc6..ef2dde0b 100644
--- a/src/app/utils/BlueprintFunctions.js
+++ b/src/app/utils/BlueprintFunctions.js
@@ -5,10 +5,11 @@ import { Modifications } from 'coriolis-data/dist';
* Generate a tooltip with details of a blueprint's effects
* @param {Object} translate The translate object
* @param {Object} blueprint The blueprint at the required grade
+ * @param {Array} engineers The engineers supplying this blueprint
* @param {Object} m The module to compare with
* @returns {Object} The react components
*/
-export function blueprintTooltip(translate, blueprint, m) {
+export function blueprintTooltip(translate, blueprint, engineers, m) {
const effects = [];
for (const feature in blueprint.features) {
const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]);
@@ -33,7 +34,9 @@ export function blueprintTooltip(translate, blueprint, m) {
let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
- }
+ } else if (featureDef.type === 'numeric') {
+ current /= 100;
+ }
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
@@ -55,43 +58,96 @@ export function blueprintTooltip(translate, blueprint, m) {
}
}
}
+ if (m) {
+ // Because we have a module add in any benefits that aren't part of the primary blueprint
+ for (const feature in m.mods) {
+ if (!blueprint.features[feature]) {
+ const featureDef = Modifications.modifications[feature];
+ let symbol = '';
+ if (feature === 'jitter') {
+ symbol = '°';
+ } else if (featureDef.type === 'percentage') {
+ symbol = '%';
+ }
+ let current = m.getModValue(feature);
+ if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
+ current = Math.round(current / 10) / 10;
+ } else if (featureDef.type === 'numeric') {
+ current /= 100;
+ }
+ const currentIsBeneficial = isValueBeneficial(feature, current);
+ effects.push(
+
+ {translate(feature)}
+
+ {current}{symbol}
+
+
+ );
+ }
+ }
+ }
+ let components;
+ if (!m) {
+ components = [];
+ for (const component in blueprint.components) {
+ components.push(
+
+ {translate(component)}
+ {blueprint.components[component]}
+
+ );
+ }
+ }
- const components = [];
- for (const component in blueprint.components) {
- components.push(
-
- {translate(component)}
- {blueprint.components[component]}
-
- );
+ let engineersList;
+ if (engineers) {
+ engineersList = [];
+ for (const engineer of engineers) {
+ engineersList.push(
+
+ {engineer}
+
+ );
+ }
}
return (
-
-
-
- {translate('feature')}
- {translate('worst')}
- {m ? {translate('current')} : null }
- {translate('best')}
-
-
-
- {effects}
-
-
- { m ? null :
-
-
- {translate('component')}
- {translate('amount')}
-
-
-
- {components}
-
-
}
+
+
+
+ {translate('feature')}
+ {translate('worst')}
+ {m ? {translate('current')} : null }
+ {translate('best')}
+
+
+
+ {effects}
+
+
+ { components ?
+
+
+ {translate('component')}
+ {translate('amount')}
+
+
+
+ {components}
+
+
: null }
+ { engineersList ?
+
+
+ {translate('engineers')}
+
+
+
+ {engineersList}
+
+
: null }
);
}
diff --git a/src/app/utils/SlotFunctions.js b/src/app/utils/SlotFunctions.js
index cca7eb1e..8a57e63e 100644
--- a/src/app/utils/SlotFunctions.js
+++ b/src/app/utils/SlotFunctions.js
@@ -243,7 +243,7 @@ export function diffDetails(language, m, mm) {
}
}
- let mIntegrity = m.integrity;
+ let mIntegrity = m.integrity || 0;
let mmIntegrity = mm ? mm.getIntegrity() || 0 : 0;
if (mIntegrity != mmIntegrity) {
propDiffs.push({translate('integrity')}: {diff(formats.round, mIntegrity, mmIntegrity)}
);
From d8ac185b4d9802ab9935c92114a89fde14d98bde Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Mon, 27 Mar 2017 11:04:17 +0100
Subject: [PATCH 72/87] Add optimal multiplier to thrusters
---
src/app/components/StandardSlot.jsx | 1 +
src/app/utils/BlueprintFunctions.js | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/app/components/StandardSlot.jsx b/src/app/components/StandardSlot.jsx
index eabaa353..76915c98 100644
--- a/src/app/components/StandardSlot.jsx
+++ b/src/app/components/StandardSlot.jsx
@@ -100,6 +100,7 @@ export default class StandardSlot extends TranslatedComponent {
{ m.getMinMass() ? {translate('minimum mass')}: {formats.int(m.getMinMass())}{units.T}
: null }
{ m.getOptMass() ? {translate('optimal mass')}: {formats.int(m.getOptMass())}{units.T}
: null }
{ m.getMaxMass() ? {translate('max mass')}: {formats.int(m.getMaxMass())}{units.T}
: null }
+ { m.getOptMul() ? {translate('optimal multiplier')}: {formats.rPct(m.getOptMul())}
: null }
{ m.getRange() ? {translate('range', m.grp)}: {formats.f2(m.getRange())}{units.km}
: null }
{ m.time ? {translate('time')}: {formats.time(m.time)}
: null }
{ m.getThermalEfficiency() ? {translate('efficiency')}: {formats.f2(m.getThermalEfficiency())}
: null }
diff --git a/src/app/utils/BlueprintFunctions.js b/src/app/utils/BlueprintFunctions.js
index ef2dde0b..4175aee6 100644
--- a/src/app/utils/BlueprintFunctions.js
+++ b/src/app/utils/BlueprintFunctions.js
@@ -36,7 +36,7 @@ export function blueprintTooltip(translate, blueprint, engineers, m) {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
- }
+ }
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
@@ -74,7 +74,7 @@ export function blueprintTooltip(translate, blueprint, engineers, m) {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
- }
+ }
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
From d6246632783dc15307a8a0895a718273d7dbe7d7 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Tue, 28 Mar 2017 14:06:25 +0100
Subject: [PATCH 73/87] Remove unused components
---
src/app/components/Boost.jsx | 1 -
src/app/components/DamageDealt.jsx | 589 ------------------
src/app/components/DamageReceived.jsx | 327 ----------
src/app/components/DefenceSummary.jsx | 122 ----
src/app/components/EngineProfile.jsx | 1 -
src/app/components/FSDProfile.jsx | 1 -
...otSection.jsx => HardpointSlotSection.jsx} | 2 +-
src/app/components/JumpRange.jsx | 1 -
src/app/components/MovementSummary.jsx | 90 ---
src/app/components/OffenceSummary.jsx | 73 ---
src/app/components/Pips.jsx | 1 -
src/app/components/ShipSelector.jsx | 89 ---
src/app/components/WeaponDamageChart.jsx | 1 -
src/app/pages/OutfittingPage.jsx | 4 +-
src/app/shipyard/Ship.js | 33 -
15 files changed, 3 insertions(+), 1332 deletions(-)
delete mode 100644 src/app/components/DamageDealt.jsx
delete mode 100644 src/app/components/DamageReceived.jsx
delete mode 100644 src/app/components/DefenceSummary.jsx
rename src/app/components/{HardpointsSlotSection.jsx => HardpointSlotSection.jsx} (98%)
delete mode 100644 src/app/components/MovementSummary.jsx
delete mode 100644 src/app/components/OffenceSummary.jsx
delete mode 100644 src/app/components/ShipSelector.jsx
diff --git a/src/app/components/Boost.jsx b/src/app/components/Boost.jsx
index 10e60927..b6803138 100644
--- a/src/app/components/Boost.jsx
+++ b/src/app/components/Boost.jsx
@@ -1,7 +1,6 @@
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
-import ShipSelector from './ShipSelector';
import { nameComparator } from '../utils/SlotFunctions';
import { Pip } from './SvgIcons';
import LineChart from '../components/LineChart';
diff --git a/src/app/components/DamageDealt.jsx b/src/app/components/DamageDealt.jsx
deleted file mode 100644
index d1d0fbe6..00000000
--- a/src/app/components/DamageDealt.jsx
+++ /dev/null
@@ -1,589 +0,0 @@
-import React from 'react';
-import TranslatedComponent from './TranslatedComponent';
-import { Ships } from 'coriolis-data/dist';
-import ShipSelector from './ShipSelector';
-import { nameComparator } from '../utils/SlotFunctions';
-import { CollapseSection, ExpandSection, MountFixed, MountGimballed, MountTurret } from './SvgIcons';
-import LineChart from '../components/LineChart';
-import Slider from '../components/Slider';
-import * as ModuleUtils from '../shipyard/ModuleUtils';
-import Module from '../shipyard/Module';
-
-const DAMAGE_DEALT_COLORS = ['#FFFFFF', '#FF0000', '#00FF00', '#7777FF', '#FFFF00', '#FF00FF', '#00FFFF', '#777777'];
-
-/**
- * Generates an internationalization friendly weapon comparator that will
- * sort by specified property (if provided) then by name/group, class, rating
- * @param {function} translate Translation function
- * @param {function} propComparator Optional property comparator
- * @param {boolean} desc Use descending order
- * @return {function} Comparator function for names
- */
-export function weaponComparator(translate, propComparator, desc) {
- return (a, b) => {
- if (!desc) { // Flip A and B if ascending order
- let t = a;
- a = b;
- b = t;
- }
-
- // If a property comparator is provided use it first
- let diff = propComparator ? propComparator(a, b) : nameComparator(translate, a, b);
-
- if (diff) {
- return diff;
- }
-
- // Property matches so sort by name / group, then class, rating
- if (a.name === b.name && a.grp === b.grp) {
- if(a.class == b.class) {
- return a.rating > b.rating ? 1 : -1;
- }
- return a.class - b.class;
- }
-
- return nameComparator(translate, a, b);
- };
-}
-
-/**
- * Damage against a selected ship
- */
-export default class DamageDealt extends TranslatedComponent {
- static propTypes = {
- ship: React.PropTypes.object.isRequired,
- code: React.PropTypes.string.isRequired
- };
-
- static DEFAULT_AGAINST = Ships['anaconda'];
-
- /**
- * Constructor
- * @param {Object} props React Component properties
- * @param {Object} context React Component context
- */
- constructor(props, context) {
- super(props);
-
- this._sort = this._sort.bind(this);
- this._onShipChange = this._onShipChange.bind(this);
- this._onCollapseExpand = this._onCollapseExpand.bind(this);
-
- const ship = this.props.ship;
- const against = DamageDealt.DEFAULT_AGAINST;
- const maxRange = this._calcMaxRange(ship);
- const range = 1000 / maxRange;
- const maxDps = this._calcMaxSDps(ship, against);
- const weaponNames = this._weaponNames(ship, context);
-
- this.state = {
- predicate: 'n',
- desc: true,
- against,
- expanded: false,
- range,
- maxRange,
- maxDps,
- weaponNames,
- calcHullDpsFunc: this._calcDps.bind(this, context, ship, weaponNames, against, true),
- calcShieldsDpsFunc: this._calcDps.bind(this, context, ship, weaponNames, against, false)
- };
- }
-
- /**
- * Set the initial weapons state
- */
- componentWillMount() {
- const data = this._calcWeaponsDps(this.props.ship, this.state.against, this.state.range * this.state.maxRange, true);
- this.setState({ weapons: data.weapons, totals: data.totals });
- }
-
- /**
- * Set the updated weapons state if our ship changes
- * @param {Object} nextProps Incoming/Next properties
- * @param {Object} nextContext Incoming/Next conext
- * @return {boolean} Returns true if the component should be rerendered
- */
- componentWillReceiveProps(nextProps, nextContext) {
- if (nextProps.code != this.props.code) {
- const data = this._calcWeaponsDps(nextProps.ship, this.state.against, this.state.range * this.state.maxRange, this.props.hull);
- const weaponNames = this._weaponNames(nextProps.ship, nextContext);
- const maxRange = this._calcMaxRange(nextProps.ship);
- const maxDps = this._calcMaxSDps(nextProps.ship, this.state.against);
- this.setState({ weapons: data.weapons,
- totals: data.totals,
- weaponNames,
- maxRange,
- maxDps,
- calcHullDpsFunc: this._calcDps.bind(this, nextContext, nextProps.ship, weaponNames, this.state.against, true),
- calcShieldsDpsFunc: this._calcDps.bind(this, nextContext, nextProps.ship, weaponNames, this.state.against, false) });
- }
- return true;
- }
-
- /**
- * Calculate the maximum sustained single-weapon DPS for this ship against another ship
- * @param {Object} ship The ship
- * @param {Object} against The target
- * @return {number} The maximum sustained single-weapon DPS
- */
- _calcMaxSDps(ship, against) {
- let maxSDps = 0;
- for (let i = 0; i < ship.hardpoints.length; i++) {
- if (ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
- const m = ship.hardpoints[i].m;
- const thisSDps = m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getDps();
- if (thisSDps > maxSDps) {
- maxSDps = thisSDps;
- }
- }
- }
- return maxSDps;
- }
-
- /**
- * Calculate the per-weapon DPS for this ship against another ship at a given range
- * @param {Object} context The context
- * @param {Object} ship The ship
- * @param {Object} weaponNames The names of the weapons for which to calculate DPS
- * @param {Object} against The target
- * @param {bool} hull true if to calculate against hull, false if to calculate against shields
- * @param {Object} range The engagement range
- * @return {array} The array of weapon DPS
- */
- _calcDps(context, ship, weaponNames, against, hull, range) {
- let results = {};
- let weaponNum = 0;
- for (let i = 0; i < ship.hardpoints.length; i++) {
- if (ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
- const m = ship.hardpoints[i].m;
- results[weaponNames[weaponNum++]] = this._calcWeaponDps(context, m, against, hull, range);
- }
- }
- return results;
- }
-
- /**
- * Calculate the maximum range of a ship's weapons
- * @param {Object} ship The ship
- * @returns {int} The maximum range, in metres
- */
- _calcMaxRange(ship) {
- let maxRange = 1000;
- for (let i = 0; i < ship.hardpoints.length; i++) {
- if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
- const thisRange = ship.hardpoints[i].m.getRange();
- if (thisRange > maxRange) {
- maxRange = thisRange;
- }
- }
- }
-
- return maxRange;
- }
-
- /**
- * Obtain the weapon names for this ship
- * @param {Object} ship The ship
- * @param {Object} context The context
- * @return {array} The weapon names
- */
- _weaponNames(ship, context) {
- const translate = context.language.translate;
- let names = [];
- let num = 1;
- for (let i = 0; i < ship.hardpoints.length; i++) {
- if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
- const m = ship.hardpoints[i].m;
- let name = '' + num++ + ': ' + m.class + m.rating + (m.missile ? '/' + m.missile : '') + ' ' + translate(m.name || m.grp);
- let engineering;
- if (m.blueprint && m.blueprint.name) {
- engineering = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
- if (m.blueprint.special && m.blueprint.special.id) {
- engineering += ', ' + translate(m.blueprint.special.name);
- }
- }
- if (engineering) {
- name = name + ' (' + engineering + ')';
- }
- names.push(name);
- }
- }
- return names;
- }
-
-
- /**
- * Calculate a specific weapon DPS for this ship against another ship at a given range
- * @param {Object} context The context
- * @param {Object} m The weapon
- * @param {Object} against The target
- * @param {bool} hull true if to calculate against hull, false if to calculate against shields
- * @param {Object} range The engagement range
- * @return {number} The weapon DPS
- */
- _calcWeaponDps(context, m, against, hull, range) {
- const translate = context.language.translate;
- let dropoff = 1;
- if (m.getFalloff()) {
- // Calculate the dropoff % due to range
- if (range > m.getRange()) {
- // Weapon is out of range
- dropoff = 0;
- } else {
- const falloff = m.getFalloff();
- if (range > falloff) {
- const dropoffRange = m.getRange() - falloff;
- // Assuming straight-line falloff
- dropoff = 1 - (range - falloff) / dropoffRange;
- }
- }
- }
- const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
- let engineering;
- if (m.blueprint && m.blueprint.name) {
- engineering = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
- if (m.blueprint.special && m.blueprint.special.id) {
- engineering += ', ' + translate(m.blueprint.special.name);
- }
- }
- const effectivenessShields = dropoff;
- const effectiveDpsShields = m.getDps() * effectivenessShields;
- const effectiveSDpsShields = (m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) * effectivenessShields : effectiveDpsShields);
- const effectivenessHull = (m.getPiercing() >= against.properties.hardness ? 1 : m.getPiercing() / against.properties.hardness) * dropoff;
- const effectiveDpsHull = m.getDps() * effectivenessHull;
- const effectiveSDpsHull = (m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) * effectivenessHull : effectiveDpsHull);
-
- return hull ? effectiveSDpsHull : effectiveSDpsShields;
- }
-
- /**
- * Calculate the damage dealt by a ship
- * @param {Object} ship The ship which will deal the damage
- * @param {Object} against The ship against which damage will be dealt
- * @param {Object} range The engagement range
- * @return {object} Returns the per-weapon damage
- */
- _calcWeaponsDps(ship, against, range) {
- const translate = this.context.language.translate;
-
- // Tidy up the range so that it's to 4 decimal places
- range = Math.round(10000 * range) / 10000;
-
- // Track totals
- let totals = {};
- totals.effectivenessShields = 0;
- totals.effectiveDpsShields = 0;
- totals.effectiveSDpsShields = 0;
- totals.effectivenessHull = 0;
- totals.effectiveDpsHull = 0;
- totals.effectiveSDpsHull = 0;
- let totalDps = 0;
-
- let weapons = [];
- for (let i = 0; i < ship.hardpoints.length; i++) {
- if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
- const m = ship.hardpoints[i].m;
- if (m.getDamage() && m.grp !== 'po') {
- let dropoff = 1;
- if (m.getFalloff()) {
- // Calculate the dropoff % due to range
- if (range > m.getRange()) {
- // Weapon is out of range
- dropoff = 0;
- } else {
- const falloff = m.getFalloff();
- if (range > falloff) {
- const dropoffRange = m.getRange() - falloff;
- // Assuming straight-line falloff
- dropoff = 1 - (range - falloff) / dropoffRange;
- }
- }
- }
- const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
- let engineering;
- if (m.blueprint && m.blueprint.name) {
- engineering = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
- if (m.blueprint.special && m.blueprint.special.id >= 0) {
- engineering += ', ' + translate(m.blueprint.special.name);
- }
- }
-
- // Alter effectiveness as per standard shields (all have the same resistances)
- const sg = ModuleUtils.findModule('sg', '3v');
- let effectivenessShields = 0;
- if (m.getDamageDist().E) {
- effectivenessShields += m.getDamageDist().E * (1 - sg.getExplosiveResistance());
- }
- if (m.getDamageDist().K) {
- effectivenessShields += m.getDamageDist().K * (1 - sg.getKineticResistance());
- }
- if (m.getDamageDist().T) {
- effectivenessShields += m.getDamageDist().T * (1 - sg.getThermalResistance());
- }
- if (m.getDamageDist().A) {
- effectivenessShields += m.getDamageDist().A;
- }
- effectivenessShields *= dropoff;
- const effectiveDpsShields = m.getDps() * effectivenessShields;
- const effectiveSDpsShields = (m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) * effectivenessShields : effectiveDpsShields);
-
- // Alter effectiveness as per standard hull
- const bulkheads = new Module({ template: against.bulkheads });
- let effectivenessHull = 0;
- if (m.getDamageDist().E) {
- effectivenessHull += m.getDamageDist().E * (1 - bulkheads.getExplosiveResistance());
- }
- if (m.getDamageDist().K) {
- effectivenessHull += m.getDamageDist().K * (1 - bulkheads.getKineticResistance());
- }
- if (m.getDamageDist().T) {
- effectivenessHull += m.getDamageDist().T * (1 - bulkheads.getThermalResistance());
- }
- if (m.getDamageDist().A) {
- effectivenessHull += m.getDamageDist().A;
- }
- effectivenessHull *= Math.min(m.getPiercing() / against.properties.hardness, 1) * dropoff;
- const effectiveDpsHull = m.getDps() * effectivenessHull;
- const effectiveSDpsHull = (m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) * effectivenessHull : effectiveDpsHull);
- totals.effectiveDpsShields += effectiveDpsShields;
- totals.effectiveSDpsShields += effectiveSDpsShields;
- totals.effectiveDpsHull += effectiveDpsHull;
- totals.effectiveSDpsHull += effectiveSDpsHull;
- totalDps += m.getDps();
-
- weapons.push({ id: i,
- mount: m.mount,
- name: m.name || m.grp,
- classRating,
- engineering,
- effectiveDpsShields,
- effectiveSDpsShields,
- effectivenessShields,
- effectiveDpsHull,
- effectiveSDpsHull,
- effectivenessHull });
- }
- }
- }
- totals.effectivenessShields = totalDps == 0 ? 0 : totals.effectiveDpsShields / totalDps;
- totals.effectivenessHull = totalDps == 0 ? 0 : totals.effectiveDpsHull / totalDps;
-
- return { weapons, totals };
- }
-
- /**
- * Triggered when the collapse or expand icons are clicked
- */
- _onCollapseExpand() {
- this.setState({ expanded: !this.state.expanded });
- }
-
- /**
- * Triggered when the ship we compare against changes
- * @param {string} s the new ship ID
- */
- _onShipChange(s) {
- const against = Ships[s];
- const data = this._calcWeaponsDps(this.props.ship, against, this.state.range * this.state.maxRange);
- this.setState({ against,
- weapons: data.weapons,
- totals: data.totals,
- calcHullDpsFunc: this._calcDps.bind(this, this.context, this.props.ship, this.state.weaponNames, against, true),
- calcShieldsDpsFunc: this._calcDps.bind(this, this.context, this.props.ship, this.state.weaponNames, against, false) });
- }
-
- /**
- * Set the sort order and sort
- * @param {string} predicate Sort predicate
- */
- _sortOrder(predicate) {
- let desc = this.state.desc;
-
- if (predicate == this.state.predicate) {
- desc = !desc;
- } else {
- desc = true;
- }
-
- this._sort(this.props.ship, predicate, desc);
- this.setState({ predicate, desc });
- }
-
- /**
- * Sorts the weapon list
- * @param {Ship} ship Ship instance
- * @param {string} predicate Sort predicate
- * @param {Boolean} desc Sort order descending
- */
- _sort(ship, predicate, desc) {
- let comp = weaponComparator.bind(null, this.context.language.translate);
-
- switch (predicate) {
- case 'n': comp = comp(null, desc); break;
- case 'edpss': comp = comp((a, b) => a.effectiveDpsShields - b.effectiveDpsShields, desc); break;
- case 'esdpss': comp = comp((a, b) => a.effectiveSDpsShields - b.effectiveSDpsShields, desc); break;
- case 'es': comp = comp((a, b) => a.effectivenessShields - b.effectivenessShields, desc); break;
- case 'edpsh': comp = comp((a, b) => a.effectiveDpsHull - b.effectiveDpsHull, desc); break;
- case 'esdpsh': comp = comp((a, b) => a.effectiveSDpsHull - b.effectiveSDpsHull, desc); break;
- case 'eh': comp = comp((a, b) => a.effectivenessHull - b.effectivenessHull, desc); break;
- }
-
- this.state.weapons.sort(comp);
- }
-
- /**
- * Render individual rows for hardpoints
- * @param {Function} translate Translate function
- * @param {Object} formats Localised formats map
- * @return {array} The individual rows
- *
- */
- _renderRows(translate, formats) {
- const { termtip, tooltip } = this.context;
-
- let rows = [];
-
- if (this.state.weapons) {
- for (let i = 0; i < this.state.weapons.length; i++) {
- const weapon = this.state.weapons[i];
-
- rows.push(
-
- {weapon.mount == 'F' ? : null}
- {weapon.mount == 'G' ? : null}
- {weapon.mount == 'T' ? : null}
- {weapon.classRating} {translate(weapon.name)}
- {weapon.engineering ? ' (' + weapon.engineering + ')' : null }
-
- {formats.f1(weapon.effectiveDpsShields)}
- {formats.f1(weapon.effectiveSDpsShields)}
- {formats.pct(weapon.effectivenessShields)}
- {formats.f1(weapon.effectiveDpsHull)}
- {formats.f1(weapon.effectiveSDpsHull)}
- {formats.pct(weapon.effectivenessHull)}
- );
- }
- }
-
- return rows;
- }
-
- /**
- * Update current range
- * @param {number} range Range 0-1
- */
- _rangeChange(range) {
- const data = this._calcWeaponsDps(this.props.ship, this.state.against, this.state.range * this.state.maxRange);
- this.setState({ range,
- weapons: data.weapons,
- totals: data.totals });
- }
-
- /**
- * Render damage dealt
- * @return {React.Component} contents
- */
- render() {
- const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
- const { formats, translate, units } = language;
- const { against, expanded, maxRange, range, totals } = this.state;
- const { ship } = this.props;
-
- const sortOrder = this._sortOrder;
- const onCollapseExpand = this._onCollapseExpand;
-
- const code = ship.getHardpointsString() + '.' + ship.getModificationsString() + '.' + ship.getPowerEnabledString() + '.' + against.properties.name;
-
- return (
-
- {translate('damage dealt to')} {expanded ? : }
- {expanded ?
-
-
-
-
- {translate('weapon')}
- {translate('standard shields')}
- {translate('standard armour')}
-
-
- {translate('effective dps')}
- {translate('effective sdps')}
- {translate('effectiveness')}
- {translate('effective dps')}
- {translate('effective sdps')}
- {translate('effectiveness')}
-
-
-
- {this._renderRows(translate, formats)}
-
-
-
- {translate('total')}
- {formats.f1(totals.effectiveDpsShields)}
- {formats.f1(totals.effectiveSDpsShields)}
- {formats.pct(totals.effectivenessShields)}
- {formats.f1(totals.effectiveDpsHull)}
- {formats.f1(totals.effectiveSDpsHull)}
- {formats.pct(totals.effectivenessHull)}
-
-
-
-
-
-
- {translate('engagement range')}
-
-
-
-
- {formats.f2(range * maxRange / 1000)}{units.km}
-
-
-
-
-
-
{translate('sustained dps against standard shields')}
-
-
-
-
{translate('sustained dps against standard armour')}
-
-
- : null }
-
- );
- }
-}
diff --git a/src/app/components/DamageReceived.jsx b/src/app/components/DamageReceived.jsx
deleted file mode 100644
index ec1799ed..00000000
--- a/src/app/components/DamageReceived.jsx
+++ /dev/null
@@ -1,327 +0,0 @@
-import React from 'react';
-import TranslatedComponent from './TranslatedComponent';
-import { Modules } from 'coriolis-data/dist';
-import { nameComparator } from '../utils/SlotFunctions';
-import { CollapseSection, ExpandSection, MountFixed, MountGimballed, MountTurret } from './SvgIcons';
-import Module from '../shipyard/Module';
-import Slider from '../components/Slider';
-
-/**
- * Generates an internationalization friendly weapon comparator that will
- * sort by specified property (if provided) then by name/group, class, rating
- * @param {function} translate Translation function
- * @param {function} propComparator Optional property comparator
- * @param {boolean} desc Use descending order
- * @return {function} Comparator function for names
- */
-export function weaponComparator(translate, propComparator, desc) {
- return (a, b) => {
- if (!desc) { // Flip A and B if ascending order
- let t = a;
- a = b;
- b = t;
- }
-
- // If a property comparator is provided use it first
- let diff = propComparator ? propComparator(a, b) : nameComparator(translate, a, b);
-
- if (diff) {
- return diff;
- }
-
- // Property matches so sort by name / group, then class, rating
- if (a.name === b.name && a.grp === b.grp) {
- if(a.class == b.class) {
- return a.rating > b.rating ? 1 : -1;
- }
- return a.class - b.class;
- }
-
- return nameComparator(translate, a, b);
- };
-}
-
-/**
- * Damage received by a selected ship
- */
-export default class DamageReceived extends TranslatedComponent {
- static propTypes = {
- ship: React.PropTypes.object.isRequired,
- code: React.PropTypes.string.isRequired
- };
-
- /**
- * Constructor
- * @param {Object} props React Component properties
- */
- constructor(props) {
- super(props);
-
- this._sort = this._sort.bind(this);
- this._onCollapseExpand = this._onCollapseExpand.bind(this);
-
- this.state = {
- predicate: 'n',
- desc: true,
- expanded: false,
- range: 0.1667,
- maxRange: 6000
- };
- }
-
- /**
- * Set the initial weapons state
- */
- componentWillMount() {
- this.setState({ weapons: this._calcWeapons(this.props.ship, this.state.range * this.state.maxRange) });
- }
-
- /**
- * Set the updated weapons state
- * @param {Object} nextProps Incoming/Next properties
- * @param {Object} nextContext Incoming/Next conext
- * @return {boolean} Returns true if the component should be rerendered
- */
- componentWillReceiveProps(nextProps, nextContext) {
- if (nextProps.code != this.props.code) {
- this.setState({ weapons: this._calcWeapons(nextProps.ship, this.state.range * this.state.maxRange) });
- }
- return true;
- }
-
- /**
- * Calculate the damage received by a ship
- * @param {Object} ship The ship which will receive the damage
- * @param {Object} range The engagement range
- * @return {boolean} Returns the per-weapon damage
- */
- _calcWeapons(ship, range) {
- // Tidy up the range so that it's to 4 decimal places
- range = Math.round(10000 * range) / 10000;
-
- let weapons = [];
- for (let grp in Modules.hardpoints) {
- if (Modules.hardpoints[grp][0].damage && Modules.hardpoints[grp][0].damagedist) {
- for (let mId in Modules.hardpoints[grp]) {
- const m = new Module(Modules.hardpoints[grp][mId]);
- let dropoff = 1;
- if (m.getFalloff()) {
- // Calculate the dropoff % due to range
- if (range > m.getRange()) {
- // Weapon is out of range
- dropoff = 0;
- } else {
- const falloff = m.getFalloff();
- if (range > falloff) {
- const dropoffRange = m.getRange() - falloff;
- // Assuming straight-line falloff
- dropoff = 1 - (range - falloff) / dropoffRange;
- }
- }
- }
- const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
-
- // Base DPS
- const baseDps = m.getDps() * dropoff;
- const baseSDps = m.getClip() ? ((m.getClip() * baseDps / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) * dropoff : baseDps;
-
- // Effective DPS taking in to account shield resistance
- let effectivenessShields = 0;
- if (m.getDamageDist().E) {
- effectivenessShields += m.getDamageDist().E * (1 - ship.shieldExplRes);
- }
- if (m.getDamageDist().K) {
- effectivenessShields += m.getDamageDist().K * (1 - ship.shieldKinRes);
- }
- if (m.getDamageDist().T) {
- effectivenessShields += m.getDamageDist().T * (1 - ship.shieldThermRes);
- }
- if (m.getDamageDist().A) {
- effectivenessShields += m.getDamageDist().A;
- }
- effectivenessShields *= dropoff;
- const effectiveDpsShields = baseDps * effectivenessShields;
- const effectiveSDpsShields = baseSDps * effectivenessShields;
-
- // Effective DPS taking in to account hull hardness and resistance
- let effectivenessHull = 0;
- if (m.getDamageDist().E) {
- effectivenessHull += m.getDamageDist().E * (1 - ship.hullExplRes);
- }
- if (m.getDamageDist().K) {
- effectivenessHull += m.getDamageDist().K * (1 - ship.hullKinRes);
- }
- if (m.getDamageDist().T) {
- effectivenessHull += m.getDamageDist().T * (1 - ship.hullThermRes);
- }
- if (m.getDamageDist().A) {
- effectivenessHull += m.getDamageDist().A;
- }
- effectivenessHull *= Math.min(m.getPiercing() / ship.hardness, 1) * dropoff;
- const effectiveDpsHull = baseDps * effectivenessHull;
- const effectiveSDpsHull = baseSDps * effectivenessHull;
-
- weapons.push({ id: m.id,
- classRating,
- name: m.name || m.grp,
- mount: m.mount,
- effectiveDpsShields,
- effectiveSDpsShields,
- effectivenessShields,
- effectiveDpsHull,
- effectiveSDpsHull,
- effectivenessHull });
- }
- }
- }
-
- return weapons;
- }
-
- /**
- * Triggered when the collapse or expand icons are clicked
- */
- _onCollapseExpand() {
- this.setState({ expanded: !this.state.expanded });
- }
-
- /**
- * Set the sort order and sort
- * @param {string} predicate Sort predicate
- */
- _sortOrder(predicate) {
- let desc = this.state.desc;
-
- if (predicate == this.state.predicate) {
- desc = !desc;
- } else {
- desc = true;
- }
-
- this._sort(this.props.ship, predicate, desc);
- this.setState({ predicate, desc });
- }
-
- /**
- * Sorts the weapon list
- * @param {Ship} ship Ship instance
- * @param {string} predicate Sort predicate
- * @param {Boolean} desc Sort order descending
- */
- _sort(ship, predicate, desc) {
- let comp = weaponComparator.bind(null, this.context.language.translate);
-
- switch (predicate) {
- case 'n': comp = comp(null, desc); break;
- case 'edpss': comp = comp((a, b) => a.effectiveDpsShields - b.effectiveDpsShields, desc); break;
- case 'esdpss': comp = comp((a, b) => a.effectiveSDpsShields - b.effectiveSDpsShields, desc); break;
- case 'es': comp = comp((a, b) => a.effectivenessShields - b.effectivenessShields, desc); break;
- case 'edpsh': comp = comp((a, b) => a.effectiveDpsHull - b.effectiveDpsHull, desc); break;
- case 'esdpsh': comp = comp((a, b) => a.effectiveSDpsHull - b.effectiveSDpsHull, desc); break;
- case 'eh': comp = comp((a, b) => a.effectivenessHull - b.effectivenessHull, desc); break;
- }
-
- this.state.weapons.sort(comp);
- }
-
- /**
- * Render individual rows for weapons
- * @param {Function} translate Translate function
- * @param {Object} formats Localised formats map
- * @return {array} The individual rows
- *
- */
- _renderRows(translate, formats) {
- const { termtip, tooltip } = this.context;
-
- let rows = [];
-
- for (let i = 0; i < this.state.weapons.length; i++) {
- const weapon = this.state.weapons[i];
- rows.push(
-
- {weapon.mount == 'F' ? : null}
- {weapon.mount == 'G' ? : null}
- {weapon.mount == 'T' ? : null}
- {weapon.classRating} {translate(weapon.name)}
-
- {formats.round1(weapon.effectiveDpsShields)}
- {formats.round1(weapon.effectiveSDpsShields)}
- {formats.pct(weapon.effectivenessShields)}
- {formats.round1(weapon.effectiveDpsHull)}
- {formats.round1(weapon.effectiveSDpsHull)}
- {formats.pct(weapon.effectivenessHull)}
- );
- }
- return rows;
- }
-
- /**
- * Update current range
- * @param {number} range Range 0-1
- */
- _rangeChange(range) {
- this.setState({ range, weapons: this._calcWeapons(this.props.ship, this.state.range * this.state.maxRange) });
- }
-
- /**
- * Render damage received
- * @return {React.Component} contents
- */
- render() {
- const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
- const { formats, translate, units } = language;
- const { expanded, maxRange, range } = this.state;
-
- const sortOrder = this._sortOrder;
- const onCollapseExpand = this._onCollapseExpand;
-
- return (
-
- {translate('damage received from')} {expanded ? : }
- {expanded ?
-
-
-
- {translate('weapon')}
- {translate('against shields')}
- {translate('against hull')}
-
-
- {translate('DPS')}
- {translate('SDPS')}
- {translate('effectiveness')}
- {translate('DPS')}
- {translate('SDPS')}
- {translate('effectiveness')}
-
-
-
- {this._renderRows(translate, formats)}
-
-
-
-
-
- {translate('engagement range')}
-
-
-
-
- {formats.f2(range * maxRange / 1000)}{units.km}
-
-
-
-
: null }
-
- );
- }
-}
diff --git a/src/app/components/DefenceSummary.jsx b/src/app/components/DefenceSummary.jsx
deleted file mode 100644
index 93b230b0..00000000
--- a/src/app/components/DefenceSummary.jsx
+++ /dev/null
@@ -1,122 +0,0 @@
-import React from 'react';
-import cn from 'classnames';
-import TranslatedComponent from './TranslatedComponent';
-import { DamageKinetic, DamageThermal, DamageExplosive } from './SvgIcons';
-
-/**
- * Defence summary
- */
-export default class DefenceSummary extends TranslatedComponent {
- static propTypes = {
- ship: React.PropTypes.object.isRequired
- };
-
- /**
- * Constructor
- * @param {Object} props React Component properties
- */
- constructor(props) {
- super(props);
- }
-
- /**
- * Render defence summary
- * @return {React.Component} contents
- */
- render() {
- let ship = this.props.ship;
- let { language, tooltip, termtip } = this.context;
- let { formats, translate, units } = language;
- let hide = tooltip.bind(null, null);
-
- const shieldGenerator = ship.findShieldGenerator();
-
- // Damage values are 1 - resistance values
- return (
-
- {translate('defence summary')}
-
-
- {ship.shield ?
-
- {translate('shields')}: {formats.int(ship.shield)} {units.MJ}
- : null }
- {ship.shield ?
-
- {translate('recovery')}
- {formats.time(ship.calcShieldRecovery())}
- {translate('recharge')}
- {formats.time(ship.calcShieldRecharge())}
- : null }
- {ship.shield ?
-
- {translate('damage from')}
-
-
- {formats.pct1(1 - ship.shieldExplRes)}
-
-
-
- {formats.pct1(1 - ship.shieldKinRes)}
-
-
-
- {formats.pct1(1 - ship.shieldThermRes)}
-
- : null }
- {ship.shield ?
-
- {translate('total effective shield')}
-
-
- {formats.int((ship.shield + ship.shieldCells) / (1 - ship.shieldExplRes))}{units.MJ}
-
-
-
- {formats.int((ship.shield + ship.shieldCells) / (1 - ship.shieldKinRes))}{units.MJ}
-
-
-
- {formats.int((ship.shield + ship.shieldCells) / (1 - ship.shieldThermRes))}{units.MJ}
-
- : null }
-
- { ship.shield && ship.shieldCells ?
-
- {translate('shield cells')}: {formats.int(ship.shieldCells)} {units.MJ}
- : null }
-
-
- {translate('armour')}: {formats.int(ship.armour)}
-
-
- {translate('damage from')}
-
-
- {formats.pct1(1 - ship.hullExplRes)}
-
-
- {formats.pct1(1 - ship.hullKinRes)}
-
-
-
- {formats.pct1(1 - ship.hullThermRes)}
-
-
-
- {ship.modulearmour > 0 ?
-
- {translate('module armour')}: {formats.int(ship.modulearmour)}
- : null }
-
- {ship.moduleprotection > 0 ?
-
- {translate('internal protection')} {formats.pct1(ship.moduleprotection)}
- {translate('external protection')} {formats.pct1(ship.moduleprotection / 2)}
- : null }
-
-
-
- );
- }
-}
diff --git a/src/app/components/EngineProfile.jsx b/src/app/components/EngineProfile.jsx
index 8c9ddf0f..8b90165f 100644
--- a/src/app/components/EngineProfile.jsx
+++ b/src/app/components/EngineProfile.jsx
@@ -1,7 +1,6 @@
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
-import ShipSelector from './ShipSelector';
import { nameComparator } from '../utils/SlotFunctions';
import LineChart from '../components/LineChart';
import Slider from '../components/Slider';
diff --git a/src/app/components/FSDProfile.jsx b/src/app/components/FSDProfile.jsx
index 81548632..c3e7bb83 100644
--- a/src/app/components/FSDProfile.jsx
+++ b/src/app/components/FSDProfile.jsx
@@ -1,7 +1,6 @@
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
-import ShipSelector from './ShipSelector';
import { nameComparator } from '../utils/SlotFunctions';
import LineChart from '../components/LineChart';
import Slider from '../components/Slider';
diff --git a/src/app/components/HardpointsSlotSection.jsx b/src/app/components/HardpointSlotSection.jsx
similarity index 98%
rename from src/app/components/HardpointsSlotSection.jsx
rename to src/app/components/HardpointSlotSection.jsx
index 51bb79d4..5d697476 100644
--- a/src/app/components/HardpointsSlotSection.jsx
+++ b/src/app/components/HardpointSlotSection.jsx
@@ -8,7 +8,7 @@ import { stopCtxPropagation } from '../utils/UtilityFunctions';
/**
* Hardpoint slot section
*/
-export default class HardpointsSlotSection extends SlotSection {
+export default class HardpointSlotSection extends SlotSection {
/**
* Constructor
diff --git a/src/app/components/JumpRange.jsx b/src/app/components/JumpRange.jsx
index 7fc28c36..d1421ec1 100644
--- a/src/app/components/JumpRange.jsx
+++ b/src/app/components/JumpRange.jsx
@@ -1,7 +1,6 @@
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
-import ShipSelector from './ShipSelector';
import { nameComparator } from '../utils/SlotFunctions';
import LineChart from '../components/LineChart';
import Slider from '../components/Slider';
diff --git a/src/app/components/MovementSummary.jsx b/src/app/components/MovementSummary.jsx
deleted file mode 100644
index f4c268f1..00000000
--- a/src/app/components/MovementSummary.jsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import React from 'react';
-import cn from 'classnames';
-import TranslatedComponent from './TranslatedComponent';
-
-/**
- * Movement summary
- */
-export default class MovementSummary extends TranslatedComponent {
- static propTypes = {
- ship: React.PropTypes.object.isRequired
- };
-
- /**
- * Constructor
- * @param {Object} props React Component properties
- */
- constructor(props) {
- super(props);
- }
-
- /**
- * Render movement summary
- * @return {React.Component} contents
- */
- render() {
- let ship = this.props.ship;
- let { language, tooltip, termtip } = this.context;
- let { formats, translate, units } = language;
- let boostMultiplier = ship.topBoost / ship.topSpeed;
-
- return (
-
- {translate('movement summary')}
-
-
-
-
- {translate('engine pips')}
-
-
-
- 0
- 1
- 2
- 3
- 4
- 4B
-
-
- {translate('speed')} ({units['m/s']})
- {formats.int(ship.speeds[0])}
- {formats.int(ship.speeds[1])}
- {formats.int(ship.speeds[2])}
- {formats.int(ship.speeds[3])}
- {formats.int(ship.speeds[4])}
- {formats.int(ship.speeds[4] * boostMultiplier)}
-
-
- {translate('pitch')} ({units['°/s']})
- {formats.int(ship.pitches[0])}
- {formats.int(ship.pitches[1])}
- {formats.int(ship.pitches[2])}
- {formats.int(ship.pitches[3])}
- {formats.int(ship.pitches[4])}
- {formats.int(ship.pitches[4] * boostMultiplier)}
-
-
- {translate('roll')} ({units['°/s']})
- {formats.int(ship.rolls[0])}
- {formats.int(ship.rolls[1])}
- {formats.int(ship.rolls[2])}
- {formats.int(ship.rolls[3])}
- {formats.int(ship.rolls[4])}
- {formats.int(ship.rolls[4] * boostMultiplier)}
-
-
- {translate('yaw')} ({units['°/s']})
- {formats.int(ship.yaws[0])}
- {formats.int(ship.yaws[1])}
- {formats.int(ship.yaws[2])}
- {formats.int(ship.yaws[3])}
- {formats.int(ship.yaws[4])}
- {formats.int(ship.yaws[4] * boostMultiplier)}
-
-
-
-
- );
- }
-}
diff --git a/src/app/components/OffenceSummary.jsx b/src/app/components/OffenceSummary.jsx
deleted file mode 100644
index d7778a70..00000000
--- a/src/app/components/OffenceSummary.jsx
+++ /dev/null
@@ -1,73 +0,0 @@
-import React from 'react';
-import cn from 'classnames';
-import TranslatedComponent from './TranslatedComponent';
-import { DamageAbsolute, DamageKinetic, DamageThermal, DamageExplosive } from './SvgIcons';
-
-/**
- * Offence summary
- */
-export default class OffenceSummary extends TranslatedComponent {
- static propTypes = {
- ship: React.PropTypes.object.isRequired
- };
-
- /**
- * Constructor
- * @param {Object} props React Component properties
- */
- constructor(props) {
- super(props);
- }
-
- /**
- * Render offence summary
- * @return {React.Component} contents
- */
- render() {
- let ship = this.props.ship;
- let { language, tooltip, termtip } = this.context;
- let { formats, translate } = language;
-
- return (
-
- {translate('offence summary')}
-
-
-
- {translate('dps')}: {formats.f1(ship.totalDps)}
-
-
- {translate('damage by')}
- {formats.f1(ship.totalAbsDps)}
- {formats.f1(ship.totalExplDps)}
- {formats.f1(ship.totalKinDps)}
- {formats.f1(ship.totalThermDps)}
-
-
-
- {translate('sdps')}: {formats.f1(ship.totalSDps)}
-
-
- {translate('damage by')}
- {formats.f1(ship.totalAbsSDps)}
- {formats.f1(ship.totalExplSDps)}
- {formats.f1(ship.totalKinSDps)}
- {formats.f1(ship.totalThermSDps)}
-
-
-
- {translate('dpe')}: {formats.f1(ship.totalDpe)}
-
-
- {translate('damage by')}
- {formats.f1(ship.totalAbsDpe)}
- {formats.f1(ship.totalExplDpe)}
- {formats.f1(ship.totalKinDpe)}
- {formats.f1(ship.totalThermDpe)}
-
-
-
-
- );
- }
-}
diff --git a/src/app/components/Pips.jsx b/src/app/components/Pips.jsx
index 29051318..08102c40 100644
--- a/src/app/components/Pips.jsx
+++ b/src/app/components/Pips.jsx
@@ -1,7 +1,6 @@
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
-import ShipSelector from './ShipSelector';
import { nameComparator } from '../utils/SlotFunctions';
import { Pip } from './SvgIcons';
import LineChart from '../components/LineChart';
diff --git a/src/app/components/ShipSelector.jsx b/src/app/components/ShipSelector.jsx
deleted file mode 100644
index a48ba4a8..00000000
--- a/src/app/components/ShipSelector.jsx
+++ /dev/null
@@ -1,89 +0,0 @@
-import React from 'react';
-import cn from 'classnames';
-import { Ships } from 'coriolis-data/dist';
-import TranslatedComponent from './TranslatedComponent';
-import { Rocket } from './SvgIcons';
-
-/**
- * Selector for ships
- */
-export default class ShipSelector extends TranslatedComponent {
- static propTypes = {
- initial: React.PropTypes.object.isRequired,
- onChange: React.PropTypes.func.isRequired
- };
-
- /**
- * Constructor
- * @param {Object} props React Component properties
- */
- constructor(props) {
- super(props);
-
- this.state = { ship : this.props.initial };
- }
-
- /**
- * Generate the ships menu
- * @return {React.Component} Menu
- */
- _getShipsMenu() {
- const _selectShip = this._selectShip;
-
- let shipList = [];
-
- for (let s in Ships) {
- shipList.push({Ships[s].properties.name}
);
- }
-
- return shipList;
- }
-
- /**
- * Handle opening the menu
- * @param {string} menu The ID of the opened menu
- * @param {SyntheticEvent} event Event
- */
- _openMenu(menu, event) {
- event.stopPropagation();
- if (this.props.currentMenu == menu) {
- menu = null;
- }
-
- this.context.openMenu(menu);
- }
-
- /**
- * Handle selection of a ship
- * @param {string} s The selected ship ID
- */
- _selectShip(s) {
- this.setState({ ship: Ships[s] });
-
- this.context.openMenu(null);
- this.props.onChange(s);
- }
-
- /**
- * Render ship selector
- * @return {React.Component} contents
- */
- render() {
- const currentMenu = this.props.currentMenu;
- const ship = this.state.ship;
-
- return (
-
-
-
-
{ship.properties.name}
- {currentMenu == 'wds' ?
-
e.stopPropagation() }>
- {this._getShipsMenu()}
-
: null }
-
-
-
- );
- }
-}
diff --git a/src/app/components/WeaponDamageChart.jsx b/src/app/components/WeaponDamageChart.jsx
index 9c04fbef..a2c407b0 100644
--- a/src/app/components/WeaponDamageChart.jsx
+++ b/src/app/components/WeaponDamageChart.jsx
@@ -1,7 +1,6 @@
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
-import ShipSelector from './ShipSelector';
import { nameComparator } from '../utils/SlotFunctions';
import { CollapseSection, ExpandSection, MountFixed, MountGimballed, MountTurret } from './SvgIcons';
import LineChart from '../components/LineChart';
diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx
index 18dae989..cc3aec70 100644
--- a/src/app/pages/OutfittingPage.jsx
+++ b/src/app/pages/OutfittingPage.jsx
@@ -14,7 +14,7 @@ import { FloppyDisk, Bin, Switch, Download, Reload, LinkIcon, ShoppingIcon } fro
import LZString from 'lz-string';
import ShipSummaryTable from '../components/ShipSummaryTable';
import StandardSlotSection from '../components/StandardSlotSection';
-import HardpointsSlotSection from '../components/HardpointsSlotSection';
+import HardpointSlotSection from '../components/HardpointSlotSection';
import InternalSlotSection from '../components/InternalSlotSection';
import UtilitySlotSection from '../components/UtilitySlotSection';
import Pips from '../components/Pips';
@@ -574,7 +574,7 @@ export default class OutfittingPage extends Page {
-
+
{/* Control of ship and opponent */}
diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js
index 40a80237..d622254e 100755
--- a/src/app/shipyard/Ship.js
+++ b/src/app/shipyard/Ship.js
@@ -221,39 +221,6 @@ export default class Ship {
return Calc.calcYaw(this.unladenMass + fuel + cargo, this.yaw, this.standard[1].m, this.pipSpeed, eng, this.topBoost / this.topSpeed, boost);
}
- /**
- * Calculate the recovery time after losing or turning on shields
- * Thanks to CMDRs Al Gray, GIF, and Nomad Enigma for providing Shield recharge data and formulas
- *
- * @return {Number} Recovery time in seconds
- */
- calcShieldRecovery() {
- const shieldGenerator = this.findShieldGenerator();
- if (shieldGenerator) {
- const brokenRegenRate = shieldGenerator.getBrokenRegenerationRate();
- // 50% of shield strength / broken recharge rate + 15 second delay before recharge starts
- return ((this.shield / 2) / brokenRegenRate) + 15;
- }
- return 0;
- }
-
- /**
- * Calculate the recharge time for a shield going from 50% to 100%
- * Thanks to CMDRs Al Gray, GIF, and Nomad Enigma for providing Shield recharge data and formulas
- *
- * @return {Number} 50 - 100% Recharge time in seconds
- */
- calcShieldRecharge() {
- const shieldGenerator = this.findShieldGenerator();
- if (shieldGenerator) {
- const regenRate = shieldGenerator.getRegenerationRate();
-
- // 50% of shield strength / recharge rate
- return (this.shield / 2) / regenRate;
- }
- return 0;
- }
-
/**
* Calculate the hypothetical shield strength for the ship using the specified parameters
* @param {Object} sg [optional] Shield Generator to use
From da274f1b75b5bea95caded68383441a23f407749 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Thu, 30 Mar 2017 08:17:13 +0100
Subject: [PATCH 74/87] Fix issue for additive specials not showing up
---
src/app/components/ModificationsMenu.jsx | 2 ++
src/app/shipyard/Module.js | 2 +-
2 files changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/app/components/ModificationsMenu.jsx b/src/app/components/ModificationsMenu.jsx
index 3a8a19ed..d576419f 100644
--- a/src/app/components/ModificationsMenu.jsx
+++ b/src/app/components/ModificationsMenu.jsx
@@ -110,6 +110,7 @@ export default class ModificationsMenu extends TranslatedComponent {
* @param {int} grade The grade of the selected blueprint
*/
_blueprintSelected(fdname, grade) {
+ this.context.tooltip(null);
const { m } = this.props;
const blueprint = getBlueprint(fdname, m);
blueprint.grade = grade;
@@ -133,6 +134,7 @@ export default class ModificationsMenu extends TranslatedComponent {
* @param {int} special The name of the selected special
*/
_specialSelected(special) {
+ this.context.tooltip(null);
const { m, ship } = this.props;
if (m.blueprint) {
diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js
index 710c1528..37984d85 100755
--- a/src/app/shipyard/Module.js
+++ b/src/app/shipyard/Module.js
@@ -48,7 +48,7 @@ export default class Module {
// this special effect modifies our returned value
const modification = Modifications.modifications[name];
if (modification.method === 'additive') {
- result = result + modifierActions[name];
+ result = result + modifierActions[name] * 100;
} else if (modification.method === 'overwrite') {
result = modifierActions[name];
} else {
From 3b058bda7f75398d366ed7944e1598b18c7cc7ff Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Fri, 31 Mar 2017 07:40:12 +0100
Subject: [PATCH 75/87] Consistency
---
ChangeLog.md | 1 +
src/app/components/PowerBands.jsx | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/ChangeLog.md b/ChangeLog.md
index 0b03097a..9b9e758b 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -22,6 +22,7 @@
* Profiles section provides a number of graphs that show how various components of the build (top speed, sustained DPS against opponent's shields and armour etc) are affected by mass, range, etc.
* Offence section provides details of your build's damage distribution and per-weapon effectiveness. It also gives summary information for how long it will take for your build to wear down your opponent's shields and armour
* Defence section provides details of your build's defences against your selected opponent. It provides details of the effectiveness of your resistances of both shields and armour, and effective strength of each as a result. It also provides key metrics around shield longevity and recovery times, as well as module protection
+ * Fix power band marker to show safe power limit at 40% rather than 50%
#2.2.19
* Power management panel now displays modules in descending order of power usage by default
diff --git a/src/app/components/PowerBands.jsx b/src/app/components/PowerBands.jsx
index ad2ebcaf..fc9a1d7a 100644
--- a/src/app/components/PowerBands.jsx
+++ b/src/app/components/PowerBands.jsx
@@ -189,7 +189,7 @@ export default class PowerBands extends TranslatedComponent {
let { f2, pct1 } = formats; // wattFmt, pctFmt
let { available, bands } = props;
let { innerWidth, ret, dep } = state;
- let pwrWarningClass = cn('threshold', { exceeded: bands[0].retractedSum * 2 >= available });
+ let pwrWarningClass = cn('threshold', { exceeded: bands[0].retractedSum > available * 0.4 });
let deployed = [];
let retracted = [];
let retSelected = Object.keys(ret).length > 0;
@@ -268,7 +268,7 @@ export default class PowerBands extends TranslatedComponent {
axis.call(this.pctAxis);
axis.select('g:nth-child(6)').selectAll('line, text').attr('class', pwrWarningClass);
}} className='pct axis' transform={`translate(0,${state.innerHeight})`}>
-
+
{translate('ret')}
{translate('dep')}
{f2(Math.max(0, retSum))} ({pct1(Math.max(0, retSum / available))})
From db1a976e20d4ee80025cde67bf9d902cec95eaf3 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Fri, 31 Mar 2017 09:21:13 +0100
Subject: [PATCH 76/87] Restyle modifications menu
---
ChangeLog.md | 1 +
src/app/components/ModificationsMenu.jsx | 94 ++++++-----
src/app/components/Slot.jsx | 5 +-
src/less/app.less | 1 -
src/less/select.less | 2 +-
src/less/shipselector.less | 199 -----------------------
6 files changed, 62 insertions(+), 240 deletions(-)
delete mode 100755 src/less/shipselector.less
diff --git a/ChangeLog.md b/ChangeLog.md
index 9b9e758b..a70fa2ee 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -23,6 +23,7 @@
* Offence section provides details of your build's damage distribution and per-weapon effectiveness. It also gives summary information for how long it will take for your build to wear down your opponent's shields and armour
* Defence section provides details of your build's defences against your selected opponent. It provides details of the effectiveness of your resistances of both shields and armour, and effective strength of each as a result. It also provides key metrics around shield longevity and recovery times, as well as module protection
* Fix power band marker to show safe power limit at 40% rather than 50%
+ * Restyle blueprint list to improve consistency with similar menus
#2.2.19
* Power management panel now displays modules in descending order of power usage by default
diff --git a/src/app/components/ModificationsMenu.jsx b/src/app/components/ModificationsMenu.jsx
index d576419f..c0b452b4 100644
--- a/src/app/components/ModificationsMenu.jsx
+++ b/src/app/components/ModificationsMenu.jsx
@@ -15,6 +15,7 @@ export default class ModificationsMenu extends TranslatedComponent {
static propTypes = {
ship: React.PropTypes.object.isRequired,
m: React.PropTypes.object.isRequired,
+ marker: React.PropTypes.string.isRequired,
onChange: React.PropTypes.func.isRequired
};
@@ -25,7 +26,6 @@ export default class ModificationsMenu extends TranslatedComponent {
*/
constructor(props, context) {
super(props);
- this.state = this._initState(props, context);
this._toggleBlueprintsMenu = this._toggleBlueprintsMenu.bind(this);
this._toggleSpecialsMenu = this._toggleSpecialsMenu.bind(this);
@@ -34,33 +34,59 @@ export default class ModificationsMenu extends TranslatedComponent {
this._rollBest = this._rollBest.bind(this);
this._rollExtreme = this._rollExtreme.bind(this);
this._reset = this._reset.bind(this);
+
+ this.state = {
+ blueprintMenuOpened: false,
+ specialMenuOpened: false
+ };
}
/**
- * Initialise state
- * @param {Object} props React Component properties
- * @param {Object} context React Component context
+ * Render the blueprints
+ * @param {Object} props React component properties
+ * @param {Object} context React component context
* @return {Object} list: Array of React Components
*/
- _initState(props, context) {
- let { m } = props;
+ _renderBlueprints(props, context) {
+ const { m } = props;
const { language, tooltip, termtip } = context;
const translate = language.translate;
- // Set up the blueprints
- let blueprints = [];
+ const blueprints = [];
for (const blueprintName in Modifications.modules[m.grp].blueprints) {
- for (const grade in Modifications.modules[m.grp].blueprints[blueprintName].grades) {
+ const blueprint = getBlueprint(blueprintName, m);
+ let blueprintGrades = [];
+ for (let grade in Modifications.modules[m.grp].blueprints[blueprintName].grades) {
+ // Grade is a string in the JSON so make it a number
+ grade = Number(grade);
+ const classes = cn('c', {
+ active: m.blueprint && blueprint.id === m.blueprint.id && grade === m.blueprint.grade
+ });
const close = this._blueprintSelected.bind(this, blueprintName, grade);
const key = blueprintName + ':' + grade;
- const blueprint = getBlueprint(blueprintName, m);
const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers);
- blueprints.push({translate(blueprint.name + ' grade ' + grade)}
);
+ blueprintGrades.unshift({grade} );
+ }
+ if (blueprintGrades) {
+ blueprints.push({translate(blueprint.name)}
);
+ blueprints.push();
}
}
+ return blueprints;
+ }
- // Set up the special effects
- let specials = [];
+ /**
+ * Render the specials
+ * @param {Object} props React component properties
+ * @param {Object} context React component context
+ * @return {Object} list: Array of React Components
+ */
+ _renderSpecials(props, context) {
+ const { m } = props;
+ const { language, tooltip, termtip } = context;
+ const translate = language.translate;
+
+ const specials = [];
if (Modifications.modules[m.grp].specials && Modifications.modules[m.grp].specials.length > 0) {
const close = this._specialSelected.bind(this, null);
specials.push({translate('PHRASE_NO_SPECIAL')}
);
@@ -69,24 +95,17 @@ export default class ModificationsMenu extends TranslatedComponent {
specials.push({translate(Modifications.specials[specialName].name)}
);
}
}
-
- // Set up the modifications
- const modifications = this._setModifications(props);
-
- const blueprintMenuOpened = false;
- const specialMenuOpened = false;
-
- return { blueprintMenuOpened, blueprints, modifications, specialMenuOpened, specials };
+ return specials;
}
/**
- * Initialise the modifications
+ * Render the modifications
* @param {Object} props React Component properties
* @return {Object} list: Array of React Components
*/
- _setModifications(props) {
+ _renderModifications(props) {
const { m, onChange, ship } = props;
- let modifications = [];
+ const modifications = [];
for (const modName of Modifications.modules[m.grp].modifications) {
if (!Modifications.modifications[modName].hidden) {
const key = modName + (m.getModValue(modName) / 100 || 0);
@@ -116,8 +135,7 @@ export default class ModificationsMenu extends TranslatedComponent {
blueprint.grade = grade;
m.blueprint = blueprint;
- const blueprintMenuOpened = false;
- this.setState({ blueprintMenuOpened });
+ this.setState({ blueprintMenuOpened: false });
this.props.onChange();
}
@@ -148,8 +166,7 @@ export default class ModificationsMenu extends TranslatedComponent {
ship.recalculateEps();
}
- const specialMenuOpened = false;
- this.setState({ specialMenuOpened, modifications: this._setModifications(this.props) });
+ this.setState({ specialMenuOpened: false });
this.props.onChange();
}
@@ -181,7 +198,7 @@ export default class ModificationsMenu extends TranslatedComponent {
let value = features[featureName][0];
this._setRollResult(ship, m, featureName, value);
}
- this.setState({ modifications: this._setModifications(this.props) });
+
this.props.onChange();
}
@@ -196,7 +213,7 @@ export default class ModificationsMenu extends TranslatedComponent {
let value = features[featureName][0] + (Math.random() * (features[featureName][1] - features[featureName][0]));
this._setRollResult(ship, m, featureName, value);
}
- this.setState({ modifications: this._setModifications(this.props) });
+
this.props.onChange();
}
@@ -210,7 +227,7 @@ export default class ModificationsMenu extends TranslatedComponent {
let value = features[featureName][1];
this._setRollResult(ship, m, featureName, value);
}
- this.setState({ modifications: this._setModifications(this.props) });
+
this.props.onChange();
}
@@ -241,7 +258,7 @@ export default class ModificationsMenu extends TranslatedComponent {
this._setRollResult(ship, m, featureName, value);
}
- this.setState({ modifications: this._setModifications(this.props) });
+
this.props.onChange();
}
@@ -253,7 +270,6 @@ export default class ModificationsMenu extends TranslatedComponent {
ship.clearModifications(m);
ship.clearBlueprint(m);
- this.setState({ modifications: this._setModifications(this.props) });
this.props.onChange();
}
@@ -291,8 +307,10 @@ export default class ModificationsMenu extends TranslatedComponent {
specialLabel = translate('PHRASE_SELECT_SPECIAL');
}
+ const specials = this._renderSpecials(this.props, this.context);
+
const showBlueprintsMenu = blueprintMenuOpened;
- const showSpecial = haveBlueprint && this.state.specials.length > 0 && !blueprintMenuOpened;
+ const showSpecial = haveBlueprint && specials.length && !blueprintMenuOpened;
const showSpecialsMenu = specialMenuOpened;
const showRolls = haveBlueprint && !blueprintMenuOpened && !specialMenuOpened;
const showReset = !blueprintMenuOpened && !specialMenuOpened;
@@ -304,12 +322,12 @@ export default class ModificationsMenu extends TranslatedComponent {
onClick={(e) => e.stopPropagation() }
onContextMenu={stopCtxPropagation}
>
- { haveBlueprint ?
+ { showBlueprintsMenu ? '' : haveBlueprint ?
{blueprintLabel}
:
{translate('PHRASE_SELECT_BLUEPRINT')}
}
- { showBlueprintsMenu ? this.state.blueprints : null }
+ { showBlueprintsMenu ? this._renderBlueprints(this.props, this.context) : null }
{ showSpecial ? {specialLabel}
: null }
- { showSpecialsMenu ? this.state.specials : null }
+ { showSpecialsMenu ? specials : null }
{ showRolls || showReset ?
@@ -329,7 +347,7 @@ export default class ModificationsMenu extends TranslatedComponent {
: null }
{ showMods ?
- { this.state.modifications }
+ { this._renderModifications(this.props) }
: null }
);
diff --git a/src/app/components/Slot.jsx b/src/app/components/Slot.jsx
index 9959a16f..03964d35 100644
--- a/src/app/components/Slot.jsx
+++ b/src/app/components/Slot.jsx
@@ -79,7 +79,7 @@ export default class Slot extends TranslatedComponent {
let language = this.context.language;
let translate = language.translate;
let { ship, m, dropClass, dragOver, onOpen, onChange, selected, eligible, onSelect, warning, availableModules } = this.props;
- let slotDetails, menu;
+ let slotDetails, modificationsMarker, menu;
if (!selected) {
// If not selected then sure that modifications flag is unset
@@ -88,8 +88,10 @@ export default class Slot extends TranslatedComponent {
if (m) {
slotDetails = this._getSlotDetails(m, translate, language.formats, language.units); // Must be implemented by sub classes
+ modificationsMarker = JSON.stringify(m);
} else {
slotDetails = {translate(eligible ? 'emptyrestricted' : 'empty')}
;
+ modificationsMarker = '';
}
if (selected) {
@@ -99,6 +101,7 @@ export default class Slot extends TranslatedComponent {
onChange={onChange}
ship={ship}
m={m}
+ marker={modificationsMarker}
/>;
} else {
menu =
Date: Fri, 31 Mar 2017 12:04:38 +0100
Subject: [PATCH 77/87] Fix names of features in tooltips
---
src/app/components/HardpointSlot.jsx | 2 +-
src/app/components/InternalSlot.jsx | 2 +-
src/app/components/ModificationsMenu.jsx | 4 ++--
src/app/components/StandardSlot.jsx | 5 ++++-
src/app/utils/BlueprintFunctions.js | 13 +++++++------
5 files changed, 15 insertions(+), 11 deletions(-)
diff --git a/src/app/components/HardpointSlot.jsx b/src/app/components/HardpointSlot.jsx
index 2dd96bb3..6ac44e65 100644
--- a/src/app/components/HardpointSlot.jsx
+++ b/src/app/components/HardpointSlot.jsx
@@ -55,7 +55,7 @@ export default class HardpointSlot extends Slot {
modTT = (
{modTT}
- {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m)}
+ {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
);
}
diff --git a/src/app/components/InternalSlot.jsx b/src/app/components/InternalSlot.jsx
index 12cfe0aa..32916038 100644
--- a/src/app/components/InternalSlot.jsx
+++ b/src/app/components/InternalSlot.jsx
@@ -34,7 +34,7 @@ export default class InternalSlot extends Slot {
modTT = (
{modTT}
- {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m)}
+ {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
);
}
diff --git a/src/app/components/ModificationsMenu.jsx b/src/app/components/ModificationsMenu.jsx
index c0b452b4..1e657e79 100644
--- a/src/app/components/ModificationsMenu.jsx
+++ b/src/app/components/ModificationsMenu.jsx
@@ -64,7 +64,7 @@ export default class ModificationsMenu extends TranslatedComponent {
});
const close = this._blueprintSelected.bind(this, blueprintName, grade);
const key = blueprintName + ':' + grade;
- const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers);
+ const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers, m.grp);
blueprintGrades.unshift({grade} );
}
if (blueprintGrades) {
@@ -297,7 +297,7 @@ export default class ModificationsMenu extends TranslatedComponent {
if (m.blueprint && !isEmpty(m.blueprint)) {
blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
haveBlueprint = true;
- blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade].engineers);
+ blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade].engineers, m.grp);
}
let specialLabel;
diff --git a/src/app/components/StandardSlot.jsx b/src/app/components/StandardSlot.jsx
index 76915c98..2d1fda65 100644
--- a/src/app/components/StandardSlot.jsx
+++ b/src/app/components/StandardSlot.jsx
@@ -57,7 +57,7 @@ export default class StandardSlot extends TranslatedComponent {
modTT = (
{modTT}
- {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m)}
+ {blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
);
}
@@ -67,6 +67,8 @@ export default class StandardSlot extends TranslatedComponent {
this._modificationsSelected = false;
}
+ const modificationsMarker = JSON.stringify(m);
+
if (selected) {
if (this._modificationsSelected) {
menu = ;
} else {
menu =
- {translate(feature)}
+ {translate(feature, grp)}
{lowerBound}{symbol}
{current}{symbol}
{upperBound}{symbol}
@@ -50,7 +51,7 @@ export function blueprintTooltip(translate, blueprint, engineers, m) {
// We do not have a module, no value
effects.push(
- {translate(feature)}
+ {translate(feature, grp)}
{lowerBound}{symbol}
{upperBound}{symbol}
@@ -78,7 +79,7 @@ export function blueprintTooltip(translate, blueprint, engineers, m) {
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
- {translate(feature)}
+ {translate(feature, grp)}
{current}{symbol}
@@ -191,8 +192,8 @@ export function getBlueprint(name, module) {
// Start with a copy of the blueprint
const blueprint = JSON.parse(JSON.stringify(Modifications.blueprints[name]));
if (module) {
- if (module.grp === 'bh' || module.grp === 'hr') {
- // Bulkheads and hull reinforcements need to have their resistances altered by the base values
+ if (module.grp === 'bh' || module.grp === 'hr' || module.grp === 'sg' || module.grp === 'psg' || module.grp === 'bsg') {
+ // Bulkheads, hull reinforcements and shield generators need to have their resistances altered by the base values
for (const grade in blueprint.grades) {
for (const feature in blueprint.grades[grade].features) {
if (feature === 'explres') {
From 7d7ea18447fee48beae481beac8d99d861ec7e19 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Fri, 31 Mar 2017 12:28:29 +0100
Subject: [PATCH 78/87] Tidy-ups for modification
---
src/app/components/Modification.jsx | 7 +++++--
src/app/i18n/en.js | 12 ++++++++++++
2 files changed, 17 insertions(+), 2 deletions(-)
diff --git a/src/app/components/Modification.jsx b/src/app/components/Modification.jsx
index 15cf5a44..4212e3e0 100644
--- a/src/app/components/Modification.jsx
+++ b/src/app/components/Modification.jsx
@@ -37,7 +37,7 @@ export default class Modification extends TranslatedComponent {
const name = this.props.name;
let scaledValue = Math.round(Number(value) * 100);
- // Limit to +1000% / -100%
+ // Limit to +1000% / -99.99%
if (scaledValue > 100000) {
scaledValue = 100000;
value = 1000;
@@ -52,6 +52,9 @@ export default class Modification extends TranslatedComponent {
ship.setModification(m, name, scaledValue, true);
this.setState({ value });
+ }
+
+ _updateFinished() {
this.props.onChange();
}
@@ -79,7 +82,7 @@ export default class Modification extends TranslatedComponent {
}
return (
-
+
{translate(name, m.grp)}{symbol}
diff --git a/src/app/i18n/en.js b/src/app/i18n/en.js
index b0a25be5..6a425f6d 100644
--- a/src/app/i18n/en.js
+++ b/src/app/i18n/en.js
@@ -254,6 +254,18 @@ export const terms = {
minmul_sg: 'Minimum strength',
optmul_sg: 'Optimal strength',
maxmul_sg: 'Minimum strength',
+ minmass_psg: 'Minimum hull mass',
+ optmass_psg: 'Optimal hull mass',
+ maxmass_psg: 'Maximum hull mass',
+ minmul_psg: 'Minimum strength',
+ optmul_psg: 'Optimal strength',
+ maxmul_psg: 'Minimum strength',
+ minmass_bsg: 'Minimum hull mass',
+ optmass_bsg: 'Optimal hull mass',
+ maxmass_bsg: 'Maximum hull mass',
+ minmul_bsg: 'Minimum strength',
+ optmul_bsg: 'Optimal strength',
+ maxmul_bsg: 'Minimum strength',
range_s: 'Typical emission range',
From 0e86ae79c103edf9c464be6148f794013918b536 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Fri, 31 Mar 2017 16:34:50 +0100
Subject: [PATCH 79/87] Revert 2.3 diminishing returns on boosters
---
ChangeLog.md | 1 -
src/app/shipyard/Calculations.js | 4 +++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/ChangeLog.md b/ChangeLog.md
index a70fa2ee..0cb5e994 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,4 @@
#2.3.0
- * Add 2.3 diminishing returns on shield value
* Make scan time visible on scanners where available
* Update power distributor able-to-boost calculation to take fractional MJ values in to account
* Revert to floating header due to issues on iOS
diff --git a/src/app/shipyard/Calculations.js b/src/app/shipyard/Calculations.js
index 3b806710..ad040e0c 100644
--- a/src/app/shipyard/Calculations.js
+++ b/src/app/shipyard/Calculations.js
@@ -341,7 +341,9 @@ export function shieldMetrics(ship, sys) {
}
// Calculate diminishing returns for boosters
- boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5);
+ // Diminishing returns not currently in-game
+ //boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5);
+
// Remove base shield generator strength
boost -= 1;
// Apply diminishing returns
From 171a84dd3334b9a4b06714a0cad0bae8836d8732 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Sat, 1 Apr 2017 21:20:44 +0100
Subject: [PATCH 80/87] Add reload to hardpoint info
---
src/app/components/HardpointSlot.jsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/app/components/HardpointSlot.jsx b/src/app/components/HardpointSlot.jsx
index 6ac44e65..b10d22df 100644
--- a/src/app/components/HardpointSlot.jsx
+++ b/src/app/components/HardpointSlot.jsx
@@ -86,6 +86,7 @@ export default class HardpointSlot extends Slot {
{ m.getFalloff() ? {translate('falloff')} {formats.round(m.getFalloff() / 1000)}{u.km}
: null }
{ m.getShieldBoost() ? +{formats.pct1(m.getShieldBoost())}
: null }
{ m.getAmmo() ? {translate('ammunition')}: {formats.int(m.getClip())}/{formats.int(m.getAmmo())}
: null }
+ { m.getReload() ? {translate('reload')}: {formats.round(m.getReload())}{u.s}
: null }
{ m.getShotSpeed() ? {translate('shotspeed')}: {formats.int(m.getShotSpeed())}{u.mps}
: null }
{ m.getPiercing() ? {translate('piercing')}: {formats.int(m.getPiercing())}
: null }
{ m.getJitter() ? {translate('jitter')}: {formats.f2(m.getJitter())}°
: null }
From 1237833c7a1bac1eada5190d2121a342782f3f9d Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Mon, 3 Apr 2017 13:46:14 +0100
Subject: [PATCH 81/87] Ensure that opponent piips are updated immediately when
a new opponent is selecteD
---
src/app/pages/OutfittingPage.jsx | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx
index cc3aec70..575b9f96 100644
--- a/src/app/pages/OutfittingPage.jsx
+++ b/src/app/pages/OutfittingPage.jsx
@@ -267,15 +267,29 @@ export default class OutfittingPage extends Page {
*/
_opponentUpdated(opponent, opponentBuild) {
const opponentShip = new Ship(opponent, Ships[opponent].properties, Ships[opponent].slots);
+ let opponentSys = this.state.opponentSys;
+ let opponentEng = this.state.opponentEng;
+ let opponentWep = this.state.opponentWep;
if (opponentBuild && Persist.getBuild(opponent, opponentBuild)) {
// Ship is a particular build
opponentShip.buildFrom(Persist.getBuild(opponent, opponentBuild));
+ // Set pips for opponent
+ const opponentParts = Persist.getBuild(opponent, opponentBuild).split('.');
+ if (opponentParts.length >= 5) {
+ const opponentControl = LZString.decompressFromBase64(Utils.fromUrlSafe(opponentParts[4])).split('/');
+ opponentSys = parseFloat(opponentControl[0]);
+ opponentEng = parseFloat(opponentControl[1]);
+ opponentWep = parseFloat(opponentControl[2]);
+ }
} else {
// Ship is a stock build
opponentShip.buildWith(Ships[opponent].defaults);
+ opponentSys = 2;
+ opponentEng = 2;
+ opponentWep = 2;
}
- this.setState({ opponent: opponentShip, opponentBuild }, () => this._updateRouteOnControlChange());
+ this.setState({ opponent: opponentShip, opponentBuild, opponentSys, opponentEng, opponentWep }, () => this._updateRouteOnControlChange());
}
/**
From 42c43d3f2d6581da3041d33ffa1988687571d472 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Tue, 4 Apr 2017 08:51:02 +0100
Subject: [PATCH 82/87] Use single calculation for shield metrics
---
.../anaconda-test-detailed-export-v4.json | 2 +-
src/app/components/Modification.jsx | 3 ++
src/app/shipyard/Calculations.js | 2 +-
src/app/shipyard/Ship.js | 52 +++----------------
4 files changed, 11 insertions(+), 48 deletions(-)
diff --git a/__tests__/fixtures/anaconda-test-detailed-export-v4.json b/__tests__/fixtures/anaconda-test-detailed-export-v4.json
index 65893b57..32d622cf 100644
--- a/__tests__/fixtures/anaconda-test-detailed-export-v4.json
+++ b/__tests__/fixtures/anaconda-test-detailed-export-v4.json
@@ -310,7 +310,7 @@
"unladenRange": 18.74,
"yaw": 10,
"fullTankRange": 18.36,
- "hardness": 170,
+ "hardness": 65,
"ladenRange": 16.59,
"unladenFastestRange": 74.2,
"ladenFastestRange": 66.96,
diff --git a/src/app/components/Modification.jsx b/src/app/components/Modification.jsx
index 4212e3e0..4869a694 100644
--- a/src/app/components/Modification.jsx
+++ b/src/app/components/Modification.jsx
@@ -54,6 +54,9 @@ export default class Modification extends TranslatedComponent {
this.setState({ value });
}
+ /**
+ * Triggered when an update to slider value is finished i.e. when losing focus
+ */
_updateFinished() {
this.props.onChange();
}
diff --git a/src/app/shipyard/Calculations.js b/src/app/shipyard/Calculations.js
index ad040e0c..42dc5cee 100644
--- a/src/app/shipyard/Calculations.js
+++ b/src/app/shipyard/Calculations.js
@@ -342,7 +342,7 @@ export function shieldMetrics(ship, sys) {
// Calculate diminishing returns for boosters
// Diminishing returns not currently in-game
- //boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5);
+ // boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5);
// Remove base shield generator strength
boost -= 1;
diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js
index d622254e..020e32bc 100755
--- a/src/app/shipyard/Ship.js
+++ b/src/app/shipyard/Ship.js
@@ -1204,53 +1204,13 @@ export default class Ship {
* @return {this} The ship instance (for chaining operations)
*/
recalculateShield() {
- let shield = 0;
- let shieldBoost = 1;
- let shieldExplRes = null;
- let shieldKinRes = null;
- let shieldThermRes = null;
- let shieldExplDRStart = null;
- let shieldExplDREnd = null;
- let shieldKinDRStart = null;
- let shieldKinDREnd = null;
- let shieldThermDRStart = null;
- let shieldThermDREnd = null;
-
- const sgSlot = this.findInternalByGroup('sg');
- if (sgSlot && sgSlot.enabled) {
- // Shield from generator
- shield = Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sgSlot.m, 1);
- shieldExplRes = 1 - sgSlot.m.getExplosiveResistance();
- shieldExplDRStart = shieldExplRes * 0.7;
- shieldExplDREnd = 0;
- shieldKinRes = 1 - sgSlot.m.getKineticResistance();
- shieldKinDRStart = shieldKinRes * 0.7;
- shieldKinDREnd = 0;
- shieldThermRes = 1 - sgSlot.m.getThermalResistance();
- shieldThermDRStart = shieldThermRes * 0.7;
- shieldThermDREnd = 0;
-
- // Shield from boosters
- for (let slot of this.hardpoints) {
- if (slot.enabled && slot.m && slot.m.grp == 'sb') {
- shieldBoost += slot.m.getShieldBoost();
- shieldExplRes *= (1 - slot.m.getExplosiveResistance());
- shieldKinRes *= (1 - slot.m.getKineticResistance());
- shieldThermRes *= (1 - slot.m.getThermalResistance());
- }
- }
- }
-
- // We apply diminishing returns to the boosted value
- shieldBoost = Math.min(shieldBoost, (1 - Math.pow(Math.E, -0.7 * shieldBoost)) * 2.5);
-
- shield = shield * shieldBoost;
-
- this.shield = shield;
- this.shieldExplRes = shieldExplRes ? 1 - this.diminishingReturns(shieldExplRes, shieldExplDREnd, shieldExplDRStart) : null;
- this.shieldKinRes = shieldKinRes ? 1 - this.diminishingReturns(shieldKinRes, shieldKinDREnd, shieldKinDRStart) : null;
- this.shieldThermRes = shieldThermRes ? 1 - this.diminishingReturns(shieldThermRes, shieldThermDREnd, shieldThermDRStart) : null;
+ // Obtain shield metrics with 0 pips to sys (parts affected by SYS aren't used here)
+ const metrics = Calc.shieldMetrics(this, 0);
+ this.shield = metrics.generator + metrics.boosters;
+ this.shieldExplRes = this.shield > 0 ? 1 - metrics.explosive.total : null;
+ this.shieldKinRes = this.shield > 0 ? 1 - metrics.kinetic.total : null;
+ this.shieldThermRes = this.shield > 0 ? 1 - metrics.thermal.total : null;
return this;
}
From 2fa2625a8fcab30b8fcf18ca757dd379c809f68d Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Tue, 4 Apr 2017 23:55:08 +0100
Subject: [PATCH 83/87] Fix display and selection of modules near weight limit
in menu
---
src/app/components/AvailableModulesMenu.jsx | 10 ++++++++-
src/app/components/OutfittingSubpages.jsx | 4 ++--
src/app/components/ShipSummaryTable.jsx | 8 ++++---
src/app/components/StandardSlot.jsx | 3 ++-
src/app/components/StandardSlotSection.jsx | 4 ++--
src/app/pages/OutfittingPage.jsx | 10 ++++-----
src/app/shipyard/Module.js | 8 -------
src/app/shipyard/Ship.js | 24 ++++++++++++---------
src/app/utils/CompanionApiUtils.js | 3 ++-
src/app/utils/SlotFunctions.js | 6 +++---
10 files changed, 44 insertions(+), 36 deletions(-)
diff --git a/src/app/components/AvailableModulesMenu.jsx b/src/app/components/AvailableModulesMenu.jsx
index fba8ad15..1d3df29c 100644
--- a/src/app/components/AvailableModulesMenu.jsx
+++ b/src/app/components/AvailableModulesMenu.jsx
@@ -1,4 +1,5 @@
import React from 'react';
+import * as ModuleUtils from '../shipyard/ModuleUtils';
import { findDOMNode } from 'react-dom';
import TranslatedComponent from './TranslatedComponent';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
@@ -213,7 +214,14 @@ export default class AvailableModulesMenu extends TranslatedComponent {
for (let i = 0; i < sortedModules.length; i++) {
let m = sortedModules[i];
let mount = null;
- let disabled = m.maxmass && (mass + (m.mass ? m.mass : 0)) > m.maxmass;
+ let disabled = false;
+ if (ModuleUtils.isShieldGenerator(m.grp)) {
+ // Shield generators care about maximum hull mass
+ disabled = mass > m.maxmass;
+ } else if (m.maxmass) {
+ // Thrusters care about total mass
+ disabled = mass + m.mass > m.maxmass;
+ }
let active = mountedModule && mountedModule.id === m.id;
let classes = cn(m.name ? 'lc' : 'c', {
warning: !disabled && warningFunc && warningFunc(m),
diff --git a/src/app/components/OutfittingSubpages.jsx b/src/app/components/OutfittingSubpages.jsx
index 2d25037c..e849ecfe 100644
--- a/src/app/components/OutfittingSubpages.jsx
+++ b/src/app/components/OutfittingSubpages.jsx
@@ -85,12 +85,12 @@ export default class OutfittingSubpages extends TranslatedComponent {
_profilesTab() {
const { ship, opponent, cargo, fuel, eng, boost, engagementRange, opponentSys } = this.props;
const { translate } = this.context.language;
- let realBoost = boost && ship.canBoost();
+ let realBoost = boost && ship.canBoost(cargo, fuel);
Persist.setOutfittingTab('profiles');
const engineProfileMarker = `${ship.toString()}:${cargo}:${fuel}:${eng}:${realBoost}`;
const fsdProfileMarker = `${ship.toString()}:${cargo}:${fuel}`;
- const movementMarker = `${ship.topSpeed}:${ship.pitch}:${ship.roll}:${ship.yaw}:${ship.canBoost()}`;
+ const movementMarker = `${ship.topSpeed}:${ship.pitch}:${ship.roll}:${ship.yaw}:${ship.canBoost(cargo, fuel)}`;
const damageMarker = `${ship.toString()}:${opponent.toString()}:${engagementRange}:${opponentSys}`;
return
diff --git a/src/app/components/ShipSummaryTable.jsx b/src/app/components/ShipSummaryTable.jsx
index 9212bb1b..881a3b25 100644
--- a/src/app/components/ShipSummaryTable.jsx
+++ b/src/app/components/ShipSummaryTable.jsx
@@ -11,6 +11,8 @@ export default class ShipSummaryTable extends TranslatedComponent {
static propTypes = {
ship: React.PropTypes.object.isRequired,
+ cargo: React.PropTypes.number.isRequired,
+ fuel: React.PropTypes.number.isRequired,
marker: React.PropTypes.string.isRequired,
};
@@ -19,7 +21,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
* @return {React.Component} Summary table
*/
render() {
- const { ship } = this.props;
+ const { ship, cargo, fuel } = this.props;
let { language, tooltip, termtip } = this.context;
let translate = language.translate;
let u = language.units;
@@ -31,9 +33,9 @@ export default class ShipSummaryTable extends TranslatedComponent {
const sgClassNames = cn({ warning: shieldGenerator && !ship.shield, muted: !shieldGenerator });
const sgTooltip = shieldGenerator ? 'TT_SUMMARY_SHIELDS' : 'TT_SUMMARY_SHIELDS_NONFUNCTIONAL';
const timeToDrain = Calc.timeToDrainWep(ship, 4);
- const canThrust = ship.canThrust();
+ const canThrust = ship.canThrust(cargo, fuel);
const speedTooltip = canThrust ? 'TT_SUMMARY_SPEED' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
- const canBoost = ship.canBoost();
+ const canBoost = ship.canBoost(cargo, fuel);
const boostTooltip = canBoost ? 'TT_SUMMARY_BOOST' : canThrust ? 'TT_SUMMARY_BOOST_NONFUNCTIONAL' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
return
diff --git a/src/app/components/StandardSlot.jsx b/src/app/components/StandardSlot.jsx
index 2d1fda65..10d4a69c 100644
--- a/src/app/components/StandardSlot.jsx
+++ b/src/app/components/StandardSlot.jsx
@@ -5,6 +5,7 @@ import TranslatedComponent from './TranslatedComponent';
import { diffDetails } from '../utils/SlotFunctions';
import AvailableModulesMenu from './AvailableModulesMenu';
import ModificationsMenu from './ModificationsMenu';
+import * as ModuleUtils from '../shipyard/ModuleUtils';
import { ListModifications, Modified } from './SvgIcons';
import { Modifications } from 'coriolis-data/dist';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
@@ -82,7 +83,7 @@ export default class StandardSlot extends TranslatedComponent {
menu =
m instanceof Module ? m.getMaxMass() < (ship.ladenMass - st[1].mass + m.mass) : m.maxmass < (ship.ladenMass - st[1].mass + m.mass)}
+ warning={m => m instanceof Module ? m.getMaxMass() < (ship.unladenMass + cargo + fuel - st[1].m.mass + m.mass) : m.maxmass < (ship.unladenMass + cargo + fuel - st[1].m.mass + m.mass)}
/>;
diff --git a/src/app/pages/OutfittingPage.jsx b/src/app/pages/OutfittingPage.jsx
index 575b9f96..358443f3 100644
--- a/src/app/pages/OutfittingPage.jsx
+++ b/src/app/pages/OutfittingPage.jsx
@@ -545,11 +545,11 @@ export default class OutfittingPage extends Page {
const _pStr = `${ship.getPowerEnabledString()}${ship.getPowerPrioritiesString()}`;
const _mStr = ship.getModificationsString();
- const standardSlotMarker = `${ship.name}${_sStr}${_pStr}${_mStr}`;
+ const standardSlotMarker = `${ship.name}${_sStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`;
const internalSlotMarker = `${ship.name}${_iStr}${_pStr}${_mStr}`;
const hardpointsSlotMarker = `${ship.name}${_hStr}${_pStr}${_mStr}`;
- const boostMarker = `${ship.canBoost()}`;
- const shipSummaryMarker = `${ship.name}${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}`;
+ const boostMarker = `${ship.canBoost(cargo, fuel)}`;
+ const shipSummaryMarker = `${ship.name}${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}${ship.ladenMass}${ship.cargo}${ship.fuel}`;
return (
@@ -585,8 +585,8 @@ export default class OutfittingPage extends Page {
{/* Main tables */}
-
-
+
+
diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js
index 37984d85..db4cb8a1 100755
--- a/src/app/shipyard/Module.js
+++ b/src/app/shipyard/Module.js
@@ -162,14 +162,6 @@ export default class Module {
return result;
}
- /**
- * Return true if this is a shield generator
- * @return {Boolean} if this is a shield generator
- */
- isShieldGenerator() {
- return (this.grp === 'sg' || this.grp === 'psg' || this.grp === 'bsg');
- }
-
/**
* Get the power generation of this module, taking in to account modifications
* @return {Number} the power generation of this module
diff --git a/src/app/shipyard/Ship.js b/src/app/shipyard/Ship.js
index 020e32bc..0de8b2ef 100755
--- a/src/app/shipyard/Ship.js
+++ b/src/app/shipyard/Ship.js
@@ -123,19 +123,23 @@ export default class Ship {
/**
* Can the ship thrust/move
+ * @param {Number} cargo Amount of cargo in the ship
+ * @param {Number} fuel Amount of fuel in the ship
* @return {[type]} True if thrusters operational
*/
- canThrust() {
+ canThrust(cargo, fuel) {
return this.getSlotStatus(this.standard[1]) == 3 && // Thrusters are powered
- this.ladenMass < this.standard[1].m.getMaxMass(); // Max mass not exceeded
+ this.unladenMass + cargo + fuel < this.standard[1].m.getMaxMass(); // Max mass not exceeded
}
/**
* Can the ship boost
+ * @param {Number} cargo Amount of cargo in the ship
+ * @param {Number} fuel Amount of fuel in the ship
* @return {[type]} True if boost capable
*/
- canBoost() {
- return this.canThrust() && // Thrusters operational
+ canBoost(cargo, fuel) {
+ return this.canThrust(cargo, fuel) && // Thrusters operational
this.standard[4].m.getEnginesCapacity() > this.boostEnergy; // PD capacitor is sufficient for boost
}
@@ -1185,7 +1189,7 @@ export default class Ship {
updateMovement() {
this.speeds = Calc.speed(this.unladenMass + this.fuelCapacity, this.speed, this.standard[1].m, this.pipSpeed);
this.topSpeed = this.speeds[4];
- this.topBoost = this.canBoost() ? this.speeds[4] * this.boost / this.speed : 0;
+ this.topBoost = this.canBoost(0, 0) ? this.speeds[4] * this.boost / this.speed : 0;
this.pitches = Calc.pitch(this.unladenMass + this.fuelCapacity, this.pitch, this.standard[1].m, this.pipSpeed);
this.topPitch = this.pitches[4];
@@ -1207,7 +1211,7 @@ export default class Ship {
// Obtain shield metrics with 0 pips to sys (parts affected by SYS aren't used here)
const metrics = Calc.shieldMetrics(this, 0);
- this.shield = metrics.generator + metrics.boosters;
+ this.shield = metrics.generator ? metrics.generator + metrics.boosters : 0;
this.shieldExplRes = this.shield > 0 ? 1 - metrics.explosive.total : null;
this.shieldKinRes = this.shield > 0 ? 1 - metrics.kinetic.total : null;
this.shieldThermRes = this.shield > 0 ? 1 - metrics.thermal.total : null;
@@ -1639,11 +1643,11 @@ export default class Ship {
let mass = this.hullMass;
mass += m.pp ? m.pp.getMass() : ModuleUtils.standard(0, '2D').getMass();
mass += m.th ? m.th.getMass() : ModuleUtils.standard(1, '2D').getMass();
- mass += m.fsd ? m.fsd.getMass() : ModuleUtils.standard(2, this.standard[2].maxClass + 'D').getMass();
+ mass += m.fsd ? m.fsd.getMass() : ModuleUtils.standard(2, '2D').getMass();
mass += m.ls ? m.ls.getMass() : ModuleUtils.standard(3, this.standard[3].maxClass + 'D').getMass() * 0.3; // Lightweight grade 4 mod reduces mass by up to 70%
- mass += m.pd ? m.pd.getMass() : ModuleUtils.standard(4, '2D').getMass();
- mass += m.s ? m.s.getMass() : ModuleUtils.standard(5, this.standard[5].maxClass + 'D').getMass();
- mass += m.ft ? m.ft.getMass() : ModuleUtils.standard(6, '1C').getMass();
+ mass += m.pd ? m.pd.getMass() : ModuleUtils.standard(4, '1D').getMass();
+ mass += m.s ? m.s.getMass() : ModuleUtils.standard(5, this.standard[5].maxClass + 'D').getMass() * 0.2; // Lightweight grade 5 mod reduces mass by up to 80%
+ // Ignore fuel tank as it could be empty
return mass;
}
diff --git a/src/app/utils/CompanionApiUtils.js b/src/app/utils/CompanionApiUtils.js
index 5f6df7c5..25124864 100644
--- a/src/app/utils/CompanionApiUtils.js
+++ b/src/app/utils/CompanionApiUtils.js
@@ -3,6 +3,7 @@ import { Modifications, Modules, Ships } from 'coriolis-data/dist';
import Module from '../shipyard/Module';
import Ship from '../shipyard/Ship';
import { getBlueprint } from '../utils/BlueprintFunctions';
+import * as ModuleUtils from '../shipyard/ModuleUtils';
// mapping from fd's ship model names to coriolis'
const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
@@ -378,7 +379,7 @@ function _addModifications(module, modifiers, blueprint, grade) {
// Shield generator resistance is actually a damage modifier, so needs to be inverted.
// In addition, the modification is based off the inherent resistance of the module
- if (module.isShieldGenerator()) {
+ if (ModuleUtils.isShieldGenerator(module.grp)) {
if (module.getModValue('explres')) {
module.setModValue('explres', ((1 - (1 - module.explres) * (1 + module.getModValue('explres') / 10000)) - module.explres) * 10000);
}
diff --git a/src/app/utils/SlotFunctions.js b/src/app/utils/SlotFunctions.js
index 8a57e63e..c2f5f4ba 100644
--- a/src/app/utils/SlotFunctions.js
+++ b/src/app/utils/SlotFunctions.js
@@ -1,9 +1,9 @@
import React from 'react';
import cn from 'classnames';
-import { isShieldGenerator } from '../shipyard/ModuleUtils';
import Module from '../shipyard/Module';
import { Infinite } from '../components/SvgIcons';
import Persist from '../stores/Persist';
+import * as ModuleUtils from '../shipyard/ModuleUtils';
/**
* Determine if a slot on a ship can mount a module of a particular class and group
@@ -159,8 +159,8 @@ export function diffDetails(language, m, mm) {
let mmDps = mm ? mm.getDps() || 0 : 0;
if (mDps && mDps != mmDps) propDiffs.push({translate('dps')}: {diff(formats.round, mDps, mmDps)}
);
- let mAffectsShield = isShieldGenerator(m.grp) || m.grp == 'sb';
- let mmAffectsShield = isShieldGenerator(mm ? mm.grp : null) || mm && mm.grp == 'sb';
+ let mAffectsShield = ModuleUtils.isShieldGenerator(m.grp) || m.grp == 'sb';
+ let mmAffectsShield = mm ? ModuleUtils.isShieldGenerator(m.grp) || mm.grp == 'sb' : false;
if (mAffectsShield || mmAffectsShield) {
let shield = this.calcShieldStrengthWith(); // Get shield strength regardless of slot active / inactive
let newShield = 0;
From 243f1123ba01d6b35b3c477df39f4e1bd79d0229 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Thu, 6 Apr 2017 19:54:45 +0100
Subject: [PATCH 84/87] Fix typo
---
src/app/components/Offence.jsx | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/app/components/Offence.jsx b/src/app/components/Offence.jsx
index f67f721f..18bb24b0 100644
--- a/src/app/components/Offence.jsx
+++ b/src/app/components/Offence.jsx
@@ -230,8 +230,8 @@ export default class Offence extends TranslatedComponent {
{translate('weapon')}
- {translate('opponent\`s shields')}
- {translate('opponent\`s armour')}
+ {translate('opponent\'s shields')}
+ {translate('opponent\'s armour')}
{'sdps'}
From 93d6c1b871553ea4513258a44451721332df083a Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Sat, 8 Apr 2017 00:00:08 +0100
Subject: [PATCH 85/87] Update defence tooltips - #93
---
src/app/components/Defence.jsx | 167 ++++++++++++++++++++++-----------
1 file changed, 114 insertions(+), 53 deletions(-)
diff --git a/src/app/components/Defence.jsx b/src/app/components/Defence.jsx
index 7d6f5e78..545e6eac 100644
--- a/src/app/components/Defence.jsx
+++ b/src/app/components/Defence.jsx
@@ -61,50 +61,90 @@ export default class Defence extends TranslatedComponent {
const shieldSourcesData = [];
const effectiveShieldData = [];
const shieldDamageTakenData = [];
- const shieldTooltipDetails = [];
- const shieldAbsoluteTooltipDetails = [];
- const shieldExplosiveTooltipDetails = [];
- const shieldKineticTooltipDetails = [];
- const shieldThermalTooltipDetails = [];
+ const shieldSourcesTt = [];
+ const shieldDamageTakenAbsoluteTt = [];
+ const shieldDamageTakenExplosiveTt = [];
+ const shieldDamageTakenKineticTt = [];
+ const shieldDamageTakenThermalTt = [];
+ const effectiveShieldAbsoluteTt = [];
+ const effectiveShieldExplosiveTt = [];
+ const effectiveShieldKineticTt = [];
+ const effectiveShieldThermalTt = [];
let maxEffectiveShield = 0;
if (shield.total) {
shieldSourcesData.push({ value: Math.round(shield.generator), label: translate('generator') });
shieldSourcesData.push({ value: Math.round(shield.boosters), label: translate('boosters') });
shieldSourcesData.push({ value: Math.round(shield.cells), label: translate('cells') });
- if (shield.generator > 0) shieldTooltipDetails.push({translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}
);
- if (shield.boosters > 0) shieldTooltipDetails.push({translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}
);
- if (shield.cells > 0) shieldTooltipDetails.push({translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}
);
+ if (shield.generator > 0) {
+ shieldSourcesTt.push({translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}
);
+ effectiveShieldAbsoluteTt.push({translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}
);
+ effectiveShieldExplosiveTt.push({translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}
);
+ effectiveShieldKineticTt.push({translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}
);
+ effectiveShieldThermalTt.push({translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}
);
+ if (shield.boosters > 0) {
+ shieldSourcesTt.push({translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}
);
+ effectiveShieldAbsoluteTt.push({translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}
);
+ effectiveShieldExplosiveTt.push({translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}
);
+ effectiveShieldKineticTt.push({translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}
);
+ effectiveShieldThermalTt.push({translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}
);
+ }
- shieldAbsoluteTooltipDetails.push({translate('generator') + ' ' + formats.pct1(shield.absolute.generator)}
);
- shieldAbsoluteTooltipDetails.push({translate('boosters') + ' ' + formats.pct1(shield.absolute.boosters)}
);
- shieldAbsoluteTooltipDetails.push({translate('power distributor') + ' ' + formats.pct1(shield.absolute.sys)}
);
+ if (shield.cells > 0) {
+ shieldSourcesTt.push({translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}
);
+ effectiveShieldAbsoluteTt.push({translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}
);
+ effectiveShieldExplosiveTt.push({translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}
);
+ effectiveShieldKineticTt.push({translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}
);
+ effectiveShieldThermalTt.push({translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}
);
+ }
- shieldExplosiveTooltipDetails.push({translate('generator') + ' ' + formats.pct1(shield.explosive.generator)}
);
- shieldExplosiveTooltipDetails.push({translate('boosters') + ' ' + formats.pct1(shield.explosive.boosters)}
);
- shieldExplosiveTooltipDetails.push({translate('power distributor') + ' ' + formats.pct1(shield.explosive.sys)}
);
+ // Add effective shield from resistances
+ const rawMj = shield.generator + shield.boosters + shield.cells;
+ const explosiveMj = rawMj / (shield.explosive.generator * shield.explosive.boosters) - rawMj;
+ if (explosiveMj != 0) effectiveShieldExplosiveTt.push({translate('resistance') + ' ' + formats.int(explosiveMj)}{units.MJ}
);
+ const kineticMj = rawMj / (shield.kinetic.generator * shield.kinetic.boosters) - rawMj;
+ if (kineticMj != 0) effectiveShieldKineticTt.push({translate('resistance') + ' ' + formats.int(kineticMj)}{units.MJ}
);
+ const thermalMj = rawMj / (shield.thermal.generator * shield.thermal.boosters) - rawMj;
+ if (thermalMj != 0) effectiveShieldThermalTt.push({translate('resistance') + ' ' + formats.int(thermalMj)}{units.MJ}
);
- shieldKineticTooltipDetails.push({translate('generator') + ' ' + formats.pct1(shield.kinetic.generator)}
);
- shieldKineticTooltipDetails.push({translate('boosters') + ' ' + formats.pct1(shield.kinetic.boosters)}
);
- shieldKineticTooltipDetails.push({translate('power distributor') + ' ' + formats.pct1(shield.kinetic.sys)}
);
+ // Add effective shield from power distributor SYS pips
+ if (shield.absolute.sys != 1) {
+ effectiveShieldAbsoluteTt.push({translate('power distributor') + ' ' + formats.int(rawMj / shield.absolute.sys - rawMj)}{units.MJ}
);
+ effectiveShieldExplosiveTt.push({translate('power distributor') + ' ' + formats.int(rawMj / shield.explosive.sys - rawMj)}{units.MJ}
);
+ effectiveShieldKineticTt.push({translate('power distributor') + ' ' + formats.int(rawMj / shield.kinetic.sys - rawMj)}{units.MJ}
);
+ effectiveShieldThermalTt.push({translate('power distributor') + ' ' + formats.int(rawMj / shield.thermal.sys - rawMj)}{units.MJ}
);
+ }
+ }
- shieldThermalTooltipDetails.push({translate('generator') + ' ' + formats.pct1(shield.thermal.generator)}
);
- shieldThermalTooltipDetails.push({translate('boosters') + ' ' + formats.pct1(shield.thermal.boosters)}
);
- shieldThermalTooltipDetails.push({translate('power distributor') + ' ' + formats.pct1(shield.thermal.sys)}
);
+ shieldDamageTakenAbsoluteTt.push({translate('generator') + ' ' + formats.pct1(shield.absolute.generator)}
);
+ shieldDamageTakenAbsoluteTt.push({translate('boosters') + ' ' + formats.pct1(shield.absolute.boosters)}
);
+ shieldDamageTakenAbsoluteTt.push({translate('power distributor') + ' ' + formats.pct1(shield.absolute.sys)}
);
+
+ shieldDamageTakenExplosiveTt.push({translate('generator') + ' ' + formats.pct1(shield.explosive.generator)}
);
+ shieldDamageTakenExplosiveTt.push({translate('boosters') + ' ' + formats.pct1(shield.explosive.boosters)}
);
+ shieldDamageTakenExplosiveTt.push({translate('power distributor') + ' ' + formats.pct1(shield.explosive.sys)}
);
+
+ shieldDamageTakenKineticTt.push({translate('generator') + ' ' + formats.pct1(shield.kinetic.generator)}
);
+ shieldDamageTakenKineticTt.push({translate('boosters') + ' ' + formats.pct1(shield.kinetic.boosters)}
);
+ shieldDamageTakenKineticTt.push({translate('power distributor') + ' ' + formats.pct1(shield.kinetic.sys)}
);
+
+ shieldDamageTakenThermalTt.push({translate('generator') + ' ' + formats.pct1(shield.thermal.generator)}
);
+ shieldDamageTakenThermalTt.push({translate('boosters') + ' ' + formats.pct1(shield.thermal.boosters)}
);
+ shieldDamageTakenThermalTt.push({translate('power distributor') + ' ' + formats.pct1(shield.thermal.sys)}
);
const effectiveAbsoluteShield = shield.total / shield.absolute.total;
- effectiveShieldData.push({ value: Math.round(effectiveAbsoluteShield), label: translate('absolute') });
+ effectiveShieldData.push({ value: Math.round(effectiveAbsoluteShield), label: translate('absolute'), tooltip: effectiveShieldAbsoluteTt });
const effectiveExplosiveShield = shield.total / shield.explosive.total;
- effectiveShieldData.push({ value: Math.round(effectiveExplosiveShield), label: translate('explosive') });
+ effectiveShieldData.push({ value: Math.round(effectiveExplosiveShield), label: translate('explosive'), tooltip: effectiveShieldExplosiveTt });
const effectiveKineticShield = shield.total / shield.kinetic.total;
- effectiveShieldData.push({ value: Math.round(effectiveKineticShield), label: translate('kinetic') });
+ effectiveShieldData.push({ value: Math.round(effectiveKineticShield), label: translate('kinetic'), tooltip: effectiveShieldKineticTt });
const effectiveThermalShield = shield.total / shield.thermal.total;
- effectiveShieldData.push({ value: Math.round(effectiveThermalShield), label: translate('thermal') });
+ effectiveShieldData.push({ value: Math.round(effectiveThermalShield), label: translate('thermal'), tooltip: effectiveShieldThermalTt });
- shieldDamageTakenData.push({ value: Math.round(shield.absolute.total * 100), label: translate('absolute'), tooltip: shieldAbsoluteTooltipDetails });
- shieldDamageTakenData.push({ value: Math.round(shield.explosive.total * 100), label: translate('explosive'), tooltip: shieldExplosiveTooltipDetails });
- shieldDamageTakenData.push({ value: Math.round(shield.kinetic.total * 100), label: translate('kinetic'), tooltip: shieldKineticTooltipDetails });
- shieldDamageTakenData.push({ value: Math.round(shield.thermal.total * 100), label: translate('thermal'), tooltip: shieldThermalTooltipDetails });
+ shieldDamageTakenData.push({ value: Math.round(shield.absolute.total * 100), label: translate('absolute'), tooltip: shieldDamageTakenAbsoluteTt });
+ shieldDamageTakenData.push({ value: Math.round(shield.explosive.total * 100), label: translate('explosive'), tooltip: shieldDamageTakenExplosiveTt });
+ shieldDamageTakenData.push({ value: Math.round(shield.kinetic.total * 100), label: translate('kinetic'), tooltip: shieldDamageTakenKineticTt });
+ shieldDamageTakenData.push({ value: Math.round(shield.thermal.total * 100), label: translate('thermal'), tooltip: shieldDamageTakenThermalTt });
maxEffectiveShield = Math.max(shield.total / shield.absolute.max, shield.total / shield.explosive.max, shield.total / shield.kinetic.max, shield.total / shield.thermal.max);
}
@@ -113,41 +153,62 @@ export default class Defence extends TranslatedComponent {
armourSourcesData.push({ value: Math.round(armour.bulkheads), label: translate('bulkheads') });
armourSourcesData.push({ value: Math.round(armour.reinforcement), label: translate('reinforcement') });
- const armourTooltipDetails = [];
- if (armour.bulkheads > 0) armourTooltipDetails.push({translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}
);
- if (armour.reinforcement > 0) armourTooltipDetails.push({translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}
);
+ const armourSourcesTt = [];
+ const effectiveArmourAbsoluteTt = [];
+ const effectiveArmourExplosiveTt = [];
+ const effectiveArmourKineticTt = [];
+ const effectiveArmourThermalTt = [];
+ if (armour.bulkheads > 0) {
+ armourSourcesTt.push({translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}
);
+ effectiveArmourAbsoluteTt.push({translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}
);
+ effectiveArmourExplosiveTt.push({translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}
);
+ effectiveArmourKineticTt.push({translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}
);
+ effectiveArmourThermalTt.push({translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}
);
+ if (armour.reinforcement > 0) {
+ armourSourcesTt.push({translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}
);
+ effectiveArmourAbsoluteTt.push({translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}
);
+ effectiveArmourExplosiveTt.push({translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}
);
+ effectiveArmourKineticTt.push({translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}
);
+ effectiveArmourThermalTt.push({translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}
);
+ }
+ }
- const armourAbsoluteTooltipDetails = [];
- armourAbsoluteTooltipDetails.push({translate('bulkheads') + ' ' + formats.pct1(armour.absolute.bulkheads)}
);
- armourAbsoluteTooltipDetails.push({translate('reinforcement') + ' ' + formats.pct1(armour.absolute.reinforcement)}
);
+ const rawArmour = armour.bulkheads + armour.reinforcement;
- const armourExplosiveTooltipDetails = [];
- armourExplosiveTooltipDetails.push({translate('bulkheads') + ' ' + formats.pct1(armour.explosive.bulkheads)}
);
- armourExplosiveTooltipDetails.push({translate('reinforcement') + ' ' + formats.pct1(armour.explosive.reinforcement)}
);
+ const armourDamageTakenTt = [];
+ armourDamageTakenTt.push({translate('bulkheads') + ' ' + formats.pct1(armour.absolute.bulkheads)}
);
+ armourDamageTakenTt.push({translate('reinforcement') + ' ' + formats.pct1(armour.absolute.reinforcement)}
);
- const armourKineticTooltipDetails = [];
- armourKineticTooltipDetails.push({translate('bulkheads') + ' ' + formats.pct1(armour.kinetic.bulkheads)}
);
- armourKineticTooltipDetails.push({translate('reinforcement') + ' ' + formats.pct1(armour.kinetic.reinforcement)}
);
+ const armourDamageTakenExplosiveTt = [];
+ armourDamageTakenExplosiveTt.push({translate('bulkheads') + ' ' + formats.pct1(armour.explosive.bulkheads)}
);
+ armourDamageTakenExplosiveTt.push({translate('reinforcement') + ' ' + formats.pct1(armour.explosive.reinforcement)}
);
+ if (armour.explosive.bulkheads * armour.explosive.reinforcement != 1) effectiveArmourExplosiveTt.push({translate('resistance') + ' ' + formats.int(rawArmour / (armour.explosive.bulkheads * armour.explosive.reinforcement) - rawArmour)}
);
- const armourThermalTooltipDetails = [];
- armourThermalTooltipDetails.push({translate('bulkheads') + ' ' + formats.pct1(armour.thermal.bulkheads)}
);
- armourThermalTooltipDetails.push({translate('reinforcement') + ' ' + formats.pct1(armour.thermal.reinforcement)}
);
+ const armourDamageTakenKineticTt = [];
+ armourDamageTakenKineticTt.push({translate('bulkheads') + ' ' + formats.pct1(armour.kinetic.bulkheads)}
);
+ armourDamageTakenKineticTt.push({translate('reinforcement') + ' ' + formats.pct1(armour.kinetic.reinforcement)}
);
+ if (armour.kinetic.bulkheads * armour.kinetic.reinforcement != 1) effectiveArmourKineticTt.push({translate('resistance') + ' ' + formats.int(rawArmour / (armour.kinetic.bulkheads * armour.kinetic.reinforcement) - rawArmour)}
);
+
+ const armourDamageTakenThermalTt = [];
+ armourDamageTakenThermalTt.push({translate('bulkheads') + ' ' + formats.pct1(armour.thermal.bulkheads)}
);
+ armourDamageTakenThermalTt.push({translate('reinforcement') + ' ' + formats.pct1(armour.thermal.reinforcement)}
);
+ if (armour.thermal.bulkheads * armour.thermal.reinforcement != 1) effectiveArmourThermalTt.push({translate('resistance') + ' ' + formats.int(rawArmour / (armour.thermal.bulkheads * armour.thermal.reinforcement) - rawArmour)}
);
const effectiveArmourData = [];
const effectiveAbsoluteArmour = armour.total / armour.absolute.total;
- effectiveArmourData.push({ value: Math.round(effectiveAbsoluteArmour), label: translate('absolute') });
+ effectiveArmourData.push({ value: Math.round(effectiveAbsoluteArmour), label: translate('absolute'), tooltip: effectiveArmourAbsoluteTt });
const effectiveExplosiveArmour = armour.total / armour.explosive.total;
- effectiveArmourData.push({ value: Math.round(effectiveExplosiveArmour), label: translate('explosive') });
+ effectiveArmourData.push({ value: Math.round(effectiveExplosiveArmour), label: translate('explosive'), tooltip: effectiveArmourExplosiveTt });
const effectiveKineticArmour = armour.total / armour.kinetic.total;
- effectiveArmourData.push({ value: Math.round(effectiveKineticArmour), label: translate('kinetic') });
+ effectiveArmourData.push({ value: Math.round(effectiveKineticArmour), label: translate('kinetic'), tooltip: effectiveArmourKineticTt });
const effectiveThermalArmour = armour.total / armour.thermal.total;
- effectiveArmourData.push({ value: Math.round(effectiveThermalArmour), label: translate('thermal') });
+ effectiveArmourData.push({ value: Math.round(effectiveThermalArmour), label: translate('thermal'), tooltip: effectiveArmourThermalTt });
const armourDamageTakenData = [];
- armourDamageTakenData.push({ value: Math.round(armour.absolute.total * 100), label: translate('absolute'), tooltip: armourAbsoluteTooltipDetails });
- armourDamageTakenData.push({ value: Math.round(armour.explosive.total * 100), label: translate('explosive'), tooltip: armourExplosiveTooltipDetails });
- armourDamageTakenData.push({ value: Math.round(armour.kinetic.total * 100), label: translate('kinetic'), tooltip: armourKineticTooltipDetails });
- armourDamageTakenData.push({ value: Math.round(armour.thermal.total * 100), label: translate('thermal'), tooltip: armourThermalTooltipDetails });
+ armourDamageTakenData.push({ value: Math.round(armour.absolute.total * 100), label: translate('absolute'), tooltip: armourDamageTakenTt });
+ armourDamageTakenData.push({ value: Math.round(armour.explosive.total * 100), label: translate('explosive'), tooltip: armourDamageTakenExplosiveTt });
+ armourDamageTakenData.push({ value: Math.round(armour.kinetic.total * 100), label: translate('kinetic'), tooltip: armourDamageTakenKineticTt });
+ armourDamageTakenData.push({ value: Math.round(armour.thermal.total * 100), label: translate('thermal'), tooltip: armourDamageTakenThermalTt });
return (
@@ -155,7 +216,7 @@ export default class Defence extends TranslatedComponent {
{translate('shield metrics')}
- {shieldTooltipDetails} )} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw shield strength')} {formats.int(shield.total)}{units.MJ}
+ {shieldSourcesTt})} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw shield strength')} {formats.int(shield.total)}{units.MJ}
{translate('PHRASE_TIME_TO_LOSE_SHIELDS')} {shielddamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(shield.total, shielddamage.totalsdps, shielddamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * opponentWep / 4))}
{translate('PHRASE_TIME_TO_RECOVER_SHIELDS')} {shield.recover === Math.Inf ? translate('never') : formats.time(shield.recover)}
{translate('PHRASE_TIME_TO_RECHARGE_SHIELDS')} {shield.recharge === Math.Inf ? translate('never') : formats.time(shield.recharge)}
@@ -176,7 +237,7 @@ export default class Defence extends TranslatedComponent {
{translate('armour metrics')}
- {armourTooltipDetails} )} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw armour strength')} {formats.int(armour.total)}
+ {armourSourcesTt})} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw armour strength')} {formats.int(armour.total)}
{translate('PHRASE_TIME_TO_LOSE_ARMOUR')} {armourdamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(armour.total, armourdamage.totalsdps, armourdamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * opponentWep / 4))}
{translate('raw module armour')} {formats.int(armour.modulearmour)}
{translate('PHRASE_MODULE_PROTECTION_EXTERNAL')} {formats.pct1(armour.moduleprotection / 2)}
From 2b1402d099259484dedc7c5b9138570e9739d908 Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Sun, 9 Apr 2017 09:23:20 +0100
Subject: [PATCH 86/87] Bump for 2.3
---
package.json | 2 +-
src/app/i18n/en.js | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/package.json b/package.json
index e018f952..c74d4062 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "coriolis_shipyard",
- "version": "2.3.0b",
+ "version": "2.3.0",
"repository": {
"type": "git",
"url": "https://github.com/EDCD/coriolis"
diff --git a/src/app/i18n/en.js b/src/app/i18n/en.js
index 6a425f6d..974d6be6 100644
--- a/src/app/i18n/en.js
+++ b/src/app/i18n/en.js
@@ -314,7 +314,7 @@ Along the top of the screen are some of the key values for your build. This is
Here, along with most places in Coriolis, acronyms will have tooltips explaining what they mean. Hover over the acronym to obtain more detail, or look in the glossary at the end of this help.
-All values are the highest possible, assuming that you an optimal setup for that particular value (maximum pips in ENG for speed, minimum fuel for jump range, etc.). Details of the specific setup for each value are listed in the associated tootip.
+All values are the highest possible, assuming that you an optimal setup for that particular value (maximum pips in ENG for speed, minimum fuel for jump range, etc.). This means that these values will not be affected by changes to pip settings. Details of the specific setup for each value are listed in the associated tootip.
Modules
The next set of panels laid out horizontally across the screen contain the modules you have put in your build. From left to right these are the core modules, the internal modules, the hardpoints and the utility mounts. These represent the available slots in your ship and cannot be altered. Each slot has a class, or size, and in general any module up to a given size can fit in a given slot (exceptions being bulkheads, life support and sensors in core modules and restricted internal slots, which can only take a subset of module depending on their restrictions).
From f48af272c31852cb0c52f264e37049f154df1afb Mon Sep 17 00:00:00 2001
From: Cmdr McDonald
Date: Sun, 9 Apr 2017 09:23:31 +0100
Subject: [PATCH 87/87] Bump for 2.3
---
ChangeLog.md | 19 +++++++++++++++++++
1 file changed, 19 insertions(+)
diff --git a/ChangeLog.md b/ChangeLog.md
index 0cb5e994..f357241d 100644
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -23,6 +23,25 @@
* Defence section provides details of your build's defences against your selected opponent. It provides details of the effectiveness of your resistances of both shields and armour, and effective strength of each as a result. It also provides key metrics around shield longevity and recovery times, as well as module protection
* Fix power band marker to show safe power limit at 40% rather than 50%
* Restyle blueprint list to improve consistency with similar menus
+ * Use coriolis-data 2.3.0:
+ * Add Dolphin
+ * Add turreted mining lasers
+ * Add long range / wide angle / fast scan scanner blueprints
+ * Fix EDDB IDs for class 5 and 7 fighter hangars for correct shopping list
+ * Fix cost for rocket-propelled FSD disruptor
+ * Add module names for blueprints
+ * Fix erroneous value for grade 5 kinetic shield booster
+ * Add missing integrity values for some modules
+ * Update module reinforcement package integrity
+ * Update specs of Beluga as per 2.3
+ * Update specs of Asp Scout as per 2.3
+ * Update specs of Diamondback Explorer as per 2.3
+ * Add ED ID for Rocket Propelled FSD Disruptor
+ * Fix ED name for target lock breaker special
+ * Update scan range and angle information for sensors
+ * Tidy up shield cell bank information to allow for accurate calculations with modifications
+ * Update mine launcher stats
+ * Add appropriate engineers to per-module blueprint information
#2.2.19
* Power management panel now displays modules in descending order of power usage by default