mirror of
https://github.com/EDCD/coriolis.git
synced 2025-12-09 14:45:35 +00:00
Compare commits
272 Commits
feature/ne
...
feature/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59b6945fee | ||
|
|
4613e7ca7a | ||
|
|
3315570edf | ||
|
|
1fd545ba1f | ||
|
|
25839de1fe | ||
|
|
15feff9344 | ||
|
|
a599b1a076 | ||
|
|
3a6ac818c2 | ||
|
|
6f077d4c41 | ||
|
|
9c767c928c | ||
|
|
515f4ad3da | ||
|
|
4fcf074595 | ||
|
|
e5f8153a34 | ||
|
|
571854a11c | ||
|
|
1f22f249a1 | ||
|
|
718ac0a514 | ||
|
|
8f089cb1ee | ||
|
|
d19a7276dd | ||
|
|
10fffe67fc | ||
|
|
f0bf8e8ce2 | ||
|
|
598cf8d677 | ||
|
|
90f03de3fe | ||
|
|
e0766f4424 | ||
|
|
28a90768e4 | ||
|
|
f3d917ccbe | ||
|
|
7e5d52385d | ||
|
|
4368015dc0 | ||
|
|
1201da1811 | ||
|
|
d195b568b0 | ||
|
|
c9866c146b | ||
|
|
5d52809d0d | ||
|
|
8f0cca4fd9 | ||
|
|
e46bb425fe | ||
|
|
06dc110025 | ||
|
|
e9c34c636a | ||
|
|
59d38cbd33 | ||
|
|
51f5188efc | ||
|
|
be8934da80 | ||
|
|
18d78b3089 | ||
|
|
b1ff4e84f7 | ||
|
|
bed2ede701 | ||
|
|
124bd62d2c | ||
|
|
975846f4ab | ||
|
|
3f73f9be10 | ||
|
|
d218101708 | ||
|
|
608ce12156 | ||
|
|
80d653483a | ||
|
|
f9a1e5afd9 | ||
|
|
7f5181a9c7 | ||
|
|
93d8f15f26 | ||
|
|
06d8930777 | ||
|
|
23f4024e09 | ||
|
|
a5e9a71037 | ||
|
|
423d42bc8a | ||
|
|
4075bab3d0 | ||
|
|
469f7a0a48 | ||
|
|
25d9f8ec61 | ||
|
|
1151bd1614 | ||
|
|
2456ce330b | ||
|
|
f86ce62c27 | ||
|
|
c295a9f4e4 | ||
|
|
c4186faa4a | ||
|
|
00f4889c93 | ||
|
|
b6c83f4aba | ||
|
|
3fa040c210 | ||
|
|
8c4957c21e | ||
|
|
a178c59aa3 | ||
|
|
0d9fcd97d7 | ||
|
|
b02ca117be | ||
|
|
ca2e2a22dd | ||
|
|
035e217124 | ||
|
|
6424ab283a | ||
|
|
f30f10c904 | ||
|
|
8ce09a607f | ||
|
|
5b48e30798 | ||
|
|
0d7e6af2e6 | ||
|
|
be3ca23aee | ||
|
|
28f464ba5a | ||
|
|
8cedd1b8bc | ||
|
|
56cbc49d04 | ||
|
|
5d1ccd2a20 | ||
|
|
f898fbc55e | ||
|
|
63acad3aeb | ||
|
|
5b4b0b8dff | ||
|
|
8492dd74f9 | ||
|
|
77e652cd34 | ||
|
|
5069d7e464 | ||
|
|
8b3e9c0f63 | ||
|
|
99e76e480b | ||
|
|
c161ed2298 | ||
|
|
464770f096 | ||
|
|
9fba1be814 | ||
|
|
b419b8b104 | ||
|
|
0cae955ca2 | ||
|
|
ec70ad5d29 | ||
|
|
8a386c4ece | ||
|
|
013460ada4 | ||
|
|
d46ad89dc5 | ||
|
|
86c53c8e46 | ||
|
|
df14786e79 | ||
|
|
24f206ad82 | ||
|
|
242c3efe45 | ||
|
|
07a848b906 | ||
|
|
1b5402fd2d | ||
|
|
903d791549 | ||
|
|
f03c164f1c | ||
|
|
107bab0192 | ||
|
|
41f5ebb2f1 | ||
|
|
266090dc2a | ||
|
|
83f29f3d62 | ||
|
|
3e6137ca96 | ||
|
|
58510bbd22 | ||
|
|
2f7be75bcf | ||
|
|
0aecbbf892 | ||
|
|
9ef2f4179b | ||
|
|
afbfe3ea12 | ||
|
|
b145a2da85 | ||
|
|
a679f7dd98 | ||
|
|
f343366ea1 | ||
|
|
1bbea7dda0 | ||
|
|
da097e0955 | ||
|
|
1307474755 | ||
|
|
80f5a95297 | ||
|
|
e619966679 | ||
|
|
3f94382925 | ||
|
|
ea7a6f8872 | ||
|
|
4e87b3a0b8 | ||
|
|
8463dd46f7 | ||
|
|
3febe465f6 | ||
|
|
45b834c424 | ||
|
|
1ca041097b | ||
|
|
587f7acd5b | ||
|
|
9e64e4a26b | ||
|
|
96e09ab36c | ||
|
|
4268d3f07b | ||
|
|
1767a2aed5 | ||
|
|
9f061506bb | ||
|
|
0eb9a2048d | ||
|
|
faeafb2402 | ||
|
|
2a0acfa6cb | ||
|
|
0ac44ac267 | ||
|
|
b9ae3a4d5a | ||
|
|
b1aefb0003 | ||
|
|
f6a41ec55c | ||
|
|
39650cc584 | ||
|
|
d837287da7 | ||
|
|
6e718a39d1 | ||
|
|
2e7db02238 | ||
|
|
dbfd108819 | ||
|
|
82c0f6fc0f | ||
|
|
9e6bbaa67d | ||
|
|
012a203c4a | ||
|
|
c7ba129ed7 | ||
|
|
fbe57d00db | ||
|
|
dc4fdf215c | ||
|
|
2166ac1584 | ||
|
|
504756de09 | ||
|
|
90f04dba94 | ||
|
|
bfd7645fb7 | ||
|
|
c37c6983c3 | ||
|
|
736c700aa0 | ||
|
|
de1cb901fc | ||
|
|
3d9d6397f6 | ||
|
|
dbe836729f | ||
|
|
82fa93e676 | ||
|
|
90a3392b80 | ||
|
|
b7f715bd5e | ||
|
|
5f88f46770 | ||
|
|
2244c91a64 | ||
|
|
78134404c3 | ||
|
|
c9cd6b175d | ||
|
|
5f70d283e0 | ||
|
|
cd1e27fd11 | ||
|
|
0fd1c19514 | ||
|
|
a82dffd77d | ||
|
|
d3eed87077 | ||
|
|
d99f8ad7e7 | ||
|
|
e933305407 | ||
|
|
690bc5a64a | ||
|
|
d2380a5c9c | ||
|
|
c2d0a6e9e0 | ||
|
|
a8c44fddca | ||
|
|
0e6b306bdc | ||
|
|
9b2a47ba0c | ||
|
|
3038ebc1c7 | ||
|
|
5a48d5b400 | ||
|
|
f36edfe98d | ||
|
|
3e80d6e13b | ||
|
|
c8f0a1ccc6 | ||
|
|
79325a3129 | ||
|
|
1ca0fffe5e | ||
|
|
f5b9842b2e | ||
|
|
72ea3f125e | ||
|
|
f7a2408e44 | ||
|
|
c05de9a085 | ||
|
|
fc657f3b2d | ||
|
|
e388de0364 | ||
|
|
26e55afb29 | ||
|
|
9b8e1039f6 | ||
|
|
082d14ba50 | ||
|
|
87ead595c6 | ||
|
|
fb073acdc3 | ||
|
|
3dda4e24bf | ||
|
|
c966bd08ed | ||
|
|
692c73a6d0 | ||
|
|
849b484b4d | ||
|
|
9489baccda | ||
|
|
62cac15222 | ||
|
|
0ee9ce2958 | ||
|
|
a83007fc6c | ||
|
|
901f266dad | ||
|
|
6009426f04 | ||
|
|
6fb89df4cd | ||
|
|
48b99425a3 | ||
|
|
9569f9b09b | ||
|
|
7421896902 | ||
|
|
b837c8d66c | ||
|
|
e180f96eab | ||
|
|
de1f1f790e | ||
|
|
fefa74ce0f | ||
|
|
a2ceb70aa4 | ||
|
|
634103f144 | ||
|
|
f3379de81e | ||
|
|
064ee74c1a | ||
|
|
0e0c15fe3b | ||
|
|
3c855c608d | ||
|
|
414516289c | ||
|
|
d79fa71337 | ||
|
|
39c2ca94c8 | ||
|
|
acf1c5ce04 | ||
|
|
fd7923155f | ||
|
|
2aa6a6daa2 | ||
|
|
9662621980 | ||
|
|
37d007d9ab | ||
|
|
a4e48c359a | ||
|
|
8ef0101a6e | ||
|
|
e21f109026 | ||
|
|
737837eebd | ||
|
|
ba09b54409 | ||
|
|
417091c648 | ||
|
|
5a02368298 | ||
|
|
da7c739497 | ||
|
|
0842281466 | ||
|
|
b480f879b1 | ||
|
|
f8a215d790 | ||
|
|
da69f3b2c8 | ||
|
|
fc442c1a42 | ||
|
|
cf59a6b9fd | ||
|
|
34afcd511a | ||
|
|
e2444a2e4e | ||
|
|
692516de9b | ||
|
|
2f121bef5e | ||
|
|
9e253012e6 | ||
|
|
6ef3b227b8 | ||
|
|
c3dd1886c9 | ||
|
|
69bb90a0e4 | ||
|
|
6f02965149 | ||
|
|
b8e15f691d | ||
|
|
8797d84605 | ||
|
|
1d544099f6 | ||
|
|
49a6c5f2c4 | ||
|
|
298eaa8b4b | ||
|
|
7b87038a8c | ||
|
|
fdc9171c69 | ||
|
|
f43bd100e6 | ||
|
|
fbba0e3ea5 | ||
|
|
b15695128f | ||
|
|
0870b90443 | ||
|
|
48ccab152b | ||
|
|
f3bc900f16 | ||
|
|
ec148847a9 | ||
|
|
0474af912a |
33
.babelrc
33
.babelrc
@@ -1,3 +1,34 @@
|
||||
{
|
||||
"presets": ["env", "react", "stage-0"]
|
||||
"presets": [
|
||||
["@babel/preset-env", {"modules": "commonjs"}],
|
||||
"@babel/preset-react"
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-syntax-dynamic-import",
|
||||
"@babel/plugin-syntax-import-meta",
|
||||
["@babel/plugin-proposal-class-properties", { "loose": true }],
|
||||
"@babel/plugin-proposal-json-strings",
|
||||
[
|
||||
"@babel/plugin-proposal-decorators",
|
||||
{
|
||||
"legacy": true
|
||||
}
|
||||
],
|
||||
"@babel/plugin-proposal-function-sent",
|
||||
"@babel/plugin-proposal-export-namespace-from",
|
||||
"@babel/plugin-proposal-numeric-separator",
|
||||
"@babel/plugin-proposal-throw-expressions",
|
||||
"@babel/plugin-proposal-export-default-from",
|
||||
"@babel/plugin-proposal-logical-assignment-operators",
|
||||
"@babel/plugin-proposal-optional-chaining",
|
||||
[
|
||||
"@babel/plugin-proposal-pipeline-operator",
|
||||
{
|
||||
"proposal": "minimal"
|
||||
}
|
||||
],
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator",
|
||||
"@babel/plugin-proposal-do-expressions",
|
||||
"@babel/plugin-proposal-function-bind"
|
||||
]
|
||||
}
|
||||
|
||||
2
.docker/.dockerignore
Normal file
2
.docker/.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
npm-debug.log
|
||||
35
.docker/Dockerfile
Normal file
35
.docker/Dockerfile
Normal file
@@ -0,0 +1,35 @@
|
||||
### STAGE 1: Build ###
|
||||
FROM node:9.11.1-alpine as builder
|
||||
ARG branch=develop
|
||||
ENV BRANCH=$branch
|
||||
WORKDIR /src/app
|
||||
RUN mkdir -p /src/app/coriolis
|
||||
RUN mkdir -p /src/app/coriolis-data
|
||||
|
||||
COPY ./coriolis/ /src/app/coriolis
|
||||
COPY ./coriolis-data/ /src/app/coriolis-data
|
||||
|
||||
RUN apk update
|
||||
RUN apk add git
|
||||
|
||||
RUN npm i -g npm
|
||||
|
||||
# Set up coriolis-data
|
||||
WORKDIR /src/app/coriolis-data
|
||||
RUN git fetch --all
|
||||
RUN npm install --no-package-lock
|
||||
RUN npm start
|
||||
|
||||
WORKDIR /src/app/coriolis
|
||||
RUN git fetch --all
|
||||
RUN npm install --no-package-lock
|
||||
RUN npm run build
|
||||
|
||||
|
||||
### STAGE 2: Production Environment ###
|
||||
FROM nginx:1.13.12-alpine as web
|
||||
COPY coriolis/.docker/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY --from=builder /src/app/coriolis/build /usr/share/nginx/html
|
||||
WORKDIR /usr/share/nginx/html
|
||||
EXPOSE 80
|
||||
CMD ["nginx", "-c", "/etc/nginx/nginx.conf", "-g", "daemon off;"]
|
||||
34
.docker/docker-compose.yml
Normal file
34
.docker/docker-compose.yml
Normal file
@@ -0,0 +1,34 @@
|
||||
version: '2.2'
|
||||
|
||||
services:
|
||||
coriolis_prod:
|
||||
image: edcd/coriolis:master
|
||||
restart: always
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||
networks:
|
||||
- web
|
||||
labels:
|
||||
- "traefik.docker.network=web"
|
||||
- "traefik.enable=true"
|
||||
- "traefik.basic.frontend.rule=Host:coriolis.io,coriolis.edcd.io"
|
||||
- "traefik.basic.port=80"
|
||||
- "traefik.basic.protocol=http"
|
||||
|
||||
coriolis_dev:
|
||||
image: edcd/coriolis:develop
|
||||
restart: always
|
||||
volumes:
|
||||
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||
networks:
|
||||
- web
|
||||
labels:
|
||||
- "traefik.docker.network=web"
|
||||
- "traefik.enable=true"
|
||||
- "traefik.basic.frontend.rule=Host:beta.coriolis.io,beta.coriolis.edcd.io"
|
||||
- "traefik.basic.port=80"
|
||||
- "traefik.basic.protocol=http"
|
||||
|
||||
networks:
|
||||
web:
|
||||
external: true
|
||||
45
.docker/nginx.conf
Normal file
45
.docker/nginx.conf
Normal file
@@ -0,0 +1,45 @@
|
||||
worker_processes 1;
|
||||
user nobody nobody;
|
||||
error_log /tmp/error.log;
|
||||
pid /tmp/nginx.pid;
|
||||
|
||||
events {
|
||||
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
sendfile on;
|
||||
client_body_temp_path /tmp/client_body;
|
||||
fastcgi_temp_path /tmp/fastcgi_temp;
|
||||
proxy_temp_path /tmp/proxy_temp;
|
||||
scgi_temp_path /tmp/scgi_temp;
|
||||
uwsgi_temp_path /tmp/uwsgi_temp;
|
||||
access_log /tmp/access.log;
|
||||
error_log /tmp/error.log;
|
||||
|
||||
keepalive_timeout 3000;
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
index index.html;
|
||||
server_name localhost;
|
||||
root /usr/share/nginx/html;
|
||||
autoindex on;
|
||||
|
||||
location ~* \.(?:manifest|appcache|html?|xml|json|css|js|map|jpg|jpeg|gif|png|ico|svg|eot|ttf|woff|woff2)$ {
|
||||
expires -1;
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Access-Control-Allow-Credentials true;
|
||||
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
|
||||
add_header Access-Control-Allow-Headers 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
|
||||
access_log off;
|
||||
}
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html =404;
|
||||
}
|
||||
}
|
||||
}
|
||||
103
.esdoc.json
Normal file
103
.esdoc.json
Normal file
@@ -0,0 +1,103 @@
|
||||
{
|
||||
"source": "./src/app",
|
||||
"includes": ["\\.js$", "\\.jsx$"],
|
||||
"destination": "./docs",
|
||||
"index": "./README.md",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "esdoc-standard-plugin",
|
||||
"option": {
|
||||
"lint": {
|
||||
"enable": false
|
||||
},
|
||||
"coverage": {
|
||||
"enable": false
|
||||
},
|
||||
"accessor": {
|
||||
"access": [
|
||||
"public",
|
||||
"protected",
|
||||
"private"
|
||||
],
|
||||
"autoPrivate": true
|
||||
},
|
||||
"undocumentIdentifier": {
|
||||
"enable": true
|
||||
},
|
||||
"unexportedIdentifier": {
|
||||
"enable": false
|
||||
},
|
||||
"typeInference": {
|
||||
"enable": true
|
||||
},
|
||||
"brand": {
|
||||
"logo": "./src/images/logo/192x192.png",
|
||||
"title": "Coriolis",
|
||||
"description": "Coriolis Shipyard for Elite Dangerous",
|
||||
"repository": "https://github.com/EDCD/coriolis",
|
||||
"site": "https://coriolis.edcd.io",
|
||||
"author": "https://github.com/edcd",
|
||||
"image": "./src/images/logo/192x192.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "esdoc-ecmascript-proposal-plugin",
|
||||
"option": {
|
||||
"all": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "esdoc-react-plugin"
|
||||
},
|
||||
{
|
||||
"name": "esdoc-standard-plugin",
|
||||
"option": {
|
||||
"lint": {
|
||||
"enable": false
|
||||
},
|
||||
"coverage": {
|
||||
"enable": false
|
||||
},
|
||||
"accessor": {
|
||||
"access": [
|
||||
"public",
|
||||
"protected",
|
||||
"private"
|
||||
],
|
||||
"autoPrivate": true
|
||||
},
|
||||
"undocumentIdentifier": {
|
||||
"enable": true
|
||||
},
|
||||
"unexportedIdentifier": {
|
||||
"enable": false
|
||||
},
|
||||
"typeInference": {
|
||||
"enable": true
|
||||
},
|
||||
"brand": {
|
||||
"logo": "./src/images/logo/192x192.png",
|
||||
"title": "Coriolis",
|
||||
"description": "Coriolis Shipyard for Elite Dangerous",
|
||||
"repository": "https://github.com/EDCD/coriolis",
|
||||
"site": "https://coriolis.edcd.io",
|
||||
"author": "https://github.com/edcd",
|
||||
"image": "./src/images/logo/192x192.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "esdoc-jsx-plugin",
|
||||
"option": {
|
||||
"enable": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "esdoc-publish-html-plugin",
|
||||
"option": {
|
||||
"template": "./node_modules/esdoc-custom-theme/template"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -9,7 +9,8 @@
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"plugins": [
|
||||
"react"
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -8,3 +8,6 @@ nginx.pid
|
||||
env
|
||||
*.swp
|
||||
.project
|
||||
.vscode/
|
||||
docs/
|
||||
package-lock.json
|
||||
|
||||
@@ -23,6 +23,7 @@ Chat to us on [Discord](https://discord.gg/0uwCh6R62aPRjk9w)!
|
||||
|
||||
See the [Developer's Guide](https://github.com/EDCD/coriolis/wiki/Developing-for-Coriolis) in the wiki.
|
||||
|
||||
Also see [the documentation site.](https://coriolis.willb.info/)
|
||||
|
||||
### Ship and Module Database
|
||||
|
||||
|
||||
@@ -275,11 +275,11 @@
|
||||
"totalCost": 882362060,
|
||||
"unladenMass": 1179.2,
|
||||
"totalDps": 29,
|
||||
"passengerCapacity": 0,
|
||||
"powerAvailable": 36,
|
||||
"powerRetracted": 23.33,
|
||||
"powerDeployed": 34.76,
|
||||
"unladenRange": 18.49,
|
||||
"fullTankRange": 18.12,
|
||||
"ladenRange": 16.39,
|
||||
"unladenFastestRange": 73.21,
|
||||
"ladenFastestRange": 66.15,
|
||||
|
||||
@@ -287,6 +287,7 @@
|
||||
"totalThermSDps": 58.64,
|
||||
"baseShieldStrength": 350,
|
||||
"baseArmour": 945,
|
||||
"hullCausRes": 0,
|
||||
"hullExplRes": 0.22,
|
||||
"hullKinRes": 0.27,
|
||||
"hullMass": 400,
|
||||
@@ -303,13 +304,13 @@
|
||||
"armour": 2227.5,
|
||||
"baseArmour": 525,
|
||||
"unladenMass": 1163.2,
|
||||
"passengerCapacity": 0,
|
||||
"powerAvailable": 39.6,
|
||||
"powerRetracted": 23.33,
|
||||
"powerDeployed": 34.13,
|
||||
"roll": 60,
|
||||
"unladenRange": 18.74,
|
||||
"yaw": 10,
|
||||
"fullTankRange": 18.36,
|
||||
"hardness": 65,
|
||||
"ladenRange": 16.59,
|
||||
"unladenFastestRange": 74.2,
|
||||
|
||||
@@ -237,14 +237,15 @@
|
||||
"shieldExplRes": 0.5,
|
||||
"shieldKinRes": 0.6,
|
||||
"shieldThermRes": 1.2,
|
||||
"hullCausRes": 0,
|
||||
"hullExplRes": 1.4,
|
||||
"hullKinRes": 1.2,
|
||||
"hullThermRes": 1,
|
||||
"passengerCapacity": 0,
|
||||
"powerAvailable": 20.41,
|
||||
"powerRetracted": 11.91,
|
||||
"powerDeployed": 11.91,
|
||||
"unladenRange": 50.45,
|
||||
"fullTankRange": 47.03,
|
||||
"ladenRange": 42.71,
|
||||
"unladenFastestRange": 317.24,
|
||||
"ladenFastestRange": 287.02,
|
||||
|
||||
@@ -309,14 +309,15 @@
|
||||
"shieldExplRes": 0.48,
|
||||
"shieldKinRes": 0.55,
|
||||
"shieldThermRes": 1.09,
|
||||
"hullCausRes": 0,
|
||||
"hullExplRes": 1.4,
|
||||
"hullKinRes": 1.2,
|
||||
"hullThermRes": 1,
|
||||
"passengerCapacity": 0,
|
||||
"powerAvailable": 17.24,
|
||||
"powerRetracted": 11.3,
|
||||
"powerDeployed": 16.41,
|
||||
"unladenRange": 25.57,
|
||||
"fullTankRange": 23.92,
|
||||
"ladenRange": 22.09,
|
||||
"unladenFastestRange": 116.23,
|
||||
"ladenFastestRange": 107,
|
||||
|
||||
@@ -115,7 +115,6 @@
|
||||
"powerRetracted": 7.21,
|
||||
"powerDeployed": 7.21,
|
||||
"unladenRange": 32.48,
|
||||
"fullTankRange": 30.27,
|
||||
"ladenRange": 19.61,
|
||||
"unladenFastestRange": 176.71,
|
||||
"ladenFastestRange": 112.92,
|
||||
@@ -249,7 +248,6 @@
|
||||
"powerRetracted": 9.33,
|
||||
"powerDeployed": 10.33,
|
||||
"unladenRange": 30.24,
|
||||
"fullTankRange": 28.32,
|
||||
"ladenRange": 19.8,
|
||||
"unladenFastestRange": 164.89,
|
||||
"ladenFastestRange": 114.03,
|
||||
@@ -379,7 +377,6 @@
|
||||
"powerRetracted": 7.28,
|
||||
"powerDeployed": 8.06,
|
||||
"unladenRange": 31.71,
|
||||
"fullTankRange": 29.61,
|
||||
"ladenRange": 23.58,
|
||||
"unladenFastestRange": 172.68,
|
||||
"ladenFastestRange": 136.46,
|
||||
@@ -511,7 +508,6 @@
|
||||
"powerRetracted": 8.81,
|
||||
"powerDeployed": 8.81,
|
||||
"unladenRange": 26.41,
|
||||
"fullTankRange": 24.97,
|
||||
"ladenRange": 17.36,
|
||||
"unladenFastestRange": 172.04,
|
||||
"ladenFastestRange": 118.55,
|
||||
@@ -653,7 +649,6 @@
|
||||
"powerRetracted": 10.66,
|
||||
"powerDeployed": 11.66,
|
||||
"unladenRange": 25.77,
|
||||
"fullTankRange": 24.39,
|
||||
"ladenRange": 17.98,
|
||||
"unladenFastestRange": 167.93,
|
||||
"ladenFastestRange": 122.84,
|
||||
@@ -791,7 +786,6 @@
|
||||
"powerRetracted": 8.95,
|
||||
"powerDeployed": 9.73,
|
||||
"unladenRange": 19.27,
|
||||
"fullTankRange": 18.95,
|
||||
"ladenRange": 15.43,
|
||||
"unladenFastestRange": 67.34,
|
||||
"ladenFastestRange": 54.75,
|
||||
@@ -954,7 +948,6 @@
|
||||
"powerRetracted": 15.49,
|
||||
"powerDeployed": 19.43,
|
||||
"unladenRange": 27.1,
|
||||
"fullTankRange": 25.58,
|
||||
"ladenRange": 21.94,
|
||||
"unladenFastestRange": 176.39,
|
||||
"ladenFastestRange": 150.58,
|
||||
@@ -1096,7 +1089,6 @@
|
||||
"powerRetracted": 10.38,
|
||||
"powerDeployed": 12.58,
|
||||
"unladenRange": 26.01,
|
||||
"fullTankRange": 25.42,
|
||||
"ladenRange": 17.19,
|
||||
"unladenFastestRange": 90.67,
|
||||
"ladenFastestRange": 61.04,
|
||||
@@ -1260,7 +1252,6 @@
|
||||
"powerRetracted": 19.35,
|
||||
"powerDeployed": 25.31,
|
||||
"unladenRange": 15.19,
|
||||
"fullTankRange": 14.99,
|
||||
"ladenRange": 14.99,
|
||||
"unladenFastestRange": 53.15,
|
||||
"ladenFastestRange": 53.15,
|
||||
@@ -1360,7 +1351,6 @@
|
||||
"powerRetracted": 10.7,
|
||||
"powerDeployed": 10.7,
|
||||
"unladenRange": 24.25,
|
||||
"fullTankRange": 23.73,
|
||||
"ladenRange": 23.73,
|
||||
"unladenFastestRange": 84.56,
|
||||
"ladenFastestRange": 84.56,
|
||||
@@ -1499,7 +1489,6 @@
|
||||
"powerRetracted": 13.13,
|
||||
"powerDeployed": 13.13,
|
||||
"unladenRange": 19.51,
|
||||
"fullTankRange": 18.58,
|
||||
"ladenRange": 13.09,
|
||||
"unladenFastestRange": 152.32,
|
||||
"ladenFastestRange": 106.49,
|
||||
@@ -1637,7 +1626,6 @@
|
||||
"powerRetracted": 10.25,
|
||||
"powerDeployed": 10.25,
|
||||
"unladenRange": 28.25,
|
||||
"fullTankRange": 26.6,
|
||||
"ladenRange": 16.67,
|
||||
"unladenFastestRange": 183.67,
|
||||
"ladenFastestRange": 113.69,
|
||||
@@ -1808,7 +1796,6 @@
|
||||
"powerRetracted": 19.34,
|
||||
"powerDeployed": 26.18,
|
||||
"unladenRange": 20.32,
|
||||
"fullTankRange": 19.46,
|
||||
"ladenRange": 14.65,
|
||||
"unladenFastestRange": 133.17,
|
||||
"ladenFastestRange": 99.65,
|
||||
@@ -1987,7 +1974,6 @@
|
||||
"powerRetracted": 22.61,
|
||||
"powerDeployed": 29.63,
|
||||
"unladenRange": 14.32,
|
||||
"fullTankRange": 13.89,
|
||||
"ladenRange": 13.46,
|
||||
"unladenFastestRange": 94.42,
|
||||
"ladenFastestRange": 91.49,
|
||||
@@ -2217,7 +2203,6 @@
|
||||
"powerRetracted": 25.88,
|
||||
"powerDeployed": 37.51,
|
||||
"unladenRange": 16.99,
|
||||
"fullTankRange": 16.68,
|
||||
"ladenRange": 15.91,
|
||||
"unladenFastestRange": 67.35,
|
||||
"ladenFastestRange": 64.2,
|
||||
@@ -2372,7 +2357,6 @@
|
||||
"powerRetracted": 11.92,
|
||||
"powerDeployed": 11.92,
|
||||
"unladenRange": 39.26,
|
||||
"fullTankRange": 37.65,
|
||||
"ladenRange": 21.21,
|
||||
"unladenFastestRange": 153.79,
|
||||
"ladenFastestRange": 85.82,
|
||||
@@ -2528,7 +2512,6 @@
|
||||
"powerRetracted": 12.49,
|
||||
"powerDeployed": 12.49,
|
||||
"unladenRange": 38.44,
|
||||
"fullTankRange": 36.89,
|
||||
"ladenRange": 21.04,
|
||||
"unladenFastestRange": 150.62,
|
||||
"ladenFastestRange": 85.16,
|
||||
@@ -2696,7 +2679,6 @@
|
||||
"powerRetracted": 13,
|
||||
"powerDeployed": 12.4,
|
||||
"unladenRange": 38.04,
|
||||
"fullTankRange": 30.11,
|
||||
"ladenRange": 23.03,
|
||||
"unladenFastestRange": 675.7,
|
||||
"ladenFastestRange": 501.97,
|
||||
@@ -2914,7 +2896,6 @@
|
||||
"powerRetracted": 23.93,
|
||||
"powerDeployed": 35.56,
|
||||
"unladenRange": 18.49,
|
||||
"fullTankRange": 18.12,
|
||||
"ladenRange": 16.39,
|
||||
"unladenFastestRange": 73.21,
|
||||
"ladenFastestRange": 66.15,
|
||||
@@ -3042,7 +3023,6 @@
|
||||
"powerRetracted": 8.48,
|
||||
"powerDeployed": 7.88,
|
||||
"unladenRange": 35.99,
|
||||
"fullTankRange": 33.36,
|
||||
"ladenRange": 33.36,
|
||||
"unladenFastestRange": 232.28,
|
||||
"ladenFastestRange": 232.28,
|
||||
@@ -3179,7 +3159,6 @@
|
||||
"powerRetracted": 14.87,
|
||||
"powerDeployed": 17.51,
|
||||
"unladenRange": 15.06,
|
||||
"fullTankRange": 14.86,
|
||||
"ladenRange": 14.86,
|
||||
"unladenFastestRange": 42.5,
|
||||
"ladenFastestRange": 42.5,
|
||||
@@ -3333,7 +3312,6 @@
|
||||
"powerRetracted": 17.71,
|
||||
"powerDeployed": 23.14,
|
||||
"unladenRange": 12.51,
|
||||
"fullTankRange": 12.38,
|
||||
"ladenRange": 12.38,
|
||||
"unladenFastestRange": 35.35,
|
||||
"ladenFastestRange": 35.35,
|
||||
@@ -3452,7 +3430,6 @@
|
||||
"powerRetracted": 9.46,
|
||||
"powerDeployed": 11.17,
|
||||
"unladenRange": 17.12,
|
||||
"fullTankRange": 16.71,
|
||||
"ladenRange": 16.71,
|
||||
"unladenFastestRange": 42.4,
|
||||
"ladenFastestRange": 42.4,
|
||||
@@ -3610,7 +3587,6 @@
|
||||
"powerRetracted": 9.31,
|
||||
"powerDeployed": 13.91,
|
||||
"unladenRange": 8.43,
|
||||
"fullTankRange": 8.09,
|
||||
"ladenRange": 7.25,
|
||||
"unladenFastestRange": 81.5,
|
||||
"ladenFastestRange": 72.9,
|
||||
|
||||
@@ -6,41 +6,70 @@ import TU from 'react-testutils-additions';
|
||||
|
||||
let origAddEventListener = window.addEventListener;
|
||||
let storageListener;
|
||||
let ls = {};
|
||||
|
||||
// Implment mock localStorage
|
||||
let localStorage = {
|
||||
getItem: function(key) {
|
||||
return ls[key];
|
||||
},
|
||||
setItem: function(key, value) {
|
||||
ls[key] = value;
|
||||
},
|
||||
removeItem: function(key) {
|
||||
delete ls[key];
|
||||
},
|
||||
clear: function() {
|
||||
ls = {};
|
||||
/**
|
||||
* Mock local storage
|
||||
*/
|
||||
class LocalStorage {
|
||||
/**
|
||||
* Sets up storage variable
|
||||
*/
|
||||
constructor() {
|
||||
this.ls = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a value from local storage
|
||||
* @param {String} key Storage key
|
||||
* @return {*} Storage value
|
||||
*/
|
||||
getItem(key) {
|
||||
return this.ls[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a value to local storage
|
||||
* @param {String} key Storage key
|
||||
* @param {*} value Storage value
|
||||
*/
|
||||
setItem(key, value) {
|
||||
this.ls[key] = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears a value from local storage
|
||||
* @param {String} key Storage key
|
||||
*/
|
||||
removeItem(key) {
|
||||
delete this.ls[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears local storage
|
||||
*/
|
||||
clear() {
|
||||
this.ls = {};
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener = function(eventName, listener) {
|
||||
const LOCAL_STORAGE = new LocalStorage();
|
||||
|
||||
window.addEventListener = function(eventName, listener) {
|
||||
if(eventName == 'storage') {
|
||||
storageListener = listener; // Keep track of latest storage listener
|
||||
} else {
|
||||
origAddEventListener.apply(arguments);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
describe('Persist', function() {
|
||||
|
||||
const Persist = require('../src/app/stores/Persist').Persist;
|
||||
|
||||
describe('Multi tab/window', function() {
|
||||
it("syncs builds", function() {
|
||||
window.localStorage = localStorage;
|
||||
ls = {};
|
||||
localStorage.clear();
|
||||
|
||||
let p = new Persist();
|
||||
let newBuilds = {
|
||||
anaconda: { test: '1234' }
|
||||
@@ -54,7 +83,8 @@ describe('Persist', function() {
|
||||
describe('General and Settings', function() {
|
||||
it("has defaults", function() {
|
||||
window.localStorage = localStorage;
|
||||
ls = {};
|
||||
localStorage.clear();
|
||||
|
||||
let p = new Persist();
|
||||
expect(p.getLangCode()).toBe('en');
|
||||
expect(p.showTooltips()).toBe(true);
|
||||
@@ -65,14 +95,14 @@ describe('Persist', function() {
|
||||
});
|
||||
|
||||
it("loads from localStorage correctly", function() {
|
||||
window.localStorage = localStorage;
|
||||
window.localStorage = LOCAL_STORAGE;
|
||||
let savedData = require('./fixtures/valid-backup');
|
||||
ls = {};
|
||||
ls.builds = JSON.stringify(savedData.builds);
|
||||
ls.NG_TRANSLATE_LANG_KEY = 'de';
|
||||
ls.insurance = 'Standard';
|
||||
ls.shipDiscount = 0.25;
|
||||
ls.moduleDiscount = 0.15;
|
||||
LOCAL_STORAGE.clear();
|
||||
LOCAL_STORAGE.setItem('builds', JSON.stringify(savedData.builds));
|
||||
LOCAL_STORAGE.setItem('NG_TRANSLATE_LANG_KEY', 'de');
|
||||
LOCAL_STORAGE.setItem('insurance', 'Standard');
|
||||
LOCAL_STORAGE.setItem('shipDiscount', 0.25);
|
||||
LOCAL_STORAGE.setItem('moduleDiscount', 0.15);
|
||||
let p = new Persist();
|
||||
|
||||
expect(p.getInsurance()).toBe('standard');
|
||||
@@ -86,13 +116,13 @@ describe('Persist', function() {
|
||||
});
|
||||
|
||||
it("uses defaults from a corrupted localStorage", function() {
|
||||
window.localStorage = localStorage;
|
||||
ls = {};
|
||||
ls.builds = "not valid json";
|
||||
ls.comparisons = "1, 3, 4";
|
||||
ls.insurance = 'this insurance does not exist';
|
||||
ls.shipDiscount = 'this is not a number';
|
||||
ls.moduleDiscount = 10; // Way to big
|
||||
window.localStorage = LOCAL_STORAGE;
|
||||
LOCAL_STORAGE.clear();
|
||||
LOCAL_STORAGE.setItem('builds', 'not valid json');
|
||||
LOCAL_STORAGE.setItem('comparisons', '1, 3, 4');
|
||||
LOCAL_STORAGE.setItem('insurance', 'this insurance does not exist');
|
||||
LOCAL_STORAGE.setItem('shipDiscount', 'this is not a number');
|
||||
LOCAL_STORAGE.setItem('moduleDiscount', 10); // Way to big
|
||||
|
||||
let p = new Persist();
|
||||
expect(p.getLangCode()).toBe('en');
|
||||
@@ -122,13 +152,13 @@ describe('Persist', function() {
|
||||
});
|
||||
|
||||
it("generates the backup", function() {
|
||||
window.localStorage = localStorage;
|
||||
window.localStorage = LOCAL_STORAGE;
|
||||
let savedData = require('./fixtures/valid-backup');
|
||||
ls = {};
|
||||
ls.builds = JSON.stringify(savedData.builds);
|
||||
ls.insurance = 'Beta';
|
||||
ls.shipDiscount = 0.25;
|
||||
ls.moduleDiscount = 0.15;
|
||||
LOCAL_STORAGE.clear();
|
||||
LOCAL_STORAGE.setItem('builds', JSON.stringify(savedData.builds));
|
||||
LOCAL_STORAGE.setItem('insurance', 'Beta');
|
||||
LOCAL_STORAGE.setItem('shipDiscount', 0.25);
|
||||
LOCAL_STORAGE.setItem('moduleDiscount', 0.15);
|
||||
|
||||
let p = new Persist();
|
||||
let backup = p.getAll();
|
||||
@@ -140,4 +170,4 @@ describe('Persist', function() {
|
||||
expect(backup.comparisons).toEqual({});
|
||||
});
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
@@ -3,32 +3,31 @@ import { Ships } from 'coriolis-data/dist';
|
||||
import * as ModuleUtils from '../src/app/shipyard/ModuleUtils';
|
||||
|
||||
describe("Ship", function() {
|
||||
|
||||
it("can build all ships", function() {
|
||||
for (let s in Ships) {
|
||||
for (let s in Ships) {
|
||||
it("can build " + s, function() {
|
||||
let shipData = Ships[s];
|
||||
let ship = new Ship(s, shipData.properties, shipData.slots);
|
||||
|
||||
for (let p in shipData.properties) {
|
||||
expect(ship[p]).toEqual(shipData.properties[p], s + ' property [' + p + '] does not match when built');
|
||||
expect(ship[p]).toEqual(shipData.properties[p]);
|
||||
}
|
||||
|
||||
ship.buildWith(shipData.defaults);
|
||||
|
||||
expect(ship.totalCost).toEqual(shipData.retailCost, s + ' retail cost does not match default build cost');
|
||||
expect(ship.totalCost).toEqual(shipData.retailCost);
|
||||
expect(ship.cargoCapacity).toBeDefined();
|
||||
expect(ship.priorityBands[0].retracted).toBeGreaterThan(0, s + ' priorityBands');
|
||||
expect(ship.powerAvailable).toBeGreaterThan(0, s + ' powerAvailable');
|
||||
expect(ship.unladenRange).toBeGreaterThan(0, s + ' unladenRange');
|
||||
expect(ship.ladenRange).toBeGreaterThan(0, s + ' ladenRange');
|
||||
expect(ship.fuelCapacity).toBeGreaterThan(0, s + ' fuelCapacity');
|
||||
expect(ship.unladenFastestRange).toBeGreaterThan(0, s + ' unladenFastestRange');
|
||||
expect(ship.ladenFastestRange).toBeGreaterThan(0, s + ' ladenFastestRange');
|
||||
expect(ship.shield).toBeGreaterThan(0, s + ' shield');
|
||||
expect(ship.armour).toBeGreaterThan(0, s + ' armour');
|
||||
expect(ship.topSpeed).toBeGreaterThan(0, s + ' topSpeed');
|
||||
}
|
||||
});
|
||||
expect(ship.priorityBands[0].retracted).toBeGreaterThan(0);
|
||||
expect(ship.powerAvailable).toBeGreaterThan(0);
|
||||
expect(ship.unladenRange).toBeGreaterThan(0);
|
||||
expect(ship.ladenRange).toBeGreaterThan(0);
|
||||
expect(ship.fuelCapacity).toBeGreaterThan(0);
|
||||
expect(ship.unladenFastestRange).toBeGreaterThan(0);
|
||||
expect(ship.ladenFastestRange).toBeGreaterThan(0);
|
||||
expect(ship.shield).toBeGreaterThan(0);
|
||||
expect(ship.armour).toBeGreaterThan(0);
|
||||
expect(ship.topSpeed).toBeGreaterThan(0);
|
||||
});
|
||||
}
|
||||
|
||||
it("resets and rebuilds properly", function() {
|
||||
var id = 'cobra_mk_iii';
|
||||
|
||||
11
d3-funcs.js
vendored
11
d3-funcs.js
vendored
@@ -1,11 +0,0 @@
|
||||
export {
|
||||
axisBottom,
|
||||
axisLeft,
|
||||
axisTop,
|
||||
formatLocale,
|
||||
line,
|
||||
scaleBand,
|
||||
scaleLinear,
|
||||
scaleOrdinal,
|
||||
select
|
||||
} from 'd3';
|
||||
12862
package-lock.json
generated
12862
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
261
package.json
261
package.json
@@ -1,117 +1,144 @@
|
||||
{
|
||||
"name": "coriolis_shipyard",
|
||||
"version": "2.9.16",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/EDCD/coriolis"
|
||||
},
|
||||
"homepage": "https://coriolis.edcd.io",
|
||||
"bugs": "https://github.com/EDCD/coriolis/issues",
|
||||
"private": true,
|
||||
"engine": "node >= 4.8.1",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"prepublish": "rollup -c && uglifyjs d3.js -c -m -o d3.min.js",
|
||||
"extract-translations": "grep -hroE \"(translate\\('[^']+'\\))|(tip.bind\\(null, '[^']+')\" src/* | grep -oE \"'[^']+'\" | grep -oE \"[^']+\" | sort -u -f",
|
||||
"clean": "rimraf build",
|
||||
"start": "node devServer.js",
|
||||
"lint": "eslint --ext .js,.jsx src",
|
||||
"test": "jest",
|
||||
"prod-serve": "nginx -p $(pwd) -c nginx.conf",
|
||||
"prod-stop": "kill -QUIT $(cat nginx.pid)",
|
||||
"build": "npm run clean && cross-env NODE_ENV=production webpack -p --config webpack.config.prod.js",
|
||||
"rsync": "rsync -ae \"ssh -i $CORIOLIS_PEM\" --delete build/ $CORIOLIS_USER@$CORIOLIS_HOST:~/wwws",
|
||||
"deploy": "npm run lint && npm test && npm run build && npm run rsync"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
".*": "<rootDir>/node_modules/babel-jest"
|
||||
},
|
||||
"testRegex": "(/__tests__/test-.*|\\.(test|spec))\\.js$",
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"jsx"
|
||||
],
|
||||
"automock": true,
|
||||
"bail": false,
|
||||
"unmockedModulePathPatterns": [
|
||||
"<rootDir>/node_modules/lodash",
|
||||
"<rootDir>/node_modules/react",
|
||||
"<rootDir>/node_modules/react-dom",
|
||||
"<rootDir>/node_modules/react-transition-group",
|
||||
"<rootDir>/node_modules/react-testutils-additions",
|
||||
"<rootDir>/node_modules/fbjs",
|
||||
"<rootDir>/node_modules/fbemitter",
|
||||
"<rootDir>/node_modules/classnames",
|
||||
"<rootDir>/node_modules/d3",
|
||||
"<rootDir>/node_modules/lz-string",
|
||||
"<rootDir>/node_modules/jsen",
|
||||
"coriolis-data",
|
||||
"<rootDir>/src/app/shipyard",
|
||||
"<rootDir>/src/app/i18n",
|
||||
"<rootDir>/src/app/utils",
|
||||
"<rootDir>/src/schemas",
|
||||
"<rootDir>/__tests__"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"appcache-webpack-plugin": "^1.3.0",
|
||||
"babel-core": "*",
|
||||
"babel-eslint": "*",
|
||||
"babel-jest": "*",
|
||||
"babel-loader": "*",
|
||||
"babel-preset-env": "*",
|
||||
"babel-preset-react": "*",
|
||||
"babel-preset-stage-0": "*",
|
||||
"create-react-class": "^15.6.2",
|
||||
"css-loader": "^0.28.0",
|
||||
"cross-env": "^5.1.4",
|
||||
"d3-selection": "1",
|
||||
"eslint": "3.19.0",
|
||||
"eslint-plugin-react": "^6.10.3",
|
||||
"expose-loader": "^0.7.3",
|
||||
"express": "^4.15.2",
|
||||
"extract-text-webpack-plugin": "2.1.0",
|
||||
"file-loader": "^0.11.1",
|
||||
"html-webpack-plugin": "^2.28.0",
|
||||
"jest-cli": "^21.2.1",
|
||||
"jsen": "^0.6.4",
|
||||
"json-loader": "^0.5.4",
|
||||
"less": "^2.7.2",
|
||||
"less-loader": "^4.0.3",
|
||||
"react-addons-perf": "^15.4.2",
|
||||
"react-measure": "^1.4.7",
|
||||
"react-testutils-additions": "^15.2.0",
|
||||
"react-transition-group": "^1.1.2",
|
||||
"rimraf": "^2.6.1",
|
||||
"rollup": "0.41",
|
||||
"rollup-plugin-node-resolve": "3",
|
||||
"style-loader": "^0.16.1",
|
||||
"uglify-js": "^2.4.11",
|
||||
"url-loader": "^0.5.8",
|
||||
"webpack": "^2.4.1",
|
||||
"webpack-dev-server": "^2.4.4",
|
||||
"webpack-notifier": "^1.6.0",
|
||||
"webpack-bugsnag-plugins": "^1.1.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"babel-polyfill": "*",
|
||||
"browserify-zlib-next": "^1.0.1",
|
||||
"classnames": "^2.2.5",
|
||||
"coriolis-data": "../coriolis-data",
|
||||
"d3": "4.8.0",
|
||||
"detect-browser": "^1.7.0",
|
||||
"fbemitter": "^2.1.1",
|
||||
"lodash": "^4.17.4",
|
||||
"lz-string": "^1.4.4",
|
||||
"pako": "^1.0.6",
|
||||
"prop-types": "^15.5.8",
|
||||
"react": "^15.5.4",
|
||||
"react-dom": "^15.5.4",
|
||||
"react-ga": "^2.5.3",
|
||||
"react-number-editor": "Athanasius/react-number-editor.git#miggy",
|
||||
"recharts": "^0.22.3",
|
||||
"superagent": "^3.5.2"
|
||||
}
|
||||
}
|
||||
{
|
||||
"name": "coriolis_shipyard",
|
||||
"version": "3.0.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/EDCD/coriolis"
|
||||
},
|
||||
"homepage": "https://coriolis.edcd.io",
|
||||
"bugs": "https://github.com/EDCD/coriolis/issues",
|
||||
"private": true,
|
||||
"engine": "node >= 4.8.1",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"extract-translations": "grep -hroE \"(translate\\('[^']+'\\))|(tip.bind\\(null, '[^']+')\" src/* | grep -oE \"'[^']+'\" | grep -oE \"[^']+\" | sort -u -f",
|
||||
"clean": "rimraf build",
|
||||
"start": "node devServer.js",
|
||||
"lint": "eslint --ext .js,.jsx src",
|
||||
"test": "jest",
|
||||
"prod-serve": "nginx -p $(pwd) -c nginx.conf",
|
||||
"prod-stop": "kill -QUIT $(cat nginx.pid)",
|
||||
"build": "npm run clean && cross-env NODE_ENV=production webpack -p --config webpack.config.prod.js",
|
||||
"rsync": "rsync -ae \"ssh -i $CORIOLIS_PEM\" --delete build/ $CORIOLIS_USER@$CORIOLIS_HOST:~/wwws",
|
||||
"deploy": "npm run lint && npm test && npm run build && npm run rsync"
|
||||
},
|
||||
"jest": {
|
||||
"transform": {
|
||||
".*": "<rootDir>/node_modules/babel-jest"
|
||||
},
|
||||
"testRegex": "(/__tests__/test-.*|\\.(test|spec))\\.js$",
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"jsx"
|
||||
],
|
||||
"automock": true,
|
||||
"bail": false,
|
||||
"unmockedModulePathPatterns": [
|
||||
"<rootDir>/node_modules/lodash",
|
||||
"<rootDir>/node_modules/react",
|
||||
"<rootDir>/node_modules/react-dom",
|
||||
"<rootDir>/node_modules/react-transition-group",
|
||||
"<rootDir>/node_modules/react-testutils-additions",
|
||||
"<rootDir>/node_modules/fbjs",
|
||||
"<rootDir>/node_modules/fbemitter",
|
||||
"<rootDir>/node_modules/classnames",
|
||||
"<rootDir>/node_modules/d3",
|
||||
"<rootDir>/node_modules/lz-string",
|
||||
"<rootDir>/node_modules/jsen",
|
||||
"coriolis-data",
|
||||
"<rootDir>/src/app/shipyard",
|
||||
"<rootDir>/src/app/i18n",
|
||||
"<rootDir>/src/app/utils",
|
||||
"<rootDir>/src/schemas",
|
||||
"<rootDir>/__tests__"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.0.0",
|
||||
"@babel/plugin-proposal-decorators": "^7.0.0",
|
||||
"@babel/plugin-proposal-do-expressions": "^7.0.0",
|
||||
"@babel/plugin-proposal-export-default-from": "^7.0.0",
|
||||
"@babel/plugin-proposal-export-namespace-from": "^7.0.0",
|
||||
"@babel/plugin-proposal-function-bind": "^7.0.0",
|
||||
"@babel/plugin-proposal-function-sent": "^7.0.0",
|
||||
"@babel/plugin-proposal-json-strings": "^7.0.0",
|
||||
"@babel/plugin-proposal-logical-assignment-operators": "^7.0.0",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
|
||||
"@babel/plugin-proposal-numeric-separator": "^7.0.0",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
|
||||
"@babel/plugin-proposal-pipeline-operator": "^7.0.0",
|
||||
"@babel/plugin-proposal-throw-expressions": "^7.0.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
||||
"@babel/plugin-syntax-import-meta": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"appcache-webpack-plugin": "^1.4.0",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-jest": "^23.6.0",
|
||||
"babel-loader": "^8.0.0",
|
||||
"copy-webpack-plugin": "^4.5.2",
|
||||
"create-react-class": "^15.6.3",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^1.0.0",
|
||||
"d3-selection": "^1.3.2",
|
||||
"esdoc": "^1.1.0",
|
||||
"esdoc-custom-theme": "^1.4.2",
|
||||
"esdoc-ecmascript-proposal-plugin": "^1.0.0",
|
||||
"esdoc-jsx-plugin": "^1.0.0",
|
||||
"esdoc-publish-html-plugin": "^1.1.2",
|
||||
"esdoc-react-plugin": "^1.0.1",
|
||||
"esdoc-standard-plugin": "^1.0.0",
|
||||
"eslint": "^5.6.0",
|
||||
"eslint-plugin-react": "^7.11.1",
|
||||
"expose-loader": "^0.7.5",
|
||||
"express": "^4.16.3",
|
||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||
"file-loader": "^2.0.0",
|
||||
"html-webpack-plugin": "^3.0.7",
|
||||
"jest-cli": "^23.6.0",
|
||||
"jsen": "^0.6.4",
|
||||
"json-loader": "^0.5.4",
|
||||
"less": "^3.8.1",
|
||||
"less-loader": "^4.1.0",
|
||||
"react-addons-perf": "^15.4.2",
|
||||
"react-container-dimensions": "^1.4.1",
|
||||
"react-testutils-additions": "^16.0.0",
|
||||
"react-transition-group": "^2.5.0",
|
||||
"rimraf": "^2.6.1",
|
||||
"rollup": "^0.66.2",
|
||||
"rollup-plugin-node-resolve": "^3.4.0",
|
||||
"style-loader": "^0.23.0",
|
||||
"uglify-js": "^3.4.9",
|
||||
"url-loader": "^1.1.1",
|
||||
"webpack": "^4.20.2",
|
||||
"webpack-bugsnag-plugins": "^1.2.2",
|
||||
"webpack-cli": "^3.1.1",
|
||||
"webpack-dev-server": "^3.1.9",
|
||||
"webpack-notifier": "^1.6.0",
|
||||
"workbox-webpack-plugin": "^3.6.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/polyfill": "^7.0.0",
|
||||
"browserify-zlib-next": "^1.0.1",
|
||||
"classnames": "^2.2.6",
|
||||
"coriolis-data": "../coriolis-data",
|
||||
"d3": "^5.7.0",
|
||||
"detect-browser": "^3.0.1",
|
||||
"fbemitter": "^2.1.1",
|
||||
"lodash": "^4.17.11",
|
||||
"lz-string": "^1.4.4",
|
||||
"pako": "^1.0.6",
|
||||
"prop-types": "^15.6.2",
|
||||
"react": "^15.5.4",
|
||||
"react-dom": "^15.5.4",
|
||||
"react-extras": "^0.7.1",
|
||||
"react-ga": "^2.5.3",
|
||||
"react-number-editor": "Athanasius/react-number-editor.git#miggy",
|
||||
"recharts": "^1.2.0",
|
||||
"register-service-worker": "^1.5.2",
|
||||
"superagent": "^3.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Router from './Router';
|
||||
import { register } from 'register-service-worker'
|
||||
import { EventEmitter } from 'fbemitter';
|
||||
import { getLanguage } from './i18n/Language';
|
||||
import Persist from './stores/Persist';
|
||||
@@ -22,12 +23,12 @@ import ShipyardPage from './pages/ShipyardPage';
|
||||
import ErrorDetails from './pages/ErrorDetails';
|
||||
|
||||
const zlib = require('pako');
|
||||
const request = require('superagent');
|
||||
|
||||
/**
|
||||
* Coriolis App
|
||||
*/
|
||||
export default class Coriolis extends React.Component {
|
||||
|
||||
static childContextTypes = {
|
||||
closeMenu: PropTypes.func.isRequired,
|
||||
hideModal: PropTypes.func.isRequired,
|
||||
@@ -66,11 +67,12 @@ export default class Coriolis extends React.Component {
|
||||
this.state = {
|
||||
noTouch: !('ontouchstart' in window || navigator.msMaxTouchPoints || navigator.maxTouchPoints),
|
||||
page: null,
|
||||
announcements: [],
|
||||
language: getLanguage(Persist.getLangCode()),
|
||||
route: {},
|
||||
sizeRatio: Persist.getSizeRatio()
|
||||
};
|
||||
|
||||
this._getAnnouncements()
|
||||
Router('', (r) => this._setPage(ShipyardPage, r));
|
||||
Router('/import?', (r) => this._importBuild(r));
|
||||
Router('/import/:data', (r) => this._importBuild(r));
|
||||
@@ -109,6 +111,14 @@ export default class Coriolis extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
_getAnnouncements() {
|
||||
return request.get('https://orbis.zone/api/announcement')
|
||||
.query({showInCoriolis: true})
|
||||
.then(announces => {
|
||||
this.setState({ announcements: announces.body })
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates / Sets the page and route context
|
||||
* @param {[type]} page The page to be shown
|
||||
@@ -134,9 +144,9 @@ export default class Coriolis extends React.Component {
|
||||
console && console.error && console.error(arguments); // eslint-disable-line no-console
|
||||
if (errObj) {
|
||||
if (errObj instanceof Error) {
|
||||
bugsnagClient.notify(errObj) // eslint-disable-line
|
||||
bugsnagClient.notify(errObj); // eslint-disable-line
|
||||
} else if (errObj instanceof String) {
|
||||
bugsnagClient.notify(msg, errObj) // eslint-disable-line
|
||||
bugsnagClient.notify(msg, errObj); // eslint-disable-line
|
||||
}
|
||||
}
|
||||
this.setState({
|
||||
@@ -180,13 +190,13 @@ export default class Coriolis extends React.Component {
|
||||
case 72: // 'h'
|
||||
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + h
|
||||
e.preventDefault();
|
||||
this._showModal(<ModalHelp />);
|
||||
this._showModal(<ModalHelp/>);
|
||||
}
|
||||
break;
|
||||
case 73: // 'i'
|
||||
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + i
|
||||
e.preventDefault();
|
||||
this._showModal(<ModalImport />);
|
||||
this._showModal(<ModalImport/>);
|
||||
}
|
||||
break;
|
||||
case 79: // 'o'
|
||||
@@ -208,7 +218,7 @@ export default class Coriolis extends React.Component {
|
||||
* @param {React.Component} content Modal Content
|
||||
*/
|
||||
_showModal(content) {
|
||||
let modal = <div className='modal-bg' onClick={(e) => this._hideModal() }>{content}</div>;
|
||||
let modal = <div className='modal-bg' onClick={(e) => this._hideModal()}>{content}</div>;
|
||||
this.setState({ modal });
|
||||
}
|
||||
|
||||
@@ -286,7 +296,7 @@ export default class Coriolis extends React.Component {
|
||||
return this.emitter.addListener('windowResize', listener);
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Add a listener to global commands such as save,
|
||||
* @param {Function} listener Listener callback
|
||||
* @return {Object} Subscription token
|
||||
@@ -322,14 +332,50 @@ export default class Coriolis extends React.Component {
|
||||
*/
|
||||
componentWillMount() {
|
||||
// Listen for appcache updated event, present refresh to update view
|
||||
if (window.applicationCache) {
|
||||
window.applicationCache.addEventListener('updateready', () => {
|
||||
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
|
||||
this.setState({ appCacheUpdate: true }); // Browser downloaded a new app cache.
|
||||
}
|
||||
// Check that service workers are registered
|
||||
if (navigator.storage && navigator.storage.persist) {
|
||||
window.addEventListener('load', () => {
|
||||
navigator.storage.persist().then(granted => {
|
||||
if (granted)
|
||||
console.log('Storage will not be cleared except by explicit user action');
|
||||
else
|
||||
console.log('Storage may be cleared by the UA under storage pressure.');
|
||||
});
|
||||
});
|
||||
}
|
||||
if ('serviceWorker' in navigator) {
|
||||
window.addEventListener('load', () => {
|
||||
// Your service-worker.js *must* be located at the top-level directory relative to your site.
|
||||
// It won't be able to control pages unless it's located at the same level or higher than them.
|
||||
// *Don't* register service worker file in, e.g., a scripts/ sub-directory!
|
||||
// See https://github.com/slightlyoff/ServiceWorker/issues/468
|
||||
const self = this;
|
||||
register('/service-worker.js', {
|
||||
ready (registration) {
|
||||
console.log('Service worker is active.')
|
||||
},
|
||||
registered (registration) {
|
||||
console.log('Service worker has been registered.')
|
||||
},
|
||||
cached (registration) {
|
||||
console.log('Content has been cached for offline use.')
|
||||
},
|
||||
updatefound (registration) {
|
||||
console.log('New content is downloading.')
|
||||
},
|
||||
updated (registration) {
|
||||
self.setState({ appCacheUpdate: true });
|
||||
console.log('New content is available; please refresh.')
|
||||
},
|
||||
offline () {
|
||||
console.log('No internet connection found. App is running in offline mode.')
|
||||
},
|
||||
error (error) {
|
||||
console.error('Error during service worker registration:', error)
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
window.onerror = this._onError.bind(this);
|
||||
window.addEventListener('resize', () => this.emitter.emit('windowResize'));
|
||||
document.getElementById('coriolis').addEventListener('scroll', () => this._tooltip());
|
||||
@@ -347,16 +393,22 @@ export default class Coriolis extends React.Component {
|
||||
render() {
|
||||
let currentMenu = this.state.currentMenu;
|
||||
|
||||
return <div style={{ minHeight: '100%' }} onClick={this._closeMenu} className={ this.state.noTouch ? 'no-touch' : null }>
|
||||
<Header appCacheUpdate={this.state.appCacheUpdate} currentMenu={currentMenu} />
|
||||
{ this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) : <NotFoundPage/> }
|
||||
{ this.state.modal }
|
||||
{ this.state.tooltip }
|
||||
return <div style={{ minHeight: '100%' }} onClick={this._closeMenu}
|
||||
className={this.state.noTouch ? 'no-touch' : null}>
|
||||
<Header announcements={this.state.announcements} appCacheUpdate={this.state.appCacheUpdate} currentMenu={currentMenu}/>
|
||||
{this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) :
|
||||
<NotFoundPage/>}
|
||||
{this.state.modal}
|
||||
{this.state.tooltip}
|
||||
<footer>
|
||||
<div className="right cap">
|
||||
<a href="https://github.com/EDCD/coriolis" target="_blank" title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>
|
||||
<a href="https://github.com/EDCD/coriolis" target="_blank"
|
||||
title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>
|
||||
<br/>
|
||||
<a href={'https://github.com/EDCD/coriolis/compare/edcd:develop@{' + window.CORIOLIS_DATE + '}...edcd:develop'} target="_blank" title={'Coriolis Commits since' + window.CORIOLIS_DATE}>Commits since last release ({window.CORIOLIS_DATE})</a>
|
||||
<a
|
||||
href={'https://github.com/EDCD/coriolis/compare/edcd:develop@{' + window.CORIOLIS_DATE + '}...edcd:develop'}
|
||||
target="_blank" title={'Coriolis Commits since' + window.CORIOLIS_DATE}>Commits since last release
|
||||
({window.CORIOLIS_DATE})</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>;
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
// Check whether a test is run because then we need to
|
||||
// initialize ReactGA in test mode
|
||||
let isTest = false;
|
||||
try {
|
||||
isTest = process.env.node_env === 'test';
|
||||
} catch (e) {}
|
||||
|
||||
import Persist from './stores/Persist';
|
||||
import ReactGA from 'react-ga';
|
||||
ReactGA.initialize('UA-55840909-18');
|
||||
ReactGA.initialize('UA-55840909-18', { testMode: isTest });
|
||||
let standalone = undefined;
|
||||
|
||||
/**
|
||||
@@ -258,6 +265,15 @@ Route.prototype.match = function(path, params) {
|
||||
* @param {string} path Path to track
|
||||
*/
|
||||
function gaTrack(path) {
|
||||
const match = path.match(/\/outfit\/(.*)(\?code=.*)/);
|
||||
if (match) {
|
||||
if (match[1]) {
|
||||
ReactGA.ga('set', 'contentGroup1', match[1]);
|
||||
}
|
||||
if (match[2]) {
|
||||
ReactGA.ga('set', 'contentGroup2', match[2]);
|
||||
}
|
||||
}
|
||||
ReactGA.pageview(path);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,6 @@ function isActive(href) {
|
||||
* Active Link - Highlighted when URL matches window location
|
||||
*/
|
||||
export default class ActiveLink extends Link {
|
||||
|
||||
/**
|
||||
* Renders the component
|
||||
* @return {React.Component} The active link
|
||||
@@ -29,5 +28,4 @@ export default class ActiveLink extends Link {
|
||||
|
||||
return <a {...this.props} className={className} onClick={this.handler}>{this.props.children}</a>;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
31
src/app/components/Announcement.jsx
Normal file
31
src/app/components/Announcement.jsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { autoBind } from 'react-extras';
|
||||
|
||||
/**
|
||||
* Announcement component
|
||||
*/
|
||||
export default class Announcement extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
text: PropTypes.string
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
autoBind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the announcement
|
||||
* @return {React.Component} A href element
|
||||
*/
|
||||
render() {
|
||||
return <p>{this.props.text}</p>;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -45,6 +45,12 @@ const GRPCAT = {
|
||||
'mr': 'ordnance',
|
||||
'axmr': 'experimental',
|
||||
'rcpl': 'experimental',
|
||||
'dtl': 'experimental',
|
||||
'tbsc': 'experimental',
|
||||
'tbem': 'experimental',
|
||||
'tbrfl': 'experimental',
|
||||
'mahr': 'experimental',
|
||||
'rsl': 'experimental',
|
||||
'tp': 'ordnance',
|
||||
'nl': 'ordnance',
|
||||
'sc': 'scanners',
|
||||
@@ -58,10 +64,15 @@ const GRPCAT = {
|
||||
'po': 'defence',
|
||||
'ec': 'defence',
|
||||
'sfn': 'defence',
|
||||
// Standard
|
||||
// Guardian
|
||||
'gpp': 'guardian',
|
||||
'gpc': 'guardian',
|
||||
'ggc': 'guardian'
|
||||
'gsrp': 'guardian',
|
||||
'ggc': 'guardian',
|
||||
'gfsb': 'guardian',
|
||||
'gmrp': 'guardian',
|
||||
'gsc': 'guardian',
|
||||
'ghrp': 'guardian'
|
||||
};
|
||||
// Order here is the order in which items will be shown in the modules menu
|
||||
const CATEGORIES = {
|
||||
@@ -87,17 +98,16 @@ const CATEGORIES = {
|
||||
'defence': ['ch', 'po', 'ec'],
|
||||
'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners
|
||||
// Experimental
|
||||
'experimental': ['axmc', 'axmr', 'rfl', 'xs', 'sfn', 'rcpl'],
|
||||
'experimental': ['axmc', 'axmr', 'rfl', 'tbrfl', 'tbsc', 'tbem', 'xs', 'sfn', 'rcpl', 'dtl', 'rsl', 'mahr',],
|
||||
|
||||
// Guardian
|
||||
'guardian': ['gpp', 'gpc', 'ggc']
|
||||
'guardian': ['gpp', 'gpd', 'gpc', 'ggc', 'gsrp', 'gfsb', 'ghrp', 'gmrp', 'gsc']
|
||||
};
|
||||
|
||||
/**
|
||||
* Available modules menu
|
||||
*/
|
||||
export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
modules: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
@@ -148,7 +158,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
this._hideDiff(event);
|
||||
onSelect(m);
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
if (modules instanceof Array) {
|
||||
list = buildGroup(modules[0].grp, modules);
|
||||
@@ -467,7 +477,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
}
|
||||
/**
|
||||
* Handle focus if the component updates
|
||||
*
|
||||
*
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
if(this.props.slotDiv) {
|
||||
@@ -491,14 +501,13 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
render() {
|
||||
return (
|
||||
<div ref={node => this.node = node}
|
||||
className={cn('select', this.props.className)}
|
||||
onScroll={this._hideDiff}
|
||||
onClick={(e) => e.stopPropagation() }
|
||||
onContextMenu={stopCtxPropagation}
|
||||
className={cn('select', this.props.className)}
|
||||
onScroll={this._hideDiff}
|
||||
onClick={(e) => e.stopPropagation() }
|
||||
onContextMenu={stopCtxPropagation}
|
||||
>
|
||||
{this.state.list}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ function insertLinebreaks(d) {
|
||||
* Bar Chart
|
||||
*/
|
||||
export default class BarChart extends TranslatedComponent {
|
||||
|
||||
static defaultProps = {
|
||||
colors: ['#7b6888', '#6b486b', '#3182bd', '#a05d56', '#d0743c'],
|
||||
labels: null,
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import { nameComparator } from '../utils/SlotFunctions';
|
||||
import { Pip } from './SvgIcons';
|
||||
import LineChart from '../components/LineChart';
|
||||
import Slider from '../components/Slider';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import Module from '../shipyard/Module';
|
||||
|
||||
/**
|
||||
* Boost displays a boost button that toggles bosot
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import Slider from '../components/Slider';
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,6 @@ import { outfitURL } from '../utils/UrlGenerators';
|
||||
* Comparison Table
|
||||
*/
|
||||
export default class ComparisonTable extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
facets: PropTypes.array.isRequired,
|
||||
builds: PropTypes.array.isRequired,
|
||||
|
||||
@@ -13,7 +13,6 @@ import { ShoppingIcon } from '../components/SvgIcons';
|
||||
* Cost Section
|
||||
*/
|
||||
export default class CostSection extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
ship: PropTypes.object.isRequired,
|
||||
code: PropTypes.string.isRequired,
|
||||
@@ -361,11 +360,11 @@ export default class CostSection extends TranslatedComponent {
|
||||
for (let i = 0, l = retrofitCosts.length; i < l; i++) {
|
||||
let item = retrofitCosts[i];
|
||||
rows.push(<tr key={i} className={cn('highlight', { disabled: !item.retroItem.incCost })} onClick={this._toggleRetrofitCost.bind(this, item)}>
|
||||
<td className='ptr' style={{ width: '1em' }}>{item.sellClassRating}</td>
|
||||
<td className='le ptr shorten cap'>{translate(item.sellName)}</td>
|
||||
<td className='ptr' style={{ width: '1em' }}>{item.buyClassRating}</td>
|
||||
<td className='le ptr shorten cap'>{translate(item.buyName)}</td>
|
||||
<td colSpan='2' className={cn('ri ptr', item.retroItem.incCost ? item.netCost > 0 ? 'warning' : 'secondary-disabled' : 'disabled')}>{int(item.netCost)}{units.CR}</td>
|
||||
<td className='ptr' style={{ width: '1em' }}>{item.sellClassRating}</td>
|
||||
<td className='le ptr shorten cap'>{translate(item.sellName)}</td>
|
||||
<td className='ptr' style={{ width: '1em' }}>{item.buyClassRating}</td>
|
||||
<td className='le ptr shorten cap'>{translate(item.buyName)}</td>
|
||||
<td colSpan='2' className={cn('ri ptr', item.retroItem.incCost ? item.netCost > 0 ? 'warning' : 'secondary-disabled' : 'disabled')}>{int(item.netCost)}{units.CR}</td>
|
||||
</tr>);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -76,6 +76,7 @@ export default class Defence extends TranslatedComponent {
|
||||
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') });
|
||||
shieldSourcesData.push({ value: Math.round(shield.addition), label: translate('shield addition') });
|
||||
|
||||
if (shield.generator > 0) {
|
||||
shieldSourcesTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
||||
@@ -101,19 +102,19 @@ export default class Defence extends TranslatedComponent {
|
||||
|
||||
// Add effective shield from resistances
|
||||
const rawMj = shield.generator + shield.boosters + shield.cells;
|
||||
const explosiveMj = rawMj / (shield.explosive.generator * shield.explosive.boosters) - rawMj;
|
||||
const explosiveMj = rawMj / (shield.explosive.base) - rawMj;
|
||||
if (explosiveMj != 0) effectiveShieldExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(explosiveMj)}{units.MJ}</div>);
|
||||
const kineticMj = rawMj / (shield.kinetic.generator * shield.kinetic.boosters) - rawMj;
|
||||
const kineticMj = rawMj / (shield.kinetic.base) - rawMj;
|
||||
if (kineticMj != 0) effectiveShieldKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(kineticMj)}{units.MJ}</div>);
|
||||
const thermalMj = rawMj / (shield.thermal.generator * shield.thermal.boosters) - rawMj;
|
||||
const thermalMj = rawMj / (shield.thermal.base) - rawMj;
|
||||
if (thermalMj != 0) effectiveShieldThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(thermalMj)}{units.MJ}</div>);
|
||||
|
||||
// Add effective shield from power distributor SYS pips
|
||||
if (shield.absolute.sys != 1) {
|
||||
effectiveShieldAbsoluteTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.absolute.sys - rawMj)}{units.MJ}</div>);
|
||||
effectiveShieldExplosiveTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.explosive.sys - rawMj)}{units.MJ}</div>);
|
||||
effectiveShieldKineticTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.kinetic.sys - rawMj)}{units.MJ}</div>);
|
||||
effectiveShieldThermalTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.thermal.sys - rawMj)}{units.MJ}</div>);
|
||||
effectiveShieldAbsoluteTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.absolute.total - rawMj)}{units.MJ}</div>);
|
||||
effectiveShieldExplosiveTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.explosive.total - rawMj / shield.explosive.base)}{units.MJ}</div>);
|
||||
effectiveShieldKineticTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.kinetic.total - rawMj / shield.kinetic.base)}{units.MJ}</div>);
|
||||
effectiveShieldThermalTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.thermal.total - rawMj / shield.thermal.base)}{units.MJ}</div>);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,18 +160,21 @@ export default class Defence extends TranslatedComponent {
|
||||
const effectiveArmourExplosiveTt = [];
|
||||
const effectiveArmourKineticTt = [];
|
||||
const effectiveArmourThermalTt = [];
|
||||
const effectiveArmourCausticTt = [];
|
||||
if (armour.bulkheads > 0) {
|
||||
armourSourcesTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
||||
effectiveArmourAbsoluteTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
||||
effectiveArmourExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
||||
effectiveArmourKineticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
||||
effectiveArmourThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
||||
effectiveArmourCausticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
||||
if (armour.reinforcement > 0) {
|
||||
armourSourcesTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
||||
effectiveArmourAbsoluteTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
||||
effectiveArmourExplosiveTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
||||
effectiveArmourKineticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
||||
effectiveArmourThermalTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
||||
effectiveArmourCausticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,17 +187,22 @@ export default class Defence extends TranslatedComponent {
|
||||
const armourDamageTakenExplosiveTt = [];
|
||||
armourDamageTakenExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.explosive.bulkheads)}</div>);
|
||||
armourDamageTakenExplosiveTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.explosive.reinforcement)}</div>);
|
||||
if (armour.explosive.bulkheads * armour.explosive.reinforcement != 1) effectiveArmourExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.explosive.bulkheads * armour.explosive.reinforcement) - rawArmour)}</div>);
|
||||
if (armour.explosive.total != 1) effectiveArmourExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.explosive.total - rawArmour)}</div>);
|
||||
|
||||
const armourDamageTakenKineticTt = [];
|
||||
armourDamageTakenKineticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.kinetic.bulkheads)}</div>);
|
||||
armourDamageTakenKineticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.kinetic.reinforcement)}</div>);
|
||||
if (armour.kinetic.bulkheads * armour.kinetic.reinforcement != 1) effectiveArmourKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.kinetic.bulkheads * armour.kinetic.reinforcement) - rawArmour)}</div>);
|
||||
if (armour.kinetic.total != 1) effectiveArmourKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.kinetic.total - rawArmour)}</div>);
|
||||
|
||||
const armourDamageTakenThermalTt = [];
|
||||
armourDamageTakenThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.thermal.bulkheads)}</div>);
|
||||
armourDamageTakenThermalTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.thermal.reinforcement)}</div>);
|
||||
if (armour.thermal.bulkheads * armour.thermal.reinforcement != 1) effectiveArmourThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.thermal.bulkheads * armour.thermal.reinforcement) - rawArmour)}</div>);
|
||||
if (armour.thermal.total != 1) effectiveArmourThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.thermal.total - rawArmour)}</div>);
|
||||
|
||||
const armourDamageTakenCausticTt = [];
|
||||
armourDamageTakenCausticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.caustic.bulkheads)}</div>);
|
||||
armourDamageTakenCausticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.caustic.reinforcement)}</div>);
|
||||
if (armour.thermal.total != 1) effectiveArmourCausticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.caustic.total - rawArmour)}</div>);
|
||||
|
||||
const effectiveArmourData = [];
|
||||
const effectiveAbsoluteArmour = armour.total / armour.absolute.total;
|
||||
@@ -204,36 +213,39 @@ export default class Defence extends TranslatedComponent {
|
||||
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'), tooltip: effectiveArmourThermalTt });
|
||||
const effectiveCausticArmour = armour.total / armour.caustic.total;
|
||||
effectiveArmourData.push({ value: Math.round(effectiveCausticArmour), label: translate('caustic'), tooltip: effectiveArmourCausticTt });
|
||||
|
||||
const armourDamageTakenData = [];
|
||||
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 });
|
||||
armourDamageTakenData.push({ value: Math.round(armour.caustic.total * 100), label: translate('caustic'), tooltip: armourDamageTakenCausticTt });
|
||||
|
||||
return (
|
||||
<span id='defence'>
|
||||
{shield.total ? <span>
|
||||
<div className='group quarter'>
|
||||
<h2>{translate('shield metrics')}</h2>
|
||||
<br/>
|
||||
<h2 onMouseOver={termtip.bind(null, <div>{shieldSourcesTt}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw shield strength')}<br/>{formats.int(shield.total)}{units.MJ}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_SHIELDS')}<br/>{shielddamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(shield.total, shielddamage.totalsdps, shielddamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * opponentWep / 4))}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECOVER'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECOVER_SHIELDS')}<br/>{shield.recover === Math.Inf ? translate('never') : formats.time(shield.recover)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECHARGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECHARGE_SHIELDS')}<br/>{shield.recharge === Math.Inf ? translate('never') : formats.time(shield.recharge)}</h2>
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_SOURCES'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield sources')}</h2>
|
||||
<PieChart data={shieldSourcesData} />
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_DAMAGE_TAKEN'))} onMouseOut={tooltip.bind(null, null)}>{translate('damage taken')}(%)</h2>
|
||||
<VerticalBarChart data={shieldDamageTakenData} yMax={140} />
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_EFFECTIVE_SHIELD'))} onMouseOut={tooltip.bind(null, null)}>{translate('effective shield')}(MJ)</h2>
|
||||
<VerticalBarChart data={effectiveShieldData} yMax={maxEffectiveShield}/>
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2>{translate('shield metrics')}</h2>
|
||||
<br/>
|
||||
<h2 onMouseOver={termtip.bind(null, <div>{shieldSourcesTt}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw shield strength')}<br/>{formats.int(shield.total)}{units.MJ}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_SHIELDS')}<br/>{shielddamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(shield.total, shielddamage.totalsdps, shielddamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * opponentWep / 4))}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECOVER'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECOVER_SHIELDS')}<br/>{shield.recover === Math.Inf ? translate('never') : formats.time(shield.recover)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECHARGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECHARGE_SHIELDS')}<br/>{shield.recharge === Math.Inf ? translate('never') : formats.time(shield.recharge)}</h2>
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_SOURCES'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield sources')}</h2>
|
||||
<PieChart data={shieldSourcesData} />
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_DAMAGE_TAKEN'))} onMouseOut={tooltip.bind(null, null)}>{translate('damage taken')}(%)</h2>
|
||||
<VerticalBarChart data={shieldDamageTakenData} yMax={140} />
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_EFFECTIVE_SHIELD'))} onMouseOut={tooltip.bind(null, null)}>{translate('effective shield')}(MJ)</h2>
|
||||
<VerticalBarChart data={effectiveShieldData} yMax={maxEffectiveShield}/>
|
||||
</div>
|
||||
</span> : null }
|
||||
|
||||
<div className='group quarter'>
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import Slider from '../components/Slider';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import { nameComparator } from '../utils/SlotFunctions';
|
||||
import LineChart from '../components/LineChart';
|
||||
import Slider from '../components/Slider';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import Module from '../shipyard/Module';
|
||||
import * as Calc from '../shipyard/Calculations';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import { nameComparator } from '../utils/SlotFunctions';
|
||||
import LineChart from '../components/LineChart';
|
||||
import Slider from '../components/Slider';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import Module from '../shipyard/Module';
|
||||
import * as Calc from '../shipyard/Calculations';
|
||||
|
||||
/**
|
||||
@@ -57,7 +52,7 @@ export default class FSDProfile extends TranslatedComponent {
|
||||
*/
|
||||
_calcMaxRange(ship, fuel, mass) {
|
||||
// Obtain the maximum range
|
||||
return Calc.jumpRange(mass, ship.standard[2].m, Math.min(fuel, ship.standard[2].m.getMaxFuelPerJump()));
|
||||
return Calc.jumpRange(mass, ship.standard[2].m, Math.min(fuel, ship.standard[2].m.getMaxFuelPerJump()), ship);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,7 +64,7 @@ export default class FSDProfile extends TranslatedComponent {
|
||||
const { formats, translate, units } = language;
|
||||
const { ship, cargo, fuel } = this.props;
|
||||
|
||||
|
||||
|
||||
// Calculate bounds for our line chart - use thruster info for X
|
||||
const thrusters = ship.standard[1].m;
|
||||
const fsd = ship.standard[2].m;
|
||||
@@ -77,7 +72,7 @@ export default class FSDProfile extends TranslatedComponent {
|
||||
const maxMass = thrusters.getMaxMass();
|
||||
const mass = ship.unladenMass + fuel + cargo;
|
||||
const minRange = 0;
|
||||
const maxRange = Calc.jumpRange(minMass + fsd.getMaxFuelPerJump(), fsd, fsd.getMaxFuelPerJump());
|
||||
const maxRange = Calc.jumpRange(minMass + fsd.getMaxFuelPerJump(), fsd, fsd.getMaxFuelPerJump(), ship);
|
||||
// Add a mark at our current mass
|
||||
const mark = Math.min(mass, maxMass);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import Slider from '../components/Slider';
|
||||
|
||||
/**
|
||||
|
||||
@@ -79,7 +79,7 @@ export default class HardpointSlot extends Slot {
|
||||
<div className={'r'}>{formats.round(m.getMass())}{u.T}</div>
|
||||
</div>
|
||||
<div className={'cb'}>
|
||||
{ m.getDps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'dpssdps' : 'dps')} onMouseOut={tooltip.bind(null, null)}>{translate('DPS')}: {formats.round1(m.getDps())} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) })</span> : null }</div> : null }
|
||||
{ m.getDps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'dpssdps' : 'dps')} onMouseOut={tooltip.bind(null, null)}>{translate('DPS')}: {formats.round1(m.getDps())} { m.getClip() ? <span>({formats.round1(m.getSDps()) })</span> : null }</div> : null }
|
||||
{ m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'epsseps' : 'eps')} onMouseOut={tooltip.bind(null, null)}>{translate('EPS')}: {formats.round1(m.getEps())}{u.MW} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) }{u.MW})</span> : null }</div> : null }
|
||||
{ m.getHps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'hpsshps' : 'hps')} onMouseOut={tooltip.bind(null, null)}>{translate('HPS')}: {formats.round1(m.getHps())} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getHps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) })</span> : null }</div> : null }
|
||||
{ m.getDps() && m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, 'dpe')} onMouseOut={tooltip.bind(null, null)}>{translate('DPE')}: {formats.f1(m.getDps() / m.getEps())}</div> : null }
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import SlotSection from './SlotSection';
|
||||
import HardpointSlot from './HardpointSlot';
|
||||
import cn from 'classnames';
|
||||
import { MountFixed, MountGimballed, MountTurret } from '../components/SvgIcons';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
|
||||
|
||||
@@ -9,11 +9,14 @@ import { Cogs, CoriolisLogo, Hammer, Help, Rocket, StatsBars } from './SvgIcons'
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import Persist from '../stores/Persist';
|
||||
import { toDetailedExport } from '../shipyard/Serializer';
|
||||
import Ship from '../shipyard/Ship';
|
||||
import ModalBatchOrbis from './ModalBatchOrbis';
|
||||
import ModalDeleteAll from './ModalDeleteAll';
|
||||
import ModalExport from './ModalExport';
|
||||
import ModalHelp from './ModalHelp';
|
||||
import ModalImport from './ModalImport';
|
||||
import Slider from './Slider';
|
||||
import Announcement from './Announcement';
|
||||
import { outfitURL } from '../utils/UrlGenerators';
|
||||
|
||||
const SIZE_MIN = 0.65;
|
||||
@@ -74,8 +77,11 @@ export default class Header extends TranslatedComponent {
|
||||
this._openShips = this._openMenu.bind(this, 's');
|
||||
this._openBuilds = this._openMenu.bind(this, 'b');
|
||||
this._openComp = this._openMenu.bind(this, 'comp');
|
||||
this._openAnnounce = this._openMenu.bind(this, 'announce');
|
||||
this._getAnnouncementsMenu = this._getAnnouncementsMenu.bind(this);
|
||||
this._openSettings = this._openMenu.bind(this, 'settings');
|
||||
this._showHelp = this._showHelp.bind(this);
|
||||
this.update = this.update.bind(this);
|
||||
this.languageOptions = [];
|
||||
this.insuranceOptions = [];
|
||||
this.state = {
|
||||
@@ -235,6 +241,43 @@ export default class Header extends TranslatedComponent {
|
||||
/>);
|
||||
};
|
||||
|
||||
/**
|
||||
* Uploads all ship-builds to orbis
|
||||
* @param {e} e Event
|
||||
*/
|
||||
_uploadAllBuildsToOrbis(e) {
|
||||
e.preventDefault();
|
||||
const data = Persist.getBuilds();
|
||||
let postObject = [];
|
||||
for (const ship in data) {
|
||||
for (const code in data[ship]) {
|
||||
const shipModel = ship;
|
||||
if (!shipModel) {
|
||||
throw 'No such ship found: "' + ship + '"';
|
||||
}
|
||||
const shipTemplate = Ships[shipModel];
|
||||
const shipPostObject = {};
|
||||
let shipInstance = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots);
|
||||
shipInstance.buildWith(null);
|
||||
shipInstance.buildFrom(data[ship][code]);
|
||||
shipPostObject.coriolisId = shipInstance.id;
|
||||
shipPostObject.coriolisShip = shipInstance;
|
||||
|
||||
shipPostObject.coriolisShip.url = window.location.origin + outfitURL(shipModel, data[ship][code], code);
|
||||
shipPostObject.title = code || shipInstance.id;
|
||||
shipPostObject.description = code || shipInstance.id;
|
||||
shipPostObject.ShipName = shipInstance.id;
|
||||
shipPostObject.Ship = shipInstance.id;
|
||||
postObject.push(shipPostObject);
|
||||
}
|
||||
}
|
||||
console.log(postObject);
|
||||
|
||||
this.context.showModal(<ModalBatchOrbis
|
||||
ships={postObject}
|
||||
/>);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show export modal with detailed export
|
||||
* @param {SyntheticEvent} e Event
|
||||
@@ -372,6 +415,29 @@ export default class Header extends TranslatedComponent {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the announcement menu
|
||||
* @return {React.Component} Menu
|
||||
*/
|
||||
_getAnnouncementsMenu() {
|
||||
let announcements;
|
||||
let translate = this.context.language.translate;
|
||||
|
||||
if (this.props.announcements) {
|
||||
announcements = [];
|
||||
for (let announce of this.props.announcements) {
|
||||
announcements.push(<Announcement text={announce.message} />);
|
||||
announcements.push(<hr/>);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className='menu-list' onClick={ (e) => e.stopPropagation() } style={{ whiteSpace: 'nowrap' }}>
|
||||
{announcements}
|
||||
<hr />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the settings menu
|
||||
* @return {React.Component} Menu
|
||||
@@ -430,6 +496,7 @@ export default class Header extends TranslatedComponent {
|
||||
{translate('builds')} & {translate('comparisons')}
|
||||
<li><Link href="#" className='block' onClick={this._showBackup.bind(this)}>{translate('backup')}</Link></li>
|
||||
<li><Link href="#" className='block' onClick={this._showDetailedExport.bind(this)}>{translate('detailed export')}</Link></li>
|
||||
<li><Link href="#" className='block' onClick={this._uploadAllBuildsToOrbis.bind(this)}>{translate('upload all builds to orbis')}</Link></li>
|
||||
<li><Link href="#" className='block' onClick={this._showImport.bind(this)}>{translate('import')}</Link></li>
|
||||
<li><Link href="#" className='block' onClick={this._showDeleteAll.bind(this)}>{translate('delete all')}</Link></li>
|
||||
</ul>
|
||||
@@ -494,6 +561,15 @@ export default class Header extends TranslatedComponent {
|
||||
}
|
||||
}
|
||||
|
||||
async update() {
|
||||
const reg = await navigator.serviceWorker.getRegistration();
|
||||
if (!reg || !reg.waiting) {
|
||||
return window.location.reload();
|
||||
}
|
||||
reg.waiting.postMessage('skipWaiting');
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the header
|
||||
* @return {React.Component} Header
|
||||
@@ -504,7 +580,7 @@ export default class Header extends TranslatedComponent {
|
||||
let hasBuilds = Persist.hasBuilds();
|
||||
return (
|
||||
<header>
|
||||
{this.props.appCacheUpdate && <div id="app-update" onClick={() => window.location.reload() }>{translate('PHRASE_UPDATE_RDY')}</div>}
|
||||
{this.props.appCacheUpdate && <div id="app-update" onClick={this.update}>{translate('PHRASE_UPDATE_RDY')}</div>}
|
||||
{this.props.appCacheUpdate ? <a className={'view-changes'} href={'https://github.com/EDCD/coriolis/compare/edcd:develop@{' + window.CORIOLIS_DATE + '}...edcd:develop'} target="_blank">
|
||||
{'View Release Changes'}
|
||||
</a> : null}
|
||||
@@ -531,6 +607,23 @@ export default class Header extends TranslatedComponent {
|
||||
{openedMenu == 'comp' ? this._getComparisonsMenu() : null}
|
||||
</div>
|
||||
|
||||
<div className='l menu'>
|
||||
<div className={cn('menu-header', { selected: openedMenu == 'announce', disabled: this.props.announcements.length === 0})} onClick={this.props.announcements.length !== 0 && this._openAnnounce}>
|
||||
<span className='menu-item-label'>{translate('announcements')}</span>
|
||||
</div>
|
||||
{openedMenu == 'announce' ? this._getAnnouncementsMenu() : null}
|
||||
</div>
|
||||
|
||||
{window.location.origin.search('.edcd.io') >= 0 ?
|
||||
<div className='l menu'>
|
||||
<a href="https://youtu.be/4SvnLcefhtI" target="_blank">
|
||||
<div className={cn('menu-header')}>
|
||||
<Rocket className='warning'/><span className='menu-item-label'>{translate('please migrate to coriolis.io')}</span>
|
||||
</div>
|
||||
</a>
|
||||
</div> : null
|
||||
}
|
||||
|
||||
<div className='r menu'>
|
||||
<div className={cn('menu-header', { selected: openedMenu == 'settings' })} onClick={this._openSettings}>
|
||||
<Cogs className='xl warning'/><span className='menu-item-label'>{translate('settings')}</span>
|
||||
|
||||
@@ -63,6 +63,9 @@ export default class InternalSlot extends Slot {
|
||||
{ m.getSpinup() ? <div className={'l'}>{translate('spinup')}: {formats.f1(m.getSpinup())}{u.s}</div> : null }
|
||||
{ m.getDuration() ? <div className={'l'}>{translate('duration')}: {formats.f1(m.getDuration())}{u.s}</div> : null }
|
||||
{ m.grp === 'scb' ? <div className={'l'}>{translate('cells')}: {formats.int(m.getAmmo() + 1)}</div> : null }
|
||||
{ m.grp === 'gsrp' ? <div className={'l'}>{translate('shield addition')}: {formats.f1(m.getShieldAddition())}{u.MJ}</div> : null }
|
||||
{ m.grp === 'gfsb' ? <div className={'l'}>{translate('jump addition')}: {formats.f1(m.getJumpBoost())}{u.LY}</div> : null }
|
||||
{ m.grp === 'gs' ? <div className={'l'}>{translate('shield addition')}: {formats.f1(m.getShieldAddition())}{u.MJ}</div> : null }
|
||||
{ m.getShieldReinforcement() ? <div className={'l'}>{translate('shieldreinforcement')}: {formats.f1(m.getDuration() * m.getShieldReinforcement())}{u.MJ}</div> : null }
|
||||
{ m.getShieldReinforcement() ? <div className={'l'}>{translate('total')}: {formats.int((m.getAmmo() + 1) * (m.getDuration() * m.getShieldReinforcement()))}{u.MJ}</div> : null }
|
||||
{ m.repair ? <div className={'l'}>{translate('repair')}: {m.repair}</div> : null }
|
||||
@@ -82,6 +85,7 @@ export default class InternalSlot extends Slot {
|
||||
{ showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null }
|
||||
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
|
||||
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
|
||||
{ showModuleResistances && m.getCausticResistance() ? <div className='l'>{translate('causres')}: {formats.pct(m.getCausticResistance())}</div> : null }
|
||||
{ m.getHullReinforcement() ? <div className='l'>{translate('armour')}: {formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost') / 10000)}</div> : null }
|
||||
{ m.getProtection() ? <div className='l'>{translate('protection')}: {formats.rPct(m.getProtection())}</div> : null }
|
||||
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import SlotSection from './SlotSection';
|
||||
import InternalSlot from './InternalSlot';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import { nameComparator } from '../utils/SlotFunctions';
|
||||
import LineChart from '../components/LineChart';
|
||||
import Slider from '../components/Slider';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import Module from '../shipyard/Module';
|
||||
import * as Calc from '../shipyard/Calculations';
|
||||
|
||||
/**
|
||||
@@ -61,7 +57,7 @@ export default class JumpRange extends TranslatedComponent {
|
||||
const fuel = this.state.fuelLevel * ship.fuelCapacity;
|
||||
|
||||
// Obtain the jump range
|
||||
return Calc.jumpRange(ship.unladenMass + fuel + cargo, fsd, fuel);
|
||||
return Calc.jumpRange(ship.unladenMass + fuel + cargo, fsd, fuel, ship);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,283 +1,281 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Measure from 'react-measure';
|
||||
import * as d3 from 'd3';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
|
||||
const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 };
|
||||
|
||||
/**
|
||||
* Line Chart
|
||||
*/
|
||||
export default class LineChart extends TranslatedComponent {
|
||||
|
||||
static defaultProps = {
|
||||
code: '',
|
||||
xMin: 0,
|
||||
yMin: 0,
|
||||
points: 20,
|
||||
colors: ['#ff8c0d'],
|
||||
aspect: 0.5
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
func: PropTypes.func.isRequired,
|
||||
xLabel: PropTypes.string.isRequired,
|
||||
xMin: PropTypes.number,
|
||||
xMax: PropTypes.number.isRequired,
|
||||
xUnit: PropTypes.string.isRequired,
|
||||
xMark: PropTypes.number,
|
||||
yLabel: PropTypes.string.isRequired,
|
||||
yMin: PropTypes.number,
|
||||
yMax: PropTypes.number.isRequired,
|
||||
yUnit: PropTypes.string,
|
||||
series: PropTypes.array,
|
||||
colors: PropTypes.array,
|
||||
points: PropTypes.number,
|
||||
aspect: PropTypes.number,
|
||||
code: PropTypes.string,
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
this._updateDimensions = this._updateDimensions.bind(this);
|
||||
this._updateSeries = this._updateSeries.bind(this);
|
||||
this._tooltip = this._tooltip.bind(this);
|
||||
this._showTip = this._showTip.bind(this);
|
||||
this._hideTip = this._hideTip.bind(this);
|
||||
this._moveTip = this._moveTip.bind(this);
|
||||
|
||||
const series = props.series;
|
||||
|
||||
let xScale = d3.scaleLinear();
|
||||
let yScale = d3.scaleLinear();
|
||||
let xAxisScale = d3.scaleLinear();
|
||||
|
||||
this.xAxis = d3.axisBottom(xAxisScale).tickSizeOuter(0);
|
||||
this.yAxis = d3.axisLeft(yScale).ticks(6).tickSizeOuter(0);
|
||||
|
||||
this.state = {
|
||||
xScale,
|
||||
xAxisScale,
|
||||
yScale,
|
||||
tipHeight: 2 + (1.2 * (series ? series.length : 0.8)),
|
||||
dimensions: {
|
||||
width: 100,
|
||||
height: 100
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update tooltip content
|
||||
* @param {number} xPos x coordinate
|
||||
*/
|
||||
_tooltip(xPos) {
|
||||
let { xLabel, yLabel, xUnit, yUnit, func, series } = this.props;
|
||||
let { xScale, yScale } = this.state;
|
||||
let { width } = this.state.dimensions;
|
||||
let { formats, translate } = this.context.language;
|
||||
let x0 = xScale.invert(xPos),
|
||||
y0 = func(x0),
|
||||
tips = this.tipContainer,
|
||||
yTotal = 0,
|
||||
flip = (xPos / width > 0.50),
|
||||
tipWidth = 0,
|
||||
tipHeightPx = tips.selectAll('rect').node().getBoundingClientRect().height;
|
||||
|
||||
|
||||
xPos = xScale(x0); // Clamp xPos
|
||||
|
||||
tips.selectAll('text.text-tip.y').text(function(d, i) {
|
||||
let yVal = series ? y0[series[i]] : y0;
|
||||
yTotal += yVal;
|
||||
return (series ? translate(series[i]) : '') + ' ' + formats.f2(yVal);
|
||||
}).append('tspan').attr('class', 'metric').text(yUnit ? ' ' + yUnit : '');
|
||||
|
||||
tips.selectAll('text').each(function() {
|
||||
if (this.getBBox().width > tipWidth) {
|
||||
tipWidth = Math.ceil(this.getBBox().width);
|
||||
}
|
||||
});
|
||||
|
||||
let tipY = Math.floor(yScale(yTotal / (series ? series.length : 1)) - (tipHeightPx / 2));
|
||||
|
||||
tipWidth += 8;
|
||||
tips.attr('transform', 'translate(' + xPos + ',' + tipY + ')');
|
||||
tips.selectAll('text.text-tip').attr('x', flip ? -12 : 12).style('text-anchor', flip ? 'end' : 'start');
|
||||
tips.selectAll('text.text-tip.x').text(formats.f2(x0)).append('tspan').attr('class', 'metric').text(' ' + xUnit);
|
||||
tips.selectAll('rect').attr('width', tipWidth + 4).attr('x', flip ? -tipWidth - 12 : 8).attr('y', 0).style('text-anchor', flip ? 'end' : 'start');
|
||||
this.markersContainer.selectAll('circle').attr('cx', xPos).attr('cy', (d, i) => yScale(series ? y0[series[i]] : y0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update dimensions based on properties and scale
|
||||
* @param {Object} props React Component properties
|
||||
* @param {number} scale size ratio / scale
|
||||
* @returns {Object} calculated dimensions
|
||||
*/
|
||||
_updateDimensions(props, scale) {
|
||||
const { xMax, xMin, yMin, yMax } = props;
|
||||
const { width, height } = this.state.dimensions;
|
||||
const innerWidth = width - MARGIN.left - MARGIN.right;
|
||||
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);
|
||||
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
|
||||
return { innerWidth, outerHeight, innerHeight };
|
||||
}
|
||||
|
||||
/**
|
||||
* Show tooltip
|
||||
* @param {SyntheticEvent} e Event
|
||||
*/
|
||||
_showTip(e) {
|
||||
e.preventDefault();
|
||||
this.tipContainer.style('display', null);
|
||||
this.markersContainer.style('display', null);
|
||||
this._moveTip(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move and update tooltip
|
||||
* @param {SyntheticEvent} e Event
|
||||
*/
|
||||
_moveTip(e) {
|
||||
let clientX = e.touches ? e.touches[0].clientX : e.clientX;
|
||||
this._tooltip(Math.round(clientX - e.currentTarget.getBoundingClientRect().left));
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide tooltip
|
||||
* @param {SyntheticEvent} e Event
|
||||
*/
|
||||
_hideTip(e) {
|
||||
e.preventDefault();
|
||||
this.tipContainer.style('display', 'none');
|
||||
this.markersContainer.style('display', 'none');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update series generated from props
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} state React Component state
|
||||
*/
|
||||
_updateSeries(props, state) {
|
||||
let { func, xMin, xMax, series, points } = props;
|
||||
let delta = (xMax - xMin) / points;
|
||||
let seriesData = new Array(points);
|
||||
|
||||
if (delta) {
|
||||
seriesData = new Array(points);
|
||||
for (let i = 0, x = xMin; i < points; i++) {
|
||||
seriesData[i] = [x, func(x)];
|
||||
x += delta;
|
||||
}
|
||||
seriesData[points - 1] = [xMax, func(xMax)];
|
||||
} else {
|
||||
let yVal = func(xMin);
|
||||
seriesData = [[0, yVal], [1, yVal]];
|
||||
}
|
||||
|
||||
const markerElems = [];
|
||||
const detailElems = [<text key='lbl' className='text-tip x' y='1.25em'/>];
|
||||
const seriesLines = [];
|
||||
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(<text key={i} className='text-tip y' strokeWidth={0} fill={props.colors[i]} y={1.25 * (i + 2) + 'em'}/>);
|
||||
markerElems.push(<circle key={i} className='marker' r='4' />);
|
||||
}
|
||||
|
||||
const tipHeight = 2 + (1.2 * (seriesLines ? seriesLines.length : 0.8));
|
||||
|
||||
this.setState({ markerElems, detailElems, seriesLines, seriesData, tipHeight });
|
||||
}
|
||||
|
||||
/**
|
||||
* Update dimensions and series data based on props and context.
|
||||
*/
|
||||
componentWillMount() {
|
||||
this._updateSeries(this.props, this.state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update state based on property and context changes
|
||||
* @param {Object} nextProps Incoming/Next properties
|
||||
* @param {Object} nextContext Incoming/Next conext
|
||||
*/
|
||||
componentWillReceiveProps(nextProps, nextContext) {
|
||||
const props = this.props;
|
||||
|
||||
if (props.code != nextProps.code) {
|
||||
this._updateSeries(nextProps, this.state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the chart
|
||||
* @return {React.Component} Chart SVG
|
||||
*/
|
||||
render() {
|
||||
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) => <path key={i} className='line' fill='none' stroke={colors[i]} strokeWidth='1' d={line(seriesData)} />).reverse();
|
||||
|
||||
const markX = xMark ? innerWidth * (xMark - xMin) / (xMax - xMin) : 0;
|
||||
const xmark = xMark ? <path key={'mark'} className='line' fill='none' strokeDasharray='5,5' stroke={'#ff8c0d'} strokeWidth='1' d={'M ' + markX + ' ' + innerHeight + ' L ' + markX + ' 0'} /> : '';
|
||||
|
||||
return (
|
||||
<Measure width='100%' whitelist={['width', 'top']} onMeasure={ (dimensions) => { this.setState({ dimensions }); }}>
|
||||
<div width={width} height={height}>
|
||||
<svg style={{ width: '100%', height: outerHeight }}>
|
||||
<g transform={`translate(${MARGIN.left},${MARGIN.top})`}>
|
||||
<g>{xmark}</g>
|
||||
<g>{lines}</g>
|
||||
<g className='x axis' ref={(elem) => d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}>
|
||||
<text className='cap' y='30' dy='.1em' x={innerWidth / 2} style={{ textAnchor: 'middle' }}>
|
||||
<tspan>{xLabel}</tspan>
|
||||
<tspan className='metric'> ({xUnit})</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g className='y axis' ref={(elem) => d3.select(elem).call(this.yAxis)}>
|
||||
<text className='cap' transform='rotate(-90)' y='-50' dy='.1em' x={innerHeight / -2} style={{ textAnchor: 'middle' }}>
|
||||
<tspan>{yLabel}</tspan>
|
||||
{ yUnit && <tspan className='metric'> ({yUnit})</tspan> }
|
||||
</text>
|
||||
</g>
|
||||
<g ref={(g) => this.tipContainer = d3.select(g)} style={{ display: 'none' }}>
|
||||
<rect className='tooltip' height={tipHeight + 'em'}></rect>
|
||||
{detailElems}
|
||||
</g>
|
||||
<g ref={(g) => this.markersContainer = d3.select(g)} style={{ display: 'none' }}>
|
||||
{markerElems}
|
||||
</g>
|
||||
<rect
|
||||
fillOpacity='0'
|
||||
height={innerHeight}
|
||||
width={innerWidth + 1}
|
||||
onMouseEnter={this._showTip}
|
||||
onTouchStart={this._showTip}
|
||||
onMouseLeave={this._hideTip}
|
||||
onTouchEnd={this._hideTip}
|
||||
onMouseMove={this._moveTip}
|
||||
onTouchMove={this._moveTip}
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</Measure>
|
||||
);
|
||||
}
|
||||
}
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ContainerDimensions from 'react-container-dimensions';
|
||||
import * as d3 from 'd3';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
|
||||
const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 };
|
||||
|
||||
/**
|
||||
* Line Chart
|
||||
*/
|
||||
export default class LineChart extends TranslatedComponent {
|
||||
|
||||
static defaultProps = {
|
||||
code: '',
|
||||
xMin: 0,
|
||||
yMin: 0,
|
||||
points: 20,
|
||||
colors: ['#ff8c0d'],
|
||||
aspect: 0.5
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
func: PropTypes.func.isRequired,
|
||||
xLabel: PropTypes.string.isRequired,
|
||||
xMin: PropTypes.number,
|
||||
xMax: PropTypes.number.isRequired,
|
||||
xUnit: PropTypes.string.isRequired,
|
||||
xMark: PropTypes.number,
|
||||
yLabel: PropTypes.string.isRequired,
|
||||
yMin: PropTypes.number,
|
||||
yMax: PropTypes.number.isRequired,
|
||||
yUnit: PropTypes.string,
|
||||
series: PropTypes.array,
|
||||
colors: PropTypes.array,
|
||||
points: PropTypes.number,
|
||||
aspect: PropTypes.number,
|
||||
code: PropTypes.string,
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
this._updateDimensions = this._updateDimensions.bind(this);
|
||||
this._updateSeries = this._updateSeries.bind(this);
|
||||
this._tooltip = this._tooltip.bind(this);
|
||||
this._showTip = this._showTip.bind(this);
|
||||
this._hideTip = this._hideTip.bind(this);
|
||||
this._moveTip = this._moveTip.bind(this);
|
||||
|
||||
const series = props.series;
|
||||
|
||||
let xScale = d3.scaleLinear();
|
||||
let yScale = d3.scaleLinear();
|
||||
let xAxisScale = d3.scaleLinear();
|
||||
|
||||
this.xAxis = d3.axisBottom(xAxisScale).tickSizeOuter(0);
|
||||
this.yAxis = d3.axisLeft(yScale).ticks(6).tickSizeOuter(0);
|
||||
|
||||
this.state = {
|
||||
xScale,
|
||||
xAxisScale,
|
||||
yScale,
|
||||
tipHeight: 2 + (1.2 * (series ? series.length : 0.8)),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update tooltip content
|
||||
* @param {number} xPos x coordinate
|
||||
* @param {number} width current container width
|
||||
*/
|
||||
_tooltip(xPos, width) {
|
||||
let { xLabel, yLabel, xUnit, yUnit, func, series } = this.props;
|
||||
let { xScale, yScale } = this.state;
|
||||
let { formats, translate } = this.context.language;
|
||||
let x0 = xScale.invert(xPos),
|
||||
y0 = func(x0),
|
||||
tips = this.tipContainer,
|
||||
yTotal = 0,
|
||||
flip = (xPos / width > 0.50),
|
||||
tipWidth = 0,
|
||||
tipHeightPx = tips.selectAll('rect').node().getBoundingClientRect().height;
|
||||
|
||||
|
||||
xPos = xScale(x0); // Clamp xPos
|
||||
|
||||
tips.selectAll('text.text-tip.y').text(function(d, i) {
|
||||
let yVal = series ? y0[series[i]] : y0;
|
||||
yTotal += yVal;
|
||||
return (series ? translate(series[i]) : '') + ' ' + formats.f2(yVal);
|
||||
}).append('tspan').attr('class', 'metric').text(yUnit ? ' ' + yUnit : '');
|
||||
|
||||
tips.selectAll('text').each(function() {
|
||||
if (this.getBBox().width > tipWidth) {
|
||||
tipWidth = Math.ceil(this.getBBox().width);
|
||||
}
|
||||
});
|
||||
|
||||
let tipY = Math.floor(yScale(yTotal / (series ? series.length : 1)) - (tipHeightPx / 2));
|
||||
|
||||
tipWidth += 8;
|
||||
tips.attr('transform', 'translate(' + xPos + ',' + tipY + ')');
|
||||
tips.selectAll('text.text-tip').attr('x', flip ? -12 : 12).style('text-anchor', flip ? 'end' : 'start');
|
||||
tips.selectAll('text.text-tip.x').text(formats.f2(x0)).append('tspan').attr('class', 'metric').text(' ' + xUnit);
|
||||
tips.selectAll('rect').attr('width', tipWidth + 4).attr('x', flip ? -tipWidth - 12 : 8).attr('y', 0).style('text-anchor', flip ? 'end' : 'start');
|
||||
this.markersContainer.selectAll('circle').attr('cx', xPos).attr('cy', (d, i) => yScale(series ? y0[series[i]] : y0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Update dimensions based on properties and scale
|
||||
* @param {Object} props React Component properties
|
||||
* @param {number} scale size ratio / scale
|
||||
* @param {number} width current width of the container
|
||||
* @returns {Object} calculated dimensions
|
||||
*/
|
||||
_updateDimensions(props, scale, width) {
|
||||
const { xMax, xMin, yMin, yMax } = props;
|
||||
const innerWidth = width - MARGIN.left - MARGIN.right;
|
||||
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);
|
||||
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
|
||||
return { innerWidth, outerHeight, innerHeight };
|
||||
}
|
||||
|
||||
/**
|
||||
* Show tooltip
|
||||
* @param {SyntheticEvent} e Event
|
||||
*/
|
||||
_showTip(e) {
|
||||
e.preventDefault();
|
||||
this.tipContainer.style('display', null);
|
||||
this.markersContainer.style('display', null);
|
||||
this._moveTip(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Move and update tooltip
|
||||
* @param {SyntheticEvent} e Event
|
||||
* @param {number} width current container width
|
||||
*/
|
||||
_moveTip(e, width) {
|
||||
let clientX = e.touches ? e.touches[0].clientX : e.clientX;
|
||||
this._tooltip(Math.round(clientX - e.currentTarget.getBoundingClientRect().left), width);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide tooltip
|
||||
* @param {SyntheticEvent} e Event
|
||||
*/
|
||||
_hideTip(e) {
|
||||
e.preventDefault();
|
||||
this.tipContainer.style('display', 'none');
|
||||
this.markersContainer.style('display', 'none');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update series generated from props
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} state React Component state
|
||||
*/
|
||||
_updateSeries(props, state) {
|
||||
let { func, xMin, xMax, series, points } = props;
|
||||
let delta = (xMax - xMin) / points;
|
||||
let seriesData = new Array(points);
|
||||
|
||||
if (delta) {
|
||||
seriesData = new Array(points);
|
||||
for (let i = 0, x = xMin; i < points; i++) {
|
||||
seriesData[i] = [x, func(x)];
|
||||
x += delta;
|
||||
}
|
||||
seriesData[points - 1] = [xMax, func(xMax)];
|
||||
} else {
|
||||
let yVal = func(xMin);
|
||||
seriesData = [[0, yVal], [1, yVal]];
|
||||
}
|
||||
|
||||
const markerElems = [];
|
||||
const detailElems = [<text key='lbl' className='text-tip x' y='1.25em'/>];
|
||||
const seriesLines = [];
|
||||
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(<text key={i} className='text-tip y' strokeWidth={0} fill={props.colors[i]} y={1.25 * (i + 2) + 'em'}/>);
|
||||
markerElems.push(<circle key={i} className='marker' r='4' />);
|
||||
}
|
||||
|
||||
const tipHeight = 2 + (1.2 * (seriesLines ? seriesLines.length : 0.8));
|
||||
|
||||
this.setState({ markerElems, detailElems, seriesLines, seriesData, tipHeight });
|
||||
}
|
||||
|
||||
/**
|
||||
* Update dimensions and series data based on props and context.
|
||||
*/
|
||||
componentWillMount() {
|
||||
this._updateSeries(this.props, this.state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update state based on property and context changes
|
||||
* @param {Object} nextProps Incoming/Next properties
|
||||
* @param {Object} nextContext Incoming/Next conext
|
||||
*/
|
||||
componentWillReceiveProps(nextProps, nextContext) {
|
||||
const props = this.props;
|
||||
|
||||
if (props.code != nextProps.code) {
|
||||
this._updateSeries(nextProps, this.state);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the chart
|
||||
* @return {React.Component} Chart SVG
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<ContainerDimensions>
|
||||
{ ({ width, height }) => {
|
||||
const { innerWidth, outerHeight, innerHeight } = this._updateDimensions(this.props, this.context.sizeRatio, width, height);
|
||||
const { xMin, xMax, xLabel, yLabel, xUnit, yUnit, xMark, colors } = this.props;
|
||||
const { tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state;
|
||||
const lines = seriesLines.map((line, i) => <path key={i} className='line' fill='none' stroke={colors[i]} strokeWidth='1' d={line(seriesData)} />).reverse();
|
||||
|
||||
const markX = xMark ? innerWidth * (xMark - xMin) / (xMax - xMin) : 0;
|
||||
const xmark = xMark ? <path key={'mark'} className='line' fill='none' strokeDasharray='5,5' stroke={'#ff8c0d'} strokeWidth='1' d={'M ' + markX + ' ' + innerHeight + ' L ' + markX + ' 0'} /> : '';
|
||||
return (
|
||||
<div width={width} height={height}>
|
||||
<svg style={{ width: '100%', height: outerHeight }}>
|
||||
<g transform={`translate(${MARGIN.left},${MARGIN.top})`}>
|
||||
<g>{xmark}</g>
|
||||
<g>{lines}</g>
|
||||
<g className='x axis' ref={(elem) => d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}>
|
||||
<text className='cap' y='30' dy='.1em' x={innerWidth / 2} style={{ textAnchor: 'middle' }}>
|
||||
<tspan>{xLabel}</tspan>
|
||||
<tspan className='metric'> ({xUnit})</tspan>
|
||||
</text>
|
||||
</g>
|
||||
<g className='y axis' ref={(elem) => d3.select(elem).call(this.yAxis)}>
|
||||
<text className='cap' transform='rotate(-90)' y='-50' dy='.1em' x={innerHeight / -2} style={{ textAnchor: 'middle' }}>
|
||||
<tspan>{yLabel}</tspan>
|
||||
{ yUnit && <tspan className='metric'> ({yUnit})</tspan> }
|
||||
</text>
|
||||
</g>
|
||||
<g ref={(g) => this.tipContainer = d3.select(g)} style={{ display: 'none' }}>
|
||||
<rect className='tooltip' height={tipHeight + 'em'}></rect>
|
||||
{detailElems}
|
||||
</g>
|
||||
<g ref={(g) => this.markersContainer = d3.select(g)} style={{ display: 'none' }}>
|
||||
{markerElems}
|
||||
</g>
|
||||
<rect
|
||||
fillOpacity='0'
|
||||
height={innerHeight}
|
||||
width={innerWidth + 1}
|
||||
onMouseEnter={this._showTip}
|
||||
onTouchStart={this._showTip}
|
||||
onMouseLeave={this._hideTip}
|
||||
onTouchEnd={this._hideTip}
|
||||
onMouseMove={e => this._moveTip(e, width)}
|
||||
onTouchMove={e => this._moveTip(e, width)}
|
||||
/>
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</ContainerDimensions>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
93
src/app/components/ModalBatchOrbis.jsx
Normal file
93
src/app/components/ModalBatchOrbis.jsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import request from 'superagent';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { orbisUpload } from '../utils/ShortenUrl';
|
||||
import Persist from '../stores/Persist';
|
||||
|
||||
/**
|
||||
* Permalink modal
|
||||
*/
|
||||
export default class ModalBatchOrbis extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
ships: PropTypes.any.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
orbisCreds: Persist.getOrbisCreds(),
|
||||
resp: ''
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Send ship to Orbis.zone
|
||||
* @param {SyntheticEvent} e React Event
|
||||
* @return {Promise} Promise sending post request to orbis
|
||||
*/
|
||||
sendToOrbis(e) {
|
||||
let agent;
|
||||
try {
|
||||
agent = request.agent(); // apparently this crashes somehow
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
if (!agent) {
|
||||
agent = request;
|
||||
}
|
||||
const API_ORBIS = 'https://orbis.zone/api/builds/add/batch';
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
agent
|
||||
.post(API_ORBIS)
|
||||
.withCredentials()
|
||||
.redirects(0)
|
||||
.set('Content-Type', 'application/json')
|
||||
.send(this.props.ships)
|
||||
.end((err, response) => {
|
||||
console.log(response);
|
||||
if (err) {
|
||||
console.error(err);
|
||||
this.setState({ resp: response.text });
|
||||
reject('Bad Request');
|
||||
} else {
|
||||
this.setState({ resp: 'All builds uploaded. Check https://orbis.zone' });
|
||||
resolve('All builds uploaded. Check https://orbis.zone');
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
reject(e.message ? e.message : e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the modal
|
||||
* @return {React.Component} Modal Content
|
||||
*/
|
||||
render() {
|
||||
let translate = this.context.language.translate;
|
||||
this.sendToOrbis = this.sendToOrbis.bind(this);
|
||||
|
||||
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
|
||||
<h2>{translate('permalink')}</h2>
|
||||
<br/>
|
||||
<a className='button' href="https://orbis.zone/api/auth">Log in / signup to Orbis</a>
|
||||
<br/><br/>
|
||||
<h3 >{translate('success')}</h3>
|
||||
<input value={this.state.resp} readOnly size={25} onFocus={ (e) => e.target.select() }/>
|
||||
<br/><br/>
|
||||
<p>Orbis.zone is currently in a trial period, and may be wiped at any time as development progresses. Some elements are also still placeholders.</p>
|
||||
<button className={'l cb dismiss cap'} disabled={!!this.state.failed} onClick={this.sendToOrbis}>{translate('PHASE_UPLOAD_ORBIS')}</button>
|
||||
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import Persist from '../stores/Persist';
|
||||
* Delete All saved data modal
|
||||
*/
|
||||
export default class ModalDeleteAll extends TranslatedComponent {
|
||||
|
||||
/**
|
||||
* Delete everything and hide the modal
|
||||
*/
|
||||
|
||||
@@ -126,7 +126,11 @@ export default class ModalImport extends TranslatedComponent {
|
||||
if (importData.builds && typeof importData.builds == 'object') {
|
||||
for (let shipId in importData.builds) {
|
||||
for (let buildName in importData.builds[shipId]) {
|
||||
validateBuild(shipId, importData.builds[shipId][buildName], buildName);
|
||||
try {
|
||||
validateBuild(shipId, importData.builds[shipId][buildName], buildName);
|
||||
} catch (err) {
|
||||
delete importData.builds[shipId][buildName];
|
||||
}
|
||||
}
|
||||
}
|
||||
this.setState({ builds: importData.builds });
|
||||
|
||||
117
src/app/components/ModalOrbis.jsx
Normal file
117
src/app/components/ModalOrbis.jsx
Normal file
@@ -0,0 +1,117 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { orbisUpload } from '../utils/ShortenUrl';
|
||||
import Persist from '../stores/Persist';
|
||||
|
||||
/**
|
||||
* Permalink modal
|
||||
*/
|
||||
export default class ModalOrbis extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
ship: PropTypes.any.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
orbisCreds: Persist.getOrbisCreds(),
|
||||
orbisUrl: '...',
|
||||
authenticatedStatus: 'Checking...'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Send ship to Orbis.zone
|
||||
* @param {SyntheticEvent} e React Event
|
||||
*/
|
||||
sendToOrbis(e) {
|
||||
const target = e.target;
|
||||
target.disabled = true;
|
||||
this.setState({ orbisUrl: 'Sending...' }, () => {
|
||||
orbisUpload(this.props.ship, this.state.orbisCreds)
|
||||
.then(orbisUrl => {
|
||||
target.disabled = false;
|
||||
this.setState({ orbisUrl });
|
||||
})
|
||||
.catch(err => {
|
||||
target.disabled = false;
|
||||
this.setState({ orbisUrl: 'Error - ' + err });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Orbis.zone auth status
|
||||
* @returns {Object} auth status
|
||||
*/
|
||||
getOrbisAuthStatus() {
|
||||
return fetch('https://orbis.zone/api/checkauth', {
|
||||
credentials: 'include',
|
||||
mode: 'cors'
|
||||
})
|
||||
.then(data => data.json())
|
||||
.then(res => {
|
||||
this.setState({ authenticatedStatus: res.status || res.error });
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
this.setState({ authenticatedStatus: err.message });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for changing cmdr name
|
||||
* @param {SyntheticEvent} e React Event
|
||||
*/
|
||||
orbisPasswordHandler(e) {
|
||||
let password = e.target.value;
|
||||
this.setState({ orbisCreds: { email: this.state.orbisCreds.email, password } }, () => {
|
||||
Persist.setOrbisCreds(this.state.orbisCreds);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for changing cmdr name
|
||||
* @param {SyntheticEvent} e React Event
|
||||
*/
|
||||
orbisUsername(e) {
|
||||
let orbisUsername = e.target.value;
|
||||
this.setState({ orbisCreds: { email: orbisUsername, password: this.state.orbisCreds.password } }, () => {
|
||||
Persist.setOrbisCreds(this.state.orbisCreds);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the modal
|
||||
* @return {React.Component} Modal Content
|
||||
*/
|
||||
render() {
|
||||
let translate = this.context.language.translate;
|
||||
this.orbisPasswordHandler = this.orbisPasswordHandler.bind(this);
|
||||
this.orbisUsername = this.orbisUsername.bind(this);
|
||||
this.sendToOrbis = this.sendToOrbis.bind(this);
|
||||
this.getOrbisAuthStatus();
|
||||
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
|
||||
<h2>{translate('upload to orbis')}</h2>
|
||||
<br/>
|
||||
<label>Orbis auth status: </label>
|
||||
<input value={this.state.authenticatedStatus} readOnly size={25} onFocus={ (e) => e.target.select() }/>
|
||||
<br/><br/>
|
||||
<a className='button' href="https://orbis.zone/api/auth">Log in / signup to Orbis</a>
|
||||
<br/><br/>
|
||||
<h3 >{translate('Orbis link')}</h3>
|
||||
<input value={this.state.orbisUrl} readOnly size={25} onFocus={ (e) => e.target.select() }/>
|
||||
<br/><br/>
|
||||
<p>Orbis.zone is currently in a trial period, and may be wiped at any time as development progresses. Some elements are also still placeholders.</p>
|
||||
<button className={'l cb dismiss cap'} disabled={!!this.state.failed} onClick={this.sendToOrbis}>{translate('PHASE_UPLOAD_ORBIS')}</button>
|
||||
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,7 @@ export default class ModalPermalink extends TranslatedComponent {
|
||||
<h3 >{translate('shortened')}</h3>
|
||||
<input value={this.state.shortenedUrl} readOnly size={25} onFocus={ (e) => e.target.select() }/>
|
||||
<br/><br/>
|
||||
<p>s.orbis.zone is the new URL shortener domain, old eddp.co urls are considered end of life and could go down at any moment. Sorry for any inconvenience.</p>
|
||||
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import ShortenUrl from '../utils/ShortenUrl';
|
||||
import request from 'superagent';
|
||||
import Persist from '../stores/Persist';
|
||||
|
||||
/**
|
||||
@@ -22,7 +22,11 @@ export default class ModalShoppingList extends TranslatedComponent {
|
||||
this.state = {
|
||||
matsList: '',
|
||||
mats: {},
|
||||
matsPerGrade: Persist.getRolls()
|
||||
failed: false,
|
||||
cmdrName: Persist.getCmdr().selected,
|
||||
cmdrs: Persist.getCmdr().cmdrs,
|
||||
matsPerGrade: Persist.getRolls(),
|
||||
blueprints: []
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,6 +35,114 @@ export default class ModalShoppingList extends TranslatedComponent {
|
||||
*/
|
||||
componentDidMount() {
|
||||
this.renderMats();
|
||||
if (this.checkBrowserIsCompatible()) {
|
||||
this.getCommanders();
|
||||
this.registerBPs();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all blueprints needed to make a build.
|
||||
*/
|
||||
registerBPs() {
|
||||
const ship = this.props.ship;
|
||||
let blueprints = [];
|
||||
for (const module of ship.costList) {
|
||||
if (module.type === 'SHIP') {
|
||||
continue;
|
||||
}
|
||||
if (module.m && module.m.blueprint) {
|
||||
if (!module.m.blueprint.grade || !module.m.blueprint.grades) {
|
||||
continue;
|
||||
}
|
||||
if (module.m.blueprint.special) {
|
||||
console.log(module.m.blueprint.special);
|
||||
blueprints.push({ uuid: module.m.blueprint.special.uuid, number: 1 });
|
||||
}
|
||||
for (const g in module.m.blueprint.grades) {
|
||||
if (!module.m.blueprint.grades.hasOwnProperty(g)) {
|
||||
continue;
|
||||
}
|
||||
if (g > module.m.blueprint.grade) {
|
||||
continue;
|
||||
}
|
||||
blueprints.push({ uuid: module.m.blueprint.grades[g].uuid, number: this.state.matsPerGrade[g] });
|
||||
}
|
||||
}
|
||||
}
|
||||
this.setState({ blueprints });
|
||||
}
|
||||
|
||||
/**
|
||||
* Check browser isn't firefox.
|
||||
* @return {boolean} true if compatible, false if not.
|
||||
*/
|
||||
checkBrowserIsCompatible() {
|
||||
// Firefox 1.0+
|
||||
return typeof InstallTrigger === 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of commanders from EDEngineer.
|
||||
*/
|
||||
getCommanders() {
|
||||
request
|
||||
.get('http://localhost:44405/commanders')
|
||||
.end((err, res) => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
return this.setState({ failed: true });
|
||||
}
|
||||
const cmdrs = JSON.parse(res.text);
|
||||
if (!this.state.cmdrName) {
|
||||
this.setState({ cmdrName: cmdrs[0] });
|
||||
}
|
||||
this.setState({ cmdrs }, () => {
|
||||
Persist.setCmdr({ selected: this.state.cmdrName, cmdrs });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Send all blueprints to ED Engineer
|
||||
* @param {Event} event React event
|
||||
*/
|
||||
sendToEDEng(event) {
|
||||
event.preventDefault();
|
||||
const target = event.target;
|
||||
target.disabled = this.state.blueprints.length > 0;
|
||||
if (this.state.blueprints.length === 0) {
|
||||
target.innerText = 'No modded components.';
|
||||
target.disabled = true;
|
||||
setTimeout(() => {
|
||||
target.innerText = 'Send to EDEngineer';
|
||||
target.disabled = false;
|
||||
}, 3000);
|
||||
} else {
|
||||
target.innerText = 'Sending...';
|
||||
}
|
||||
let countSent = 0;
|
||||
let countTotal = this.state.blueprints.length;
|
||||
|
||||
for (const i of this.state.blueprints) {
|
||||
request
|
||||
.patch(`http://localhost:44405/${this.state.cmdrName}/shopping-list`)
|
||||
.field('uuid', i.uuid)
|
||||
.field('size', i.number)
|
||||
.end(err => {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
if (err.message !== 'Bad Request') {
|
||||
this.setState({ failed: true });
|
||||
}
|
||||
}
|
||||
countSent++;
|
||||
if (countSent === countTotal) {
|
||||
target.disabled = false;
|
||||
target.innerText = 'Send to EDEngineer';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,10 +160,16 @@ export default class ModalShoppingList extends TranslatedComponent {
|
||||
continue;
|
||||
}
|
||||
for (const g in module.m.blueprint.grades) {
|
||||
if (!module.m.blueprint.grades.hasOwnProperty(g)) {
|
||||
continue;
|
||||
}
|
||||
if (g > module.m.blueprint.grade) {
|
||||
continue;
|
||||
}
|
||||
for (const i in module.m.blueprint.grades[g].components) {
|
||||
if (!module.m.blueprint.grades[g].components.hasOwnProperty(i)) {
|
||||
continue;
|
||||
}
|
||||
if (mats[i]) {
|
||||
mats[i] += module.m.blueprint.grades[g].components[i] * this.state.matsPerGrade[g];
|
||||
} else {
|
||||
@@ -86,6 +204,18 @@ export default class ModalShoppingList extends TranslatedComponent {
|
||||
this.setState({ matsPerGrade: newState });
|
||||
Persist.setRolls(newState);
|
||||
this.renderMats();
|
||||
this.registerBPs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for changing cmdr name
|
||||
* @param {SyntheticEvent} e React Event
|
||||
*/
|
||||
cmdrChangeHandler(e) {
|
||||
let cmdrName = e.target.value;
|
||||
this.setState({ cmdrName }, () => {
|
||||
Persist.setCmdr({ selected: this.state.cmdrName, cmdrs: this.state.cmdrs });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -95,6 +225,9 @@ export default class ModalShoppingList extends TranslatedComponent {
|
||||
render() {
|
||||
let translate = this.context.language.translate;
|
||||
this.changeHandler = this.changeHandler.bind(this);
|
||||
const compatible = this.checkBrowserIsCompatible();
|
||||
this.cmdrChangeHandler = this.cmdrChangeHandler.bind(this);
|
||||
this.sendToEDEng = this.sendToEDEng.bind(this);
|
||||
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
|
||||
<h2>{translate('PHRASE_SHOPPING_MATS')}</h2>
|
||||
<label>Grade 1 rolls </label>
|
||||
@@ -114,6 +247,15 @@ export default class ModalShoppingList extends TranslatedComponent {
|
||||
<div>
|
||||
<textarea className='cb json' readOnly value={this.state.matsList} />
|
||||
</div>
|
||||
<label hidden={!compatible} className={'l cap'}>CMDR Name </label>
|
||||
<br/>
|
||||
<select hidden={!compatible} className={'cmdr-select l cap'} onChange={this.cmdrChangeHandler} defaultValue={this.state.cmdrName}>
|
||||
{this.state.cmdrs.map(e => <option key={e}>{e}</option>)}
|
||||
</select>
|
||||
<br/>
|
||||
<p hidden={!this.state.failed} id={'failed'} className={'l'}>Failed to send to EDEngineer (Launch EDEngineer and make sure the API is started then refresh the page.)</p>
|
||||
<p hidden={compatible} id={'browserbad'} className={'l'}>Sending to EDEngineer is not compatible with Firefox's security settings. Please try again with Chrome.</p>
|
||||
<button className={'l cb dismiss cap'} disabled={!!this.state.failed || !compatible} onClick={this.sendToEDEng}>{translate('Send To EDEngineer')}</button>
|
||||
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
|
||||
</div>;
|
||||
}
|
||||
|
||||
@@ -3,12 +3,12 @@ import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import cn from 'classnames';
|
||||
import NumberEditor from 'react-number-editor';
|
||||
import { isValueBeneficial } from '../utils/BlueprintFunctions';
|
||||
|
||||
/**
|
||||
* Modification
|
||||
*/
|
||||
export default class Modification extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
ship: PropTypes.object.isRequired,
|
||||
m: PropTypes.object.isRequired,
|
||||
@@ -38,30 +38,31 @@ export default class Modification extends TranslatedComponent {
|
||||
* in a value by hand
|
||||
*/
|
||||
_updateValue(value) {
|
||||
const name = this.props.name;
|
||||
let scaledValue = Math.round(Number(value) * 100);
|
||||
// Limit to +1000% / -99.99%
|
||||
if (scaledValue > 100000) {
|
||||
scaledValue = 100000;
|
||||
value = 1000;
|
||||
}
|
||||
if (scaledValue < -9999) {
|
||||
scaledValue = -9999;
|
||||
value = -99.99;
|
||||
}
|
||||
|
||||
let m = this.props.m;
|
||||
let ship = this.props.ship;
|
||||
ship.setModification(m, name, scaledValue, true);
|
||||
|
||||
this.setState({ value });
|
||||
let reCast = String(Number(value));
|
||||
if (reCast.endsWith(value) || reCast.startsWith(value)) {
|
||||
let { m, name, ship } = this.props;
|
||||
value = Math.max(Math.min(value, 50000), -50000);
|
||||
ship.setModification(m, name, value, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when a key is pressed down with focus on the number editor.
|
||||
* @param {SyntheticEvent} event Key down event
|
||||
*/
|
||||
_keyDown(event) {
|
||||
if (event.key == 'Enter') {
|
||||
this._updateFinished();
|
||||
}
|
||||
this.props.onKeyDown(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when an update to slider value is finished i.e. when losing focus
|
||||
*
|
||||
* pnellesen (24/05/2018): added value check below - this should prevent experimental effects from being recalculated
|
||||
* with each onBlur event, even when no change has actually been made to the field.
|
||||
*
|
||||
* pnellesen (24/05/2018): added value check below - this should prevent experimental effects from being recalculated
|
||||
* with each onBlur event, even when no change has actually been made to the field.
|
||||
*/
|
||||
_updateFinished() {
|
||||
if (this.props.value != this.state.value) {
|
||||
@@ -75,28 +76,55 @@ export default class Modification extends TranslatedComponent {
|
||||
* @return {React.Component} modification
|
||||
*/
|
||||
render() {
|
||||
let translate = this.context.language.translate;
|
||||
let { translate, formats, units } = this.context.language;
|
||||
let { m, name } = this.props;
|
||||
let modValue = m.getChange(name);
|
||||
|
||||
if (name === 'damagedist') {
|
||||
// We don't show damage distribution
|
||||
return null;
|
||||
}
|
||||
|
||||
let symbol;
|
||||
if (name === 'jitter') {
|
||||
symbol = '°';
|
||||
} else if (name !== 'burst' && name != 'burstrof') {
|
||||
symbol = '%';
|
||||
}
|
||||
if (symbol) {
|
||||
symbol = ' (' + symbol + ')';
|
||||
}
|
||||
let inputClassNames = {
|
||||
'cb': true,
|
||||
'greyed-out': !this.props.highlight
|
||||
};
|
||||
|
||||
return (
|
||||
<div onBlur={this._updateFinished.bind(this)} className={'cb'} key={name} ref={ modItem => this.props.modItems[name] = modItem }>
|
||||
<div className={'cb'}>{translate(name, m.grp)}{symbol}</div>
|
||||
<NumberEditor className={'cb'} style={{ width: '90%', textAlign: 'center' }} step={0.01} stepModifier={1} decimals={2} value={this.state.value} onValueChange={this._updateValue.bind(this)} onKeyDown={ this.props.onKeyDown } />
|
||||
<div onBlur={this._updateFinished.bind(this)} key={name}
|
||||
className={cn('cb', 'modification-container')}
|
||||
ref={ modItem => this.props.modItems[name] = modItem }>
|
||||
<span className={'cb'}>{translate(name, m.grp)}</span>
|
||||
<span className={'header-adjuster'}></span>
|
||||
<table style={{ width: '100%' }}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className={'input-container'}>
|
||||
<span>
|
||||
{this.props.editable ?
|
||||
<NumberEditor className={cn(inputClassNames)} value={this.state.value}
|
||||
decimals={2} style={{ textAlign: 'right' }} step={0.01}
|
||||
stepModifier={1} onKeyDown={this._keyDown.bind(this)}
|
||||
onValueChange={this._updateValue.bind(this)} /> :
|
||||
<input type="text" value={formats.f2(this.state.value)}
|
||||
disabled className={cn('number-editor', 'greyed-out')}
|
||||
style={{ textAlign: 'right', cursor: 'inherit' }}/>
|
||||
}
|
||||
<span className={'unit-container'}>
|
||||
{units[m.getStoredUnitFor(name)]}
|
||||
</span>
|
||||
</span>
|
||||
</td>
|
||||
<td style={{ textAlign: 'center' }} className={
|
||||
modValue ?
|
||||
isValueBeneficial(name, modValue) ? 'secondary' : 'warning' :
|
||||
''
|
||||
}>
|
||||
{formats.f2(modValue / 100) || 0}%
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as _ from 'lodash';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { isEmpty, stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
import cn from 'classnames';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
import Modification from './Modification';
|
||||
@@ -15,11 +15,14 @@ import {
|
||||
specialToolTip
|
||||
} from '../utils/BlueprintFunctions';
|
||||
|
||||
const MODIFICATIONS_COMPARATOR = (mod1, mod2) => {
|
||||
return mod1.props.name.localeCompare(mod2.props.name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Modifications menu
|
||||
*/
|
||||
export default class ModificationsMenu extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
ship: PropTypes.object.isRequired,
|
||||
m: PropTypes.object.isRequired,
|
||||
@@ -124,7 +127,7 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
// Initial modification menu
|
||||
event.preventDefault();
|
||||
this.modItems[this.lastModId].focus();
|
||||
return;
|
||||
return;
|
||||
} else if (event.currentTarget.className.indexOf('button-inline-menu') >= 0 && event.currentTarget.previousElementSibling == null && this.lastNeId != null && this.modItems[this.lastNeId] != null) {
|
||||
// shift-tab on first element in modifications menu. set focus to last number editor field if open
|
||||
event.preventDefault();
|
||||
@@ -205,15 +208,26 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
*/
|
||||
_renderModifications(props) {
|
||||
const { m, onChange, ship } = props;
|
||||
const modifiableModifications = [];
|
||||
const modifications = [];
|
||||
for (const modName of Modifications.modules[m.grp].modifications) {
|
||||
if (!Modifications.modifications[modName].hidden) {
|
||||
const key = modName + (m.getModValue(modName) / 100 || 0);
|
||||
const editable = modName !== 'fallofffromrange';
|
||||
const highlight = m.blueprint.grades[m.blueprint.grade].features[modName];
|
||||
this.lastNeId = modName;
|
||||
modifications.push(<Modification key={ key } ship={ ship } m={ m } name={ modName } value={ m.getModValue(modName) / 100 || 0 } onChange={ onChange } onKeyDown={ this._keyDown } modItems={ this.modItems } handleModChange = {this._handleModChange} />);
|
||||
(editable && highlight ? modifiableModifications : modifications).push(
|
||||
<Modification key={ key } ship={ ship } m={ m } highlight={highlight}
|
||||
value={m.getPretty(modName) || 0} modItems={this.modItems}
|
||||
onChange={onChange} onKeyDown={this._keyDown} name={modName}
|
||||
editable={editable} handleModChange = {this._handleModChange} />
|
||||
);
|
||||
}
|
||||
}
|
||||
return modifications;
|
||||
|
||||
modifiableModifications.sort(MODIFICATIONS_COMPARATOR);
|
||||
modifications.sort(MODIFICATIONS_COMPARATOR);
|
||||
return modifiableModifications.concat(modifications);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -446,10 +460,10 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
}
|
||||
return (
|
||||
<div
|
||||
className={cn('select', this.props.className)}
|
||||
onClick={(e) => e.stopPropagation() }
|
||||
onContextMenu={stopCtxPropagation}
|
||||
ref={modItem => this.modItems['modMainDiv'] = modItem}
|
||||
className={cn('select', this.props.className)}
|
||||
onClick={(e) => e.stopPropagation() }
|
||||
onContextMenu={stopCtxPropagation}
|
||||
ref={modItem => this.modItems['modMainDiv'] = modItem}
|
||||
>
|
||||
{ showBlueprintsMenu | showSpecialsMenu ? '' : haveBlueprint ?
|
||||
<div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={termtip.bind(null, blueprintTt)} onMouseOut={tooltip.bind(null, null)} onClick={_toggleBlueprintsMenu} onKeyDown={ this._keyDown } ref={modItems => this.modItems[this.firstBPLabel] = modItems}>{blueprintLabel}</div> :
|
||||
@@ -458,11 +472,11 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
{ showSpecial & !showSpecialsMenu ? <div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: specialMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={specialTt ? termtip.bind(null, specialTt) : null} onMouseOut={specialTt ? tooltip.bind(null, null) : null} onClick={_toggleSpecialsMenu} onKeyDown={ this._keyDown }>{specialLabel}</div> : null }
|
||||
{ showSpecialsMenu ? specials : null }
|
||||
{ showReset ? <div tabIndex="0" className={'section-menu button-inline-menu warning'} style={{ cursor: 'pointer' }} onClick={_reset} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')} onMouseOut={tooltip.bind(null, null)}> { translate('reset') } </div> : null }
|
||||
{ showRolls ?
|
||||
{ showRolls ?
|
||||
|
||||
<table style={{ width: '100%', backgroundColor: 'transparent' }}>
|
||||
<tbody>
|
||||
{ showRolls ?
|
||||
<table style={{ width: '100%', backgroundColor: 'transparent' }}>
|
||||
<tbody>
|
||||
{ showRolls ?
|
||||
<tr>
|
||||
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: false }) }> { translate('roll') }: </td>
|
||||
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 0 }) } style={{ cursor: 'pointer' }} onClick={_rollWorst} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')} onMouseOut={tooltip.bind(null, null)}> { translate('0%') } </td>
|
||||
@@ -470,7 +484,7 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 100 })} style={{ cursor: 'pointer' }} onClick={_rollFull} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')} onMouseOut={tooltip.bind(null, null)}> { translate('100%') } </td>
|
||||
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === null || blueprintCv % 50 != 0 })} style={{ cursor: 'pointer' }} onClick={_rollRandom} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')} onMouseOut={tooltip.bind(null, null)}> { translate('random') } </td>
|
||||
</tr> : null }
|
||||
</tbody>
|
||||
</tbody>
|
||||
</table> : null }
|
||||
{ showMods ? <hr /> : null }
|
||||
{ showMods ?
|
||||
|
||||
@@ -35,13 +35,13 @@ export default class Movement extends TranslatedComponent {
|
||||
return (
|
||||
<span id='movement'>
|
||||
<svg viewBox='0 0 600 600' fillRule="evenodd" clipRule="evenodd">
|
||||
// Axes
|
||||
{/* Axes */}
|
||||
<path d="M150 250v300" strokeWidth='1'/>
|
||||
<path d="M150 250l236 236" strokeWidth='1'/>
|
||||
<path d="M150 250l350 -200" strokeWidth='1'/>
|
||||
// End Arrow
|
||||
{/* End Arrow */}
|
||||
<path d="M508 43.3L487 67l-10-17.3 31-6.4z"/>
|
||||
// Axes arcs and arrows
|
||||
{/* Axes arcs and arrows */}
|
||||
<path d="M71.7 251.7C64.2 259.2 60 269.4 60 280c0 22 18 40 40 40s40-18 40-40c0-10.6-4.2-20.8-11.7-28.3 7.5 7.5 11.7 17.7 11.7 28.3 0 22-18 40-40 40s-40-18-40-40c0-10.6 4.2-20.8 11.7-28.3z" strokeWidth='4' transform="matrix(.6 0 0 .3 87.5 376.3)"/>
|
||||
<path d="M142.8 453l-13.2 8.7-2.6-9.7 15.8 1z"/>
|
||||
<path d="M144.7 451.6l.5 1.6-16.2 10.6h-.4l-3.5-13 .7-.4 19.3 1.2zm-14.2 7.7l7.7-5-9.2-.7 1.5 5.7zm25.7-6.3l15.8-1-2.6 9.7-13.2-8.8z"/>
|
||||
@@ -57,13 +57,13 @@ export default class Movement extends TranslatedComponent {
|
||||
<path d="M359.5 422.4l-1.2 19.3-1.6.4-10.7-16 .2-.2 13-3.4.3.4zm-9 5l5.2 7.8.6-9.3-5.7 1.2zm-10.5 24l-13.2 8.6-2.6-9.7 15.8 1z"/>
|
||||
<path d="M342 450l.4 1.5-16.2 10.7-.4-.2-3.5-13 .3-.3L342 450zm-14.3 7.6l7.7-5-9.2-.6 1.5 5.6z"/>
|
||||
|
||||
// Speed
|
||||
{/* Speed */}
|
||||
<text x="470" y="30" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcSpeed(eng, fuel, cargo, boost)) + 'm/s' : '-'}</text>
|
||||
// Pitch
|
||||
{/* Pitch */}
|
||||
<text x="355" y="410" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcPitch(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
|
||||
// Roll
|
||||
{/* Roll */}
|
||||
<text x="450" y="110" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcRoll(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
|
||||
// Yaw
|
||||
{/* Yaw */}
|
||||
<text x="160" y="430" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcYaw(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
|
||||
</svg>
|
||||
</span>);
|
||||
|
||||
@@ -5,7 +5,6 @@ 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
|
||||
@@ -42,6 +41,63 @@ export function weaponComparator(translate, propComparator, desc) {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a tooltip that shows damage by type.
|
||||
* @param {function} translate Translation function
|
||||
* @param {Object} formats Object that holds format functions
|
||||
* @param {Calc.SDps} sdpsObject Object that holds sdps split up by type
|
||||
* @returns {Array} Tooltip
|
||||
*/
|
||||
function getSDpsTooltip(translate, formats, sdpsObject) {
|
||||
return Object.keys(sdpsObject).filter(key => sdpsObject[key])
|
||||
.map(key => {
|
||||
return (
|
||||
<div key={key}>
|
||||
{translate(key) + ' ' + formats.f1(sdpsObject[key])}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a data object used by {@link PieChart} that shows damage by type.
|
||||
* @param {function} translate Translation function
|
||||
* @param {Calc.SDps} sdpsObject Object that holds sdps split up by type
|
||||
* @returns {Object} Data object
|
||||
*/
|
||||
function getSDpsData(translate, sdpsObject) {
|
||||
return Object.keys(sdpsObject).map(key => {
|
||||
return {
|
||||
value: Math.round(sdpsObject[key]),
|
||||
label: translate(key)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all damage of `add` onto `addOn`.
|
||||
* @param {Calc.SDps} addOn Object that holds sdps split up by type (will be mutated)
|
||||
* @param {Calc.SDps} add Object that holds sdps split up by type
|
||||
*/
|
||||
function addSDps(addOn, add) {
|
||||
Object.keys(addOn).map(k => addOn[k] += (add[k] ? add[k] : 0));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the overall sdps of an sdps object.
|
||||
* @param {Calc.SDps} sdpsObject Object that holds sdps spluit up by type
|
||||
*/
|
||||
function sumSDps(sdpsObject) {
|
||||
if (sdpsObject.total) {
|
||||
return sdpsObject.total;
|
||||
}
|
||||
|
||||
return Object.keys(sdpsObject).reduce(
|
||||
(acc, k) => acc + (sdpsObject[k] ? sdpsObject[k] : 0),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Offence information
|
||||
* Offence information consists of four panels:
|
||||
@@ -70,7 +126,7 @@ export default class Offence extends TranslatedComponent {
|
||||
this._sort = this._sort.bind(this);
|
||||
|
||||
const damage = Calc.offenceMetrics(props.ship, props.opponent, props.wep, props.opponentSys, props.engagementrange);
|
||||
this.state = {
|
||||
this.state = {
|
||||
predicate: 'n',
|
||||
desc: true,
|
||||
damage
|
||||
@@ -144,51 +200,36 @@ export default class Offence extends TranslatedComponent {
|
||||
|
||||
const timeToDrain = Calc.timeToDrainWep(ship, wep);
|
||||
|
||||
let absoluteShieldsSDps = 0;
|
||||
let explosiveShieldsSDps = 0;
|
||||
let kineticShieldsSDps = 0;
|
||||
let thermalShieldsSDps = 0;
|
||||
let absoluteArmourSDps = 0;
|
||||
let explosiveArmourSDps = 0;
|
||||
let kineticArmourSDps = 0;
|
||||
let thermalArmourSDps = 0;
|
||||
|
||||
let totalSEps = 0;
|
||||
let totalSDpsObject = { 'absolute': 0, 'explosive': 0, 'kinetic': 0, 'thermal': 0 };
|
||||
let shieldsSDpsObject = { 'absolute': 0, 'explosive': 0, 'kinetic': 0, 'thermal': 0 };
|
||||
let armourSDpsObject = { 'absolute': 0, 'explosive': 0, 'kinetic': 0, 'thermal': 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;
|
||||
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;
|
||||
addSDps(totalSDpsObject, weapon.sdps.base);
|
||||
addSDps(shieldsSDpsObject, weapon.sdps.shields);
|
||||
addSDps(armourSDpsObject, weapon.sdps.armour);
|
||||
|
||||
const baseSDpsTooltipDetails = getSDpsTooltip(translate, formats, weapon.sdps.base);
|
||||
|
||||
const effectivenessShieldsTooltipDetails = [];
|
||||
effectivenessShieldsTooltipDetails.push(<div key='range'>{translate('range') + ' ' + formats.pct1(weapon.effectiveness.shields.range)}</div>);
|
||||
effectivenessShieldsTooltipDetails.push(<div key='resistance'>{translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.shields.resistance)}</div>);
|
||||
effectivenessShieldsTooltipDetails.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(weapon.effectiveness.shields.sys)}</div>);
|
||||
|
||||
const effectiveShieldsSDpsTooltipDetails = [];
|
||||
if (weapon.sdps.shields.absolute) effectiveShieldsSDpsTooltipDetails.push(<div key='absolute'>{translate('absolute') + ' ' + formats.f1(weapon.sdps.shields.absolute)}</div>);
|
||||
if (weapon.sdps.shields.explosive) effectiveShieldsSDpsTooltipDetails.push(<div key='explosive'>{translate('explosive') + ' ' + formats.f1(weapon.sdps.shields.explosive)}</div>);
|
||||
if (weapon.sdps.shields.kinetic) effectiveShieldsSDpsTooltipDetails.push(<div key='kinetic'>{translate('kinetic') + ' ' + formats.f1(weapon.sdps.shields.kinetic)}</div>);
|
||||
if (weapon.sdps.shields.thermal) effectiveShieldsSDpsTooltipDetails.push(<div key='thermal'>{translate('thermal') + ' ' + formats.f1(weapon.sdps.shields.thermal)}</div>);
|
||||
const effectiveShieldsSDpsTooltipDetails = getSDpsTooltip(translate, formats, weapon.sdps.armour);
|
||||
|
||||
const effectivenessArmourTooltipDetails = [];
|
||||
effectivenessArmourTooltipDetails.push(<div key='range'>{translate('range') + ' ' + formats.pct1(weapon.effectiveness.armour.range)}</div>);
|
||||
effectivenessArmourTooltipDetails.push(<div key='resistance'>{translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.armour.resistance)}</div>);
|
||||
effectivenessArmourTooltipDetails.push(<div key='hardness'>{translate('hardness') + ' ' + formats.pct1(weapon.effectiveness.armour.hardness)}</div>);
|
||||
const effectiveArmourSDpsTooltipDetails = [];
|
||||
if (weapon.sdps.armour.absolute) effectiveArmourSDpsTooltipDetails.push(<div key='absolute'>{translate('absolute') + ' ' + formats.f1(weapon.sdps.armour.absolute)}</div>);
|
||||
if (weapon.sdps.armour.explosive) effectiveArmourSDpsTooltipDetails.push(<div key='explosive'>{translate('explosive') + ' ' + formats.f1(weapon.sdps.armour.explosive)}</div>);
|
||||
if (weapon.sdps.armour.kinetic) effectiveArmourSDpsTooltipDetails.push(<div key='kinetic'>{translate('kinetic') + ' ' + formats.f1(weapon.sdps.armour.kinetic)}</div>);
|
||||
if (weapon.sdps.armour.thermal) effectiveArmourSDpsTooltipDetails.push(<div key='thermal'>{translate('thermal') + ' ' + formats.f1(weapon.sdps.armour.thermal)}</div>);
|
||||
|
||||
const effectiveArmourSDpsTooltipDetails = getSDpsTooltip(translate, formats, weapon.sdps.armour);
|
||||
|
||||
rows.push(
|
||||
<tr key={weapon.id}>
|
||||
@@ -199,27 +240,25 @@ export default class Offence extends TranslatedComponent {
|
||||
{weapon.classRating} {translate(weapon.name)}
|
||||
{weapon.engineering ? ' (' + weapon.engineering + ')' : null }
|
||||
</td>
|
||||
<td className='ri'><span onMouseOver={termtip.bind(null, baseSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.base.total)}</span></td>
|
||||
<td className='ri'><span onMouseOver={termtip.bind(null, effectiveShieldsSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.shields.total)}</span></td>
|
||||
<td className='ri'><span onMouseOver={termtip.bind(null, effectivenessShieldsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(weapon.effectiveness.shields.total)}</span></td>
|
||||
<td className='ri'><span onMouseOver={termtip.bind(null, effectiveArmourSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.armour.total)}</span></td>
|
||||
<td className='ri'><span onMouseOver={termtip.bind(null, effectivenessArmourTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(weapon.effectiveness.armour.total)}</span></td>
|
||||
</tr>);
|
||||
}
|
||||
}
|
||||
|
||||
const totalShieldsSDps = absoluteShieldsSDps + explosiveShieldsSDps + kineticShieldsSDps + thermalShieldsSDps;
|
||||
const totalArmourSDps = absoluteArmourSDps + explosiveArmourSDps + kineticArmourSDps + thermalArmourSDps;
|
||||
const totalSDps = sumSDps(totalSDpsObject);
|
||||
const totalSDpsTooltipDetails = getSDpsTooltip(translate, formats, totalSDpsObject);
|
||||
const totalSDpsData = getSDpsData(translate, totalSDpsObject);
|
||||
|
||||
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 totalShieldsSDps = sumSDps(shieldsSDpsObject);
|
||||
const totalShieldsSDpsTooltipDetails = getSDpsTooltip(translate, formats, shieldsSDpsObject);
|
||||
const shieldsSDpsData = getSDpsData(translate, shieldsSDpsObject);
|
||||
|
||||
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') });
|
||||
const totalArmourSDps = sumSDps(armourSDpsObject);
|
||||
const totalArmourSDpsTooltipDetails = getSDpsTooltip(translate, formats, armourSDpsObject);
|
||||
const armourSDpsData = getSDpsData(translate, armourSDpsObject);
|
||||
|
||||
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));
|
||||
@@ -227,24 +266,36 @@ export default class Offence extends TranslatedComponent {
|
||||
return (
|
||||
<span id='offence'>
|
||||
<div className='group full'>
|
||||
<table>
|
||||
<thead>
|
||||
<tr className='main'>
|
||||
<th rowSpan='2' className='sortable' onClick={sortOrder.bind(this, 'n')}>{translate('weapon')}</th>
|
||||
<th colSpan='2'>{translate('opponent\'s shields')}</th>
|
||||
<th colSpan='2'>{translate('opponent\'s armour')}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')} onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'esdpss')}>{'sdps'}</th>
|
||||
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_SHIELDS')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'es')}>{'eft'}</th>
|
||||
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'esdpsh')}>{'sdps'}</th>
|
||||
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'eh')}>{'eft'}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows}
|
||||
</tbody>
|
||||
</table>
|
||||
<table>
|
||||
<thead>
|
||||
<tr className='main'>
|
||||
<th rowSpan='2' className='sortable' onClick={sortOrder.bind(this, 'n')}>{translate('weapon')}</th>
|
||||
<th colSpan='1'>{translate('overall')}</th>
|
||||
<th colSpan='2'>{translate('opponent\'s shields')}</th>
|
||||
<th colSpan='2'>{translate('opponent\'s armour')}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')} onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'esdpss')}>{'sdps'}</th>
|
||||
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')} onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'esdpss')}>{'sdps'}</th>
|
||||
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_SHIELDS')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'es')}>{'eft'}</th>
|
||||
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'esdpsh')}>{'sdps'}</th>
|
||||
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'eh')}>{'eft'}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows}
|
||||
{rows.length > 0 &&
|
||||
<tr>
|
||||
<td></td>
|
||||
<td className='ri'><span onMouseOver={termtip.bind(null, totalSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>={formats.f1(totalSDps)}</span></td>
|
||||
<td className='ri'><span onMouseOver={termtip.bind(null, totalShieldsSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>={formats.f1(totalShieldsSDps)}</span></td>
|
||||
<td></td>
|
||||
<td className='ri'><span onMouseOver={termtip.bind(null, totalArmourSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>={formats.f1(totalArmourSDps)}</span></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2>{translate('offence metrics')}</h2>
|
||||
@@ -254,6 +305,10 @@ export default class Offence extends TranslatedComponent {
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_EFFECTIVE_SDPS_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_EFFECTIVE_SDPS_ARMOUR')}<br/>{formats.f1(totalArmourSDps)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_REMOVE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_REMOVE_ARMOUR')}<br/>{timeToDepleteArmour === Infinity ? translate('never') : formats.time(timeToDepleteArmour)}</h2>
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_OVERALL_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('overall damage')}</h2>
|
||||
<PieChart data={totalSDpsData} />
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield damage sources')}</h2>
|
||||
<PieChart data={shieldsSDpsData} />
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import Ship from '../shipyard/Ship';
|
||||
import Persist from '../stores/Persist';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import PowerManagement from './PowerManagement';
|
||||
|
||||
@@ -1,97 +1,92 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Measure from 'react-measure';
|
||||
import * as d3 from 'd3';
|
||||
|
||||
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
|
||||
const LABEL_COLOUR = '#000000';
|
||||
|
||||
/**
|
||||
* A pie chart
|
||||
*/
|
||||
export default class PieChart extends Component {
|
||||
|
||||
static propTypes = {
|
||||
data : PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
this.pie = d3.pie().value((d) => d.value);
|
||||
this.colors = CORIOLIS_COLOURS;
|
||||
this.arc = d3.arc();
|
||||
this.arc.innerRadius(0);
|
||||
|
||||
this.state = {
|
||||
dimensions: {
|
||||
width: 100,
|
||||
height: 100
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate a slice of the pie chart
|
||||
* @param {Object} d the data for this slice
|
||||
* @param {number} i the index of this slice
|
||||
* @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;
|
||||
|
||||
// Push the labels further out from the centre of the slice
|
||||
let [labelX, labelY] = this.arc.centroid(d);
|
||||
const labelTranslate = `translate(${labelX * 1.5}, ${labelY * 1.5})`;
|
||||
|
||||
// Put the keys in a line with equal spacing
|
||||
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 (
|
||||
<g key={`group-${i}`}>
|
||||
<path key={`arc-${i}`} d={this.arc(d)} style={{ fill: this.colors[i] }} />
|
||||
<text key={`label-${i}`} transform={labelTranslate} style={{ strokeWidth: '0px', fill: LABEL_COLOUR }} textAnchor='middle'>{d.value}</text>
|
||||
<text key={`key-${i}`} transform={keyTranslate} style={{ strokeWidth:'0px', fill: this.colors[i] }} textAnchor='middle'>{d.data.label}</text>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the component
|
||||
* @returns {object} Markup
|
||||
*/
|
||||
render() {
|
||||
const { width, height } = this.state.dimensions;
|
||||
const pie = this.pie(this.props.data),
|
||||
translate = `translate(${width / 2}, ${width * 0.4})`;
|
||||
|
||||
this.arc.outerRadius(width * 0.4);
|
||||
|
||||
return (
|
||||
<Measure width='100%' whitelist={['width', 'top']} onMeasure={ (dimensions) => { this.setState({ dimensions }); }}>
|
||||
<div width={width} height={width}>
|
||||
<svg style={{ stroke: 'None' }} width={width} height={width * 0.9}>
|
||||
<g transform={translate}>
|
||||
{pie.map((d, i) => this.sliceGenerator(d, i))}
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
</Measure>
|
||||
);
|
||||
}
|
||||
}
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ContainerDimensions from 'react-container-dimensions';
|
||||
import * as d3 from 'd3';
|
||||
|
||||
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
|
||||
const LABEL_COLOUR = '#000000';
|
||||
|
||||
/**
|
||||
* A pie chart
|
||||
*/
|
||||
export default class PieChart extends Component {
|
||||
|
||||
static propTypes = {
|
||||
data : PropTypes.array.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
this.pie = d3.pie().value((d) => d.value);
|
||||
this.colors = CORIOLIS_COLOURS;
|
||||
this.arc = d3.arc();
|
||||
this.arc.innerRadius(0);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Generate a slice of the pie chart
|
||||
* @param {Object} d the data for this slice
|
||||
* @param {number} i the index of this slice
|
||||
* @param {number} width the current width of the parent container
|
||||
* @returns {Object} the SVG for the slice
|
||||
*/
|
||||
sliceGenerator(d, i, width) {
|
||||
if (!d || d.value == 0) {
|
||||
// Ignore 0 values
|
||||
return null;
|
||||
}
|
||||
|
||||
const { data } = this.props;
|
||||
|
||||
// Push the labels further out from the centre of the slice
|
||||
let [labelX, labelY] = this.arc.centroid(d);
|
||||
const labelTranslate = `translate(${labelX * 1.5}, ${labelY * 1.5})`;
|
||||
|
||||
// Put the keys in a line with equal spacing
|
||||
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 (
|
||||
<g key={`group-${i}`}>
|
||||
<path key={`arc-${i}`} d={this.arc(d)} style={{ fill: this.colors[i] }} />
|
||||
<text key={`label-${i}`} transform={labelTranslate} style={{ strokeWidth: '0px', fill: LABEL_COLOUR }} textAnchor='middle'>{d.value}</text>
|
||||
<text key={`key-${i}`} transform={keyTranslate} style={{ strokeWidth:'0px', fill: this.colors[i] }} textAnchor='middle'>{d.data.label}</text>
|
||||
</g>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the component
|
||||
* @returns {object} Markup
|
||||
*/
|
||||
render() {
|
||||
return (
|
||||
<ContainerDimensions>
|
||||
{ ({ width }) => {
|
||||
const pie = this.pie(this.props.data),
|
||||
translate = `translate(${width / 2}, ${width * 0.4})`;
|
||||
|
||||
this.arc.outerRadius(width * 0.4);
|
||||
return (
|
||||
<div width={width} height={width}>
|
||||
<svg style={{ stroke: 'None' }} width={width} height={width * 0.9}>
|
||||
<g transform={translate}>
|
||||
{pie.map((d, i) => this.sliceGenerator(d, i, width))}
|
||||
</g>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</ContainerDimensions>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import { nameComparator } from '../utils/SlotFunctions';
|
||||
import { Pip } from './SvgIcons';
|
||||
import LineChart from '../components/LineChart';
|
||||
import Slider from '../components/Slider';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import Module from '../shipyard/Module';
|
||||
import { autoBind } from 'react-extras';
|
||||
|
||||
/**
|
||||
* Pips displays SYS/ENG/WEP pips and allows users to change them with key presses by clicking on the relevant area.
|
||||
@@ -18,6 +13,9 @@ export default class Pips extends TranslatedComponent {
|
||||
sys: PropTypes.number.isRequired,
|
||||
eng: PropTypes.number.isRequired,
|
||||
wep: PropTypes.number.isRequired,
|
||||
mcSys: PropTypes.number.isRequired,
|
||||
mcEng: PropTypes.number.isRequired,
|
||||
mcWep: PropTypes.number.isRequired,
|
||||
onChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@@ -28,9 +26,7 @@ export default class Pips extends TranslatedComponent {
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
const { sys, eng, wep } = props;
|
||||
|
||||
this._keyDown = this._keyDown.bind(this);
|
||||
autoBind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -74,30 +70,21 @@ export default class Pips extends TranslatedComponent {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a click
|
||||
* @param {string} which Which item was clicked
|
||||
*/
|
||||
onClick(which) {
|
||||
if (which == 'SYS') {
|
||||
this._incSys();
|
||||
} else if (which == 'ENG') {
|
||||
this._incEng();
|
||||
} else if (which == 'WEP') {
|
||||
this._incWep();
|
||||
} else if (which == 'RST') {
|
||||
this._reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the capacitor
|
||||
*/
|
||||
_reset() {
|
||||
let { sys, eng, wep } = this.props;
|
||||
if (sys != 2 || eng != 2 || wep != 2) {
|
||||
_reset(isMc) {
|
||||
let { sys, eng, wep, mcSys, mcEng, mcWep } = this.props;
|
||||
if (isMc) {
|
||||
if (mcSys || mcEng || mcWep) {
|
||||
sys -= mcSys;
|
||||
eng -= mcEng;
|
||||
wep -= mcWep;
|
||||
this.props.onChange(sys, eng, wep, 0, 0, 0);
|
||||
}
|
||||
} else if (sys != 2 || eng != 2 || wep != 2) {
|
||||
sys = eng = wep = 2;
|
||||
this.props.onChange(sys, eng, wep);
|
||||
this.props.onChange(sys + mcSys, eng + mcEng, wep + mcWep, mcSys, mcEng, mcWep);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,151 +92,133 @@ export default class Pips extends TranslatedComponent {
|
||||
* Increment the SYS capacitor
|
||||
*/
|
||||
_incSys() {
|
||||
let { sys, eng, wep } = this.props;
|
||||
|
||||
const required = Math.min(1, 4 - sys);
|
||||
if (required > 0) {
|
||||
if (required == 0.5) {
|
||||
// Take from whichever is larger
|
||||
if (eng > wep) {
|
||||
eng -= 0.5;
|
||||
sys += 0.5;
|
||||
} else {
|
||||
wep -= 0.5;
|
||||
sys += 0.5;
|
||||
}
|
||||
} else {
|
||||
// Required is 1 - take from both if possible
|
||||
if (eng == 0) {
|
||||
wep -= 1;
|
||||
sys += 1;
|
||||
} else if (wep == 0) {
|
||||
eng -= 1;
|
||||
sys += 1;
|
||||
} else {
|
||||
eng -= 0.5;
|
||||
wep -= 0.5;
|
||||
sys += 1;
|
||||
}
|
||||
}
|
||||
this.props.onChange(sys, eng, wep);
|
||||
}
|
||||
this._inc('sys', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the ENG capacitor
|
||||
*/
|
||||
_incEng() {
|
||||
let { sys, eng, wep } = this.props;
|
||||
|
||||
const required = Math.min(1, 4 - eng);
|
||||
if (required > 0) {
|
||||
if (required == 0.5) {
|
||||
// Take from whichever is larger
|
||||
if (sys > wep) {
|
||||
sys -= 0.5;
|
||||
eng += 0.5;
|
||||
} else {
|
||||
wep -= 0.5;
|
||||
eng += 0.5;
|
||||
}
|
||||
} else {
|
||||
// Required is 1 - take from both if possible
|
||||
if (sys == 0) {
|
||||
wep -= 1;
|
||||
eng += 1;
|
||||
} else if (wep == 0) {
|
||||
sys -= 1;
|
||||
eng += 1;
|
||||
} else {
|
||||
sys -= 0.5;
|
||||
wep -= 0.5;
|
||||
eng += 1;
|
||||
}
|
||||
}
|
||||
this.props.onChange(sys, eng, wep);
|
||||
}
|
||||
this._inc('eng', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the WEP capacitor
|
||||
*/
|
||||
_incWep() {
|
||||
let { sys, eng, wep } = this.props;
|
||||
this._inc('wep', false);
|
||||
}
|
||||
|
||||
const required = Math.min(1, 4 - wep);
|
||||
if (required > 0) {
|
||||
if (required == 0.5) {
|
||||
// Take from whichever is larger
|
||||
if (sys > eng) {
|
||||
sys -= 0.5;
|
||||
wep += 0.5;
|
||||
} else {
|
||||
eng -= 0.5;
|
||||
wep += 0.5;
|
||||
}
|
||||
_wrapMcClick(key) {
|
||||
return (event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
if (key == 'rst') {
|
||||
this._reset(true);
|
||||
} else {
|
||||
// Required is 1 - take from both if possible
|
||||
if (sys == 0) {
|
||||
eng -= 1;
|
||||
wep += 1;
|
||||
} else if (eng == 0) {
|
||||
sys -= 1;
|
||||
wep += 1;
|
||||
this._inc(key, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Increases a given capacitor
|
||||
* @param {String} key Pip name to increase (one of 'sys', 'eng', 'wep')
|
||||
* @param {Boolean} isMc True when increase is by multi crew
|
||||
*/
|
||||
_inc(key, isMc) {
|
||||
if (!['sys', 'eng', 'wep'].includes(key)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let { sys, eng, wep, mcSys, mcEng, mcWep } = this.props;
|
||||
let mc = key == 'sys' ? mcSys : (key == 'eng' ? mcEng : mcWep);
|
||||
let pips = this.props[key] - mc;
|
||||
let other1 = key == 'sys' ? eng - mcEng : sys - mcSys;
|
||||
let other2 = key == 'wep' ? eng - mcEng : wep - mcWep;
|
||||
|
||||
const required = Math.min(1, 4 - mc - pips);
|
||||
if (isMc) {
|
||||
// We can only set full pips in multi-crew also we can only set two pips
|
||||
if (required > 0.5 && mcSys + mcEng + mcWep < 2) {
|
||||
if (key == 'sys') {
|
||||
mcSys += 1;
|
||||
} else if (key == 'eng') {
|
||||
mcEng += 1;
|
||||
} else {
|
||||
sys -= 0.5;
|
||||
eng -= 0.5;
|
||||
wep += 1;
|
||||
mcWep += 1;
|
||||
}
|
||||
}
|
||||
this.props.onChange(sys, eng, wep);
|
||||
} else if (required > 0) {
|
||||
if (required == 0.5) {
|
||||
// Take from whichever is larger
|
||||
if (other1 > other2) {
|
||||
other1 -= 0.5;
|
||||
} else {
|
||||
other2 -= 0.5;
|
||||
}
|
||||
pips += 0.5;
|
||||
} else {
|
||||
// Required is 1 - take from both if possible
|
||||
if (other1 == 0) {
|
||||
other2 -= 1;
|
||||
} else if (other2 == 0) {
|
||||
other1 -= 1;
|
||||
} else {
|
||||
other1 -= 0.5;
|
||||
other2 -= 0.5;
|
||||
}
|
||||
pips += 1;
|
||||
}
|
||||
}
|
||||
|
||||
sys = mcSys + (key == 'sys' ? pips : other1);
|
||||
eng = mcEng + (key == 'eng' ? pips : (key == 'sys' ? other1 : other2));
|
||||
wep = mcWep + (key == 'wep' ? pips : other2);
|
||||
this.props.onChange(sys, eng, wep, mcSys, mcEng, mcWep);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the rendering for pips
|
||||
* @param {int} sys the SYS pips
|
||||
* @param {int} eng the ENG pips
|
||||
* @param {int} wep the WEP pips
|
||||
* @param {Number} sys the SYS pips
|
||||
* @param {Number} eng the ENG pips
|
||||
* @param {Number} wep the WEP pips
|
||||
* @param {Number} mcSys SYS pips from multi-crew
|
||||
* @param {Number} mcEng ENG pips from multi-crew
|
||||
* @param {Number} mcWep WEP pips from multi-crew
|
||||
* @returns {Object} Object containing the rendering for the pips
|
||||
*/
|
||||
_renderPips(sys, eng, wep) {
|
||||
const pipsSvg = {};
|
||||
_renderPips(sys, eng, wep, mcSys, mcEng, mcWep) {
|
||||
const pipsSvg = {
|
||||
SYS: [],
|
||||
ENG: [],
|
||||
WEP: [],
|
||||
};
|
||||
|
||||
// SYS
|
||||
pipsSvg['SYS'] = [];
|
||||
for (let i = 0; i < Math.floor(sys); i++) {
|
||||
pipsSvg['SYS'].push(<Pip className='full' key={i} />);
|
||||
}
|
||||
if (sys > Math.floor(sys)) {
|
||||
pipsSvg['SYS'].push(<Pip className='half' key={'half'} />);
|
||||
}
|
||||
for (let i = Math.floor(sys + 0.5); i < 4; i++) {
|
||||
pipsSvg['SYS'].push(<Pip className='empty' key={i} />);
|
||||
}
|
||||
// Multi-crew pipsSettings actually are included in the overall pip count therefore
|
||||
// we can consider [0, sys - mcSys] as normal pipsSettings whilst [sys - mcSys, sys]
|
||||
// are the multi-crew pipsSettings in what follows.
|
||||
|
||||
// ENG
|
||||
pipsSvg['ENG'] = [];
|
||||
for (let i = 0; i < Math.floor(eng); i++) {
|
||||
pipsSvg['ENG'].push(<Pip className='full' key={i} />);
|
||||
}
|
||||
if (eng > Math.floor(eng)) {
|
||||
pipsSvg['ENG'].push(<Pip className='half' key={'half'} />);
|
||||
}
|
||||
for (let i = Math.floor(eng + 0.5); i < 4; i++) {
|
||||
pipsSvg['ENG'].push(<Pip className='empty' key={i} />);
|
||||
}
|
||||
let pipsSettings = {
|
||||
SYS: [sys, mcSys],
|
||||
ENG: [eng, mcEng],
|
||||
WEP: [wep, mcWep],
|
||||
};
|
||||
|
||||
// WEP
|
||||
pipsSvg['WEP'] = [];
|
||||
for (let i = 0; i < Math.floor(wep); i++) {
|
||||
pipsSvg['WEP'].push(<Pip className='full' key={i} />);
|
||||
}
|
||||
if (wep > Math.floor(wep)) {
|
||||
pipsSvg['WEP'].push(<Pip className='half' key={'half'} />);
|
||||
}
|
||||
for (let i = Math.floor(wep + 0.5); i < 4; i++) {
|
||||
pipsSvg['WEP'].push(<Pip className='empty' key={i} />);
|
||||
for (let pipName in pipsSettings) {
|
||||
let [pips, mcPips] = pipsSettings[pipName];
|
||||
for (let i = 0; i < Math.floor(pips - mcPips); i++) {
|
||||
pipsSvg[pipName].push(<Pip key={i} className='full' />);
|
||||
}
|
||||
if (pips > Math.floor(pips)) {
|
||||
pipsSvg[pipName].push(<Pip className='half' key={'half'} />);
|
||||
}
|
||||
for (let i = pips - mcPips; i < Math.floor(pips); i++) {
|
||||
pipsSvg[pipName].push(<Pip key={i} className='mc' />);
|
||||
}
|
||||
for (let i = Math.floor(pips + 0.5); i < 4; i++) {
|
||||
pipsSvg[pipName].push(<Pip className='empty' key={i} />);
|
||||
}
|
||||
}
|
||||
|
||||
return pipsSvg;
|
||||
@@ -260,15 +229,11 @@ export default class Pips extends TranslatedComponent {
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { tooltip, termtip } = this.context;
|
||||
const { formats, translate, units } = this.context.language;
|
||||
const { sys, eng, wep } = this.props;
|
||||
const { sys, eng, wep, mcSys, mcEng, mcWep } = 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);
|
||||
const pipsSvg = this._renderPips(sys, eng, wep, mcSys, mcEng, mcWep);
|
||||
return (
|
||||
<span id='pips'>
|
||||
<table>
|
||||
@@ -276,20 +241,38 @@ export default class Pips extends TranslatedComponent {
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
<td className='clickable' onClick={onEngClicked}>{pipsSvg['ENG']}</td>
|
||||
<td className='clickable' onClick={() => this._inc('eng')}
|
||||
onContextMenu={this._wrapMcClick('eng')}>{pipsSvg['ENG']}</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td className='clickable' onClick={onSysClicked}>{pipsSvg['SYS']}</td>
|
||||
<td className='clickable' onClick={onEngClicked}>{translate('ENG')}</td>
|
||||
<td className='clickable' onClick={onWepClicked}>{pipsSvg['WEP']}</td>
|
||||
<td className='clickable' onClick={this._incSys}
|
||||
onContextMenu={this._wrapMcClick('sys')}>{pipsSvg['SYS']}</td>
|
||||
<td className='clickable' onClick={this._incEng}
|
||||
onContextMenu={this._wrapMcClick('eng')}>{translate('ENG')}</td>
|
||||
<td className='clickable' onClick={this._incWep}
|
||||
onContextMenu={this._wrapMcClick('wep')}>{pipsSvg['WEP']}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td className='clickable' onClick={onSysClicked}>{translate('SYS')}</td>
|
||||
<td className='clickable' onClick={onRstClicked}>{translate('RST')}</td>
|
||||
<td className='clickable' onClick={onWepClicked}>{translate('WEP')}</td>
|
||||
<td className='clickable' onClick={this._incSys}
|
||||
onContextMenu={this._wrapMcClick('sys')}>{translate('SYS')}</td>
|
||||
<td className='clickable' onClick={this._reset.bind(this, false)}>
|
||||
{translate('RST')}
|
||||
</td>
|
||||
<td className='clickable' onClick={this._incWep}
|
||||
onContextMenu={this._wrapMcClick('wep')}>{translate('WEP')}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
<td className='clickable secondary' onClick={this._wrapMcClick('rst')}
|
||||
onMouseEnter={termtip.bind(null, 'PHRASE_MULTI_CREW_CAPACITOR_POINTS')}
|
||||
onMouseLeave={tooltip.bind(null, null)}>
|
||||
{translate('RST')}
|
||||
</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -32,7 +32,6 @@ function bandText(val, index, wattScale) {
|
||||
* Renders the SVG to simulate in-game power bands
|
||||
*/
|
||||
export default class PowerBands extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
bands: PropTypes.array.isRequired,
|
||||
available: PropTypes.number.isRequired,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import Ship from '../shipyard/Ship';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import { Rocket } from './SvgIcons';
|
||||
import Persist from '../stores/Persist';
|
||||
|
||||
@@ -50,7 +50,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
||||
const speedTooltip = canThrust ? 'TT_SUMMARY_SPEED' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
|
||||
const canBoost = ship.canBoost(cargo, ship.fuelCapacity);
|
||||
const boostTooltip = canBoost ? 'TT_SUMMARY_BOOST' : canThrust ? 'TT_SUMMARY_BOOST_NONFUNCTIONAL' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
|
||||
const sgMetrics = Calc.shieldMetrics(ship, pips.sys || 2);
|
||||
const sgMetrics = Calc.shieldMetrics(ship, pips.sys);
|
||||
const shipBoost = canBoost ? Calc.calcBoost(ship) : 'No Boost';
|
||||
const armourMetrics = Calc.armourMetrics(ship);
|
||||
let shieldColour = 'blue';
|
||||
@@ -63,130 +63,150 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
||||
shieldColour
|
||||
};
|
||||
return <div id='summary'>
|
||||
<table className={'summaryTable'}>
|
||||
<thead>
|
||||
<tr className='main'>
|
||||
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canThrust }) }>{translate('speed')}</th>
|
||||
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canBoost }) }>{translate('boost')}</th>
|
||||
<th colSpan={5}>{translate('jump range')}</th>
|
||||
<th rowSpan={2}>{translate('shield')}</th>
|
||||
<th rowSpan={2}>{translate('integrity')}</th>
|
||||
<th rowSpan={2}>{translate('DPS')}</th>
|
||||
<th rowSpan={2}>{translate('EPS')}</th>
|
||||
<th rowSpan={2}>{translate('TTD')}</th>
|
||||
{/* <th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th> */}
|
||||
<th rowSpan={2}>{translate('cargo')}</th>
|
||||
<th rowSpan={2}>{translate('pax')}</th>
|
||||
<th rowSpan={2}>{translate('fuel')}</th>
|
||||
<th colSpan={3}>{translate('mass')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'hull hardness', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('hrd')}</th>
|
||||
<th rowSpan={2}>{translate('crew')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'mass lock factor', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_BOOST_TIME', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('boost time')}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className='lft'>{translate('max')}</th>
|
||||
<th>{translate('unladen')}</th>
|
||||
<th>{translate('laden')}</th>
|
||||
<th>{translate('total unladen')}</th>
|
||||
<th>{translate('total laden')}</th>
|
||||
<th className='lft'>{translate('hull')}</th>
|
||||
<th>{translate('unladen')}</th>
|
||||
<th>{translate('laden')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td onMouseEnter={termtip.bind(null, speedTooltip, { cap: 0 })} onMouseLeave={hide}>{ canThrust ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, false))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
||||
<td onMouseEnter={termtip.bind(null, boostTooltip, { cap: 0 })} onMouseLeave={hide}>{ canBoost ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, true))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
||||
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_MAX_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.standard[2].m.getMaxFuelPerJump(), ship.standard[2].m, ship.standard[2].m.getMaxFuelPerJump()))}{u.LY}</span></td>
|
||||
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</span></td>
|
||||
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</span></td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</td>
|
||||
<td className={sgClassNames} onMouseEnter={termtip.bind(null, sgTooltip, { cap: 0 })} onMouseLeave={hide}>{int(ship.shield)}{u.MJ}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_INTEGRITY', { cap: 0 })} onMouseLeave={hide}>{int(ship.armour)}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_DPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalDps)}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_EPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalEps)}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_TTD', { cap: 0 })} onMouseLeave={hide}>{timeToDrain === Infinity ? '∞' : time(timeToDrain)}</td>
|
||||
{/* <td>{f1(ship.totalHps)}</td> */}
|
||||
<td>{round(ship.cargoCapacity)}{u.T}</td>
|
||||
<td>{ship.passengerCapacity}</td>
|
||||
<td>{round(ship.fuelCapacity)}{u.T}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_HULL_MASS', { cap: 0 })} onMouseLeave={hide}>{ship.hullMass}{u.T}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.unladenMass)}{u.T}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.ladenMass)}{u.T}</td>
|
||||
<td>{int(ship.hardness)}</td>
|
||||
<td>{ship.crew}</td>
|
||||
<td>{ship.masslock}</td>
|
||||
<td>{shipBoost !== 'No Boost' ? formats.time(shipBoost) : 'No Boost'}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table className={'summaryTable'}>
|
||||
<thead className={this.state.shieldColour}>
|
||||
<tr>
|
||||
<th onMouseEnter={termtip.bind(null, 'shield', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('shield')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'explres', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('explres')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'kinres', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('kinres')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'thermres', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('thermres')}</th>
|
||||
<div style={{display: "table", width: "100%"}}>
|
||||
<div style={{display: "table-row"}}>
|
||||
<table className={'summaryTable'}>
|
||||
<thead>
|
||||
<tr className='main'>
|
||||
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canThrust }) }>{translate('speed')}</th>
|
||||
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canBoost }) }>{translate('boost')}</th>
|
||||
<th colSpan={5}>{translate('jump range')}</th>
|
||||
<th rowSpan={2}>{translate('shield')}</th>
|
||||
<th rowSpan={2}>{translate('integrity')}</th>
|
||||
<th rowSpan={2}>{translate('DPS')}</th>
|
||||
<th rowSpan={2}>{translate('EPS')}</th>
|
||||
<th rowSpan={2}>{translate('TTD')}</th>
|
||||
{/* <th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th> */}
|
||||
<th rowSpan={2}>{translate('cargo')}</th>
|
||||
<th rowSpan={2}>{translate('pax')}</th>
|
||||
<th rowSpan={2}>{translate('fuel')}</th>
|
||||
<th colSpan={3}>{translate('mass')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'hull hardness', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('hrd')}</th>
|
||||
<th rowSpan={2}>{translate('crew')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'mass lock factor', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_BOOST_TIME', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('boost time')}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className='lft'>{translate('max')}</th>
|
||||
<th>{translate('unladen')}</th>
|
||||
<th>{translate('laden')}</th>
|
||||
<th>{translate('total unladen')}</th>
|
||||
<th>{translate('total laden')}</th>
|
||||
<th className='lft'>{translate('hull')}</th>
|
||||
<th>{translate('unladen')}</th>
|
||||
<th>{translate('laden')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td onMouseEnter={termtip.bind(null, speedTooltip, { cap: 0 })} onMouseLeave={hide}>{ canThrust ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, false))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
||||
<td onMouseEnter={termtip.bind(null, boostTooltip, { cap: 0 })} onMouseLeave={hide}>{ canBoost ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, true))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
||||
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_MAX_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.standard[2].m.getMaxFuelPerJump(), ship.standard[2].m, ship.standard[2].m.getMaxFuelPerJump(), ship))}{u.LY}</span></td>
|
||||
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</span></td>
|
||||
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</span></td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</td>
|
||||
<td className={sgClassNames} onMouseEnter={termtip.bind(null, sgTooltip, { cap: 0 })} onMouseLeave={hide}>{int(ship.shield)}{u.MJ}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_INTEGRITY', { cap: 0 })} onMouseLeave={hide}>{int(ship.armour)}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_DPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalDps)}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_EPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalEps)}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_TTD', { cap: 0 })} onMouseLeave={hide}>{timeToDrain === Infinity ? '∞' : time(timeToDrain)}</td>
|
||||
{/* <td>{f1(ship.totalHps)}</td> */}
|
||||
<td>{round(ship.cargoCapacity)}{u.T}</td>
|
||||
<td>{ship.passengerCapacity}</td>
|
||||
<td>{round(ship.fuelCapacity)}{u.T}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_HULL_MASS', { cap: 0 })} onMouseLeave={hide}>{ship.hullMass}{u.T}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.unladenMass)}{u.T}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.ladenMass)}{u.T}</td>
|
||||
<td>{int(ship.hardness)}</td>
|
||||
<td>{ship.crew}</td>
|
||||
<td>{ship.masslock}</td>
|
||||
<td>{shipBoost !== 'No Boost' ? formats.time(shipBoost) : 'No Boost'}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table className={'summaryTable'}>
|
||||
<thead className={this.state.shieldColour}>
|
||||
<tr>
|
||||
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'shield', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('shield')}</th>
|
||||
<th colSpan={4} className='lft'>{translate('resistance')}</th>
|
||||
|
||||
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_SHIELDS_SCB', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('absolute')} ${translate('HP')}`}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_SHIELDS_SCB', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('explosive')} ${translate('HP')}`}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_SHIELDS_SCB', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('kinetic')} ${translate('HP')}`}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_SHIELDS_SCB', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('thermal')} ${translate('HP')}`}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECOVER', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('recovery')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECHARGE', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('recharge')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{translate(shieldGenerator && shieldGenerator.m.grp || 'No Shield')}</td>
|
||||
<td>{int(ship.shieldExplRes * 100) + '%'}</td>
|
||||
<td>{int(ship.shieldKinRes * 100) + '%'}</td>
|
||||
<td>{int(ship.shieldThermRes * 100) + '%'}</td>
|
||||
<td>{int(sgMetrics && sgMetrics.generator ? sgMetrics.total / sgMetrics.absolute.total : 0)}</td>
|
||||
<td>{int(sgMetrics && sgMetrics.generator ? sgMetrics.total / sgMetrics.explosive.total : 0)}</td>
|
||||
<td>{int(sgMetrics && sgMetrics.generator ? sgMetrics.total / sgMetrics.kinetic.total : 0)}</td>
|
||||
<td>{int(sgMetrics && sgMetrics.generator ? sgMetrics.total / sgMetrics.thermal.total : 0)}</td>
|
||||
<td>{sgMetrics && sgMetrics.recover ? formats.time(sgMetrics.recover) : 0}</td>
|
||||
<td>{sgMetrics && sgMetrics.recharge ? formats.time(sgMetrics.recharge) : 0}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<thead>
|
||||
<tr>
|
||||
<th onMouseEnter={termtip.bind(null, 'armour', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('armour')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'explres', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('explres')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'kinres', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('kinres')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'thermres', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('thermres')}</th>
|
||||
<th colSpan={5} onMouseEnter={termtip.bind(null, 'TT_SUMMARY_SHIELDS_SCB', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('HP')}`}</th>
|
||||
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECOVER', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('recovery')}</th>
|
||||
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECHARGE', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('recharge')}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{`${translate('explosive')}`}</th>
|
||||
<th>{`${translate('kinetic')}`}</th>
|
||||
<th>{`${translate('thermal')}`}</th>
|
||||
<th></th>
|
||||
|
||||
<th onMouseEnter={termtip.bind(null, 'PHRASE_EFFECTIVE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('absolute')} ${translate('HP')}`}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'PHRASE_EFFECTIVE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('explosive')} ${translate('HP')}`}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'PHRASE_EFFECTIVE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('kinetic')} ${translate('HP')}`}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'PHRASE_EFFECTIVE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('thermal')} ${translate('HP')}`}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'TT_MODULE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('raw module armour')}</th>
|
||||
<th onMouseEnter={termtip.bind(null, 'armour', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('internal protection')}</th>
|
||||
<th className={'bordered'}>{`${translate('absolute')}`}</th>
|
||||
<th>{`${translate('explosive')}`}</th>
|
||||
<th>{`${translate('kinetic')}`}</th>
|
||||
<th>{`${translate('thermal')}`}</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{translate(shieldGenerator && shieldGenerator.m.grp || 'No Shield')}</td>
|
||||
<td>{formats.pct1(ship.shieldExplRes)}</td>
|
||||
<td>{formats.pct1(ship.shieldKinRes)}</td>
|
||||
<td>{formats.pct1(ship.shieldThermRes)}</td>
|
||||
<td></td>
|
||||
|
||||
<td>{int(ship && ship.shield > 0 ? ship.shield : 0)}{u.MJ}</td>
|
||||
<td>{int(ship && ship.shield > 0 ? ship.shield * ((1 / (1 - (ship.shieldExplRes)))) : 0)}{u.MJ}</td>
|
||||
<td>{int(ship && ship.shield > 0 ? ship.shield * ((1 / (1 - (ship.shieldKinRes)))) : 0)}{u.MJ}</td>
|
||||
<td>{int(ship && ship.shield > 0 ? ship.shield * ((1 / (1 - (ship.shieldThermRes)))) : 0)}{u.MJ}</td>
|
||||
<td></td>
|
||||
<td>{sgMetrics && sgMetrics.recover === Math.Inf ? translate('Never') : formats.time(sgMetrics.recover)}</td>
|
||||
<td>{sgMetrics && sgMetrics.recharge === Math.Inf ? translate('Never') : formats.time(sgMetrics.recharge)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<thead>
|
||||
<tr>
|
||||
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'armour', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('armour')}</th>
|
||||
<th colSpan={4} className='lft'>{translate('resistance')}</th>
|
||||
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{translate(ship && ship.bulkheads && ship.bulkheads.m && ship.bulkheads.m.name || 'No Armour')}</td>
|
||||
<td>{int(ship.hullExplRes * 100) + '%'}</td>
|
||||
<td>{int(ship.hullKinRes * 100) + '%'}</td>
|
||||
<td>{int(ship.hullThermRes * 100) + '%'}</td>
|
||||
<td>{int(armourMetrics.total / armourMetrics.absolute.total)}</td>
|
||||
<td>{int(armourMetrics.total / armourMetrics.explosive.total)}</td>
|
||||
<td>{int(armourMetrics.total / armourMetrics.kinetic.total)}</td>
|
||||
<td>{int(armourMetrics.total / armourMetrics.thermal.total)}</td>
|
||||
<td>{int(armourMetrics.modulearmour)}</td>
|
||||
<td>{int(armourMetrics.moduleprotection * 100) + '%'}</td>
|
||||
<th colSpan={5} onMouseEnter={termtip.bind(null, 'PHRASE_EFFECTIVE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('HP')}`}</th>
|
||||
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'TT_MODULE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('raw module armour')}</th>
|
||||
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'TT_MODULE_PROTECTION_INTERNAL', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('internal protection')}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{`${translate('explosive')}`}</th>
|
||||
<th>{`${translate('kinetic')}`}</th>
|
||||
<th>{`${translate('thermal')}`}</th>
|
||||
<th>{`${translate('caustic')}`}</th>
|
||||
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<th className={'bordered'}>{`${translate('absolute')}`}</th>
|
||||
<th>{`${translate('explosive')}`}</th>
|
||||
<th>{`${translate('kinetic')}`}</th>
|
||||
<th>{`${translate('thermal')}`}</th>
|
||||
<th>{`${translate('caustic')}`}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{translate(ship && ship.bulkheads && ship.bulkheads.m && ship.bulkheads.m.name || 'No Armour')}</td>
|
||||
<td>{formats.pct1(ship.hullExplRes)}</td>
|
||||
<td>{formats.pct1(ship.hullKinRes)}</td>
|
||||
<td>{formats.pct1(ship.hullThermRes)}</td>
|
||||
<td>{formats.pct1(ship.hullCausRes)}</td>
|
||||
<td>{int(ship.armour)}</td>
|
||||
<td>{int(ship.armour * ((1 / (1 - (ship.hullExplRes)))))}</td>
|
||||
<td>{int(ship.armour * ((1 / (1 - (ship.hullKinRes)))))}</td>
|
||||
<td>{int(ship.armour * ((1 / (1 - (ship.hullThermRes)))))}</td>
|
||||
<td>{int(ship.armour * ((1 / (1 - (ship.hullCausRes)))))}</td>
|
||||
<td>{int(armourMetrics.modulearmour)}</td>
|
||||
<td>{int(armourMetrics.moduleprotection * 100) + '%'}</td>
|
||||
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import AvailableModulesMenu from './AvailableModulesMenu';
|
||||
import ModificationsMenu from './ModificationsMenu';
|
||||
import { diffDetails } from '../utils/SlotFunctions';
|
||||
import { wrapCtxMenu } from '../utils/UtilityFunctions';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
|
||||
/**
|
||||
* Abstract Slot
|
||||
@@ -88,7 +87,7 @@ export default class Slot extends TranslatedComponent {
|
||||
if(event.target.className == 'r') {
|
||||
this._toggleModifications();
|
||||
}
|
||||
this.props.onOpen(event);
|
||||
this.props.onOpen(event);
|
||||
}
|
||||
}
|
||||
/**
|
||||
|
||||
@@ -34,7 +34,7 @@ export default class SlotSection extends TranslatedComponent {
|
||||
this.sectionId = sectionId;
|
||||
this.sectionName = sectionName;
|
||||
this.ssHeadRef = null;
|
||||
|
||||
|
||||
this.sectionRefArr = this.props.sectionMenuRefs[this.sectionId] = [];
|
||||
this.sectionRefArr['selectedRef'] = null;
|
||||
this._getSlots = this._getSlots.bind(this);
|
||||
@@ -55,11 +55,11 @@ export default class SlotSection extends TranslatedComponent {
|
||||
// _contextMenu()
|
||||
// componentDidUpdate(prevProps)
|
||||
|
||||
/**
|
||||
* TODO: May either need to send the function to be triggered when Enter key is pressed, or else
|
||||
/**
|
||||
* TODO: May either need to send the function to be triggered when Enter key is pressed, or else
|
||||
* may need a separate keyDown handler for each subclass (StandardSlotSection, HardpointSlotSection, etc.)
|
||||
* ex: _keyDown(_keyDownfn, event)
|
||||
*
|
||||
*
|
||||
* @param {SyntheticEvent} event KeyDown event
|
||||
*/
|
||||
_keyDown(event) {
|
||||
@@ -85,14 +85,14 @@ export default class SlotSection extends TranslatedComponent {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set focus on appropriate Slot Section Menu element
|
||||
* @param {Object} focusPrevProps prevProps for componentDidUpdate() from ...SlotSection.jsx
|
||||
* @param {Object} focusPrevProps prevProps for componentDidUpdate() from ...SlotSection.jsx
|
||||
* @param {String} firstRef id of the first ref in ...SlotSection.jsx
|
||||
* @param {String} lastRef id of the last ref in ...SlotSection.jsx
|
||||
*
|
||||
*
|
||||
*/
|
||||
_handleSectionFocus(focusPrevProps, firstRef, lastRef) {
|
||||
if (this.selectedRefId !== null && this.sectionRefArr[this.selectedRefId]) {
|
||||
@@ -228,6 +228,18 @@ export default class SlotSection extends TranslatedComponent {
|
||||
targetSlot.priority = targetPriority;
|
||||
}
|
||||
this.props.onChange();
|
||||
this.props.ship
|
||||
.updatePowerGenerated()
|
||||
.updatePowerUsed()
|
||||
.recalculateMass()
|
||||
.updateJumpStats()
|
||||
.recalculateShield()
|
||||
.recalculateShieldCells()
|
||||
.recalculateArmour()
|
||||
.recalculateDps()
|
||||
.recalculateEps()
|
||||
.recalculateHps()
|
||||
.updateMovement();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,9 @@ export default class StandardSlot extends TranslatedComponent {
|
||||
if (m && m.name && m.name === 'Guardian Hybrid Power Plant') {
|
||||
validMods = [];
|
||||
}
|
||||
if (m && m.name && m.name === 'Guardian Power Distributor') {
|
||||
validMods = [];
|
||||
}
|
||||
let showModuleResistances = Persist.showModuleResistances();
|
||||
let mass = m.getMass() || m.cargo || m.fuel || 0;
|
||||
|
||||
|
||||
@@ -228,6 +228,30 @@ export class LinkIcon extends SvgIcon {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Link / Permalink / Chain
|
||||
*/
|
||||
export class OrbisIcon extends SvgIcon {
|
||||
/**
|
||||
* Generate the SVG
|
||||
* @return {React.Component} SVG Contents
|
||||
*/
|
||||
svg() {
|
||||
return (
|
||||
<g transform="scale(.037296)">
|
||||
<path d="m429 319c60.75 0 110 49.248 110 110 0 60.75-49.247 110-110 110s-110-49.247-110-110c0-60.749 49.248-110 110-110m0-20c-34.724 0-67.369 13.522-91.922 38.075s-38.075 57.198-38.075 91.922 13.522 67.369 38.075 91.922c24.553 24.554 57.198 38.075 91.922 38.075s67.369-13.521 91.922-38.075c24.554-24.553 38.075-57.198 38.075-91.922s-13.521-67.369-38.075-91.922c-24.553-24.553-57.198-38.075-91.922-38.075z" />
|
||||
<path d="m429 235c107.14 0 194 86.855 194 194s-86.855 194-194 194-194-86.855-194-194 86.855-194 194-194m0-20c-28.881 0-56.908 5.661-83.304 16.825-25.485 10.779-48.368 26.207-68.016 45.853-19.646 19.647-35.074 42.53-45.853 68.015-11.163 26.396-16.824 54.423-16.824 83.304s5.661 56.908 16.825 83.304c10.779 25.484 26.207 48.368 45.853 68.016 19.647 19.646 42.53 35.073 68.015 45.854 26.396 11.164 54.423 16.825 83.304 16.825s56.908-5.661 83.304-16.825c25.484-10.779 48.368-26.206 68.016-45.854 19.646-19.646 35.073-42.53 45.854-68.016 11.164-26.396 16.825-54.423 16.825-83.304s-5.661-56.908-16.825-83.304c-10.779-25.485-26.206-48.368-45.854-68.015-19.646-19.646-42.53-35.074-68.016-45.853-26.396-11.164-54.423-16.825-83.304-16.825z" />
|
||||
<path d="m429 63c202.14 0 366 163.86 366 366s-163.86 366-366 366-366-163.86-366-366 163.86-366 366-366m0-20c-52.101 0-102.65 10.208-150.25 30.342-45.966 19.442-87.244 47.271-122.69 82.714s-63.272 76.721-82.714 122.69c-20.134 47.601-30.342 98.153-30.342 150.25s10.208 102.65 30.342 150.25c19.442 45.967 47.271 87.244 82.714 122.69 35.443 35.442 76.721 63.271 122.69 82.715 47.601 20.133 98.153 30.342 150.25 30.342s102.65-10.209 150.25-30.342c45.967-19.442 87.244-47.271 122.69-82.715 35.441-35.442 63.271-76.721 82.714-122.69 20.133-47.601 30.342-98.153 30.342-150.25s-10.209-102.65-30.342-150.25c-19.442-45.966-47.271-87.244-82.714-122.69s-76.722-63.272-122.69-82.714c-47.601-20.134-98.153-30.342-150.25-30.342z"/>
|
||||
<path d="m429 63c202.14 0 366 163.86 366 366s-163.86 366-366 366-366-163.86-366-366 163.86-366 366-366m0-20c-52.101 0-102.65 10.208-150.25 30.342-45.966 19.442-87.244 47.271-122.69 82.714s-63.272 76.721-82.714 122.69c-20.134 47.601-30.342 98.153-30.342 150.25s10.208 102.65 30.342 150.25c19.442 45.967 47.271 87.244 82.714 122.69 35.443 35.442 76.721 63.271 122.69 82.715 47.601 20.133 98.153 30.342 150.25 30.342s102.65-10.209 150.25-30.342c45.967-19.442 87.244-47.271 122.69-82.715 35.441-35.442 63.271-76.721 82.714-122.69 20.133-47.601 30.342-98.153 30.342-150.25s-10.209-102.65-30.342-150.25c-19.442-45.966-47.271-87.244-82.714-122.69s-76.722-63.272-122.69-82.714c-47.601-20.134-98.153-30.342-150.25-30.342z" />
|
||||
<path d="m429 20c225.88 0 409 183.11 409 409s-183.11 409-409 409-409-183.11-409-409 183.11-409 409-409m0-20c-57.905 0-114.09 11.345-166.99 33.721-51.087 21.608-96.963 52.538-136.36 91.93s-70.321 85.269-91.93 136.36c-22.376 52.902-33.721 109.09-33.721 166.99s11.345 114.09 33.721 166.99c21.608 51.087 52.538 96.964 91.93 136.35 39.392 39.392 85.269 70.321 136.36 91.931 52.902 22.375 109.09 33.721 166.99 33.721s114.09-11.346 166.99-33.721c51.087-21.608 96.964-52.538 136.35-91.931 39.392-39.392 70.321-85.269 91.931-136.35 22.375-52.902 33.721-109.09 33.721-166.99s-11.346-114.09-33.721-166.99c-21.608-51.087-52.538-96.963-91.931-136.36-39.392-39.392-85.269-70.321-136.35-91.93-52.902-22.376-109.09-33.721-166.99-33.721z"/>
|
||||
<path d="m155.34 679.12 173.25-190.21-15.626-13.721-170.9 190.4zm31.01 31.714 202.41-169.1-16.418-14.417-198.76 170.43z"/>
|
||||
<path d="m702.66 178.87-173.25 190.21 15.625 13.721 170.9-190.4zm-31.01-31.714-202.41 169.1 16.418 14.417 198.76-170.43z" />
|
||||
<rect transform="matrix(-.7071 -.7071 .7071 -.7071 429.34 1036.2)" x="387.09" y="420.77" width="84.379" height="16.859" />
|
||||
</g>);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Material
|
||||
*/
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import SlotSection from './SlotSection';
|
||||
import HardpointSlot from './HardpointSlot';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
@@ -8,7 +7,6 @@ import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
* Utility Slot Section
|
||||
*/
|
||||
export default class UtilitySlotSection extends SlotSection {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
@@ -133,5 +131,4 @@ export default class UtilitySlotSection extends SlotSection {
|
||||
</ul>
|
||||
</div>;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,111 +1,104 @@
|
||||
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 AXIS_COLOUR = '#C06400';
|
||||
|
||||
const ASPECT = 1;
|
||||
|
||||
const merge = function(one, two) {
|
||||
return Object.assign({}, one, two);
|
||||
};
|
||||
|
||||
/**
|
||||
* A vertical bar chart
|
||||
*/
|
||||
export default class VerticalBarChart extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
data : PropTypes.array.isRequired,
|
||||
yMax : PropTypes.number
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
this._termtip = this._termtip.bind(this);
|
||||
|
||||
this.state = {
|
||||
dimensions: {
|
||||
width: 300,
|
||||
height: 300
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the bar chart
|
||||
* @returns {Object} the markup
|
||||
*/
|
||||
render() {
|
||||
const { width, height } = this.state.dimensions;
|
||||
const { tooltip, termtip } = this.context;
|
||||
|
||||
// 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 (
|
||||
<Measure whitelist={['width', 'top']} onMeasure={ (dimensions) => this.setState({ dimensions }) }>
|
||||
<div width='100%'>
|
||||
<BarChart width={width} height={width * ASPECT} data={this.props.data} margin={{ top: 5, right: 5, left: 5, bottom: 5 }}>
|
||||
<XAxis interval={0} fontSize='0.8em' stroke={AXIS_COLOUR} dataKey='label' />
|
||||
<YAxis interval={'preserveStart'} tickCount={11} fontSize='0.8em' stroke={AXIS_COLOUR} type='number' domain={[0, localMax]}/>
|
||||
<Bar dataKey='value' label={<ValueLabel />} fill={CORIOLIS_COLOURS[0]} isAnimationActive={false} onMouseOver={this._termtip} onMouseOut={tooltip.bind(null, null)}/>
|
||||
</BarChart>
|
||||
</div>
|
||||
</Measure>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
class ValueLabel extends React.Component {
|
||||
static propTypes = {
|
||||
x: PropTypes.number,
|
||||
y: PropTypes.number,
|
||||
payload: PropTypes.object,
|
||||
value: PropTypes.number
|
||||
};
|
||||
|
||||
/**
|
||||
* Render offence
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { x, y, payload, value } = this.props;
|
||||
|
||||
const em = value < 1000 ? '1em' : value < 1000 ? '0.8em' : '0.7em';
|
||||
|
||||
return (
|
||||
<text x={x} y={y} fill="#000000" textAnchor="middle" dy={20} style={{ fontSize: em }}>{value}</text>
|
||||
);
|
||||
}
|
||||
};
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import React, { PropTypes } from 'react';
|
||||
import ContainerDimensions from 'react-container-dimensions';
|
||||
import { BarChart, Bar, XAxis, YAxis } from 'recharts';
|
||||
|
||||
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
|
||||
const LABEL_COLOUR = '#000000';
|
||||
const AXIS_COLOUR = '#C06400';
|
||||
|
||||
const ASPECT = 1;
|
||||
|
||||
const merge = function(one, two) {
|
||||
return Object.assign({}, one, two);
|
||||
};
|
||||
|
||||
/**
|
||||
* A vertical bar chart
|
||||
*/
|
||||
export default class VerticalBarChart extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
data : PropTypes.array.isRequired,
|
||||
yMax : PropTypes.number
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
this._termtip = this._termtip.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the bar chart
|
||||
* @returns {Object} the markup
|
||||
*/
|
||||
render() {
|
||||
const { tooltip, termtip } = this.context;
|
||||
|
||||
// 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 (
|
||||
<ContainerDimensions>
|
||||
{ ({ width }) => (
|
||||
<div width='100%'>
|
||||
<BarChart width={width} height={width * ASPECT} data={this.props.data} margin={{ top: 5, right: 5, left: 5, bottom: 5 }}>
|
||||
<XAxis interval={0} fontSize='0.8em' stroke={AXIS_COLOUR} dataKey='label' />
|
||||
<YAxis interval={'preserveStart'} tickCount={11} fontSize='0.8em' stroke={AXIS_COLOUR} type='number' domain={[0, localMax]}/>
|
||||
<Bar dataKey='value' label={<ValueLabel />} fill={CORIOLIS_COLOURS[0]} isAnimationActive={false} onMouseOver={this._termtip} onMouseOut={tooltip.bind(null, null)}/>
|
||||
</BarChart>
|
||||
</div>
|
||||
)}
|
||||
</ContainerDimensions>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
class ValueLabel extends React.Component {
|
||||
static propTypes = {
|
||||
x: PropTypes.number,
|
||||
y: PropTypes.number,
|
||||
payload: PropTypes.object,
|
||||
value: PropTypes.number
|
||||
};
|
||||
|
||||
/**
|
||||
* Render offence
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { x, y, payload, value } = this.props;
|
||||
|
||||
const em = value < 1000 ? '1em' : value < 1000 ? '0.8em' : '0.7em';
|
||||
|
||||
return (
|
||||
<text x={x} y={y} fill="#000000" textAnchor="middle" dy={20} style={{ fontSize: em }}>{value}</text>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
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'];
|
||||
|
||||
@@ -74,7 +69,7 @@ export default class WeaponDamageChart extends TranslatedComponent {
|
||||
* 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++) {
|
||||
@@ -184,7 +179,7 @@ export default class WeaponDamageChart extends TranslatedComponent {
|
||||
const code = `${ship.toString()}:${opponent.toString()}`;
|
||||
|
||||
return (
|
||||
<span>
|
||||
<div>
|
||||
<LineChart
|
||||
xMax={maxRange}
|
||||
yMax={this.state.maxDps}
|
||||
@@ -198,7 +193,7 @@ export default class WeaponDamageChart extends TranslatedComponent {
|
||||
points={200}
|
||||
code={code}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,17 +60,20 @@ export function getLanguage(langCode) {
|
||||
},
|
||||
translate,
|
||||
units: {
|
||||
ang: '°', // Angle
|
||||
CR: <u>{translate('CR')}</u>, // Credits
|
||||
kg: <u>{translate('kg')}</u>, // Kilograms
|
||||
kgs: <u>{translate('kg/s')}</u>, // Kilograms per second
|
||||
km: <u>{translate('km')}</u>, // Kilometers
|
||||
Ls: <u>{translate('Ls')}</u>, // Light Seconds
|
||||
LY: <u>{translate('LY')}</u>, // Light Years
|
||||
m: <u>{translate('m')}</u>, // Meters
|
||||
MJ: <u>{translate('MJ')}</u>, // Mega Joules
|
||||
'm/s': <u>{translate('m/s')}</u>, // Meters per second
|
||||
'°/s': <u>{translate('°/s')}</u>, // Degrees per second
|
||||
MW: <u>{translate('MW')}</u>, // Mega Watts (same as Mega Joules per second)
|
||||
mps: <u>{translate('m/s')}</u>, // Metres per second
|
||||
pct: '%', // Percent
|
||||
ps: <u>{translate('/s')}</u>, // per second
|
||||
pm: <u>{translate('/min')}</u>, // per minute
|
||||
s: <u>{translate('secs')}</u>, // Seconds
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -20,11 +20,11 @@
|
||||
"PHRASE_BLUEPRINT_BEST": "Лучшие основные значения для чертежа",
|
||||
"PHRASE_BLUEPRINT_EXTREME": "Лучшие положительные и худшие отрицательные основные значения для чертежа",
|
||||
"PHRASE_BLUEPRINT_RESET": "Убрать все изменения и чертёж",
|
||||
"PHRASE_SELECT_SPECIAL": "Нажмите чтобы выбрать экспериментальный эффект",
|
||||
"PHRASE_SELECT_SPECIAL": "Нажмите, чтобы выбрать экспериментальный эффект",
|
||||
"PHRASE_NO_SPECIAL": "Без экспериментального эффекта",
|
||||
"PHRASE_SHOPPING_LIST": "Станции что продают эту сборку",
|
||||
"PHRASE_REFIT_SHOPPING_LIST": "Станции что продают необходимые модули",
|
||||
"PHRASE_TOTAL_EFFECTIVE_SHIELD": "Общий урон что может быть нанесён в каждым типе, если используются все щитонакопители",
|
||||
"PHRASE_SHOPPING_LIST": "Станции, что продают эту сборку",
|
||||
"PHRASE_REFIT_SHOPPING_LIST": "Станции, что продают необходимые модули",
|
||||
"PHRASE_TOTAL_EFFECTIVE_SHIELD": "Общий урон, что может быть нанесён в каждым типе, если используются все щитонакопители",
|
||||
"PHRASE_TIME_TO_LOSE_SHIELDS": "Щиты продержатся",
|
||||
"PHRASE_TIME_TO_RECOVER_SHIELDS": "Щиты восстановятся за",
|
||||
"PHRASE_TIME_TO_RECHARGE_SHIELDS": "Щиты будут заряжены за",
|
||||
@@ -43,12 +43,12 @@
|
||||
"PHRASE_TIME_TO_REMOVE_ARMOUR": "Снимет броню за",
|
||||
"TT_TIME_TO_REMOVE_ARMOUR": "Непрерывным огнём из всех орудий",
|
||||
"PHRASE_TIME_TO_DRAIN_WEP": "Опустошит ОРУЖ за",
|
||||
"TT_TIME_TO_DRAIN_WEP": "Время за которое опустошится аккумулятор ОРУЖ при стрельбе из всех орудий",
|
||||
"TT_TIME_TO_DRAIN_WEP": "Время, за которое опустошится аккумулятор ОРУЖ при стрельбе из всех орудий",
|
||||
"TT_TIME_TO_LOSE_SHIELDS": "Против поддерживаемой стрельбы из всех орудий противника",
|
||||
"TT_TIME_TO_LOSE_ARMOUR": "Против поддерживаемой стрельбы из всех орудий противника",
|
||||
"TT_MODULE_ARMOUR": "Броня защищаюшае модули от урона",
|
||||
"TT_MODULE_PROTECTION_EXTERNAL": "Процент урона перенаправленного от гнёзд на наборы для усиления модулей",
|
||||
"TT_MODULE_PROTECTION_INTERNAL": "Процент урона перенаправленного от модулей вне гнёзд на наборы для усиления модулей",
|
||||
"TT_MODULE_ARMOUR": "Броня, защищающая модули от урона",
|
||||
"TT_MODULE_PROTECTION_EXTERNAL": "Процент урона, перенаправленного от гнёзд на наборы для усиления модулей",
|
||||
"TT_MODULE_PROTECTION_INTERNAL": "Процент урона, перенаправленного от модулей вне гнёзд на наборы для усиления модулей",
|
||||
"TT_EFFECTIVE_SDPS_SHIELDS": "Реальный поддерживаемый ДПС пока аккумулятор ОРУЖ не пуст",
|
||||
"TT_EFFECTIVENESS_SHIELDS": "Эффективность в сравнении с попаданием по цели с 0-сопротивляемостью без пунктов в СИС на 0 метрах",
|
||||
"TT_EFFECTIVE_SDPS_ARMOUR": "Реальный поддерживаемый ДПС пока аккумулятор ОРУЖ не пуст",
|
||||
@@ -56,7 +56,7 @@
|
||||
"PHRASE_EFFECTIVE_SDPS_SHIELDS": "ПДПС против щитов",
|
||||
"PHRASE_EFFECTIVE_SDPS_ARMOUR": "ПДПС против брони",
|
||||
"TT_SUMMARY_SPEED": "С полным топливным баком и 4 пунктами в ДВИ",
|
||||
"TT_SUMMARY_SPEED_NONFUNCTIONAL": "маневровые двигатели выключены или превышена максимальная масса с топливом и грузом",
|
||||
"TT_SUMMARY_SPEED_NONFUNCTIONAL": "Маневровые двигатели выключены или превышена максимальная масса с топливом и грузом",
|
||||
"TT_SUMMARY_BOOST": "С полным топливным баком и 4 пунктами в ДВИ",
|
||||
"TT_SUMMARY_BOOST_NONFUNCTIONAL": "Распределитель питания не может обеспечить достаточно энергии для форсажа",
|
||||
"TT_SUMMARY_SHIELDS": "Чистая сила щита, включая усилители",
|
||||
@@ -68,16 +68,16 @@
|
||||
"TT_SUMMARY_DPS": "Урон в секунду при стрельбе из всех орудий",
|
||||
"TT_SUMMARY_EPS": "Расход аккумулятора ОРУЖ в секунду при стрельбе из всех орудий",
|
||||
"TT_SUMMARY_TTD": "Время расхода аккумулятора ОРУЖ при стрельбе из всех орудий и с 4 пунктами в ОРУЖ",
|
||||
"TT_SUMMARY_MAX_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с топливом достаточным только на сам прыжок",
|
||||
"TT_SUMMARY_MAX_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с топливом, достаточным только на сам прыжок",
|
||||
"TT_SUMMARY_UNLADEN_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с полным топливным баком",
|
||||
"TT_SUMMARY_LADEN_SINGLE_JUMP": "Самый дальний возможный прыжок с полным грузовым отсеком и с полным топливным баком",
|
||||
"TT_SUMMARY_UNLADEN_TOTAL_JUMP": "Самая дальняя общая дистанция без груза, с полным топливным баком и при прыжках на максимальное расстояние",
|
||||
"TT_SUMMARY_LADEN_TOTAL_JUMP": "Самая дальняя общая дистанция с полным грузовым отсеком, с полным топливным баком и при прыжках на максимальное расстояние",
|
||||
"HELP_MODIFICATIONS_MENU": "Ткните на номер чтобы ввести новое значение, или потяните вдоль полосы для малых изменений",
|
||||
"HELP_MODIFICATIONS_MENU": "Нажмите на номер, чтобы ввести новое значение, или потяните вдоль полосы для малых изменений",
|
||||
"am": "Блок Автом. Полевого Ремонта",
|
||||
"bh": "Переборки",
|
||||
"bl": "Пучковый Лазер",
|
||||
"bsg": "Двухпоточный Щитогенератор",
|
||||
"bl": "Пучковый лазер",
|
||||
"bsg": "Двухпоточный щитогенератор",
|
||||
"c": "Орудие",
|
||||
"cc": "Контроллер магнитного снаряда для сбора",
|
||||
"ch": "Разбрасыватель дипольных отражателей",
|
||||
@@ -89,7 +89,7 @@
|
||||
"fh": "Ангар для истребителя",
|
||||
"fi": "FSD-перехватчик",
|
||||
"fs": "Топливозаборник",
|
||||
"fsd": "Рамочно Сместительный двигатель",
|
||||
"fsd": "Рамочно-сместительный двигатель",
|
||||
"ft": "Топливный бак",
|
||||
"fx": "Контроллер магнитного снаряда для топлива",
|
||||
"hb": "Контроллер магнитного снаряда для взлома трюма",
|
||||
@@ -110,7 +110,7 @@
|
||||
"pcm": "Каюта пассажира первого класса",
|
||||
"pcq": "Каюта пассажира класса люкс",
|
||||
"pd": "Распределитель питания",
|
||||
"pl": "Ипмульсный лазер",
|
||||
"pl": "Импульсный лазер",
|
||||
"po": "Точечная оборона",
|
||||
"pp": "Силовая установка",
|
||||
"psg": "Призматический щитогенератор",
|
||||
@@ -122,7 +122,7 @@
|
||||
"sc": "Сканер обнаружения",
|
||||
"scb": "Щитонакопитель",
|
||||
"sg": "Щитогенератор",
|
||||
"ss": "Сканер Поверхностей",
|
||||
"ss": "Сканер поверхностей",
|
||||
"t": "Маневровые двигатели",
|
||||
"tp": "Торпедная стойка",
|
||||
"ul": "Пульсирующие лазеры",
|
||||
@@ -130,7 +130,7 @@
|
||||
"emptyrestricted": "пусто (ограниченно)",
|
||||
"damage dealt to": "Урон нанесён",
|
||||
"damage received from": "Урон получен от",
|
||||
"against shields": "Против шитов",
|
||||
"against shields": "Против щитов",
|
||||
"against hull": "Против корпуса",
|
||||
"total effective shield": "Общие эффективные щиты",
|
||||
"ammunition": "Припасы",
|
||||
@@ -149,7 +149,7 @@
|
||||
"eps": "Энергия в секунду",
|
||||
"epsseps": "Энергия в секунду (поддерживаемая энергия в секунду)",
|
||||
"hps": "Нагрев в секунду",
|
||||
"hpsshps": "Heat per second (sustained heat per second)",
|
||||
"hpsshps": "Нагрев в секунду (поддерживаемый нагрев в секунду)",
|
||||
"damage by": "Урон",
|
||||
"damage from": "Урон от",
|
||||
"shield cells": "Щитонакопители",
|
||||
@@ -183,7 +183,7 @@
|
||||
"hullreinforcement": "Укрепление корпуса",
|
||||
"integrity": "Целостность",
|
||||
"jitter": "Дрожание",
|
||||
"kinres": "Сопротивление китетическому урону",
|
||||
"kinres": "Сопротивление кинетическому урону",
|
||||
"maxfuel": "Макс. топлива на прыжок",
|
||||
"mass": "Масса",
|
||||
"optmass": "Оптимизированная масса",
|
||||
@@ -381,4 +381,4 @@
|
||||
"URL": "Ссылка",
|
||||
"WEP": "ОРУЖ",
|
||||
"yes": "Да"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'babel-polyfill';
|
||||
import '@babel/polyfill';
|
||||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import '../less/app.less';
|
||||
|
||||
@@ -44,6 +44,12 @@ export default class AboutPage extends Page {
|
||||
|
||||
<h3>Supporting Coriolis</h3>
|
||||
<p>Coriolis is an open source project, and I work on it in my free time. I have set up a patreon at <a href='https://www.patreon.com/coriolis_elite'>patreon.com/coriolis_elite</a>, which will be used to keep Coriolis up to date and the servers running.</p>
|
||||
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
|
||||
<input type="hidden" name="cmd" value="_s-xclick"/>
|
||||
<input type="hidden" name="hosted_button_id" value="SJBKT2SWEEU68" />
|
||||
<input type="image" src="https://www.paypalobjects.com/en_AU/i/btn/btn_donate_SM.gif" border="0" name="submit" alt="PayPal – The safer, easier way to pay online!" />
|
||||
<img alt="" border="0" src="https://www.paypalobjects.com/en_AU/i/scr/pixel.gif" width="1" height="1" />
|
||||
</form>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,11 +7,20 @@ import Module from './Module';
|
||||
* @param {object} fsd The FDS object/component with maxfuel, fuelmul, fuelpower, optmass
|
||||
* @param {number} fuel Optional - The fuel consumed during the jump
|
||||
* @return {number} Distance in Light Years
|
||||
* @param {object} ship Ship instance
|
||||
*/
|
||||
export function jumpRange(mass, fsd, fuel) {
|
||||
export function jumpRange(mass, fsd, fuel, ship) {
|
||||
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;
|
||||
let jumpAddition = 0;
|
||||
if (ship) {
|
||||
for (const module of ship.internal) {
|
||||
if (module && module.m && module.m.grp === 'gfsb') {
|
||||
jumpAddition += module.m.getJumpBoost();
|
||||
}
|
||||
}
|
||||
}
|
||||
return (Math.pow(Math.min(fuel === undefined ? fsdMaxFuelPerJump : fuel, fsdMaxFuelPerJump) / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass) + jumpAddition;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -21,8 +30,9 @@ export function jumpRange(mass, fsd, fuel) {
|
||||
* @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
|
||||
* @param {object} ship Ship instance
|
||||
*/
|
||||
export function totalJumpRange(mass, fsd, fuel) {
|
||||
export function totalJumpRange(mass, fsd, fuel, ship) {
|
||||
const fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
|
||||
const fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass;
|
||||
|
||||
@@ -30,7 +40,7 @@ export function totalJumpRange(mass, fsd, fuel) {
|
||||
let totalRange = 0;
|
||||
while (fuelRemaining > 0) {
|
||||
const fuelForThisJump = Math.min(fuelRemaining, fsdMaxFuelPerJump);
|
||||
totalRange += this.jumpRange(mass, fsd, fuelForThisJump);
|
||||
totalRange += this.jumpRange(mass, fsd, fuelForThisJump, ship);
|
||||
// Mass is reduced
|
||||
mass -= fuelForThisJump;
|
||||
fuelRemaining -= fuelForThisJump;
|
||||
@@ -60,7 +70,7 @@ export function shieldStrength(mass, baseShield, sg, multiplier) {
|
||||
let ynorm = Math.pow(xnorm, exponent);
|
||||
let mul = minMul + ynorm * (maxMul - minMul);
|
||||
|
||||
return baseShield * mul * multiplier;
|
||||
return (baseShield * mul * multiplier);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,6 +96,16 @@ export function speed(mass, baseSpeed, thrusters, engpip) {
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate pip multiplier for speed.
|
||||
* @param {number} baseSpeed The base speed of ship in data
|
||||
* @param {number} topSpeed The top speed of ship in data
|
||||
* @return {number} The multiplier that pips affect speed.
|
||||
*/
|
||||
export function calcPipSpeed(baseSpeed, topSpeed) {
|
||||
return (topSpeed - baseSpeed) / (4 * topSpeed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate pitch of a ship based on mass and thrusters
|
||||
* @param {number} mass the mass of the ship
|
||||
@@ -201,7 +221,7 @@ function calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, base
|
||||
* Calculate speed for a given setup
|
||||
* @param {number} mass the mass of the ship
|
||||
* @param {number} baseSpeed the base speed of the ship
|
||||
* @param {ojbect} thrusters the thrusters of the ship
|
||||
* @param {object} thrusters the thrusters of the ship
|
||||
* @param {number} engpip the multiplier per pip to engines
|
||||
* @param {number} eng the pips to engines
|
||||
* @param {number} boostFactor the boost factor for ths ship
|
||||
@@ -229,7 +249,7 @@ export function calcSpeed(mass, baseSpeed, thrusters, engpip, eng, boostFactor,
|
||||
* Calculate pitch for a given setup
|
||||
* @param {number} mass the mass of the ship
|
||||
* @param {number} basePitch the base pitch of the ship
|
||||
* @param {ojbect} thrusters the thrusters of the ship
|
||||
* @param {object} thrusters the thrusters of the ship
|
||||
* @param {number} engpip the multiplier per pip to engines
|
||||
* @param {number} eng the pips to engines
|
||||
* @param {number} boostFactor the boost factor for ths ship
|
||||
@@ -320,41 +340,76 @@ export function shieldMetrics(ship, sys) {
|
||||
const maxSysResistance = this.sysResistance(4);
|
||||
|
||||
let shield = {};
|
||||
const dimReturnLine = (res) => 1 - (1 - res) * 0.7;
|
||||
|
||||
const shieldGeneratorSlot = ship.findInternalByGroup('sg');
|
||||
if (shieldGeneratorSlot && shieldGeneratorSlot.enabled && shieldGeneratorSlot.m) {
|
||||
const shieldGenerator = shieldGeneratorSlot.m;
|
||||
|
||||
let res = {
|
||||
kin: shieldGenerator.kinres,
|
||||
therm: shieldGenerator.thermres,
|
||||
expl: shieldGenerator.explres
|
||||
};
|
||||
// Boosters
|
||||
let boost = 1;
|
||||
let boosterExplDmg = 1;
|
||||
let boosterKinDmg = 1;
|
||||
let boosterThermDmg = 1;
|
||||
// const explDim = dimReturnLine(shieldGenerator.explres);
|
||||
// const thermDim = dimReturnLine(shieldGenerator.thermres);
|
||||
// const kinDim = dimReturnLine(shieldGenerator.kinres);
|
||||
for (let slot of ship.hardpoints) {
|
||||
if (slot.enabled && slot.m && slot.m.grp == 'sb') {
|
||||
boost += slot.m.getShieldBoost();
|
||||
res.expl += slot.m.getExplosiveResistance();
|
||||
res.kin += slot.m.getKineticResistance();
|
||||
res.therm += slot.m.getThermalResistance();
|
||||
boosterExplDmg = boosterExplDmg * (1 - slot.m.getExplosiveResistance());
|
||||
boosterKinDmg = boosterKinDmg * (1 - slot.m.getKineticResistance());
|
||||
boosterThermDmg = boosterThermDmg * (1 - slot.m.getThermalResistance());
|
||||
}
|
||||
}
|
||||
if (slot.m && slot.m.grp == 'gsrp') {
|
||||
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
|
||||
|
||||
// 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);
|
||||
// if (res.expl > explDim) {
|
||||
// const overage = (res.expl - explDim) * 0.5;
|
||||
// res.expl = explDim + overage;
|
||||
// boosterExplDmg = explDim + overage;
|
||||
// }
|
||||
//
|
||||
// if (res.therm > thermDim) {
|
||||
// const overage = (res.therm - thermDim) * 0.5;
|
||||
// res.therm = thermDim + overage;
|
||||
// boosterThermDmg = thermDim + overage;
|
||||
// }
|
||||
//
|
||||
// if (res.kin > kinDim) {
|
||||
// const overage = (res.kin - kinDim) * 0.5;
|
||||
// res.kin = kinDim + overage;
|
||||
// boosterKinDmg = kinDim + overage;
|
||||
// }
|
||||
let shieldAddition = 0;
|
||||
if (ship) {
|
||||
for (const module of ship.internal) {
|
||||
if (module && module.m && module.m.grp === 'gsrp') {
|
||||
shieldAddition += module.m.getShieldAddition();
|
||||
}
|
||||
}
|
||||
}
|
||||
let 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 shieldToRecover = (generatorStrength + boostersStrength + shieldAddition) / 2;
|
||||
const powerDistributor = ship.standard[4].m;
|
||||
const sysRechargeRate = this.sysRechargeRate(powerDistributor, sys);
|
||||
|
||||
@@ -373,7 +428,7 @@ export function shieldMetrics(ship, sys) {
|
||||
const remainingShieldToRecover = shieldToRecover - capacitorLifetime * shieldGenerator.getBrokenRegenerationRate();
|
||||
if (sys === 0) {
|
||||
// No system pips so will never recover shields
|
||||
recover = Math.Inf;
|
||||
recover = Math.Infinity;
|
||||
} else {
|
||||
// Recover remaining shields at the rate of the power distributor's recharge
|
||||
recover += remainingShieldToRecover / (sysRechargeRate / 0.6);
|
||||
@@ -381,7 +436,7 @@ export function shieldMetrics(ship, sys) {
|
||||
}
|
||||
|
||||
// Recharge time is the time taken to go from 50% to 100%
|
||||
const shieldToRecharge = (generatorStrength + boostersStrength) / 2;
|
||||
const shieldToRecharge = (generatorStrength + boostersStrength + shieldAddition) / 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
|
||||
@@ -408,8 +463,9 @@ export function shieldMetrics(ship, sys) {
|
||||
shield = {
|
||||
generator: generatorStrength,
|
||||
boosters: boostersStrength,
|
||||
addition: shieldAddition,
|
||||
cells: ship.shieldCells,
|
||||
total: generatorStrength + boostersStrength + ship.shieldCells,
|
||||
total: generatorStrength + boostersStrength + ship.shieldCells + shieldAddition,
|
||||
recover,
|
||||
recharge,
|
||||
};
|
||||
@@ -424,44 +480,71 @@ export function shieldMetrics(ship, sys) {
|
||||
max: 1 - maxSysResistance
|
||||
};
|
||||
|
||||
/**
|
||||
* An object that stores a selection of difference damage multipliers that
|
||||
* deal with a ship's shield strength.
|
||||
* @typedef {Object} ShieldDamageMults
|
||||
* @property {number} generator Base damage multiplier of the shield
|
||||
* contributing it's base resistance.
|
||||
* @property {number} boosters Damage multiplier contributed by all
|
||||
* boosters, i.e. `rawMj / (generator * boosters)` equals shield strength
|
||||
* with 0 pips to sys.
|
||||
* @property {number} sys Damage multiplier contributed by pips to sys.
|
||||
* @property {number} base Damage multiplier with 0 pips to sys; just
|
||||
* boosters and shield generator. Equals `generator * boosters`.
|
||||
* @property {number} total Damage multiplier with current pip settings.
|
||||
* @property {number} max Damage multiplier with 4 pips to sys.
|
||||
*/
|
||||
|
||||
let sgExplosiveDmg = 1 - shieldGenerator.getExplosiveResistance();
|
||||
let sgSbExplosiveDmg = diminishDamageMult(sgExplosiveDmg * 0.7, (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg);
|
||||
/** @type {ShieldDamageMults} */
|
||||
shield.explosive = {
|
||||
generator: 1 - shieldGenerator.getExplosiveResistance(),
|
||||
boosters: boosterExplDmg,
|
||||
generator: sgExplosiveDmg,
|
||||
boosters: sgSbExplosiveDmg / sgExplosiveDmg,
|
||||
sys: (1 - sysResistance),
|
||||
total: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - sysResistance),
|
||||
max: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - maxSysResistance)
|
||||
base: sgSbExplosiveDmg,
|
||||
total: sgSbExplosiveDmg * (1 - sysResistance),
|
||||
max: sgSbExplosiveDmg * (1 - maxSysResistance),
|
||||
};
|
||||
|
||||
let sgKineticDmg = 1 - shieldGenerator.getKineticResistance();
|
||||
let sgSbKineticDmg = diminishDamageMult(sgKineticDmg * 0.7, (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg);
|
||||
/** @type {ShieldDamageMults} */
|
||||
shield.kinetic = {
|
||||
generator: 1 - shieldGenerator.getKineticResistance(),
|
||||
boosters: boosterKinDmg,
|
||||
generator: sgKineticDmg,
|
||||
boosters: sgSbKineticDmg / sgKineticDmg,
|
||||
sys: (1 - sysResistance),
|
||||
total: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - sysResistance),
|
||||
max: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - maxSysResistance)
|
||||
base: sgSbKineticDmg,
|
||||
total: sgSbKineticDmg * (1 - sysResistance),
|
||||
max: sgSbKineticDmg * (1 - maxSysResistance),
|
||||
};
|
||||
|
||||
let sgThermalDmg = 1 - shieldGenerator.getThermalResistance();
|
||||
let sgSbThermalDmg = diminishDamageMult(sgThermalDmg * 0.7, (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg);
|
||||
/** @type {ShieldDamageMults} */
|
||||
shield.thermal = {
|
||||
generator: 1 - shieldGenerator.getThermalResistance(),
|
||||
boosters: boosterThermDmg,
|
||||
generator: sgThermalDmg,
|
||||
boosters: sgSbThermalDmg / sgThermalDmg,
|
||||
sys: (1 - sysResistance),
|
||||
total: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - sysResistance),
|
||||
max: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - maxSysResistance)
|
||||
base: sgSbThermalDmg,
|
||||
total: sgSbThermalDmg * (1 - sysResistance),
|
||||
max: sgSbThermalDmg * (1 - maxSysResistance),
|
||||
};
|
||||
}
|
||||
|
||||
return shield;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate time from one boost to another
|
||||
* @return {number} Boost frequency in seconds
|
||||
* @param ship
|
||||
* @param {Ship} ship Ship object
|
||||
*/
|
||||
export function calcBoost(ship) {
|
||||
if (!ship.boostEnergy || !ship.standard[4] || !ship.standard[4].m) {
|
||||
return undefined;
|
||||
}
|
||||
return ship.boostEnergy / ship.standard[4].m.engrate;
|
||||
return ship.boostEnergy / ship.standard[4].m.getEnginesRechargeRate();
|
||||
}
|
||||
|
||||
|
||||
@@ -477,33 +560,58 @@ export function armourMetrics(ship) {
|
||||
|
||||
let moduleArmour = 0;
|
||||
let moduleProtection = 1;
|
||||
|
||||
const bulkheads = ship.bulkheads.m;
|
||||
let hullExplDmg = 1;
|
||||
let hullKinDmg = 1;
|
||||
let hullThermDmg = 1;
|
||||
|
||||
let hullCausDmg = 1;
|
||||
// const dimReturnLine = (res) => 1 - (1 - res) * 0.7;
|
||||
// let res = {
|
||||
// kin: 0,
|
||||
// therm: 0,
|
||||
// expl: 0
|
||||
// };
|
||||
// Armour from HRPs and module armour from MRPs
|
||||
for (let slot of ship.internal) {
|
||||
if (slot.m && slot.m.grp == 'hr') {
|
||||
if (slot.m && (slot.m.grp === 'hr' || slot.m.grp === 'ghrp' || slot.m.grp == 'mahr')) {
|
||||
armourReinforcement += slot.m.getHullReinforcement();
|
||||
// Hull boost for HRPs is applied against the ship's base armour
|
||||
armourReinforcement += ship.baseArmour * slot.m.getModValue('hullboost') / 10000;
|
||||
|
||||
// res.expl += slot.m.getExplosiveResistance();
|
||||
// res.kin += slot.m.getKineticResistance();
|
||||
// res.therm += slot.m.getThermalResistance();
|
||||
hullExplDmg = hullExplDmg * (1 - slot.m.getExplosiveResistance());
|
||||
hullKinDmg = hullKinDmg * (1 - slot.m.getKineticResistance());
|
||||
hullThermDmg = hullThermDmg * (1 - slot.m.getThermalResistance());
|
||||
hullCausDmg = hullCausDmg * (1 - slot.m.getCausticResistance());
|
||||
}
|
||||
if (slot.m && slot.m.grp == 'mrp') {
|
||||
if (slot.m && (slot.m.grp == 'mrp' || slot.m.grp == 'gmrp')) {
|
||||
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 explDim = dimReturnLine(bulkheads.explres);
|
||||
// const thermDim = dimReturnLine(bulkheads.thermres);
|
||||
// const kinDim = dimReturnLine(bulkheads.kinres);
|
||||
// if (res.expl > explDim) {
|
||||
// const overage = (res.expl - explDim) * 0.5;
|
||||
// res.expl = explDim + overage;
|
||||
// hullExplDmg = explDim + overage;
|
||||
// }
|
||||
//
|
||||
// if (res.therm > thermDim) {
|
||||
// const overage = (res.therm - thermDim) * 0.5;
|
||||
// res.therm = thermDim + overage;
|
||||
// hullThermDmg = thermDim + overage;
|
||||
// }
|
||||
//
|
||||
// if (res.kin > kinDim) {
|
||||
// const overage = (res.kin - kinDim) * 0.5;
|
||||
// res.kin = kinDim + overage;
|
||||
// hullKinDmg = kinDim + overage;
|
||||
// }
|
||||
|
||||
const armour = {
|
||||
bulkheads: armourBulkheads,
|
||||
@@ -521,24 +629,41 @@ export function armourMetrics(ship) {
|
||||
total: 1
|
||||
};
|
||||
|
||||
let armourExplDmg = diminishDamageMult(0.7, 1 - ship.bulkheads.m.getExplosiveResistance());
|
||||
let armourReinforcedExplDmg = diminishDamageMult(0.7, (1 - ship.bulkheads.m.getExplosiveResistance()) * hullExplDmg);
|
||||
armour.explosive = {
|
||||
bulkheads: 1 - ship.bulkheads.m.getExplosiveResistance(),
|
||||
reinforcement: hullExplDmg,
|
||||
total: (1 - ship.bulkheads.m.getExplosiveResistance()) * hullExplDmg
|
||||
bulkheads: armourExplDmg,
|
||||
reinforcement: armourReinforcedExplDmg / armourExplDmg,
|
||||
total: armourReinforcedExplDmg,
|
||||
res: 1 - armourReinforcedExplDmg
|
||||
};
|
||||
|
||||
let armourKinDmg = diminishDamageMult(0.7, 1 - ship.bulkheads.m.getKineticResistance());
|
||||
let armourReinforcedKinDmg = diminishDamageMult(0.7, (1 - ship.bulkheads.m.getKineticResistance()) * hullKinDmg);
|
||||
armour.kinetic = {
|
||||
bulkheads: 1 - ship.bulkheads.m.getKineticResistance(),
|
||||
reinforcement: hullKinDmg,
|
||||
total: (1 - ship.bulkheads.m.getKineticResistance()) * hullKinDmg
|
||||
bulkheads: armourKinDmg,
|
||||
reinforcement: armourReinforcedKinDmg / armourKinDmg,
|
||||
total: armourReinforcedKinDmg,
|
||||
res: 1 - armourReinforcedKinDmg
|
||||
};
|
||||
|
||||
let armourThermDmg = diminishDamageMult(0.7, 1 - ship.bulkheads.m.getThermalResistance());
|
||||
let armourReinforcedThermDmg = diminishDamageMult(0.7, (1 - ship.bulkheads.m.getThermalResistance()) * hullThermDmg);
|
||||
armour.thermal = {
|
||||
bulkheads: 1 - ship.bulkheads.m.getThermalResistance(),
|
||||
reinforcement: hullThermDmg,
|
||||
total: (1 - ship.bulkheads.m.getThermalResistance()) * hullThermDmg
|
||||
bulkheads: armourThermDmg,
|
||||
reinforcement: armourReinforcedThermDmg / armourThermDmg,
|
||||
total: armourReinforcedThermDmg,
|
||||
res: 1 - armourReinforcedThermDmg
|
||||
};
|
||||
|
||||
let armourCausDmg = diminishDamageMult(0.7, 1 - ship.bulkheads.m.getCausticResistance());
|
||||
let armourReinforcedCausDmg = diminishDamageMult(0.7, (1 - ship.bulkheads.m.getCausticResistance()) * hullCausDmg);
|
||||
armour.caustic = {
|
||||
bulkheads: armourCausDmg,
|
||||
reinforcement: armourReinforcedCausDmg / armourCausDmg,
|
||||
total: armourReinforcedCausDmg,
|
||||
res: 1 - armourReinforcedCausDmg,
|
||||
};
|
||||
return armour;
|
||||
}
|
||||
|
||||
@@ -710,20 +835,55 @@ export function _sustainedDps(ship, opponent, opponentShields, opponentArmour, e
|
||||
return { shieldsdps, armoursdps, eps };
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores SDPS split up by type.
|
||||
* @typedef {Object} SDps
|
||||
* @property {number} absolute Damage of type absolute
|
||||
* @property {number} explosive Damage of type explosive
|
||||
* @property {number} kinetic Damage of type kinetic
|
||||
* @property {number} thermal Damage of type thermal
|
||||
* @property {number} [total] Sum of all damage types
|
||||
*/
|
||||
|
||||
/**
|
||||
* An object that holds information about SDPS for a given weapon and opponent.
|
||||
* @typedef {Object} WeaponDamage
|
||||
* @property {number} eps Energy per second
|
||||
* @property {Object} damage An object that stores damage inflicted by
|
||||
* the weapon.
|
||||
* @property {Object} effectiveness An object that stores the effectiveness of
|
||||
* the weapon against the opponent given.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Stores overall SDPS and against a given opponent's shields and armour.
|
||||
* @typedef {Object} WeaponDamage~damage
|
||||
* @property {SDps} base Overall SDPS.
|
||||
* @property {SDps} shields SDPS against the given opponent's shields.
|
||||
* @property {SDps} armour SDPS against the given opponent's armour.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Calculate the sustained DPS for a weapon at a given range
|
||||
* @param {Object} m The weapon
|
||||
* @param {Object} opponent The opponent 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
|
||||
* @returns {WeaponDamage} Sustained DPS for shield and armour
|
||||
*/
|
||||
export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementrange) {
|
||||
const opponentHasShields = opponentShields.generator ? true : false;
|
||||
const weapon = {
|
||||
eps: 0,
|
||||
damage: {
|
||||
base: {
|
||||
absolute: 0,
|
||||
explosive: 0,
|
||||
kinetic: 0,
|
||||
thermal: 0,
|
||||
total: 0,
|
||||
},
|
||||
shields: {
|
||||
absolute: 0,
|
||||
explosive: 0,
|
||||
@@ -757,7 +917,7 @@ export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour
|
||||
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();
|
||||
let sDps = m.getSDps();
|
||||
|
||||
// Take fall-off in to account
|
||||
const falloff = m.getFalloff();
|
||||
@@ -768,6 +928,12 @@ export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour
|
||||
sDps *= dropoff;
|
||||
}
|
||||
|
||||
weapon.damage.base.absolute = sDps * m.getDamageDist().A;
|
||||
weapon.damage.base.explosive = sDps * m.getDamageDist().E;
|
||||
weapon.damage.base.kinetic = sDps * m.getDamageDist().K;
|
||||
weapon.damage.base.thermal = sDps * m.getDamageDist().T;
|
||||
weapon.damage.base.total = sDps;
|
||||
|
||||
// Piercing/hardness modifier (for armour only)
|
||||
const armourMultiple = m.getPiercing() >= opponent.hardness ? 1 : m.getPiercing() / opponent.hardness;
|
||||
weapon.effectiveness.armour.hardness = armourMultiple;
|
||||
@@ -865,3 +1031,17 @@ export function timeToDeplete(amount, dps, eps, capacity, recharge) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies diminishing returns to resistances.
|
||||
* @param {number} diminishFrom The base resistance up to which no diminishing returns are applied.
|
||||
* @param {number} damageMult Resistance as damage multiplier
|
||||
* @returns {number} Actual damage multiplier
|
||||
*/
|
||||
export function diminishDamageMult(diminishFrom, damageMult) {
|
||||
if (damageMult > diminishFrom) {
|
||||
return damageMult;
|
||||
} else {
|
||||
return (diminishFrom / 2) + 0.5 * damageMult;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ export const ModuleGroupToName = {
|
||||
// Standard
|
||||
pp: 'Power Plant',
|
||||
gpp: 'Guardian Hybrid Power Plant',
|
||||
gpd: 'Guardian Hybrid Power Distributor',
|
||||
gpd: 'Guardian Power Distributor',
|
||||
t: 'Thrusters',
|
||||
fsd: 'Frame Shift Drive',
|
||||
ls: 'Life Support',
|
||||
@@ -52,6 +52,11 @@ export const ModuleGroupToName = {
|
||||
pcq: 'Luxury Passenger Cabin',
|
||||
cc: 'Collector Limpet Controller',
|
||||
ss: 'Surface Scanner',
|
||||
gsrp: 'Guardian Shield Reinforcement Packages',
|
||||
gfsb: 'Guardian Frame Shift Drive Booster',
|
||||
ghrp: 'Guardian Hull Reinforcement Package',
|
||||
gmrp: 'Guardian Module Reinforcement Package',
|
||||
mahr: 'Meta Alloy Hull Reinforcement Package',
|
||||
|
||||
// Hard Points
|
||||
bl: 'Beam Laser',
|
||||
@@ -81,8 +86,14 @@ export const ModuleGroupToName = {
|
||||
sfn: 'Shutdown Field Neutraliser',
|
||||
xs: 'Xeno Scanner',
|
||||
rcpl: 'Recon Limpet Controller',
|
||||
rsl: 'Research Limpet Controller',
|
||||
dtl: 'Decontamination Limpet Controller',
|
||||
gpc: 'Guardian Plasma Charger',
|
||||
ggc: 'Guardian Gauss Cannon',
|
||||
tbsc: 'Shock Cannon',
|
||||
gsc: 'Guardian Shard Cannon',
|
||||
tbem: 'Enzyme Missile Rack',
|
||||
tbrfl: 'Remote Release Flechette Launcher',
|
||||
};
|
||||
|
||||
let GrpNameToCodeMap = {};
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
* Modification - a modification and its value
|
||||
*/
|
||||
export default class Modification {
|
||||
|
||||
/**
|
||||
* @param {String} id Unique modification ID
|
||||
* @param {Number} value Value of the modification
|
||||
@@ -11,5 +10,4 @@ export default class Modification {
|
||||
this.id = id;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -17,7 +17,6 @@ function filter(arr, maxClass, minClass, mass) {
|
||||
* The available module set for a specific ship
|
||||
*/
|
||||
export default class ModuleSet {
|
||||
|
||||
/**
|
||||
* Instantiate the module set
|
||||
* @param {Object} modules All Modules
|
||||
|
||||
@@ -7,9 +7,10 @@ import LZString from 'lz-string';
|
||||
import * as _ from 'lodash';
|
||||
import isEqual from 'lodash/lang';
|
||||
import { Ships, Modifications } from 'coriolis-data/dist';
|
||||
import { chain } from 'lodash';
|
||||
const zlib = require('zlib');
|
||||
|
||||
const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs', 'fh'];
|
||||
const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs', 'fh', 'gfsb'];
|
||||
|
||||
// Constants for modifications struct
|
||||
const SLOT_ID_DONE = -1;
|
||||
@@ -70,7 +71,6 @@ function reduceToIDs(idArray, slot, slotIndex) {
|
||||
* Ship Model - Encapsulates and models in-game ship behavior
|
||||
*/
|
||||
export default class Ship {
|
||||
|
||||
/**
|
||||
* @param {String} id Unique ship Id / Key
|
||||
* @param {Object} properties Basic ship properties such as name, manufacturer, mass, etc
|
||||
@@ -151,7 +151,7 @@ export default class Ship {
|
||||
* @return {Number} Jump range in Light Years
|
||||
*/
|
||||
calcLadenRange(massDelta, fuel, fsd) {
|
||||
return Calc.jumpRange(this.ladenMass + (massDelta || 0), fsd || this.standard[2].m, fuel);
|
||||
return Calc.jumpRange(this.ladenMass + (massDelta || 0), fsd || this.standard[2].m, fuel, this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -164,7 +164,7 @@ export default class Ship {
|
||||
calcUnladenRange(massDelta, fuel, fsd) {
|
||||
fsd = fsd || this.standard[2].m;
|
||||
let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
|
||||
return Calc.jumpRange(this.unladenMass + (massDelta || 0) + Math.min(fsdMaxFuelPerJump, fuel || this.fuelCapacity), fsd || this.standard[2].m, fuel);
|
||||
return Calc.jumpRange(this.unladenMass + (massDelta || 0) + Math.min(fsdMaxFuelPerJump, fuel || this.fuelCapacity), fsd || this.standard[2].m, fuel, this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -239,7 +239,6 @@ export default class Ship {
|
||||
}
|
||||
sg = sgSlot.m;
|
||||
}
|
||||
|
||||
// TODO Not accurate if the ship has modified shield boosters
|
||||
return Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sg, 1 + (multiplierDelta || 0));
|
||||
}
|
||||
@@ -416,16 +415,16 @@ export default class Ship {
|
||||
clearModifications(m) {
|
||||
m.mods = {};
|
||||
this.updatePowerGenerated()
|
||||
.updatePowerUsed()
|
||||
.recalculateMass()
|
||||
.updateJumpStats()
|
||||
.recalculateShield()
|
||||
.recalculateShieldCells()
|
||||
.recalculateArmour()
|
||||
.recalculateDps()
|
||||
.recalculateEps()
|
||||
.recalculateHps()
|
||||
.updateMovement();
|
||||
.updatePowerUsed()
|
||||
.recalculateMass()
|
||||
.updateJumpStats()
|
||||
.recalculateShield()
|
||||
.recalculateShieldCells()
|
||||
.recalculateArmour()
|
||||
.recalculateDps()
|
||||
.recalculateEps()
|
||||
.recalculateHps()
|
||||
.updateMovement();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -485,10 +484,7 @@ export default class Ship {
|
||||
* @param {Object} m The module for which to clear the blueprint
|
||||
*/
|
||||
clearModuleSpecial(m) {
|
||||
if (m.blueprint) {
|
||||
m.blueprint.special = null;
|
||||
}
|
||||
this.recalculateDps().recalculateHps().recalculateEps();
|
||||
this.setModuleSpecial(m, null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -497,68 +493,62 @@ export default class Ship {
|
||||
* @param {Object} name The name of the modification to change
|
||||
* @param {Number} value The new value of the modification. The value of the modification is scaled to provide two decimal places of precision in an integer. For example 1.23% is stored as 123
|
||||
* @param {bool} sentfromui True if this update was sent from the UI
|
||||
* @param {bool} isAbsolute True if value is an absolute value and not a
|
||||
* modification value
|
||||
*/
|
||||
setModification(m, name, value, sentfromui) {
|
||||
setModification(m, name, value, sentfromui, isAbsolute) {
|
||||
if (isNaN(value)) {
|
||||
// Value passed is invalid; reset it to 0
|
||||
value = 0;
|
||||
}
|
||||
|
||||
if (isAbsolute) {
|
||||
m.setPretty(name, value, sentfromui);
|
||||
} else {
|
||||
m.setModValue(name, value, sentfromui);
|
||||
}
|
||||
|
||||
// Handle special cases
|
||||
if (name === 'pgen') {
|
||||
// Power generation
|
||||
m.setModValue(name, value, sentfromui);
|
||||
this.updatePowerGenerated();
|
||||
} else if (name === 'power') {
|
||||
// Power usage
|
||||
m.setModValue(name, value, sentfromui);
|
||||
this.updatePowerUsed();
|
||||
} else if (name === 'mass') {
|
||||
// Mass
|
||||
m.setModValue(name, value, sentfromui);
|
||||
this.recalculateMass();
|
||||
this.updateMovement();
|
||||
this.updateJumpStats();
|
||||
} else if (name === 'maxfuel') {
|
||||
m.setModValue(name, value, sentfromui);
|
||||
this.updateJumpStats();
|
||||
} else if (name === 'optmass') {
|
||||
m.setModValue(name, value, sentfromui);
|
||||
// Could be for any of thrusters, FSD or shield
|
||||
this.updateMovement();
|
||||
this.updateJumpStats();
|
||||
this.recalculateShield();
|
||||
} else if (name === 'optmul') {
|
||||
m.setModValue(name, value, sentfromui);
|
||||
// Could be for any of thrusters, FSD or shield
|
||||
this.updateMovement();
|
||||
this.updateJumpStats();
|
||||
this.recalculateShield();
|
||||
} else if (name === 'shieldboost') {
|
||||
m.setModValue(name, value, sentfromui);
|
||||
this.recalculateShield();
|
||||
} else if (name === 'hullboost' || name === 'hullreinforcement' || name === 'modulereinforcement') {
|
||||
m.setModValue(name, value, sentfromui);
|
||||
this.recalculateArmour();
|
||||
} else if (name === 'shieldreinforcement') {
|
||||
m.setModValue(name, value, sentfromui);
|
||||
this.recalculateShieldCells();
|
||||
} else if (name === 'burst' || name == 'burstrof' || name === 'clip' || name === 'damage' || name === 'distdraw' || name === 'jitter' || name === 'piercing' || name === 'range' || name === 'reload' || name === 'rof' || name === 'thermload') {
|
||||
m.setModValue(name, value, sentfromui);
|
||||
this.recalculateDps();
|
||||
this.recalculateHps();
|
||||
this.recalculateEps();
|
||||
} 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 === 'engcap') {
|
||||
m.setModValue(name, value, sentfromui);
|
||||
// Might have resulted in a change in boostability
|
||||
this.updateMovement();
|
||||
} else {
|
||||
// Generic
|
||||
m.setModValue(name, value, sentfromui);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -705,16 +695,16 @@ export default class Ship {
|
||||
// Update aggragated stats
|
||||
if (comps) {
|
||||
this.updatePowerGenerated()
|
||||
.updatePowerUsed()
|
||||
.recalculateMass()
|
||||
.updateJumpStats()
|
||||
.recalculateShield()
|
||||
.recalculateShieldCells()
|
||||
.recalculateArmour()
|
||||
.recalculateDps()
|
||||
.recalculateEps()
|
||||
.recalculateHps()
|
||||
.updateMovement();
|
||||
.updatePowerUsed()
|
||||
.recalculateMass()
|
||||
.updateJumpStats()
|
||||
.recalculateShield()
|
||||
.recalculateShieldCells()
|
||||
.recalculateArmour()
|
||||
.recalculateDps()
|
||||
.recalculateEps()
|
||||
.recalculateHps()
|
||||
.updateMovement();
|
||||
}
|
||||
|
||||
return this.updatePowerPrioritesString().updatePowerEnabledString().updateModificationsString();
|
||||
@@ -941,9 +931,14 @@ export default class Ship {
|
||||
let epsChanged = n && n.getEps() || old && old.getEps();
|
||||
let hpsChanged = n && n.getHps() || old && old.getHps();
|
||||
|
||||
let armourChange = (slot === this.bulkheads) || (n && n.grp === 'hr') || (old && old.grp === 'hr') || (n && n.grp === 'mrp') || (old && old.grp === 'mrp');
|
||||
let armourChange = (slot === this.bulkheads) ||
|
||||
(n && n.grp === 'hr') || (old && old.grp === 'hr') ||
|
||||
(n && n.grp === 'ghrp') || (old && old.grp === 'ghrp') ||
|
||||
(n && n.grp == 'mahr') || (old && old.grp == 'mahr') ||
|
||||
(n && n.grp === 'mrp') || (old && old.grp === 'mrp') ||
|
||||
(n && n.grp === 'gmrp') || (old && old.grp == 'gmrp');
|
||||
|
||||
let shieldChange = (n && n.grp === 'bsg') || (old && old.grp === 'bsg') || (n && n.grp === 'psg') || (old && old.grp === 'psg') || (n && n.grp === 'sg') || (old && old.grp === 'sg') || (n && n.grp === 'sb') || (old && old.grp === 'sb');
|
||||
let shieldChange = (n && n.grp === 'bsg') || (old && old.grp === 'bsg') || (n && n.grp === 'psg') || (old && old.grp === 'psg') || (n && n.grp === 'sg') || (old && old.grp === 'sg') || (n && n.grp === 'sb') || (old && old.grp === 'sb') || (old && old.grp === 'gsrp') || (n && n.grp === 'gsrp');
|
||||
|
||||
let shieldCellsChange = (n && n.grp === 'scb') || (old && old.grp === 'scb');
|
||||
|
||||
@@ -1000,25 +995,6 @@ export default class Ship {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate diminishing returns value, where values below a given limit are returned
|
||||
* as-is, and values between the lower and upper limit of the diminishing returns are
|
||||
* given at half value.
|
||||
* Commonly used for resistances.
|
||||
* @param {Number} val The value
|
||||
* @param {Number} drll The lower limit for diminishing returns
|
||||
* @param {Number} drul The upper limit for diminishing returns
|
||||
* @return {this} The ship instance (for chaining operations)
|
||||
*/
|
||||
diminishingReturns(val, drll, drul) {
|
||||
if (val < drll) {
|
||||
val = drll;
|
||||
} else if (val < drul) {
|
||||
val = drul - (drul - val) / 2;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate damage per second and related items for weapons
|
||||
* @return {this} The ship instance (for chaining operations)
|
||||
@@ -1205,38 +1181,35 @@ export default class Ship {
|
||||
|
||||
unladenMass += this.bulkheads.m.getMass();
|
||||
|
||||
for (let slotNum in this.standard) {
|
||||
const slot = this.standard[slotNum];
|
||||
if (slot.m) {
|
||||
unladenMass += slot.m.getMass();
|
||||
if (slot.m.grp === 'ft') {
|
||||
fuelCapacity += slot.m.fuel;
|
||||
}
|
||||
}
|
||||
}
|
||||
let slots = this.standard.concat(this.internal, this.hardpoints);
|
||||
// TODO: create class for slot and also add slot.get
|
||||
// handle unladen mass
|
||||
unladenMass += chain(slots)
|
||||
.map(slot => slot.m ? slot.m.get('mass') : null)
|
||||
.map(mass => mass || 0)
|
||||
.reduce((sum, mass) => sum + mass)
|
||||
.value();
|
||||
|
||||
for (let slotNum in this.internal) {
|
||||
const slot = this.internal[slotNum];
|
||||
if (slot.m) {
|
||||
unladenMass += slot.m.getMass();
|
||||
if (slot.m.grp === 'ft') {
|
||||
fuelCapacity += slot.m.fuel;
|
||||
} else if (slot.m.grp === 'cr') {
|
||||
cargoCapacity += slot.m.cargo;
|
||||
} else if (slot.m.grp.slice(0,2) === 'pc') {
|
||||
if (slot.m.passengers) {
|
||||
passengerCapacity += slot.m.passengers;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// handle fuel capacity
|
||||
fuelCapacity += chain(slots)
|
||||
.map(slot => slot.m ? slot.m.get('fuel') : null)
|
||||
.map(fuel => fuel || 0)
|
||||
.reduce((sum, fuel) => sum + fuel)
|
||||
.value();
|
||||
|
||||
for (let slotNum in this.hardpoints) {
|
||||
const slot = this.hardpoints[slotNum];
|
||||
if (slot.m) {
|
||||
unladenMass += slot.m.getMass();
|
||||
}
|
||||
}
|
||||
// handle cargo capacity
|
||||
cargoCapacity += chain(slots)
|
||||
.map(slot => slot.m ? slot.m.get('cargo') : null)
|
||||
.map(cargo => cargo || 0)
|
||||
.reduce((sum, cargo) => sum + cargo)
|
||||
.value();
|
||||
|
||||
// handle passenger capacity
|
||||
passengerCapacity += chain(slots)
|
||||
.map(slot => slot.m ? slot.m.get('passengers') : null)
|
||||
.map(passengers => passengers || 0)
|
||||
.reduce((sum, passengers) => sum + passengers)
|
||||
.value();
|
||||
|
||||
// Update global stats
|
||||
this.unladenMass = unladenMass;
|
||||
@@ -1277,7 +1250,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.generator + metrics.boosters : 0;
|
||||
this.shield = metrics.generator ? metrics.generator + metrics.boosters + metrics.addition : 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;
|
||||
@@ -1310,45 +1283,15 @@ export default class Ship {
|
||||
*/
|
||||
recalculateArmour() {
|
||||
// Armour from bulkheads
|
||||
let bulkhead = this.bulkheads.m;
|
||||
let armour = this.baseArmour + (this.baseArmour * bulkhead.getHullBoost());
|
||||
let modulearmour = 0;
|
||||
let moduleprotection = 1;
|
||||
let hullExplRes = 1 - bulkhead.getExplosiveResistance();
|
||||
const hullExplResDRStart = hullExplRes * 0.7;
|
||||
const hullExplResDREnd = hullExplRes * 0;
|
||||
let hullKinRes = 1 - bulkhead.getKineticResistance();
|
||||
const hullKinResDRStart = hullKinRes * 0.7;
|
||||
const hullKinResDREnd = hullKinRes * 0;
|
||||
let hullThermRes = 1 - bulkhead.getThermalResistance();
|
||||
const hullThermResDRStart = hullThermRes * 0.7;
|
||||
const hullThermResDREnd = hullThermRes * 0;
|
||||
|
||||
// Armour from HRPs and module armour from MRPs
|
||||
for (let slot of this.internal) {
|
||||
if (slot.m && slot.m.grp == 'hr') {
|
||||
armour += slot.m.getHullReinforcement();
|
||||
// Hull boost for HRPs is applied against the ship's base armour
|
||||
armour += this.baseArmour * slot.m.getModValue('hullboost') / 10000;
|
||||
|
||||
hullExplRes *= (1 - slot.m.getExplosiveResistance());
|
||||
hullKinRes *= (1 - slot.m.getKineticResistance());
|
||||
hullThermRes *= (1 - slot.m.getThermalResistance());
|
||||
}
|
||||
if (slot.m && slot.m.grp == 'mrp') {
|
||||
modulearmour += slot.m.getIntegrity();
|
||||
moduleprotection = moduleprotection * (1 - slot.m.getProtection());
|
||||
}
|
||||
}
|
||||
moduleprotection = 1 - moduleprotection;
|
||||
|
||||
this.armour = armour;
|
||||
this.modulearmour = modulearmour;
|
||||
this.moduleprotection = moduleprotection;
|
||||
this.hullExplRes = 1 - this.diminishingReturns(hullExplRes, hullExplResDREnd, hullExplResDRStart);
|
||||
this.hullKinRes = 1 - this.diminishingReturns(hullKinRes, hullKinResDREnd, hullKinResDRStart);
|
||||
this.hullThermRes = 1 - this.diminishingReturns(hullThermRes, hullThermResDREnd, hullThermResDRStart);
|
||||
let metrics = Calc.armourMetrics(this);
|
||||
|
||||
this.armour = metrics.total ? metrics.total : 0;
|
||||
this.modulearmour = metrics.modulearmour;
|
||||
this.moduleprotection = metrics.moduleprotection;
|
||||
this.hullExplRes = 1 - metrics.explosive.total;
|
||||
this.hullKinRes = 1 - metrics.kinetic.total;
|
||||
this.hullThermRes = 1 - metrics.thermal.total;
|
||||
this.hullCausRes = 1 - metrics.caustic.total;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -1360,10 +1303,10 @@ export default class Ship {
|
||||
let fsd = this.standard[2].m; // Frame Shift Drive;
|
||||
let { unladenMass, fuelCapacity } = this;
|
||||
this.unladenRange = this.calcUnladenRange(); // Includes fuel weight for jump
|
||||
this.fullTankRange = Calc.jumpRange(unladenMass + fuelCapacity, fsd); // Full Tank
|
||||
this.fullTankRange = Calc.jumpRange(unladenMass + fuelCapacity, fsd, this); // Full Tank
|
||||
this.ladenRange = this.calcLadenRange(); // Includes full tank and caro
|
||||
this.unladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity, fsd, fuelCapacity);
|
||||
this.ladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity + this.cargoCapacity, fsd, fuelCapacity);
|
||||
this.unladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity, fsd, fuelCapacity, this);
|
||||
this.ladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity + this.cargoCapacity, fsd, fuelCapacity, this);
|
||||
this.maxJumpCount = Math.ceil(fuelCapacity / fsd.getMaxFuelPerJump());
|
||||
return this;
|
||||
}
|
||||
@@ -1739,11 +1682,11 @@ export default class Ship {
|
||||
updated;
|
||||
|
||||
this.useBulkhead(0)
|
||||
.use(standard[2], fsd) // FSD
|
||||
.use(standard[3], ls) // Life Support
|
||||
.use(standard[5], s) // Sensors
|
||||
.use(standard[4], pd) // Power Distributor
|
||||
.use(standard[6], ft); // Fuel Tank
|
||||
.use(standard[2], fsd) // FSD
|
||||
.use(standard[3], ls) // Life Support
|
||||
.use(standard[5], s) // Sensors
|
||||
.use(standard[4], pd) // Power Distributor
|
||||
.use(standard[6], ft); // Fuel Tank
|
||||
|
||||
// Turn off nearly everything
|
||||
if (m.fsdDisabled) this.setSlotEnabled(this.standard[2], false);
|
||||
|
||||
82
src/app/shipyard/StatsFormatting.js
Normal file
82
src/app/shipyard/StatsFormatting.js
Normal file
@@ -0,0 +1,82 @@
|
||||
export const SI_PREFIXES = {
|
||||
'Y': 1e+24, // Yotta
|
||||
'Z': 1e+21, // Zetta
|
||||
'E': 1e+18, // Peta
|
||||
'P': 1e+15, // Peta
|
||||
'T': 1e+12, // Tera
|
||||
'G': 1e+9, // Giga
|
||||
'M': 1e+6, // Mega
|
||||
'k': 1e+3, // Kilo
|
||||
'h': 1e+2, // Hekto
|
||||
'da': 1e+1, // Deka
|
||||
'': 1,
|
||||
'd': 1e-1, // Dezi
|
||||
'c': 1e-2, // Zenti
|
||||
'm': 1e-3, // Milli
|
||||
'μ': 1e-6, // mikro not supported due to charset
|
||||
'n': 10e-9, // Nano
|
||||
'p': 1e-12, // Nano
|
||||
'f': 1e-15, // Femto
|
||||
'a': 1e-18, // Atto
|
||||
'z': 1e-21, // Zepto
|
||||
'y': 1e-24 // Yokto
|
||||
};
|
||||
|
||||
export const STATS_FORMATTING = {
|
||||
'ammo': { 'format': 'int', },
|
||||
'boot': { 'format': 'int', 'unit': 'secs' },
|
||||
'brokenregen': { 'format': 'round1', 'unit': 'ps' },
|
||||
'burst': { 'format': 'int' },
|
||||
'burstrof': { 'format': 'round1', 'unit': 'ps' },
|
||||
'causres': { 'format': 'pct' },
|
||||
'clip': { 'format': 'int' },
|
||||
'damage': { 'format': 'round' },
|
||||
'dps': { 'format': 'round', 'units': 'ps', 'synthetic': 'getDps' },
|
||||
'dpe': { 'format': 'round', 'units': 'ps', 'synthetic': 'getDpe' },
|
||||
'distdraw': { 'format': 'round', 'unit': 'MW' },
|
||||
'duration': { 'format': 'round1', 'unit': 's' },
|
||||
'eff': { 'format': 'round2' },
|
||||
'engcap': { 'format': 'round1', 'unit': 'MJ' },
|
||||
'engrate': { 'format': 'round1', 'unit': 'MW' },
|
||||
'eps': { 'format': 'round', 'units': 'ps', 'synthetic': 'getEps' },
|
||||
'explres': { 'format': 'pct' },
|
||||
'facinglimit': { 'format': 'round1', 'unit': 'ang' },
|
||||
'falloff': { 'format': 'round', 'unit': 'km', 'storedUnit': 'm' },
|
||||
'fallofffromrange': { 'format': 'round', 'unit': 'km', 'storedUnit': 'm', 'synthetic': 'getFalloff' },
|
||||
'hps': { 'format': 'round', 'units': 'ps', 'synthetic': 'getHps' },
|
||||
'hullboost': { 'format': 'pct1', 'change': 'additive' },
|
||||
'hullreinforcement': { 'format': 'int' },
|
||||
'integrity': { 'format': 'round1' },
|
||||
'jitter': { 'format': 'round', 'unit': 'ang' },
|
||||
'kinres': { 'format': 'pct' },
|
||||
'mass': { 'format': 'round1', 'unit': 'T' },
|
||||
'maxfuel': { 'format': 'round1', 'unit': 'T' },
|
||||
'optmass': { 'format': 'int', 'unit': 'T' },
|
||||
'optmul': { 'format': 'pct', 'change': 'additive' },
|
||||
'pgen': { 'format': 'round1', 'unit': 'MW' },
|
||||
'piercing': { 'format': 'int' },
|
||||
'power': { 'format': 'round', 'unit': 'MW' },
|
||||
'protection': { 'format': 'pct' },
|
||||
'range': { 'format': 'f2', 'unit': 'km', 'storedUnit': 'm' },
|
||||
'ranget': { 'format': 'f1', 'unit': 's' },
|
||||
'regen': { 'format': 'round1', 'unit': 'ps' },
|
||||
'reload': { 'format': 'int', 'unit': 's' },
|
||||
'rof': { 'format': 'round1', 'unit': 'ps' },
|
||||
'angle': { 'format': 'round1', 'unit': 'ang' },
|
||||
'scanrate': { 'format': 'int' },
|
||||
'scantime': { 'format': 'round1', 'unit': 's' },
|
||||
'sdps': { 'format': 'round1', 'units': 'ps', 'synthetic': 'getSDps' },
|
||||
'shield': { 'format': 'int', 'unit': 'MJ' },
|
||||
'shieldaddition': { 'format': 'round1', 'unit': 'MJ' },
|
||||
'shieldboost': { 'format': 'pct1', 'change': 'additive' },
|
||||
'shieldreinforcement': { 'format': 'round1', 'unit': 'MJ' },
|
||||
'shotspeed': { 'format': 'int', 'unit': 'm/s' },
|
||||
'spinup': { 'format': 'round1', 'unit': 's' },
|
||||
'syscap': { 'format': 'round1', 'unit': 'MJ' },
|
||||
'sysrate': { 'format': 'round1', 'unit': 'MW' },
|
||||
'thermload': { 'format': 'round1' },
|
||||
'thermres': { 'format': 'pct' },
|
||||
'wepcap': { 'format': 'round1', 'unit': 'MJ' },
|
||||
'weprate': { 'format': 'round1', 'unit': 'MW' },
|
||||
'jumpboost': { 'format': 'round1', 'unit': 'LY' }
|
||||
};
|
||||
@@ -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_CMDR_NAME = 'cmdrName';
|
||||
const LS_KEY_OUTFITTING_TAB = 'outfittingTab';
|
||||
const LS_KEY_INSURANCE = 'insurance';
|
||||
const LS_KEY_SHIP_DISCOUNT = 'shipDiscount';
|
||||
@@ -14,6 +15,7 @@ const LS_KEY_SIZE_RATIO = 'sizeRatio';
|
||||
const LS_KEY_TOOLTIPS = 'tooltips';
|
||||
const LS_KEY_MODULE_RESISTANCES = 'moduleResistances';
|
||||
const LS_KEY_ROLLS = 'matsPerGrade';
|
||||
const LS_KEY_ORBIS = 'orbis';
|
||||
|
||||
let LS;
|
||||
|
||||
@@ -68,7 +70,6 @@ function _delete(key) {
|
||||
* export is an instance (see end of this file).
|
||||
*/
|
||||
export class Persist extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Create an instance
|
||||
*/
|
||||
@@ -86,6 +87,7 @@ export class Persist extends EventEmitter {
|
||||
|
||||
let moduleResistances = _get(LS_KEY_MODULE_RESISTANCES);
|
||||
let matsPerGrade = _get(LS_KEY_ROLLS);
|
||||
let cmdrName = _get(LS_KEY_CMDR_NAME);
|
||||
let tips = _get(LS_KEY_TOOLTIPS);
|
||||
let insurance = _getString(LS_KEY_INSURANCE);
|
||||
let shipDiscount = _get(LS_KEY_SHIP_DISCOUNT);
|
||||
@@ -93,6 +95,7 @@ export class Persist extends EventEmitter {
|
||||
let buildJson = _get(LS_KEY_BUILDS);
|
||||
let comparisonJson = _get(LS_KEY_COMPARISONS);
|
||||
|
||||
this.orbisCreds = _get(LS_KEY_ORBIS) || { email: '', password: '' };
|
||||
this.onStorageChange = this.onStorageChange.bind(this);
|
||||
this.langCode = _getString(LS_KEY_LANG) || 'en';
|
||||
this.insurance = insurance && Insurance[insurance.toLowerCase()] !== undefined ? insurance : 'standard';
|
||||
@@ -111,6 +114,7 @@ export class Persist extends EventEmitter {
|
||||
4: 4,
|
||||
5: 10
|
||||
};
|
||||
this.cmdrName = cmdrName || { selected: '', cmdrs: [] };
|
||||
this.tooltipsEnabled = tips === null ? true : tips;
|
||||
this.moduleResistancesEnabled = moduleResistances === null ? true : moduleResistances;
|
||||
|
||||
@@ -165,6 +169,10 @@ export class Persist extends EventEmitter {
|
||||
this.matsPerGrade = JSON.parse(newValue);
|
||||
this.emit('matsPerGrade', this.matsPerGrade);
|
||||
break;
|
||||
case LS_KEY_ORBIS:
|
||||
this.orbisCreds = JSON.parse(newValue);
|
||||
this.emit('orbis', this.orbisCreds);
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
// On JSON.Parse Error - don't sync or do anything
|
||||
@@ -190,6 +198,24 @@ export class Persist extends EventEmitter {
|
||||
this.emit('language', langCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current orbis.zone credentials
|
||||
* @return {String} language code
|
||||
*/
|
||||
getOrbisCreds() {
|
||||
return this.orbisCreds;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update and save the orbis.zone credentials
|
||||
* @param {Object} creds object with username and password properties.
|
||||
*/
|
||||
setOrbisCreds(creds) {
|
||||
this.langCode = creds;
|
||||
_put(LS_KEY_ORBIS, creds);
|
||||
this.emit('orbis', creds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show tooltips setting
|
||||
* @param {boolean} show Optional - update setting
|
||||
@@ -487,6 +513,14 @@ export class Persist extends EventEmitter {
|
||||
return this.matsPerGrade;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the saved Mats per grade
|
||||
* @return {Object} # of rolls per grade
|
||||
*/
|
||||
getCmdr() {
|
||||
return this.cmdrName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist selected cost tab
|
||||
* @param {number} tabName Cost tab name
|
||||
@@ -496,6 +530,16 @@ export class Persist extends EventEmitter {
|
||||
_put(LS_KEY_COST_TAB, tabName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist cmdr name
|
||||
* @param {Object} cmdrName Commander name for EDEngineer
|
||||
*/
|
||||
setCmdr(cmdrName) {
|
||||
this.cmdrName = cmdrName;
|
||||
_put(LS_KEY_CMDR_NAME, cmdrName);
|
||||
this.emit('cmdr');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the saved discount
|
||||
* @return {number} val Discount value/amount
|
||||
|
||||
@@ -19,7 +19,7 @@ export function specialToolTip(translate, blueprint, grp, m, specialName) {
|
||||
// We also add in any benefits from specials that aren't covered above
|
||||
if (m.blueprint) {
|
||||
for (const feature in Modifications.modifierActions[specialName]) {
|
||||
// if (!blueprint.features[feature] && !m.mods.feature) {
|
||||
// if (!blueprint.features[feature] && !m.mods.feature) {
|
||||
const featureDef = Modifications.modifications[feature];
|
||||
if (featureDef && !featureDef.hidden) {
|
||||
let symbol = '';
|
||||
@@ -37,14 +37,14 @@ export function specialToolTip(translate, blueprint, grp, m, specialName) {
|
||||
const currentIsBeneficial = isValueBeneficial(feature, current);
|
||||
|
||||
effects.push(
|
||||
<tr key={feature + '_specialTT'}>
|
||||
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
|
||||
<td> </td>
|
||||
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'}
|
||||
style={{ textAlign: 'right' }}>{current}{symbol}</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
);
|
||||
<tr key={feature + '_specialTT'}>
|
||||
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
|
||||
<td> </td>
|
||||
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'}
|
||||
style={{ textAlign: 'right' }}>{current}{symbol}</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,7 +54,7 @@ export function specialToolTip(translate, blueprint, grp, m, specialName) {
|
||||
<div>
|
||||
<table width='100%'>
|
||||
<tbody>
|
||||
{effects}
|
||||
{effects}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -215,12 +215,12 @@ export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
|
||||
<div>
|
||||
<table width='100%'>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>{translate('feature')}</td>
|
||||
<td>{translate('worst')}</td>
|
||||
<tr>
|
||||
<td>{translate('feature')}</td>
|
||||
<td>{translate('worst')}</td>
|
||||
{m ? <td>{translate('current')}</td> : null }
|
||||
<td>{translate('best')}</td>
|
||||
</tr>
|
||||
<td>{translate('best')}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{effects}
|
||||
@@ -228,10 +228,10 @@ export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
|
||||
</table>
|
||||
{ components ? <table width='100%'>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>{translate('component')}</td>
|
||||
<td>{translate('amount')}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{translate('component')}</td>
|
||||
<td>{translate('amount')}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{components}
|
||||
@@ -239,9 +239,9 @@ export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
|
||||
</table> : null }
|
||||
{ engineersList ? <table width='100%'>
|
||||
<thead>
|
||||
<tr>
|
||||
<td>{translate('engineers')}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{translate('engineers')}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{engineersList}
|
||||
@@ -294,21 +294,6 @@ export function getBlueprint(name, module) {
|
||||
return {};
|
||||
}
|
||||
const blueprint = JSON.parse(JSON.stringify(found));
|
||||
if (module) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -387,7 +372,9 @@ export function getPercent(m) {
|
||||
|
||||
let value = _getValue(m, featureName);
|
||||
let mult;
|
||||
if (Modifications.modifications[featureName].higherbetter) {
|
||||
if (featureName == 'shieldboost') {
|
||||
mult = ((1 + value) * (1 + m.shieldboost)) - 1 - m.shieldboost;
|
||||
} else if (Modifications.modifications[featureName].higherbetter) {
|
||||
// Higher is better, but is this making it better or worse?
|
||||
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
|
||||
mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100);
|
||||
|
||||
@@ -6,7 +6,7 @@ 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 = {
|
||||
export const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
|
||||
'Adder': 'adder',
|
||||
'Anaconda': 'anaconda',
|
||||
'Asp': 'asp',
|
||||
@@ -29,6 +29,7 @@ const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
|
||||
'FerDeLance': 'fer_de_lance',
|
||||
'Hauler': 'hauler',
|
||||
'Independant_Trader': 'keelback',
|
||||
'Krait_MkII': 'krait_mkii',
|
||||
'Orca': 'orca',
|
||||
'Python': 'python',
|
||||
'SideWinder': 'sidewinder',
|
||||
@@ -37,6 +38,8 @@ const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
|
||||
'Type9': 'type_9_heavy',
|
||||
'Type9_Military': 'type_10_defender',
|
||||
'TypeX': 'alliance_chieftain',
|
||||
'TypeX_2': 'alliance_crusader',
|
||||
'TypeX_3': 'alliance_challenger',
|
||||
'Viper': 'viper',
|
||||
'Viper_MkIV': 'viper_mk_iv',
|
||||
'Vulture': 'vulture'
|
||||
|
||||
@@ -70,7 +70,7 @@ export function shipFromLoadoutJSON(json) {
|
||||
let ship = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots);
|
||||
ship.buildWith(null);
|
||||
// Initial Ship building, don't do engineering yet.
|
||||
let opts = [];
|
||||
let modsToAdd = [];
|
||||
|
||||
for (const module of json.Modules) {
|
||||
switch (module.Slot.toLowerCase()) {
|
||||
@@ -147,86 +147,85 @@ export function shipFromLoadoutJSON(json) {
|
||||
break;
|
||||
default:
|
||||
}
|
||||
for (const module of json.Modules) {
|
||||
if (module.Slot.toLowerCase().search(/hardpoint/) !== -1) {
|
||||
// Add hardpoints
|
||||
let hardpoint;
|
||||
let hardpointClassNum = -1;
|
||||
let hardpointSlotNum = -1;
|
||||
let hardpointArrayNum = 0;
|
||||
for (let i in shipTemplate.slots.hardpoints) {
|
||||
if (shipTemplate.slots.hardpoints[i] === hardpointClassNum) {
|
||||
// Another slot of the same class
|
||||
hardpointSlotNum++;
|
||||
} else {
|
||||
// The first slot of a new class
|
||||
hardpointClassNum = shipTemplate.slots.hardpoints[i];
|
||||
hardpointSlotNum = 1;
|
||||
}
|
||||
|
||||
// Now that we know what we're looking for, find it
|
||||
const hardpointName = HARDPOINT_NUM_TO_CLASS[hardpointClassNum] + 'Hardpoint' + hardpointSlotNum;
|
||||
const hardpointSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === hardpointName.toLowerCase());
|
||||
if (!hardpointSlot) {
|
||||
// This can happen with old imports that don't contain new hardpoints
|
||||
} else if (!hardpointSlot) {
|
||||
// No module
|
||||
} else {
|
||||
hardpoint = _moduleFromFdName(hardpointSlot.Item);
|
||||
ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true);
|
||||
ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On;
|
||||
ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority;
|
||||
opts.push({ coriolisMod: hardpoint, json: hardpointSlot });
|
||||
}
|
||||
hardpointArrayNum++;
|
||||
if (module.Slot.toLowerCase().search(/hardpoint/) !== -1) {
|
||||
// Add hardpoints
|
||||
let hardpoint;
|
||||
let hardpointClassNum = -1;
|
||||
let hardpointSlotNum = -1;
|
||||
let hardpointArrayNum = 0;
|
||||
for (let i in shipTemplate.slots.hardpoints) {
|
||||
if (shipTemplate.slots.hardpoints[i] === hardpointClassNum) {
|
||||
// Another slot of the same class
|
||||
hardpointSlotNum++;
|
||||
} else {
|
||||
// The first slot of a new class
|
||||
hardpointClassNum = shipTemplate.slots.hardpoints[i];
|
||||
hardpointSlotNum = 1;
|
||||
}
|
||||
}
|
||||
if (module.Slot.toLowerCase().search(/slot\d/) !== -1) {
|
||||
let internalSlotNum = 1;
|
||||
let militarySlotNum = 1;
|
||||
for (let i in shipTemplate.slots.internal) {
|
||||
const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name = 'military' : false;
|
||||
|
||||
// The internal slot might be a standard or a military slot. Military slots have a different naming system
|
||||
let internalSlot = null;
|
||||
if (isMilitary) {
|
||||
const internalName = 'Military0' + militarySlotNum;
|
||||
internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
|
||||
militarySlotNum++;
|
||||
} else {
|
||||
// Slot numbers are not contiguous so handle skips.
|
||||
while (internalSlot === null && internalSlotNum < 99) {
|
||||
// Slot sizes have no relationship to the actual size, either, so check all possibilities
|
||||
for (let slotsize = 0; slotsize < 9; slotsize++) {
|
||||
const internalName = 'Slot' + (internalSlotNum <= 9 ? '0' : '') + internalSlotNum + '_Size' + slotsize;
|
||||
if (json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase())) {
|
||||
internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
|
||||
break;
|
||||
}
|
||||
}
|
||||
internalSlotNum++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!internalSlot) {
|
||||
// This can happen with old imports that don't contain new slots
|
||||
} else if (!internalSlot) {
|
||||
// No module
|
||||
} else {
|
||||
const internalJson = internalSlot;
|
||||
const internal = _moduleFromFdName(internalJson.Item);
|
||||
ship.use(ship.internal[i], internal, true);
|
||||
ship.internal[i].enabled = internalJson.On === true;
|
||||
ship.internal[i].priority = internalJson.Priority;
|
||||
opts.push({ coriolisMod: internal, json: internalSlot });
|
||||
}
|
||||
// Now that we know what we're looking for, find it
|
||||
const hardpointName = HARDPOINT_NUM_TO_CLASS[hardpointClassNum] + 'Hardpoint' + hardpointSlotNum;
|
||||
const hardpointSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === hardpointName.toLowerCase());
|
||||
if (!hardpointSlot) {
|
||||
// This can happen with old imports that don't contain new hardpoints
|
||||
} else if (!hardpointSlot) {
|
||||
// No module
|
||||
} else {
|
||||
hardpoint = _moduleFromFdName(hardpointSlot.Item);
|
||||
ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true);
|
||||
ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On;
|
||||
ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority;
|
||||
modsToAdd.push({ coriolisMod: hardpoint, json: hardpointSlot });
|
||||
}
|
||||
hardpointArrayNum++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const i of opts) {
|
||||
if (i.json.Engineering) _addModifications(i.coriolisMod, i.json.Engineering.Modifiers, i.json.Engineering.BlueprintName, i.json.Engineering.Level, i.json.Engineering.ExperimentalEffect);
|
||||
let internalSlotNum = 0;
|
||||
let militarySlotNum = 1;
|
||||
for (let i in shipTemplate.slots.internal) {
|
||||
if (!shipTemplate.slots.internal.hasOwnProperty(i)) {
|
||||
continue;
|
||||
}
|
||||
const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name == 'Military' : false;
|
||||
|
||||
// The internal slot might be a standard or a military slot. Military slots have a different naming system
|
||||
let internalSlot = null;
|
||||
if (isMilitary) {
|
||||
const internalName = 'Military0' + militarySlotNum;
|
||||
internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
|
||||
militarySlotNum++;
|
||||
} else {
|
||||
// Slot numbers are not contiguous so handle skips.
|
||||
for (; internalSlot === null && internalSlotNum < 99; internalSlotNum++) {
|
||||
// Slot sizes have no relationship to the actual size, either, so check all possibilities
|
||||
for (let slotsize = 0; slotsize < 9; slotsize++) {
|
||||
const internalName = 'Slot' + (internalSlotNum <= 9 ? '0' : '') + internalSlotNum + '_Size' + slotsize;
|
||||
if (json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase())) {
|
||||
internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!internalSlot) {
|
||||
// This can happen with old imports that don't contain new slots
|
||||
} else {
|
||||
const internalJson = internalSlot;
|
||||
const internal = _moduleFromFdName(internalJson.Item);
|
||||
ship.use(ship.internal[i], internal, true);
|
||||
ship.internal[i].enabled = internalJson.On === true;
|
||||
ship.internal[i].priority = internalJson.Priority;
|
||||
modsToAdd.push({ coriolisMod: internal, json: internalSlot });
|
||||
}
|
||||
}
|
||||
|
||||
for (const i of modsToAdd) {
|
||||
if (i.json.Engineering) {
|
||||
_addModifications(i.coriolisMod, i.json.Engineering.Modifiers, i.json.Engineering.BlueprintName, i.json.Engineering.Level, i.json.Engineering.ExperimentalEffect);
|
||||
}
|
||||
}
|
||||
// We don't have any information on it so guess it's priority 5 and disabled
|
||||
if (!ship.cargoHatch) {
|
||||
@@ -252,6 +251,16 @@ function _addModifications(module, modifiers, blueprint, grade, specialModificat
|
||||
if (specialModifications) {
|
||||
special = Modifications.specials[specialModifications];
|
||||
}
|
||||
// Add the blueprint definition, grade and special
|
||||
if (blueprint) {
|
||||
module.blueprint = getBlueprint(blueprint, module);
|
||||
if (grade) {
|
||||
module.blueprint.grade = Number(grade);
|
||||
}
|
||||
if (special) {
|
||||
module.blueprint.special = special;
|
||||
}
|
||||
}
|
||||
for (const i in modifiers) {
|
||||
// Some special modifications
|
||||
// Look up the modifiers to find what we need to do
|
||||
@@ -268,24 +277,17 @@ function _addModifications(module, modifiers, blueprint, grade, specialModificat
|
||||
if (modifiers[i].Label.search('Resistance') >= 0) {
|
||||
value = (modifiers[i].Value * 100) - (modifiers[i].OriginalValue * 100);
|
||||
}
|
||||
if (modifiers[i].Label.search('ShieldMultiplier') >= 0 || modifiers[i].Label.search('DefenceModifierHealthMultiplier') >= 0) {
|
||||
value = ((100 + modifiers[i].Value) / (100 + modifiers[i].OriginalValue) * 100 - 100) * 100;
|
||||
}
|
||||
|
||||
// Carry out the required changes
|
||||
for (const action in modifierActions) {
|
||||
if (isNaN(modifierActions[action])) {
|
||||
module.setModValue(action, modifierActions[action]);
|
||||
} else {
|
||||
module.setModValue(action, value);
|
||||
module.setModValue(action, value, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the blueprint definition, grade and special
|
||||
if (blueprint) {
|
||||
module.blueprint = getBlueprint(blueprint, module);
|
||||
if (grade) {
|
||||
module.blueprint.grade = Number(grade);
|
||||
}
|
||||
if (special) {
|
||||
module.blueprint.special = special;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
import request from 'superagent';
|
||||
|
||||
let agent;
|
||||
try {
|
||||
agent = request.agent(); // apparently this crashes somehow
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
if (!agent) {
|
||||
agent = request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shorten a URL
|
||||
* @param {string} url The URL to shorten
|
||||
* @param {function} success Success callback
|
||||
* @param {function} error Failure/Error callback
|
||||
*/
|
||||
*/
|
||||
export default function shorternUrl(url, success, error) {
|
||||
shortenUrlEddp(url, success, error);
|
||||
orbisShorten(url, success, error);
|
||||
}
|
||||
|
||||
const SHORTEN_API_GOOGLE = 'https://www.googleapis.com/urlshortener/v1/url?key=';
|
||||
@@ -64,3 +73,68 @@ function shortenUrlEddp(url, success, error) {
|
||||
error('Not Online');
|
||||
}
|
||||
}
|
||||
|
||||
const SHORTEN_API_ORBIS = 'https://s.orbis.zone/api.php';
|
||||
/**
|
||||
* Shorten a URL using Orbis's URL shortener API
|
||||
* @param {string} url The URL to shorten
|
||||
* @param {function} success Success callback
|
||||
* @param {function} error Failure/Error callback
|
||||
*/
|
||||
function orbisShorten(url, success, error) {
|
||||
if (window.navigator.onLine) {
|
||||
try {
|
||||
request.post(SHORTEN_API_ORBIS)
|
||||
.field('action', 'shorturl')
|
||||
.field('url', url)
|
||||
.field('format', 'json')
|
||||
.end(function(err, response) {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
error('Bad Request');
|
||||
} else {
|
||||
success(response.body.shorturl);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
error(e.message ? e.message : e);
|
||||
}
|
||||
} else {
|
||||
error('Not Online');
|
||||
}
|
||||
}
|
||||
|
||||
const API_ORBIS = 'https://orbis.zone/api/builds/add';
|
||||
/**
|
||||
* Upload to Orbis
|
||||
* @param {object} ship The URL to shorten
|
||||
* @param {object} creds Orbis credentials
|
||||
* @return {Promise<any>} Either a URL or error message.
|
||||
*/
|
||||
export function orbisUpload(ship, creds) {
|
||||
return new Promise(async(resolve, reject) => {
|
||||
if (window.navigator.onLine) {
|
||||
try {
|
||||
agent
|
||||
.post(API_ORBIS)
|
||||
.withCredentials()
|
||||
.redirects(0)
|
||||
.set('Content-Type', 'application/json')
|
||||
.send(ship)
|
||||
.end(function(err, response) {
|
||||
if (err) {
|
||||
reject('Bad Request');
|
||||
} else {
|
||||
resolve(response.body.link);
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
reject(e.message ? e.message : e);
|
||||
}
|
||||
} else {
|
||||
reject('Not Online');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import Module from '../shipyard/Module';
|
||||
import { Infinite } from '../components/SvgIcons';
|
||||
import Persist from '../stores/Persist';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
"density": "4.0"
|
||||
}
|
||||
],
|
||||
"start_url": "https:\/\/edcd.coriolis.io",
|
||||
"start_url": "https:\/\/coriolis.io",
|
||||
"display": "standalone",
|
||||
"orientation": "portrait"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html <%= htmlWebpackPlugin.options.appCache ? 'manifest=/' + htmlWebpackPlugin.options.appCache : '' %> >
|
||||
<html manifest="/">
|
||||
<head>
|
||||
<title>Coriolis EDCD Edition</title>
|
||||
<meta charset="UTF-8">
|
||||
@@ -26,7 +26,8 @@
|
||||
<script>
|
||||
window.CORIOLIS_GAPI_KEY = '<%- htmlWebpackPlugin.options.gapiKey %>';
|
||||
window.CORIOLIS_VERSION = '<%- htmlWebpackPlugin.options.version %>';
|
||||
window.CORIOLIS_DATE = '<%- new Date().toISOString().slice(0, 10) %>';
|
||||
window.CORIOLIS_DATE = '<%- htmlWebpackPlugin.options.date.toISOString().slice(0, 10) %>';
|
||||
window.BUGSNAG_VERSION = '<%- htmlWebpackPlugin.options.version + '-' + htmlWebpackPlugin.options.date.toISOString() %>';
|
||||
</script>
|
||||
<% if (htmlWebpackPlugin.options.uaTracking) { %>
|
||||
<script>
|
||||
@@ -36,7 +37,7 @@
|
||||
</script>
|
||||
<script async src='https://www.google-analytics.com/analytics.js'></script>
|
||||
<% } %>
|
||||
|
||||
|
||||
<!-- Piwik -->
|
||||
<!-- <script type="text/javascript">
|
||||
var _paq = _paq || [];
|
||||
@@ -58,13 +59,12 @@
|
||||
<script src="//d2wy8f7a9ursnm.cloudfront.net/v4/bugsnag.min.js"></script>
|
||||
<script src="//d2wy8f7a9ursnm.cloudfront.net/bugsnag-plugins/v1/bugsnag-react.min.js"></script>
|
||||
<script>
|
||||
window.bugsnagClient = bugsnag('ba9fae819372850fb660755341fa6ef5', {appVersion: window.CORIOLIS_VERSION || undefined})
|
||||
window.bugsnagClient = bugsnag('ba9fae819372850fb660755341fa6ef5', {appVersion: window.BUGSNAG_VERSION || undefined})
|
||||
window.Bugsnag = window.bugsnagClient
|
||||
</script>
|
||||
</head>
|
||||
<body style="background-color:#000;">
|
||||
<section id="coriolis"></section>
|
||||
<script src="<%= htmlWebpackPlugin.files.chunks.lib.entry %>" charset="utf-8" crossorigin="anonymous"></script>
|
||||
<script src="<%= htmlWebpackPlugin.files.chunks.app.entry %>" charset="utf-8" crossorigin="anonymous"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -93,6 +93,9 @@
|
||||
color: @primary-bg;
|
||||
}
|
||||
|
||||
& thead th.bordered {
|
||||
border-left: 1px solid @primary-bg;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,12 @@
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
// A multi-crew pip
|
||||
.mc {
|
||||
stroke: @secondary;
|
||||
fill: @secondary;
|
||||
}
|
||||
|
||||
// A full pip
|
||||
.full {
|
||||
stroke: @primary;
|
||||
|
||||
@@ -22,6 +22,11 @@ select {
|
||||
}
|
||||
}
|
||||
|
||||
.cmdr-select {
|
||||
border: 1px solid @primary;
|
||||
padding: 0.5em 0.5em;
|
||||
}
|
||||
|
||||
.select {
|
||||
color: @primary-disabled;
|
||||
position: absolute;
|
||||
|
||||
@@ -37,6 +37,35 @@
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.modification-container {
|
||||
@input-container-width: 75%;
|
||||
td {
|
||||
width: 100% - @input-container-width;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.input-container {
|
||||
width: @input-container-width;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.unit-container {
|
||||
width: 30px;
|
||||
padding: 3px;
|
||||
text-align: left;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.header-adjuster {
|
||||
width: 100% - @input-container-width;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.cb {
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -45,6 +74,11 @@
|
||||
border-color:#fff;
|
||||
}
|
||||
|
||||
input.greyed-out {
|
||||
border-color: #888;
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.l {
|
||||
text-transform: capitalize;
|
||||
margin-right: 0.8em;
|
||||
|
||||
@@ -227,11 +227,6 @@
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"fullTankRange": {
|
||||
"description": "Single Jump range with a full tank (unladenMass + fuel)",
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
},
|
||||
"ladenMass": {
|
||||
"description": "Mass of the Ship + fuel + cargo (hull + all components + fuel tank + cargo capacity)",
|
||||
"type": "number",
|
||||
|
||||
@@ -254,11 +254,6 @@
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"fullTankRange": {
|
||||
"description": "Single Jump range with a full tank (unladenMass + fuel)",
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
},
|
||||
"ladenMass": {
|
||||
"description": "Mass of the Ship + fuel + cargo (hull + all components + fuel tank + cargo capacity)",
|
||||
"type": "number",
|
||||
|
||||
@@ -258,11 +258,6 @@
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"fullTankRange": {
|
||||
"description": "Single Jump range with a full tank (unladenMass + fuel)",
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
},
|
||||
"ladenMass": {
|
||||
"description": "Mass of the Ship + fuel + cargo (hull + all components + fuel tank + cargo capacity)",
|
||||
"type": "number",
|
||||
|
||||
@@ -300,11 +300,6 @@
|
||||
"type": "integer",
|
||||
"minimum": 1
|
||||
},
|
||||
"fullTankRange": {
|
||||
"description": "Single Jump range with a full tank (unladenMass + fuel)",
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
},
|
||||
"ladenMass": {
|
||||
"description": "Mass of the Ship + fuel + cargo (hull + all components + fuel tank + cargo capacity)",
|
||||
"type": "number",
|
||||
|
||||
80
src/sw.js
Normal file
80
src/sw.js
Normal file
@@ -0,0 +1,80 @@
|
||||
console.log('Hello from sw.js');
|
||||
|
||||
if (workbox) {
|
||||
console.log('Yay! Workbox is loaded 🎉');
|
||||
workbox.routing.registerRoute(
|
||||
new RegExp('https://fonts.(?:googleapis|gstatic).com/(.*)'),
|
||||
workbox.strategies.cacheFirst({
|
||||
cacheName: 'google-fonts',
|
||||
plugins: [
|
||||
new workbox.expiration.Plugin({
|
||||
maxEntries: 30
|
||||
}),
|
||||
new workbox.cacheableResponse.Plugin({
|
||||
statuses: [0, 200]
|
||||
})
|
||||
]
|
||||
})
|
||||
);
|
||||
workbox.routing.registerRoute(
|
||||
/\.(?:png|gif|jpg|jpeg|svg)$/,
|
||||
workbox.strategies.cacheFirst({
|
||||
cacheName: 'images',
|
||||
plugins: [
|
||||
new workbox.expiration.Plugin({
|
||||
maxEntries: 60,
|
||||
maxAgeSeconds: 30 * 24 * 60 * 60 // 30 Days
|
||||
})
|
||||
]
|
||||
})
|
||||
);
|
||||
workbox.routing.registerRoute(
|
||||
/\.(?:js|css)$/,
|
||||
workbox.strategies.staleWhileRevalidate({
|
||||
cacheName: 'static-resources'
|
||||
})
|
||||
);
|
||||
try {
|
||||
workbox.googleAnalytics.initialize();
|
||||
} catch (e) {
|
||||
console.log('Probably an ad-blocker');
|
||||
}
|
||||
} else {
|
||||
console.log('Boo! Workbox didn\'t load 😬');
|
||||
}
|
||||
|
||||
|
||||
self.addEventListener('message', event => {
|
||||
if (!event.data) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.data) {
|
||||
case 'skipWaiting':
|
||||
self.skipWaiting();
|
||||
break;
|
||||
default:
|
||||
// NOOP
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', function(event) {
|
||||
console.log('Handling fetch event for', event.request.url);
|
||||
|
||||
event.respondWith(
|
||||
caches.match(event.request).then(function(response) {
|
||||
if (response) {
|
||||
return response;
|
||||
}
|
||||
|
||||
return fetch(event.request)
|
||||
.then(function(response) {
|
||||
return response;
|
||||
})
|
||||
.catch(function(error) {
|
||||
return caches.match(OFFLINE_URL);
|
||||
});
|
||||
})
|
||||
);
|
||||
});
|
||||
@@ -1,51 +1,47 @@
|
||||
const path = require('path')
|
||||
const exec = require('child_process').exec
|
||||
const webpack = require('webpack')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
const WebpackNotifierPlugin = require('webpack-notifier')
|
||||
const pkgJson = require('./package')
|
||||
|
||||
function CopyDirPlugin(source, destination) {
|
||||
this.source = source
|
||||
this.destination = destination
|
||||
}
|
||||
|
||||
CopyDirPlugin.prototype.apply = function (compiler) {
|
||||
compiler.plugin('done', () => {
|
||||
console.log(compiler.outputPath, this.destination)
|
||||
exec('cp -r ' + this.source + ' ' + path.join(compiler.outputPath, this.destination))
|
||||
})
|
||||
}
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
const WebpackNotifierPlugin = require('webpack-notifier');
|
||||
const pkgJson = require('./package');
|
||||
const buildDate = new Date();
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
devtool: 'source-map',
|
||||
devServer: {
|
||||
headers: {'Access-Control-Allow-Origin': '*'}
|
||||
headers: { 'Access-Control-Allow-Origin': '*' }
|
||||
},
|
||||
mode: 'development',
|
||||
entry: {
|
||||
app: ['webpack-dev-server/client?http://0.0.0.0:3300', 'webpack/hot/only-dev-server', path.join(__dirname, 'src/app/index.js')],
|
||||
lib: ['d3', 'react', 'react-dom', 'classnames', 'fbemitter', 'lz-string']
|
||||
main: './src/app/index.js'
|
||||
},
|
||||
resolve: {
|
||||
// When requiring, you don't need to add these extensions
|
||||
extensions: ['.js', '.jsx', '.json', '.less']
|
||||
},
|
||||
optimization: {
|
||||
minimize: false,
|
||||
splitChunks: {
|
||||
chunks: 'all'
|
||||
}
|
||||
},
|
||||
output: {
|
||||
path: path.join(__dirname, 'build'),
|
||||
filename: 'app.js',
|
||||
publicPath: '/'
|
||||
},
|
||||
plugins: [
|
||||
new CopyDirPlugin(path.join(__dirname, 'src/.htaccess'), ''),
|
||||
new webpack.optimize.CommonsChunkPlugin({
|
||||
name: 'lib',
|
||||
filename: 'lib.js'
|
||||
}),
|
||||
new CopyWebpackPlugin(['src/.htaccess']),
|
||||
// new webpack.optimize.CommonsChunkPlugin({
|
||||
// name: 'lib',
|
||||
// filename: 'lib.js'
|
||||
// }),
|
||||
new HtmlWebpackPlugin({
|
||||
inject: false,
|
||||
inject: true,
|
||||
template: path.join(__dirname, 'src/index.ejs'),
|
||||
version: pkgJson.version,
|
||||
date: buildDate,
|
||||
gapiKey: process.env.CORIOLIS_GAPI_KEY || ''
|
||||
}),
|
||||
new ExtractTextPlugin({
|
||||
@@ -53,20 +49,20 @@ module.exports = {
|
||||
disable: false,
|
||||
allChunks: true
|
||||
}),
|
||||
new WebpackNotifierPlugin({alwaysNotify: true}),
|
||||
new WebpackNotifierPlugin({ alwaysNotify: true }),
|
||||
new webpack.HotModuleReplacementPlugin(),
|
||||
new webpack.NoEmitOnErrorsPlugin()
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{test: /\.css$/, loader: ExtractTextPlugin.extract({fallback: 'style-loader', use: 'css-loader'})},
|
||||
{test: /\.less$/, loader: ExtractTextPlugin.extract({fallback: 'style-loader', use: 'css-loader!less-loader'})},
|
||||
{test: /\.(js|jsx)$/, loaders: ['babel-loader'], include: path.join(__dirname, 'src')},
|
||||
{test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff'},
|
||||
{test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff'},
|
||||
{test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream'},
|
||||
{test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader'},
|
||||
{test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=image/svg+xml'}
|
||||
{ test: /\.css$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader' }) },
|
||||
{ test: /\.less$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader!less-loader' }) },
|
||||
{ test: /\.(js|jsx)$/, loaders: ['babel-loader'], include: path.join(__dirname, 'src') },
|
||||
{ test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' },
|
||||
{ test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' },
|
||||
{ test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream' },
|
||||
{ test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader' },
|
||||
{ test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=image/svg+xml' }
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,96 +1,73 @@
|
||||
const path = require('path')
|
||||
const exec = require('child_process').exec
|
||||
const webpack = require('webpack')
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin')
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin')
|
||||
const AppCachePlugin = require('appcache-webpack-plugin')
|
||||
const {BugsnagSourceMapUploaderPlugin} = require('webpack-bugsnag-plugins')
|
||||
const pkgJson = require('./package')
|
||||
|
||||
function CopyDirPlugin (source, destination) {
|
||||
this.source = source
|
||||
this.destination = destination
|
||||
}
|
||||
|
||||
CopyDirPlugin.prototype.apply = function (compiler) {
|
||||
compiler.plugin('done', () => {
|
||||
console.log(compiler.outputPath, this.destination)
|
||||
exec('cp -r ' + this.source + ' ' + path.join(compiler.outputPath, this.destination))
|
||||
})
|
||||
}
|
||||
const path = require('path');
|
||||
const webpack = require('webpack');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin');
|
||||
const { InjectManifest } = require('workbox-webpack-plugin');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const { BugsnagSourceMapUploaderPlugin } = require('webpack-bugsnag-plugins');
|
||||
const pkgJson = require('./package');
|
||||
const buildDate = new Date();
|
||||
|
||||
module.exports = {
|
||||
cache: true,
|
||||
devtool: 'source-map',
|
||||
entry: {
|
||||
app: ['babel-polyfill', path.resolve(__dirname, 'src/app/index')],
|
||||
lib: ['d3', 'react', 'react-dom', 'classnames', 'fbemitter', 'lz-string']
|
||||
main: ['./src/app/index.js']
|
||||
},
|
||||
resolve: {
|
||||
extensions: ['.js', '.jsx', '.json', '.less']
|
||||
},
|
||||
output: {
|
||||
path: path.join(__dirname, 'build'),
|
||||
filename: '[name].[chunkhash:6].js',
|
||||
chunkFilename: '[name].[chunkhash:6]',
|
||||
publicPath: '/'
|
||||
filename: '[name].[hash].js',
|
||||
publicPath: '/',
|
||||
globalObject: 'this'
|
||||
},
|
||||
mode: 'production',
|
||||
optimization: {
|
||||
minimize: true,
|
||||
splitChunks: {
|
||||
chunks: 'all'
|
||||
}
|
||||
},
|
||||
plugins: [
|
||||
new webpack.optimize.UglifyJsPlugin({
|
||||
'screw-ie8': true,
|
||||
sourceMap: true
|
||||
}),
|
||||
//new webpack.optimize.CommonsChunkPlugin({
|
||||
new CopyWebpackPlugin(['src/.htaccess', { from: 'src/schemas', to: 'schemas' }, {from: 'src/images/logo/*', flatten: true, to: ''}]),
|
||||
// new webpack.optimize.CommonsChunkPlugin({
|
||||
// name: 'lib',
|
||||
// filename: 'lib.[chunkhash:6].js'
|
||||
//}),
|
||||
// }),
|
||||
new HtmlWebpackPlugin({
|
||||
inject: false,
|
||||
appCache: 'coriolis.appcache',
|
||||
minify: {
|
||||
collapseBooleanAttributes: true,
|
||||
collapseWhitespace: true,
|
||||
removeAttributeQuotes: true,
|
||||
removeComments: true,
|
||||
removeEmptyAttributes: true,
|
||||
removeRedundantAttributes: true,
|
||||
removeScriptTypeAttributes: true,
|
||||
removeStyleLinkTypeAttributes: true
|
||||
},
|
||||
inject: true,
|
||||
template: path.join(__dirname, 'src/index.ejs'),
|
||||
uaTracking: process.env.CORIOLIS_UA_TRACKING || '',
|
||||
gapiKey: process.env.CORIOLIS_GAPI_KEY || '',
|
||||
date: buildDate,
|
||||
version: pkgJson.version
|
||||
}),
|
||||
new ExtractTextPlugin({
|
||||
filename: '[contenthash:6].css',
|
||||
filename: '[hash:6].css',
|
||||
disable: false,
|
||||
allChunks: true
|
||||
}),
|
||||
new BugsnagSourceMapUploaderPlugin({
|
||||
apiKey: 'ba9fae819372850fb660755341fa6ef5',
|
||||
appVersion: pkgJson.version
|
||||
// new BugsnagSourceMapUploaderPlugin({
|
||||
// apiKey: 'ba9fae819372850fb660755341fa6ef5',
|
||||
// appVersion: `${pkgJson.version}-${buildDate.toISOString()}`
|
||||
// }),
|
||||
new InjectManifest({
|
||||
swSrc: './src/sw.js',
|
||||
importWorkboxFrom: 'cdn',
|
||||
swDest: 'service-worker.js'
|
||||
}),
|
||||
new CopyDirPlugin(path.join(__dirname, 'src/schemas'), 'schemas'),
|
||||
new CopyDirPlugin(path.join(__dirname, 'src/images/logo/*'), ''),
|
||||
new CopyDirPlugin(path.join(__dirname, 'src/.htaccess'), ''),
|
||||
new AppCachePlugin({
|
||||
network: ['*'],
|
||||
settings: ['prefer-online'],
|
||||
exclude: ['index.html', /.*\.map$/],
|
||||
output: 'coriolis.appcache'
|
||||
})
|
||||
],
|
||||
module: {
|
||||
rules: [
|
||||
{test: /\.css$/, loader: ExtractTextPlugin.extract({fallback: 'style-loader', use: 'css-loader'})},
|
||||
{test: /\.less$/, loader: ExtractTextPlugin.extract({fallback: 'style-loader', use: 'css-loader!less-loader'})},
|
||||
{test: /\.(js|jsx)$/, loader: 'babel-loader?cacheDirectory=true', include: path.join(__dirname, 'src')},
|
||||
{test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff'},
|
||||
{test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff'},
|
||||
{test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream'},
|
||||
{test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader'},
|
||||
{test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=image/svg+xml'}
|
||||
{ test: /\.css$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader' }) },
|
||||
{ test: /\.less$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader!less-loader' }) },
|
||||
{ test: /\.(js|jsx)$/, loader: 'babel-loader?cacheDirectory=true', include: path.join(__dirname, 'src') },
|
||||
{ test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' },
|
||||
{ test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' },
|
||||
{ test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream' },
|
||||
{ test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader' },
|
||||
{ test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=image/svg+xml' }
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user