Compare commits

...

94 Commits

Author SHA1 Message Date
William Blythe
9abb4f6d05 Merge branch 'develop' into dw2 2019-01-09 09:24:29 +11:00
Willyb321
4b854b8305 Search tweaks 2019-01-04 09:28:21 +11:00
William Blythe
b400db8216 only cache if not 404 2019-01-04 08:01:02 +11:00
Willyb321
deeb525433 Remove dynamic import for pages
Closes #441
2019-01-03 08:54:22 +11:00
Willyb321
a181791500 We don't run ads now. 2019-01-03 08:45:20 +11:00
William Blythe
48092d4395 trycatch announcements check 2019-01-02 12:13:27 +11:00
William Blythe
593f069806 Fix title being page not found even after page has loaded 2018-12-31 08:18:36 +11:00
William Blythe
a073692632 add probe radius 2018-12-27 08:18:38 +11:00
William Blythe
7752d5c9db Fix #445 2018-12-26 09:23:44 +11:00
William Blythe
98782da200 make shipyardpage not dynamic import 2018-12-26 09:07:05 +11:00
William Blythe
2936364934 prep for abl 2018-12-26 08:50:00 +11:00
William Blythe
f85a03a9ae remove christmas theme
its 8:30am in sydney ok
2018-12-26 08:29:45 +11:00
William Blythe
2703c2aa23 move mining laser to new mining group 2018-12-25 09:35:01 +11:00
William Blythe
954921c231 probably fix scrolling 2018-12-25 09:07:39 +11:00
William Blythe
ee3c50e27d add christmas snow
Credit: https://codepen.io/keithclark/pen/yBcsr
2018-12-25 08:53:05 +11:00
Willyb321
03020743b3 some docker adjustments, fix brotli comprpession 2018-12-23 07:51:42 +11:00
William Blythe
001fed67b7 change order of compression 2018-12-23 06:51:50 +11:00
William Blythe
3894915740 fix nginx conf 2018-12-23 06:39:32 +11:00
William Blythe
68fd13e8dc /shrgu 2018-12-22 08:25:53 +11:00
William Blythe
fdf16cd959 maybe 2018-12-22 08:25:10 +11:00
William Blythe
d916c67fe0 f 2018-12-22 08:13:06 +11:00
William Blythe
d8a8e224f4 enable brotli 2018-12-22 08:07:20 +11:00
William Blythe
e1c115747c nginx compression 2018-12-22 08:03:35 +11:00
William Blythe
e9b6d71606 dynamically import pages, should make things a bit faster 2018-12-22 07:48:35 +11:00
William Blythe
e03e249d2f probably fix .dockerignore 2018-12-22 07:19:53 +11:00
monopoint
0cfb0b6878 Fix background highlight on sort in shipyard (#447)
* Fix background highlight on sort in shipyard

* removed docs line
2018-12-21 12:14:43 -08:00
William Blythe
600df162aa Prep for sub surface displacement missile 2018-12-22 06:45:52 +11:00
Willyb321
94141aa3c5 mining tools in their own section, prep for scl 2018-12-19 08:21:13 +11:00
Willyb321
aca90d7077 prep for pulse wave analysers 2018-12-18 08:25:30 +11:00
felixlinker
194db07057 Hotfix pip parsing in control code 2018-12-05 20:31:46 +00:00
willyb321
38038fcc32 fix a bug 2018-12-05 07:34:02 +11:00
William Blythe
9fa740f54e more work 2018-12-04 12:01:08 +11:00
William Blythe
d37d69c3a7 add some more guards on ships 2018-11-30 10:59:32 +11:00
William Blythe
78d8779641 add some guards on ships 2018-11-30 10:55:05 +11:00
Willyb321
352023f0fb Start work on roles 2018-11-30 09:08:26 +11:00
willyb321
3ea194d43e Merge branch 'develop' into dw2 2018-11-30 06:56:16 +11:00
willyb321
bbba048129 update to bugsnag 5 2018-11-30 06:56:01 +11:00
willyb321
c6269192f0 mor edw2 2018-11-30 06:52:47 +11:00
willyb321
841e6c3348 More dw2 work, update roles etc 2018-11-30 06:37:09 +11:00
willyb321
0febc581d1 Merge branch 'develop' into dw2 2018-11-30 06:25:41 +11:00
Willyb321
dc6e398526 fighter hangar 2018-11-30 06:23:41 +11:00
Willyb321
cb24c1fc69 more dw2 work 2018-11-30 06:16:21 +11:00
William Blythe
90ad9de831 more dw2 2018-11-28 14:23:01 +11:00
William Blythe
9360b1d574 more dw2 work 2018-11-28 11:31:09 +11:00
William Blythe
08d2573d1f internals 2018-11-28 09:42:21 +11:00
Willyb321
427b9af7de up to shields 2018-11-28 08:25:05 +11:00
willyb321
222173b388 add category select to orbis modal 2018-11-25 07:54:16 +11:00
willyb321
ec0d05e081 add beta resting heat 2018-11-22 07:31:45 +11:00
Willyb321
b3be0bd639 remove logs 2018-11-21 09:00:50 +11:00
Willyb321
529d80682c Merge branch 'develop' of github.com:edcd/coriolis into develop 2018-11-21 08:51:32 +11:00
Willyb321
934de01803 add announcements to actual page 2018-11-21 08:50:53 +11:00
William
3367580d78 Apply diminishing returns for hull resistance modding to all modules (#425)
References #396
2018-11-21 08:38:46 +11:00
felixlinker
fbf59219d0 Apply diminishing returns for hull resistance modding to all modules 2018-11-20 20:57:39 +00:00
William Blythe
318d06d9f9 dw2 build work 2018-11-20 12:18:16 +11:00
William Blythe
1f3c3726f2 Merge branch 'dw2' of github.com:EDCD/coriolis into dw2 2018-11-20 09:29:08 +11:00
William
71beda3a6c Update docker-compose.yml 2018-11-20 08:36:16 +11:00
William
1d6644b531 Update docker-compose.yml 2018-11-20 08:34:53 +11:00
William Blythe
77401a3b3f get ready for dw2 site 2018-11-19 09:21:18 +11:00
willyb321
7c587c29aa add build reporter plugin 2018-11-19 07:12:36 +11:00
willyb321
2295dccd82 re-enable bugsnag 2018-11-19 07:10:05 +11:00
willyb321
cc4ad6d132 and a bit moree 2018-11-19 06:58:53 +11:00
willyb321
8a2d27290a add some logging to assist DWE2 people (temp) 2018-11-19 06:58:12 +11:00
willyb321
eda61a8e06 in theory fix guardian pds
Closes #339
2018-11-17 09:26:57 +11:00
William Blythe
71b90eb6f4 maybe an actually good service worker for once 2018-11-13 09:11:43 +11:00
willyb321
4e891f382c prep for import 2018-11-13 06:25:17 +11:00
willyb321
3d3f9e44b5 add damage per shot 2018-11-11 01:57:06 +11:00
willyb321
dd7a133caa add search bar initial
why am i awake its like 2am
2018-11-11 01:31:30 +11:00
willyb321
ae247c4812 support beta flag 2018-11-10 09:39:22 +11:00
William Blythe
6c932f96a6 add fill weapons for AX weps 2018-11-09 11:28:37 +11:00
William Blythe
0ea25692d3 work on sw 2018-11-07 13:09:04 +11:00
William Blythe
8e5dd9fb8d add iframe to allow posting builds 2018-11-05 11:51:50 +11:00
William Blythe
97ce2828e0 mamba prep 2018-11-02 09:57:48 +11:00
William Blythe
6e79ced51e remove halloween 2018-11-01 07:28:54 +11:00
William Blythe
0b10cac85c halloween 2018-10-31 09:20:05 +11:00
Stratos Gerakakis
34c04a6354 Fixed Typo on "Armour Tactics" (#414)
* fix sw not registered

* Fix/no adsense (#412)

* Fixed Type on "Armour Tactics"
2018-10-29 09:14:32 +11:00
William Blythe
4e337c4ca1 add pax tooltip to outfitting too 2018-10-26 09:28:33 +11:00
William Blythe
5048b7e094 add pax tooltip 2018-10-26 09:05:33 +11:00
William Blythe
27fbc1ad66 remove file 2018-10-25 10:36:35 +11:00
William
dfffc3a268 Fix/no adsense (#412)
* No adsense

* remove script tag
2018-10-24 09:06:24 +11:00
William
b59fa15e00 Merge pull request #411 from EDCD/fix/chart-labels
Fix VerticalBarchart labels
2018-10-24 07:52:58 +11:00
felixlinker
12bca4c44e Fix VerticalBarchart labels 2018-10-23 21:48:07 +01:00
William Blythe
7d99471f89 sw 2018-10-23 12:41:56 +11:00
William Blythe
a2ab708ac9 format 2018-10-23 12:14:28 +11:00
William Blythe
a34a9c355f fix sw not registered 2018-10-23 11:57:07 +11:00
William Blythe
557c0afd9b Merge branch 'master' into develop 2018-10-23 11:50:58 +11:00
William Blythe
d52365a204 /shrug 2018-10-23 09:55:42 +11:00
felixlinker
14b2a14e58 Merge branch 'master' into develop 2018-10-22 23:52:09 +01:00
William Blythe
7f24904f77 various bits 2018-10-23 09:46:50 +11:00
William Blythe
da07790594 various bits 2018-10-23 09:40:13 +11:00
William
5008c7cd74 Update sw.js 2018-10-23 08:15:54 +11:00
willyb321
a778b1b6e1 update 2018-10-22 17:15:09 +11:00
William Blythe
bd9771f9ba test run 2018-10-22 11:49:15 +11:00
William Blythe
600c244f9b test run 2018-10-22 11:44:14 +11:00
willyb321
3e0a5e22b1 Merge branch 'develop' 2018-10-21 10:21:04 +11:00
50 changed files with 8986 additions and 1156 deletions

View File

@@ -1,2 +0,0 @@
node_modules
npm-debug.log

View File

@@ -1,45 +0,0 @@
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;
}
}
}

77
.dockerignore Normal file
View File

@@ -0,0 +1,77 @@
node_modules
npm-debug.log
### Node template
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless

View File

@@ -35,7 +35,7 @@
"title": "Coriolis",
"description": "Coriolis Shipyard for Elite Dangerous",
"repository": "https://github.com/EDCD/coriolis",
"site": "https://coriolis.edcd.io",
"site": "https://coriolis.io",
"author": "https://github.com/edcd",
"image": "./src/images/logo/192x192.png"
}
@@ -81,7 +81,7 @@
"title": "Coriolis",
"description": "Coriolis Shipyard for Elite Dangerous",
"repository": "https://github.com/EDCD/coriolis",
"site": "https://coriolis.edcd.io",
"site": "https://coriolis.io",
"author": "https://github.com/edcd",
"image": "./src/images/logo/192x192.png"
}
@@ -100,4 +100,4 @@
}
}
]
}
}

View File

@@ -6,29 +6,28 @@ 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 add --update git
RUN apk update
RUN apk add git
COPY . /src/app/coriolis
RUN npm i -g npm
# Set up coriolis-data
WORKDIR /src/app/coriolis-data
RUN git fetch --all
RUN git clone https://github.com/EDCD/coriolis-data.git .
RUN git checkout ${BRANCH}
RUN npm install --no-package-lock
RUN npm start
# Set up coriolis
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
FROM fholzer/nginx-brotli as web
COPY nginx.conf /etc/nginx/nginx.conf
COPY --from=builder /src/app/coriolis/build /usr/share/nginx/html
WORKDIR /usr/share/nginx/html
EXPOSE 80

6089
d3.js vendored Normal file

File diff suppressed because it is too large Load Diff

1
d3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -29,6 +29,23 @@ services:
- "traefik.basic.port=80"
- "traefik.basic.protocol=http"
coriolis_dw2:
image: edcd/coriolis:dw2
restart: always
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
networks:
- web
labels:
- "traefik.docker.network=web"
- "traefik.enable=true"
- "traefik.basic.frontend.rule=Host:dw2.coriolis.io"
- "traefik.basic.port=80"
- "traefik.basic.protocol=http"
networks:
web:
external: true

View File

@@ -1,59 +1,96 @@
worker_processes 2;
error_log ./nginx.error.log;
worker_rlimit_nofile 8192;
pid nginx.pid;
worker_processes 1;
user nobody nobody;
error_log /tmp/error.log;
pid /tmp/nginx.pid;
events {
worker_connections 1024;
multi_accept on;
worker_connections 1024;
}
http {
access_log off;
charset UTF-8;
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;
types {
text/html html htm shtml;
text/css css;
text/xml xml rss;
image/gif gif;
image/jpeg jpeg jpg;
application/x-javascript js;
text/plain txt;
image/png png;
image/svg+xml svg;
image/x-icon ico;
application/pdf pdf;
text/cache-manifest appcache;
}
# https://nginx.org/en/docs/http/ngx_http_gzip_module.html
# Enable gzip compression.
# Default: off
gzip off;
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_buffers 16 8k;
gzip_http_version 1.1;
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
# Compression level (1-9).
# 5 is a perfect compromise between size and CPU usage, offering about
# 75% reduction for most ASCII files (almost identical to level 9).
# Default: 1
gzip_comp_level 5;
server {
listen 3301;
server_name localhost;
root ./build/;
index index.html;
# Don't compress anything that's already small and unlikely to shrink much
# if at all (the default is 20 bytes, which is bad as that usually leads to
# larger files after gzipping).
# Default: 20
gzip_min_length 256;
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 *;
# Compress data even for clients that are connecting to us via proxies,
# identified by the "Via" header (required for CloudFront).
# Default: off
gzip_proxied any;
# Tell proxies to cache both the gzipped and regular version of a resource
# whenever the client's Accept-Encoding capabilities header varies;
# Avoids the issue where a non-gzip capable client (which is extremely rare
# today) would display gibberish if their proxy gave them the gzipped version.
# Default: off
gzip_vary on;
# Compress all output labeled with one of the following MIME-types.
# text/html is always compressed by gzip module.
# Default: text/html
gzip_types *;
brotli on;
# brotli_static on;
brotli_types *;
# This should be turned on if you are going to have pre-compressed copies (.gz) of
# static files available. If not it should be left off as it will cause extra I/O
# for the check. It is best if you enable this in a location{} block for
# a specific directory, or on an individual server{} level.
# gzip_static on;
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)$ {
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 /service-worker.js {
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;
}
}
access_log off;
}
location / {
try_files $uri $uri/ /index.html =404;
}
location /iframe.html {
try_files $uri $uri/ /iframe.html =404;
}
}
}

View File

@@ -5,7 +5,7 @@
"type": "git",
"url": "https://github.com/EDCD/coriolis"
},
"homepage": "https://coriolis.edcd.io",
"homepage": "https://coriolis.io",
"bugs": "https://github.com/EDCD/coriolis/issues",
"private": true,
"engine": "node >= 4.8.1",
@@ -120,6 +120,7 @@
"webpack-notifier": "^1.6.0",
"workbox-webpack-plugin": "^3.6.1"
},
"sideEffects": false,
"dependencies": {
"@babel/polyfill": "^7.0.0",
"browserify-zlib-next": "^1.0.1",
@@ -135,6 +136,7 @@
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react-extras": "^0.7.1",
"react-fuzzy": "^0.5.2",
"react-ga": "^2.5.3",
"react-number-editor": "Athanasius/react-number-editor.git#miggy",
"recharts": "^1.2.0",

View File

@@ -1,11 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
import Router from './Router';
import { register } from 'register-service-worker'
import { register } from 'register-service-worker';
import { EventEmitter } from 'fbemitter';
import { getLanguage } from './i18n/Language';
import Persist from './stores/Persist';
import Announcement from './components/Announcement';
import Header from './components/Header';
import Tooltip from './components/Tooltip';
import ModalExport from './components/ModalExport';
@@ -14,7 +15,6 @@ import ModalImport from './components/ModalImport';
import ModalPermalink from './components/ModalPermalink';
import * as CompanionApiUtils from './utils/CompanionApiUtils';
import * as JournalUtils from './utils/JournalUtils';
import AboutPage from './pages/AboutPage';
import NotFoundPage from './pages/NotFoundPage';
import OutfittingPage from './pages/OutfittingPage';
@@ -72,7 +72,7 @@ export default class Coriolis extends React.Component {
route: {},
sizeRatio: Persist.getSizeRatio()
};
this._getAnnouncements()
this._getAnnouncements();
Router('', (r) => this._setPage(ShipyardPage, r));
Router('/import?', (r) => this._importBuild(r));
Router('/import/:data', (r) => this._importBuild(r));
@@ -105,18 +105,20 @@ export default class Coriolis extends React.Component {
}
r.params.ship = ship.id;
r.params.code = ship.toString();
this._setPage(OutfittingPage, r);
this._setPage(OutfittingPage, r)
} catch (err) {
this._onError('Failed to import ship', r.path, 0, 0, err);
}
}
_getAnnouncements() {
return request.get('https://orbis.zone/api/announcement')
.query({showInCoriolis: true})
.then(announces => {
this.setState({ announcements: announces.body })
})
async _getAnnouncements() {
try {
const announces = await request.get('https://orbis.zone/api/announcement')
.query({ showInCoriolis: true });
this.setState({ announcements: announces.body });
} catch (err) {
console.error(err)
}
}
/**
@@ -344,37 +346,37 @@ export default class Coriolis extends React.Component {
});
}
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;
// 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;
if (process.env.NODE_ENV === 'production') {
register('/service-worker.js', {
ready (registration) {
console.log('Service worker is active.')
ready(registration) {
console.log('Service worker is active.');
},
registered (registration) {
console.log('Service worker has been registered.')
registered(registration) {
console.log('Service worker has been registered.');
},
cached (registration) {
console.log('Content has been cached for offline use.')
cached(registration) {
console.log('Content has been cached for offline use.');
},
updatefound (registration) {
console.log('New content is downloading.')
updatefound(registration) {
console.log('New content is downloading.');
},
updated (registration) {
updated(registration) {
self.setState({ appCacheUpdate: true });
console.log('New content is available; please refresh.')
console.log('New content is available; please refresh.');
},
offline () {
console.log('No internet connection found. App is running in offline mode.')
offline() {
console.log('No internet connection found. App is running in offline mode.');
},
error (error) {
console.error('Error during service worker registration:', error)
error(error) {
console.error('Error during service worker registration:', error);
}
});
});
}
}
window.onerror = this._onError.bind(this);
window.addEventListener('resize', () => this.emitter.emit('windowResize'));
@@ -394,20 +396,24 @@ export default class Coriolis extends React.Component {
let currentMenu = this.state.currentMenu;
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}/>
className={this.state.noTouch ? 'no-touch' : null}>
<Header announcements={this.state.announcements} appCacheUpdate={this.state.appCacheUpdate}
currentMenu={currentMenu}/>
<div className="announcement-container">{this.state.announcements.map(a => <Announcement
text={a.message}/>)}</div>
{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" rel="noopener noreferrer"
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
target="_blank" rel="noopener noreferrer" title={'Coriolis Commits since' + window.CORIOLIS_DATE}>Commits
since last release
({window.CORIOLIS_DATE})</a>
</div>
</footer>

View File

@@ -1,5 +1,6 @@
import Persist from './stores/Persist';
import ReactGA from 'react-ga';
ReactGA.initialize('UA-55840909-18');
let standalone = undefined;

View File

@@ -25,7 +25,7 @@ export default class Announcement extends React.Component {
* @return {React.Component} A href element
*/
render() {
return <p>{this.props.text}</p>;
return <div className="announcement" >{this.props.text}</div>;
}
}

View File

@@ -5,6 +5,7 @@ import TranslatedComponent from './TranslatedComponent';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
import cn from 'classnames';
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
import FuzzySearch from 'react-fuzzy';
const PRESS_THRESHOLD = 500; // mouse/touch down threshold
@@ -39,7 +40,7 @@ const GRPCAT = {
'mc': 'projectiles',
'axmc': 'experimental',
'fc': 'projectiles',
'rfl': 'experimental',
'rfl': 'experimental',
'pa': 'projectiles',
'rg': 'projectiles',
'mr': 'ordnance',
@@ -72,7 +73,12 @@ const GRPCAT = {
'gfsb': 'guardian',
'gmrp': 'guardian',
'gsc': 'guardian',
'ghrp': 'guardian'
'ghrp': 'guardian',
// Mining
'scl': 'mining',
'pwa': 'mining',
'sdm': 'mining'
};
// Order here is the order in which items will be shown in the modules menu
const CATEGORIES = {
@@ -89,7 +95,7 @@ const CATEGORIES = {
'structural reinforcement': ['hr', 'mrp'],
'dc': ['dc'],
// Hardpoints
'lasers': ['pl', 'ul', 'bl', 'ml'],
'lasers': ['pl', 'ul', 'bl'],
'projectiles': ['mc', 'c', 'fc', 'pa', 'rg'],
'ordnance': ['mr', 'tp', 'nl'],
// Utilities
@@ -101,7 +107,9 @@ const CATEGORIES = {
'experimental': ['axmc', 'axmr', 'rfl', 'tbrfl', 'tbsc', 'tbem', 'xs', 'sfn', 'rcpl', 'dtl', 'rsl', 'mahr',],
// Guardian
'guardian': ['gpp', 'gpd', 'gpc', 'ggc', 'gsrp', 'gfsb', 'ghrp', 'gmrp', 'gsc']
'guardian': ['gpp', 'gpd', 'gpc', 'ggc', 'gsrp', 'gfsb', 'ghrp', 'gmrp', 'gsc'],
'mining': ['ml', 'scl', 'pwa', 'sdm', 'abl'],
};
/**
@@ -133,6 +141,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
constructor(props, context) {
super(props);
this._hideDiff = this._hideDiff.bind(this);
this._showSearch = this._showSearch.bind(this);
this.state = this._initState(props, context);
this.slotItems = [];// Array to hold <li> refs.
}
@@ -159,7 +168,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
onSelect(m);
}
);
let fuzzy = [];
if (modules instanceof Array) {
list = buildGroup(modules[0].grp, modules);
} else {
@@ -167,9 +176,11 @@ export default class AvailableModulesMenu extends TranslatedComponent {
// At present time slots with grouped options (Hardpoints and Internal) can be empty
if (m) {
let emptyId = 'empty';
if(this.firstSlotId == null) this.firstSlotId = emptyId;
if (this.firstSlotId == null) this.firstSlotId = emptyId;
let keyDown = this._keyDown.bind(this, onSelect);
list.push(<div className='empty-c upp' key={emptyId} data-id={emptyId} onClick={onSelect.bind(null, null)} onKeyDown={keyDown} tabIndex="0" ref={slotItem => this.slotItems[emptyId] = slotItem} >{translate('empty')}</div>);
list.push(<div className='empty-c upp' key={emptyId} data-id={emptyId} onClick={onSelect.bind(null, null)}
onKeyDown={keyDown} tabIndex="0"
ref={slotItem => this.slotItems[emptyId] = slotItem}>{translate('empty')}</div>);
}
// Need to regroup the modules by our own categorisation
@@ -197,7 +208,8 @@ export default class AvailableModulesMenu extends TranslatedComponent {
if (categories.length === 1) {
// Show category header instead of group header
if (m && grp == m.grp) {
list.push(<div ref={(elem) => this.groupElem = elem} key={category} className={'select-category upp'}>{translate(category)}</div>);
list.push(<div ref={(elem) => this.groupElem = elem} key={category}
className={'select-category upp'}>{translate(category)}</div>);
} else {
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
}
@@ -208,19 +220,32 @@ export default class AvailableModulesMenu extends TranslatedComponent {
categoryHeader = true;
}
if (m && grp == m.grp) {
list.push(<div ref={(elem) => this.groupElem = elem} key={grp} className={'select-group cap'}>{translate(grp)}</div>);
list.push(<div ref={(elem) => this.groupElem = elem} key={grp}
className={'select-group cap'}>{translate(grp)}</div>);
} else {
list.push(<div key={grp} className={'select-group cap'}>{translate(grp)}</div>);
}
}
list.push(buildGroup(grp, modules[grp]));
for (const i of modules[grp]) {
let mount = '';
if (i.mount === 'F') {
mount = 'Fixed';
} else if (i.mount === 'G') {
mount = 'Gimballed';
} else if (i.mount === 'T') {
mount = 'Turreted';
}
const fuzz = { grp, m: i, name: `${i.class}${i.rating}${mount ? ' ' + mount : ''} ${translate(grp)}` };
fuzzy.push(fuzz);
}
}
}
}
}
}
let trackingFocus = false;
return { list, currentGroup, trackingFocus };
return { list, currentGroup, fuzzy, trackingFocus };
}
/**
@@ -242,9 +267,11 @@ export default class AvailableModulesMenu extends TranslatedComponent {
const sortedModules = modules.sort(this._moduleOrder);
// Calculate the number of items per class. Used so we don't have long lists with only a few items in each row
const tmp = sortedModules.map((v, i) => v['class']).reduce((count, cls) => { count[cls] = ++count[cls] || 1; return count; }, {});
const tmp = sortedModules.map((v, i) => v['class']).reduce((count, cls) => {
count[cls] = ++count[cls] || 1;
return count;
}, {});
const itemsPerClass = Math.max.apply(null, Object.keys(tmp).map(key => tmp[key]));
let itemsOnThisRow = 0;
@@ -297,22 +324,29 @@ export default class AvailableModulesMenu extends TranslatedComponent {
};
}
switch(m.mount) {
case 'F': mount = <MountFixed className={'lg'} />; break;
case 'G': mount = <MountGimballed className={'lg'}/>; break;
case 'T': mount = <MountTurret className={'lg'}/>; break;
switch (m.mount) {
case 'F':
mount = <MountFixed className={'lg'}/>;
break;
case 'G':
mount = <MountGimballed className={'lg'}/>;
break;
case 'T':
mount = <MountTurret className={'lg'}/>;
break;
}
if (m.name && m.name === prevName) {
// elems.push(<br key={'b' + m.grp + i} />);
itemsOnThisRow = 0;
}
if (itemsOnThisRow == 6 || i > 0 && sortedModules.length > 3 && itemsPerClass > 2 && m.class != prevClass && (m.rating != prevRating || m.mount)) {
elems.push(<br key={'b' + m.grp + i} />);
elems.push(<br key={'b' + m.grp + i}/>);
itemsOnThisRow = 0;
}
let tbIdx = (classes.indexOf('disabled') < 0) ? 0 : undefined;
elems.push(
<li key={m.id} data-id={m.id} className={classes} {...eventHandlers} tabIndex={tbIdx} ref={slotItem => this.slotItems[m.id] = slotItem}>
<li key={m.id} data-id={m.id} className={classes} {...eventHandlers} tabIndex={tbIdx}
ref={slotItem => this.slotItems[m.id] = slotItem}>
{mount}
{(mount ? ' ' : '') + m.class + m.rating + (m.missile ? '/' + m.missile : '') + (m.name ? ' ' + translate(m.name) : '')}
</li>
@@ -340,6 +374,40 @@ export default class AvailableModulesMenu extends TranslatedComponent {
}
}
/**
* Generate tooltip content for the difference between the
* mounted module and the hovered modules
*/
_showSearch() {
if (this.props.modules instanceof Array) {
return;
}
return (
<FuzzySearch
list={this.state.fuzzy}
keys={['grp', 'name']}
tokenize={true}
className={'input'}
width={'100%'}
style={{ padding: 0 }}
onSelect={e => this.props.onSelect.bind(null, e.m)()}
resultsTemplate={(props, state, styles, clickHandler) => {
return state.results.map((val, i) => {
return (
<div
key={i}
className={'lc'}
onClick={() => clickHandler(i)}
>
{val.name}
</div>
);
});
}}
/>
);
}
/**
* Mouse over diff handler
* @param {Function} showDiff diff tooltip callback
@@ -405,7 +473,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
* @param {Function} select Select module callback
* @param {SytheticEvent} event Event
*/
_keyUp(select,event) {
_keyUp(select, event) {
// nothing here yet
}
@@ -475,12 +543,13 @@ export default class AvailableModulesMenu extends TranslatedComponent {
this.slotItems[this.firstSlotId].focus();
}
}
/**
* Handle focus if the component updates
*
*/
componentWillUnmount() {
if(this.props.slotDiv) {
if (this.props.slotDiv) {
this.props.slotDiv.focus();
}
}
@@ -501,11 +570,12 @@ 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._showSearch()}
{this.state.list}
</div>
);

View File

@@ -2,12 +2,21 @@ import React from 'react';
import cn from 'classnames';
import Slot from './Slot';
import Persist from '../stores/Persist';
import { DamageAbsolute, DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret, ListModifications, Modified } from './SvgIcons';
import {
DamageAbsolute,
DamageKinetic,
DamageThermal,
DamageExplosive,
MountFixed,
MountGimballed,
MountTurret,
ListModifications,
Modified
} from './SvgIcons';
import { Modifications } from 'coriolis-data/dist';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
import { blueprintTooltip } from '../utils/BlueprintFunctions';
/**
* Hardpoint / Utility Slot
*/
@@ -27,7 +36,7 @@ export default class HardpointSlot extends Slot {
* @return {string} Label
*/
_getMaxClassLabel(translate) {
return translate(['U','S','M','L','H'][this.props.maxClass]);
return translate(['U', 'S', 'M', 'L', 'H'][this.props.maxClass]);
}
/**
@@ -66,42 +75,76 @@ export default class HardpointSlot extends Slot {
return <div className={className} draggable='true' onDragStart={drag} onDragEnd={drop}>
<div className={'cb'}>
<div className={'l'}>
{m.mount && m.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed /></span> : ''}
{m.mount && m.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : ''}
{m.mount && m.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : ''}
{m.getDamageDist() && m.getDamageDist().K ? <span onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /></span> : ''}
{m.getDamageDist() && m.getDamageDist().T ? <span onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /></span> : ''}
{m.getDamageDist() && m.getDamageDist().E ? <span onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /></span> : ''}
{m.getDamageDist() && m.getDamageDist().A ? <span onMouseOver={termtip.bind(null, 'absolute')} onMouseOut={tooltip.bind(null, null)}><DamageAbsolute /></span> : ''}
{classRating} {translate(m.name || m.grp)}{ m.mods && Object.keys(m.mods).length > 0 ? <span className='r' onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : null }
{m.mount && m.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')}
onMouseOut={tooltip.bind(null, null)}><MountFixed/></span> : ''}
{m.mount && m.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')}
onMouseOut={tooltip.bind(null, null)}><MountGimballed/></span> : ''}
{m.mount && m.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')}
onMouseOut={tooltip.bind(null, null)}><MountTurret/></span> : ''}
{m.getDamageDist() && m.getDamageDist().K ? <span onMouseOver={termtip.bind(null, 'kinetic')}
onMouseOut={tooltip.bind(null, null)}><DamageKinetic/></span> : ''}
{m.getDamageDist() && m.getDamageDist().T ? <span onMouseOver={termtip.bind(null, 'thermal')}
onMouseOut={tooltip.bind(null, null)}><DamageThermal/></span> : ''}
{m.getDamageDist() && m.getDamageDist().E ? <span onMouseOver={termtip.bind(null, 'explosive')}
onMouseOut={tooltip.bind(null, null)}><DamageExplosive/></span> : ''}
{m.getDamageDist() && m.getDamageDist().A ? <span onMouseOver={termtip.bind(null, 'absolute')}
onMouseOut={tooltip.bind(null, null)}><DamageAbsolute/></span> : ''}
{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span className='r'
onMouseOver={termtip.bind(null, modTT)}
onMouseOut={tooltip.bind(null, null)}><Modified/></span> : null}
</div>
<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.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 }
{ m.getRoF() ? <div className={'l'} onMouseOver={termtip.bind(null, 'rof')} onMouseOut={tooltip.bind(null, null)}>{translate('ROF')}: {formats.f1(m.getRoF())}{u.ps}</div> : null }
{ m.getRange() ? <div className={'l'}>{translate('range', m.grp)} {formats.f1(m.getRange() / 1000)}{u.km}</div> : null }
{ m.getScanTime() ? <div className={'l'}>{translate('scantime')} {formats.f1(m.getScanTime())}{u.s}</div> : null }
{ m.getFalloff() ? <div className={'l'}>{translate('falloff')} {formats.round(m.getFalloff() / 1000)}{u.km}</div> : null }
{ m.getShieldBoost() ? <div className={'l'}>+{formats.pct1(m.getShieldBoost())}</div> : null }
{ m.getAmmo() ? <div className={'l'}>{translate('ammunition')}: {formats.int(m.getClip())}/{formats.int(m.getAmmo())}</div> : null }
{ m.getReload() ? <div className={'l'}>{translate('reload')}: {formats.round(m.getReload())}{u.s}</div> : null }
{ m.getShotSpeed() ? <div className={'l'}>{translate('shotspeed')}: {formats.int(m.getShotSpeed())}{u.mps}</div> : null }
{ m.getPiercing() ? <div className={'l'}>{translate('piercing')}: {formats.int(m.getPiercing())}</div> : null }
{ m.getJitter() ? <div className={'l'}>{translate('jitter')}: {formats.f2(m.getJitter())}°</div> : null }
{ 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 }
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
{ m && validMods.length > 0 ? <div className='r' tabIndex="0" ref={ modButton => this.modButton = modButton }><button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></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.getDamage() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getDamage() ? 'shotdmg' : 'shotdmg')}
onMouseOut={tooltip.bind(null, null)}>{translate('shotdmg')}: {formats.round1(m.getDamage())}</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}
{m.getRoF() ? <div className={'l'} onMouseOver={termtip.bind(null, 'rof')}
onMouseOut={tooltip.bind(null, null)}>{translate('ROF')}: {formats.f1(m.getRoF())}{u.ps}</div> : null}
{m.getRange() ? <div
className={'l'}>{translate('range', m.grp)} {formats.f1(m.getRange() / 1000)}{u.km}</div> : null}
{m.getScanTime() ? <div
className={'l'}>{translate('scantime')} {formats.f1(m.getScanTime())}{u.s}</div> : null}
{m.getFalloff() ? <div
className={'l'}>{translate('falloff')} {formats.round(m.getFalloff() / 1000)}{u.km}</div> : null}
{m.getShieldBoost() ? <div className={'l'}>+{formats.pct1(m.getShieldBoost())}</div> : null}
{m.getAmmo() ? <div
className={'l'}>{translate('ammunition')}: {formats.int(m.getClip())}/{formats.int(m.getAmmo())}</div> : null}
{m.getReload() ? <div className={'l'}>{translate('reload')}: {formats.round(m.getReload())}{u.s}</div> : null}
{m.getShotSpeed() ? <div
className={'l'}>{translate('shotspeed')}: {formats.int(m.getShotSpeed())}{u.mps}</div> : null}
{m.getPiercing() ? <div className={'l'}>{translate('piercing')}: {formats.int(m.getPiercing())}</div> : null}
{m.getJitter() ? <div className={'l'}>{translate('jitter')}: {formats.f2(m.getJitter())}°</div> : null}
{m.getScanAngle() ? <div className={'l'}>{translate('scan angle')}: {formats.f2(m.getScanAngle())}°</div> : null}
{m.getScanRange() ? <div className={'l'}>{translate('scan range')}: {formats.int(m.getScanRange())}{u.m}</div> : null}
{m.getMaxAngle() ? <div className={'l'}>{translate('max angle')}: {formats.f2(m.getMaxAngle())}°</div> : null}
{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}
{m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null}
{m && validMods.length > 0 ? <div className='r' tabIndex="0" ref={modButton => this.modButton = modButton}>
<button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation}
onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}>
<ListModifications/></button>
</div> : null}
</div>
</div>;
} else {
return <div className={'empty'}>{translate('empty')}</div>;
return <div className={'empty'}>{translate('empty')}</div>;
}
}

View File

@@ -153,6 +153,15 @@ export default class HardpointSlotSection extends SlotSection {
<ul>
<li className='lc' tabIndex='0' onClick={_fill.bind(this, 'nl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['nl-F'] = smRef}>{translate('nl')}</li>
</ul>
<div className='select-group cap'>{translate('ggc')}</div>
<ul>
<li className='lc' tabIndex='0' onClick={_fill.bind(this, 'ggc', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ggc-F'] = smRef}>{translate('ggc')}</li>
</ul>
<div className='select-group cap'>{translate('rfl')}</div>
<ul>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'rfl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['rfl-F'] = smRef}><MountFixed className='lg'/></li>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'rfl', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['rfl-T'] = smRef}><MountTurret className='lg'/></li>
</ul>
</div>;
}

View File

@@ -23,8 +23,10 @@ export default class ModalOrbis extends TranslatedComponent {
this.state = {
orbisCreds: Persist.getOrbisCreds(),
orbisUrl: '...',
ship: this.props.ship,
authenticatedStatus: 'Checking...'
};
this.orbisCategory = this.orbisCategory.bind(this);
}
/**
@@ -88,6 +90,17 @@ export default class ModalOrbis extends TranslatedComponent {
});
}
/**
* Handler for changing category
* @param {SyntheticEvent} e React Event
*/
orbisCategory(e) {
let ship = this.state.ship;
let cat = e.target.value;
ship.category = cat;
this.setState({ship});
}
/**
* Render the modal
* @return {React.Component} Modal Content
@@ -106,6 +119,17 @@ export default class ModalOrbis extends TranslatedComponent {
<br/><br/>
<a className='button' href="https://orbis.zone/api/auth">Log in / signup to Orbis</a>
<br/><br/>
<h3>Category</h3>
<select onChange={this.orbisCategory}>
<option value="">No Category</option>
<option>Combat</option>
<option>Mining</option>
<option>Trading</option>
<option>Exploration</option>
<option>Passenger Liner</option>
<option>PvP</option>
</select>
<br/><br/>
<h3 >{translate('Orbis link')}</h3>
<input value={this.state.orbisUrl} readOnly size={25} onFocus={ (e) => e.target.select() }/>
<br/><br/>

View File

@@ -52,6 +52,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
const boostTooltip = canBoost ? 'TT_SUMMARY_BOOST' : canThrust ? 'TT_SUMMARY_BOOST_NONFUNCTIONAL' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
const sgMetrics = Calc.shieldMetrics(ship, pips.sys);
const shipBoost = canBoost ? Calc.calcBoost(ship) : 'No Boost';
const restingHeat = Math.sqrt(((ship.standard[0].m.pgen * ship.standard[0].m.eff) / ship.heatCapacity) / 0.2);
const armourMetrics = Calc.armourMetrics(ship);
let shieldColour = 'blue';
if (shieldGenerator && shieldGenerator.m.grp === 'psg') {
@@ -78,13 +79,14 @@ export default class ShipSummaryTable extends TranslatedComponent {
<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} onMouseEnter={termtip.bind(null, 'passenger capacity', { cap: 0 })} onMouseLeave={hide}>{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>
<th rowSpan={2}>{translate('resting heat (Beta)')}</th>
</tr>
<tr>
<th className='lft'>{translate('max')}</th>
@@ -122,6 +124,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
<td>{ship.crew}</td>
<td>{ship.masslock}</td>
<td>{shipBoost !== 'No Boost' ? formats.time(shipBoost) : 'No Boost'}</td>
<td>{formats.pct(restingHeat)}</td>
</tr>
</tbody>
</table>

View File

@@ -20,16 +20,28 @@ export default class StandardSlotSection extends SlotSection {
super(props, context, 'standard', 'core internal');
this._optimizeStandard = this._optimizeStandard.bind(this);
this._selectBulkhead = this._selectBulkhead.bind(this);
this._showDW2Menu = this._showDW2Menu.bind(this);
this._dw2 = this._dw2.bind(this);
this.selectedRefId = null;
this.firstRefId = 'maxjump';
this.lastRefId = 'racer';
this.lastRefId = 'dw2';
this.state = {
showDW2: false,
DW2Tier: -1,
DW2Eng: -1,
DW2Role: '',
DW2Gfsb: false,
DW2Gpp: false,
DW2Fighter: false
};
}
/**
* Handle focus if the component updates
* @param {Object} prevProps React Component properties
*/
componentDidUpdate(prevProps) {
this._handleSectionFocus(prevProps,this.firstRefId, this.lastRefId);
this._handleSectionFocus(prevProps, this.firstRefId, this.lastRefId);
}
/**
@@ -72,6 +84,114 @@ export default class StandardSlotSection extends SlotSection {
this._close();
}
/**
* DW2 Build
*/
_dw2() {
this.selectedRefId = 'dw2';
this.setState({ showDW2: false });
ShipRoles.dw2Build(this.props.ship, this.state.DW2Tier, this.state.DW2Eng, this.state.DW2Role, this.state.DW2Gfsb, this.state.DW2Gpp, this.state.DW2Fighter);
this.props.ship.updateModificationsString();
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
_showDW2Menu(translate) {
return (
<div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<div className='select-group cap'>{translate('Tier')}</div>
<ul id={'tier'}>
<li className={cn({ active: this.state.DW2Tier === 1 }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Tier: 1 })} onKeyDown={this._keyDown}
>{translate('1 - Max. Jump Range, Unshielded')}</li>
<li className={cn({ active: this.state.DW2Tier === 2 }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Tier: 2 })} onKeyDown={this._keyDown}
>{translate('2 - Max. Jump Range, Minimal Shields')}</li>
<li className={cn({ active: this.state.DW2Tier === 3 }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Tier: 3 })} onKeyDown={this._keyDown}
>{translate('3 - Max. Jump Range, Optimal Shields')}</li>
<li className={cn({ active: this.state.DW2Tier === 4 }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Tier: 4 })} onKeyDown={this._keyDown}
>{translate('4 - Max. Jump Range, Optimal Shields & Thrusters')}</li>
</ul>
<hr/>
<div className='select-group cap'>{translate('Engineering Level')}</div>
<ul id={'engLevel'}>
<li className={cn({ active: this.state.DW2Eng === 1 }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Eng: 1 })} onKeyDown={this._keyDown}
>{translate('No engineering')}</li>
<li className={cn({ active: this.state.DW2Eng === 2 }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Eng: 2 })} onKeyDown={this._keyDown}
>{translate('Only Felicity Farseer and Elvira Martuuk')}</li>
<li className={cn({ active: this.state.DW2Eng === 3 }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Eng: 3 })} onKeyDown={this._keyDown}
>{translate('All exploration engineers')}</li>
</ul>
<hr/>
<div className='select-group cap'>{translate('Role')}</div>
<ul id={'role'}>
<li className={cn({ active: this.state.DW2Role === 'exploration' }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Role: 'exploration' })}
onKeyDown={this._keyDown}
>{translate('Space exploration')}</li>
<li className={cn({ active: this.state.DW2Role === 'surface' }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Role: 'surface' })}
onKeyDown={this._keyDown}
>{translate('Surface exploration')}</li>
<li className={cn({ active: this.state.DW2Role === 'materialProspector' }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Role: 'materialProspector' })}
onKeyDown={this._keyDown}
>{translate('Material prospector')}</li>
<li className={cn({ active: this.state.DW2Role === 'propectorMining' }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Role: 'propectorMining' })}
onKeyDown={this._keyDown}
>{translate('Prospector/Sapper Miner')}</li>
<li className={cn({ active: this.state.DW2Role === 'bigRigMining' }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Role: 'bigRigMining' })}
onKeyDown={this._keyDown}
>{translate('Big Rig, full mining')}</li>
<li className={cn({ active: this.state.DW2Role === 'fuelRat' }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Role: 'fuelRat' })} onKeyDown={this._keyDown}
>{translate('Fuel Rat')}</li>
<li className={cn({ active: this.state.DW2Role === 'mechanic' }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Role: 'mechanic' })} onKeyDown={this._keyDown}
>{translate('Mechanic')}</li>
<li className={cn({ active: this.state.DW2Role === 'trucker' }, 'lc')} tabIndex="0"
onClick={() => this.setState({ DW2Role: 'trucker' })} onKeyDown={this._keyDown}
>{translate('Trucker')}</li>
</ul>
<hr/>
<ul>
<li className={cn({ active: this.state.DW2Gfsb === true }, 'lc')}
onClick={() => this.setState({ DW2Gfsb: this.state.DW2Gfsb !== true })}>
Add Guardian FSD Booster
</li>
</ul>
<ul>
<li className={cn({ active: this.state.DW2Gpp === true }, 'lc')}
onClick={() => this.setState({ DW2Gpp: this.state.DW2Gpp !== true })}>
Add Guardian Power Plant
</li>
</ul>
<ul>
<li className={cn({ active: this.state.DW2Fighter === true }, 'lc')}
onClick={() => this.setState({ DW2Fighter: this.state.DW2Fighter !== true })}>
Add Fighter
</li>
</ul>
<hr/>
<ul>
<li onClick={this._dw2} className={cn('lc')} tabIndex="0"
onKeyDown={this._keyDown}>
<button className="button">Apply</button>
</li>
</ul>
</div>
);
}
/**
* Miner Build
* @param {Boolean} shielded True if shield generator should be included
@@ -177,7 +297,6 @@ export default class StandardSlotSection extends SlotSection {
warning={m => m instanceof Module ? m.getMaxMass() < (ship.unladenMass + cargo + fuel - st[1].m.mass + m.mass) : m.maxmass < (ship.unladenMass + cargo + fuel - st[1].m.mass + m.mass)}
/>;
slots[3] = <StandardSlot
key='fsd'
slot={st[2]}
@@ -232,7 +351,7 @@ export default class StandardSlotSection extends SlotSection {
selected={currentMenu == st[6]}
onChange={this.props.onChange}
ship={ship}
warning= {m => m.fuel < st[2].m.maxfuel} // Show warning when fuel tank is smaller than FSD Max Fuel
warning={m => m.fuel < st[2].m.maxfuel} // Show warning when fuel tank is smaller than FSD Max Fuel
/>;
return slots;
@@ -245,19 +364,34 @@ export default class StandardSlotSection extends SlotSection {
*/
_getSectionMenu(translate) {
let planetaryDisabled = this.props.ship.internal.length < 4;
if (this.state.showDW2 === true) {
return this._showDW2Menu(translate);
}
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul>
<li className='lc' tabIndex="0" onClick={this._optimizeStandard} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['maxjump'] = smRef}>{translate('Maximize Jump Range')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeStandard} onKeyDown={this._keyDown}
ref={smRef => this.sectionRefArr['maxjump'] = smRef}>{translate('Maximize Jump Range')}</li>
</ul>
<div className='select-group cap'>{translate('roles')}</div>
<ul>
<li className='lc' tabIndex="0" onClick={this._multiPurpose.bind(this, false, 0)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['multipurpose'] = smRef}>{translate('Multi-purpose')}</li>
<li className='lc' tabIndex="0" onClick={this._multiPurpose.bind(this, true, 2)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['combat'] = smRef}>{translate('Combat')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeCargo.bind(this, true)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['trader'] = smRef}>{translate('Trader')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeExplorer.bind(this, false)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['explorer'] = smRef}>{translate('Explorer')}</li>
<li className={cn('lc', { disabled: planetaryDisabled })} tabIndex={planetaryDisabled ? '' : '0'} onClick={!planetaryDisabled && this._optimizeExplorer.bind(this, true)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['planetary'] = smRef}>{translate('Planetary Explorer')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeMiner.bind(this, true)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['miner'] = smRef}>{translate('Miner')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeRacer.bind(this)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['racer'] = smRef}>{translate('Racer')}</li>
<li className='lc' tabIndex="0" onClick={this._multiPurpose.bind(this, false, 0)} onKeyDown={this._keyDown}
ref={smRef => this.sectionRefArr['multipurpose'] = smRef}>{translate('Multi-purpose')}</li>
<li className='lc' tabIndex="0" onClick={this._multiPurpose.bind(this, true, 2)} onKeyDown={this._keyDown}
ref={smRef => this.sectionRefArr['combat'] = smRef}>{translate('Combat')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeCargo.bind(this, true)} onKeyDown={this._keyDown}
ref={smRef => this.sectionRefArr['trader'] = smRef}>{translate('Trader')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeExplorer.bind(this, false)} onKeyDown={this._keyDown}
ref={smRef => this.sectionRefArr['explorer'] = smRef}>{translate('Explorer')}</li>
<li className={cn('lc', { disabled: planetaryDisabled })} tabIndex={planetaryDisabled ? '' : '0'}
onClick={!planetaryDisabled && this._optimizeExplorer.bind(this, true)} onKeyDown={this._keyDown}
ref={smRef => this.sectionRefArr['planetary'] = smRef}>{translate('Planetary Explorer')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeMiner.bind(this, true)} onKeyDown={this._keyDown}
ref={smRef => this.sectionRefArr['miner'] = smRef}>{translate('Miner')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeRacer.bind(this)} onKeyDown={this._keyDown}
ref={smRef => this.sectionRefArr['racer'] = smRef}>{translate('Racer')}</li>
<li className='lc' tabIndex="0" onClick={() => this.setState({ showDW2: !this.state.showDW2 })}
onKeyDown={this._keyDown}
ref={smRef => this.sectionRefArr['dw2'] = smRef}>{translate('DW2')}</li>
</ul>
</div>;
}

View File

@@ -1,7 +1,7 @@
import TranslatedComponent from './TranslatedComponent';
import React, { PropTypes } from 'react';
import ContainerDimensions from 'react-container-dimensions';
import { BarChart, Bar, XAxis, YAxis } from 'recharts';
import { BarChart, Bar, XAxis, YAxis, LabelList } from 'recharts';
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
const LABEL_COLOUR = '#000000';
@@ -53,7 +53,9 @@ export default class VerticalBarChart extends TranslatedComponent {
<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)}/>
<Bar dataKey='value' fill={CORIOLIS_COLOURS[0]} isAnimationActive={false} onMouseOver={this._termtip} onMouseOut={tooltip.bind(null, null)}>
<LabelList dataKey='value' position='insideTop'/>
</Bar>
</BarChart>
</div>
)}
@@ -76,29 +78,3 @@ export default class VerticalBarChart extends TranslatedComponent {
}
}
}
/**
* 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>
);
}
};

File diff suppressed because one or more lines are too long

View File

@@ -6,7 +6,6 @@ import { CoriolisLogo, GitHub } from '../components/SvgIcons';
* About Page
*/
export default class AboutPage extends Page {
/**
* Constructor
* @param {Object} props React Component properties
@@ -23,33 +22,112 @@ export default class AboutPage extends Page {
* @return {React.Component} The page contents
*/
renderPage() {
return <div className={'page'} style={{ textAlign: 'left', maxWidth: 800, margin: '0 auto' }}>
<h1><CoriolisLogo style={{ marginRight: '0.4em' }} className='xl'/><span className='warning'>Coriolis EDCD Edition</span></h1>
return (
<div
className={'page'}
style={{ textAlign: 'left', maxWidth: 800, margin: '0 auto' }}
>
<h1>
<CoriolisLogo style={{ marginRight: '0.4em' }} className="xl" />
<span className="warning">Coriolis EDCD Edition</span>
</h1>
<p>This is a clone of the Coriolis project, whose original author is currently unable to maintain it. This clone is maintained by the <a href="http://edcd.github.io/">EDCD community</a>.</p>
<p>To recover your builds, go to <a href='https://coriolis.io/' target='_blank'>https://coriolis.io/</a>, backup your builds (Settings / Backup), copy the text, return here and import (Settings / Import).</p>
<p>The Coriolis project was inspired by <a href='http://www.edshipyard.com/' target='_blank'>E:D Shipyard</a> and, of course, <a href='http://www.elitedangerous.com' target='_blank'>Elite Dangerous</a>. The ultimate goal of Coriolis is to provide rich features to support in-game play and planning while engaging the E:D community to support its development.</p>
<p>Coriolis was created using assets and imagery from Elite: Dangerous, with the permission of Frontier Developments plc, for non-commercial purposes. It is not endorsed by nor reflects the views or opinions of Frontier Developments. A number of assets were sourced from <a href='http://edassets.org' target='_blank'>ED Assets</a></p>
<p>
This is a clone of the Coriolis project, whose original author is
currently unable to maintain it. This clone is maintained by the{' '}
<a href="http://edcd.github.io/">EDCD community</a>.
</p>
<p>
To recover your builds, go to{' '}
<a href="https://coriolis.io/" target="_blank">
https://coriolis.io/
</a>
, backup your builds (Settings / Backup), copy the text, return here
and import (Settings / Import).
</p>
<p>
The Coriolis project was inspired by{' '}
<a href="http://www.edshipyard.com/" target="_blank">
E:D Shipyard
</a>{' '}
and, of course,{' '}
<a href="http://www.elitedangerous.com" target="_blank">
Elite Dangerous
</a>
. The ultimate goal of Coriolis is to provide rich features to support
in-game play and planning while engaging the E:D community to support
its development.
</p>
<p>
Coriolis was created using assets and imagery from Elite: Dangerous,
with the permission of Frontier Developments plc, for non-commercial
purposes. It is not endorsed by nor reflects the views or opinions of
Frontier Developments. A number of assets were sourced from{' '}
<a href="http://edassets.org" target="_blank">
ED Assets
</a>
</p>
<a style={{ display: 'block', textDecoration: 'none' }} href='https://github.com/EDCD/coriolis' target='_blank' title='Coriolis Github Project'>
<GitHub style={{ margin: '0.4em' }} className='l fg xl'/>
<h2 style={{ margin: 0, textDecoration: 'none' }}>Github</h2>
github.com/EDCD/coriolis
</a>
<a
style={{ display: 'block', textDecoration: 'none' }}
href="https://github.com/EDCD/coriolis"
target="_blank"
title="Coriolis Github Project"
>
<GitHub style={{ margin: '0.4em' }} className="l fg xl" />
<h2 style={{ margin: 0, textDecoration: 'none' }}>Github</h2>
github.com/EDCD/coriolis
</a>
<p>Coriolis is an open source project. Checkout the list of upcoming features and to-do list on github. Any and all contributions and feedback are welcome. If you encounter any bugs please report them and provide as much detail as possible.</p>
<p>
Coriolis is an open source project. Checkout the list of upcoming
features and to-do list on github. Any and all contributions and
feedback are welcome. If you encounter any bugs please report them and
provide as much detail as possible.
</p>
<h3>Chat</h3>
<p>You can chat to us on our <a href='https://discord.gg/0uwCh6R62aPRjk9w' target='_blank'>EDCD Discord server</a>.</p>
<h3>Chat</h3>
<p>
You can chat to us on our{' '}
<a href="https://discord.gg/0uwCh6R62aPRjk9w" target="_blank">
EDCD Discord server
</a>
.
</p>
<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" />
<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>;
</div>
);
}
}

View File

@@ -13,7 +13,14 @@ import ModalCompare from '../components/ModalCompare';
import ModalExport from '../components/ModalExport';
import ModalPermalink from '../components/ModalPermalink';
import ModalImport from '../components/ModalImport';
import { FloppyDisk, Bin, Download, Embed, Rocket, LinkIcon } from '../components/SvgIcons';
import {
FloppyDisk,
Bin,
Download,
Embed,
Rocket,
LinkIcon
} from '../components/SvgIcons';
import ShortenUrl from '../utils/ShortenUrl';
import { comparisonBBCode } from '../utils/BBCode';
const browser = require('detect-browser');
@@ -42,7 +49,6 @@ function sortBy(predicate) {
* Comparison Page
*/
export default class ComparisonPage extends Page {
/**
* Constructor
* @param {Object} props React Component properties
@@ -81,7 +87,13 @@ export default class ComparisonPage extends Page {
for (let shipId in allBuilds) {
for (let buildName in allBuilds[shipId]) {
if (buildName && allBuilds[shipId][buildName]) {
builds.push(this._createBuild(shipId, buildName, allBuilds[shipId][buildName]));
builds.push(
this._createBuild(
shipId,
buildName,
allBuilds[shipId][buildName]
)
);
}
}
}
@@ -89,7 +101,9 @@ export default class ComparisonPage extends Page {
let comparisonData = Persist.getComparison(name);
if (comparisonData) {
defaultFacets = comparisonData.facets;
comparisonData.builds.forEach((b) => builds.push(this._createBuild(b.shipId, b.buildName)));
comparisonData.builds.forEach(b =>
builds.push(this._createBuild(b.shipId, b.buildName))
);
saved = true;
newName = name;
}
@@ -101,7 +115,7 @@ export default class ComparisonPage extends Page {
newName = name = comparisonData.n;
predicate = comparisonData.p;
desc = comparisonData.d;
comparisonData.b.forEach((build) => {
comparisonData.b.forEach(build => {
builds.push(this._createBuild(build.s, build.n, build.c));
if (!importObj[build.s]) {
importObj[build.s] = {};
@@ -118,9 +132,9 @@ export default class ComparisonPage extends Page {
let selectedFacets = new Array(selectedLength);
for (let i = 0; i < ShipFacets.length; i++) {
let facet = Object.assign({ }, ShipFacets[i]);
let facet = Object.assign({}, ShipFacets[i]);
let defaultIndex = defaultFacets.indexOf(facet.i);
if(defaultIndex == -1) {
if (defaultIndex == -1) {
facets.push(facet);
} else {
facet.active = true;
@@ -155,17 +169,18 @@ export default class ComparisonPage extends Page {
_createBuild(id, name, code) {
code = code ? code : Persist.getBuild(id, name); // Retrieve build code if not passed
if (!code) { // No build found
if (!code) {
// No build found
return;
}
let data = Ships[id]; // Get ship properties
let data = Ships[id]; // Get ship properties
let b = new Ship(id, data.properties, data.slots); // Create a new Ship instance
b.buildFrom(code); // Populate components from code
b.buildFrom(code); // Populate components from code
b.buildName = name;
b.applyDiscounts(Persist.getShipDiscount(), Persist.getModuleDiscount());
return b;
};
}
/**
* Update state with the specified sort predicates
@@ -184,13 +199,18 @@ export default class ComparisonPage extends Page {
}
this.setState({ predicate, desc });
};
}
/**
* Show selected builds modal
*/
_selectBuilds() {
this.context.showModal(<ModalCompare onSelect={this._buildsSelected} builds={this.state.builds} />);
this.context.showModal(
<ModalCompare
onSelect={this._buildsSelected}
builds={this.state.builds}
/>
);
}
/**
@@ -224,7 +244,7 @@ export default class ComparisonPage extends Page {
_facetDrag(e) {
this.nodeAfter = false;
this.dragged = e.currentTarget;
let placeholder = this.placeholder = document.createElement('li');
let placeholder = (this.placeholder = document.createElement('li'));
placeholder.style.width = Math.round(this.dragged.offsetWidth) + 'px';
placeholder.className = 'facet-placeholder';
if (!browser || (browser.name !== 'edge' && browser.name !== 'ie')) {
@@ -262,7 +282,7 @@ export default class ComparisonPage extends Page {
_facetDragOver(e) {
e.preventDefault();
if(e.target.className == 'facet-placeholder') {
if (e.target.className == 'facet-placeholder') {
return;
} else if (e.target != e.currentTarget) {
this.over = e.target;
@@ -272,7 +292,7 @@ export default class ComparisonPage extends Page {
let parent = e.target.parentNode;
if (parent == e.currentTarget) {
if(relX > width && this.dragged != e.target) {
if (relX > width && this.dragged != e.target) {
this.nodeAfter = true;
parent.insertBefore(this.placeholder, e.target.nextElementSibling);
} else {
@@ -321,7 +341,7 @@ export default class ComparisonPage extends Page {
let { newName, builds, facets } = this.state;
let selectedFacets = [];
facets.forEach((f) => {
facets.forEach(f => {
if (f.active) {
selectedFacets.unshift(f.i);
}
@@ -348,14 +368,20 @@ export default class ComparisonPage extends Page {
let code = fromComparison(name, builds, selectedFacets, predicate, desc);
let loc = window.location;
return loc.protocol + '//' + loc.host + '/comparison?code=' + encodeURIComponent(code);
return (
loc.protocol +
'//' +
loc.host +
'/comparison?code=' +
encodeURIComponent(code)
);
}
/**
* Generates the long permalink URL
*/
_genPermalink() {
this.context.showModal(<ModalPermalink url={this._buildUrl()}/>);
this.context.showModal(<ModalPermalink url={this._buildUrl()} />);
}
/**
@@ -365,18 +391,25 @@ export default class ComparisonPage extends Page {
let { translate, formats } = this.context.language;
let { facets, builds } = this.state;
let generator = (callback) => {
let generator = callback => {
let url = this._buildUrl();
ShortenUrl(url,
(shortenedUrl) => callback(comparisonBBCode(translate, formats, facets, builds, shortenedUrl)),
(error) => callback(comparisonBBCode(translate, formats, facets, builds, url))
ShortenUrl(
url,
shortenedUrl =>
callback(
comparisonBBCode(translate, formats, facets, builds, shortenedUrl)
),
error =>
callback(comparisonBBCode(translate, formats, facets, builds, url))
);
};
this.context.showModal(<ModalExport
title={translate('forum') + ' BBCode'}
generator={generator}
/>);
this.context.showModal(
<ModalExport
title={translate('forum') + ' BBCode'}
generator={generator}
/>
);
}
/**
@@ -409,7 +442,8 @@ export default class ComparisonPage extends Page {
* @param {Object} nextContext Incoming/Next conext
*/
componentWillReceiveProps(nextProps, nextContext) {
if (this.context.route !== nextContext.route) { // Only reinit state if the route has changed
if (this.context.route !== nextContext.route) {
// Only reinit state if the route has changed
this.setState(this._initState(nextContext));
}
}
@@ -419,7 +453,10 @@ export default class ComparisonPage extends Page {
*/
componentWillMount() {
this.resizeListener = this.context.onWindowResize(this._updateDimensions);
this.persistListener = Persist.addListener('discounts', this._updateDiscounts);
this.persistListener = Persist.addListener(
'discounts',
this._updateDiscounts
);
}
/**
@@ -444,65 +481,132 @@ export default class ComparisonPage extends Page {
renderPage() {
let translate = this.context.language.translate;
let compareHeader;
let { newName, name, saved, builds, facets, predicate, desc, chartWidth } = this.state;
let {
newName,
name,
saved,
builds,
facets,
predicate,
desc,
chartWidth
} = this.state;
if (this.state.compareMode) {
compareHeader = <tr>
<td className='head'>{translate('comparison')}</td>
<td>
<input value={newName} onChange={this._onNameChange} placeholder={translate('Enter Name')} maxLength='50' />
<button onClick={this._save} disabled={!newName || newName == 'all' || saved}>
<FloppyDisk className='lg'/><span className='button-lbl'>{translate('save')}</span>
</button>
<button onClick={this._delete} disabled={name == 'all' || !saved}><Bin className='lg warning'/></button>
<button onClick={this._selectBuilds}>
<Rocket className='lg'/><span className='button-lbl'>{translate('builds')}</span>
</button>
<button className='r' onClick={this._genPermalink} disabled={builds.length == 0}>
<LinkIcon className='lg'/><span className='button-lbl'>{translate('permalink')}</span>
</button>
<button className='r' onClick={this._genBBcode} disabled={builds.length == 0}>
<Embed className='lg'/><span className='button-lbl'>{translate('forum')}</span>
</button>
</td>
</tr>;
compareHeader = (
<tr>
<td className="head">{translate('comparison')}</td>
<td>
<input
value={newName}
onChange={this._onNameChange}
placeholder={translate('Enter Name')}
maxLength="50"
/>
<button
onClick={this._save}
disabled={!newName || newName == 'all' || saved}
>
<FloppyDisk className="lg" />
<span className="button-lbl">{translate('save')}</span>
</button>
<button onClick={this._delete} disabled={name == 'all' || !saved}>
<Bin className="lg warning" />
</button>
<button onClick={this._selectBuilds}>
<Rocket className="lg" />
<span className="button-lbl">{translate('builds')}</span>
</button>
<button
className="r"
onClick={this._genPermalink}
disabled={builds.length == 0}
>
<LinkIcon className="lg" />
<span className="button-lbl">{translate('permalink')}</span>
</button>
<button
className="r"
onClick={this._genBBcode}
disabled={builds.length == 0}
>
<Embed className="lg" />
<span className="button-lbl">{translate('forum')}</span>
</button>
</td>
</tr>
);
} else {
compareHeader = <tr>
<td className='head'>{translate('comparison')}</td>
<td>
<h3>{name}</h3>
<button className='r' onClick={this._import}><Download className='lg'/>{translate('import')}</button>
</td>
</tr>;
compareHeader = (
<tr>
<td className="head">{translate('comparison')}</td>
<td>
<h3>{name}</h3>
<button className="r" onClick={this._import}>
<Download className="lg" />
{translate('import')}
</button>
</td>
</tr>
);
}
return (
<div className={'page'} style={{ fontSize: this.context.sizeRatio + 'em' }}>
<table id='comparison'>
<div
className={'page'}
style={{ fontSize: this.context.sizeRatio + 'em' }}
>
<table id="comparison">
<tbody>
{compareHeader}
<tr key='facets'>
<td className='head'>{translate('compare')}</td>
<tr key="facets">
<td className="head">{translate('compare')}</td>
<td>
<ul id='facet-container' onDragOver={this._facetDragOver}>
{facets.map((f, i) =>
<li key={f.title} data-i={i} draggable='true' onDragStart={this._facetDrag} onDragEnd={this._facetDrop} className={cn('facet', { active: f.active })} onClick={this._toggleFacet.bind(this, f)}>
<ul id="facet-container" onDragOver={this._facetDragOver}>
{facets.map((f, i) => (
<li
key={f.title}
data-i={i}
draggable="true"
onDragStart={this._facetDrag}
onDragEnd={this._facetDrop}
className={cn('facet', { active: f.active })}
onClick={this._toggleFacet.bind(this, f)}
>
{'↔ ' + translate(f.title)}
</li>
)}
))}
</ul>
</td>
</tr>
</tbody>
</table>
<ComparisonTable builds={builds} facets={facets} onSort={this._sortShips} predicate={predicate} desc={desc} />
<ComparisonTable
builds={builds}
facets={facets}
onSort={this._sortShips}
predicate={predicate}
desc={desc}
/>
{!builds.length ?
<div className='chart' ref={node => this.chartRef = node}>{translate('PHRASE_NO_BUILDS')}</div> :
facets.filter((f) => f.active).map((f, i) =>
<div key={f.title} className='chart' ref={ i == 0 ? node => this.chartRef = node : null}>
<h3 className='ptr' onClick={this._sortShips.bind(this, f.props[0])}>{translate(f.title)}</h3>
{!builds.length ? (
<div className="chart" ref={node => (this.chartRef = node)}>
{translate('PHRASE_NO_BUILDS')}
</div>
) : (
facets.filter(f => f.active).map((f, i) => (
<div
key={f.title}
className="chart"
ref={i == 0 ? node => (this.chartRef = node) : null}
>
<h3
className="ptr"
onClick={this._sortShips.bind(this, f.props[0])}
>
{translate(f.title)}
</h3>
<BarChart
width={chartWidth}
data={builds}
@@ -515,8 +619,8 @@ export default class ComparisonPage extends Page {
desc={desc}
/>
</div>
))
)}
</div>
);
}

View File

@@ -5,7 +5,6 @@ import Page from './Page';
* 404 Page
*/
export default class NotFoundPage extends Page {
/**
* Constructor
* @param {Object} props React Component properties
@@ -22,6 +21,10 @@ export default class NotFoundPage extends Page {
* @return {React.Component} The page contents
*/
renderPage() {
return <div className='page' style={{ marginTop: 30 }}>Page <small>{this.context.route.path}</small> Not Found</div>;
return (
<div className="page" style={{ marginTop: 30 }}>
Page <small>{this.context.route.path}</small> Not Found
</div>
);
}
}

View File

@@ -53,7 +53,6 @@ function getTitle(shipName, buildName) {
* The Outfitting Page
*/
export default class OutfittingPage extends Page {
/**
* Constructor
* @param {Object} props React Component properties
@@ -85,22 +84,38 @@ export default class OutfittingPage extends Page {
let shipId = params.ship;
let code = params.code;
let buildName = params.bn;
let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults
let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults
let savedCode = Persist.getBuild(shipId, buildName);
if (!data) {
return { error: { message: 'Ship not found: ' + shipId } };
}
let ship = new Ship(shipId, data.properties, data.slots); // Create a new Ship instance
let ship = new Ship(shipId, data.properties, data.slots); // Create a new Ship instance
if (code) {
ship.buildFrom(code); // Populate modules from serialized 'code' URL param
ship.buildFrom(code); // Populate modules from serialized 'code' URL param
} else {
ship.buildWith(data.defaults); // Populate with default components
ship.buildWith(data.defaults); // Populate with default components
}
this._getTitle = getTitle.bind(this, data.properties.name);
// Obtain ship control from code
const { sys, eng, wep, mcSys, mcEng, mcWep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange } = this._obtainControlFromCode(ship, code);
const {
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
opponentSys,
opponentEng,
opponentWep,
engagementRange
} = this._obtainControlFromCode(ship, code);
return {
error: null,
title: this._getTitle(buildName),
@@ -139,7 +154,10 @@ export default class OutfittingPage extends Page {
};
if (Persist.hasBuild(this.state.shipId, stateChanges.newBuildName)) {
stateChanges.savedCode = Persist.getBuild(this.state.shipId, stateChanges.newBuildName);
stateChanges.savedCode = Persist.getBuild(
this.state.shipId,
stateChanges.newBuildName
);
} else {
stateChanges.savedCode = null;
}
@@ -165,7 +183,9 @@ export default class OutfittingPage extends Page {
* @returns {string} the code for this ship
*/
_fullCode(ship, fuel, cargo) {
return `${ship.toString()}.${LZString.compressToBase64(this._controlCode(fuel, cargo))}`;
return `${ship.toString()}.${LZString.compressToBase64(
this._controlCode(fuel, cargo)
)}`;
}
/**
@@ -185,7 +205,11 @@ export default class OutfittingPage extends Page {
let boost = false;
let fuel = ship.fuelCapacity;
let cargo = ship.cargoCapacity;
let opponent = new Ship('eagle', Ships['eagle'].properties, Ships['eagle'].slots).buildWith(Ships['eagle'].defaults);
let opponent = new Ship(
'eagle',
Ships['eagle'].properties,
Ships['eagle'].slots
).buildWith(Ships['eagle'].defaults);
let opponentSys = 2;
let opponentEng = 2;
let opponentWep = 2;
@@ -197,16 +221,25 @@ export default class OutfittingPage extends Page {
const parts = code.split('.');
if (parts.length >= 5) {
// We have control information in the code
const control = LZString.decompressFromBase64(Utils.fromUrlSafe(parts[4])).split('/');
sys = parseFloat(control[0]) || sys;
eng = parseFloat(control[1]) || eng;
wep = parseFloat(control[2]) || wep;
const control = LZString.decompressFromBase64(
Utils.fromUrlSafe(parts[4])
).split('/');
sys = parseFloat(control[0]);
eng = parseFloat(control[1]);
wep = parseFloat(control[2]);
if (sys + eng + wep > 6) {
sys = eng = wep = 2;
}
boost = control[3] == 1 ? true : false;
fuel = parseFloat(control[4]) || fuel;
cargo = parseInt(control[5]) || cargo;
if (control[6]) {
const shipId = control[6];
opponent = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots);
opponent = new Ship(
shipId,
Ships[shipId].properties,
Ships[shipId].slots
);
if (control[7] && Persist.getBuild(shipId, control[7])) {
// Ship is a particular build
const opponentCode = Persist.getBuild(shipId, control[7]);
@@ -216,7 +249,9 @@ export default class OutfittingPage extends Page {
// Obtain opponent's sys/eng/wep pips from their code
const opponentParts = opponentCode.split('.');
if (opponentParts.length >= 5) {
const opponentControl = LZString.decompressFromBase64(Utils.fromUrlSafe(opponentParts[4])).split('/');
const opponentControl = LZString.decompressFromBase64(
Utils.fromUrlSafe(opponentParts[4])
).split('/');
opponentSys = parseFloat(opponentControl[0]) || opponentSys;
opponentEng = parseFloat(opponentControl[1]) || opponentEng;
opponentWep = parseFloat(opponentControl[2]) || opponentWep;
@@ -237,7 +272,23 @@ export default class OutfittingPage extends Page {
}
}
return { sys, eng, wep, mcSys, mcEng, mcWep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange };
return {
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
opponentSys,
opponentEng,
opponentWep,
engagementRange
};
}
/**
@@ -252,7 +303,9 @@ export default class OutfittingPage extends Page {
* @param {number} mcWep WEP pips from multi-crew
*/
_pipsUpdated(sys, eng, wep, mcSys, mcEng, mcWep) {
this.setState({ sys, eng, wep, mcSys, mcEng, mcWep }, () => this._updateRouteOnControlChange());
this.setState({ sys, eng, wep, mcSys, mcEng, mcWep }, () =>
this._updateRouteOnControlChange()
);
}
/**
@@ -260,7 +313,7 @@ export default class OutfittingPage extends Page {
* @param {boolean} boost true if boosting
*/
_boostUpdated(boost) {
this.setState({ boost }, () => this._updateRouteOnControlChange());
this.setState({ boost }, () => this._updateRouteOnControlChange());
}
/**
@@ -268,7 +321,7 @@ export default class OutfittingPage extends Page {
* @param {number} fuel the amount of fuel, in T
*/
_fuelUpdated(fuel) {
this.setState({ fuel }, () => this._updateRouteOnControlChange());
this.setState({ fuel }, () => this._updateRouteOnControlChange());
}
/**
@@ -276,7 +329,7 @@ export default class OutfittingPage extends Page {
* @param {number} cargo the amount of cargo, in T
*/
_cargoUpdated(cargo) {
this.setState({ cargo }, () => this._updateRouteOnControlChange());
this.setState({ cargo }, () => this._updateRouteOnControlChange());
}
/**
@@ -284,7 +337,9 @@ export default class OutfittingPage extends Page {
* @param {number} engagementRange the engagement range, in m
*/
_engagementRangeUpdated(engagementRange) {
this.setState({ engagementRange }, () => this._updateRouteOnControlChange());
this.setState({ engagementRange }, () =>
this._updateRouteOnControlChange()
);
}
/**
@@ -293,7 +348,11 @@ export default class OutfittingPage extends Page {
* @param {string} opponentBuild the name of the opponent's build
*/
_opponentUpdated(opponent, opponentBuild) {
const opponentShip = new Ship(opponent, Ships[opponent].properties, Ships[opponent].slots);
const opponentShip = new Ship(
opponent,
Ships[opponent].properties,
Ships[opponent].slots
);
let opponentSys = this.state.opponentSys;
let opponentEng = this.state.opponentEng;
let opponentWep = this.state.opponentWep;
@@ -301,9 +360,13 @@ export default class OutfittingPage extends Page {
// Ship is a particular build
opponentShip.buildFrom(Persist.getBuild(opponent, opponentBuild));
// Set pips for opponent
const opponentParts = Persist.getBuild(opponent, opponentBuild).split('.');
const opponentParts = Persist.getBuild(opponent, opponentBuild).split(
'.'
);
if (opponentParts.length >= 5) {
const opponentControl = LZString.decompressFromBase64(Utils.fromUrlSafe(opponentParts[4])).split('/');
const opponentControl = LZString.decompressFromBase64(
Utils.fromUrlSafe(opponentParts[4])
).split('/');
opponentSys = parseFloat(opponentControl[0]);
opponentEng = parseFloat(opponentControl[1]);
opponentWep = parseFloat(opponentControl[2]);
@@ -316,7 +379,16 @@ export default class OutfittingPage extends Page {
opponentWep = 2;
}
this.setState({ opponent: opponentShip, opponentBuild, opponentSys, opponentEng, opponentWep }, () => this._updateRouteOnControlChange());
this.setState(
{
opponent: opponentShip,
opponentBuild,
opponentSys,
opponentEng,
opponentWep
},
() => this._updateRouteOnControlChange()
);
}
/**
@@ -326,8 +398,22 @@ export default class OutfittingPage extends Page {
* @returns {string} The control code
*/
_controlCode(fuel, cargo) {
const { sys, eng, wep, mcSys, mcEng, mcWep, boost, opponent, opponentBuild, engagementRange } = this.state;
const code = `${sys}/${eng}/${wep}/${boost ? 1 : 0}/${fuel || this.state.fuel}/${cargo || this.state.cargo}/${opponent.id}/${opponentBuild ? opponentBuild : ''}/${engagementRange}/${mcSys}/${mcEng}/${mcWep}`;
const {
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
opponent,
opponentBuild,
engagementRange
} = this.state;
const code = `${sys}/${eng}/${wep}/${boost ? 1 : 0}/${fuel ||
this.state.fuel}/${cargo || this.state.cargo}/${opponent.id}/${
opponentBuild ? opponentBuild : ''
}/${engagementRange}/${mcSys}/${mcEng}/${mcWep}`;
return code;
}
@@ -338,27 +424,44 @@ export default class OutfittingPage extends Page {
const { ship, buildName, newBuildName, shipId } = this.state;
// If this is a stock ship the code won't be set, so ensure that we have it
const code = this.state.code || ship.toString();
const code = this.state.code || ship.toString();
Persist.saveBuild(shipId, newBuildName, code);
this._updateRoute(shipId, newBuildName, code);
let opponent, opponentBuild, opponentSys, opponentEng, opponentWep;
if (shipId === this.state.opponent.id && buildName === this.state.opponentBuild) {
if (
shipId === this.state.opponent.id &&
buildName === this.state.opponentBuild
) {
// This is a save of our current opponent build; update it
opponentBuild = newBuildName;
opponent = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots).buildFrom(code);
opponent = new Ship(
shipId,
Ships[shipId].properties,
Ships[shipId].slots
).buildFrom(code);
opponentSys = this.state.sys;
opponentEng = this.state.eng;
opponentWep = this.state.wep;
} else {
opponentBuild = this.state.opponentBuild;
opponent = this.state.opponent;
opponent = this.state.opponent;
opponentSys = this.state.opponentSys;
opponentEng = this.state.opponentEng;
opponentWep = this.state.opponentWep;
}
this.setState({ buildName: newBuildName, code, savedCode: code, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, title: this._getTitle(newBuildName) });
this.setState({
buildName: newBuildName,
code,
savedCode: code,
opponent,
opponentBuild,
opponentSys,
opponentEng,
opponentWep,
title: this._getTitle(newBuildName)
});
}
/**
@@ -370,7 +473,12 @@ export default class OutfittingPage extends Page {
Persist.deleteBuild(shipId, buildName);
Persist.saveBuild(shipId, newBuildName, code);
this._updateRoute(shipId, newBuildName, code);
this.setState({ buildName: newBuildName, code, savedCode: code, opponentBuild: newBuildName });
this.setState({
buildName: newBuildName,
code,
savedCode: code,
opponentBuild: newBuildName
});
}
}
@@ -390,9 +498,7 @@ export default class OutfittingPage extends Page {
ship.buildWith(Ships[shipId].defaults);
// Reset controls
const code = ship.toString();
const { sys, eng, wep, mcSys, mcEng, mcWep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = this._obtainControlFromCode(ship, code);
// Update state, and refresh the ship
this.setState({
const {
sys,
eng,
wep,
@@ -405,7 +511,25 @@ export default class OutfittingPage extends Page {
opponent,
opponentBuild,
engagementRange
}, () => this._updateRoute(shipId, buildName, code));
} = this._obtainControlFromCode(ship, code);
// Update state, and refresh the ship
this.setState(
{
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
engagementRange
},
() => this._updateRoute(shipId, buildName, code)
);
}
/**
@@ -416,7 +540,10 @@ export default class OutfittingPage extends Page {
Persist.deleteBuild(shipId, buildName);
let opponentBuild;
if (shipId === this.state.opponent.id && buildName === this.state.opponentBuild) {
if (
shipId === this.state.opponent.id &&
buildName === this.state.opponentBuild
) {
// Our current opponent has been deleted; revert to stock
opponentBuild = null;
} else {
@@ -433,11 +560,13 @@ export default class OutfittingPage extends Page {
_exportBuild() {
let translate = this.context.language.translate;
let { buildName, ship } = this.state;
this.context.showModal(<ModalExport
title={(buildName || ship.name) + ' ' + translate('export')}
description={translate('PHRASE_EXPORT_DESC')}
data={toDetailedBuild(buildName, ship, ship.toString())}
/>);
this.context.showModal(
<ModalExport
title={(buildName || ship.name) + ' ' + translate('export')}
description={translate('PHRASE_EXPORT_DESC')}
data={toDetailedBuild(buildName, ship, ship.toString())}
/>
);
}
/**
@@ -450,9 +579,7 @@ export default class OutfittingPage extends Page {
this.state.ship.buildFrom(code);
// Obtain controls from the code
const { sys, eng, wep, mcSys, mcEng, mcWep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = this._obtainControlFromCode(ship, code);
// Update state, and refresh the route when complete
this.setState({
const {
sys,
eng,
wep,
@@ -465,7 +592,25 @@ export default class OutfittingPage extends Page {
opponent,
opponentBuild,
engagementRange
}, () => this._updateRoute(shipId, buildName, code));
} = this._obtainControlFromCode(ship, code);
// Update state, and refresh the route when complete
this.setState(
{
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
engagementRange
},
() => this._updateRoute(shipId, buildName, code)
);
}
/**
@@ -481,8 +626,14 @@ export default class OutfittingPage extends Page {
}
const code = this._fullCode(ship, fuel, cargo);
// Only update the state if this really has been updated
if (this.state.code != code || this.state.cargo != cargo || this.state.fuel != fuel) {
this.setState({ code, cargo, fuel }, () => this._updateRoute(shipId, buildName, code));
if (
this.state.code != code ||
this.state.cargo != cargo ||
this.state.fuel != fuel
) {
this.setState({ code, cargo, fuel }, () =>
this._updateRoute(shipId, buildName, code)
);
}
}
@@ -502,7 +653,8 @@ export default class OutfittingPage extends Page {
* @param {Object} nextContext Incoming/Next conext
*/
componentWillReceiveProps(nextProps, nextContext) {
if (this.context.route !== nextContext.route) { // Only reinit state if the route has changed
if (this.context.route !== nextContext.route) {
// Only reinit state if the route has changed
this.setState(this._initState(nextProps, nextContext));
}
}
@@ -525,7 +677,7 @@ export default class OutfittingPage extends Page {
* Generates the short URL
*/
_genShortlink() {
this.context.showModal(<ModalPermalink url={window.location.href}/>);
this.context.showModal(<ModalPermalink url={window.location.href} />);
}
/**
@@ -542,7 +694,7 @@ export default class OutfittingPage extends Page {
data.ShipName = ship.id;
data.Ship = ship.id;
console.log(data);
this.context.showModal(<ModalOrbis ship={data}/>);
this.context.showModal(<ModalOrbis ship={data} />);
}
/**
@@ -553,17 +705,23 @@ export default class OutfittingPage extends Page {
const shipId = Ships[ship.id].eddbID;
// Provide unique list of non-PP module EDDB IDs
const modIds = ship.internal.concat(ship.bulkheads, ship.standard, ship.hardpoints).filter(slot => slot !== null && slot.m !== null && !slot.m.pp).map(slot => slot.m.eddbID).filter((v, i, a) => a.indexOf(v) === i);
const modIds = ship.internal
.concat(ship.bulkheads, ship.standard, ship.hardpoints)
.filter(slot => slot !== null && slot.m !== null && !slot.m.pp)
.map(slot => slot.m.eddbID)
.filter((v, i, a) => a.indexOf(v) === i);
// Open up the relevant URL
window.open('https://eddb.io/station?s=' + shipId + '&m=' + modIds.join(','));
window.open(
'https://eddb.io/station?s=' + shipId + '&m=' + modIds.join(',')
);
}
/**
* Generates the shopping list
*/
_genShoppingList() {
this.context.showModal(<ModalShoppingList ship={this.state.ship}/>);
this.context.showModal(<ModalShoppingList ship={this.state.ship} />);
}
/**
@@ -573,8 +731,9 @@ export default class OutfittingPage extends Page {
_keyDown(e) {
// .keyCode will eventually be replaced with .key
switch (e.keyCode) {
case 69: // 'e'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + e
case 69: // 'e'
if (e.ctrlKey || e.metaKey) {
// CTRL/CMD + e
e.preventDefault();
this._exportBuild();
}
@@ -590,7 +749,28 @@ export default class OutfittingPage extends Page {
let state = this.state,
{ language, termtip, tooltip, sizeRatio, onWindowResize } = this.context,
{ translate, units, formats } = language,
{ ship, code, savedCode, buildName, newBuildName, sys, eng, wep, mcSys, mcEng, mcWep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange } = state,
{
ship,
code,
savedCode,
buildName,
newBuildName,
sys,
eng,
wep,
mcSys,
mcEng,
mcWep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
opponentSys,
opponentEng,
opponentWep,
engagementRange
} = state,
hide = tooltip.bind(null, null),
menu = this.props.currentMenu,
shipUpdated = this._shipUpdated,
@@ -608,11 +788,15 @@ export default class OutfittingPage extends Page {
const _pStr = `${ship.getPowerEnabledString()}${ship.getPowerPrioritiesString()}`;
const _mStr = ship.getModificationsString();
const standardSlotMarker = `${ship.name}${_sStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`;
const standardSlotMarker = `${ship.name}${_sStr}${_pStr}${_mStr}${
ship.ladenMass
}${cargo}${fuel}`;
const internalSlotMarker = `${ship.name}${_iStr}${_pStr}${_mStr}`;
const hardpointsSlotMarker = `${ship.name}${_hStr}${_pStr}${_mStr}`;
const boostMarker = `${ship.canBoost(cargo, fuel)}`;
const shipSummaryMarker = `${ship.name}${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`;
const shipSummaryMarker = `${
ship.name
}${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`;
const requirements = Ships[ship.id].requirements;
let requirementElements = [];
@@ -624,94 +808,275 @@ export default class OutfittingPage extends Page {
*/
function renderRequirement(className, textKey, tooltipTextKey) {
if (textKey.startsWith('empire') || textKey.startsWith('federation')) {
requirementElements.push(<div key={textKey} className={className} onMouseEnter={termtip.bind(null, tooltipTextKey)} onMouseLeave={hide}><a href={textKey.startsWith('empire') ? 'http://elite-dangerous.wikia.com/wiki/Empire/Ranks' : 'http://elite-dangerous.wikia.com/wiki/Federation/Ranks'} target="_blank" rel="noopener">{translate(textKey)}</a></div>);
requirementElements.push(
<div
key={textKey}
className={className}
onMouseEnter={termtip.bind(null, tooltipTextKey)}
onMouseLeave={hide}
>
<a
href={
textKey.startsWith('empire') ?
'http://elite-dangerous.wikia.com/wiki/Empire/Ranks' :
'http://elite-dangerous.wikia.com/wiki/Federation/Ranks'
}
target="_blank"
rel="noopener"
>
{translate(textKey)}
</a>
</div>
);
} else {
requirementElements.push(<div key={textKey} className={className} onMouseEnter={termtip.bind(null, tooltipTextKey)} onMouseLeave={hide}>{translate(textKey)}</div>);
requirementElements.push(
<div
key={textKey}
className={className}
onMouseEnter={termtip.bind(null, tooltipTextKey)}
onMouseLeave={hide}
>
{translate(textKey)}
</div>
);
}
}
if (requirements) {
requirements.federationRank && renderRequirement('federation', 'federation rank ' + requirements.federationRank, 'federation rank required');
requirements.empireRank && renderRequirement('empire', 'empire rank ' + requirements.empireRank, 'empire rank required');
requirements.horizons && renderRequirement('horizons', 'horizons', 'horizons required');
requirements.horizonsEarlyAdoption && renderRequirement('horizons', 'horizons early adoption', 'horizons early adoption required');
requirements.federationRank &&
renderRequirement(
'federation',
'federation rank ' + requirements.federationRank,
'federation rank required'
);
requirements.empireRank &&
renderRequirement(
'empire',
'empire rank ' + requirements.empireRank,
'empire rank required'
);
requirements.horizons &&
renderRequirement('horizons', 'horizons', 'horizons required');
requirements.horizonsEarlyAdoption &&
renderRequirement(
'horizons',
'horizons early adoption',
'horizons early adoption required'
);
}
return (
<div id='outfit' className={'page'} style={{ fontSize: (sizeRatio * 0.9) + 'em' }}>
<div id='overview'>
<div
id="outfit"
className={'page'}
style={{ fontSize: sizeRatio * 0.9 + 'em' }}
>
<div id="overview">
<h1>{ship.name}</h1>
<div id='requirements'>{requirementElements}</div>
<div id='build'>
<input value={newBuildName || ''} onChange={this._buildNameChange} placeholder={translate('Enter Name')} maxLength={50} />
<button onClick={canSave && this._saveBuild} disabled={!canSave} onMouseOver={termtip.bind(null, 'save')} onMouseOut={hide}>
<FloppyDisk className='lg' />
<div id="requirements">{requirementElements}</div>
<div id="build">
<input
value={newBuildName || ''}
onChange={this._buildNameChange}
placeholder={translate('Enter Name')}
maxLength={50}
/>
<button
onClick={canSave && this._saveBuild}
disabled={!canSave}
onMouseOver={termtip.bind(null, 'save')}
onMouseOut={hide}
>
<FloppyDisk className="lg" />
</button>
<button onClick={canRename && this._renameBuild} disabled={!canRename} onMouseOver={termtip.bind(null, 'rename')} onMouseOut={hide}>
<span style={{ textTransform: 'none', fontSize: '1.8em' }}>a|</span>
<button
onClick={canRename && this._renameBuild}
disabled={!canRename}
onMouseOver={termtip.bind(null, 'rename')}
onMouseOut={hide}
>
<span style={{ textTransform: 'none', fontSize: '1.8em' }}>
a|
</span>
</button>
<button onClick={canReload && this._reloadBuild} disabled={!canReload} onMouseOver={termtip.bind(null, 'reload')} onMouseOut={hide}>
<Reload className='lg'/>
<button
onClick={canReload && this._reloadBuild}
disabled={!canReload}
onMouseOver={termtip.bind(null, 'reload')}
onMouseOut={hide}
>
<Reload className="lg" />
</button>
<button className={'danger'} onClick={savedCode && this._deleteBuild} disabled={!savedCode} onMouseOver={termtip.bind(null, 'delete')} onMouseOut={hide}>
<Bin className='lg'/>
<button
className={'danger'}
onClick={savedCode && this._deleteBuild}
disabled={!savedCode}
onMouseOver={termtip.bind(null, 'delete')}
onMouseOut={hide}
>
<Bin className="lg" />
</button>
<button onClick={code && this._resetBuild} disabled={!code} onMouseOver={termtip.bind(null, 'reset')} onMouseOut={hide}>
<Switch className='lg'/>
<button
onClick={code && this._resetBuild}
disabled={!code}
onMouseOver={termtip.bind(null, 'reset')}
onMouseOut={hide}
>
<Switch className="lg" />
</button>
<button onClick={buildName && this._exportBuild} disabled={!buildName} onMouseOver={termtip.bind(null, 'export')} onMouseOut={hide}>
<Download className='lg'/>
<button
onClick={buildName && this._exportBuild}
disabled={!buildName}
onMouseOver={termtip.bind(null, 'export')}
onMouseOut={hide}
>
<Download className="lg" />
</button>
<button onClick={this._eddbShoppingList} onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_LIST')} onMouseOut={hide}>
<ShoppingIcon className='lg' />
<button
onClick={this._eddbShoppingList}
onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_LIST')}
onMouseOut={hide}
>
<ShoppingIcon className="lg" />
</button>
<button onClick={this._genShortlink} onMouseOver={termtip.bind(null, 'shortlink')} onMouseOut={hide}>
<LinkIcon className='lg' />
<button
onClick={this._genShortlink}
onMouseOver={termtip.bind(null, 'shortlink')}
onMouseOut={hide}
>
<LinkIcon className="lg" />
</button>
<button onClick={this._genOrbis} onMouseOver={termtip.bind(null, 'PHASE_UPLOAD_ORBIS')} onMouseOut={hide}>
<OrbisIcon className='lg' />
<button
onClick={this._genOrbis}
onMouseOver={termtip.bind(null, 'PHASE_UPLOAD_ORBIS')}
onMouseOut={hide}
>
<OrbisIcon className="lg" />
</button>
<button onClick={this._genShoppingList} onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_MATS')} onMouseOut={hide}>
<MatIcon className='lg' />
<button
onClick={this._genShoppingList}
onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_MATS')}
onMouseOut={hide}
>
<MatIcon className="lg" />
</button>
</div>
</div>
{/* Main tables */}
<ShipSummaryTable ship={ship} fuel={fuel} cargo={cargo} marker={shipSummaryMarker} pips={{ sys: this.state.sys, wep: this.state.wep, eng: this.state.eng }} />
<StandardSlotSection ship={ship} fuel={fuel} cargo={cargo} code={standardSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} sectionMenuRefs={this._sectionMenuRefs}/>
<InternalSlotSection ship={ship} code={internalSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} sectionMenuRefs={this._sectionMenuRefs}/>
<HardpointSlotSection ship={ship} code={hardpointsSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} sectionMenuRefs={this._sectionMenuRefs}/>
<UtilitySlotSection ship={ship} code={hardpointsSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} sectionMenuRefs={this._sectionMenuRefs}/>
<ShipSummaryTable
ship={ship}
fuel={fuel}
cargo={cargo}
marker={shipSummaryMarker}
pips={{
sys: this.state.sys,
wep: this.state.wep,
eng: this.state.eng
}}
/>
<StandardSlotSection
ship={ship}
fuel={fuel}
cargo={cargo}
code={standardSlotMarker}
onChange={shipUpdated}
onCargoChange={this._cargoUpdated}
onFuelChange={this._fuelUpdated}
currentMenu={menu}
sectionMenuRefs={this._sectionMenuRefs}
/>
<InternalSlotSection
ship={ship}
code={internalSlotMarker}
onChange={shipUpdated}
onCargoChange={this._cargoUpdated}
onFuelChange={this._fuelUpdated}
currentMenu={menu}
sectionMenuRefs={this._sectionMenuRefs}
/>
<HardpointSlotSection
ship={ship}
code={hardpointsSlotMarker}
onChange={shipUpdated}
onCargoChange={this._cargoUpdated}
onFuelChange={this._fuelUpdated}
currentMenu={menu}
sectionMenuRefs={this._sectionMenuRefs}
/>
<UtilitySlotSection
ship={ship}
code={hardpointsSlotMarker}
onChange={shipUpdated}
onCargoChange={this._cargoUpdated}
onFuelChange={this._fuelUpdated}
currentMenu={menu}
sectionMenuRefs={this._sectionMenuRefs}
/>
{/* Control of ship and opponent */}
<div className='group quarter'>
<div className='group half'>
<h2 style={{ verticalAlign: 'middle', textAlign: 'left' }}>{translate('ship control')}</h2>
<div className="group quarter">
<div className="group half">
<h2 style={{ verticalAlign: 'middle', textAlign: 'left' }}>
{translate('ship control')}
</h2>
</div>
<div className='group half'>
<Boost marker={boostMarker} ship={ship} boost={boost} onChange={this._boostUpdated} />
<div className="group half">
<Boost
marker={boostMarker}
ship={ship}
boost={boost}
onChange={this._boostUpdated}
/>
</div>
</div>
<div className='group quarter'>
<Pips sys={sys} eng={eng} wep={wep} mcSys={mcSys} mcEng={mcEng} mcWep={mcWep} onChange={this._pipsUpdated} />
<div className="group quarter">
<Pips
sys={sys}
eng={eng}
wep={wep}
mcSys={mcSys}
mcEng={mcEng}
mcWep={mcWep}
onChange={this._pipsUpdated}
/>
</div>
<div className='group quarter'>
<Fuel fuelCapacity={ship.fuelCapacity} fuel={fuel} onChange={this._fuelUpdated}/>
<div className="group quarter">
<Fuel
fuelCapacity={ship.fuelCapacity}
fuel={fuel}
onChange={this._fuelUpdated}
/>
</div>
<div className='group quarter'>
{ ship.cargoCapacity > 0 ? <Cargo cargoCapacity={ship.cargoCapacity} cargo={cargo} onChange={this._cargoUpdated}/> : null }
<div className="group quarter">
{ship.cargoCapacity > 0 ? (
<Cargo
cargoCapacity={ship.cargoCapacity}
cargo={cargo}
onChange={this._cargoUpdated}
/>
) : null}
</div>
<div className='group half'>
<div className='group quarter'>
<h2 style={{ verticalAlign: 'middle', textAlign: 'left' }}>{translate('opponent')}</h2>
<div className="group half">
<div className="group quarter">
<h2 style={{ verticalAlign: 'middle', textAlign: 'left' }}>
{translate('opponent')}
</h2>
</div>
<div className='group threequarters'>
<ShipPicker ship={opponent.id} build={opponentBuild} onChange={this._opponentUpdated}/>
<div className="group threequarters">
<ShipPicker
ship={opponent.id}
build={opponentBuild}
onChange={this._opponentUpdated}
/>
</div>
</div>
<div className='group half'>
<EngagementRange ship={ship} engagementRange={engagementRange} onChange={this._engagementRangeUpdated}/>
<div className="group half">
<EngagementRange
ship={ship}
engagementRange={engagementRange}
onChange={this._engagementRangeUpdated}
/>
</div>
{/* Tabbed subpages */}

View File

@@ -70,6 +70,13 @@ export default class Page extends React.Component {
document.title = this.state.title || 'Coriolis';
}
/**
* Update the window title upon mount
*/
componentDidMount() {
document.title = this.state.title || 'Coriolis';
}
/**
* Updates the title upon change
* @param {Object} newProps Incoming properties

View File

@@ -22,9 +22,11 @@ function countHp(slot) {
*/
function countInt(slot) {
let crEligible = !slot.eligible || slot.eligible.cr;
this.int[slot.maxClass - 1]++; // Subtract 1 since there is no Class 0 Internal compartment
this.int[slot.maxClass - 1]++; // Subtract 1 since there is no Class 0 Internal compartment
this.intCount++;
this.maxCargo += crEligible ? ModuleUtils.findInternal('cr', slot.maxClass, 'E').cargo : 0;
this.maxCargo += crEligible ?
ModuleUtils.findInternal('cr', slot.maxClass, 'E').cargo :
0;
// if no eligiblity, then assume pce
let passSlotType = null;
@@ -42,7 +44,9 @@ function countInt(slot) {
passSlotType = 'pcq';
passSlotRating = 'B';
}
let passengerBay = passSlotType ? ModuleUtils.findMaxInternal(passSlotType, slot.maxClass, passSlotRating) : null;
let passengerBay = passSlotType ?
ModuleUtils.findMaxInternal(passSlotType, slot.maxClass, passSlotRating) :
null;
this.maxPassengers += passengerBay ? passengerBay.passengers : 0;
}
@@ -57,23 +61,27 @@ function shipSummary(shipId, shipData) {
id: shipId,
hpCount: 0,
intCount: 0,
beta: shipData.beta,
maxCargo: 0,
maxPassengers: 0,
hp: [0, 0, 0, 0, 0], // Utility, Small, Medium, Large, Huge
int: [0, 0, 0, 0, 0, 0, 0, 0], // Sizes 1 - 8
standard: shipData.slots.standard,
agility: shipData.properties.pitch + shipData.properties.yaw + shipData.properties.roll
agility:
shipData.properties.pitch +
shipData.properties.yaw +
shipData.properties.roll
};
Object.assign(summary, shipData.properties);
let ship = new Ship(shipId, shipData.properties, shipData.slots);
// Build Ship
ship.buildWith(shipData.defaults); // Populate with stock/default components
ship.buildWith(shipData.defaults); // Populate with stock/default components
ship.hardpoints.forEach(countHp.bind(summary)); // Count Hardpoints by class
ship.internal.forEach(countInt.bind(summary)); // Count Internal Compartments by class
summary.retailCost = ship.totalCost; // Record Stock/Default/retail cost
ship.optimizeMass({ pd: '1D' }); // Optimize Mass with 1D PD for maximum possible jump range
summary.maxJumpRange = ship.unladenRange; // Record Jump Range
ship.internal.forEach(countInt.bind(summary)); // Count Internal Compartments by class
summary.retailCost = ship.totalCost; // Record Stock/Default/retail cost
ship.optimizeMass({ pd: '1D' }); // Optimize Mass with 1D PD for maximum possible jump range
summary.maxJumpRange = ship.unladenRange; // Record Jump Range
// Best thrusters
let th;
@@ -97,7 +105,6 @@ function shipSummary(shipId, shipData) {
* The Shipyard summary page
*/
export default class ShipyardPage extends Page {
static cachedShipSummaries = null;
/**
@@ -145,12 +152,15 @@ export default class ShipyardPage extends Page {
shipPredicateIndex = undefined;
}
if (this.state.shipPredicate == shipPredicate && this.state.shipPredicateIndex == shipPredicateIndex) {
if (
this.state.shipPredicate == shipPredicate &&
this.state.shipPredicateIndex == shipPredicateIndex
) {
shipDesc = !shipDesc;
}
this.setState({ shipPredicate, shipDesc, shipPredicateIndex });
};
}
/**
* Generate the table row summary for the ship
@@ -159,56 +169,59 @@ export default class ShipyardPage extends Page {
* @param {Object} u Localized unit map
* @param {Function} fInt Localized integer formatter
* @param {Function} fRound Localized round formatter
* @param {Boolean} highlight Should this row be highlighted
* @return {React.Component} Table Row
*/
_shipRowElement(s, translate, u, fInt, fRound, highlight) {
_shipRowElement(s, translate, u, fInt, fRound) {
let noTouch = this.context.noTouch;
return <tr
return (
<tr
key={s.id}
style={{ height: '1.5em' }}
className={cn({ highlighted: noTouch && this.state.shipId === s.id, alt: highlight })}
className={cn({
highlighted: noTouch && this.state.shipId === s.id,
})}
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
>
<td className='ri'>{s.manufacturer}</td>
<td className='ri'>{fInt(s.retailCost)}</td>
<td className='ri cap'>{translate(SizeMap[s.class])}</td>
<td className='ri'>{fInt(s.crew)}</td>
<td className='ri'>{s.masslock}</td>
<td className='ri'>{fInt(s.agility)}</td>
<td className='ri'>{fInt(s.hardness)}</td>
<td className='ri'>{fInt(s.hullMass)}</td>
<td className='ri'>{fInt(s.speed)}</td>
<td className='ri'>{fInt(s.boost)}</td>
<td className='ri'>{fInt(s.baseArmour)}</td>
<td className='ri'>{fInt(s.baseShieldStrength)}</td>
<td className='ri'>{fInt(s.topSpeed)}</td>
<td className='ri'>{fInt(s.topBoost)}</td>
<td className='ri'>{fRound(s.maxJumpRange)}</td>
<td className='ri'>{fInt(s.maxCargo)}</td>
<td className='ri'>{fInt(s.maxPassengers)}</td>
<td className='cn'>{s.standard[0]}</td>
<td className='cn'>{s.standard[1]}</td>
<td className='cn'>{s.standard[2]}</td>
<td className='cn'>{s.standard[3]}</td>
<td className='cn'>{s.standard[4]}</td>
<td className='cn'>{s.standard[5]}</td>
<td className='cn'>{s.standard[6]}</td>
<td className={cn({ disabled: !s.hp[1] })}>{s.hp[1]}</td>
<td className={cn({ disabled: !s.hp[2] })}>{s.hp[2]}</td>
<td className={cn({ disabled: !s.hp[3] })}>{s.hp[3]}</td>
<td className={cn({ disabled: !s.hp[4] })}>{s.hp[4]}</td>
<td className={cn({ disabled: !s.hp[0] })}>{s.hp[0]}</td>
<td className={cn({ disabled: !s.int[0] })}>{s.int[0]}</td>
<td className={cn({ disabled: !s.int[1] })}>{s.int[1]}</td>
<td className={cn({ disabled: !s.int[2] })}>{s.int[2]}</td>
<td className={cn({ disabled: !s.int[3] })}>{s.int[3]}</td>
<td className={cn({ disabled: !s.int[4] })}>{s.int[4]}</td>
<td className={cn({ disabled: !s.int[5] })}>{s.int[5]}</td>
<td className={cn({ disabled: !s.int[6] })}>{s.int[6]}</td>
<td className={cn({ disabled: !s.int[7] })}>{s.int[7]}</td>
</tr>;
<td className="ri">{s.manufacturer}</td>
<td className="ri">{fInt(s.retailCost)}</td>
<td className="ri cap">{translate(SizeMap[s.class])}</td>
<td className="ri">{fInt(s.crew)}</td>
<td className="ri">{s.masslock}</td>
<td className="ri">{fInt(s.agility)}</td>
<td className="ri">{fInt(s.hardness)}</td>
<td className="ri">{fInt(s.hullMass)}</td>
<td className="ri">{fInt(s.speed)}</td>
<td className="ri">{fInt(s.boost)}</td>
<td className="ri">{fInt(s.baseArmour)}</td>
<td className="ri">{fInt(s.baseShieldStrength)}</td>
<td className="ri">{fInt(s.topSpeed)}</td>
<td className="ri">{fInt(s.topBoost)}</td>
<td className="ri">{fRound(s.maxJumpRange)}</td>
<td className="ri">{fInt(s.maxCargo)}</td>
<td className="ri">{fInt(s.maxPassengers)}</td>
<td className="cn">{s.standard[0]}</td>
<td className="cn">{s.standard[1]}</td>
<td className="cn">{s.standard[2]}</td>
<td className="cn">{s.standard[3]}</td>
<td className="cn">{s.standard[4]}</td>
<td className="cn">{s.standard[5]}</td>
<td className="cn">{s.standard[6]}</td>
<td className={cn({ disabled: !s.hp[1] })}>{s.hp[1]}</td>
<td className={cn({ disabled: !s.hp[2] })}>{s.hp[2]}</td>
<td className={cn({ disabled: !s.hp[3] })}>{s.hp[3]}</td>
<td className={cn({ disabled: !s.hp[4] })}>{s.hp[4]}</td>
<td className={cn({ disabled: !s.hp[0] })}>{s.hp[0]}</td>
<td className={cn({ disabled: !s.int[0] })}>{s.int[0]}</td>
<td className={cn({ disabled: !s.int[1] })}>{s.int[1]}</td>
<td className={cn({ disabled: !s.int[2] })}>{s.int[2]}</td>
<td className={cn({ disabled: !s.int[3] })}>{s.int[3]}</td>
<td className={cn({ disabled: !s.int[4] })}>{s.int[4]}</td>
<td className={cn({ disabled: !s.int[5] })}>{s.int[5]}</td>
<td className={cn({ disabled: !s.int[6] })}>{s.int[6]}</td>
<td className={cn({ disabled: !s.int[7] })}>{s.int[7]}</td>
</tr>
);
}
/**
@@ -222,7 +235,8 @@ export default class ShipyardPage extends Page {
let fInt = formats.int;
let fRound = formats.round;
let { shipSummaries, shipPredicate, shipPredicateIndex } = this.state;
let sortShips = (predicate, index) => this._sortShips.bind(this, predicate, index);
let sortShips = (predicate, index) =>
this._sortShips.bind(this, predicate, index);
let filters = {
// 'class': { 1: 1, 2: 1}
@@ -239,7 +253,8 @@ export default class ShipyardPage extends Page {
// Sort shipsOverview
shipSummaries.sort((a, b) => {
let valA = a[shipPredicate], valB = b[shipPredicate];
let valA = a[shipPredicate],
valB = b[shipPredicate];
if (shipPredicateIndex != undefined) {
valA = valA[shipPredicateIndex];
@@ -252,7 +267,7 @@ export default class ShipyardPage extends Page {
valB = val;
}
if(valA == valB) {
if (valA == valB) {
if (a.name > b.name) {
return 1;
} else {
@@ -269,47 +284,55 @@ export default class ShipyardPage extends Page {
let shipRows = new Array(shipSummaries.length);
let detailRows = new Array(shipSummaries.length);
let lastShipSortValue = null;
let backgroundHighlight = false;
for (let s of shipSummaries) {
let shipSortValue = s[shipPredicate];
if(shipPredicateIndex != undefined) {
shipSortValue = shipSortValue[shipPredicateIndex];
}
if(shipSortValue != lastShipSortValue) {
backgroundHighlight = !backgroundHighlight;
lastShipSortValue = shipSortValue;
}
detailRows[i] = this._shipRowElement(s, translate, units, fInt, formats.f1, backgroundHighlight);
detailRows[i] = this._shipRowElement(
s,
translate,
units,
fInt,
formats.f1,
);
shipRows[i] = (
<tr
key={i}
style={{ height: '1.5em' }}
className={cn({ highlighted: noTouch && this.state.shipId === s.id, alt: backgroundHighlight })}
className={cn({
highlighted: noTouch && this.state.shipId === s.id,
})}
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
>
<td className='le'><Link href={'/outfit/' + s.id}>{s.name}</Link></td>
<td className="le">
<Link href={'/outfit/' + s.id}>{s.name} {s.beta === true ? '(Beta)' : null}</Link>
</td>
</tr>
);
i++;
}
return (
<div className='page' style={{ fontSize: sizeRatio + 'em' }}>
<div style={{ whiteSpace: 'nowrap', margin: '0 auto', fontSize: '0.8em', position: 'relative', display: 'inline-block', maxWidth: '100%' }}>
<div className="page" style={{ fontSize: sizeRatio + 'em' }}>
<div
style={{
whiteSpace: 'nowrap',
margin: '0 auto',
fontSize: '0.8em',
position: 'relative',
display: 'inline-block',
maxWidth: '100%'
}}
>
<table style={{ width: '12em', position: 'absolute', zIndex: 1 }}>
<thead>
<tr>
<th className='le rgt'>&nbsp;</th>
<th className="le rgt">&nbsp;</th>
</tr>
<tr className='main'>
<th className='sortable le rgt' onClick={sortShips('name')}>{translate('ship')}</th>
<tr className="main">
<th className="sortable le rgt" onClick={sortShips('name')}>
{translate('ship')}
</th>
</tr>
<tr>
<th className='le rgt invisible'>{units['m/s']}</th>
<th className="le rgt invisible">{units['m/s']}</th>
</tr>
</thead>
<tbody onMouseLeave={this._highlightShip.bind(this, null)}>
@@ -317,80 +340,261 @@ export default class ShipyardPage extends Page {
</tbody>
</table>
<div style={{ overflowX: 'scroll', maxWidth: '100%' }}>
<table style={{ marginLeft: 'calc(12em - 1px)', zIndex: 0 }}>
<thead>
<tr className='main'>
<th rowSpan={3} className='sortable' onClick={sortShips('manufacturer')}>{translate('manufacturer')}</th>
<th>&nbsp;</th>
<th rowSpan={3} className='sortable' onClick={sortShips('class')}>{translate('size')}</th>
<th rowSpan={3} className='sortable' onClick={sortShips('crew')}>{translate('crew')}</th>
<th rowSpan={3} className='sortable' onMouseEnter={termtip.bind(null, 'mass lock factor')} onMouseLeave={hide} onClick={sortShips('masslock')} >{translate('MLF')}</th>
<th rowSpan={3} className='sortable' onClick={sortShips('agility')}>{translate('agility')}</th>
<th rowSpan={3} className='sortable' onMouseEnter={termtip.bind(null, 'hardness')} onMouseLeave={hide} onClick={sortShips('hardness')}>{translate('hrd')}</th>
<th>&nbsp;</th>
<th colSpan={4}>{translate('base')}</th>
<th colSpan={5}>{translate('max')}</th>
<th className='lft' colSpan={7}></th>
<th className='lft' colSpan={5}></th>
<th className='lft' colSpan={8}></th>
</tr>
<tr>
<th className='sortable lft' onClick={sortShips('retailCost')}>{translate('cost')}</th>
<th className='sortable lft' onClick={sortShips('hullMass')}>{translate('hull')}</th>
<th className='sortable lft' onClick={sortShips('speed')}>{translate('speed')}</th>
<th className='sortable' onClick={sortShips('boost')}>{translate('boost')}</th>
<th className='sortable' onClick={sortShips('baseArmour')}>{translate('armour')}</th>
<th className='sortable' onClick={sortShips('baseShieldStrength')}>{translate('shields')}</th>
<table style={{ marginLeft: 'calc(12em - 1px)', zIndex: 0 }}>
<thead>
<tr className="main">
<th
rowSpan={3}
className="sortable"
onClick={sortShips('manufacturer')}
>
{translate('manufacturer')}
</th>
<th>&nbsp;</th>
<th
rowSpan={3}
className="sortable"
onClick={sortShips('class')}
>
{translate('size')}
</th>
<th
rowSpan={3}
className="sortable"
onClick={sortShips('crew')}
>
{translate('crew')}
</th>
<th
rowSpan={3}
className="sortable"
onMouseEnter={termtip.bind(null, 'mass lock factor')}
onMouseLeave={hide}
onClick={sortShips('masslock')}
>
{translate('MLF')}
</th>
<th
rowSpan={3}
className="sortable"
onClick={sortShips('agility')}
>
{translate('agility')}
</th>
<th
rowSpan={3}
className="sortable"
onMouseEnter={termtip.bind(null, 'hardness')}
onMouseLeave={hide}
onClick={sortShips('hardness')}
>
{translate('hrd')}
</th>
<th>&nbsp;</th>
<th colSpan={4}>{translate('base')}</th>
<th colSpan={5}>{translate('max')}</th>
<th className="lft" colSpan={7} />
<th className="lft" colSpan={5} />
<th className="lft" colSpan={8} />
</tr>
<tr>
<th
className="sortable lft"
onClick={sortShips('retailCost')}
>
{translate('cost')}
</th>
<th className="sortable lft" onClick={sortShips('hullMass')}>
{translate('hull')}
</th>
<th className="sortable lft" onClick={sortShips('speed')}>
{translate('speed')}
</th>
<th className="sortable" onClick={sortShips('boost')}>
{translate('boost')}
</th>
<th className="sortable" onClick={sortShips('baseArmour')}>
{translate('armour')}
</th>
<th
className="sortable"
onClick={sortShips('baseShieldStrength')}
>
{translate('shields')}
</th>
<th className='sortable lft' onClick={sortShips('topSpeed')}>{translate('speed')}</th>
<th className='sortable' onClick={sortShips('topBoost')}>{translate('boost')}</th>
<th className='sortable' onClick={sortShips('maxJumpRange')}>{translate('jump')}</th>
<th className='sortable' onClick={sortShips('maxCargo')}>{translate('cargo')}</th>
<th className='sortable' onClick={sortShips('maxPassengers')}>{translate('pax')}</th>
<th className="sortable lft" onClick={sortShips('topSpeed')}>
{translate('speed')}
</th>
<th className="sortable" onClick={sortShips('topBoost')}>
{translate('boost')}
</th>
<th className="sortable" onClick={sortShips('maxJumpRange')}>
{translate('jump')}
</th>
<th className="sortable" onClick={sortShips('maxCargo')}>
{translate('cargo')}
</th>
<th className="sortable" onClick={sortShips('maxPassengers')} onMouseEnter={termtip.bind(null, 'passenger capacity')}
onMouseLeave={hide}>
{translate('pax')}
</th>
<th className="lft" colSpan={7}>
{translate('core module classes')}
</th>
<th
colSpan={5}
className="sortable lft"
onClick={sortShips('hpCount')}
>
{translate('hardpoints')}
</th>
<th
colSpan={8}
className="sortable lft"
onClick={sortShips('intCount')}
>
{translate('internal compartments')}
</th>
</tr>
<tr>
<th
className="sortable lft"
onClick={sortShips('retailCost')}
>
{units.CR}
</th>
<th className="sortable lft" onClick={sortShips('hullMass')}>
{units.T}
</th>
<th className="sortable lft" onClick={sortShips('speed')}>
{units['m/s']}
</th>
<th className="sortable" onClick={sortShips('boost')}>
{units['m/s']}
</th>
<th>&nbsp;</th>
<th
className="sortable"
onClick={sortShips('baseShieldStrength')}
>
{units.MJ}
</th>
<th className="sortable lft" onClick={sortShips('topSpeed')}>
{units['m/s']}
</th>
<th className="sortable" onClick={sortShips('topBoost')}>
{units['m/s']}
</th>
<th className="sortable" onClick={sortShips('maxJumpRange')}>
{units.LY}
</th>
<th className="sortable" onClick={sortShips('maxCargo')}>
{units.T}
</th>
<th>&nbsp;</th>
<th
className="sortable lft"
onMouseEnter={termtip.bind(null, 'power plant')}
onMouseLeave={hide}
onClick={sortShips('standard', 0)}
>
{'pp'}
</th>
<th
className="sortable"
onMouseEnter={termtip.bind(null, 'thrusters')}
onMouseLeave={hide}
onClick={sortShips('standard', 1)}
>
{'th'}
</th>
<th
className="sortable"
onMouseEnter={termtip.bind(null, 'frame shift drive')}
onMouseLeave={hide}
onClick={sortShips('standard', 2)}
>
{'fsd'}
</th>
<th
className="sortable"
onMouseEnter={termtip.bind(null, 'life support')}
onMouseLeave={hide}
onClick={sortShips('standard', 3)}
>
{'ls'}
</th>
<th
className="sortable"
onMouseEnter={termtip.bind(null, 'power distriubtor')}
onMouseLeave={hide}
onClick={sortShips('standard', 4)}
>
{'pd'}
</th>
<th
className="sortable"
onMouseEnter={termtip.bind(null, 'sensors')}
onMouseLeave={hide}
onClick={sortShips('standard', 5)}
>
{'s'}
</th>
<th
className="sortable"
onMouseEnter={termtip.bind(null, 'fuel tank')}
onMouseLeave={hide}
onClick={sortShips('standard', 6)}
>
{'ft'}
</th>
<th className="sortable lft" onClick={sortShips('hp', 1)}>
{translate('S')}
</th>
<th className="sortable" onClick={sortShips('hp', 2)}>
{translate('M')}
</th>
<th className="sortable" onClick={sortShips('hp', 3)}>
{translate('L')}
</th>
<th className="sortable" onClick={sortShips('hp', 4)}>
{translate('H')}
</th>
<th className="sortable" onClick={sortShips('hp', 0)}>
{translate('U')}
</th>
<th className='lft' colSpan={7}>{translate('core module classes')}</th>
<th colSpan={5} className='sortable lft' onClick={sortShips('hpCount')}>{translate('hardpoints')}</th>
<th colSpan={8} className='sortable lft' onClick={sortShips('intCount')}>{translate('internal compartments')}</th>
</tr>
<tr>
<th className='sortable lft' onClick={sortShips('retailCost')}>{units.CR}</th>
<th className='sortable lft' onClick={sortShips('hullMass')}>{units.T}</th>
<th className='sortable lft' onClick={sortShips('speed')}>{units['m/s']}</th>
<th className='sortable' onClick={sortShips('boost')}>{units['m/s']}</th>
<th>&nbsp;</th>
<th className='sortable' onClick={sortShips('baseShieldStrength')}>{units.MJ}</th>
<th className='sortable lft' onClick={sortShips('topSpeed')}>{units['m/s']}</th>
<th className='sortable' onClick={sortShips('topBoost')}>{units['m/s']}</th>
<th className='sortable' onClick={sortShips('maxJumpRange')}>{units.LY}</th>
<th className='sortable' onClick={sortShips('maxCargo')}>{units.T}</th>
<th>&nbsp;</th>
<th className='sortable lft' onMouseEnter={termtip.bind(null, 'power plant')} onMouseLeave={hide} onClick={sortShips('standard', 0)}>{'pp'}</th>
<th className='sortable' onMouseEnter={termtip.bind(null, 'thrusters')} onMouseLeave={hide} onClick={sortShips('standard', 1)}>{'th'}</th>
<th className='sortable' onMouseEnter={termtip.bind(null, 'frame shift drive')} onMouseLeave={hide} onClick={sortShips('standard', 2)}>{'fsd'}</th>
<th className='sortable' onMouseEnter={termtip.bind(null, 'life support')} onMouseLeave={hide} onClick={sortShips('standard', 3)}>{'ls'}</th>
<th className='sortable' onMouseEnter={termtip.bind(null, 'power distriubtor')} onMouseLeave={hide} onClick={sortShips('standard', 4)}>{'pd'}</th>
<th className='sortable' onMouseEnter={termtip.bind(null, 'sensors')} onMouseLeave={hide} onClick={sortShips('standard', 5)}>{'s'}</th>
<th className='sortable' onMouseEnter={termtip.bind(null, 'fuel tank')} onMouseLeave={hide} onClick={sortShips('standard', 6)}>{'ft'}</th>
<th className='sortable lft' onClick={sortShips('hp',1)}>{translate('S')}</th>
<th className='sortable' onClick={sortShips('hp', 2)}>{translate('M')}</th>
<th className='sortable' onClick={sortShips('hp', 3)}>{translate('L')}</th>
<th className='sortable' onClick={sortShips('hp', 4)}>{translate('H')}</th>
<th className='sortable' onClick={sortShips('hp', 0)}>{translate('U')}</th>
<th className='sortable lft' onClick={sortShips('int', 0)} >1</th>
<th className='sortable' onClick={sortShips('int', 1)} >2</th>
<th className='sortable' onClick={sortShips('int', 2)} >3</th>
<th className='sortable' onClick={sortShips('int', 3)} >4</th>
<th className='sortable' onClick={sortShips('int', 4)} >5</th>
<th className='sortable' onClick={sortShips('int', 5)} >6</th>
<th className='sortable' onClick={sortShips('int', 6)} >7</th>
<th className='sortable' onClick={sortShips('int', 7)} >8</th>
</tr>
</thead>
<tbody onMouseLeave={this._highlightShip.bind(this, null)}>
{detailRows}
</tbody>
</table>
<th className="sortable lft" onClick={sortShips('int', 0)}>
1
</th>
<th className="sortable" onClick={sortShips('int', 1)}>
2
</th>
<th className="sortable" onClick={sortShips('int', 2)}>
3
</th>
<th className="sortable" onClick={sortShips('int', 3)}>
4
</th>
<th className="sortable" onClick={sortShips('int', 4)}>
5
</th>
<th className="sortable" onClick={sortShips('int', 5)}>
6
</th>
<th className="sortable" onClick={sortShips('int', 6)}>
7
</th>
<th className="sortable" onClick={sortShips('int', 7)}>
8
</th>
</tr>
</thead>
<tbody onMouseLeave={this._highlightShip.bind(this, null)}>
{detailRows}
</tbody>
</table>
</div>
</div>
</div>

View File

@@ -94,6 +94,10 @@ export const ModuleGroupToName = {
gsc: 'Guardian Shard Cannon',
tbem: 'Enzyme Missile Rack',
tbrfl: 'Remote Release Flechette Launcher',
pwa: 'Pulse Wave Analyser',
abl: 'Abrasion Blaster',
scl: 'Seismic Charge Launcher',
sdm: 'Sub-Surface Displacement Missile',
};
let GrpNameToCodeMap = {};

View File

@@ -41,6 +41,7 @@ export default class Module {
* @return {object} The value of the modification. If it is a numeric value then it is returned as an integer value scaled so that 1.23% == 123
*/
getModValue(name, raw) {
let baseVal = this[name];
let result = this.mods && this.mods[name] ? this.mods[name] : null;
if ((!raw) && this.blueprint && this.blueprint.special) {
@@ -51,13 +52,8 @@ export default class Module {
const modification = Modifications.modifications[name];
const multiplier = modification.type === 'percentage' ? 10000 : 100;
if (name === 'explres' || name === 'kinres' || name === 'thermres' || name === 'causres') {
// Resistance modifications in itself are additive, however their
// special effects are multiplicative. They affect the overall result
// by (special effect resistance) * (damage mult after modification),
// i. e. we need to apply the special effect as a multiplier to the
// overall result and then calculate the difference.
let baseMult = this[name] ? 1 - this[name] : 1;
result = (baseMult - (baseMult - result / multiplier) * (1 - modifierActions[name] / 100)) * multiplier;
// Apply resistance modding mechanisms to special effects subsequently
result = result + modifierActions[name] * (1 - (this[name] + result / multiplier)) * 100;
} else if (modification.method === 'additive') {
result = result + modifierActions[name] * 100;
} else if (modification.method === 'overwrite') {
@@ -75,15 +71,6 @@ export default class Module {
}
}
// Resistance modding for hull reinforcement packages has additional
// diminishing returns implemented. The mod value gets lowered by
// the amount of base resistance the hrp has.
if (!isNaN(result) && this.grp === 'hr' &&
(name === 'kinres' || name === 'thermres' || name === 'explres')) {
let baseRes = this[name];
result = result * (1 - baseRes);
}
// Sanitise the resultant value to 4dp equivalent
return isNaN(result) ? result : Math.round(result);
}
@@ -108,11 +95,11 @@ export default class Module {
// This special effect modifies the value being set, so we need to revert it prior to storing the value
const modification = Modifications.modifications[name];
if (name === 'explres' || name === 'kinres' || name === 'thermres' || name === 'causres') {
// Resistance modifications in itself are additive but their
// experimentals are applied multiplicatively therefor we must handle
// them differently here (cf. documentation in getModValue).
let baseMult = (this[name] ? 1 - this[name] : 1);
value = ((baseMult - value / 10000) / (1 - modifierActions[name] / 100) - baseMult) * -10000;
let res = (this[name] ? this[name] : 0) + value / 10000;
let experimental = modifierActions[name] / 100;
value = (experimental - res) / (experimental - 1) - this[name];
value *= 10000;
// value = ((baseMult - value / 10000) / (1 - modifierActions[name] / 100) - baseMult) * -10000;
} else if (modification.method === 'additive') {
value = value - modifierActions[name];
} else if (modification.method === 'overwrite') {
@@ -177,10 +164,6 @@ export default class Module {
baseValue = 0;
}
modValue = value - baseValue;
if (this.grp === 'hr' &&
(name === 'kinres' || name === 'thermres' || name === 'explres')) {
modValue = modValue / (1 - baseValue);
}
} else if (name === 'shieldboost' || name === 'hullboost') {
modValue = (1 + value) / (1 + baseValue) - 1;
} else { // multiplicative
@@ -1090,4 +1073,31 @@ export default class Module {
getHackTime(modified = true) {
return this.get('hacktime', modified);
}
/**
* Get the scan range for this module
* @param {Boolean} [modified=true] Whether to take modifications into account
* @return {string} the time for this module
*/
getScanRange(modified = true) {
return this.get('scanrange', modified);
}
/**
* Get the scan angle for this module
* @param {Boolean} [modified=true] Whether to take modifications into account
* @return {string} the time for this module
*/
getScanAngle(modified = true) {
return this.get('scanangle', modified);
}
/**
* Get the max angle for this module
* @param {Boolean} [modified=true] Whether to take modifications into account
* @return {string} the time for this module
*/
getMaxAngle(modified = true) {
return this.get('maxangle', modified);
}
}

View File

@@ -164,13 +164,13 @@ export default class ModuleSet {
/**
* Finds the lightest usable Shield Generator
* @param {number} hullMass Ship hull mass
* @return {Object} Thruster
* @param {string} rating The optional rating of the shield
* @return {Object} Shield Generator
*/
lightestShieldGenerator(hullMass) {
lightestShieldGenerator(hullMass, rating) {
let sg = this.internal.sg[0];
for (let s of this.internal.sg) {
if (s.mass < sg.mass && s.maxmass > hullMass) {
if ((!rating || rating === s.rating) && s.mass <= sg.mass && s.maxmass > hullMass) {
sg = s;
}
}

View File

@@ -63,7 +63,10 @@ export function standard(type, id) {
if (!isNaN(type)) {
type = StandardArray[type];
}
let s = Modules.standard[type].find(e => e.id == id || (e.class == id.charAt(0) && e.rating == id.charAt(1)));
let s = Modules.standard[type].find(e => e.id === id);
if (!s) {
s = Modules.standard[type].find(e => (e.class == id.charAt(0) && e.rating == id.charAt(1)));
}
if (s) {
s = new Module({ template: s });
}

View File

@@ -85,12 +85,12 @@ export function toDetailedBuild(buildName, ship) {
code = ship.toString();
let data = {
$schema: 'https://coriolis.edcd.io/schemas/ship-loadout/4.json#',
$schema: 'https://coriolis.io/schemas/ship-loadout/4.json#',
name: buildName,
ship: ship.name,
references: [{
name: 'Coriolis.io',
url: 'https://coriolis.edcd.io' + outfitURL(ship.id, code, buildName),
url: 'https://coriolis.io' + outfitURL(ship.id, code, buildName),
code,
shipId: ship.id
}],

View File

@@ -505,6 +505,11 @@ export default class Ship {
if (isAbsolute) {
m.setPretty(name, value, sentfromui);
} else {
// Resistance modifiers scale with the base value
if (name == 'kinres' || name == 'thermres' || name == 'causres' || name == 'explres') {
let baseValue = m.get(name, false);
value = (1 - baseValue) * value;
}
m.setModValue(name, value, sentfromui);
}
@@ -1501,7 +1506,7 @@ export default class Ship {
} else {
buffer.writeInt32LE(slotMod.value, curpos);
}
// const modification = _.find(Modifications.modifications, function(o) { return o.id === slotMod.id; });
const modification = _.find(Modifications.modifications, function(o) { return o.id === slotMod.id; });
// console.log('ENCODE Slot ' + i + ': ' + modification.name + ' = ' + slotMod.value);
curpos += 4;
}
@@ -1514,6 +1519,7 @@ export default class Ship {
}
this.serialized.modifications = zlib.gzipSync(buffer).toString('base64');
// console.log(this.serialized.modifications)
} else {
this.serialized.modifications = null;
}

View File

@@ -1,5 +1,7 @@
import * as ModuleUtils from './ModuleUtils';
import { Modifications } from 'coriolis-data/dist';
import { canMount } from '../utils/SlotFunctions';
import { getBlueprint, setPercent } from '../utils/BlueprintFunctions';
/**
* Standard / typical role for multi-purpose or combat (if shielded with better bulkheads)
@@ -14,7 +16,7 @@ export function multiPurpose(ship, shielded, bulkheadIndex) {
.useBulkhead(bulkheadIndex);
if (shielded) {
ship.internal.some(function(slot) {
ship.internal.some(function (slot) {
if (canMount(ship, slot, 'sg')) { // Assuming largest slot can hold an eligible shield
ship.use(slot, ModuleUtils.findInternal('sg', slot.maxClass, 'A'));
ship.setSlotEnabled(slot, true);
@@ -24,6 +26,577 @@ export function multiPurpose(ship, shielded, bulkheadIndex) {
}
}
/**
* Distant Worlds 2 role
* Tiers:
* 1- Max. Jump Range, Unshielded
* 2- Max. Jump Range, Minimal Shields
* 3- Max. Jump Range, Optimal Shields
* 4- Max. Jump Range, Optimal Shields & Thrusters
*
* Engineering level:
* No engineering
* Only Felicity Farseer and Elvira Martuuk
* All exploration related engineers
*
* Role
* Exploration
* Surface exploration
* Big Rig, full mining
* Saper / Prospector mining
* Fuel rat
* Repair rat
* Mechanic
* Trucker
*
* @param ship {Ship} Ship instance
* @param tier {Number}
* @param engineeringLevel {Number}
* @param role {String}
* @param gfsb {Boolean} add Guardian FSD Booster
* @param gpp {Boolean} add Guardian Power Plant
* @param fighter {Boolean} add fighter if supported
*/
export function dw2Build(ship, tier, engineeringLevel, role, gfsb, gpp, fighter) {
ship
.emptyInternal()
.emptyHardpoints()
.emptyUtility();
const fsd = ModuleUtils.findStandard('fsd', ship.standard[2].maxClass, 'A');
ship.use(ship.standard[2], fsd);
ship.use(ship.standard[3], ModuleUtils.findStandard('ls', ship.standard[3].maxClass, 'D'));
ship.use(ship.standard[4], ModuleUtils.findStandard('pd', 1, 'D'));
ship.use(ship.standard[5], ModuleUtils.findStandard('s', ship.standard[5].maxClass, 'D'));
const fuelNeeded = ship.standard[2].m.maxfuel * 2;
const fuelTank = ship.availCS.standard[6]
.filter(e => e.fuel)
.filter(e => e.fuel >= fuelNeeded);
ship.use(ship.standard[6], fuelTank[0]);
ship.useBulkhead(0, false);
if (engineeringLevel === 2) {
const bp = getBlueprint('FSD_LongRange', ship.standard[2]);
bp.grade = 5;
bp.special = Modifications.specials['special_fsd_heavy'];
ship.standard[2].m.blueprint = bp;
setPercent(ship, ship.standard[2].m, 100);
// Sensors G3 LW
const sBP = getBlueprint('Sensor_Sensor_LightWeight', ship.standard[5]);
sBP.grade = 3;
ship.standard[5].m.blueprint = sBP;
setPercent(ship, ship.standard[5].m, 100);
} else if (engineeringLevel === 3) {
// Armour G5 HD + Deep Plating
const armourBP = getBlueprint('Armour_HeavyDuty', ship.bulkheads);
armourBP.grade = 5;
armourBP.special = Modifications.specials['special_armour_chunky'];
ship.bulkheads.m.blueprint = armourBP;
setPercent(ship, ship.bulkheads.m, 100);
// FSD G5 IR + Mass Manager
const fsdBP = getBlueprint('FSD_LongRange', ship.standard[2]);
fsdBP.grade = 5;
fsdBP.special = Modifications.specials['special_fsd_heavy'];
ship.standard[2].m.blueprint = fsdBP;
setPercent(ship, ship.standard[2].m, 100);
// LS G4 LW
const lsBP = getBlueprint('LifeSupport_LightWeight', ship.standard[3]);
lsBP.grade = 4;
ship.standard[3].m.blueprint = lsBP;
setPercent(ship, ship.standard[3].m, 100);
// Sensors G5 LW
const sBP = getBlueprint('Sensor_Sensor_LightWeight', ship.standard[5]);
sBP.grade = 5;
ship.standard[5].m.blueprint = sBP;
setPercent(ship, ship.standard[5].m, 100);
}
if (ship.id === 'imperial_clipper') {
const fs = ModuleUtils.findInternal('fs', 4, 'A');
const slot = ship.internal.filter(a => a.maxClass === 4)[0];
ship.use(slot, fs);
} else if (ship.id === 'imperial_cutter') {
const fs = ModuleUtils.findInternal('fs', 6, 'A');
const slot = ship.internal.filter(a => a.maxClass === 6)[0];
ship.use(slot, fs);
} else if (fsd.class === 2 && fsd.rating === 'A') {
let fs = ModuleUtils.findInternal('fs', 2, 'A');
let slot = ship.internal.filter(a => a.maxClass >= 2).filter(a => a.maxClass >= fs.class)
.sort((a, b) => a.maxClass.toString().localeCompare(b.maxClass.toString()))
[0];
if (slot.m) {
fs = ModuleUtils.findInternal('fs', 1, 'A');
slot = ship.internal.filter(a => a.maxClass === 1)[0];
ship.use(slot, fs);
} else {
ship.use(slot, fs);
}
} else if (fsd.class === 3 && fsd.rating === 'A') {
let fs = ModuleUtils.findInternal('fs', 3, 'B');
let slot = ship.internal.filter(a => a.maxClass >= 3).filter(a => a.maxClass >= fs.class)
.sort((a, b) => a.maxClass.toString().localeCompare(b.maxClass.toString()))
[0];
if (slot.m) {
fs = ModuleUtils.findInternal('fs', 2, 'A');
slot = ship.internal.filter(a => a.maxClass === 2)[0];
ship.use(slot, fs);
} else {
ship.use(slot, fs);
}
} else if (fsd.class === 4 && fsd.rating === 'A') {
let fs = ModuleUtils.findInternal('fs', 4, 'b');
let slot = ship.internal.filter(a => a.maxClass >= 4).filter(a => a.maxClass >= fs.class)
.sort((a, b) => a.maxClass.toString().localeCompare(b.maxClass.toString()))
[0];
if (slot.m) {
fs = ModuleUtils.findInternal('fs', 3, 'A');
slot = ship.internal.filter(a => a.maxClass === 3)[0];
ship.use(slot, fs);
} else {
ship.use(slot, fs);
}
} else if (fsd.class === 5 && fsd.rating === 'A') {
let fs = ModuleUtils.findInternal('fs', 5, 'B');
let slot = ship.internal.filter(a => a.maxClass >= 5).filter(a => a.maxClass >= fs.class)
.sort((a, b) => a.maxClass.toString().localeCompare(b.maxClass.toString()))
[0];
if (slot.m) {
fs = ModuleUtils.findInternal('fs', 4, 'A');
slot = ship.internal.filter(a => a.maxClass === 4).filter(a => a.maxClass >= fs.class)
.sort((a, b) => a.maxClass.toString().localeCompare(b.maxClass.toString()))
[0];
if (fs) {
ship.use(slot, fs);
}
} else {
if (fs) {
ship.use(slot, fs);
}
}
} else if (fsd.class === 6 && fsd.rating === 'A') {
let fs = ModuleUtils.findInternal('fs', 6, 'B');
let slot = ship.internal.filter(a => a.maxClass >= 6)
.filter(a => a.maxClass >= fs.class)
.sort((a, b) => a.maxClass.toString().localeCompare(b.maxClass.toString()))
[0];
if (slot.m) {
fs = ModuleUtils.findInternal('fs', 5, 'A');
slot = ship.internal.filter(a => a.maxClass === 5)[0];
if (fs) {
ship.use(slot, fs);
}
} else {
if (fs) {
ship.use(slot, fs);
}
}
} else if (fsd.class === 7 && fsd.rating === 'A') {
let fs = ModuleUtils.findInternal('fs', 7, 'B');
let slot = ship.internal.filter(a => a.maxClass >= 7).filter(a => a.maxClass >= fs.class)
.sort((a, b) => a.maxClass.toString().localeCompare(b.maxClass.toString()))
[0];
if (slot && slot.m) {
fs = ModuleUtils.findInternal('fs', 6, 'A');
slot = ship.internal.filter(a => a.maxClass === 6)[0];
if (fs) {
ship.use(slot, fs);
}
} else {
if (fs) {
ship.use(slot, fs);
}
}
}
if (tier !== 1) {
const fuelNeeded = ship.standard[2].m.maxfuel * 3;
const fuelTank = ship.availCS.standard[6]
.filter(e => e.fuel)
.filter(e => e.fuel >= fuelNeeded);
if (fuelTank[0]) {
ship.use(ship.standard[6], fuelTank[0]);
}
}
if (tier === 2) {
if (ship.id === 'alliance_chieftain' || ship.id === 'alliance_crusader' || ship.id === 'federal_gunship' || ship.id === 'vulture') {
const hrp = ModuleUtils.findInternal('hrp', 3, 'D');
const slot = ship.internal.filter(e => e.eligible && e.maxClass === 3);
if (hrp) {
ship.use(slot, hrp);
}
} else {
const sg = ship.getAvailableModules().lightestShieldGenerator(ship.ladenMass);
const slot = ship.internal.filter(a => !a.m)
.filter(a => a.maxClass >= sg.class)
.sort((a, b) => a.maxClass.toString().localeCompare(b.maxClass.toString()))
[0];
if (sg) {
ship.use(slot, sg);
}
if (engineeringLevel === 2) {
// ELP G3
const shieldBP = getBlueprint('ShieldGenerator_Optimised', ship.findShieldGenerator());
shieldBP.grade = 3;
ship.findShieldGenerator().blueprint = shieldBP;
setPercent(ship, ship.findShieldGenerator(), 100);
} else if (engineeringLevel === 3) {
// ELP G5
const shieldBP = getBlueprint('ShieldGenerator_Optimised', ship.findShieldGenerator());
shieldBP.grade = 5;
ship.findShieldGenerator().blueprint = shieldBP;
setPercent(ship, ship.findShieldGenerator(), 100);
}
// const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8].reverse();
// const shieldInternals = ship.internal.filter(a => !a.m)
// .filter(a => (!a.eligible) || a.eligible.sg)
// .filter(a => a.maxClass >= sg.class)
// .sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
// for (let i = 0; i < shieldInternals.length; i++) {
// if (canMount(ship, shieldInternals[i], 'sg')) {
// ship.use(shieldInternals[i], sg);
// break;
// }
// }
}
} else if (tier === 3 || tier === 4) {
const sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass, 'A');
const slot = ship.internal.filter(a => !a.m)
.filter(a => a.maxClass >= sg.class)
.sort((a, b) => a.maxClass.toString().localeCompare(b.maxClass.toString()))
[0];
if (sg) {
ship.use(slot, sg);
}
if (engineeringLevel === 1) {
// ELP G3
const shieldBP = getBlueprint('ShieldGenerator_Optimised', ship.findShieldGenerator());
shieldBP.grade = 3;
shieldBP.special = Modifications.specials['special_shield_lightweight'];
ship.findShieldGenerator().blueprint = shieldBP;
setPercent(ship, ship.findShieldGenerator(), 100);
} else if (engineeringLevel === 2) {
// ELP G5
const shieldBP = getBlueprint('ShieldGenerator_Optimised', ship.findShieldGenerator());
shieldBP.grade = 5;
shieldBP.special = Modifications.specials['special_shield_lightweight'];
ship.findShieldGenerator().blueprint = shieldBP;
setPercent(ship, ship.findShieldGenerator(), 100);
}
}
if (tier === 4) {
let t;
if (canMount(ship, ship.standard[1], 't', ship.standard[1].maxClass - 1)) {
t = ModuleUtils.findStandard('t', ship.standard[1].maxClass - 1, 'A');
} else {
t = ModuleUtils.findStandard('t', ship.standard[1].maxClass, 'A');
}
if (t) {
ship.use(ship.standard[1], t);
}
if (engineeringLevel === 1) {
// DD G3
const tBP = getBlueprint('Engine_Dirty', ship.standard[1]);
tBP.grade = 3;
tBP.special = Modifications.specials['special_engine_lightweight'];
ship.standard[1].m.blueprint = tBP;
setPercent(ship, ship.standard[1].m, 100);
} else if (engineeringLevel === 2) {
// DD G5
const tBP = getBlueprint('Engine_Dirty', ship.standard[1]);
tBP.grade = 5;
tBP.special = Modifications.specials['special_engine_lightweight'];
ship.standard[1].m.blueprint = tBP;
setPercent(ship, ship.standard[1].m, 100);
}
}
if (tier === 4 || tier === 3) {
if (engineeringLevel === 3) {
const pd = ship.availCS.standard[4]
.filter(d => d.rating === 'D')
.filter(d => (d.engcap * 1.728) >= ship.boostEnergy)
.sort((a, b) => a.class.toString().localeCompare(b.class.toString()))[0];
if (pd) {
ship.use(ship.standard[4], pd);
}
// CE G5
const pdBP = getBlueprint('PowerDistributor_HighFrequency', ship.standard[4]);
pdBP.grade = 5;
pdBP.special = Modifications.specials['special_powerdistributor_capacity'];
ship.standard[4].m.blueprint = pdBP;
setPercent(ship, ship.standard[4].m, 100);
} else {
const pd = ship.availCS.standard[4]
.filter(d => d.rating === 'D')
.sort((a, b) => a.engcap > b.engcap)
[0];
if (pd) {
ship.use(ship.standard[4], pd);
}
}
} else if (tier === 4) {
if (engineeringLevel === 3) {
const pd = ship.availCS.standard[4]
.filter(d => d.rating === 'D')
.sort((a, b) => b.class.toString().localeCompare(a.class.toString()))[0];
if (pd) {
ship.use(ship.standard[4], pd);
}
// CE G5
const pdBP = getBlueprint('PowerDistributor_HighFrequency', ship.standard[4]);
pdBP.grade = 5;
pdBP.special = Modifications.specials['special_powerdistributor_capacity'];
ship.standard[4].m.blueprint = pdBP;
setPercent(ship, ship.standard[4].m, 100);
} else {
const pd = ship.availCS.standard[4]
.filter(d => d.rating === 'D')
.sort((a, b) => b.class.toString().localeCompare(a.class.toString()))[0];
if (pd) {
ship.use(ship.standard[4], pd);
}
}
}
if (ship.fighterHangars && fighter) {
const slot = ship.internal.filter(s => s.maxClass >= 5 && !s.m)
.sort((a, b) => a.maxClass.toString().localeCompare(b.maxClass.toString()))
[0];
const mod = ModuleUtils.findInternal('fh', 5, 'D');
if (slot && mod) {
ship.use(slot, mod);
}
}
if (tier === 1) {
const pd = ModuleUtils.findStandard('pd', 1, 'D');
if (pd) {
ship.use(ship.standard[4]);
}
}
let dssPriority = 0;
let srvPriority = 0;
let afmu = true;
let cargo = false;
let miningLaserPriority = 0;
let refinery = false;
let collector = false;
let prospector = false;
let miningTools = false;
let refuelLimpets = false;
let repairLimpets = false;
console.log(role);
if (role === 'exploration') {
dssPriority = 2;
afmu = true;
} else if (role === 'surface') {
dssPriority = 2;
srvPriority = 2;
} else if (role === 'materialProspector') {
miningLaserPriority = 2;
srvPriority = 1;
} else if (role === 'propectorMining') {
dssPriority = 1;
prospector = true;
miningLaserPriority = 1;
cargo = true;
miningTools = true;
} else if (role === 'bigRigMining') {
dssPriority = 1;
miningLaserPriority = 2;
cargo = true;
collector = true;
refinery = true;
miningTools = true;
} else if (role === 'fuelRat') {
refuelLimpets = true;
cargo = true;
srvPriority = 1;
} else if (role === 'mechanic') {
repairLimpets = true;
cargo = true;
srvPriority = 1;
} else if (role === 'trucker') {
cargo = true;
}
if (dssPriority === 2) {
const mod = ModuleUtils.findModule('ss', '2i');
const slot = ship.internal.filter(s => !s.m)
.sort((a, b) => a.maxClass.toString().localeCompare(b.maxClass.toString()))[0];
console.log(slot);
console.log(mod);
ship.use(slot, mod);
}
if (srvPriority === 2) {
let mod;
let slot = ship.internal.filter(s => !s.m)
.filter(s => s.maxClass >= 6)
.sort((a, b) => a.maxClass.toString().localeCompare(b.maxClass.toString()))[0];
if (slot) {
mod = ModuleUtils.findModule('pv', 'v2');
ship.use(slot, mod);
} else if (!slot) {
slot = ship.internal.filter(s => !s.m)
.filter(s => s.maxClass >= 4)
.sort((a, b) => a.maxClass.toString().localeCompare(b.maxClass.toString()))[0];
if (slot) {
mod = ModuleUtils.findModule('pv', 'v4');
ship.use(slot, mod);
} else {
slot = ship.internal.filter(s => !s.m)
.filter(s => s.maxClass >= 2)
.sort((a, b) => a.maxClass.toString().localeCompare(b.maxClass.toString()))[0];
if (slot) {
mod = ModuleUtils.findModule('pv', 'v6');
ship.use(slot, mod);
}
}
}
}
if (cargo === true) {
const slot = ship.internal.filter(s => !s.m)
.sort((a, b) => b.maxClass.toString().localeCompare(a.maxClass.toString()))[0];
const mod = ModuleUtils.findInternal('cr', slot.maxClass, 'E');
ship.use(slot, mod);
}
if (refuelLimpets === true) {
const mod = ModuleUtils.findModule('fx', 'F4');
const slot = ship.internal.filter(s => !s.m)
.sort((a, b) => a.maxClass.toString().localeCompare(b.maxClass.toString()))[0];
ship.use(mod, slot);
}
if (repairLimpets === true) {
const slot = ship.internal.filter(s => !s.m)
.sort((a, b) => a.maxClass.toString().localeCompare(b.maxClass.toString()))[0];
let mod;
if (slot.maxClass >= 3) {
mod = ModuleUtils.findModule('rpl', '9e');
} else {
mod = ModuleUtils.findModule('rpl', '9s');
}
ship.use(mod, slot);
}
if (prospector === true) {
const slot = ship.internal.filter(s => !s.m)
.sort((a, b) => a.maxClass.toString().localeCompare(b.maxClass.toString()))[0];
let mod;
if (slot.maxClass >= 3) {
mod = ModuleUtils.findModule('pc', 'P9');
} else {
mod = ModuleUtils.findModule('pc', 'P4');
}
ship.use(mod, slot);
}
if (collector === true) {
const slots = ship.internal.filter(s => !s.m)
.sort((a, b) => a.maxClass.toString().localeCompare(b.maxClass.toString()));
if (slots.length >= 2) {
let slot = slots.find(s => s.maxClass >= 5);
let mod;
if (slot) {
mod = ModuleUtils.findInternal('cc', slot.maxClass, 'D');
} else if (slots.find(s => s.maxClass <= 4)) {
slot = slots.find(s => s.maxClass <= 4);
mod = ModuleUtils.findInternal('cc', slot.maxClass, 'D');
}
ship.use(slot, mod);
}
}
if (refinery === true) {
const slots = ship.internal.filter(s => !s.m)
.filter(s => s.maxClass >= 4)
.sort((a, b) => a.maxClass.toString().localeCompare(b.maxClass.toString()))[0];
if (slots) {
const mod = ModuleUtils.findInternal('rf', 4, 'A');
ship.use(slots, mod);
} else {
const slot = ship.internal.filter(s => !s.m)
.filter(s => s.maxClass <= 3)
.sort((a, b) => b.maxClass.toString().localeCompare(a.maxClass.toString()))[0];
const mod = ModuleUtils.findInternal('rf', slot.maxClass, 'A');
ship.use(slots, mod);
}
}
if (dssPriority === 1) {
const slot = ship.internal.filter(s => !s.m)
.sort((a, b) => a.maxClass.toString().localeCompare(b.maxClass.toString()))[0];
const dss = ModuleUtils.findInternal('ss', 1, 'C')
if (slot) {
ship.use(slot, dss);
}
}
if (srvPriority === 1) {
const slot = ship.internal.filter(s => !s.m)
.filter(s => s.maxClass >= 2)
.sort((a, b) => a.maxClass.toString().localeCompare(b.maxClass.toString()));
if (slot.find(s => s.maxClass >= 4)) {
const slot = slot.find(s => s.maxClass >= 4);
const srv = ModuleUtils.findInternal('pv', 4, 'G')
ship.use(slot, srv);
} else if (slot.find(s => s.maxClass >= 2)) {
const slot = slot.find(s => s.maxClass >= 2);
const srv = ModuleUtils.findInternal('pv', 2, 'G')
ship.use(slot, srv);
}
}
if (gfsb === true) {
const slots = ship.internal.filter(s => !s.m)
.filter(s => s.maxClass >= 1)
.sort((a, b) => b.maxClass.toString().localeCompare(a.maxClass.toString()));
if (slots.find(s => s.maxClass >= 5)) {
const mod = ModuleUtils.findInternal('gfsb', 5, 'H');
ship.use(slots.find(s => s.maxClass >= 5), mod)
} else if (slots.find(s => s.maxClass >= 4)) {
const mod = ModuleUtils.findInternal('gfsb', 4, 'H');
ship.use(slots.find(s => s.maxClass >= 4), mod)
} else if (slots.find(s => s.maxClass >= 3)) {
const mod = ModuleUtils.findInternal('gfsb', 3, 'H');
ship.use(slots.find(s => s.maxClass >= 3), mod)
} else if (slots.find(s => s.maxClass >= 2)) {
const mod = ModuleUtils.findInternal('gfsb', 2, 'H');
ship.use(slots.find(s => s.maxClass >= 2), mod)
} else if (slots.find(s => s.maxClass >= 1)) {
const mod = ModuleUtils.findInternal('gfsb', 1, 'H');
ship.use(slots.find(s => s.maxClass >= 1), mod)
}
}
// const pp = ship.getAvailableModules().lightestPowerPlant(Math.max(ship.powerRetracted, ship.powerDeployed), 'A');
// const t = ship.getAvailableModules().lightestThruster(ship.ladenMass);
// ship.use(ship.standard[0], pp);
// ship.use(ship.standard[1], t);
// ship.useLightestStandard(standardOpts);
ship.updatePowerGenerated()
.updatePowerUsed()
.recalculateMass()
.updateJumpStats()
.recalculateShield()
.recalculateShieldCells()
.recalculateArmour()
.recalculateDps()
.recalculateEps()
.recalculateHps()
.updateMovement()
.updateModificationsString();
}
/**
* Trader Role
* @param {Ship} ship Ship instance
@@ -36,7 +609,7 @@ export function trader(ship, shielded, standardOpts) {
let sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
ship.useStandard('A')
.use(ship.standard[3], ModuleUtils.standard(3, ship.standard[3].maxClass + 'D')) // D Life Support
.use(ship.standard[1], ModuleUtils.standard(1, ship.standard[1].maxClass + 'D')) // D Life Support
.use(ship.standard[1], ModuleUtils.standard(1, ship.standard[1].maxClass + 'D')) // D Power Plant
.use(ship.standard[4], ModuleUtils.standard(4, ship.standard[4].maxClass + 'D')) // D Life Support
.use(ship.standard[5], ModuleUtils.standard(5, ship.standard[5].maxClass + 'D')); // D Sensors
@@ -45,7 +618,7 @@ export function trader(ship, shielded, standardOpts) {
.filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class)
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
shieldInternals.some(function(slot) {
shieldInternals.some(function (slot) {
if (canMount(ship, slot, 'sg')) { // Assuming largest slot can hold an eligible shield
const shield = ModuleUtils.findInternal('sg', slot.maxClass, 'A');
if (shield && shield.maxmass > ship.hullMass) {
@@ -87,11 +660,11 @@ export function trader(ship, shielded, standardOpts) {
*/
export function explorer(ship, planetary) {
let standardOpts = { ppRating: 'A' },
heatSinkCount = 2, // Fit 2 heat sinks if possible
usedSlots = [],
sgSlot,
fuelScoopSlot,
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
heatSinkCount = 2, // Fit 2 heat sinks if possible
usedSlots = [],
sgSlot,
fuelScoopSlot,
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
if (!planetary) { // Non-planetary explorers don't really need to boost
standardOpts.pd = '1D';
@@ -216,9 +789,9 @@ export function explorer(ship, planetary) {
export function miner(ship, shielded) {
shielded = true;
let standardOpts = { ppRating: 'A' },
miningLaserCount = 2,
usedSlots = [],
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
miningLaserCount = 2,
usedSlots = [],
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
// Cargo hatch should be enabled
ship.setSlotEnabled(ship.cargoHatch, true);
@@ -269,7 +842,7 @@ export function miner(ship, shielded) {
// Dual mining lasers of highest possible class; remove anything else
const miningLaserOrder = [2, 3, 4, 1, 0];
const miningLaserHardpoints = ship.hardpoints.concat().sort(function(a, b) {
const miningLaserHardpoints = ship.hardpoints.concat().sort(function (a, b) {
return miningLaserOrder.indexOf(a.maxClass) - miningLaserOrder.indexOf(b.maxClass);
});
for (let s of miningLaserHardpoints) {
@@ -283,7 +856,7 @@ export function miner(ship, shielded) {
// Number of collector limpets required to be active is a function of the size of the ship and the power of the lasers
const miningLaserDps = ship.hardpoints.filter(h => h.m != null)
.reduce(function(a, b) {
.reduce(function (a, b) {
return a + b.m.getDps();
}, 0);
// Find out how many internal slots we have, and their potential cargo size
@@ -314,7 +887,7 @@ export function miner(ship, shielded) {
// Power distributor to power the mining lasers indefinitely
const wepRateRequired = ship.hardpoints.filter(h => h.m != null)
.reduce(function(a, b) {
.reduce(function (a, b) {
return a + b.m.getEps();
}, 0);
standardOpts.pd = ship.getAvailableModules().matchingPowerDist({ weprate: wepRateRequired }).id;
@@ -336,9 +909,9 @@ export function miner(ship, shielded) {
*/
export function racer(ship) {
let standardOpts = {},
usedSlots = [],
sgSlot,
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
usedSlots = [],
sgSlot,
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
// Cargo hatch can be disabled
ship.setSlotEnabled(ship.cargoHatch, false);

View File

@@ -78,5 +78,6 @@ export const STATS_FORMATTING = {
'thermres': { 'format': 'pct' },
'wepcap': { 'format': 'round1', 'unit': 'MJ' },
'weprate': { 'format': 'round1', 'unit': 'MW' },
'jumpboost': { 'format': 'round1', 'unit': 'LY' }
'jumpboost': { 'format': 'round1', 'unit': 'LY' },
'proberadius': { 'format': 'pct1', 'unit': 'pct' },
};

View File

@@ -1,417 +1,417 @@
import React from 'react';
import { Modifications } from 'coriolis-data/dist';
/**
* Generate a tooltip with details of a blueprint's specials
* @param {Object} translate The translate object
* @param {Object} blueprint The blueprint at the required grade
* @param {string} grp The group of the module
* @param {Object} m The module to compare with
* @param {string} specialName The name of the special
* @returns {Object} The react components
*/
export function specialToolTip(translate, blueprint, grp, m, specialName) {
const effects = [];
if (!blueprint || !blueprint.features) {
return undefined;
}
if (m) {
// 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) {
const featureDef = Modifications.modifications[feature];
if (featureDef && !featureDef.hidden) {
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let current = m.getModValue(feature) - m.getModValue(feature, true);
if (featureDef.type === 'percentage') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature + '_specialTT'}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'}
style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td>
</tr>
);
}
}
}
}
return (
<div>
<table width='100%'>
<tbody>
{effects}
</tbody>
</table>
</div>
);
}
/**
* Generate a tooltip with details of a blueprint's effects
* @param {Object} translate The translate object
* @param {Object} blueprint The blueprint at the required grade
* @param {Array} engineers The engineers supplying this blueprint
* @param {string} grp The group of the module
* @param {Object} m The module to compare with
* @returns {Object} The react components
*/
export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
const effects = [];
if (!blueprint || !blueprint.features) {
return undefined;
}
for (const feature in blueprint.features) {
const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]);
const featureDef = Modifications.modifications[feature];
if (!featureDef.hidden) {
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let lowerBound = blueprint.features[feature][0];
let upperBound = blueprint.features[feature][1];
if (featureDef.type === 'percentage') {
lowerBound = Math.round(lowerBound * 1000) / 10;
upperBound = Math.round(upperBound * 1000) / 10;
}
const lowerIsBeneficial = isValueBeneficial(feature, lowerBound);
const upperIsBeneficial = isValueBeneficial(feature, upperBound);
if (m) {
// We have a module - add in the current value
let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
</tr>
);
} else {
// We do not have a module, no value
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
</tr>
);
}
}
}
if (m) {
// Because we have a module add in any benefits that aren't part of the primary blueprint
for (const feature in m.mods) {
if (!blueprint.features[feature]) {
const featureDef = Modifications.modifications[feature];
if (featureDef && !featureDef.hidden) {
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td>
</tr>
);
}
}
}
// We also add in any benefits from specials that aren't covered above
if (m.blueprint && m.blueprint.special) {
for (const feature in Modifications.modifierActions[m.blueprint.special.edname]) {
if (!blueprint.features[feature] && !m.mods.feature) {
const featureDef = Modifications.modifications[feature];
if (featureDef && !featureDef.hidden) {
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td>
</tr>
);
}
}
}
}
}
let components;
if (!m) {
components = [];
for (const component in blueprint.components) {
components.push(
<tr key={component}>
<td style={{ textAlign: 'left' }}>{translate(component)}</td>
<td style={{ textAlign: 'right' }}>{blueprint.components[component]}</td>
</tr>
);
}
}
let engineersList;
if (engineers) {
engineersList = [];
for (const engineer of engineers) {
engineersList.push(
<tr key={engineer}>
<td style={{ textAlign: 'left' }}>{engineer}</td>
</tr>
);
}
}
return (
<div>
<table width='100%'>
<thead>
<tr>
<td>{translate('feature')}</td>
<td>{translate('worst')}</td>
{m ? <td>{translate('current')}</td> : null }
<td>{translate('best')}</td>
</tr>
</thead>
<tbody>
{effects}
</tbody>
</table>
{ components ? <table width='100%'>
<thead>
<tr>
<td>{translate('component')}</td>
<td>{translate('amount')}</td>
</tr>
</thead>
<tbody>
{components}
</tbody>
</table> : null }
{ engineersList ? <table width='100%'>
<thead>
<tr>
<td>{translate('engineers')}</td>
</tr>
</thead>
<tbody>
{engineersList}
</tbody>
</table> : null }
</div>
);
}
/**
* Is this blueprint feature beneficial?
* @param {string} feature The name of the feature
* @param {array} values The value of the feature
* @returns {boolean} True if this feature is beneficial
*/
export function isBeneficial(feature, values) {
const fact = (values[0] < 0 || (values[0] === 0 && values[1] < 0));
if (Modifications.modifications[feature].higherbetter) {
return !fact;
} else {
return fact;
}
}
/**
* Is this feature value beneficial?
* @param {string} feature The name of the feature
* @param {number} value The value of the feature
* @returns {boolean} True if this value is beneficial
*/
export function isValueBeneficial(feature, value) {
if (Modifications.modifications[feature].higherbetter) {
return value > 0;
} else {
return value < 0;
}
}
/**
* Get a blueprint with a given name and an optional module
* @param {string} name The name of the blueprint
* @param {Object} module The module for which to obtain this blueprint
* @returns {Object} The matching blueprint
*/
export function getBlueprint(name, module) {
// Start with a copy of the blueprint
const findMod = val => Object.keys(Modifications.blueprints).find(elem => elem.toString().toLowerCase().search(val.toString().toLowerCase().replace(/(OutfittingFieldType_|persecond)/igm, '')) >= 0);
const found = Modifications.blueprints[findMod(name)];
if (!found || !found.fdname) {
return {};
}
const blueprint = JSON.parse(JSON.stringify(found));
return blueprint;
}
/**
* Provide 'percent' primary modifications
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
* @param {Number} percent The percent to set values to of full.
*/
export function setPercent(ship, m, percent) {
ship.clearModifications(m);
// Pick given value as multiplier
const mult = percent / 100;
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
let value;
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)) {
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
} else {
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
}
} else {
// Higher is worse, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
} else {
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
}
}
_setValue(ship, m, featureName, value);
}
}
/**
* Provide 'random' primary modifications
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
*/
export function setRandom(ship, m) {
// Pick a single value for our randomness
setPercent(ship, m, Math.random() * 100);
}
/**
* Set a modification feature value
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
* @param {string} featureName The feature being set
* @param {number} value The value being set for the feature
*/
function _setValue(ship, m, featureName, value) {
if (Modifications.modifications[featureName].type == 'percentage') {
ship.setModification(m, featureName, value * 10000);
} else if (Modifications.modifications[featureName].type == 'numeric') {
ship.setModification(m, featureName, value * 100);
} else {
ship.setModification(m, featureName, value);
}
}
/**
* Provide 'percent' primary query
* @param {Object} m The module for which to perform the query
* @returns {Number} percent The percentage indicator of current applied values.
*/
export function getPercent(m) {
let result = null;
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
if (features[featureName][0] === features[featureName][1]) {
continue;
}
let value = _getValue(m, featureName);
let mult;
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);
} else {
mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100);
}
} else {
// Higher is worse, 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][0]) / (features[featureName][1] - features[featureName][0]) * 100);
} else {
mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100);
}
}
if (result && result != mult) {
return null;
} else if (result != mult) {
result = mult;
}
}
return result;
}
/**
* Query a feature value
* @param {Object} m The module for which to perform the query
* @param {string} featureName The feature being queried
* @returns {number} The value of the modification as a %
*/
function _getValue(m, featureName) {
if (Modifications.modifications[featureName].type == 'percentage') {
return m.getModValue(featureName, true) / 10000;
} else if (Modifications.modifications[featureName].type == 'numeric') {
return m.getModValue(featureName, true) / 100;
} else {
return m.getModValue(featureName, true);
}
}
import React from 'react';
import { Modifications } from 'coriolis-data/dist';
/**
* Generate a tooltip with details of a blueprint's specials
* @param {Object} translate The translate object
* @param {Object} blueprint The blueprint at the required grade
* @param {string} grp The group of the module
* @param {Object} m The module to compare with
* @param {string} specialName The name of the special
* @returns {Object} The react components
*/
export function specialToolTip(translate, blueprint, grp, m, specialName) {
const effects = [];
if (!blueprint || !blueprint.features) {
return undefined;
}
if (m) {
// 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) {
const featureDef = Modifications.modifications[feature];
if (featureDef && !featureDef.hidden) {
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let current = m.getModValue(feature) - m.getModValue(feature, true);
if (featureDef.type === 'percentage') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature + '_specialTT'}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'}
style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td>
</tr>
);
}
}
}
}
return (
<div>
<table width='100%'>
<tbody>
{effects}
</tbody>
</table>
</div>
);
}
/**
* Generate a tooltip with details of a blueprint's effects
* @param {Object} translate The translate object
* @param {Object} blueprint The blueprint at the required grade
* @param {Array} engineers The engineers supplying this blueprint
* @param {string} grp The group of the module
* @param {Object} m The module to compare with
* @returns {Object} The react components
*/
export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
const effects = [];
if (!blueprint || !blueprint.features) {
return undefined;
}
for (const feature in blueprint.features) {
const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]);
const featureDef = Modifications.modifications[feature];
if (!featureDef.hidden) {
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let lowerBound = blueprint.features[feature][0];
let upperBound = blueprint.features[feature][1];
if (featureDef.type === 'percentage') {
lowerBound = Math.round(lowerBound * 1000) / 10;
upperBound = Math.round(upperBound * 1000) / 10;
}
const lowerIsBeneficial = isValueBeneficial(feature, lowerBound);
const upperIsBeneficial = isValueBeneficial(feature, upperBound);
if (m) {
// We have a module - add in the current value
let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
</tr>
);
} else {
// We do not have a module, no value
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
</tr>
);
}
}
}
if (m) {
// Because we have a module add in any benefits that aren't part of the primary blueprint
for (const feature in m.mods) {
if (!blueprint.features[feature]) {
const featureDef = Modifications.modifications[feature];
if (featureDef && !featureDef.hidden) {
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td>
</tr>
);
}
}
}
// We also add in any benefits from specials that aren't covered above
if (m.blueprint && m.blueprint.special) {
for (const feature in Modifications.modifierActions[m.blueprint.special.edname]) {
if (!blueprint.features[feature] && !m.mods.feature) {
const featureDef = Modifications.modifications[feature];
if (featureDef && !featureDef.hidden) {
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td>
</tr>
);
}
}
}
}
}
let components;
if (!m) {
components = [];
for (const component in blueprint.components) {
components.push(
<tr key={component}>
<td style={{ textAlign: 'left' }}>{translate(component)}</td>
<td style={{ textAlign: 'right' }}>{blueprint.components[component]}</td>
</tr>
);
}
}
let engineersList;
if (engineers) {
engineersList = [];
for (const engineer of engineers) {
engineersList.push(
<tr key={engineer}>
<td style={{ textAlign: 'left' }}>{engineer}</td>
</tr>
);
}
}
return (
<div>
<table width='100%'>
<thead>
<tr>
<td>{translate('feature')}</td>
<td>{translate('worst')}</td>
{m ? <td>{translate('current')}</td> : null }
<td>{translate('best')}</td>
</tr>
</thead>
<tbody>
{effects}
</tbody>
</table>
{ components ? <table width='100%'>
<thead>
<tr>
<td>{translate('component')}</td>
<td>{translate('amount')}</td>
</tr>
</thead>
<tbody>
{components}
</tbody>
</table> : null }
{ engineersList ? <table width='100%'>
<thead>
<tr>
<td>{translate('engineers')}</td>
</tr>
</thead>
<tbody>
{engineersList}
</tbody>
</table> : null }
</div>
);
}
/**
* Is this blueprint feature beneficial?
* @param {string} feature The name of the feature
* @param {array} values The value of the feature
* @returns {boolean} True if this feature is beneficial
*/
export function isBeneficial(feature, values) {
const fact = (values[0] < 0 || (values[0] === 0 && values[1] < 0));
if (Modifications.modifications[feature].higherbetter) {
return !fact;
} else {
return fact;
}
}
/**
* Is this feature value beneficial?
* @param {string} feature The name of the feature
* @param {number} value The value of the feature
* @returns {boolean} True if this value is beneficial
*/
export function isValueBeneficial(feature, value) {
if (Modifications.modifications[feature].higherbetter) {
return value > 0;
} else {
return value < 0;
}
}
/**
* Get a blueprint with a given name and an optional module
* @param {string} name The name of the blueprint
* @param {Object} module The module for which to obtain this blueprint
* @returns {Object} The matching blueprint
*/
export function getBlueprint(name, module) {
// Start with a copy of the blueprint
const findMod = val => Object.keys(Modifications.blueprints).find(elem => elem.toString().toLowerCase().search(val.toString().toLowerCase().replace(/(OutfittingFieldType_|persecond)/igm, '')) >= 0);
const found = Modifications.blueprints[findMod(name)];
if (!found || !found.fdname) {
return {};
}
const blueprint = JSON.parse(JSON.stringify(found));
return blueprint;
}
/**
* Provide 'percent' primary modifications
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
* @param {Number} percent The percent to set values to of full.
*/
export function setPercent(ship, m, percent) {
ship.clearModifications(m);
// Pick given value as multiplier
const mult = percent / 100;
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
let value;
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)) {
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
} else {
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
}
} else {
// Higher is worse, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
} else {
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
}
}
_setValue(ship, m, featureName, value);
}
}
/**
* Provide 'random' primary modifications
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
*/
export function setRandom(ship, m) {
// Pick a single value for our randomness
setPercent(ship, m, Math.random() * 100);
}
/**
* Set a modification feature value
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
* @param {string} featureName The feature being set
* @param {number} value The value being set for the feature
*/
function _setValue(ship, m, featureName, value) {
if (Modifications.modifications[featureName].type == 'percentage') {
ship.setModification(m, featureName, value * 10000);
} else if (Modifications.modifications[featureName].type == 'numeric') {
ship.setModification(m, featureName, value * 100);
} else {
ship.setModification(m, featureName, value);
}
}
/**
* Provide 'percent' primary query
* @param {Object} m The module for which to perform the query
* @returns {Number} percent The percentage indicator of current applied values.
*/
export function getPercent(m) {
let result = null;
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
if (features[featureName][0] === features[featureName][1]) {
continue;
}
let value = _getValue(m, featureName);
let mult;
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);
} else {
mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100);
}
} else {
// Higher is worse, 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][0]) / (features[featureName][1] - features[featureName][0]) * 100);
} else {
mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100);
}
}
if (result && result != mult) {
return null;
} else if (result != mult) {
result = mult;
}
}
return result;
}
/**
* Query a feature value
* @param {Object} m The module for which to perform the query
* @param {string} featureName The feature being queried
* @returns {number} The value of the modification as a %
*/
function _getValue(m, featureName) {
if (Modifications.modifications[featureName].type == 'percentage') {
return m.getModValue(featureName, true) / 10000;
} else if (Modifications.modifications[featureName].type == 'numeric') {
return m.getModValue(featureName, true) / 100;
} else {
return m.getModValue(featureName, true);
}
}

View File

@@ -30,6 +30,8 @@ export const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
'Hauler': 'hauler',
'Independant_Trader': 'keelback',
'Krait_MkII': 'krait_mkii',
'Mamba': 'mamba',
'Krait_Light': 'krait_phantom',
'Orca': 'orca',
'Python': 'python',
'SideWinder': 'sidewinder',

9
src/iframe.html Normal file
View File

@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html>
<head>
<script src="xdLocalStoragePostMessageApi.min.js"></script>
</head>
<body>
This is the magical iframe
</body>
</html>

View File

@@ -1,16 +1,19 @@
<!DOCTYPE html>
<html manifest="/">
<html>
<head>
<title>Coriolis EDCD Edition</title>
<meta charset="UTF-8">
<link rel="stylesheet" href="<%= htmlWebpackPlugin.files.css[0] %>">
<!-- Standard headers -->
<meta name="description" content="A ship builder, outfitting and comparison tool for Elite Dangerous">
<meta name="description" content="A ship builder, outfitting and comparison
tool for Elite Dangerous">
<meta name="mobile-web-app-capable" content="yes">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<meta name="viewport" content="width=device-width, initial-scale=1.0,
maximum-scale=1.0, user-scalable=0">
<link rel="manifest" href="/manifest.json">
<link rel="shortcut icon" href=/favicon2.ico>
<link rel="icon" sizes="152x152 192x192" type="image/png" href="/192x192.png">
<link rel="icon" sizes="152x152 192x192" type="image/png"
href="/192x192.png">
<!-- Apple/iOS headers -->
<meta name="apple-mobile-web-app-capable" content="yes">
@@ -30,16 +33,16 @@
window.BUGSNAG_VERSION = '<%- htmlWebpackPlugin.options.version + '-' + htmlWebpackPlugin.options.date.toISOString() %>';
</script>
<% if (htmlWebpackPlugin.options.uaTracking) { %>
<script>
<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', '<%- htmlWebpackPlugin.options.uaTracking %>', 'auto');
ga('send', 'pageview');
</script>
<script async src='https://www.google-analytics.com/analytics.js'></script>
<% } %>
<script async src='https://www.google-analytics.com/analytics.js'></script>
<% } %>
<!-- Piwik -->
<!-- <script type="text/javascript">
<!-- Piwik -->
<!-- <script type="text/javascript">
var _paq = _paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(["setCookieDomain", "*.coriolis.edcd.io"]);
@@ -53,18 +56,17 @@
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
})();
</script>-->
<!-- End Piwik Code -->
<!-- End Piwik Code -->
<!-- Bugsnag -->
<script src="//d2wy8f7a9ursnm.cloudfront.net/v4/bugsnag.min.js"></script>
<script src="//d2wy8f7a9ursnm.cloudfront.net/bugsnag-plugins/v1/bugsnag-react.min.js"></script>
<script>
<!-- Bugsnag -->
<script src="https://d2wy8f7a9ursnm.cloudfront.net/v5.0.0/bugsnag.min.js"></script>
<script>
window.bugsnagClient = bugsnag('ba9fae819372850fb660755341fa6ef5', {appVersion: window.BUGSNAG_VERSION || undefined})
window.Bugsnag = window.bugsnagClient
</script>
</head>
<body style="background-color:#000;">
<section id="coriolis"></section>
</head>
<body style="background-color:#000;">
<section id="coriolis"></section>
</body>
</html>
</body>
</html>

View File

@@ -171,3 +171,16 @@ footer {
text-align: right;
}
}
.announcement-container {
display: flex;
align-items: center;
padding-top: 10px;
justify-content: center;
flex-flow: row wrap;
}
.announcement {
border: 1px @secondary solid;
padding: 10px;
}

View File

@@ -22,6 +22,15 @@ select {
}
}
.react-fuzzy-search > * {
padding: 0 !important;
color: @primary;
& > input {
border: 1px solid @primary !important;
color: @primary-bg;
}
}
.cmdr-select {
border: 1px solid @primary;
padding: 0.5em 0.5em;

View File

@@ -55,7 +55,7 @@ tbody tr {
background-color: @warning-bg;
}
&.alt {
&:nth-child(odd){
background-color: @alt-primary-bg;
}
}

View File

@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "https://coriolis.edcd.io/schemas/ship-loadout/1.json#",
"id": "https://coriolis.io/schemas/ship-loadout/1.json#",
"title": "Ship Loadout",
"type": "object",
"description": "The details for a specific ship build/loadout. DEPRECATED in favor of Version 3",

View File

@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "https://coriolis.edcd.io/schemas/ship-loadout/2.json#",
"id": "https://coriolis.io/schemas/ship-loadout/2.json#",
"title": "Ship Loadout",
"type": "object",
"description": "The details for a specific ship build/loadout. DEPRECATED in favor of Version 3",

View File

@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "https://coriolis.edcd.io/schemas/ship-loadout/3.json#",
"id": "https://coriolis.io/schemas/ship-loadout/3.json#",
"title": "Ship Loadout",
"type": "object",
"description": "The details for a specific ship build/loadout",

View File

@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "https://coriolis.edcd.io/schemas/ship-loadout/4.json#",
"id": "https://coriolis.io/schemas/ship-loadout/4.json#",
"title": "Ship Loadout",
"type": "object",
"description": "The details for a specific ship build/loadout",

View File

@@ -2,6 +2,21 @@ console.log('Hello from sw.js');
if (workbox) {
console.log('Yay! Workbox is loaded 🎉');
workbox.precaching.precacheAndRoute(self.__precacheManifest);
workbox.routing.registerNavigationRoute('/index.html');
workbox.routing.registerRoute(
new RegExp('/(.*?)'),
workbox.strategies.staleWhileRevalidate({
plugins: [
new workbox.cacheableResponse.Plugin({
statuses: [0, 200]
})
]
})
);
workbox.routing.registerRoute(
new RegExp('https://fonts.(?:googleapis|gstatic).com/(.*)'),
workbox.strategies.cacheFirst({
@@ -16,24 +31,7 @@ if (workbox) {
]
})
);
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) {
@@ -43,38 +41,17 @@ if (workbox) {
console.log('Boo! Workbox didn\'t load 😬');
}
self.addEventListener('message', event => {
if (!event.data) {
return;
}
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);
});
})
);
switch (event.data) {
case 'skipWaiting':
self.skipWaiting();
break;
default:
// NOOP
break;
}
});

View File

@@ -0,0 +1 @@
"use strict";window.XdUtils=window.XdUtils||function(){function a(a,b){var c,d=b||{};for(c in a)a.hasOwnProperty(c)&&(d[c]=a[c]);return d}return{extend:a}}(),function(){function a(a,b){var c=XdUtils.extend(b,l);c.id=a,parent.postMessage(JSON.stringify(c),"*")}function b(b,c){a(b,{key:c,value:localStorage.getItem(c)})}function c(b,c,d){localStorage.setItem(c,d),a(b,{success:localStorage.getItem(c)===d})}function d(b,c){localStorage.removeItem(c),a(b,{})}function e(b,c){a(b,{key:localStorage.key(c)})}function f(b){a(b,{size:JSON.stringify(localStorage).length})}function g(b){a(b,{length:localStorage.length})}function h(b){localStorage.clear(),a(b,{})}function i(a){var i;try{i=JSON.parse(a.data)}catch(a){}i&&i.namespace===k&&("set"===i.action?c(i.id,i.key,i.value):"get"===i.action?b(i.id,i.key):"remove"===i.action?d(i.id,i.key):"key"===i.action?e(i.id,i.key):"size"===i.action?f(i.id):"length"===i.action?g(i.id):"clear"===i.action&&h(i.id))}function j(){var a={namespace:k,id:"iframe-ready"};parent.postMessage(JSON.stringify(a),"*")}var k="cross-domain-local-message",l={namespace:k};window.addEventListener?window.addEventListener("message",i,!1):window.attachEvent("onmessage",i),j()}();

View File

@@ -14,7 +14,7 @@ module.exports = {
},
mode: 'development',
entry: {
main: './src/app/index.js'
main: './src/app/index.js',
},
resolve: {
// When requiring, you don't need to add these extensions
@@ -22,17 +22,15 @@ module.exports = {
},
optimization: {
minimize: false,
splitChunks: {
chunks: 'all'
}
usedExports: true
},
output: {
path: path.join(__dirname, 'build'),
filename: 'app.js',
chunkFilename: '[name].bundle.js',
publicPath: '/'
},
plugins: [
new CopyWebpackPlugin(['src/.htaccess']),
new CopyWebpackPlugin(['src/.htaccess', 'src/iframe.html', 'src/xdLocalStoragePostMessageApi.min.js']),
// new webpack.optimize.CommonsChunkPlugin({
// name: 'lib',
// filename: 'lib.js'
@@ -56,7 +54,10 @@ module.exports = {
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: /\.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' },

View File

@@ -4,33 +4,35 @@ 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 { BugsnagSourceMapUploaderPlugin, BugsnagBuildReporterPlugin } = require('webpack-bugsnag-plugins');
const pkgJson = require('./package');
const buildDate = new Date();
module.exports = {
devtool: 'source-map',
entry: {
main: ['./src/app/index.js']
main: './src/app/index.js'
},
resolve: {
extensions: ['.js', '.jsx', '.json', '.less']
},
output: {
path: path.join(__dirname, 'build'),
filename: '[name].[hash].js',
chunkFilename: '[name].bundle.js',
publicPath: '/',
globalObject: 'this'
},
mode: 'production',
optimization: {
minimize: true,
splitChunks: {
chunks: 'all'
}
usedExports: true
},
plugins: [
new CopyWebpackPlugin(['src/.htaccess', { from: 'src/schemas', to: 'schemas' }, {from: 'src/images/logo/*', flatten: true, to: ''}]),
new CopyWebpackPlugin(['src/.htaccess', { from: 'src/schemas', to: 'schemas' }, {
from: 'src/images/logo/*',
flatten: true,
to: ''
}, 'src/iframe.html', 'src/xdLocalStoragePostMessageApi.min.js']),
// new webpack.optimize.CommonsChunkPlugin({
// name: 'lib',
// filename: 'lib.[chunkhash:6].js'
@@ -48,8 +50,13 @@ module.exports = {
disable: false,
allChunks: true
}),
// new BugsnagBuildReporterPlugin({
// apiKey: 'ba9fae819372850fb660755341fa6ef5',
// appVersion: `${pkgJson.version}-${buildDate.toISOString()}`
// }, { /* opts */ }),
// new BugsnagSourceMapUploaderPlugin({
// apiKey: 'ba9fae819372850fb660755341fa6ef5',
// overwrite: true,
// appVersion: `${pkgJson.version}-${buildDate.toISOString()}`
// }),
new InjectManifest({
@@ -61,7 +68,10 @@ module.exports = {
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: /\.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' },