mirror of
https://github.com/EDCD/coriolis.git
synced 2025-12-10 07:05:35 +00:00
Compare commits
161 Commits
dw2
...
2eed1bc85b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2eed1bc85b | ||
|
|
3469af10b6 | ||
|
|
629ba35bc5 | ||
|
|
e453ff73b7 | ||
|
|
c73ce1c234 | ||
|
|
00d3a93b91 | ||
|
|
d4e612cb61 | ||
|
|
c07cfc6e70 | ||
|
|
c4c6d32a5d | ||
|
|
a65bb06754 | ||
|
|
23548e7c5c | ||
|
|
74e6f54e19 | ||
|
|
a46f8f97f6 | ||
|
|
44dbdb1703 | ||
|
|
187c5dae4a | ||
|
|
07d324a3fa | ||
|
|
5c63afd96c | ||
|
|
53c40ac9c4 | ||
|
|
a180cbfdd4 | ||
|
|
fc1524a943 | ||
|
|
c3747e4e5e | ||
|
|
ab153981c9 | ||
|
|
a970e052c1 | ||
|
|
df7e264a02 | ||
|
|
cdcda004f3 | ||
|
|
20e448fc0a | ||
|
|
436e626c42 | ||
|
|
3dd4675a0b | ||
|
|
d3766d9e17 | ||
|
|
d987c08ac8 | ||
|
|
f865ef6c6c | ||
|
|
f2b7daac82 | ||
|
|
832bc488b6 | ||
|
|
f513166d6c | ||
|
|
61fd0eb991 | ||
|
|
1e51e7d4a6 | ||
|
|
4943d36bb8 | ||
|
|
9271d1fa09 | ||
|
|
8a09d94dfa | ||
|
|
c44925dd62 | ||
|
|
d006bbcb0f | ||
|
|
14453f6b80 | ||
|
|
8c267150a9 | ||
|
|
ed60a78be0 | ||
|
|
82142b0cb1 | ||
|
|
d8949fedb2 | ||
|
|
cf72bd11a8 | ||
|
|
16ef7ea389 | ||
|
|
ff455e349e | ||
|
|
ba9e7f1a32 | ||
|
|
904498b20c | ||
|
|
409be7374c | ||
|
|
00c525e6ab | ||
|
|
a2f52c03a1 | ||
|
|
037df6b166 | ||
|
|
90ab5b4b0a | ||
|
|
7bbfa8c43f | ||
|
|
2fbcd158cc | ||
|
|
33c201800e | ||
|
|
9797a8d781 | ||
|
|
cee4c32551 | ||
|
|
081d8fb86a | ||
|
|
3dfd563d90 | ||
|
|
fd08cd219c | ||
|
|
6a15326d31 | ||
|
|
608ecc51b7 | ||
|
|
fcef26ebbb | ||
|
|
ba6d758ed5 | ||
|
|
43aa3e4e79 | ||
|
|
18f0e060a7 | ||
|
|
c7547e8baf | ||
|
|
ffff242abe | ||
|
|
b44c66b986 | ||
|
|
ae77ec6256 | ||
|
|
4f1e32b154 | ||
|
|
af37c2bfc5 | ||
|
|
5d4ab6f2ad | ||
|
|
0c9db53057 | ||
|
|
b689605ac2 | ||
|
|
baab91e371 | ||
|
|
70e69c7099 | ||
|
|
f4534fd3eb | ||
|
|
93594e1a65 | ||
|
|
b5e449ea54 | ||
|
|
0ff4b849aa | ||
|
|
b99e38043f | ||
|
|
28e3a59473 | ||
|
|
b20290fb60 | ||
|
|
2734beb6f8 | ||
|
|
345eec528c | ||
|
|
7a17e18a76 | ||
|
|
4697677457 | ||
|
|
7d8a5a1368 | ||
|
|
dd7402bd0e | ||
|
|
65592b0fc6 | ||
|
|
0ab66023a6 | ||
|
|
d6fad098ee | ||
|
|
1b5730d337 | ||
|
|
439b615b1b | ||
|
|
a8b30594dc | ||
|
|
9b6b1d328c | ||
|
|
ac2e2e4d69 | ||
|
|
3a5fb31860 | ||
|
|
c610eb8627 | ||
|
|
94980270c4 | ||
|
|
c685e002e3 | ||
|
|
1f665eed9e | ||
|
|
0c4fc1fd9a | ||
|
|
0fc033363e | ||
|
|
fb6e9538bc | ||
|
|
95b98fc4ed | ||
|
|
1840dafed0 | ||
|
|
1ad82b116c | ||
|
|
7bdd17504b | ||
|
|
2d820bb5d5 | ||
|
|
2e14512ed8 | ||
|
|
48ed583c6d | ||
|
|
dd444a17f3 | ||
|
|
2ea63c711e | ||
|
|
6d6d31db25 | ||
|
|
e9273dcb9b | ||
|
|
2bdc4562c6 | ||
|
|
9e8a5323e9 | ||
|
|
8e001063b3 | ||
|
|
dc88fab4c5 | ||
|
|
dfca917e50 | ||
|
|
ef7dfd6ca1 | ||
|
|
435c1b6d45 | ||
|
|
c5c9abe588 | ||
|
|
363735d36b | ||
|
|
2741e7701b | ||
|
|
3be442ea60 | ||
|
|
34cbeca201 | ||
|
|
b37e73ead6 | ||
|
|
ee775521d6 | ||
|
|
5f84aaef1b | ||
|
|
99ac58d999 | ||
|
|
f128a1e87d | ||
|
|
8c0768b451 | ||
|
|
319307136c | ||
|
|
a498452943 | ||
|
|
fb811faf5e | ||
|
|
1cb88115f6 | ||
|
|
94eec120da | ||
|
|
2457c30b94 | ||
|
|
544e5acaef | ||
|
|
9ab35bbaf9 | ||
|
|
01e1609a9f | ||
|
|
8bed35a8ba | ||
|
|
9f4ae60577 | ||
|
|
a66fa8e83f | ||
|
|
307886d4ae | ||
|
|
3987c4e681 | ||
|
|
e129e1da39 | ||
|
|
8acd32b0fc | ||
|
|
f8f99a5aaa | ||
|
|
70cfa58896 | ||
|
|
56571f9c1f | ||
|
|
4ab376d9ed | ||
|
|
2858ef3e93 | ||
|
|
3215b3942d |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,4 +10,3 @@ env
|
|||||||
.project
|
.project
|
||||||
.vscode/
|
.vscode/
|
||||||
docs/
|
docs/
|
||||||
package-lock.json
|
|
||||||
|
|||||||
13
.gitlab-ci.yml
Normal file
13
.gitlab-ci.yml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
image: docker:stable
|
||||||
|
services:
|
||||||
|
- docker:dind
|
||||||
|
|
||||||
|
stages:
|
||||||
|
- Build image
|
||||||
|
|
||||||
|
docker build:
|
||||||
|
stage: Build image
|
||||||
|
script:
|
||||||
|
- img build --build-arg branch=$CI_COMMIT_REF_NAME -t edcd/coriolis:$CI_COMMIT_REF_NAME .
|
||||||
|
- echo "$REGISTRY_PASSWORD" | img login --username "$REGISTRY_USER" --password-stdin
|
||||||
|
- img push edcd/coriolis:$CI_COMMIT_REF_NAME
|
||||||
@@ -21,6 +21,7 @@ RUN npm start
|
|||||||
|
|
||||||
# Set up coriolis
|
# Set up coriolis
|
||||||
WORKDIR /src/app/coriolis
|
WORKDIR /src/app/coriolis
|
||||||
|
RUN git checkout ${BRANCH}
|
||||||
RUN npm install --no-package-lock
|
RUN npm install --no-package-lock
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
|
|||||||
24
LICENSE.md
Normal file
24
LICENSE.md
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
All Data and [associated JSON](https://github.com/EDCD/coriolis-data) files are intellectual property and copyright of Frontier Developments plc ('Frontier', 'Frontier Developments') and are subject to their
|
||||||
|
[terms and conditions](https://www.frontierstore.net/terms-and-conditions/).
|
||||||
|
|
||||||
|
The code (Javascript, CSS, HTML, and SVG files only) specificially for Coriolis.io is released under the MIT License.
|
||||||
|
|
||||||
|
Copyright (c) 2015 Coriolis.io, Colin McLeod
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software (Javascript, CSS, HTML, and SVG files only), and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
@@ -29,6 +29,8 @@ Also see [the documentation site.](https://coriolis.willb.info/)
|
|||||||
|
|
||||||
See the [Data wiki](https://github.com/cmmcleod/coriolis-data/wiki) for details on structure, etc.
|
See the [Data wiki](https://github.com/cmmcleod/coriolis-data/wiki) for details on structure, etc.
|
||||||
|
|
||||||
|
You can find hosted and compiled versions of these data-jsons under https://coriolis.io/data/ and https://beta.coriolis.io/data/.
|
||||||
|
You might want to load these as depedency instead of reyling on the npm-dependency.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ version: '2.2'
|
|||||||
services:
|
services:
|
||||||
coriolis_prod:
|
coriolis_prod:
|
||||||
image: edcd/coriolis:master
|
image: edcd/coriolis:master
|
||||||
|
build:
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
branch: master
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- ./nginx.conf:/etc/nginx/nginx.conf
|
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||||
@@ -17,6 +21,10 @@ services:
|
|||||||
|
|
||||||
coriolis_dev:
|
coriolis_dev:
|
||||||
image: edcd/coriolis:develop
|
image: edcd/coriolis:develop
|
||||||
|
build:
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
branch: develop
|
||||||
restart: always
|
restart: always
|
||||||
volumes:
|
volumes:
|
||||||
- ./nginx.conf:/etc/nginx/nginx.conf
|
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||||
@@ -43,9 +51,6 @@ services:
|
|||||||
- "traefik.basic.port=80"
|
- "traefik.basic.port=80"
|
||||||
- "traefik.basic.protocol=http"
|
- "traefik.basic.protocol=http"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
web:
|
web:
|
||||||
external: true
|
external: true
|
||||||
|
|||||||
25347
package-lock.json
generated
Normal file
25347
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -123,11 +123,13 @@
|
|||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/polyfill": "^7.0.0",
|
"@babel/polyfill": "^7.0.0",
|
||||||
|
"auto-bind": "^2.1.1",
|
||||||
"browserify-zlib-next": "^1.0.1",
|
"browserify-zlib-next": "^1.0.1",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"coriolis-data": "../coriolis-data",
|
"coriolis-data": "../coriolis-data",
|
||||||
"d3": "^5.7.0",
|
"d3": "^5.7.0",
|
||||||
"detect-browser": "^3.0.1",
|
"detect-browser": "^3.0.1",
|
||||||
|
"ed-forge": "github:EDCD/ed-forge",
|
||||||
"fbemitter": "^2.1.1",
|
"fbemitter": "^2.1.1",
|
||||||
"lodash": "^4.17.11",
|
"lodash": "^4.17.11",
|
||||||
"lz-string": "^1.4.4",
|
"lz-string": "^1.4.4",
|
||||||
@@ -138,7 +140,7 @@
|
|||||||
"react-extras": "^0.7.1",
|
"react-extras": "^0.7.1",
|
||||||
"react-fuzzy": "^0.5.2",
|
"react-fuzzy": "^0.5.2",
|
||||||
"react-ga": "^2.5.3",
|
"react-ga": "^2.5.3",
|
||||||
"react-number-editor": "Athanasius/react-number-editor.git#miggy",
|
"react-number-editor": "^4.0.3",
|
||||||
"recharts": "^1.2.0",
|
"recharts": "^1.2.0",
|
||||||
"register-service-worker": "^1.5.2",
|
"register-service-worker": "^1.5.2",
|
||||||
"superagent": "^3.8.3"
|
"superagent": "^3.8.3"
|
||||||
|
|||||||
@@ -5,16 +5,14 @@ import { register } from 'register-service-worker';
|
|||||||
import { EventEmitter } from 'fbemitter';
|
import { EventEmitter } from 'fbemitter';
|
||||||
import { getLanguage } from './i18n/Language';
|
import { getLanguage } from './i18n/Language';
|
||||||
import Persist from './stores/Persist';
|
import Persist from './stores/Persist';
|
||||||
|
import { Ship } from 'ed-forge';
|
||||||
|
|
||||||
import Announcement from './components/Announcement';
|
import Announcement from './components/Announcement';
|
||||||
import Header from './components/Header';
|
import Header from './components/Header';
|
||||||
import Tooltip from './components/Tooltip';
|
import Tooltip from './components/Tooltip';
|
||||||
import ModalExport from './components/ModalExport';
|
|
||||||
import ModalHelp from './components/ModalHelp';
|
import ModalHelp from './components/ModalHelp';
|
||||||
import ModalImport from './components/ModalImport';
|
import ModalImport from './components/ModalImport';
|
||||||
import ModalPermalink from './components/ModalPermalink';
|
import ModalPermalink from './components/ModalPermalink';
|
||||||
import * as CompanionApiUtils from './utils/CompanionApiUtils';
|
|
||||||
import * as JournalUtils from './utils/JournalUtils';
|
|
||||||
import AboutPage from './pages/AboutPage';
|
import AboutPage from './pages/AboutPage';
|
||||||
import NotFoundPage from './pages/NotFoundPage';
|
import NotFoundPage from './pages/NotFoundPage';
|
||||||
import OutfittingPage from './pages/OutfittingPage';
|
import OutfittingPage from './pages/OutfittingPage';
|
||||||
@@ -22,7 +20,6 @@ import ComparisonPage from './pages/ComparisonPage';
|
|||||||
import ShipyardPage from './pages/ShipyardPage';
|
import ShipyardPage from './pages/ShipyardPage';
|
||||||
import ErrorDetails from './pages/ErrorDetails';
|
import ErrorDetails from './pages/ErrorDetails';
|
||||||
|
|
||||||
const zlib = require('pako');
|
|
||||||
const request = require('superagent');
|
const request = require('superagent');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,7 +58,6 @@ export default class Coriolis extends React.Component {
|
|||||||
this._onLanguageChange = this._onLanguageChange.bind(this);
|
this._onLanguageChange = this._onLanguageChange.bind(this);
|
||||||
this._onSizeRatioChange = this._onSizeRatioChange.bind(this);
|
this._onSizeRatioChange = this._onSizeRatioChange.bind(this);
|
||||||
this._keyDown = this._keyDown.bind(this);
|
this._keyDown = this._keyDown.bind(this);
|
||||||
this._importBuild = this._importBuild.bind(this);
|
|
||||||
|
|
||||||
this.emitter = new EventEmitter();
|
this.emitter = new EventEmitter();
|
||||||
this.state = {
|
this.state = {
|
||||||
@@ -93,19 +89,10 @@ export default class Coriolis extends React.Component {
|
|||||||
_importBuild(r) {
|
_importBuild(r) {
|
||||||
try {
|
try {
|
||||||
// Need to decode and gunzip the data, then build the ship
|
// Need to decode and gunzip the data, then build the ship
|
||||||
const data = zlib.inflate(new Buffer(r.params.data, 'base64'), { to: 'string' });
|
let ship = new Ship(r.params.data);
|
||||||
const json = JSON.parse(data);
|
r.params.ship = ship.getShipType();
|
||||||
console.info('Ship import data: ');
|
r.params.code = ship.compress();
|
||||||
console.info(json);
|
this._setPage(OutfittingPage, r);
|
||||||
let ship;
|
|
||||||
if (json && json.modules) {
|
|
||||||
ship = CompanionApiUtils.shipFromJson(json);
|
|
||||||
} else if (json && json.Modules) {
|
|
||||||
ship = JournalUtils.shipFromLoadoutJSON(json);
|
|
||||||
}
|
|
||||||
r.params.ship = ship.id;
|
|
||||||
r.params.code = ship.toString();
|
|
||||||
this._setPage(OutfittingPage, r)
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this._onError('Failed to import ship', r.path, 0, 0, err);
|
this._onError('Failed to import ship', r.path, 0, 0, err);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import Persist from './stores/Persist';
|
import Persist from './stores/Persist';
|
||||||
import ReactGA from 'react-ga';
|
|
||||||
|
|
||||||
ReactGA.initialize('UA-55840909-18');
|
|
||||||
let standalone = undefined;
|
let standalone = undefined;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -259,16 +257,8 @@ Route.prototype.match = function(path, params) {
|
|||||||
* @param {string} path Path to track
|
* @param {string} path Path to track
|
||||||
*/
|
*/
|
||||||
function gaTrack(path) {
|
function gaTrack(path) {
|
||||||
const match = path.match(/\/outfit\/(.*)(\?code=.*)/);
|
const _paq = window._paq || [];
|
||||||
if (match) {
|
_paq.push(['trackPageView']);
|
||||||
if (match[1]) {
|
|
||||||
ReactGA.ga('set', 'contentGroup1', match[1]);
|
|
||||||
}
|
|
||||||
if (match[2]) {
|
|
||||||
ReactGA.ga('set', 'contentGroup2', match[2]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ReactGA.pageview(path);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { autoBind } from 'react-extras';
|
|||||||
* Announcement component
|
* Announcement component
|
||||||
*/
|
*/
|
||||||
export default class Announcement extends React.Component {
|
export default class Announcement extends React.Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
text: PropTypes.string
|
text: PropTypes.string
|
||||||
};
|
};
|
||||||
@@ -27,5 +26,4 @@ export default class Announcement extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
return <div className="announcement" >{this.props.text}</div>;
|
return <div className="announcement" >{this.props.text}</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,115 +1,20 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
|
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
|
||||||
import FuzzySearch from 'react-fuzzy';
|
import FuzzySearch from 'react-fuzzy';
|
||||||
|
import { getModuleInfo } from 'ed-forge/lib/data/items';
|
||||||
|
import { groupBy, mapValues, sortBy } from 'lodash';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
|
||||||
const PRESS_THRESHOLD = 500; // mouse/touch down threshold
|
const PRESS_THRESHOLD = 500; // mouse/touch down threshold
|
||||||
|
|
||||||
/*
|
const MOUNT_MAP = {
|
||||||
* Categorisation of module groups
|
fixed: <MountFixed className={'lg'} />,
|
||||||
*/
|
gimbal: <MountGimballed className={'lg'} />,
|
||||||
const GRPCAT = {
|
turret: <MountTurret className={'lg'} />,
|
||||||
'sg': 'shields',
|
|
||||||
'bsg': 'shields',
|
|
||||||
'psg': 'shields',
|
|
||||||
'scb': 'shields',
|
|
||||||
'cc': 'limpet controllers',
|
|
||||||
'fx': 'limpet controllers',
|
|
||||||
'hb': 'limpet controllers',
|
|
||||||
'pc': 'limpet controllers',
|
|
||||||
'rpl': 'limpet controllers',
|
|
||||||
'pce': 'passenger cabins',
|
|
||||||
'pci': 'passenger cabins',
|
|
||||||
'pcm': 'passenger cabins',
|
|
||||||
'pcq': 'passenger cabins',
|
|
||||||
'fh': 'hangars',
|
|
||||||
'pv': 'hangars',
|
|
||||||
'fs': 'fuel',
|
|
||||||
'ft': 'fuel',
|
|
||||||
'hr': 'structural reinforcement',
|
|
||||||
'mrp': 'structural reinforcement',
|
|
||||||
'bl': 'lasers',
|
|
||||||
'pl': 'lasers',
|
|
||||||
'ul': 'lasers',
|
|
||||||
'ml': 'lasers',
|
|
||||||
'c': 'projectiles',
|
|
||||||
'mc': 'projectiles',
|
|
||||||
'axmc': 'experimental',
|
|
||||||
'fc': 'projectiles',
|
|
||||||
'rfl': 'experimental',
|
|
||||||
'pa': 'projectiles',
|
|
||||||
'rg': 'projectiles',
|
|
||||||
'mr': 'ordnance',
|
|
||||||
'axmr': 'experimental',
|
|
||||||
'rcpl': 'experimental',
|
|
||||||
'dtl': 'experimental',
|
|
||||||
'tbsc': 'experimental',
|
|
||||||
'tbem': 'experimental',
|
|
||||||
'tbrfl': 'experimental',
|
|
||||||
'mahr': 'experimental',
|
|
||||||
'rsl': 'experimental',
|
|
||||||
'tp': 'ordnance',
|
|
||||||
'nl': 'ordnance',
|
|
||||||
'sc': 'scanners',
|
|
||||||
'ss': 'scanners',
|
|
||||||
// Utilities
|
|
||||||
'cs': 'scanners',
|
|
||||||
'kw': 'scanners',
|
|
||||||
'ws': 'scanners',
|
|
||||||
'xs': 'scanners',
|
|
||||||
'ch': 'defence',
|
|
||||||
'po': 'defence',
|
|
||||||
'ec': 'defence',
|
|
||||||
'sfn': 'defence',
|
|
||||||
// Guardian
|
|
||||||
'gpp': 'guardian',
|
|
||||||
'gpc': 'guardian',
|
|
||||||
'gsrp': 'guardian',
|
|
||||||
'ggc': 'guardian',
|
|
||||||
'gfsb': 'guardian',
|
|
||||||
'gmrp': 'guardian',
|
|
||||||
'gsc': '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 = {
|
|
||||||
// Internals
|
|
||||||
'am': ['am'],
|
|
||||||
'cr': ['cr'],
|
|
||||||
'fi': ['fi'],
|
|
||||||
'fuel': ['ft', 'fs'],
|
|
||||||
'hangars': ['fh', 'pv'],
|
|
||||||
'limpet controllers': ['cc', 'fx', 'hb', 'pc', 'rpl'],
|
|
||||||
'passenger cabins': ['pce', 'pci', 'pcm', 'pcq'],
|
|
||||||
'rf': ['rf'],
|
|
||||||
'shields': ['sg', 'bsg', 'psg', 'scb'],
|
|
||||||
'structural reinforcement': ['hr', 'mrp'],
|
|
||||||
'dc': ['dc'],
|
|
||||||
// Hardpoints
|
|
||||||
'lasers': ['pl', 'ul', 'bl'],
|
|
||||||
'projectiles': ['mc', 'c', 'fc', 'pa', 'rg'],
|
|
||||||
'ordnance': ['mr', 'tp', 'nl'],
|
|
||||||
// Utilities
|
|
||||||
'sb': ['sb'],
|
|
||||||
'hs': ['hs'],
|
|
||||||
'defence': ['ch', 'po', 'ec'],
|
|
||||||
'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners
|
|
||||||
// Experimental
|
|
||||||
'experimental': ['axmc', 'axmr', 'rfl', 'tbrfl', 'tbsc', 'tbem', 'xs', 'sfn', 'rcpl', 'dtl', 'rsl', 'mahr',],
|
|
||||||
|
|
||||||
// Guardian
|
|
||||||
'guardian': ['gpp', 'gpd', 'gpc', 'ggc', 'gsrp', 'gfsb', 'ghrp', 'gmrp', 'gsc'],
|
|
||||||
|
|
||||||
'mining': ['ml', 'scl', 'pwa', 'sdm', 'abl'],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -117,22 +22,14 @@ const CATEGORIES = {
|
|||||||
*/
|
*/
|
||||||
export default class AvailableModulesMenu extends TranslatedComponent {
|
export default class AvailableModulesMenu extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
modules: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
|
|
||||||
onSelect: PropTypes.func.isRequired,
|
onSelect: PropTypes.func.isRequired,
|
||||||
diffDetails: PropTypes.func,
|
diffDetails: PropTypes.func,
|
||||||
|
hideSearch: PropTypes.bool,
|
||||||
m: PropTypes.object,
|
m: PropTypes.object,
|
||||||
shipMass: PropTypes.number,
|
|
||||||
warning: PropTypes.func,
|
warning: PropTypes.func,
|
||||||
firstSlotId: PropTypes.string,
|
|
||||||
lastSlotId: PropTypes.string,
|
|
||||||
activeSlotId: PropTypes.string,
|
|
||||||
slotDiv: PropTypes.object
|
slotDiv: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
shipMass: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
@@ -140,10 +37,8 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
this._hideDiff = this._hideDiff.bind(this);
|
autoBind(this);
|
||||||
this._showSearch = this._showSearch.bind(this);
|
|
||||||
this.state = this._initState(props, context);
|
this.state = this._initState(props, context);
|
||||||
this.slotItems = [];// Array to hold <li> refs.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -153,165 +48,103 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
|||||||
* @return {Object} list: Array of React Components, currentGroup Component if any
|
* @return {Object} list: Array of React Components, currentGroup Component if any
|
||||||
*/
|
*/
|
||||||
_initState(props, context) {
|
_initState(props, context) {
|
||||||
let translate = context.language.translate;
|
const { translate } = context.language;
|
||||||
let { m, warning, shipMass, onSelect, modules, firstSlotId, lastSlotId } = props;
|
const { m } = props;
|
||||||
let list, currentGroup;
|
const list = [], fuzzy = [];
|
||||||
|
let currentGroup;
|
||||||
|
|
||||||
let buildGroup = this._buildGroup.bind(
|
const modules = m.getApplicableItems().map(getModuleInfo);
|
||||||
this,
|
const groups = mapValues(
|
||||||
translate,
|
groupBy(modules, (info) => info.meta.group),
|
||||||
m,
|
(infos) => groupBy(infos, (info) => info.meta.type),
|
||||||
warning,
|
|
||||||
shipMass - (m && m.mass ? m.mass : 0),
|
|
||||||
(m, event) => {
|
|
||||||
this._hideDiff(event);
|
|
||||||
onSelect(m);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
let fuzzy = [];
|
// Build categories sorted by translated category name
|
||||||
if (modules instanceof Array) {
|
const groupKeys = sortBy(Object.keys(groups), translate);
|
||||||
list = buildGroup(modules[0].grp, modules);
|
for (const group of groupKeys) {
|
||||||
} else {
|
const groupName = translate(group);
|
||||||
list = [];
|
if (groupKeys.length > 1) {
|
||||||
// At present time slots with grouped options (Hardpoints and Internal) can be empty
|
list.push(<div key={`group-${group}`} className="select-category upp">{groupName}</div>);
|
||||||
if (m) {
|
|
||||||
let emptyId = 'empty';
|
|
||||||
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>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to regroup the modules by our own categorisation
|
const categories = groups[group];
|
||||||
let catmodules = {};
|
const categoryKeys = sortBy(Object.keys(categories), translate);
|
||||||
// Pre-create to preserve ordering
|
for (const category of categoryKeys) {
|
||||||
for (let cat in CATEGORIES) {
|
const categoryName = translate(category);
|
||||||
catmodules[cat] = [];
|
const infos = categories[category];
|
||||||
|
if (categoryKeys.length > 1) {
|
||||||
|
list.push(<div key={`category-${category}`} className="select-group cap">{categoryName}</div>);
|
||||||
}
|
}
|
||||||
for (let g in modules) {
|
list.push(
|
||||||
const moduleCategory = GRPCAT[g] || g;
|
this._buildGroup(
|
||||||
const existing = catmodules[moduleCategory] || [];
|
m,
|
||||||
catmodules[moduleCategory] = existing.concat(modules[g]);
|
category,
|
||||||
}
|
infos,
|
||||||
|
),
|
||||||
for (let category in catmodules) {
|
);
|
||||||
let categoryHeader = false;
|
fuzzy.push(
|
||||||
// Order through CATEGORIES if present
|
...infos.map((info) => {
|
||||||
const categories = CATEGORIES[category] || [category];
|
const { meta } = info;
|
||||||
if (categories && categories.length) {
|
const mount = meta.mount ? ' ' + translate(meta.mount) : '';
|
||||||
for (let n in categories) {
|
return {
|
||||||
const grp = categories[n];
|
grp: groupName,
|
||||||
// We now have the group and the category. We might not have any modules, though...
|
cat: categoryName,
|
||||||
if (modules[grp]) {
|
m: info.proto.Item,
|
||||||
// Decide if we need a category header as well as a group header
|
name: `${meta.class}${meta.rating}${mount} ${categoryName}`,
|
||||||
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>);
|
|
||||||
} else {
|
|
||||||
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Show category header as well as group header
|
|
||||||
if (!categoryHeader) {
|
|
||||||
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
|
|
||||||
categoryHeader = true;
|
|
||||||
}
|
|
||||||
if (m && grp == m.grp) {
|
|
||||||
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]));
|
return { list, currentGroup, fuzzy, trackingFocus: false };
|
||||||
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, fuzzy, trackingFocus };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate React Components for Module Group
|
* Generate React Components for Module Group
|
||||||
* @param {Function} translate Translate function
|
|
||||||
* @param {Object} mountedModule Mounted Module
|
* @param {Object} mountedModule Mounted Module
|
||||||
* @param {Function} warningFunc Warning function
|
* @param {String} category Category key
|
||||||
* @param {number} mass Mass
|
|
||||||
* @param {function} onSelect Select/Mount callback
|
|
||||||
* @param {string} grp Group name
|
|
||||||
* @param {Array} modules Available modules
|
* @param {Array} modules Available modules
|
||||||
* @param {string} firstSlotId id of first slot item
|
|
||||||
* @param {string} lastSlotId id of last slot item
|
|
||||||
* @return {React.Component} Available Module Group contents
|
* @return {React.Component} Available Module Group contents
|
||||||
*/
|
*/
|
||||||
_buildGroup(translate, mountedModule, warningFunc, mass, onSelect, grp, modules, firstSlotId, lastSlotId) {
|
_buildGroup(mountedModule, category, modules) {
|
||||||
let prevClass = null, prevRating = null, prevName;
|
const { warning } = this.props;
|
||||||
let elems = [];
|
const ship = mountedModule.getShip();
|
||||||
|
const classMapping = groupBy(modules, (info) => info.meta.class);
|
||||||
|
|
||||||
const sortedModules = modules.sort(this._moduleOrder);
|
const itemsPerClass = Math.max(
|
||||||
|
...Object.values(classMapping).map((l) => l.length),
|
||||||
|
);
|
||||||
|
const itemsPerRow = itemsPerClass <= 2 ? 6 : itemsPerClass;
|
||||||
|
// Nested array of <li> elements; will be flattened before being rendered.
|
||||||
|
// Each sub-array represents one row in the final view.
|
||||||
|
const elems = [[]];
|
||||||
|
|
||||||
// Calculate the number of items per class. Used so we don't have long lists with only a few items in each row
|
// Reverse sort for descending order of module class
|
||||||
const tmp = sortedModules.map((v, i) => v['class']).reduce((count, cls) => {
|
for (const clazz of Object.keys(classMapping).sort().reverse()) {
|
||||||
count[cls] = ++count[cls] || 1;
|
for (let info of sortBy(
|
||||||
return count;
|
classMapping[clazz],
|
||||||
}, {});
|
(info) => info.meta.mount || info.meta.rating,
|
||||||
const itemsPerClass = Math.max.apply(null, Object.keys(tmp).map(key => tmp[key]));
|
)) {
|
||||||
|
const { meta } = info;
|
||||||
|
const { Item } = info.proto;
|
||||||
|
|
||||||
let itemsOnThisRow = 0;
|
// Can only be true if shieldgenmaximalmass is defined, i.e. this
|
||||||
for (let i = 0; i < sortedModules.length; i++) {
|
// module must be a shield generator
|
||||||
let m = sortedModules[i];
|
let disabled = info.props.shieldgenmaximalmass < ship.readProp('hullmass');
|
||||||
let mount = null;
|
if (meta.experimental && !mountedModule.readMeta('experimental')) {
|
||||||
let disabled = false;
|
disabled =
|
||||||
prevName = m.name;
|
4 <=
|
||||||
if (ModuleUtils.isShieldGenerator(m.grp)) {
|
ship.getHardpoints().filter((m) => m.readMeta('experimental'))
|
||||||
// Shield generators care about maximum hull mass
|
.length;
|
||||||
disabled = mass > m.maxmass;
|
|
||||||
} else if (m.maxmass) {
|
|
||||||
// Thrusters care about total mass
|
|
||||||
disabled = mass + m.mass > m.maxmass;
|
|
||||||
}
|
}
|
||||||
let active = mountedModule && mountedModule.id === m.id;
|
|
||||||
let classes = cn(m.name ? 'lc' : 'c', {
|
|
||||||
warning: !disabled && warningFunc && warningFunc(m),
|
|
||||||
active,
|
|
||||||
disabled
|
|
||||||
});
|
|
||||||
let eventHandlers;
|
|
||||||
|
|
||||||
if (disabled) {
|
|
||||||
eventHandlers = {
|
|
||||||
onKeyDown: this._keyDown.bind(this, null),
|
|
||||||
onKeyUp: this._keyUp.bind(this, null)
|
|
||||||
|
|
||||||
|
// Default event handlers for objects that are disabled
|
||||||
|
let eventHandlers = {};
|
||||||
|
if (!disabled) {
|
||||||
|
const showDiff = this._showDiff.bind(this, mountedModule, info);
|
||||||
|
const select = (event) => {
|
||||||
|
this._hideDiff(event);
|
||||||
|
this.props.onSelect(Item);
|
||||||
};
|
};
|
||||||
} else {
|
|
||||||
/**
|
|
||||||
* Get the ids of the first and last <li> elements in the <ul> that are focusable (i.e. are not active or disabled)
|
|
||||||
* Will be used to keep focus inside the <ul> on Tab and Shift-Tab while it is visible
|
|
||||||
*/
|
|
||||||
if (this.firstSlotId == null) this.firstSlotId = sortedModules[i].id;
|
|
||||||
if (active) this.activeSlotId = sortedModules[i].id;
|
|
||||||
this.lastSlotId = sortedModules[i].id;
|
|
||||||
|
|
||||||
let showDiff = this._showDiff.bind(this, mountedModule, m);
|
|
||||||
let select = onSelect.bind(null, m);
|
|
||||||
|
|
||||||
eventHandlers = {
|
eventHandlers = {
|
||||||
onMouseEnter: this._over.bind(this, showDiff),
|
onMouseEnter: this._over.bind(this, showDiff),
|
||||||
@@ -319,67 +152,67 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
|||||||
onTouchEnd: this._touchEnd.bind(this, select),
|
onTouchEnd: this._touchEnd.bind(this, select),
|
||||||
onMouseLeave: this._hideDiff,
|
onMouseLeave: this._hideDiff,
|
||||||
onClick: select,
|
onClick: select,
|
||||||
onKeyDown: this._keyDown.bind(this, select),
|
|
||||||
onKeyUp: this._keyUp.bind(this, select)
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (m.mount) {
|
const mountSymbol = MOUNT_MAP[meta.mount];
|
||||||
case 'F':
|
const li = (
|
||||||
mount = <MountFixed className={'lg'}/>;
|
<li key={Item} data-id={Item}
|
||||||
break;
|
ref={Item === mountedModule.getItem() ? (ref) => { this.activeSlotRef = ref; } : undefined}
|
||||||
case 'G':
|
className={cn(meta.type === 'armour' ? 'lc' : 'c', {
|
||||||
mount = <MountGimballed className={'lg'}/>;
|
warning: !disabled && warning && warning(info),
|
||||||
break;
|
active: mountedModule.getItem() === Item,
|
||||||
case 'T':
|
disabled,
|
||||||
mount = <MountTurret className={'lg'}/>;
|
hardpoint: mountSymbol,
|
||||||
break;
|
})}
|
||||||
}
|
{...eventHandlers}
|
||||||
if (m.name && m.name === prevName) {
|
>{mountSymbol}{meta.type === 'armour' ? Item : `${meta.class}${meta.rating}`}</li>
|
||||||
// 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}/>);
|
|
||||||
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}>
|
|
||||||
{mount}
|
|
||||||
{(mount ? ' ' : '') + m.class + m.rating + (m.missile ? '/' + m.missile : '') + (m.name ? ' ' + translate(m.name) : '')}
|
|
||||||
</li>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
itemsOnThisRow++;
|
const tail = elems.pop();
|
||||||
prevClass = m.class;
|
let newTail = [tail];
|
||||||
prevRating = m.rating;
|
if (tail.length < itemsPerRow) {
|
||||||
prevName = m.name;
|
// If the row has not grown too long, the new <li> element can be
|
||||||
|
// added to the row itself
|
||||||
|
tail.push(li);
|
||||||
|
} else {
|
||||||
|
// Otherwise, the last row gets a line break element added and this
|
||||||
|
// item is put into a new row
|
||||||
|
tail.push(<br key={elems.length}/>);
|
||||||
|
newTail.push([li]);
|
||||||
}
|
}
|
||||||
return <ul key={'modules' + grp}>{elems}</ul>;
|
elems.push(...newTail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ul key={'ul' + category}>{[].concat(...elems)}</ul>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate tooltip content for the difference between the
|
* Generate tooltip content for the difference between the
|
||||||
* mounted module and the hovered modules
|
* mounted module and the hovered modules
|
||||||
* @param {Object} mm The module mounet currently
|
* @param {Object} mountedModule The module mounted currently
|
||||||
* @param {Object} m The hovered module
|
* @param {Object} hoveringModule The hovered module
|
||||||
* @param {DOMRect} rect DOMRect for target element
|
* @param {DOMRect} rect DOMRect for target element
|
||||||
*/
|
*/
|
||||||
_showDiff(mm, m, rect) {
|
_showDiff(mountedModule, hoveringModule, rect) {
|
||||||
if (this.props.diffDetails) {
|
if (this.props.diffDetails) {
|
||||||
this.touchTimeout = null;
|
this.touchTimeout = null;
|
||||||
this.context.tooltip(this.props.diffDetails(m, mm), rect);
|
// TODO:
|
||||||
|
// this.context.tooltip(
|
||||||
|
// this.props.diffDetails(hoveringModule, mountedModule),
|
||||||
|
// rect,
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate tooltip content for the difference between the
|
* Generate tooltip content for the difference between the
|
||||||
* mounted module and the hovered modules
|
* mounted module and the hovered modules
|
||||||
|
* @returns {React.Component} Search component if available
|
||||||
*/
|
*/
|
||||||
_showSearch() {
|
_showSearch() {
|
||||||
if (this.props.modules instanceof Array) {
|
if (this.props.hideSearch) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@@ -442,41 +275,6 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
|||||||
this._hideDiff();
|
this._hideDiff();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Key down - select module on Enter key, move to next/previous module on Tab/Shift-Tab, close on Esc
|
|
||||||
* @param {Function} select Select module callback
|
|
||||||
* @param {SyntheticEvent} event Event
|
|
||||||
*/
|
|
||||||
_keyDown(select, event) {
|
|
||||||
let className = event.currentTarget.attributes['class'].value;
|
|
||||||
if (event.key == 'Enter' && className.indexOf('disabled') < 0 && className.indexOf('active') < 0) {
|
|
||||||
select();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let elemId = event.currentTarget.attributes['data-id'].value;
|
|
||||||
if (className.indexOf('disabled') < 0 && event.key == 'Tab') {
|
|
||||||
if (event.shiftKey && elemId == this.firstSlotId) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.slotItems[this.lastSlotId].focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!event.shiftKey && elemId == this.lastSlotId) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.slotItems[this.firstSlotId].focus();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Key Up
|
|
||||||
* @param {Function} select Select module callback
|
|
||||||
* @param {SytheticEvent} event Event
|
|
||||||
*/
|
|
||||||
_keyUp(select, event) {
|
|
||||||
// nothing here yet
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hide diff tooltip
|
* Hide diff tooltip
|
||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
@@ -487,60 +285,12 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
|||||||
this.context.tooltip();
|
this.context.tooltip();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Order two modules suitably for display in module selection
|
|
||||||
* @param {Object} a the first module
|
|
||||||
* @param {Object} b the second module
|
|
||||||
* @return {int} -1 if the first module should go first, 1 if the second module should go first
|
|
||||||
*/
|
|
||||||
_moduleOrder(a, b) {
|
|
||||||
// Named modules go last
|
|
||||||
if (!a.name && b.name) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (a.name && !b.name) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
// Class ordered from highest (8) to lowest (1)
|
|
||||||
if (a.class < b.class) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (a.class > b.class) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
// Mount type, if applicable
|
|
||||||
if (a.mount && b.mount && a.mount !== b.mount) {
|
|
||||||
if (a.mount === 'F' || (a.mount === 'G' && b.mount === 'T')) {
|
|
||||||
return -1;
|
|
||||||
} else {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Rating ordered from highest (A) to lowest (E)
|
|
||||||
if (a.rating < b.rating) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (a.rating > b.rating) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
// Do not attempt to order by name at this point, as that mucks up the order of armour
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scroll to mounted (if it exists) module group on mount
|
* Scroll to mounted (if it exists) module group on mount
|
||||||
*/
|
*/
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
if (this.groupElem) { // Scroll to currently selected group
|
if (this.activeSlotRef) {
|
||||||
this.node.scrollTop = this.groupElem.offsetTop;
|
this.activeSlotRef.focus();
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Set focus on active or first slot element, if applicable.
|
|
||||||
*/
|
|
||||||
if (this.slotItems[this.activeSlotId]) {
|
|
||||||
this.slotItems[this.activeSlotId].focus();
|
|
||||||
} else if (this.slotItems[this.firstSlotId]) {
|
|
||||||
this.slotItems[this.firstSlotId].focus();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Boost displays a boost button that toggles bosot
|
* Boost displays a boost button that toggles bosot
|
||||||
@@ -8,8 +9,6 @@ import TranslatedComponent from './TranslatedComponent';
|
|||||||
*/
|
*/
|
||||||
export default class Boost extends TranslatedComponent {
|
export default class Boost extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
marker: PropTypes.string.isRequired,
|
|
||||||
ship: PropTypes.object.isRequired,
|
|
||||||
boost: PropTypes.bool.isRequired,
|
boost: PropTypes.bool.isRequired,
|
||||||
onChange: PropTypes.func.isRequired
|
onChange: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
@@ -19,12 +18,9 @@ export default class Boost extends TranslatedComponent {
|
|||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
* @param {Object} context React Component context
|
* @param {Object} context React Component context
|
||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
const { ship, boost } = props;
|
autoBind(this);
|
||||||
|
|
||||||
this._keyDown = this._keyDown.bind(this);
|
|
||||||
this._toggleBoost = this._toggleBoost.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -70,13 +66,12 @@ export default class Boost extends TranslatedComponent {
|
|||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { formats, translate, units } = this.context.language;
|
const { translate } = this.context.language;
|
||||||
const { ship, boost } = this.props;
|
|
||||||
|
|
||||||
// TODO disable if ship cannot boost
|
|
||||||
return (
|
return (
|
||||||
<span id='boost'>
|
<span id='boost'>
|
||||||
<button id='boost' className={boost ? 'selected' : null} onClick={this._toggleBoost}>{translate('boost')}</button>
|
<button id='boost' className={this.props.boost ? 'selected' : null} onClick={this._toggleBoost}>
|
||||||
|
{translate('boost')}
|
||||||
|
</button>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import Slider from '../components/Slider';
|
import Slider from '../components/Slider';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cargo slider
|
* Cargo slider
|
||||||
@@ -21,8 +22,7 @@ export default class Cargo extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
this._cargoChange = this._cargoChange.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import Persist from '../stores/Persist';
|
import Persist from '../stores/Persist';
|
||||||
import Ship from '../shipyard/Ship';
|
import { Factory, Ship } from 'ed-forge';
|
||||||
import { Insurance } from '../shipyard/Constants';
|
import { Insurance } from '../shipyard/Constants';
|
||||||
import { slotName, slotComparator } from '../utils/SlotFunctions';
|
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { ShoppingIcon } from '../components/SvgIcons';
|
import { ShoppingIcon } from './SvgIcons';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
import { assign, differenceBy, sortBy, reverse } from 'lodash';
|
||||||
|
import { FUEL_CAPACITY } from 'ed-forge/lib/ship-stats';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cost Section
|
* Cost Section
|
||||||
@@ -16,7 +17,7 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
code: PropTypes.string.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
buildName: PropTypes.string
|
buildName: PropTypes.string,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,71 +26,34 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this._costsTab = this._costsTab.bind(this);
|
autoBind(this);
|
||||||
this._sortCost = this._sortCost.bind(this);
|
|
||||||
this._sortAmmo = this._sortAmmo.bind(this);
|
|
||||||
this._sortRetrofit = this._sortRetrofit.bind(this);
|
|
||||||
this._buildRetrofitShip = this._buildRetrofitShip.bind(this);
|
|
||||||
this._onBaseRetrofitChange = this._onBaseRetrofitChange.bind(this);
|
|
||||||
this._defaultRetrofitName = this._defaultRetrofitName.bind(this);
|
|
||||||
this._eddbShoppingList = this._eddbShoppingList.bind(this);
|
|
||||||
|
|
||||||
let data = Ships[props.ship.id]; // Retrieve the basic ship properties, slots and defaults
|
|
||||||
let retrofitName = this._defaultRetrofitName(props.ship.id, props.buildName);
|
|
||||||
let retrofitShip = this._buildRetrofitShip(props.ship.id, retrofitName);
|
|
||||||
let shipDiscount = Persist.getShipDiscount();
|
|
||||||
let moduleDiscount = Persist.getModuleDiscount();
|
|
||||||
|
|
||||||
this.props.ship.applyDiscounts(shipDiscount, moduleDiscount);
|
|
||||||
retrofitShip.applyDiscounts(shipDiscount, moduleDiscount);
|
|
||||||
|
|
||||||
|
const { ship, buildName } = props;
|
||||||
|
this.shipType = ship.getShipType();
|
||||||
this.state = {
|
this.state = {
|
||||||
retrofitShip,
|
retrofitName: Persist.hasBuild(ship.getShipType(), buildName) ? buildName : null,
|
||||||
retrofitName,
|
shipDiscount: Persist.getShipDiscount(),
|
||||||
shipDiscount,
|
moduleDiscount: Persist.getModuleDiscount(),
|
||||||
moduleDiscount,
|
|
||||||
insurance: Insurance[Persist.getInsurance()],
|
insurance: Insurance[Persist.getInsurance()],
|
||||||
tab: Persist.getCostTab(),
|
tab: Persist.getCostTab(),
|
||||||
buildOptions: Persist.getBuildsNamesFor(props.ship.id),
|
buildOptions: Persist.getBuildsNamesFor(ship.getShipType()),
|
||||||
ammoPredicate: 'cr',
|
predicate: 'cr',
|
||||||
ammoDesc: true,
|
desc: true,
|
||||||
costPredicate: 'cr',
|
excluded: {},
|
||||||
costDesc: true,
|
|
||||||
retroPredicate: 'cr',
|
|
||||||
retroDesc: true
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a ship instance to base/reference retrofit changes from
|
* Create a ship instance to base/reference retrofit changes from
|
||||||
* @param {string} shipId Ship Id
|
|
||||||
* @param {string} name Build name
|
|
||||||
* @param {Ship} retrofitShip Existing retrofit ship
|
|
||||||
* @return {Ship} Retrofit ship
|
* @return {Ship} Retrofit ship
|
||||||
*/
|
*/
|
||||||
_buildRetrofitShip(shipId, name, retrofitShip) {
|
_buildRetrofitShip() {
|
||||||
let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults
|
const { retrofitName } = this.state;
|
||||||
|
if (Persist.hasBuild(this.shipType, retrofitName)) {
|
||||||
if (!retrofitShip) { // Don't create a new instance unless needed
|
return new Ship(Persist.getBuild(this.shipType, retrofitName));
|
||||||
retrofitShip = new Ship(shipId, data.properties, data.slots); // Create a new Ship for retrofit comparison
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Persist.hasBuild(shipId, name)) {
|
|
||||||
retrofitShip.buildFrom(Persist.getBuild(shipId, name)); // Populate modules from existing build
|
|
||||||
} else {
|
} else {
|
||||||
retrofitShip.buildWith(data.defaults); // Populate with default components
|
return Factory.newShip(this.shipType);
|
||||||
}
|
}
|
||||||
return retrofitShip;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the default retrofit build name if it exists
|
|
||||||
* @param {string} shipId Ship Id
|
|
||||||
* @param {string} name Build name
|
|
||||||
* @return {string} Build name or null
|
|
||||||
*/
|
|
||||||
_defaultRetrofitName(shipId, name) {
|
|
||||||
return Persist.hasBuild(shipId, name) ? name : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -107,9 +71,6 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
_onDiscountChanged() {
|
_onDiscountChanged() {
|
||||||
let shipDiscount = Persist.getShipDiscount();
|
let shipDiscount = Persist.getShipDiscount();
|
||||||
let moduleDiscount = Persist.getModuleDiscount();
|
let moduleDiscount = Persist.getModuleDiscount();
|
||||||
this.props.ship.applyDiscounts(shipDiscount, moduleDiscount);
|
|
||||||
this.state.retrofitShip.applyDiscounts(shipDiscount, moduleDiscount);
|
|
||||||
this._updateRetrofit(this.props.ship, this.state.retrofitShip);
|
|
||||||
this.setState({ shipDiscount, moduleDiscount });
|
this.setState({ shipDiscount, moduleDiscount });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,156 +87,33 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
* @param {SyntheticEvent} event Build name to base the retrofit ship on
|
* @param {SyntheticEvent} event Build name to base the retrofit ship on
|
||||||
*/
|
*/
|
||||||
_onBaseRetrofitChange(event) {
|
_onBaseRetrofitChange(event) {
|
||||||
let retrofitName = event.target.value;
|
this.setState({ retrofitName: event.target.value });
|
||||||
let ship = this.props.ship;
|
|
||||||
|
|
||||||
if (retrofitName) {
|
|
||||||
this.state.retrofitShip.buildFrom(Persist.getBuild(ship.id, retrofitName));
|
|
||||||
} else {
|
|
||||||
this.state.retrofitShip.buildWith(Ships[ship.id].defaults); // Retrofit ship becomes stock build
|
|
||||||
}
|
|
||||||
this._updateRetrofit(ship, this.state.retrofitShip);
|
|
||||||
this.setState({ retrofitName });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On builds changed check to see if the retrofit ship needs
|
* Toggle item cost inclusion
|
||||||
* to be updated
|
* @param {String} key Key of the row to toggle
|
||||||
*/
|
*/
|
||||||
_onBuildsChanged() {
|
_toggleExcluded(key) {
|
||||||
let update = false;
|
let { excluded } = this.state;
|
||||||
let ship = this.props.ship;
|
excluded = assign({}, excluded);
|
||||||
let { retrofitName, retrofitShip } = this.state;
|
const slotExcluded = excluded[key];
|
||||||
|
excluded[key] = (slotExcluded === undefined ? true : !slotExcluded);
|
||||||
if(!Persist.hasBuild(ship.id, retrofitName)) {
|
this.setState({ excluded });
|
||||||
retrofitShip.buildWith(Ships[ship.id].defaults); // Retrofit ship becomes stock build
|
|
||||||
this.setState({ retrofitName: null });
|
|
||||||
update = true;
|
|
||||||
} else if (Persist.getBuild(ship.id, retrofitName) != retrofitShip.toString()) {
|
|
||||||
retrofitShip.buildFrom(Persist.getBuild(ship.id, retrofitName)); // Repopulate modules from saved build
|
|
||||||
update = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (update) { // Update retrofit comparison
|
|
||||||
this._updateRetrofit(ship, retrofitShip);
|
|
||||||
}
|
|
||||||
// Update list of retrofit base build options
|
|
||||||
this.setState({ buildOptions: Persist.getBuildsNamesFor(ship.id) });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle item cost inclusion in overall total
|
* Set list sort predicate
|
||||||
* @param {Object} item Cost item
|
* @param {string} newPredicate sort predicate
|
||||||
*/
|
*/
|
||||||
_toggleCost(item) {
|
_sortBy(newPredicate) {
|
||||||
this.props.ship.setCostIncluded(item, !item.incCost);
|
let { predicate, desc } = this.state;
|
||||||
this.forceUpdate();
|
|
||||||
|
if (newPredicate == predicate) {
|
||||||
|
desc = !desc;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
this.setState({ predicate: newPredicate, desc });
|
||||||
* Toggle item cost inclusion in retrofit total
|
|
||||||
* @param {Object} item Cost item
|
|
||||||
*/
|
|
||||||
_toggleRetrofitCost(item) {
|
|
||||||
let retrofitTotal = this.state.retrofitTotal;
|
|
||||||
item.retroItem.incCost = !item.retroItem.incCost;
|
|
||||||
retrofitTotal += item.netCost * (item.retroItem.incCost ? 1 : -1);
|
|
||||||
this.setState({ retrofitTotal });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set cost list sort predicate
|
|
||||||
* @param {string} predicate sort predicate
|
|
||||||
*/
|
|
||||||
_sortCostBy(predicate) {
|
|
||||||
let { costPredicate, costDesc } = this.state;
|
|
||||||
|
|
||||||
if (costPredicate == predicate) {
|
|
||||||
costDesc = !costDesc;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ costPredicate: predicate, costDesc });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort cost list
|
|
||||||
* @param {Ship} ship Ship instance
|
|
||||||
* @param {string} predicate Sort predicate
|
|
||||||
* @param {Boolean} desc Sort descending
|
|
||||||
*/
|
|
||||||
_sortCost(ship, predicate, desc) {
|
|
||||||
let costList = ship.costList;
|
|
||||||
let translate = this.context.language.translate;
|
|
||||||
|
|
||||||
if (predicate == 'm') {
|
|
||||||
costList.sort(slotComparator(translate, null, desc));
|
|
||||||
} else {
|
|
||||||
costList.sort(slotComparator(translate, (a, b) => (a.m.cost || 0) - (b.m.cost || 0), desc));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set ammo list sort predicate
|
|
||||||
* @param {string} predicate sort predicate
|
|
||||||
*/
|
|
||||||
_sortAmmoBy(predicate) {
|
|
||||||
let { ammoPredicate, ammoDesc } = this.state;
|
|
||||||
|
|
||||||
if (ammoPredicate == predicate) {
|
|
||||||
ammoDesc = !ammoDesc;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ ammoPredicate: predicate, ammoDesc });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort ammo cost list
|
|
||||||
* @param {Array} ammoCosts Ammo cost list
|
|
||||||
* @param {string} predicate Sort predicate
|
|
||||||
* @param {Boolean} desc Sort descending
|
|
||||||
*/
|
|
||||||
_sortAmmo(ammoCosts, predicate, desc) {
|
|
||||||
let translate = this.context.language.translate;
|
|
||||||
|
|
||||||
if (predicate == 'm') {
|
|
||||||
ammoCosts.sort(slotComparator(translate, null, desc));
|
|
||||||
} else {
|
|
||||||
ammoCosts.sort(slotComparator(translate, (a, b) => a[predicate] - b[predicate], desc));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set retrofit list sort predicate
|
|
||||||
* @param {string} predicate sort predicate
|
|
||||||
*/
|
|
||||||
_sortRetrofitBy(predicate) {
|
|
||||||
let { retroPredicate, retroDesc } = this.state;
|
|
||||||
|
|
||||||
if (retroPredicate == predicate) {
|
|
||||||
retroDesc = !retroDesc;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ retroPredicate: predicate, retroDesc });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort retrofit cost list
|
|
||||||
* @param {Array} retrofitCosts Retrofit cost list
|
|
||||||
* @param {string} predicate Sort predicate
|
|
||||||
* @param {Boolean} desc Sort descending
|
|
||||||
*/
|
|
||||||
_sortRetrofit(retrofitCosts, predicate, desc) {
|
|
||||||
let translate = this.context.language.translate;
|
|
||||||
|
|
||||||
if (predicate == 'cr') {
|
|
||||||
retrofitCosts.sort((a, b) => a.netCost - b.netCost);
|
|
||||||
} else {
|
|
||||||
retrofitCosts.sort((a , b) => (a[predicate] ? translate(a[predicate]).toLowerCase() : '').localeCompare(b[predicate] ? translate(b[predicate]).toLowerCase() : ''));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!desc) {
|
|
||||||
retrofitCosts.reverse();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -284,18 +122,34 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
_costsTab() {
|
_costsTab() {
|
||||||
let { ship } = this.props;
|
let { ship } = this.props;
|
||||||
let { shipDiscount, moduleDiscount, insurance } = this.state;
|
let {
|
||||||
|
excluded, shipDiscount, moduleDiscount, insurance, desc, predicate
|
||||||
|
} = this.state;
|
||||||
let { translate, formats, units } = this.context.language;
|
let { translate, formats, units } = this.context.language;
|
||||||
let rows = [];
|
let rows = [];
|
||||||
|
|
||||||
for (let i = 0, l = ship.costList.length; i < l; i++) {
|
let modules = sortBy(
|
||||||
let item = ship.costList[i];
|
ship.getModules(),
|
||||||
if (item.m && item.m.cost) {
|
(predicate === 'm' ? (m) => m.getItem() : (m) => m.readMeta('cost'))
|
||||||
let toggle = this._toggleCost.bind(this, item);
|
);
|
||||||
rows.push(<tr key={i} className={cn('highlight', { disabled: !item.incCost })}>
|
if (desc) {
|
||||||
<td className='ptr' style={{ width: '1em' }} onClick={toggle}>{item.m.class + item.m.rating}</td>
|
reverse(modules);
|
||||||
<td className='le ptr shorten cap' onClick={toggle}>{slotName(translate, item)}</td>
|
}
|
||||||
<td className='ri ptr' onClick={toggle}>{formats.int(item.discountedCost)}{units.CR}</td>
|
|
||||||
|
let totalCost = 0;
|
||||||
|
for (let module of modules) {
|
||||||
|
const cost = module.readMeta('cost');
|
||||||
|
const slot = module.getSlot();
|
||||||
|
if (cost) {
|
||||||
|
let toggle = this._toggleExcluded.bind(this, slot);
|
||||||
|
const disabled = excluded[slot];
|
||||||
|
if (!disabled) {
|
||||||
|
totalCost += cost;
|
||||||
|
}
|
||||||
|
rows.push(<tr key={slot} className={cn('highlight', { disabled })}>
|
||||||
|
<td className='ptr' style={{ width: '1em' }} onClick={toggle}>{module.getClassRating()}</td>
|
||||||
|
<td className='le ptr shorten cap' onClick={toggle}>{translate(module.readMeta('type'))}</td>
|
||||||
|
<td className='ri ptr' onClick={toggle}>{formats.int(cost * (1 - moduleDiscount))}{units.CR}</td>
|
||||||
</tr>);
|
</tr>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -304,23 +158,23 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className='main'>
|
<tr className='main'>
|
||||||
<th colSpan='2' className='sortable le' onClick={this._sortCostBy.bind(this,'m')}>
|
<th colSpan='2' className='sortable le' onClick={() => this._sortBy('m')}>
|
||||||
{translate('module')}
|
{translate('module')}
|
||||||
{shipDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} -${formats.pct(shipDiscount)}]`}</u> : null}
|
{shipDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} -${formats.pct(shipDiscount)}]`}</u> : null}
|
||||||
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
|
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
|
||||||
</th>
|
</th>
|
||||||
<th className='sortable le' onClick={this._sortCostBy.bind(this, 'cr')} >{translate('credits')}</th>
|
<th className='sortable le' onClick={() => this._sortBy('cr')} >{translate('credits')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{rows}
|
{rows}
|
||||||
<tr className='ri'>
|
<tr className='ri'>
|
||||||
<td colSpan='2' className='lbl' >{translate('total')}</td>
|
<td colSpan='2' className='lbl' >{translate('total')}</td>
|
||||||
<td className='val'>{formats.int(ship.totalCost)}{units.CR}</td>
|
<td className='val'>{formats.int(totalCost)}{units.CR}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className='ri'>
|
<tr className='ri'>
|
||||||
<td colSpan='2' className='lbl'>{translate('insurance')}</td>
|
<td colSpan='2' className='lbl'>{translate('insurance')}</td>
|
||||||
<td className='val'>{formats.int(ship.totalCost * insurance)}{units.CR}</td>
|
<td className='val'>{formats.int(totalCost * insurance)}{units.CR}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -331,14 +185,63 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
* Open up a window for EDDB with a shopping list of our retrofit components
|
* Open up a window for EDDB with a shopping list of our retrofit components
|
||||||
*/
|
*/
|
||||||
_eddbShoppingList() {
|
_eddbShoppingList() {
|
||||||
const { retrofitCosts } = this.state;
|
const {} = this.state;
|
||||||
const { ship } = this.props;
|
const { ship } = this.props;
|
||||||
|
|
||||||
// Provide unique list of non-PP module EDDB IDs to buy
|
// Provide unique list of non-PP module EDDB IDs to buy
|
||||||
const modIds = retrofitCosts.filter(item => item.retroItem.incCost && item.buyId && !item.buyPp).map(item => item.buyId).filter((v, i, a) => a.indexOf(v) === i);
|
// const modIds = retrofitCosts.filter(item => item.retroItem.incCost && item.buyId && !item.buyPp).map(item => item.buyId).filter((v, i, a) => a.indexOf(v) === i);
|
||||||
|
|
||||||
// Open up the relevant URL
|
// Open up the relevant URL
|
||||||
window.open('https://eddb.io/station?m=' + modIds.join(','));
|
// TODO:
|
||||||
|
// window.open('https://eddb.io/station?m=' + modIds.join(','));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
_retrofitInfo() {
|
||||||
|
const { ship } = this.props;
|
||||||
|
const { desc, moduleDiscount, predicate, retrofitName, excluded } = this.state;
|
||||||
|
const retrofitShip = this._buildRetrofitShip();
|
||||||
|
|
||||||
|
const currentModules = ship.getModules();
|
||||||
|
const oldModules = retrofitShip.getModules();
|
||||||
|
const buyModules = differenceBy(currentModules, oldModules, (m) => m.getItem());
|
||||||
|
const sellModules = differenceBy(oldModules, currentModules, (m) => m.getItem());
|
||||||
|
|
||||||
|
let modules = [];
|
||||||
|
let totalCost = 0;
|
||||||
|
const addModule = (m, costFactor) => {
|
||||||
|
const key = `${m.getItem()}@${m.getSlot()}`;
|
||||||
|
const cost = costFactor * m.readMeta('cost') * (1 - moduleDiscount);
|
||||||
|
modules.push({
|
||||||
|
key, cost,
|
||||||
|
rating: m.getClassRating(),
|
||||||
|
item: m.readMeta('type'),
|
||||||
|
});
|
||||||
|
if (!excluded[key]) {
|
||||||
|
totalCost += cost;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for (let m of buyModules) {
|
||||||
|
addModule(m, 1);
|
||||||
|
}
|
||||||
|
for (let m of sellModules) {
|
||||||
|
addModule(m, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
let _sortF = undefined;
|
||||||
|
switch (predicate) {
|
||||||
|
case 'cr': _sortF = (o) => o.cost; break;
|
||||||
|
case 'm':
|
||||||
|
default: _sortF = (o) => o.item; break;
|
||||||
|
};
|
||||||
|
|
||||||
|
modules = sortBy(modules, _sortF);
|
||||||
|
if (desc) {
|
||||||
|
reverse(modules);
|
||||||
|
}
|
||||||
|
return [totalCost, modules];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -346,59 +249,52 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
* @return {React.Component} Tab contents
|
* @return {React.Component} Tab contents
|
||||||
*/
|
*/
|
||||||
_retrofitTab() {
|
_retrofitTab() {
|
||||||
let { retrofitTotal, retrofitCosts, moduleDiscount, retrofitName } = this.state;
|
let { buildOptions, excluded, moduleDiscount, retrofitName } = this.state;
|
||||||
const { termtip, tooltip } = this.context;
|
const { termtip, tooltip } = this.context;
|
||||||
let { translate, formats, units } = this.context.language;
|
let { translate, formats, units } = this.context.language;
|
||||||
let int = formats.int;
|
let int = formats.int;
|
||||||
let rows = [], options = [<option key='stock' value=''>{translate('Stock')}</option>];
|
|
||||||
|
|
||||||
for (let opt of this.state.buildOptions) {
|
|
||||||
options.push(<option key={opt} value={opt}>{opt}</option>);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (retrofitCosts.length) {
|
|
||||||
for (let i = 0, l = retrofitCosts.length; i < l; i++) {
|
|
||||||
let item = retrofitCosts[i];
|
|
||||||
rows.push(<tr key={i} className={cn('highlight', { disabled: !item.retroItem.incCost })} onClick={this._toggleRetrofitCost.bind(this, item)}>
|
|
||||||
<td className='ptr' style={{ width: '1em' }}>{item.sellClassRating}</td>
|
|
||||||
<td className='le ptr shorten cap'>{translate(item.sellName)}</td>
|
|
||||||
<td className='ptr' style={{ width: '1em' }}>{item.buyClassRating}</td>
|
|
||||||
<td className='le ptr shorten cap'>{translate(item.buyName)}</td>
|
|
||||||
<td colSpan='2' className={cn('ri ptr', item.retroItem.incCost ? item.netCost > 0 ? 'warning' : 'secondary-disabled' : 'disabled')}>{int(item.netCost)}{units.CR}</td>
|
|
||||||
</tr>);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
rows = <tr><td colSpan='7' style={{ padding: '3em 0' }}>{translate('PHRASE_NO_RETROCH')}</td></tr>;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const [retrofitTotal, retrofitInfo] = this._retrofitInfo();
|
||||||
return <div>
|
return <div>
|
||||||
<div className='scroll-x'>
|
<div className='scroll-x'>
|
||||||
<table style={{ width: '100%' }}>
|
<table style={{ width: '100%' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className='main'>
|
<tr className='main'>
|
||||||
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'sellName')}>{translate('sell')}</th>
|
<th colSpan='2' className='sortable le' onClick={() => this._sortBy('m')}>{translate('module')}</th>
|
||||||
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'buyName')}>{translate('buy')}</th>
|
<th colSpan='2' className='sortable le' onClick={() => this._sortBy('cr')}>
|
||||||
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'cr')}>
|
|
||||||
{translate('net cost')}
|
{translate('net cost')}
|
||||||
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
|
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{rows}
|
{retrofitInfo.length ?
|
||||||
|
retrofitInfo.map((info) => (
|
||||||
|
<tr key={info.key} className={cn('highlight', { disabled: excluded[info.key] })}
|
||||||
|
onClick={() => this._toggleExcluded(info.key)}>
|
||||||
|
<td className='ptr' style={{ width: '1em' }}>{info.rating}</td>
|
||||||
|
<td className='le ptr shorten cap'>{translate(info.item)}</td>
|
||||||
|
<td colSpan="2" className={cn('ri ptr', excluded[info.key] ? 'disabled' : (info.cost < 0 ? 'secondary-disabled' : 'warning'))}>
|
||||||
|
{int(info.cost)}{units.CR}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
)) : (
|
||||||
|
<tr><td colSpan='7' style={{ padding: '3em 0' }}>{translate('PHRASE_NO_RETROCH')}</td></tr>
|
||||||
|
)}
|
||||||
<tr className='ri'>
|
<tr className='ri'>
|
||||||
<td className='lbl' ><button onClick={this._eddbShoppingList} onMouseOver={termtip.bind(null, 'PHRASE_REFIT_SHOPPING_LIST')} onMouseOut={tooltip.bind(null, null)}><ShoppingIcon className='lg' style={{ fill: 'black' }}/></button></td>
|
<td className='lbl' ><button onClick={this._eddbShoppingList} onMouseOver={termtip.bind(null, 'PHRASE_REFIT_SHOPPING_LIST')} onMouseOut={tooltip.bind(null, null)}><ShoppingIcon className='lg' style={{ fill: 'black' }}/></button></td>
|
||||||
<td colSpan='3' className='lbl' >{translate('cost')}</td>
|
<td className='lbl' >{translate('cost')}</td>
|
||||||
<td colSpan='2' className={cn('val', retrofitTotal > 0 ? 'warning' : 'secondary-disabled')} style={{ borderBottom:'none' }}>
|
<td colSpan='2' className={cn('val', retrofitTotal > 0 ? 'warning' : 'secondary-disabled')} style={{ borderBottom:'none' }}>
|
||||||
{int(retrofitTotal)}{units.CR}
|
{int(retrofitTotal)}{units.CR}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className='ri'>
|
<tr className='ri'>
|
||||||
<td colSpan='4' className='lbl cap' >{translate('retrofit from')}</td>
|
<td colSpan='2' className='lbl cap' >{translate('retrofit from')}</td>
|
||||||
<td className='val cen' style={{ borderRight: 'none', width: '1em' }}><u className='primary-disabled'>▾</u></td>
|
<td className='val cen' style={{ borderRight: 'none', width: '1em' }}><u className='primary-disabled'>▾</u></td>
|
||||||
<td className='val' style={{ borderLeft:'none', padding: 0 }}>
|
<td className='val' style={{ borderLeft:'none', padding: 0 }}>
|
||||||
<select style={{ width: '100%', padding: 0 }} value={retrofitName || translate('Stock')} onChange={this._onBaseRetrofitChange}>
|
<select style={{ width: '100%', padding: 0 }} value={retrofitName || translate('Stock')} onChange={this._onBaseRetrofitChange}>
|
||||||
{options}
|
<option key='stock' value=''>{translate('Stock')}</option>
|
||||||
|
{buildOptions.map((opt) => <option key={opt} value={opt}>{opt}</option>)}
|
||||||
</select>
|
</select>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -408,63 +304,50 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update retrofit costs
|
*
|
||||||
* @param {Ship} ship Ship instance
|
* @param {*} modules
|
||||||
* @param {Ship} retrofitShip Retrofit Ship instance
|
|
||||||
*/
|
*/
|
||||||
_updateRetrofit(ship, retrofitShip) {
|
_ammoInfo() {
|
||||||
let retrofitCosts = [];
|
const { ship } = this.props;
|
||||||
let retrofitTotal = 0, i, l, item;
|
const { desc, predicate } = this.state;
|
||||||
|
|
||||||
if (ship.bulkheads.m.index != retrofitShip.bulkheads.m.index) {
|
let info = [{
|
||||||
item = {
|
key: 'fuel',
|
||||||
buyClassRating: ship.bulkheads.m.class + ship.bulkheads.m.rating,
|
item: 'Fuel',
|
||||||
buyId: ship.bulkheads.m.eddbID,
|
qty: ship.get(FUEL_CAPACITY),
|
||||||
buyPp: ship.bulkheads.m.pp,
|
unitCost: 50,
|
||||||
buyName: ship.bulkheads.m.name,
|
cost: 50 * ship.get(FUEL_CAPACITY),
|
||||||
sellClassRating: retrofitShip.bulkheads.m.class + retrofitShip.bulkheads.m.rating,
|
}];
|
||||||
sellName: retrofitShip.bulkheads.m.name,
|
for (let m of ship.getModules()) {
|
||||||
netCost: ship.bulkheads.discountedCost - retrofitShip.bulkheads.discountedCost,
|
const rebuilds = m.get('bays') * m.get('rebuildsperbay');
|
||||||
retroItem: retrofitShip.bulkheads
|
const ammo = (m.get('ammomaximum') + m.get('ammoclipsize')) || rebuilds;
|
||||||
};
|
if (ammo) {
|
||||||
retrofitCosts.push(item);
|
const unitCost = m.readMeta('ammocost');
|
||||||
if (retrofitShip.bulkheads.incCost) {
|
info.push({
|
||||||
retrofitTotal += item.netCost;
|
key: `restock_${m.getSlot()}`,
|
||||||
|
rating: m.getClassRating(),
|
||||||
|
item: m.readMeta('type'),
|
||||||
|
qty: ammo,
|
||||||
|
unitCost, cost: unitCost * ammo,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let g in { standard: 1, internal: 1, hardpoints: 1 }) {
|
let _sortF = undefined;
|
||||||
let retroSlotGroup = retrofitShip[g];
|
switch (predicate) {
|
||||||
let slotGroup = ship[g];
|
case 'cr': _sortF = (o) => o.cost; break;
|
||||||
for (i = 0, l = slotGroup.length; i < l; i++) {
|
case 'qty': _sortF = (o) => o.qty; break;
|
||||||
const modId = slotGroup[i].m ? slotGroup[i].m.eddbID : null;
|
case 'cost': _sortF = (o) => o.unitCost; break;
|
||||||
const retroModId = retroSlotGroup[i].m ? retroSlotGroup[i].m.eddbID : null;
|
case 'm':
|
||||||
if (modId != retroModId) {
|
default: _sortF = (o) => o.item;
|
||||||
item = { netCost: 0, retroItem: retroSlotGroup[i] };
|
|
||||||
if (slotGroup[i].m) {
|
|
||||||
item.buyId = slotGroup[i].m.eddbID,
|
|
||||||
item.buyPp = slotGroup[i].m.pp,
|
|
||||||
item.buyName = slotGroup[i].m.name || slotGroup[i].m.grp;
|
|
||||||
item.buyClassRating = slotGroup[i].m.class + slotGroup[i].m.rating;
|
|
||||||
item.netCost = slotGroup[i].discountedCost;
|
|
||||||
}
|
|
||||||
if (retroSlotGroup[i].m) {
|
|
||||||
item.sellName = retroSlotGroup[i].m.name || retroSlotGroup[i].m.grp;
|
|
||||||
item.sellClassRating = retroSlotGroup[i].m.class + retroSlotGroup[i].m.rating;
|
|
||||||
item.netCost -= retroSlotGroup[i].discountedCost;
|
|
||||||
}
|
|
||||||
retrofitCosts.push(item);
|
|
||||||
if (retroSlotGroup[i].incCost) {
|
|
||||||
retrofitTotal += item.netCost;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
info = sortBy(info, _sortF);
|
||||||
|
if (desc) {
|
||||||
|
reverse(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setState({ retrofitCosts, retrofitTotal });
|
return info;
|
||||||
this._sortRetrofit(retrofitCosts, this.state.retroPredicate, this.state.retroDesc);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -472,20 +355,24 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
* @return {React.Component} Tab contents
|
* @return {React.Component} Tab contents
|
||||||
*/
|
*/
|
||||||
_ammoTab() {
|
_ammoTab() {
|
||||||
let { ammoTotal, ammoCosts } = this.state;
|
const { excluded } = this.state;
|
||||||
let { translate, formats, units } = this.context.language;
|
const { translate, formats, units } = this.context.language;
|
||||||
let int = formats.int;
|
const int = formats.int;
|
||||||
let rows = [];
|
const rows = [];
|
||||||
|
|
||||||
for (let i = 0, l = ammoCosts.length; i < l; i++) {
|
const ammoInfo = this._ammoInfo();
|
||||||
let item = ammoCosts[i];
|
let total = 0;
|
||||||
rows.push(<tr key={i} className='highlight'>
|
for (let i of ammoInfo) {
|
||||||
<td style={{ width: '1em' }}>{item.m.class + item.m.rating}</td>
|
const disabled = excluded[i.key];
|
||||||
<td className='le shorten cap'>{slotName(translate, item)}</td>
|
rows.push(<tr key={i.key} onClick={() => this._toggleExcluded(i.key)}
|
||||||
<td className='ri'>{int(item.max)}</td>
|
className={cn('highlight', { disabled })}>
|
||||||
<td className='ri'>{int(item.cost)}{units.CR}</td>
|
<td style={{ width: '1em' }}>{i.rating}</td>
|
||||||
<td className='ri'>{int(item.total)}{units.CR}</td>
|
<td className='le shorten cap'>{translate(i.item)}</td>
|
||||||
|
<td className='ri'>{int(i.qty)}</td>
|
||||||
|
<td className='ri'>{int(i.unitCost)}{units.CR}</td>
|
||||||
|
<td className='ri'>{int(i.cost)}{units.CR}</td>
|
||||||
</tr>);
|
</tr>);
|
||||||
|
total += disabled ? 0 : i.cost;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div>
|
return <div>
|
||||||
@@ -493,17 +380,17 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
<table style={{ width: '100%' }}>
|
<table style={{ width: '100%' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className='main'>
|
<tr className='main'>
|
||||||
<th colSpan='2' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'm')} >{translate('module')}</th>
|
<th colSpan='2' className='sortable le' onClick={() => this._sortBy('m')}>{translate('module')}</th>
|
||||||
<th colSpan='1' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'max')} >{translate('qty')}</th>
|
<th colSpan='1' className='sortable le' onClick={() => this._sortBy('qty')}>{translate('qty')}</th>
|
||||||
<th colSpan='1' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'cost')} >{translate('unit cost')}</th>
|
<th colSpan='1' className='sortable le' onClick={() => this._sortBy('cost')}>{translate('unit cost')}</th>
|
||||||
<th className='sortable le' onClick={this._sortAmmoBy.bind(this, 'total')}>{translate('subtotal')}</th>
|
<th className='sortable le' onClick={() => this._sortBy('cr')}>{translate('subtotal')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{rows}
|
{rows}
|
||||||
<tr className='ri'>
|
<tr className='ri'>
|
||||||
<td colSpan='4' className='lbl' >{translate('total')}</td>
|
<td colSpan='4' className='lbl' >{translate('total')}</td>
|
||||||
<td className='val'>{int(ammoTotal)}{units.CR}</td>
|
<td className='val'>{int(total)}{units.CR}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -511,103 +398,6 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Recalculate all ammo costs
|
|
||||||
* @param {Ship} ship Ship instance
|
|
||||||
*/
|
|
||||||
_updateAmmoCosts(ship) {
|
|
||||||
let ammoCosts = [], ammoTotal = 0, item, q, limpets = 0, srvs = 0, scoop = false;
|
|
||||||
|
|
||||||
for (let g in { standard: 1, internal: 1, hardpoints: 1 }) {
|
|
||||||
let slotGroup = ship[g];
|
|
||||||
for (let i = 0, l = slotGroup.length; i < l; i++) {
|
|
||||||
if (slotGroup[i].m) {
|
|
||||||
// Special cases needed for SCB, AFMU, and limpet controllers since they don't use standard ammo/clip
|
|
||||||
q = 0;
|
|
||||||
switch (slotGroup[i].m.grp) {
|
|
||||||
case 'fs': // Skip fuel calculation if scoop present
|
|
||||||
scoop = true;
|
|
||||||
break;
|
|
||||||
case 'scb':
|
|
||||||
q = slotGroup[i].m.getAmmo() + 1;
|
|
||||||
break;
|
|
||||||
case 'am':
|
|
||||||
q = slotGroup[i].m.getAmmo();
|
|
||||||
break;
|
|
||||||
case 'pv':
|
|
||||||
srvs += slotGroup[i].m.getBays();
|
|
||||||
break;
|
|
||||||
case 'fx': case 'hb': case 'cc': case 'pc':
|
|
||||||
limpets = ship.cargoCapacity;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
q = slotGroup[i].m.getClip() + slotGroup[i].m.getAmmo();
|
|
||||||
}
|
|
||||||
// Calculate ammo costs only if a cost is specified
|
|
||||||
if (slotGroup[i].m.ammocost > 0) {
|
|
||||||
item = {
|
|
||||||
m: slotGroup[i].m,
|
|
||||||
max: q,
|
|
||||||
cost: slotGroup[i].m.ammocost,
|
|
||||||
total: q * slotGroup[i].m.ammocost
|
|
||||||
};
|
|
||||||
ammoCosts.push(item);
|
|
||||||
ammoTotal += item.total;
|
|
||||||
}
|
|
||||||
// Add fighters
|
|
||||||
if (slotGroup[i].m.grp === 'fh') {
|
|
||||||
item = {
|
|
||||||
m: slotGroup[i].m,
|
|
||||||
max: slotGroup[i].m.getRebuildsPerBay() * slotGroup[i].m.getBays(),
|
|
||||||
cost: slotGroup[i].m.fightercost,
|
|
||||||
total: slotGroup[i].m.getRebuildsPerBay() * slotGroup[i].m.getBays() * slotGroup[i].m.fightercost
|
|
||||||
};
|
|
||||||
ammoCosts.push(item);
|
|
||||||
ammoTotal += item.total;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Limpets if controllers exist and cargo space available
|
|
||||||
if (limpets > 0) {
|
|
||||||
item = {
|
|
||||||
m: { name: 'limpets', class: '', rating: '' },
|
|
||||||
max: ship.cargoCapacity,
|
|
||||||
cost: 101,
|
|
||||||
total: ship.cargoCapacity * 101
|
|
||||||
};
|
|
||||||
ammoCosts.push(item);
|
|
||||||
ammoTotal += item.total;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (srvs > 0) {
|
|
||||||
item = {
|
|
||||||
m: { name: 'SRVs', class: '', rating: '' },
|
|
||||||
max: srvs,
|
|
||||||
cost: 1030,
|
|
||||||
total: srvs * 1030
|
|
||||||
};
|
|
||||||
ammoCosts.push(item);
|
|
||||||
ammoTotal += item.total;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate refuel costs if no scoop present
|
|
||||||
if (!scoop) {
|
|
||||||
item = {
|
|
||||||
m: { name: 'fuel', class: '', rating: '' },
|
|
||||||
max: ship.fuelCapacity,
|
|
||||||
cost: 50,
|
|
||||||
total: ship.fuelCapacity * 50
|
|
||||||
};
|
|
||||||
ammoCosts.push(item);
|
|
||||||
ammoTotal += item.total;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ ammoTotal, ammoCosts });
|
|
||||||
this._sortAmmo(ammoCosts, this.state.ammoPredicate, this.state.ammoDesc);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add listeners on mount and update costs
|
* Add listeners on mount and update costs
|
||||||
*/
|
*/
|
||||||
@@ -615,64 +405,7 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
this.listeners = [
|
this.listeners = [
|
||||||
Persist.addListener('discounts', this._onDiscountChanged.bind(this)),
|
Persist.addListener('discounts', this._onDiscountChanged.bind(this)),
|
||||||
Persist.addListener('insurance', this._onInsuranceChanged.bind(this)),
|
Persist.addListener('insurance', this._onInsuranceChanged.bind(this)),
|
||||||
Persist.addListener('builds', this._onBuildsChanged.bind(this)),
|
|
||||||
];
|
];
|
||||||
this._updateAmmoCosts(this.props.ship);
|
|
||||||
this._updateRetrofit(this.props.ship, this.state.retrofitShip);
|
|
||||||
this._sortCost(this.props.ship);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update state based on property and context changes
|
|
||||||
* @param {Object} nextProps Incoming/Next properties
|
|
||||||
* @param {Object} nextContext Incoming/Next context
|
|
||||||
*/
|
|
||||||
componentWillReceiveProps(nextProps, nextContext) {
|
|
||||||
let retrofitShip = this.state.retrofitShip;
|
|
||||||
|
|
||||||
if (nextProps.ship != this.props.ship) { // Ship has changed
|
|
||||||
let nextId = nextProps.ship.id;
|
|
||||||
let retrofitName = this._defaultRetrofitName(nextId, nextProps.buildName);
|
|
||||||
retrofitShip = this._buildRetrofitShip(nextId, retrofitName, nextId == this.props.ship.id ? retrofitShip : null);
|
|
||||||
this.setState({
|
|
||||||
retrofitShip,
|
|
||||||
retrofitName,
|
|
||||||
buildOptions: Persist.getBuildsNamesFor(nextId)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextProps.ship != this.props.ship || nextProps.code != this.props.code) {
|
|
||||||
nextProps.ship.applyDiscounts(Persist.getShipDiscount(), Persist.getModuleDiscount());
|
|
||||||
this._updateAmmoCosts(nextProps.ship);
|
|
||||||
this._updateRetrofit(nextProps.ship, retrofitShip);
|
|
||||||
this._sortCost(nextProps.ship);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort lists before render
|
|
||||||
* @param {Object} nextProps Incoming/Next properties
|
|
||||||
* @param {Object} nextState Incoming/Next state
|
|
||||||
*/
|
|
||||||
componentWillUpdate(nextProps, nextState) {
|
|
||||||
let state = this.state;
|
|
||||||
|
|
||||||
switch (nextState.tab) {
|
|
||||||
case 'ammo':
|
|
||||||
if (state.ammoPredicate != nextState.ammoPredicate || state.ammoDesc != nextState.ammoDesc) {
|
|
||||||
this._sortAmmo(nextState.ammoCosts, nextState.ammoPredicate, nextState.ammoDesc);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'retrofit':
|
|
||||||
if (state.retroPredicate != nextState.retroPredicate || state.retroDesc != nextState.retroDesc) {
|
|
||||||
this._sortRetrofit(nextState.retrofitCosts, nextState.retroPredicate, nextState.retroDesc);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (state.costPredicate != nextState.costPredicate || state.costDesc != nextState.costDesc) {
|
|
||||||
this._sortCost(nextProps.ship, nextState.costPredicate, nextState.costDesc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import TranslatedComponent from './TranslatedComponent';
|
|||||||
import * as Calc from '../shipyard/Calculations';
|
import * as Calc from '../shipyard/Calculations';
|
||||||
import PieChart from './PieChart';
|
import PieChart from './PieChart';
|
||||||
import VerticalBarChart from './VerticalBarChart';
|
import VerticalBarChart from './VerticalBarChart';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
import { ARMOUR_METRICS, MODULE_PROTECTION_METRICS, SHIELD_METRICS } from 'ed-forge/lib/ship-stats';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defence information
|
* Defence information
|
||||||
@@ -15,12 +17,10 @@ import VerticalBarChart from './VerticalBarChart';
|
|||||||
*/
|
*/
|
||||||
export default class Defence extends TranslatedComponent {
|
export default class Defence extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
marker: PropTypes.string.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
opponent: PropTypes.object.isRequired,
|
opponent: PropTypes.object.isRequired,
|
||||||
engagementrange: PropTypes.number.isRequired,
|
engagementRange: PropTypes.number.isRequired,
|
||||||
sys: PropTypes.number.isRequired,
|
|
||||||
opponentWep: PropTypes.number.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,22 +29,7 @@ export default class Defence extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
const { shield, armour, shielddamage, armourdamage } = Calc.defenceMetrics(props.ship, props.opponent, props.sys, props.opponentWep, props.engagementrange);
|
|
||||||
this.state = { shield, armour, shielddamage, armourdamage };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the state if our properties change
|
|
||||||
* @param {Object} nextProps Incoming/Next properties
|
|
||||||
* @return {boolean} Returns true if the component should be rerendered
|
|
||||||
*/
|
|
||||||
componentWillReceiveProps(nextProps) {
|
|
||||||
if (this.props.marker != nextProps.marker || this.props.sys != nextProps.sys) {
|
|
||||||
const { shield, armour, shielddamage, armourdamage } = Calc.defenceMetrics(nextProps.ship, nextProps.opponent, nextProps.sys, nextProps.opponentWep, nextProps.engagementrange);
|
|
||||||
this.setState({ shield, armour, shielddamage, armourdamage });
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,187 +37,104 @@ export default class Defence extends TranslatedComponent {
|
|||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { ship, sys, opponentWep } = this.props;
|
const { ship } = this.props;
|
||||||
const { language, tooltip, termtip } = this.context;
|
const { language, tooltip, termtip } = this.context;
|
||||||
const { formats, translate, units } = language;
|
const { formats, translate, units } = language;
|
||||||
const { shield, armour, shielddamage, armourdamage } = this.state;
|
|
||||||
|
|
||||||
const pd = ship.standard[4].m;
|
const shields = ship.get(SHIELD_METRICS);
|
||||||
|
|
||||||
const shieldSourcesData = [];
|
// Data for pie chart (absolute MJ)
|
||||||
const effectiveShieldData = [];
|
const shieldSourcesData = [
|
||||||
const shieldDamageTakenData = [];
|
'byBoosters', 'byGenerator', 'byReinforcements', 'bySCBs',
|
||||||
const shieldSourcesTt = [];
|
].map((key) => { return { label: key, value: Math.round(shields[key]) }; });
|
||||||
const shieldDamageTakenAbsoluteTt = [];
|
|
||||||
const shieldDamageTakenExplosiveTt = [];
|
|
||||||
const shieldDamageTakenKineticTt = [];
|
|
||||||
const shieldDamageTakenThermalTt = [];
|
|
||||||
const effectiveShieldAbsoluteTt = [];
|
|
||||||
const effectiveShieldExplosiveTt = [];
|
|
||||||
const effectiveShieldKineticTt = [];
|
|
||||||
const effectiveShieldThermalTt = [];
|
|
||||||
let maxEffectiveShield = 0;
|
|
||||||
if (shield.total) {
|
|
||||||
shieldSourcesData.push({ value: Math.round(shield.generator), label: translate('generator') });
|
|
||||||
shieldSourcesData.push({ value: Math.round(shield.boosters), label: translate('boosters') });
|
|
||||||
shieldSourcesData.push({ value: Math.round(shield.cells), label: translate('cells') });
|
|
||||||
shieldSourcesData.push({ value: Math.round(shield.addition), label: translate('shield addition') });
|
|
||||||
|
|
||||||
if (shield.generator > 0) {
|
// Data for tooltip
|
||||||
shieldSourcesTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
const shieldSourcesTt = shieldSourcesData.map((o) => {
|
||||||
effectiveShieldAbsoluteTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
let { label, value } = o;
|
||||||
effectiveShieldExplosiveTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
return <div key={label}>
|
||||||
effectiveShieldKineticTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
{translate(label)} {formats.int(value)}{units.MJ}
|
||||||
effectiveShieldThermalTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
</div>;
|
||||||
if (shield.boosters > 0) {
|
});
|
||||||
shieldSourcesTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
|
||||||
effectiveShieldAbsoluteTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
|
||||||
effectiveShieldExplosiveTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
|
||||||
effectiveShieldKineticTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
|
||||||
effectiveShieldThermalTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shield.cells > 0) {
|
// Shield resistances
|
||||||
shieldSourcesTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
const shieldDamageTakenData = [
|
||||||
effectiveShieldAbsoluteTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
'absolute', 'explosive', 'kinetic', 'thermal',
|
||||||
effectiveShieldExplosiveTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
].map((label) => {
|
||||||
effectiveShieldKineticTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
const dmgMult = shields[label];
|
||||||
effectiveShieldThermalTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
const tooltip = ['byBoosters', 'byGenerator', 'bySys'].map(
|
||||||
}
|
(label) => <div key={label}>
|
||||||
|
{translate(label)} {formats.pct1(dmgMult[label])}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return { label, value: Math.round(100 * dmgMult.withSys), tooltip };
|
||||||
|
});
|
||||||
|
|
||||||
// Add effective shield from resistances
|
// Effective MJ
|
||||||
const rawMj = shield.generator + shield.boosters + shield.cells;
|
const effectiveShieldData = [
|
||||||
const explosiveMj = rawMj / (shield.explosive.base) - rawMj;
|
'absolute', 'explosive', 'kinetic', 'thermal'
|
||||||
if (explosiveMj != 0) effectiveShieldExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(explosiveMj)}{units.MJ}</div>);
|
].map((label) => {
|
||||||
const kineticMj = rawMj / (shield.kinetic.base) - rawMj;
|
const dmgMult = shields[label];
|
||||||
if (kineticMj != 0) effectiveShieldKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(kineticMj)}{units.MJ}</div>);
|
const raw = shields.withSCBs;
|
||||||
const thermalMj = rawMj / (shield.thermal.base) - rawMj;
|
const tooltip = ['byBoosters', 'byGenerator', 'bySys'].map(
|
||||||
if (thermalMj != 0) effectiveShieldThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(thermalMj)}{units.MJ}</div>);
|
(label) => <div key={label}>
|
||||||
|
{translate(label)} {formats.int(raw * dmgMult[label])}{units.MJ}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return { label, value: Math.round(dmgMult.withSys * raw), tooltip };
|
||||||
|
});
|
||||||
|
const maxEffectiveShield = Math.max(...effectiveShieldData.map((o) => o.value));
|
||||||
|
|
||||||
// Add effective shield from power distributor SYS pips
|
const armour = ship.get(ARMOUR_METRICS);
|
||||||
if (shield.absolute.sys != 1) {
|
const moduleProtection = ship.get(MODULE_PROTECTION_METRICS);
|
||||||
effectiveShieldAbsoluteTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.absolute.total - rawMj)}{units.MJ}</div>);
|
|
||||||
effectiveShieldExplosiveTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.explosive.total - rawMj / shield.explosive.base)}{units.MJ}</div>);
|
|
||||||
effectiveShieldKineticTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.kinetic.total - rawMj / shield.kinetic.base)}{units.MJ}</div>);
|
|
||||||
effectiveShieldThermalTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.thermal.total - rawMj / shield.thermal.base)}{units.MJ}</div>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
shieldDamageTakenAbsoluteTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.absolute.generator)}</div>);
|
// Data for pie chart (absolute HP)
|
||||||
shieldDamageTakenAbsoluteTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.absolute.boosters)}</div>);
|
const armourSourcesData = ['base', 'byAlloys', 'byHRPs',].map(
|
||||||
shieldDamageTakenAbsoluteTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.absolute.sys)}</div>);
|
(key) => { return { label: key, value: Math.round(armour[key]) }; }
|
||||||
|
);
|
||||||
|
|
||||||
shieldDamageTakenExplosiveTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.explosive.generator)}</div>);
|
// Data for tooltip
|
||||||
shieldDamageTakenExplosiveTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.explosive.boosters)}</div>);
|
const armourSourcesTt = armourSourcesData.map((o) => {
|
||||||
shieldDamageTakenExplosiveTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.explosive.sys)}</div>);
|
let { label, value } = o;
|
||||||
|
return <div key={label}>{translate(label)} {formats.int(value)}</div>;
|
||||||
|
});
|
||||||
|
|
||||||
shieldDamageTakenKineticTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.kinetic.generator)}</div>);
|
// Armour resistances
|
||||||
shieldDamageTakenKineticTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.kinetic.boosters)}</div>);
|
const armourDamageTakenData = [
|
||||||
shieldDamageTakenKineticTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.kinetic.sys)}</div>);
|
'absolute', 'explosive', 'kinetic', 'thermal', 'caustic',
|
||||||
|
].map((label) => {
|
||||||
|
const dmgMult = armour[label];
|
||||||
|
const tooltip = ['byAlloys', 'byHRPs'].map(
|
||||||
|
(label) => <div key={label}>
|
||||||
|
{translate(label)} {formats.pct1(dmgMult[label])}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
return { label, value: Math.round(100 * dmgMult.damageMultiplier), tooltip };
|
||||||
|
});
|
||||||
|
|
||||||
shieldDamageTakenThermalTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.thermal.generator)}</div>);
|
// Effective HP
|
||||||
shieldDamageTakenThermalTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.thermal.boosters)}</div>);
|
const effectiveArmourData = [
|
||||||
shieldDamageTakenThermalTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.thermal.sys)}</div>);
|
'absolute', 'explosive', 'kinetic', 'thermal'
|
||||||
|
].map((label) => {
|
||||||
const effectiveAbsoluteShield = shield.total / shield.absolute.total;
|
const dmgMult = armour[label];
|
||||||
effectiveShieldData.push({ value: Math.round(effectiveAbsoluteShield), label: translate('absolute'), tooltip: effectiveShieldAbsoluteTt });
|
const raw = armour.armour;
|
||||||
const effectiveExplosiveShield = shield.total / shield.explosive.total;
|
const tooltip = ['byBoosters', 'byGenerator', 'bySys'].map(
|
||||||
effectiveShieldData.push({ value: Math.round(effectiveExplosiveShield), label: translate('explosive'), tooltip: effectiveShieldExplosiveTt });
|
(label) => <div key={label}>
|
||||||
const effectiveKineticShield = shield.total / shield.kinetic.total;
|
{translate(label)} {formats.int(raw * dmgMult[label])}
|
||||||
effectiveShieldData.push({ value: Math.round(effectiveKineticShield), label: translate('kinetic'), tooltip: effectiveShieldKineticTt });
|
</div>
|
||||||
const effectiveThermalShield = shield.total / shield.thermal.total;
|
);
|
||||||
effectiveShieldData.push({ value: Math.round(effectiveThermalShield), label: translate('thermal'), tooltip: effectiveShieldThermalTt });
|
return { label, value: Math.round(dmgMult.damageMultiplier * raw), tooltip };
|
||||||
|
});
|
||||||
shieldDamageTakenData.push({ value: Math.round(shield.absolute.total * 100), label: translate('absolute'), tooltip: shieldDamageTakenAbsoluteTt });
|
|
||||||
shieldDamageTakenData.push({ value: Math.round(shield.explosive.total * 100), label: translate('explosive'), tooltip: shieldDamageTakenExplosiveTt });
|
|
||||||
shieldDamageTakenData.push({ value: Math.round(shield.kinetic.total * 100), label: translate('kinetic'), tooltip: shieldDamageTakenKineticTt });
|
|
||||||
shieldDamageTakenData.push({ value: Math.round(shield.thermal.total * 100), label: translate('thermal'), tooltip: shieldDamageTakenThermalTt });
|
|
||||||
|
|
||||||
maxEffectiveShield = Math.max(shield.total / shield.absolute.max, shield.total / shield.explosive.max, shield.total / shield.kinetic.max, shield.total / shield.thermal.max);
|
|
||||||
}
|
|
||||||
|
|
||||||
const armourSourcesData = [];
|
|
||||||
armourSourcesData.push({ value: Math.round(armour.bulkheads), label: translate('bulkheads') });
|
|
||||||
armourSourcesData.push({ value: Math.round(armour.reinforcement), label: translate('reinforcement') });
|
|
||||||
|
|
||||||
const armourSourcesTt = [];
|
|
||||||
const effectiveArmourAbsoluteTt = [];
|
|
||||||
const effectiveArmourExplosiveTt = [];
|
|
||||||
const effectiveArmourKineticTt = [];
|
|
||||||
const effectiveArmourThermalTt = [];
|
|
||||||
const effectiveArmourCausticTt = [];
|
|
||||||
if (armour.bulkheads > 0) {
|
|
||||||
armourSourcesTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
|
||||||
effectiveArmourAbsoluteTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
|
||||||
effectiveArmourExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
|
||||||
effectiveArmourKineticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
|
||||||
effectiveArmourThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
|
||||||
effectiveArmourCausticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
|
||||||
if (armour.reinforcement > 0) {
|
|
||||||
armourSourcesTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
|
||||||
effectiveArmourAbsoluteTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
|
||||||
effectiveArmourExplosiveTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
|
||||||
effectiveArmourKineticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
|
||||||
effectiveArmourThermalTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
|
||||||
effectiveArmourCausticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const rawArmour = armour.bulkheads + armour.reinforcement;
|
|
||||||
|
|
||||||
const armourDamageTakenTt = [];
|
|
||||||
armourDamageTakenTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.absolute.bulkheads)}</div>);
|
|
||||||
armourDamageTakenTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.absolute.reinforcement)}</div>);
|
|
||||||
|
|
||||||
const armourDamageTakenExplosiveTt = [];
|
|
||||||
armourDamageTakenExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.explosive.bulkheads)}</div>);
|
|
||||||
armourDamageTakenExplosiveTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.explosive.reinforcement)}</div>);
|
|
||||||
if (armour.explosive.total != 1) effectiveArmourExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.explosive.total - rawArmour)}</div>);
|
|
||||||
|
|
||||||
const armourDamageTakenKineticTt = [];
|
|
||||||
armourDamageTakenKineticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.kinetic.bulkheads)}</div>);
|
|
||||||
armourDamageTakenKineticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.kinetic.reinforcement)}</div>);
|
|
||||||
if (armour.kinetic.total != 1) effectiveArmourKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.kinetic.total - rawArmour)}</div>);
|
|
||||||
|
|
||||||
const armourDamageTakenThermalTt = [];
|
|
||||||
armourDamageTakenThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.thermal.bulkheads)}</div>);
|
|
||||||
armourDamageTakenThermalTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.thermal.reinforcement)}</div>);
|
|
||||||
if (armour.thermal.total != 1) effectiveArmourThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.thermal.total - rawArmour)}</div>);
|
|
||||||
|
|
||||||
const armourDamageTakenCausticTt = [];
|
|
||||||
armourDamageTakenCausticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.caustic.bulkheads)}</div>);
|
|
||||||
armourDamageTakenCausticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.caustic.reinforcement)}</div>);
|
|
||||||
if (armour.thermal.total != 1) effectiveArmourCausticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.caustic.total - rawArmour)}</div>);
|
|
||||||
|
|
||||||
const effectiveArmourData = [];
|
|
||||||
const effectiveAbsoluteArmour = armour.total / armour.absolute.total;
|
|
||||||
effectiveArmourData.push({ value: Math.round(effectiveAbsoluteArmour), label: translate('absolute'), tooltip: effectiveArmourAbsoluteTt });
|
|
||||||
const effectiveExplosiveArmour = armour.total / armour.explosive.total;
|
|
||||||
effectiveArmourData.push({ value: Math.round(effectiveExplosiveArmour), label: translate('explosive'), tooltip: effectiveArmourExplosiveTt });
|
|
||||||
const effectiveKineticArmour = armour.total / armour.kinetic.total;
|
|
||||||
effectiveArmourData.push({ value: Math.round(effectiveKineticArmour), label: translate('kinetic'), tooltip: effectiveArmourKineticTt });
|
|
||||||
const effectiveThermalArmour = armour.total / armour.thermal.total;
|
|
||||||
effectiveArmourData.push({ value: Math.round(effectiveThermalArmour), label: translate('thermal'), tooltip: effectiveArmourThermalTt });
|
|
||||||
const effectiveCausticArmour = armour.total / armour.caustic.total;
|
|
||||||
effectiveArmourData.push({ value: Math.round(effectiveCausticArmour), label: translate('caustic'), tooltip: effectiveArmourCausticTt });
|
|
||||||
|
|
||||||
const armourDamageTakenData = [];
|
|
||||||
armourDamageTakenData.push({ value: Math.round(armour.absolute.total * 100), label: translate('absolute'), tooltip: armourDamageTakenTt });
|
|
||||||
armourDamageTakenData.push({ value: Math.round(armour.explosive.total * 100), label: translate('explosive'), tooltip: armourDamageTakenExplosiveTt });
|
|
||||||
armourDamageTakenData.push({ value: Math.round(armour.kinetic.total * 100), label: translate('kinetic'), tooltip: armourDamageTakenKineticTt });
|
|
||||||
armourDamageTakenData.push({ value: Math.round(armour.thermal.total * 100), label: translate('thermal'), tooltip: armourDamageTakenThermalTt });
|
|
||||||
armourDamageTakenData.push({ value: Math.round(armour.caustic.total * 100), label: translate('caustic'), tooltip: armourDamageTakenCausticTt });
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span id='defence'>
|
<span id='defence'>
|
||||||
{shield.total ? <span>
|
{shields.withSCBs ? <span>
|
||||||
<div className='group quarter'>
|
<div className='group quarter'>
|
||||||
<h2>{translate('shield metrics')}</h2>
|
<h2>{translate('shield metrics')}</h2>
|
||||||
<br/>
|
<br/>
|
||||||
<h2 onMouseOver={termtip.bind(null, <div>{shieldSourcesTt}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw shield strength')}<br/>{formats.int(shield.total)}{units.MJ}</h2>
|
<h2 onMouseOver={termtip.bind(null, <div>{shieldSourcesTt}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw shield strength')}<br/>{formats.int(shields.withSCBs)}{units.MJ}</h2>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_SHIELDS')}<br/>{shielddamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(shield.total, shielddamage.totalsdps, shielddamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * opponentWep / 4))}</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_SHIELDS')}<br/>TODO</h2>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECOVER'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECOVER_SHIELDS')}<br/>{shield.recover === Math.Inf ? translate('never') : formats.time(shield.recover)}</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECOVER'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECOVER_SHIELDS')}<br/>{shields.recover ? formats.time(shields.recover) : translate('never')}</h2>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECHARGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECHARGE_SHIELDS')}<br/>{shield.recharge === Math.Inf ? translate('never') : formats.time(shield.recharge)}</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECHARGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECHARGE_SHIELDS')}<br/>{shields.recharge ? formats.time(shields.recharge) : translate('never')}</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className='group quarter'>
|
<div className='group quarter'>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_SOURCES'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield sources')}</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_SOURCES'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield sources')}</h2>
|
||||||
@@ -250,11 +152,11 @@ export default class Defence extends TranslatedComponent {
|
|||||||
|
|
||||||
<div className='group quarter'>
|
<div className='group quarter'>
|
||||||
<h2>{translate('armour metrics')}</h2>
|
<h2>{translate('armour metrics')}</h2>
|
||||||
<h2 onMouseOver={termtip.bind(null, <div>{armourSourcesTt}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw armour strength')}<br/>{formats.int(armour.total)}</h2>
|
<h2 onMouseOver={termtip.bind(null, <div>{armourSourcesTt}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw armour strength')}<br/>{formats.int(armour.armour)}</h2>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_ARMOUR')}<br/>{armourdamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(armour.total, armourdamage.totalsdps, armourdamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * opponentWep / 4))}</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_ARMOUR')}<br/>TODO</h2>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('raw module armour')}<br/>{formats.int(armour.modulearmour)}</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('raw module armour')}<br/>{formats.int(moduleProtection.moduleArmour)}</h2>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_PROTECTION_EXTERNAL'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_MODULE_PROTECTION_EXTERNAL')}<br/>{formats.pct1(armour.moduleprotection / 2)}</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_PROTECTION_EXTERNAL'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_MODULE_PROTECTION_EXTERNAL')}<br/>{formats.pct1((1 - moduleProtection.moduleProtection) / 2)}</h2>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_PROTECTION_INTERNAL'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_MODULE_PROTECTION_INTERNAL')}<br/>{formats.pct1(armour.moduleprotection)}</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_PROTECTION_INTERNAL'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_MODULE_PROTECTION_INTERNAL')}<br/>{formats.pct1(1 - moduleProtection.moduleProtection)}</h2>
|
||||||
<br/>
|
<br/>
|
||||||
</div>
|
</div>
|
||||||
<div className='group quarter'>
|
<div className='group quarter'>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import Slider from '../components/Slider';
|
import Slider from '../components/Slider';
|
||||||
|
import { moduleReduce } from 'ed-forge/lib/helper';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Engagement range slider
|
* Engagement range slider
|
||||||
@@ -21,35 +22,18 @@ export default class EngagementRange extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
const { ship } = props;
|
|
||||||
|
|
||||||
const maxRange = Math.round(this._calcMaxRange(ship));
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
maxRange
|
maxRange: moduleReduce(
|
||||||
|
this.props.ship.getHardpoints(),
|
||||||
|
'maximumrange',
|
||||||
|
true,
|
||||||
|
// Don't use plain `Math.max` because callback will be passed four args
|
||||||
|
(a, v) => Math.max(a, v),
|
||||||
|
1000,
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the maximum range of a ship's weapons
|
|
||||||
* @param {Object} ship The ship
|
|
||||||
* @returns {int} The maximum range, in metres
|
|
||||||
*/
|
|
||||||
_calcMaxRange(ship) {
|
|
||||||
let maxRange = 1000;
|
|
||||||
for (let i = 0; i < ship.hardpoints.length; i++) {
|
|
||||||
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
|
|
||||||
const thisRange = ship.hardpoints[i].m.getRange();
|
|
||||||
if (thisRange > maxRange) {
|
|
||||||
maxRange = thisRange;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return maxRange;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update range
|
* Update range
|
||||||
* @param {number} rangeLevel percentage level from 0 to 1
|
* @param {number} rangeLevel percentage level from 0 to 1
|
||||||
@@ -61,7 +45,9 @@ export default class EngagementRange extends TranslatedComponent {
|
|||||||
const range = Math.round(rangeLevel * maxRange);
|
const range = Math.round(rangeLevel * maxRange);
|
||||||
|
|
||||||
if (range !== this.props.engagementRange) {
|
if (range !== this.props.engagementRange) {
|
||||||
this.props.onChange(range);
|
const { onChange, ship } = this.props;
|
||||||
|
ship.setEngagementRange(range);
|
||||||
|
onChange(range);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,8 +56,8 @@ export default class EngagementRange extends TranslatedComponent {
|
|||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
const { language, onWindowResize, sizeRatio } = this.context;
|
||||||
const { formats, translate, units } = language;
|
const { formats, translate } = language;
|
||||||
const { engagementRange } = this.props;
|
const { engagementRange } = this.props;
|
||||||
const { maxRange } = this.state;
|
const { maxRange } = this.state;
|
||||||
|
|
||||||
|
|||||||
@@ -2,98 +2,58 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import LineChart from '../components/LineChart';
|
import LineChart from '../components/LineChart';
|
||||||
import * as Calc from '../shipyard/Calculations';
|
import { getBoostMultiplier, getSpeedMultipliers } from 'ed-forge/lib/stats/SpeedProfile';
|
||||||
|
import { ShipProps } from 'ed-forge';
|
||||||
|
const { LADEN_MASS } = ShipProps;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Engine profile for a given ship
|
* Engine profile for a given ship
|
||||||
*/
|
*/
|
||||||
export default class EngineProfile extends TranslatedComponent {
|
export default class EngineProfile extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
code: PropTypes.string.isRequired,
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
cargo: PropTypes.number.isRequired,
|
cargo: PropTypes.number.isRequired,
|
||||||
fuel: PropTypes.number.isRequired,
|
fuel: PropTypes.number.isRequired,
|
||||||
eng: PropTypes.number.isRequired,
|
pips: PropTypes.number.isRequired,
|
||||||
boost: PropTypes.bool.isRequired,
|
boost: PropTypes.bool.isRequired,
|
||||||
marker: PropTypes.string.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
* @param {Object} props React Component properties
|
|
||||||
* @param {Object} context React Component context
|
|
||||||
*/
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
const ship = this.props.ship;
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
calcMaxSpeedFunc: this.calcMaxSpeed.bind(this, ship, this.props.eng, this.props.boost)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the state if our ship changes
|
|
||||||
* @param {Object} nextProps Incoming/Next properties
|
|
||||||
* @param {Object} nextContext Incoming/Next conext
|
|
||||||
* @return {boolean} Returns true if the component should be rerendered
|
|
||||||
*/
|
|
||||||
componentWillReceiveProps(nextProps, nextContext) {
|
|
||||||
if (nextProps.marker != this.props.marker) {
|
|
||||||
this.setState({ calcMaxSpeedFunc: this.calcMaxSpeed.bind(this, nextProps.ship, nextProps.eng, nextProps.boost) });
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the top speed for this ship given thrusters, mass and pips to ENG
|
|
||||||
* @param {Object} ship The ship
|
|
||||||
* @param {Object} eng The number of pips to ENG
|
|
||||||
* @param {Object} boost If boost is enabled
|
|
||||||
* @param {Object} mass The mass at which to calculate the top speed
|
|
||||||
* @return {number} The maximum speed
|
|
||||||
*/
|
|
||||||
calcMaxSpeed(ship, eng, boost, mass) {
|
|
||||||
// Obtain the top speed
|
|
||||||
return Calc.calcSpeed(mass, ship.speed, ship.standard[1].m, ship.pipSpeed, eng, ship.boost / ship.speed, boost);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render engine profile
|
* Render engine profile
|
||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
const { language } = this.context;
|
||||||
const { formats, translate, units } = language;
|
const { translate } = language;
|
||||||
const { ship, cargo, eng, fuel, boost } = this.props;
|
const { code, ship, pips, boost } = this.props;
|
||||||
|
|
||||||
// Calculate bounds for our line chart
|
// Calculate bounds for our line chart
|
||||||
const thrusters = ship.standard[1].m;
|
const minMass = ship.readProp('hullmass');
|
||||||
const minMass = ship.calcLowestPossibleMass({ th: thrusters });
|
const maxMass = ship.getThrusters().get('enginemaximalmass');
|
||||||
const maxMass = thrusters.getMaxMass();
|
const baseSpeed = ship.readProp('speed');
|
||||||
const mass = ship.unladenMass + fuel + cargo;
|
const baseBoost = getBoostMultiplier(ship);
|
||||||
const minSpeed = Calc.calcSpeed(maxMass, ship.speed, thrusters, ship.pipSpeed, 0, ship.boost / ship.speed, false);
|
const cb = (eng, boost, mass) => {
|
||||||
const maxSpeed = Calc.calcSpeed(minMass, ship.speed, thrusters, ship.pipSpeed, 4, ship.boost / ship.speed, true);
|
const mult = getSpeedMultipliers(ship, mass)[(boost ? 4 : eng) / 0.5];
|
||||||
// Add a mark at our current mass
|
return baseSpeed * (boost ? baseBoost : 1) * mult;
|
||||||
const mark = Math.min(mass, maxMass);
|
};
|
||||||
|
|
||||||
const code = `${ship.toString()}:${cargo}:${fuel}:${eng}:${boost}`;
|
|
||||||
|
|
||||||
// This graph can have a precipitous fall-off so we use lots of points to make it look a little smoother
|
// This graph can have a precipitous fall-off so we use lots of points to make it look a little smoother
|
||||||
return (
|
return (
|
||||||
<LineChart
|
<LineChart
|
||||||
xMin={minMass}
|
xMin={minMass}
|
||||||
xMax={maxMass}
|
xMax={maxMass}
|
||||||
yMin={minSpeed}
|
yMin={cb(0, false, maxMass)}
|
||||||
yMax={maxSpeed}
|
yMax={cb(4, true, minMass)}
|
||||||
xMark={mark}
|
// Add a mark at our current mass
|
||||||
|
xMark={Math.min(ship.get(LADEN_MASS), maxMass)}
|
||||||
xLabel={translate('mass')}
|
xLabel={translate('mass')}
|
||||||
xUnit={translate('T')}
|
xUnit={translate('T')}
|
||||||
yLabel={translate('maximum speed')}
|
yLabel={translate('maximum speed')}
|
||||||
yUnit={translate('m/s')}
|
yUnit={translate('m/s')}
|
||||||
func={this.state.calcMaxSpeedFunc}
|
func={cb.bind(this, pips.Eng.base + pips.Eng.mc, boost)}
|
||||||
points={1000}
|
points={1000}
|
||||||
code={code}
|
// Encode boost in code to re-render on state change
|
||||||
|
code={`${pips.Eng.base + pips.Eng.mc}:${Number(boost)}:${code}`}
|
||||||
aspect={0.7}
|
aspect={0.7}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,46 +3,21 @@ import PropTypes from 'prop-types';
|
|||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import LineChart from '../components/LineChart';
|
import LineChart from '../components/LineChart';
|
||||||
import * as Calc from '../shipyard/Calculations';
|
import * as Calc from '../shipyard/Calculations';
|
||||||
|
import { calculateJumpRange } from 'ed-forge/lib/stats/JumpRangeProfile';
|
||||||
|
import { ShipProps } from 'ed-forge';
|
||||||
|
const { LADEN_MASS } = ShipProps;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FSD profile for a given ship
|
* FSD profile for a given ship
|
||||||
*/
|
*/
|
||||||
export default class FSDProfile extends TranslatedComponent {
|
export default class FSDProfile extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
code: PropTypes.string.isRequired,
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
cargo: PropTypes.number.isRequired,
|
cargo: PropTypes.number.isRequired,
|
||||||
fuel: PropTypes.number.isRequired,
|
fuel: PropTypes.number.isRequired,
|
||||||
marker: PropTypes.string.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
* @param {Object} props React Component properties
|
|
||||||
* @param {Object} context React Component context
|
|
||||||
*/
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props);
|
|
||||||
|
|
||||||
const ship = this.props.ship;
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
calcMaxRangeFunc: this._calcMaxRange.bind(this, ship, this.props.fuel)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the state if our ship changes
|
|
||||||
* @param {Object} nextProps Incoming/Next properties
|
|
||||||
* @param {Object} nextContext Incoming/Next conext
|
|
||||||
* @return {boolean} Returns true if the component should be rerendered
|
|
||||||
*/
|
|
||||||
componentWillReceiveProps(nextProps, nextContext) {
|
|
||||||
if (nextProps.marker != this.props.marker) {
|
|
||||||
this.setState({ calcMaxRangeFunc: this._calcMaxRange.bind(this, nextProps.ship, nextProps.fuel) });
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculate the maximum range for this ship across its applicable mass
|
* Calculate the maximum range for this ship across its applicable mass
|
||||||
* @param {Object} ship The ship
|
* @param {Object} ship The ship
|
||||||
@@ -60,36 +35,27 @@ export default class FSDProfile extends TranslatedComponent {
|
|||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
const { language } = this.context;
|
||||||
const { formats, translate, units } = language;
|
const { translate } = language;
|
||||||
const { ship, cargo, fuel } = this.props;
|
const { code, ship } = this.props;
|
||||||
|
|
||||||
|
|
||||||
// Calculate bounds for our line chart - use thruster info for X
|
|
||||||
const thrusters = ship.standard[1].m;
|
|
||||||
const fsd = ship.standard[2].m;
|
|
||||||
const minMass = ship.calcLowestPossibleMass({ th: thrusters });
|
|
||||||
const maxMass = thrusters.getMaxMass();
|
|
||||||
const mass = ship.unladenMass + fuel + cargo;
|
|
||||||
const minRange = 0;
|
|
||||||
const maxRange = Calc.jumpRange(minMass + fsd.getMaxFuelPerJump(), fsd, fsd.getMaxFuelPerJump(), ship);
|
|
||||||
// Add a mark at our current mass
|
|
||||||
const mark = Math.min(mass, maxMass);
|
|
||||||
|
|
||||||
const code = ship.name + ship.toString() + '.' + fuel;
|
|
||||||
|
|
||||||
|
const minMass = ship.readProp('hullmass');
|
||||||
|
const maxMass = ship.getThrusters().get('enginemaximalmass');
|
||||||
|
const mass = ship.get(LADEN_MASS);
|
||||||
|
const cb = (mass) => calculateJumpRange(ship, mass, Infinity, true);
|
||||||
return (
|
return (
|
||||||
<LineChart
|
<LineChart
|
||||||
xMin={minMass}
|
xMin={minMass}
|
||||||
xMax={maxMass}
|
xMax={maxMass}
|
||||||
yMin={minRange}
|
yMin={0}
|
||||||
yMax={maxRange}
|
yMax={cb(minMass)}
|
||||||
xMark={mark}
|
// Add a mark at our current mass
|
||||||
|
xMark={Math.min(mass, maxMass)}
|
||||||
xLabel={translate('mass')}
|
xLabel={translate('mass')}
|
||||||
xUnit={translate('T')}
|
xUnit={translate('T')}
|
||||||
yLabel={translate('maximum range')}
|
yLabel={translate('maximum range')}
|
||||||
yUnit={translate('LY')}
|
yUnit={translate('LY')}
|
||||||
func={this.state.calcMaxRangeFunc}
|
func={cb}
|
||||||
points={200}
|
points={200}
|
||||||
code={code}
|
code={code}
|
||||||
aspect={0.7}
|
aspect={0.7}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import Slider from '../components/Slider';
|
import Slider from '../components/Slider';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fuel slider
|
* Fuel slider
|
||||||
@@ -21,8 +22,7 @@ export default class Fuel extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
this._fuelChange = this._fuelChange.bind(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,151 +0,0 @@
|
|||||||
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 { Modifications } from 'coriolis-data/dist';
|
|
||||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
|
||||||
import { blueprintTooltip } from '../utils/BlueprintFunctions';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hardpoint / Utility Slot
|
|
||||||
*/
|
|
||||||
export default class HardpointSlot extends Slot {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the CSS class name for the slot.
|
|
||||||
* @return {string} CSS Class name
|
|
||||||
*/
|
|
||||||
_getClassNames() {
|
|
||||||
return this.props.maxClass > 0 ? 'hardpoint' : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the label for the slot
|
|
||||||
* @param {Function} translate Translate function
|
|
||||||
* @return {string} Label
|
|
||||||
*/
|
|
||||||
_getMaxClassLabel(translate) {
|
|
||||||
return translate(['U', 'S', 'M', 'L', 'H'][this.props.maxClass]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate the slot contents
|
|
||||||
* @param {Object} m Mounted Module
|
|
||||||
* @param {Boolean} enabled Slot enabled
|
|
||||||
* @param {Function} translate Translate function
|
|
||||||
* @param {Object} formats Localized Formats map
|
|
||||||
* @param {Object} u Localized Units Map
|
|
||||||
* @return {React.Component} Slot contents
|
|
||||||
*/
|
|
||||||
_getSlotDetails(m, enabled, translate, formats, u) {
|
|
||||||
if (m) {
|
|
||||||
let classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
|
|
||||||
let { drag, drop } = this.props;
|
|
||||||
let { termtip, tooltip } = this.context;
|
|
||||||
let validMods = Modifications.modules[m.grp].modifications || [];
|
|
||||||
let showModuleResistances = Persist.showModuleResistances();
|
|
||||||
|
|
||||||
// Modifications tooltip shows blueprint and grade, if available
|
|
||||||
let modTT = translate('modified');
|
|
||||||
if (m && m.blueprint && m.blueprint.name) {
|
|
||||||
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
|
|
||||||
if (m.blueprint.special && m.blueprint.special.id >= 0) {
|
|
||||||
modTT += ', ' + translate(m.blueprint.special.name);
|
|
||||||
}
|
|
||||||
modTT = (
|
|
||||||
<div>
|
|
||||||
<div>{modTT}</div>
|
|
||||||
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const className = cn('details', enabled ? '' : 'disabled');
|
|
||||||
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}
|
|
||||||
</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.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>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,42 +1,29 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import SlotSection from './SlotSection';
|
import SlotSection from './SlotSection';
|
||||||
import HardpointSlot from './HardpointSlot';
|
import Slot from './Slot';
|
||||||
import { MountFixed, MountGimballed, MountTurret } from '../components/SvgIcons';
|
import { MountFixed, MountGimballed, MountTurret } from '../components/SvgIcons';
|
||||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hardpoint slot section
|
* Hardpoint slot section
|
||||||
*/
|
*/
|
||||||
export default class HardpointSlotSection extends SlotSection {
|
export default class HardpointSlotSection extends SlotSection {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
* @param {Object} context React Component context
|
|
||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context, 'hardpoints', 'hardpoints');
|
super(props, 'hardpoints');
|
||||||
this._empty = this._empty.bind(this);
|
autoBind(this);
|
||||||
this.selectedRefId = null;
|
|
||||||
this.firstRefId = 'emptyall';
|
|
||||||
this.lastRefId = 'nl-F';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle focus when component updates
|
|
||||||
* @param {Object} prevProps React Component properties
|
|
||||||
*/
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
this._handleSectionFocus(prevProps,this.firstRefId, this.lastRefId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Empty all slots
|
* Empty all slots
|
||||||
*/
|
*/
|
||||||
_empty() {
|
_empty() {
|
||||||
this.selectedRefId = 'emptyall';
|
// TODO:
|
||||||
this.props.ship.emptyWeapons();
|
// this.props.ship.emptyWeapons();
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,9 +34,8 @@ export default class HardpointSlotSection extends SlotSection {
|
|||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
*/
|
*/
|
||||||
_fill(group, mount, event) {
|
_fill(group, mount, event) {
|
||||||
this.selectedRefId = group + '-' + mount;
|
// TODO:
|
||||||
this.props.ship.useWeapon(group, mount, null, event.getModifierState('Alt'));
|
// this.props.ship.useWeapon(group, mount, null, event.getModifierState('Alt'));
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,33 +51,25 @@ export default class HardpointSlotSection extends SlotSection {
|
|||||||
* @return {Array} Array of Slots
|
* @return {Array} Array of Slots
|
||||||
*/
|
*/
|
||||||
_getSlots() {
|
_getSlots() {
|
||||||
let { ship, currentMenu } = this.props;
|
let { ship, currentMenu, propsToShow, onPropToggle } = this.props;
|
||||||
let { originSlot, targetSlot } = this.state;
|
let { originSlot, targetSlot } = this.state;
|
||||||
let slots = [];
|
let slots = [];
|
||||||
let hardpoints = ship.hardpoints;
|
|
||||||
let availableModules = ship.getAvailableModules();
|
|
||||||
|
|
||||||
for (let i = 0, l = hardpoints.length; i < l; i++) {
|
for (let h of ship.getHardpoints(undefined, true)) {
|
||||||
let h = hardpoints[i];
|
slots.push(<Slot
|
||||||
if (h.maxClass) {
|
key={h.object.Slot}
|
||||||
slots.push(<HardpointSlot
|
maxClass={h.getSize()}
|
||||||
key={i}
|
currentMenu={currentMenu}
|
||||||
maxClass={h.maxClass}
|
|
||||||
availableModules={() => availableModules.getHps(h.maxClass)}
|
|
||||||
onOpen={this._openMenu.bind(this, h)}
|
|
||||||
onSelect={this._selectModule.bind(this, h)}
|
|
||||||
onChange={this.props.onChange}
|
|
||||||
selected={currentMenu == h}
|
|
||||||
drag={this._drag.bind(this, h)}
|
drag={this._drag.bind(this, h)}
|
||||||
dragOver={this._dragOverSlot.bind(this, h)}
|
dragOver={this._dragOverSlot.bind(this, h)}
|
||||||
drop={this._drop}
|
drop={this._drop}
|
||||||
dropClass={this._dropClass(h, originSlot, targetSlot)}
|
dropClass={this._dropClass(h, originSlot, targetSlot)}
|
||||||
ship={ship}
|
m={h}
|
||||||
m={h.m}
|
|
||||||
enabled={h.enabled ? true : false}
|
enabled={h.enabled ? true : false}
|
||||||
|
propsToShow={propsToShow}
|
||||||
|
onPropToggle={onPropToggle}
|
||||||
/>);
|
/>);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return slots;
|
return slots;
|
||||||
}
|
}
|
||||||
@@ -101,68 +79,68 @@ export default class HardpointSlotSection extends SlotSection {
|
|||||||
* @param {Function} translate Translate function
|
* @param {Function} translate Translate function
|
||||||
* @return {React.Component} Section menu
|
* @return {React.Component} Section menu
|
||||||
*/
|
*/
|
||||||
_getSectionMenu(translate) {
|
_getSectionMenu() {
|
||||||
|
const { translate } = this.context.language;
|
||||||
let _fill = this._fill;
|
let _fill = this._fill;
|
||||||
|
|
||||||
return <div className='select hardpoint' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
|
return <div className='select hardpoint' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='lc' tabIndex='0' onClick={this._empty} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['emptyall'] = smRef}>{translate('empty all')}</li>
|
<li className='lc' tabIndex="0" onClick={this._empty} ref={smRef => this.sectionRefArr['emptyall'] = smRef}>{translate('empty all')}</li>
|
||||||
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
|
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('pl')}</div>
|
<div className='select-group cap'>{translate('pl')}</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'pl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pl-F'] = smRef}><MountFixed className='lg'/></li>
|
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'pl', 'F')}><MountFixed className='lg'/></li>
|
||||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'pl', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pl-G'] = smRef}><MountGimballed className='lg'/></li>
|
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'pl', 'G')}><MountGimballed className='lg'/></li>
|
||||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'pl', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pl-T'] = smRef}><MountTurret className='lg'/></li>
|
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'pl', 'T')}><MountTurret className='lg'/></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('ul')}</div>
|
<div className='select-group cap'>{translate('ul')}</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'ul', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ul-F'] = smRef}><MountFixed className='lg'/></li>
|
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'ul', 'F')}><MountFixed className='lg'/></li>
|
||||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'ul', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ul-G'] = smRef}><MountGimballed className='lg'/></li>
|
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'ul', 'G')}><MountGimballed className='lg'/></li>
|
||||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'ul', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ul-T'] = smRef}><MountTurret className='lg'/></li>
|
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'ul', 'T')}><MountTurret className='lg'/></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('bl')}</div>
|
<div className='select-group cap'>{translate('bl')}</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'bl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['bl-F'] = smRef}><MountFixed className='lg'/></li>
|
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'bl', 'F')}><MountFixed className='lg'/></li>
|
||||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'bl', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['bl-G'] = smRef}><MountGimballed className='lg'/></li>
|
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'bl', 'G')}><MountGimballed className='lg'/></li>
|
||||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'bl', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['bl-T'] = smRef}><MountTurret className='lg'/></li>
|
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'bl', 'T')}><MountTurret className='lg'/></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('mc')}</div>
|
<div className='select-group cap'>{translate('mc')}</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'mc', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['mc-F'] = smRef}><MountFixed className='lg'/></li>
|
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'mc', 'F')}><MountFixed className='lg'/></li>
|
||||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'mc', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['mc-G'] = smRef}><MountGimballed className='lg'/></li>
|
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'mc', 'G')}><MountGimballed className='lg'/></li>
|
||||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'mc', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['mc-T'] = smRef}><MountTurret className='lg'/></li>
|
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'mc', 'T')}><MountTurret className='lg'/></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('c')}</div>
|
<div className='select-group cap'>{translate('c')}</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'c', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['c-F'] = smRef}><MountFixed className='lg'/></li>
|
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'c', 'F')}><MountFixed className='lg'/></li>
|
||||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'c', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['c-G'] = smRef}><MountGimballed className='lg'/></li>
|
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'c', 'G')}><MountGimballed className='lg'/></li>
|
||||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'c', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['c-T'] = smRef}><MountTurret className='lg'/></li>
|
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'c', 'T')}><MountTurret className='lg'/></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('fc')}</div>
|
<div className='select-group cap'>{translate('fc')}</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'fc', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['fc-F'] = smRef}><MountFixed className='lg'/></li>
|
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'fc', 'F')}><MountFixed className='lg'/></li>
|
||||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'fc', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['fc-G'] = smRef}><MountGimballed className='lg'/></li>
|
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'fc', 'G')}><MountGimballed className='lg'/></li>
|
||||||
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'fc', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['fc-T'] = smRef}><MountTurret className='lg'/></li>
|
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'fc', 'T')}><MountTurret className='lg'/></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('pa')}</div>
|
<div className='select-group cap'>{translate('pa')}</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='lc' tabIndex='0' onClick={_fill.bind(this, 'pa', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pa-F'] = smRef}>{translate('pa')}</li>
|
<li className='lc' tabIndex="0" onClick={_fill.bind(this, 'pa', 'F')}>{translate('pa')}</li>
|
||||||
|
</ul>
|
||||||
|
<div className='select-group cap'>{translate('rg')}</div>
|
||||||
|
<ul>
|
||||||
|
<li className='lc' tabIndex="0" onClick={_fill.bind(this, 'rg', 'F')}>{translate('rg')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('nl')}</div>
|
<div className='select-group cap'>{translate('nl')}</div>
|
||||||
<ul>
|
<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>
|
<li className='lc' tabIndex="0" onClick={_fill.bind(this, 'nl', 'F')}>{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>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('rfl')}</div>
|
<div className='select-group cap'>{translate('rfl')}</div>
|
||||||
<ul>
|
<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', 'F')}><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>
|
<li className="c" tabIndex="0" onClick={_fill.bind(this, 'rfl', 'T')}><MountTurret className='lg'/></li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ function selectAll(e) {
|
|||||||
* Coriolis App Header section / menus
|
* Coriolis App Header section / menus
|
||||||
*/
|
*/
|
||||||
export default class Header extends TranslatedComponent {
|
export default class Header extends TranslatedComponent {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
@@ -349,7 +348,7 @@ export default class Header extends TranslatedComponent {
|
|||||||
_getShipsMenu() {
|
_getShipsMenu() {
|
||||||
let shipList = [];
|
let shipList = [];
|
||||||
|
|
||||||
for (let s in Ships) {
|
for (let s of this.shipOrder) {
|
||||||
shipList.push(<ActiveLink key={s} href={outfitURL(s)} className='block'>{Ships[s].properties.name}</ActiveLink>);
|
shipList.push(<ActiveLink key={s} href={outfitURL(s)} className='block'>{Ships[s].properties.name}</ActiveLink>);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -608,7 +607,7 @@ export default class Header extends TranslatedComponent {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='l menu'>
|
<div className='l menu'>
|
||||||
<div className={cn('menu-header', { selected: openedMenu == 'announce', disabled: this.props.announcements.length === 0})} onClick={this.props.announcements.length !== 0 && this._openAnnounce}>
|
<div className={cn('menu-header', { selected: openedMenu == 'announce', disabled: this.props.announcements.length === 0 })} onClick={this.props.announcements.length !== 0 && this._openAnnounce}>
|
||||||
<span className='menu-item-label'>{translate('announcements')}</span>
|
<span className='menu-item-label'>{translate('announcements')}</span>
|
||||||
</div>
|
</div>
|
||||||
{openedMenu == 'announce' ? this._getAnnouncementsMenu() : null}
|
{openedMenu == 'announce' ? this._getAnnouncementsMenu() : null}
|
||||||
@@ -639,5 +638,4 @@ export default class Header extends TranslatedComponent {
|
|||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,99 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import cn from 'classnames';
|
|
||||||
import Slot from './Slot';
|
|
||||||
import Persist from '../stores/Persist';
|
|
||||||
import { ListModifications, Modified } from './SvgIcons';
|
|
||||||
import { Modifications } from 'coriolis-data/dist';
|
|
||||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
|
||||||
import { blueprintTooltip } from '../utils/BlueprintFunctions';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal Slot
|
|
||||||
*/
|
|
||||||
export default class InternalSlot extends Slot {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate the slot contents
|
|
||||||
* @param {Object} m Mounted Module
|
|
||||||
* @param {Boolean} enabled Slot enabled
|
|
||||||
* @param {Function} translate Translate function
|
|
||||||
* @param {Object} formats Localized Formats map
|
|
||||||
* @param {Object} u Localized Units Map
|
|
||||||
* @return {React.Component} Slot contents
|
|
||||||
*/
|
|
||||||
_getSlotDetails(m, enabled, translate, formats, u) {
|
|
||||||
if (m) {
|
|
||||||
let classRating = m.class + m.rating;
|
|
||||||
let { drag, drop, ship } = this.props;
|
|
||||||
let { termtip, tooltip } = this.context;
|
|
||||||
let validMods = (Modifications.modules[m.grp] ? Modifications.modules[m.grp].modifications : []);
|
|
||||||
let showModuleResistances = Persist.showModuleResistances();
|
|
||||||
|
|
||||||
// Modifications tooltip shows blueprint and grade, if available
|
|
||||||
let modTT = translate('modified');
|
|
||||||
if (m && m.blueprint && m.blueprint.name) {
|
|
||||||
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
|
|
||||||
if (m.blueprint.special && m.blueprint.special.id >= 0) {
|
|
||||||
modTT += ', ' + translate(m.blueprint.special.name);
|
|
||||||
}
|
|
||||||
modTT = (
|
|
||||||
<div>
|
|
||||||
<div>{modTT}</div>
|
|
||||||
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mass = m.getMass() || m.cargo || m.fuel || 0;
|
|
||||||
|
|
||||||
const className = cn('details', enabled ? '' : 'disabled');
|
|
||||||
return <div className={className} draggable='true' onDragStart={drag} onDragEnd={drop}>
|
|
||||||
<div className={'cb'}>
|
|
||||||
<div className={'l'}>{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : ''}</div>
|
|
||||||
<div className={'r'}>{formats.round(mass)}{u.T}</div>
|
|
||||||
</div>
|
|
||||||
<div className={'cb'}>
|
|
||||||
{ m.getOptMass() ? <div className={'l'}>{translate('optmass', 'sg')}: {formats.int(m.getOptMass())}{u.T}</div> : null }
|
|
||||||
{ m.getMaxMass() ? <div className={'l'}>{translate('maxmass', 'sg')}: {formats.int(m.getMaxMass())}{u.T}</div> : null }
|
|
||||||
{ m.bins ? <div className={'l'}>{m.bins} <u>{translate('bins')}</u></div> : null }
|
|
||||||
{ m.bays ? <div className={'l'}>{translate('bays')}: {m.bays}</div> : null }
|
|
||||||
{ m.rebuildsperbay ? <div className={'l'}>{translate('rebuildsperbay')}: {m.rebuildsperbay}</div> : null }
|
|
||||||
{ m.rate ? <div className={'l'}>{translate('rate')}: {m.rate}{u.kgs} {translate('refuel time')}: {formats.time(this.props.fuel * 1000 / m.rate)}</div> : null }
|
|
||||||
{ m.getAmmo() && m.grp !== 'scb' ? <div className={'l'}>{translate('ammunition')}: {formats.gen(m.getAmmo())}</div> : null }
|
|
||||||
{ m.getSpinup() ? <div className={'l'}>{translate('spinup')}: {formats.f1(m.getSpinup())}{u.s}</div> : null }
|
|
||||||
{ m.getDuration() ? <div className={'l'}>{translate('duration')}: {formats.f1(m.getDuration())}{u.s}</div> : null }
|
|
||||||
{ m.grp === 'scb' ? <div className={'l'}>{translate('cells')}: {formats.int(m.getAmmo() + 1)}</div> : null }
|
|
||||||
{ m.grp === 'gsrp' ? <div className={'l'}>{translate('shield addition')}: {formats.f1(m.getShieldAddition())}{u.MJ}</div> : null }
|
|
||||||
{ m.grp === 'gfsb' ? <div className={'l'}>{translate('jump addition')}: {formats.f1(m.getJumpBoost())}{u.LY}</div> : null }
|
|
||||||
{ m.grp === 'gs' ? <div className={'l'}>{translate('shield addition')}: {formats.f1(m.getShieldAddition())}{u.MJ}</div> : null }
|
|
||||||
{ m.getShieldReinforcement() ? <div className={'l'}>{translate('shieldreinforcement')}: {formats.f1(m.getDuration() * m.getShieldReinforcement())}{u.MJ}</div> : null }
|
|
||||||
{ m.getShieldReinforcement() ? <div className={'l'}>{translate('total')}: {formats.int((m.getAmmo() + 1) * (m.getDuration() * m.getShieldReinforcement()))}{u.MJ}</div> : null }
|
|
||||||
{ m.repair ? <div className={'l'}>{translate('repair')}: {m.repair}</div> : null }
|
|
||||||
{ m.getFacingLimit() ? <div className={'l'}>{translate('facinglimit')} {formats.f1(m.getFacingLimit())}°</div> : null }
|
|
||||||
{ m.getRange() ? <div className={'l'}>{translate('range')} {formats.f2(m.getRange())}{u.km}</div> : null }
|
|
||||||
{ m.getRangeT() ? <div className={'l'}>{translate('ranget')} {formats.f1(m.getRangeT())}{u.s}</div> : null }
|
|
||||||
{ m.getTime() ? <div className={'l'}>{translate('time')}: {formats.time(m.getTime())}</div> : null }
|
|
||||||
{ m.getHackTime() ? <div className={'l'}>{translate('hacktime')}: {formats.time(m.getHackTime())}</div> : null }
|
|
||||||
{ m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
|
|
||||||
{ m.rangeLS ? <div className={'l'}>{translate('range')}: {m.rangeLS}{u.Ls}</div> : null }
|
|
||||||
{ m.rangeLS === null ? <div className={'l'}>∞{u.Ls}</div> : null }
|
|
||||||
{ m.rangeRating ? <div className={'l'}>{translate('range')}: {m.rangeRating}</div> : null }
|
|
||||||
{ m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
|
|
||||||
{ m.passengers ? <div className={'l'}>{translate('passengers')}: {m.passengers}</div> : null }
|
|
||||||
{ m.getRegenerationRate() ? <div className='l'>{translate('regen')}: {formats.round1(m.getRegenerationRate())}{u.ps}</div> : null }
|
|
||||||
{ m.getBrokenRegenerationRate() ? <div className='l'>{translate('brokenregen')}: {formats.round1(m.getBrokenRegenerationRate())}{u.ps}</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 }
|
|
||||||
{ showModuleResistances && m.getCausticResistance() ? <div className='l'>{translate('causres')}: {formats.pct(m.getCausticResistance())}</div> : null }
|
|
||||||
{ m.getHullReinforcement() ? <div className='l'>{translate('armour')}: {formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost') / 10000)}</div> : null }
|
|
||||||
{ m.getProtection() ? <div className='l'>{translate('protection')}: {formats.rPct(m.getProtection())}</div> : null }
|
|
||||||
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
|
|
||||||
{ 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>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +1,30 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import SlotSection from './SlotSection';
|
import SlotSection from './SlotSection';
|
||||||
import InternalSlot from './InternalSlot';
|
import Slot from './Slot';
|
||||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||||
import { canMount } from '../utils/SlotFunctions';
|
import { canMount } from '../utils/SlotFunctions';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Internal slot section
|
* Internal slot section
|
||||||
*/
|
*/
|
||||||
export default class InternalSlotSection extends SlotSection {
|
export default class InternalSlotSection extends SlotSection {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
* @param {Object} context React Component context
|
|
||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context, 'internal', 'optional internal');
|
super(props, 'optional internal');
|
||||||
this._empty = this._empty.bind(this);
|
autoBind(this);
|
||||||
this._fillWithCargo = this._fillWithCargo.bind(this);
|
|
||||||
this._fillWithCells = this._fillWithCells.bind(this);
|
|
||||||
this._fillWithArmor = this._fillWithArmor.bind(this);
|
|
||||||
this._fillWithModuleReinforcementPackages = this._fillWithModuleReinforcementPackages.bind(this);
|
|
||||||
this._fillWithFuelTanks = this._fillWithFuelTanks.bind(this);
|
|
||||||
this._fillWithLuxuryCabins = this._fillWithLuxuryCabins.bind(this);
|
|
||||||
this._fillWithFirstClassCabins = this._fillWithFirstClassCabins.bind(this);
|
|
||||||
this._fillWithBusinessClassCabins = this._fillWithBusinessClassCabins.bind(this);
|
|
||||||
this._fillWithEconomyClassCabins = this._fillWithEconomyClassCabins.bind(this);
|
|
||||||
this.selectedRefId = null;
|
|
||||||
this.firstRefId = 'emptyall';
|
|
||||||
this.lastRefId = this.sectionRefArr['pcq'] ? 'pcq' : 'pcm';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle focus when component updates
|
|
||||||
* @param {Object} prevProps React Component properties
|
|
||||||
*/
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
this._handleSectionFocus(prevProps,this.firstRefId, this.lastRefId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Empty all slots
|
* Empty all slots
|
||||||
*/
|
*/
|
||||||
_empty() {
|
_empty() {
|
||||||
this.selectedRefId = 'emptyall';
|
// TODO:
|
||||||
this.props.ship.emptyInternal();
|
// this.props.ship.emptyInternal();
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,7 +33,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
*/
|
*/
|
||||||
_fillWithCargo(event) {
|
_fillWithCargo(event) {
|
||||||
this.selectedRefId = 'cargo';
|
|
||||||
let clobber = event.getModifierState('Alt');
|
let clobber = event.getModifierState('Alt');
|
||||||
let ship = this.props.ship;
|
let ship = this.props.ship;
|
||||||
ship.internal.forEach((slot) => {
|
ship.internal.forEach((slot) => {
|
||||||
@@ -63,7 +40,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
|
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,7 +48,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
*/
|
*/
|
||||||
_fillWithFuelTanks(event) {
|
_fillWithFuelTanks(event) {
|
||||||
this.selectedRefId = 'ft';
|
|
||||||
let clobber = event.getModifierState('Alt');
|
let clobber = event.getModifierState('Alt');
|
||||||
let ship = this.props.ship;
|
let ship = this.props.ship;
|
||||||
ship.internal.forEach((slot) => {
|
ship.internal.forEach((slot) => {
|
||||||
@@ -80,7 +55,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
ship.use(slot, ModuleUtils.findInternal('ft', slot.maxClass, 'C'));
|
ship.use(slot, ModuleUtils.findInternal('ft', slot.maxClass, 'C'));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +63,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
*/
|
*/
|
||||||
_fillWithLuxuryCabins(event) {
|
_fillWithLuxuryCabins(event) {
|
||||||
this.selectedRefId = 'pcq';
|
|
||||||
let clobber = event.getModifierState('Alt');
|
let clobber = event.getModifierState('Alt');
|
||||||
let ship = this.props.ship;
|
let ship = this.props.ship;
|
||||||
ship.internal.forEach((slot) => {
|
ship.internal.forEach((slot) => {
|
||||||
@@ -97,7 +70,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
ship.use(slot, ModuleUtils.findInternal('pcq', Math.min(slot.maxClass, 6), 'B')); // Passenger cabins top out at 6
|
ship.use(slot, ModuleUtils.findInternal('pcq', Math.min(slot.maxClass, 6), 'B')); // Passenger cabins top out at 6
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +78,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
*/
|
*/
|
||||||
_fillWithFirstClassCabins(event) {
|
_fillWithFirstClassCabins(event) {
|
||||||
this.selectedRefId = 'pcm';
|
|
||||||
let clobber = event.getModifierState('Alt');
|
let clobber = event.getModifierState('Alt');
|
||||||
let ship = this.props.ship;
|
let ship = this.props.ship;
|
||||||
ship.internal.forEach((slot) => {
|
ship.internal.forEach((slot) => {
|
||||||
@@ -114,7 +85,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
ship.use(slot, ModuleUtils.findInternal('pcm', Math.min(slot.maxClass, 6), 'C')); // Passenger cabins top out at 6
|
ship.use(slot, ModuleUtils.findInternal('pcm', Math.min(slot.maxClass, 6), 'C')); // Passenger cabins top out at 6
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,7 +93,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
*/
|
*/
|
||||||
_fillWithBusinessClassCabins(event) {
|
_fillWithBusinessClassCabins(event) {
|
||||||
this.selectedRefId = 'pci';
|
|
||||||
let clobber = event.getModifierState('Alt');
|
let clobber = event.getModifierState('Alt');
|
||||||
let ship = this.props.ship;
|
let ship = this.props.ship;
|
||||||
ship.internal.forEach((slot) => {
|
ship.internal.forEach((slot) => {
|
||||||
@@ -131,7 +100,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
ship.use(slot, ModuleUtils.findInternal('pci', Math.min(slot.maxClass, 6), 'D')); // Passenger cabins top out at 6
|
ship.use(slot, ModuleUtils.findInternal('pci', Math.min(slot.maxClass, 6), 'D')); // Passenger cabins top out at 6
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,7 +108,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
*/
|
*/
|
||||||
_fillWithEconomyClassCabins(event) {
|
_fillWithEconomyClassCabins(event) {
|
||||||
this.selectedRefId = 'pce';
|
|
||||||
let clobber = event.getModifierState('Alt');
|
let clobber = event.getModifierState('Alt');
|
||||||
let ship = this.props.ship;
|
let ship = this.props.ship;
|
||||||
ship.internal.forEach((slot) => {
|
ship.internal.forEach((slot) => {
|
||||||
@@ -148,7 +115,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
ship.use(slot, ModuleUtils.findInternal('pce', Math.min(slot.maxClass, 6), 'E')); // Passenger cabins top out at 6
|
ship.use(slot, ModuleUtils.findInternal('pce', Math.min(slot.maxClass, 6), 'E')); // Passenger cabins top out at 6
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +123,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
*/
|
*/
|
||||||
_fillWithCells(event) {
|
_fillWithCells(event) {
|
||||||
this.selectedRefId = 'scb';
|
|
||||||
let clobber = event.getModifierState('Alt');
|
let clobber = event.getModifierState('Alt');
|
||||||
let ship = this.props.ship;
|
let ship = this.props.ship;
|
||||||
let chargeCap = 0; // Capacity of single activation
|
let chargeCap = 0; // Capacity of single activation
|
||||||
@@ -168,7 +133,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
chargeCap += slot.m.recharge;
|
chargeCap += slot.m.recharge;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +141,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
*/
|
*/
|
||||||
_fillWithArmor(event) {
|
_fillWithArmor(event) {
|
||||||
this.selectedRefId = 'hr';
|
|
||||||
let clobber = event.getModifierState('Alt');
|
let clobber = event.getModifierState('Alt');
|
||||||
let ship = this.props.ship;
|
let ship = this.props.ship;
|
||||||
ship.internal.forEach((slot) => {
|
ship.internal.forEach((slot) => {
|
||||||
@@ -185,7 +148,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
ship.use(slot, ModuleUtils.findInternal('hr', Math.min(slot.maxClass, 5), 'D')); // Hull reinforcements top out at 5D
|
ship.use(slot, ModuleUtils.findInternal('hr', Math.min(slot.maxClass, 5), 'D')); // Hull reinforcements top out at 5D
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -194,7 +156,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
*/
|
*/
|
||||||
_fillWithModuleReinforcementPackages(event) {
|
_fillWithModuleReinforcementPackages(event) {
|
||||||
this.selectedRefId = 'mrp';
|
|
||||||
let clobber = event.getModifierState('Alt');
|
let clobber = event.getModifierState('Alt');
|
||||||
let ship = this.props.ship;
|
let ship = this.props.ship;
|
||||||
ship.internal.forEach((slot) => {
|
ship.internal.forEach((slot) => {
|
||||||
@@ -202,7 +163,6 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
ship.use(slot, ModuleUtils.findInternal('mrp', Math.min(slot.maxClass, 5), 'D')); // Module reinforcements top out at 5D
|
ship.use(slot, ModuleUtils.findInternal('mrp', Math.min(slot.maxClass, 5), 'D')); // Module reinforcements top out at 5D
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,31 +179,20 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
*/
|
*/
|
||||||
_getSlots() {
|
_getSlots() {
|
||||||
let slots = [];
|
let slots = [];
|
||||||
let { currentMenu, ship } = this.props;
|
let { currentMenu, ship, propsToShow, onPropToggle } = this.props;
|
||||||
let { originSlot, targetSlot } = this.state;
|
let { originSlot, targetSlot } = this.state;
|
||||||
let { internal, fuelCapacity } = ship;
|
|
||||||
let availableModules = ship.getAvailableModules();
|
|
||||||
|
|
||||||
for (let i = 0, l = internal.length; i < l; i++) {
|
for (const m of ship.getInternals(undefined, true)) {
|
||||||
let s = internal[i];
|
slots.push(<Slot
|
||||||
|
key={m.object.Slot}
|
||||||
slots.push(<InternalSlot
|
currentMenu={currentMenu}
|
||||||
key={i}
|
m={m}
|
||||||
maxClass={s.maxClass}
|
drag={this._drag.bind(this, m)}
|
||||||
availableModules={() => availableModules.getInts(ship, s.maxClass, s.eligible)}
|
dragOver={this._dragOverSlot.bind(this, m)}
|
||||||
onOpen={this._openMenu.bind(this,s)}
|
|
||||||
onChange={this.props.onChange}
|
|
||||||
onSelect={this._selectModule.bind(this, s)}
|
|
||||||
selected={currentMenu == s}
|
|
||||||
eligible={s.eligible}
|
|
||||||
m={s.m}
|
|
||||||
drag={this._drag.bind(this, s)}
|
|
||||||
dragOver={this._dragOverSlot.bind(this, s)}
|
|
||||||
drop={this._drop}
|
drop={this._drop}
|
||||||
dropClass={this._dropClass(s, originSlot, targetSlot)}
|
dropClass={this._dropClass(m, originSlot, targetSlot)}
|
||||||
fuel={fuelCapacity}
|
propsToShow={propsToShow}
|
||||||
ship={ship}
|
onPropToggle={onPropToggle}
|
||||||
enabled={s.enabled ? true : false}
|
|
||||||
/>);
|
/>);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -256,22 +205,23 @@ export default class InternalSlotSection extends SlotSection {
|
|||||||
* @param {Function} ship The ship
|
* @param {Function} ship The ship
|
||||||
* @return {React.Component} Section menu
|
* @return {React.Component} Section menu
|
||||||
*/
|
*/
|
||||||
_getSectionMenu(translate, ship) {
|
_getSectionMenu() {
|
||||||
|
const { ship } = this.props;
|
||||||
|
const { translate } = this.context.language;
|
||||||
return <div className='select' onClick={e => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
|
return <div className='select' onClick={e => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='lc' tabIndex='0' onClick={this._empty} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['emptyall'] = smRef}>{translate('empty all')}</li>
|
<li className='lc' tabIndex='0' onClick={this._empty}>{translate('empty all')}</li>
|
||||||
<li className='lc' tabIndex='0' onClick={this._fillWithCargo} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['cargo'] = smRef}>{translate('cargo')}</li>
|
<li className='lc' tabIndex='0' onClick={this._fillWithCargo}>{translate('cargo')}</li>
|
||||||
<li className='lc' tabIndex='0' onClick={this._fillWithCells} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['scb'] = smRef}>{translate('scb')}</li>
|
<li className='lc' tabIndex='0' onClick={this._fillWithCells}>{translate('scb')}</li>
|
||||||
<li className='lc' tabIndex='0' onClick={this._fillWithArmor} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['hr'] = smRef}>{translate('hr')}</li>
|
<li className='lc' tabIndex='0' onClick={this._fillWithArmor}>{translate('hr')}</li>
|
||||||
<li className='lc' tabIndex='0' onClick={this._fillWithModuleReinforcementPackages} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['mrp'] = smRef}>{translate('mrp')}</li>
|
<li className='lc' tabIndex='0' onClick={this._fillWithModuleReinforcementPackages}>{translate('mrp')}</li>
|
||||||
<li className='lc' tabIndex='0' onClick={this._fillWithFuelTanks} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ft'] = smRef}>{translate('ft')}</li>
|
<li className='lc' tabIndex='0' onClick={this._fillWithFuelTanks}>{translate('ft')}</li>
|
||||||
<li className='lc' tabIndex='0' onClick={this._fillWithEconomyClassCabins} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pce'] = smRef}>{translate('pce')}</li>
|
<li className='lc' tabIndex='0' onClick={this._fillWithEconomyClassCabins}>{translate('pce')}</li>
|
||||||
<li className='lc' tabIndex='0' onClick={this._fillWithBusinessClassCabins} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pci'] = smRef}>{translate('pci')}</li>
|
<li className='lc' tabIndex='0' onClick={this._fillWithBusinessClassCabins}>{translate('pci')}</li>
|
||||||
<li className='lc' tabIndex='0' onClick={this._fillWithFirstClassCabins} onKeyDown={ship.luxuryCabins ? '' : this._keyDown} ref={smRef => this.sectionRefArr['pcm'] = smRef}>{translate('pcm')}</li>
|
<li className='lc' tabIndex='0' onClick={this._fillWithFirstClassCabins} onKeyDown={ship.luxuryCabins ? '' : this._keyDown}>{translate('pcm')}</li>
|
||||||
{ ship.luxuryCabins ? <li className='lc' tabIndex='0' onClick={this._fillWithLuxuryCabins} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pcq'] = smRef}>{translate('pcq')}</li> : ''}
|
{ ship.luxuryCabins ? <li className='lc' tabIndex='0' onClick={this._fillWithLuxuryCabins}>{translate('pcq')}</li> : ''}
|
||||||
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
|
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import ContainerDimensions from 'react-container-dimensions';
|
import ContainerDimensions from 'react-container-dimensions';
|
||||||
import * as d3 from 'd3';
|
import * as d3 from 'd3';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
|
||||||
const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 };
|
const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 };
|
||||||
|
|
||||||
@@ -10,7 +11,6 @@ const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 };
|
|||||||
* Line Chart
|
* Line Chart
|
||||||
*/
|
*/
|
||||||
export default class LineChart extends TranslatedComponent {
|
export default class LineChart extends TranslatedComponent {
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
code: '',
|
code: '',
|
||||||
xMin: 0,
|
xMin: 0,
|
||||||
@@ -45,13 +45,7 @@ export default class LineChart extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
this._updateDimensions = this._updateDimensions.bind(this);
|
|
||||||
this._updateSeries = this._updateSeries.bind(this);
|
|
||||||
this._tooltip = this._tooltip.bind(this);
|
|
||||||
this._showTip = this._showTip.bind(this);
|
|
||||||
this._hideTip = this._hideTip.bind(this);
|
|
||||||
this._moveTip = this._moveTip.bind(this);
|
|
||||||
|
|
||||||
const series = props.series;
|
const series = props.series;
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { shallowEqual } from '../utils/UtilityFunctions';
|
|||||||
* Link wrapper component
|
* Link wrapper component
|
||||||
*/
|
*/
|
||||||
export default class Link extends React.Component {
|
export default class Link extends React.Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
children: PropTypes.any,
|
children: PropTypes.any,
|
||||||
href: PropTypes.string.isRequired,
|
href: PropTypes.string.isRequired,
|
||||||
@@ -56,5 +55,4 @@ export default class Link extends React.Component {
|
|||||||
render() {
|
render() {
|
||||||
return <a {...this.props} onClick={this.handler}>{this.props.children}</a>;
|
return <a {...this.props} onClick={this.handler}>{this.props.children}</a>;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import Persist from '../stores/Persist';
|
|||||||
* Permalink modal
|
* Permalink modal
|
||||||
*/
|
*/
|
||||||
export default class ModalBatchOrbis extends TranslatedComponent {
|
export default class ModalBatchOrbis extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ships: PropTypes.any.isRequired
|
ships: PropTypes.any.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ function buildComparator(a, b) {
|
|||||||
* Compare builds modal
|
* Compare builds modal
|
||||||
*/
|
*/
|
||||||
export default class ModalCompare extends TranslatedComponent {
|
export default class ModalCompare extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onSelect: PropTypes.func.isRequired,
|
onSelect: PropTypes.func.isRequired,
|
||||||
builds: PropTypes.array
|
builds: PropTypes.array
|
||||||
@@ -105,8 +104,8 @@ export default class ModalCompare extends TranslatedComponent {
|
|||||||
|
|
||||||
let selectedBuilds = usedBuilds.map((build, i) =>
|
let selectedBuilds = usedBuilds.map((build, i) =>
|
||||||
<tr key={i} onClick={this._removeBuild.bind(this, i)}>
|
<tr key={i} onClick={this._removeBuild.bind(this, i)}>
|
||||||
<td className='tl'>{build.name}</td><
|
<td className='tl'>{build.name}</td>
|
||||||
td className='tl'>{build.buildName}</td>
|
<td className='tl'>{build.buildName}</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import TranslatedComponent from './TranslatedComponent';
|
|||||||
* Export Modal
|
* Export Modal
|
||||||
*/
|
*/
|
||||||
export default class ModalExport extends TranslatedComponent {
|
export default class ModalExport extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
generator: PropTypes.func,
|
generator: PropTypes.func,
|
||||||
|
|||||||
@@ -7,19 +7,10 @@ import TranslatedComponent from './TranslatedComponent';
|
|||||||
* Help Modal
|
* Help Modal
|
||||||
*/
|
*/
|
||||||
export default class ModalHelp extends TranslatedComponent {
|
export default class ModalHelp extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
title: PropTypes.string
|
title: PropTypes.string
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
* @param {Object} props React Component properties
|
|
||||||
*/
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the modal
|
* Render the modal
|
||||||
* @return {React.Component} Modal Content
|
* @return {React.Component} Modal Content
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import { Download } from './SvgIcons';
|
|||||||
import { outfitURL } from '../utils/UrlGenerators';
|
import { outfitURL } from '../utils/UrlGenerators';
|
||||||
import * as CompanionApiUtils from '../utils/CompanionApiUtils';
|
import * as CompanionApiUtils from '../utils/CompanionApiUtils';
|
||||||
|
|
||||||
|
const zlib = require('pako');
|
||||||
|
|
||||||
const textBuildRegex = new RegExp('^\\[([\\w \\-]+)\\]\n');
|
const textBuildRegex = new RegExp('^\\[([\\w \\-]+)\\]\n');
|
||||||
const lineRegex = new RegExp('^([\\dA-Z]{1,2}): (\\d)([A-I])[/]?([FGT])?([SD])? ([\\w\\- ]+)');
|
const lineRegex = new RegExp('^([\\dA-Z]{1,2}): (\\d)([A-I])[/]?([FGT])?([SD])? ([\\w\\- ]+)');
|
||||||
const mountMap = { 'H': 4, 'L': 3, 'M': 2, 'S': 1, 'U': 0 };
|
const mountMap = { 'H': 4, 'L': 3, 'M': 2, 'S': 1, 'U': 0 };
|
||||||
@@ -83,8 +85,6 @@ function detailedJsonToBuild(detailedBuild) {
|
|||||||
* Import Modal
|
* Import Modal
|
||||||
*/
|
*/
|
||||||
export default class ModalImport extends TranslatedComponent {
|
export default class ModalImport extends TranslatedComponent {
|
||||||
|
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
builds: PropTypes.object, // Optional: Import object
|
builds: PropTypes.object, // Optional: Import object
|
||||||
};
|
};
|
||||||
@@ -99,6 +99,7 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
this.state = {
|
this.state = {
|
||||||
builds: props.builds,
|
builds: props.builds,
|
||||||
canEdit: !props.builds,
|
canEdit: !props.builds,
|
||||||
|
loadoutEvent: null,
|
||||||
comparisons: null,
|
comparisons: null,
|
||||||
shipDiscount: null,
|
shipDiscount: null,
|
||||||
moduleDiscount: null,
|
moduleDiscount: null,
|
||||||
@@ -111,12 +112,28 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
this._process = this._process.bind(this);
|
this._process = this._process.bind(this);
|
||||||
this._import = this._import.bind(this);
|
this._import = this._import.bind(this);
|
||||||
this._importBackup = this._importBackup.bind(this);
|
this._importBackup = this._importBackup.bind(this);
|
||||||
|
this._importLoadout = this._importLoadout.bind(this);
|
||||||
this._importDetailedArray = this._importDetailedArray.bind(this);
|
this._importDetailedArray = this._importDetailedArray.bind(this);
|
||||||
this._importTextBuild = this._importTextBuild.bind(this);
|
this._importTextBuild = this._importTextBuild.bind(this);
|
||||||
this._importCompanionApiBuild = this._importCompanionApiBuild.bind(this);
|
this._importCompanionApiBuild = this._importCompanionApiBuild.bind(this);
|
||||||
this._validateImport = this._validateImport.bind(this);
|
this._validateImport = this._validateImport.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import a Loadout event from Elite: Dangerous journal files
|
||||||
|
* @param {Object} data Loadout event
|
||||||
|
* @throws {string} If import fails
|
||||||
|
*/
|
||||||
|
_importLoadout(data) {
|
||||||
|
if (data && data.Ship && data.Modules) {
|
||||||
|
const deflated = zlib.deflate(JSON.stringify(data), { to: 'string' });
|
||||||
|
let compressed = btoa(deflated);
|
||||||
|
this.setState({ loadoutEvent: compressed });
|
||||||
|
} else {
|
||||||
|
throw 'Loadout event must contain Ship and Modules';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Import a Coriolis backup
|
* Import a Coriolis backup
|
||||||
* @param {Object} importData Backup Data
|
* @param {Object} importData Backup Data
|
||||||
@@ -159,7 +176,7 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
}
|
}
|
||||||
// Check for module discount
|
// Check for module discount
|
||||||
if (!isNaN(importData.moduleDiscount)) {
|
if (!isNaN(importData.moduleDiscount)) {
|
||||||
this.setState({ shipDiscount: importData.moduleDiscount * 1 });
|
this.setState({ moduleDiscount: importData.moduleDiscount * 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof importData.insurance == 'string') {
|
if (typeof importData.insurance == 'string') {
|
||||||
@@ -345,12 +362,14 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
} else if (importData.ship && typeof importData.name !== undefined) { // Using JSON from a single ship build export
|
} else if (importData.ship && typeof importData.name !== undefined) { // Using JSON from a single ship build export
|
||||||
this._importDetailedArray([importData]); // Convert to array with singleobject
|
this._importDetailedArray([importData]); // Convert to array with singleobject
|
||||||
this.setState({ singleBuild: true });
|
this.setState({ singleBuild: true });
|
||||||
|
} else if (importData.Modules != null && importData.Modules[0] != null) {
|
||||||
|
this._importLoadout(importData);
|
||||||
} else { // Using Backup JSON
|
} else { // Using Backup JSON
|
||||||
this._importBackup(importData);
|
this._importBackup(importData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// console.log(e.stack);
|
console.log(e);
|
||||||
this.setState({ errorMsg: (typeof e == 'string') ? e : 'Cannot Parse the data!' });
|
this.setState({ errorMsg: (typeof e == 'string') ? e : 'Cannot Parse the data!' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -364,6 +383,10 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
_process() {
|
_process() {
|
||||||
let builds = null, comparisons = null;
|
let builds = null, comparisons = null;
|
||||||
|
|
||||||
|
if (this.state.loadoutEvent) {
|
||||||
|
return Router.go(`/import?data=${this.state.loadoutEvent}`);
|
||||||
|
}
|
||||||
|
|
||||||
// If only importing a single build go straight to the outfitting page
|
// If only importing a single build go straight to the outfitting page
|
||||||
if (this.state.singleBuild) {
|
if (this.state.singleBuild) {
|
||||||
builds = this.state.builds;
|
builds = this.state.builds;
|
||||||
@@ -480,7 +503,7 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
if (!state.processed) {
|
if (!state.processed) {
|
||||||
importStage = (
|
importStage = (
|
||||||
<div>
|
<div>
|
||||||
<textarea className='cb json' ref={node => this.importField = node} onChange={this._validateImport} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} />
|
<textarea spellCheck={false} className='cb json' ref={node => this.importField = node} onChange={this._validateImport} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} />
|
||||||
<button id='proceed' className='l cap' onClick={this._process} disabled={!state.importValid} >{translate('proceed')}</button>
|
<button id='proceed' className='l cap' onClick={this._process} disabled={!state.importValid} >{translate('proceed')}</button>
|
||||||
<div className='l warning' style={{ marginLeft:'3em' }}>{state.errorMsg}</div>
|
<div className='l warning' style={{ marginLeft:'3em' }}>{state.errorMsg}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import Persist from '../stores/Persist';
|
|||||||
* Permalink modal
|
* Permalink modal
|
||||||
*/
|
*/
|
||||||
export default class ModalOrbis extends TranslatedComponent {
|
export default class ModalOrbis extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.any.isRequired
|
ship: PropTypes.any.isRequired
|
||||||
};
|
};
|
||||||
@@ -98,7 +97,7 @@ export default class ModalOrbis extends TranslatedComponent {
|
|||||||
let ship = this.state.ship;
|
let ship = this.state.ship;
|
||||||
let cat = e.target.value;
|
let cat = e.target.value;
|
||||||
ship.category = cat;
|
ship.category = cat;
|
||||||
this.setState({ship});
|
this.setState({ ship });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import ShortenUrl from '../utils/ShortenUrl';
|
|||||||
* Permalink modal
|
* Permalink modal
|
||||||
*/
|
*/
|
||||||
export default class ModalPermalink extends TranslatedComponent {
|
export default class ModalPermalink extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
url: PropTypes.string.isRequired
|
url: PropTypes.string.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import Persist from '../stores/Persist';
|
|||||||
* Permalink modal
|
* Permalink modal
|
||||||
*/
|
*/
|
||||||
export default class ModalShoppingList extends TranslatedComponent {
|
export default class ModalShoppingList extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.object.isRequired
|
ship: PropTypes.object.isRequired
|
||||||
};
|
};
|
||||||
@@ -109,17 +108,18 @@ export default class ModalShoppingList extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
sendToEDEng(event) {
|
sendToEDEng(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
let translate = this.context.language.translate;
|
||||||
const target = event.target;
|
const target = event.target;
|
||||||
target.disabled = this.state.blueprints.length > 0;
|
target.disabled = this.state.blueprints.length > 0;
|
||||||
if (this.state.blueprints.length === 0) {
|
if (this.state.blueprints.length === 0) {
|
||||||
target.innerText = 'No modded components.';
|
target.innerText = translate('No modded components.');
|
||||||
target.disabled = true;
|
target.disabled = true;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
target.innerText = 'Send to EDEngineer';
|
target.innerText = translate('Send to EDEngineer');
|
||||||
target.disabled = false;
|
target.disabled = false;
|
||||||
}, 3000);
|
}, 3000);
|
||||||
} else {
|
} else {
|
||||||
target.innerText = 'Sending...';
|
target.innerText = translate('Sending...');
|
||||||
}
|
}
|
||||||
let countSent = 0;
|
let countSent = 0;
|
||||||
let countTotal = this.state.blueprints.length;
|
let countTotal = this.state.blueprints.length;
|
||||||
@@ -139,7 +139,7 @@ export default class ModalShoppingList extends TranslatedComponent {
|
|||||||
countSent++;
|
countSent++;
|
||||||
if (countSent === countTotal) {
|
if (countSent === countTotal) {
|
||||||
target.disabled = false;
|
target.disabled = false;
|
||||||
target.innerText = 'Send to EDEngineer';
|
target.innerText = translate('Send to EDEngineer');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -230,32 +230,32 @@ export default class ModalShoppingList extends TranslatedComponent {
|
|||||||
this.sendToEDEng = this.sendToEDEng.bind(this);
|
this.sendToEDEng = this.sendToEDEng.bind(this);
|
||||||
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
|
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
|
||||||
<h2>{translate('PHRASE_SHOPPING_MATS')}</h2>
|
<h2>{translate('PHRASE_SHOPPING_MATS')}</h2>
|
||||||
<label>Grade 1 rolls </label>
|
<label>{translate('Grade 1 rolls ')}</label>
|
||||||
<input id={1} type={'number'} min={0} defaultValue={this.state.matsPerGrade[1]} onChange={this.changeHandler} />
|
<input id={1} type={'number'} min={0} defaultValue={this.state.matsPerGrade[1]} onChange={this.changeHandler} />
|
||||||
<br/>
|
<br/>
|
||||||
<label>Grade 2 rolls </label>
|
<label>{translate('Grade 2 rolls ')}</label>
|
||||||
<input id={2} type={'number'} min={0} defaultValue={this.state.matsPerGrade[2]} onChange={this.changeHandler} />
|
<input id={2} type={'number'} min={0} defaultValue={this.state.matsPerGrade[2]} onChange={this.changeHandler} />
|
||||||
<br/>
|
<br/>
|
||||||
<label>Grade 3 rolls </label>
|
<label>{translate('Grade 3 rolls ')}</label>
|
||||||
<input id={3} type={'number'} min={0} value={this.state.matsPerGrade[3]} onChange={this.changeHandler} />
|
<input id={3} type={'number'} min={0} value={this.state.matsPerGrade[3]} onChange={this.changeHandler} />
|
||||||
<br/>
|
<br/>
|
||||||
<label>Grade 4 rolls </label>
|
<label>{translate('Grade 4 rolls ')}</label>
|
||||||
<input id={4} type={'number'} min={0} value={this.state.matsPerGrade[4]} onChange={this.changeHandler} />
|
<input id={4} type={'number'} min={0} value={this.state.matsPerGrade[4]} onChange={this.changeHandler} />
|
||||||
<br/>
|
<br/>
|
||||||
<label>Grade 5 rolls </label>
|
<label>{translate('Grade 5 rolls ')}</label>
|
||||||
<input id={5} type={'number'} min={0} value={this.state.matsPerGrade[5]} onChange={this.changeHandler} />
|
<input id={5} type={'number'} min={0} value={this.state.matsPerGrade[5]} onChange={this.changeHandler} />
|
||||||
<div>
|
<div>
|
||||||
<textarea className='cb json' readOnly value={this.state.matsList} />
|
<textarea className='cb json' readOnly value={this.state.matsList} />
|
||||||
</div>
|
</div>
|
||||||
<label hidden={!compatible} className={'l cap'}>CMDR Name </label>
|
<label hidden={!compatible} className={'l cap'}>{translate('CMDR Name')}</label>
|
||||||
<br/>
|
<br/>
|
||||||
<select hidden={!compatible} className={'cmdr-select l cap'} onChange={this.cmdrChangeHandler} defaultValue={this.state.cmdrName}>
|
<select hidden={!compatible} className={'cmdr-select l cap'} onChange={this.cmdrChangeHandler} defaultValue={this.state.cmdrName}>
|
||||||
{this.state.cmdrs.map(e => <option key={e}>{e}</option>)}
|
{this.state.cmdrs.map(e => <option key={e}>{e}</option>)}
|
||||||
</select>
|
</select>
|
||||||
<br/>
|
<br/>
|
||||||
<p hidden={!this.state.failed} id={'failed'} className={'l'}>Failed to send to EDEngineer (Launch EDEngineer and make sure the API is started then refresh the page.)</p>
|
<p hidden={!this.state.failed} id={'failed'} className={'l'}>{translate('PHRASE_FAIL_EDENGINEER')}</p>
|
||||||
<p hidden={compatible} id={'browserbad'} className={'l'}>Sending to EDEngineer is not compatible with Firefox's security settings. Please try again with Chrome.</p>
|
<p hidden={compatible} id={'browserbad'} className={'l'}>{translate('PHRASE_FIREFOX_EDENGINEER')}</p>
|
||||||
<button className={'l cb dismiss cap'} disabled={!!this.state.failed || !compatible} onClick={this.sendToEDEng}>{translate('Send To EDEngineer')}</button>
|
<button className={'l cb dismiss cap'} disabled={!!this.state.failed || !compatible} onClick={this.sendToEDEng}>{translate('Send to EDEngineer')}</button>
|
||||||
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
|
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,129 +3,106 @@ import PropTypes from 'prop-types';
|
|||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import NumberEditor from 'react-number-editor';
|
import NumberEditor from 'react-number-editor';
|
||||||
import { isValueBeneficial } from '../utils/BlueprintFunctions';
|
import { Module } from 'ed-forge';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modification
|
* Modification
|
||||||
*/
|
*/
|
||||||
export default class Modification extends TranslatedComponent {
|
export default class Modification extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.object.isRequired,
|
highlight: PropTypes.bool,
|
||||||
m: PropTypes.object.isRequired,
|
m: PropTypes.instanceOf(Module).isRequired,
|
||||||
name: PropTypes.string.isRequired,
|
property: PropTypes.string.isRequired,
|
||||||
value: PropTypes.number.isRequired,
|
onSet: PropTypes.func.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
showProp: PropTypes.object,
|
||||||
onKeyDown: PropTypes.func.isRequired,
|
onPropToggle: PropTypes.func.isRequired,
|
||||||
modItems: PropTypes.array.isRequired,
|
|
||||||
handleModChange: PropTypes.func.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
* @param {Object} context React Component context
|
|
||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {};
|
const { m, property, showProp } = props;
|
||||||
this.state.value = props.value;
|
const { beneficial, unit, value } = m.getFormatted(property, true);
|
||||||
|
this.state = { beneficial, unit, value, showProp };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update modification given a value.
|
* Notify listeners that a new value has been entered and commited.
|
||||||
* @param {Number} value The value to set. This comes in as a string and must be stored in state as a string,
|
|
||||||
* because it needs to allow illegal 'numbers' ('-', '1.', etc) when the user is typing
|
|
||||||
* in a value by hand
|
|
||||||
*/
|
|
||||||
_updateValue(value) {
|
|
||||||
this.setState({ value });
|
|
||||||
let reCast = String(Number(value));
|
|
||||||
if (reCast.endsWith(value) || reCast.startsWith(value)) {
|
|
||||||
let { m, name, ship } = this.props;
|
|
||||||
value = Math.max(Math.min(value, 50000), -50000);
|
|
||||||
ship.setModification(m, name, value, true, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggered when a key is pressed down with focus on the number editor.
|
|
||||||
* @param {SyntheticEvent} event Key down event
|
|
||||||
*/
|
|
||||||
_keyDown(event) {
|
|
||||||
if (event.key == 'Enter') {
|
|
||||||
this._updateFinished();
|
|
||||||
}
|
|
||||||
this.props.onKeyDown(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggered when an update to slider value is finished i.e. when losing focus
|
|
||||||
*
|
|
||||||
* pnellesen (24/05/2018): added value check below - this should prevent experimental effects from being recalculated
|
|
||||||
* with each onBlur event, even when no change has actually been made to the field.
|
|
||||||
*/
|
*/
|
||||||
_updateFinished() {
|
_updateFinished() {
|
||||||
if (this.props.value != this.state.value) {
|
const { onSet, m, property } = this.props;
|
||||||
this.props.handleModChange(true);
|
const { inputValue } = this.state;
|
||||||
this.props.onChange();
|
const numValue = Number(inputValue);
|
||||||
|
if (!isNaN(numValue) && this.state.value !== numValue) {
|
||||||
|
onSet(property, numValue);
|
||||||
|
const { beneficial, unit, value } = m.getFormatted(property, true);
|
||||||
|
this.setState({ beneficial, unit, value });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_toggleProperty() {
|
||||||
|
const { onPropToggle, property } = this.props;
|
||||||
|
const showProp = !this.state.showProp;
|
||||||
|
// TODO: defer until menu closed
|
||||||
|
onPropToggle(property, showProp);
|
||||||
|
this.setState({ showProp });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the modification
|
* Render the modification
|
||||||
* @return {React.Component} modification
|
* @return {React.Component} modification
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
let { translate, formats, units } = this.context.language;
|
const { formats } = this.context.language;
|
||||||
let { m, name } = this.props;
|
const { highlight, m, property } = this.props;
|
||||||
let modValue = m.getChange(name);
|
const { beneficial, unit, value, inputValue, showProp } = this.state;
|
||||||
|
|
||||||
if (name === 'damagedist') {
|
// Some features only apply to specific modules; these features will be
|
||||||
// We don't show damage distribution
|
// undefined on items that do not belong to the same class. Filter these
|
||||||
|
// features here
|
||||||
|
if (value === undefined) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let inputClassNames = {
|
const { value: modifierValue, unit: modifierUnit } = m.getModifierFormatted(property);
|
||||||
'cb': true,
|
|
||||||
'greyed-out': !this.props.highlight
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div onBlur={this._updateFinished.bind(this)} key={name}
|
|
||||||
className={cn('cb', 'modification-container')}
|
|
||||||
ref={ modItem => this.props.modItems[name] = modItem }>
|
|
||||||
<span className={'cb'}>{translate(name, m.grp)}</span>
|
|
||||||
<span className={'header-adjuster'}></span>
|
|
||||||
<table style={{ width: '100%' }}>
|
|
||||||
<tbody>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td className={'input-container'}>
|
<td>
|
||||||
<span>
|
<span>
|
||||||
{this.props.editable ?
|
<input type="checkbox" checked={showProp} onClick={() => this._toggleProperty()}/>
|
||||||
<NumberEditor className={cn(inputClassNames)} value={this.state.value}
|
</span>
|
||||||
decimals={2} style={{ textAlign: 'right' }} step={0.01}
|
</td>
|
||||||
stepModifier={1} onKeyDown={this._keyDown.bind(this)}
|
<td className="input-container">
|
||||||
onValueChange={this._updateValue.bind(this)} /> :
|
<span>
|
||||||
<input type="text" value={formats.f2(this.state.value)}
|
<NumberEditor value={inputValue || value} stepModifier={1}
|
||||||
disabled className={cn('number-editor', 'greyed-out')}
|
decimals={2} step={0.01} style={{ textAlign: 'right', width: '100%' }}
|
||||||
style={{ textAlign: 'right', cursor: 'inherit' }}/>
|
className={cn('cb', { 'greyed-out': !highlight })}
|
||||||
|
onKeyDown={(event) => {
|
||||||
|
if (event.key == 'Enter') {
|
||||||
|
this._updateFinished();
|
||||||
|
event.stopPropagation();
|
||||||
}
|
}
|
||||||
<span className={'unit-container'}>
|
}}
|
||||||
{units[m.getStoredUnitFor(name)]}
|
onValueChange={(inputValue) => {
|
||||||
</span>
|
if (inputValue.length <= 15) {
|
||||||
|
this.setState({ inputValue });
|
||||||
|
}
|
||||||
|
}} />
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td style={{ textAlign: 'center' }} className={
|
<td style={{ textAlign: 'left' }}>
|
||||||
modValue ?
|
<span className="unit-container">{unit}</span>
|
||||||
isValueBeneficial(name, modValue) ? 'secondary' : 'warning' :
|
|
||||||
''
|
|
||||||
}>
|
|
||||||
{formats.f2(modValue / 100) || 0}%
|
|
||||||
</td>
|
</td>
|
||||||
|
<td style={{ textAlign: 'center' }}
|
||||||
|
className={cn({
|
||||||
|
secondary: beneficial,
|
||||||
|
warning: beneficial === false,
|
||||||
|
})}
|
||||||
|
>{formats.f2(modifierValue)}{modifierUnit || ''}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +1,27 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import * as _ from 'lodash';
|
import { chain, flatMap, keys } from 'lodash';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import { Modifications } from 'coriolis-data/dist';
|
|
||||||
import Modification from './Modification';
|
import Modification from './Modification';
|
||||||
import {
|
import {
|
||||||
getBlueprint,
|
|
||||||
blueprintTooltip,
|
blueprintTooltip,
|
||||||
setPercent,
|
|
||||||
getPercent,
|
|
||||||
setRandom,
|
|
||||||
specialToolTip
|
specialToolTip
|
||||||
} from '../utils/BlueprintFunctions';
|
} from '../utils/BlueprintFunctions';
|
||||||
|
import { getBlueprintInfo, getExperimentalInfo } from 'ed-forge/lib/data/blueprints';
|
||||||
const MODIFICATIONS_COMPARATOR = (mod1, mod2) => {
|
import { getModuleInfo } from 'ed-forge/lib/data/items';
|
||||||
return mod1.props.name.localeCompare(mod2.props.name);
|
import { SHOW } from '../shipyard/StatsMapping';
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Modifications menu
|
* Modifications menu
|
||||||
*/
|
*/
|
||||||
export default class ModificationsMenu extends TranslatedComponent {
|
export default class ModificationsMenu extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.object.isRequired,
|
className: PropTypes.string,
|
||||||
m: PropTypes.object.isRequired,
|
m: PropTypes.object.isRequired,
|
||||||
marker: PropTypes.string.isRequired,
|
propsToShow: PropTypes.object.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
onPropToggle: PropTypes.func.isRequired,
|
||||||
modButton:PropTypes.object
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -41,122 +34,58 @@ export default class ModificationsMenu extends TranslatedComponent {
|
|||||||
|
|
||||||
this._toggleBlueprintsMenu = this._toggleBlueprintsMenu.bind(this);
|
this._toggleBlueprintsMenu = this._toggleBlueprintsMenu.bind(this);
|
||||||
this._toggleSpecialsMenu = this._toggleSpecialsMenu.bind(this);
|
this._toggleSpecialsMenu = this._toggleSpecialsMenu.bind(this);
|
||||||
this._rollFifty = this._rollFifty.bind(this);
|
this.selectedModRef = null;
|
||||||
this._rollRandom = this._rollRandom.bind(this);
|
this.selectedSpecialRef = null;
|
||||||
this._rollBest = this._rollBest.bind(this);
|
|
||||||
this._rollWorst = this._rollWorst.bind(this);
|
|
||||||
this._reset = this._reset.bind(this);
|
|
||||||
this._keyDown = this._keyDown.bind(this);
|
|
||||||
this.modItems = [];// Array to hold various element refs (<li>, <div>, <ul>, etc.)
|
|
||||||
this.firstModId = null;
|
|
||||||
this.firstBPLabel = null;// First item in mod menu
|
|
||||||
this.lastModId = null;
|
|
||||||
this.selectedModId = null;
|
|
||||||
this.selectedSpecialId = null;
|
|
||||||
this.lastNeId = null;// Last number editor id. Used to set focus to last number editor when shift-tab pressed on first element in mod menu.
|
|
||||||
this.modValDidChange = false; // used to determine if component update was caused by change in modification value.
|
|
||||||
this._handleModChange = this._handleModChange.bind(this);
|
|
||||||
|
|
||||||
|
const { m } = props;
|
||||||
this.state = {
|
this.state = {
|
||||||
blueprintMenuOpened: !(props.m.blueprint && props.m.blueprint.name),
|
blueprintProgress: m.getBlueprintProgress(),
|
||||||
|
blueprintMenuOpened: !m.getBlueprint(),
|
||||||
specialMenuOpened: false
|
specialMenuOpened: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the blueprints
|
* Render the blueprints
|
||||||
* @param {Object} props React component properties
|
|
||||||
* @param {Object} context React component context
|
|
||||||
* @return {Object} list: Array of React Components
|
* @return {Object} list: Array of React Components
|
||||||
*/
|
*/
|
||||||
_renderBlueprints(props, context) {
|
_renderBlueprints() {
|
||||||
const { m } = props;
|
const { m } = this.props;
|
||||||
const { language, tooltip, termtip } = context;
|
const { language, tooltip, termtip } = this.context;
|
||||||
const translate = language.translate;
|
const { translate } = language;
|
||||||
const blueprints = [];
|
|
||||||
for (const blueprintName in Modifications.modules[m.grp].blueprints) {
|
const blueprints = m.getApplicableBlueprints().map(blueprint => {
|
||||||
const blueprint = getBlueprint(blueprintName, m);
|
const info = getBlueprintInfo(blueprint);
|
||||||
let blueprintGrades = [];
|
let blueprintGrades = keys(info.features).map(grade => {
|
||||||
for (let grade in Modifications.modules[m.grp].blueprints[blueprintName].grades) {
|
|
||||||
// Grade is a string in the JSON so make it a number
|
// Grade is a string in the JSON so make it a number
|
||||||
grade = Number(grade);
|
grade = Number(grade);
|
||||||
const classes = cn('c', {
|
const active = m.getBlueprint() === blueprint && m.getBlueprintGrade() === grade;
|
||||||
active: m.blueprint && blueprint.id === m.blueprint.id && grade === m.blueprint.grade
|
const key = blueprint + ':' + grade;
|
||||||
|
return <li key={key} data-id={key} className={cn('c', { active })}
|
||||||
|
style={{ width: '2em' }}
|
||||||
|
onMouseOver={termtip.bind(null, blueprintTooltip(language, m, blueprint, grade))}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
onClick={() => {
|
||||||
|
m.setBlueprint(blueprint, grade, 1);
|
||||||
|
this.setState({
|
||||||
|
blueprintMenuOpened: false,
|
||||||
|
specialMenuOpened: true,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
ref={active ? (ref) => { this.selectedModRef = ref; } : undefined}
|
||||||
|
>{grade}</li>;
|
||||||
});
|
});
|
||||||
const close = this._blueprintSelected.bind(this, blueprintName, grade);
|
|
||||||
const key = blueprintName + ':' + grade;
|
|
||||||
const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers, m.grp);
|
|
||||||
if (classes.indexOf('active') >= 0) this.selectedModId = key;
|
|
||||||
blueprintGrades.unshift(<li key={key} tabIndex="0" data-id={key} className={classes} style={{ width: '2em' }} onMouseOver={termtip.bind(null, tooltipContent)} onMouseOut={tooltip.bind(null, null)} onClick={close} onKeyDown={this._keyDown} ref={modItem => this.modItems[key] = modItem}>{grade}</li>);
|
|
||||||
}
|
|
||||||
if (blueprintGrades) {
|
|
||||||
const thisLen = blueprintGrades.length;
|
|
||||||
if (this.firstModId == null) this.firstModId = blueprintGrades[0].key;
|
|
||||||
this.lastModId = blueprintGrades[thisLen - 1].key;
|
|
||||||
blueprints.push(<div key={blueprint.name} className={'select-group cap'}>{translate(blueprint.name)}</div>);
|
|
||||||
blueprints.push(<ul key={blueprintName}>{blueprintGrades}</ul>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return blueprints;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return [
|
||||||
* Key down - select module on Enter key, move to next/previous module on Tab/Shift-Tab, close on Esc
|
<div key={'div' + blueprint} className={'select-group cap'}>
|
||||||
* @param {SyntheticEvent} event Event
|
{translate(blueprint)}
|
||||||
*
|
</div>,
|
||||||
*/
|
<ul key={'ul' + blueprint}>{blueprintGrades}</ul>
|
||||||
_keyDown(event) {
|
];
|
||||||
let className = null;
|
});
|
||||||
let elemId = null;
|
|
||||||
if (event.currentTarget.attributes['class']) className = event.currentTarget.attributes['class'].value;
|
|
||||||
if (event.currentTarget.attributes['data-id']) elemId = event.currentTarget.attributes['data-id'].value;
|
|
||||||
|
|
||||||
if (event.key == 'Enter' && className.indexOf('disabled') < 0 && className.indexOf('active') < 0) {
|
return flatMap(blueprints);
|
||||||
event.stopPropagation();
|
|
||||||
if (elemId != null) {
|
|
||||||
this.modItems[elemId].click();
|
|
||||||
} else {
|
|
||||||
event.currentTarget.click();
|
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (event.key == 'Tab') {
|
|
||||||
// Shift-Tab
|
|
||||||
if(event.shiftKey) {
|
|
||||||
if (elemId == this.firstModId && elemId != null) {
|
|
||||||
// Initial modification menu
|
|
||||||
event.preventDefault();
|
|
||||||
this.modItems[this.lastModId].focus();
|
|
||||||
return;
|
|
||||||
} else if (event.currentTarget.className.indexOf('button-inline-menu') >= 0 && event.currentTarget.previousElementSibling == null && this.lastNeId != null && this.modItems[this.lastNeId] != null) {
|
|
||||||
// shift-tab on first element in modifications menu. set focus to last number editor field if open
|
|
||||||
event.preventDefault();
|
|
||||||
this.modItems[this.lastNeId].lastChild.focus();
|
|
||||||
return;
|
|
||||||
} else if (event.currentTarget.className.indexOf('button-inline-menu') >= 0 && event.currentTarget.previousElementSibling == null) {
|
|
||||||
// shift-tab on button-inline-menu with no number editor
|
|
||||||
event.preventDefault();
|
|
||||||
event.currentTarget.parentElement.lastElementChild.focus();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (elemId == this.lastModId && elemId != null) {
|
|
||||||
// Initial modification menu
|
|
||||||
event.preventDefault();
|
|
||||||
this.modItems[this.firstModId].focus();
|
|
||||||
return;
|
|
||||||
} else if (event.currentTarget.className.indexOf('button-inline-menu') >= 0 && event.currentTarget.nextSibling == null && event.currentTarget.nodeName != 'TD') {
|
|
||||||
// Experimental menu
|
|
||||||
event.preventDefault();
|
|
||||||
event.currentTarget.parentElement.firstElementChild.focus();
|
|
||||||
return;
|
|
||||||
} else if (event.currentTarget.className == 'cb' && event.currentTarget.parentElement.nextSibling == null) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.modItems[this.firstBPLabel].focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the specials
|
* Render the specials
|
||||||
@@ -164,204 +93,118 @@ export default class ModificationsMenu extends TranslatedComponent {
|
|||||||
* @param {Object} context React component context
|
* @param {Object} context React component context
|
||||||
* @return {Object} list: Array of React Components
|
* @return {Object} list: Array of React Components
|
||||||
*/
|
*/
|
||||||
_renderSpecials(props, context) {
|
_renderSpecials() {
|
||||||
const { m } = props;
|
const { m } = this.props;
|
||||||
const { language, tooltip, termtip } = context;
|
const { language, tooltip, termtip } = this.context;
|
||||||
const translate = language.translate;
|
const translate = language.translate;
|
||||||
const specials = [];
|
|
||||||
const specialsId = m.missile && Modifications.modules[m.grp]['specials_' + m.missile] ? 'specials_' + m.missile : 'specials';
|
const applied = m.getExperimental();
|
||||||
if (Modifications.modules[m.grp][specialsId] && Modifications.modules[m.grp][specialsId].length > 0) {
|
const experimentals = [];
|
||||||
const close = this._specialSelected.bind(this, null);
|
for (const experimental of m.getApplicableExperimentals()) {
|
||||||
specials.push(<div tabIndex="0" style={{ cursor: 'pointer', fontWeight: 'bold' }} className={ 'button-inline-menu warning' } key={ 'none' } data-id={ 'none' } onClick={ close } onKeyDown={this._keyDown} ref={modItem => this.modItems['none'] = modItem}>{translate('PHRASE_NO_SPECIAL')}</div>);
|
const active = experimental === applied;
|
||||||
for (const specialName of Modifications.modules[m.grp][specialsId]) {
|
let specialTt = specialToolTip(language, m, experimental);
|
||||||
if (Modifications.specials[specialName].name.search('Legacy') >= 0) {
|
experimentals.push(
|
||||||
continue;
|
<div key={experimental} data-id={experimental}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
className={cn('button-inline-menu', { active })}
|
||||||
|
onClick={this._specialSelected(experimental)}
|
||||||
|
ref={active ? (ref) => { this.selectedSpecialRef = ref; } : undefined}
|
||||||
|
onMouseOver={termtip.bind(null, specialTt)}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>{translate(experimental)}</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
const classes = cn('button-inline-menu', {
|
|
||||||
active: m.blueprint && m.blueprint.special && m.blueprint.special.edname == specialName
|
if (experimentals.length) {
|
||||||
});
|
experimentals.unshift(
|
||||||
if (classes.indexOf('active') >= 0) this.selectedSpecialId = specialName;
|
<div style={{ cursor: 'pointer', fontWeight: 'bold' }}
|
||||||
const close = this._specialSelected.bind(this, specialName);
|
className="button-inline-menu warning" key="none" data-id="none"
|
||||||
if (m.blueprint && m.blueprint.name) {
|
// Setting the special effect to undefined clears it
|
||||||
let tmp = {};
|
onClick={this._specialSelected(undefined)}
|
||||||
if (m.blueprint.special) {
|
ref={!applied ? (ref) => { this.selectedSpecialRef = ref; } : undefined}
|
||||||
tmp = m.blueprint.special;
|
>{translate('PHRASE_NO_SPECIAL')}</div>
|
||||||
} else {
|
);
|
||||||
tmp = undefined;
|
|
||||||
}
|
}
|
||||||
m.blueprint.special = Modifications.specials[specialName];
|
|
||||||
let specialTt = specialToolTip(translate, m.blueprint.grades[m.blueprint.grade], m.grp, m, specialName);
|
return experimentals;
|
||||||
m.blueprint.special = tmp;
|
|
||||||
specials.push(<div tabIndex="0" style={{ cursor: 'pointer' }} className={classes} key={ specialName } data-id={ specialName } onMouseOver={termtip.bind(null, specialTt)} onMouseOut={tooltip.bind(null, null)} onClick={ close } onKeyDown={this._keyDown} ref={modItem => this.modItems[specialName] = modItem}>{translate(Modifications.specials[specialName].name)}</div>);
|
|
||||||
} else {
|
|
||||||
specials.push(<div tabIndex="0" style={{ cursor: 'pointer' }} className={classes} key={ specialName } data-id={ specialName }onClick={ close } onKeyDown={this._keyDown} ref={modItem => this.modItems[specialName] = modItem}>{translate(Modifications.specials[specialName].name)}</div>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a modification component
|
||||||
|
*/
|
||||||
|
_mkModification(property, highlight) {
|
||||||
|
const { translate } = this.context.language;
|
||||||
|
const { m, propsToShow, onPropToggle } = this.props;
|
||||||
|
|
||||||
|
let onSet = m.set.bind(m);
|
||||||
|
// Show resistance instead of effectiveness
|
||||||
|
const mapped = SHOW[property];
|
||||||
|
if (mapped) {
|
||||||
|
property = mapped.as;
|
||||||
|
onSet = mapped.setter.bind(undefined, m);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return specials;
|
return [
|
||||||
|
<tr key={`th-${property}`}>
|
||||||
|
<th colSpan="4">
|
||||||
|
<span className="cb">{translate(property)}</span>
|
||||||
|
</th>
|
||||||
|
</tr>,
|
||||||
|
<Modification key={property} m={m} property={property}
|
||||||
|
onSet={onSet} highlight={highlight} showProp={propsToShow[property]}
|
||||||
|
onPropToggle={onPropToggle} />
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the modifications
|
* Render the modifications
|
||||||
* @param {Object} props React Component properties
|
* @return {Array} Array of React Components
|
||||||
* @return {Object} list: Array of React Components
|
|
||||||
*/
|
*/
|
||||||
_renderModifications(props) {
|
_renderModifications() {
|
||||||
const { m, onChange, ship } = props;
|
const { m } = this.props;
|
||||||
const modifiableModifications = [];
|
|
||||||
const modifications = [];
|
|
||||||
for (const modName of Modifications.modules[m.grp].modifications) {
|
|
||||||
if (!Modifications.modifications[modName].hidden) {
|
|
||||||
const key = modName + (m.getModValue(modName) / 100 || 0);
|
|
||||||
const editable = modName !== 'fallofffromrange';
|
|
||||||
const highlight = m.blueprint.grades[m.blueprint.grade].features[modName];
|
|
||||||
this.lastNeId = modName;
|
|
||||||
(editable && highlight ? modifiableModifications : modifications).push(
|
|
||||||
<Modification key={ key } ship={ ship } m={ m } highlight={highlight}
|
|
||||||
value={m.getPretty(modName) || 0} modItems={this.modItems}
|
|
||||||
onChange={onChange} onKeyDown={this._keyDown} name={modName}
|
|
||||||
editable={editable} handleModChange = {this._handleModChange} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
modifiableModifications.sort(MODIFICATIONS_COMPARATOR);
|
const blueprintFeatures = getBlueprintInfo(m.getBlueprint()).features[
|
||||||
modifications.sort(MODIFICATIONS_COMPARATOR);
|
m.getBlueprintGrade()
|
||||||
return modifiableModifications.concat(modifications);
|
];
|
||||||
|
const blueprintModifications = chain(keys(blueprintFeatures))
|
||||||
|
.map((feature) => this._mkModification(feature, true))
|
||||||
|
.filter(([_, mod]) => Boolean(mod))
|
||||||
|
.flatMap()
|
||||||
|
.value();
|
||||||
|
const moduleModifications = chain(keys(getModuleInfo(m.getItem()).props))
|
||||||
|
.filter((prop) => !blueprintFeatures[prop])
|
||||||
|
.map((prop) => this._mkModification(prop, false))
|
||||||
|
.flatMap()
|
||||||
|
.value();
|
||||||
|
|
||||||
|
return blueprintModifications.concat(moduleModifications);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle the blueprints menu
|
* Toggle the blueprints menu
|
||||||
*/
|
*/
|
||||||
_toggleBlueprintsMenu() {
|
_toggleBlueprintsMenu() {
|
||||||
const blueprintMenuOpened = !this.state.blueprintMenuOpened;
|
this.setState({ blueprintMenuOpened: !this.state.blueprintMenuOpened });
|
||||||
this.setState({ blueprintMenuOpened });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Activated when a blueprint is selected
|
|
||||||
* @param {int} fdname The Frontier name of the blueprint
|
|
||||||
* @param {int} grade The grade of the selected blueprint
|
|
||||||
*/
|
|
||||||
_blueprintSelected(fdname, grade) {
|
|
||||||
this.context.tooltip(null);
|
|
||||||
const { m, ship } = this.props;
|
|
||||||
const blueprint = getBlueprint(fdname, m);
|
|
||||||
blueprint.grade = grade;
|
|
||||||
ship.setModuleBlueprint(m, blueprint);
|
|
||||||
setPercent(ship, m, 100);
|
|
||||||
|
|
||||||
this.setState({ blueprintMenuOpened: false, specialMenuOpened: true });
|
|
||||||
this.props.onChange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle the specials menu
|
* Toggle the specials menu
|
||||||
*/
|
*/
|
||||||
_toggleSpecialsMenu() {
|
_toggleSpecialsMenu() {
|
||||||
const specialMenuOpened = !this.state.specialMenuOpened;
|
this.setState({ specialMenuOpened: !this.state.specialMenuOpened });
|
||||||
this.setState({ specialMenuOpened });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activated when a special is selected
|
* Creates a callback for when a special effect is being selected
|
||||||
* @param {int} special The name of the selected special
|
* @param {string} special The name of the selected special
|
||||||
|
* @returns {function} Callback
|
||||||
*/
|
*/
|
||||||
_specialSelected(special) {
|
_specialSelected(special) {
|
||||||
this.context.tooltip(null);
|
return () => {
|
||||||
const { m, ship } = this.props;
|
const { m } = this.props;
|
||||||
|
m.setExperimental(special);
|
||||||
if (special === null) {
|
|
||||||
ship.clearModuleSpecial(m);
|
|
||||||
} else {
|
|
||||||
ship.setModuleSpecial(m, Modifications.specials[special]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setState({ specialMenuOpened: false });
|
this.setState({ specialMenuOpened: false });
|
||||||
this.props.onChange();
|
};
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide a '50%' roll within the information we have
|
|
||||||
*/
|
|
||||||
_rollFifty() {
|
|
||||||
const { m, ship } = this.props;
|
|
||||||
setPercent(ship, m, 50);
|
|
||||||
|
|
||||||
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
|
|
||||||
this._handleModChange(true);
|
|
||||||
|
|
||||||
this.props.onChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide a random roll within the information we have
|
|
||||||
*/
|
|
||||||
_rollRandom() {
|
|
||||||
const { m, ship } = this.props;
|
|
||||||
setRandom(ship, m);
|
|
||||||
|
|
||||||
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
|
|
||||||
this._handleModChange(true);
|
|
||||||
|
|
||||||
this.props.onChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide a 'best' roll within the information we have
|
|
||||||
*/
|
|
||||||
_rollBest() {
|
|
||||||
const { m, ship } = this.props;
|
|
||||||
setPercent(ship, m, 100);
|
|
||||||
|
|
||||||
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
|
|
||||||
this._handleModChange(true);
|
|
||||||
|
|
||||||
this.props.onChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide a 'worst' roll within the information we have
|
|
||||||
*/
|
|
||||||
_rollWorst() {
|
|
||||||
const { m, ship } = this.props;
|
|
||||||
setPercent(ship, m, 0);
|
|
||||||
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
|
|
||||||
this._handleModChange(true);
|
|
||||||
this.props.onChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset modification information
|
|
||||||
*/
|
|
||||||
_reset() {
|
|
||||||
const { m, ship } = this.props;
|
|
||||||
ship.clearModifications(m);
|
|
||||||
ship.clearModuleBlueprint(m);
|
|
||||||
this.selectedModId = null;
|
|
||||||
this.selectedSpecialId = null;
|
|
||||||
this.props.onChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* set mod did change boolean
|
|
||||||
* @param {boolean} b Boolean to determine if a change has been made to a module
|
|
||||||
*/
|
|
||||||
_handleModChange(b) {
|
|
||||||
this.modValDidChange = b;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set focus on first element in modifications menu
|
|
||||||
* after it first mounts
|
|
||||||
*/
|
|
||||||
componentDidMount() {
|
|
||||||
let firstEleCn = this.modItems['modMainDiv'].children.length > 0 ? this.modItems['modMainDiv'].children[0].className : null;
|
|
||||||
if (firstEleCn.indexOf('select-group cap') >= 0) {
|
|
||||||
this.modItems['modMainDiv'].children[1].firstElementChild.focus();
|
|
||||||
} else {
|
|
||||||
this.modItems['modMainDiv'].firstElementChild.focus();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -370,33 +213,13 @@ export default class ModificationsMenu extends TranslatedComponent {
|
|||||||
* in a modification
|
* in a modification
|
||||||
*/
|
*/
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
if (!this.modValDidChange) {
|
if (this.selectedModRef) {
|
||||||
if (this.modItems['modMainDiv'].children.length > 0) {
|
this.selectedModRef.focus();
|
||||||
if (this.modItems[this.selectedModId]) {
|
|
||||||
this.modItems[this.selectedModId].focus();
|
|
||||||
return;
|
return;
|
||||||
} else if (this.modItems[this.selectedSpecialId]) {
|
} else if (this.selectedSpecialRef) {
|
||||||
this.modItems[this.selectedSpecialId].focus();
|
this.selectedSpecialRef.focus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let firstEleCn = this.modItems['modMainDiv'].children[0].className;
|
|
||||||
if (firstEleCn.indexOf('button-inline-menu') >= 0) {
|
|
||||||
this.modItems['modMainDiv'].firstElementChild.focus();
|
|
||||||
} else if (firstEleCn.indexOf('select-group cap') >= 0) {
|
|
||||||
this.modItems['modMainDiv'].children[1].firstElementChild.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this._handleModChange(false);// Need to reset if component update due to value change
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* set focus to the modification menu icon after mod menu is unmounted.
|
|
||||||
*/
|
|
||||||
componentWillUnmount() {
|
|
||||||
if (this.props.modButton) {
|
|
||||||
this.props.modButton.focus();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -407,90 +230,155 @@ export default class ModificationsMenu extends TranslatedComponent {
|
|||||||
const { language, tooltip, termtip } = this.context;
|
const { language, tooltip, termtip } = this.context;
|
||||||
const translate = language.translate;
|
const translate = language.translate;
|
||||||
const { m } = this.props;
|
const { m } = this.props;
|
||||||
const { blueprintMenuOpened, specialMenuOpened } = this.state;
|
const {
|
||||||
|
blueprintProgress, blueprintMenuOpened, specialMenuOpened,
|
||||||
|
} = this.state;
|
||||||
|
|
||||||
const _toggleBlueprintsMenu = this._toggleBlueprintsMenu;
|
const appliedBlueprint = m.getBlueprint();
|
||||||
const _toggleSpecialsMenu = this._toggleSpecialsMenu;
|
const appliedGrade = m.getBlueprintGrade();
|
||||||
const _rollFull = this._rollBest;
|
const appliedExperimental = m.getExperimental();
|
||||||
const _rollWorst = this._rollWorst;
|
|
||||||
const _rollFifty = this._rollFifty;
|
|
||||||
const _rollRandom = this._rollRandom;
|
|
||||||
const _reset = this._reset;
|
|
||||||
|
|
||||||
let blueprintLabel;
|
let renderComponents = [];
|
||||||
let haveBlueprint = false;
|
switch (true) {
|
||||||
let blueprintTt;
|
case !appliedBlueprint || blueprintMenuOpened:
|
||||||
let blueprintCv;
|
renderComponents = this._renderBlueprints();
|
||||||
// TODO: Fix this to actually find the correct blueprint.
|
break;
|
||||||
if (!m.blueprint || !m.blueprint.name || !m.blueprint.fdname || !Modifications.modules[m.grp].blueprints || !Modifications.modules[m.grp].blueprints[m.blueprint.fdname]) {
|
case specialMenuOpened:
|
||||||
this.props.ship.clearModuleBlueprint(m);
|
renderComponents = this._renderSpecials();
|
||||||
this.props.ship.clearModuleSpecial(m);
|
break;
|
||||||
}
|
default:
|
||||||
if (m.blueprint && m.blueprint.name && Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade]) {
|
// Since the first case didn't apply, there is a blueprint applied so
|
||||||
blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
|
// we render the modifications
|
||||||
haveBlueprint = true;
|
let blueprintTt = blueprintTooltip(language, m, appliedBlueprint, appliedGrade);
|
||||||
blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade].engineers, m.grp);
|
|
||||||
blueprintCv = getPercent(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
let specialLabel;
|
renderComponents.push(
|
||||||
let specialTt;
|
<div style={{ cursor: 'pointer' }} key="blueprintsMenu"
|
||||||
if (m.blueprint && m.blueprint.special) {
|
className="section-menu button-inline-menu"
|
||||||
specialLabel = m.blueprint.special.name;
|
onMouseOver={termtip.bind(null, blueprintTt)}
|
||||||
specialTt = specialToolTip(translate, m.blueprint.grades[m.blueprint.grade], m.grp, m, m.blueprint.special.edname);
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
} else {
|
onClick={this._toggleBlueprintsMenu}
|
||||||
specialLabel = translate('PHRASE_SELECT_SPECIAL');
|
|
||||||
}
|
|
||||||
|
|
||||||
const specials = this._renderSpecials(this.props, this.context);
|
|
||||||
/**
|
|
||||||
* pnellesen - 05/28/2018 - added additional checks for specials.length below to ensure menus
|
|
||||||
* display correctly in cases where there are no specials (ex: AFMUs.)
|
|
||||||
*/
|
|
||||||
const showBlueprintsMenu = blueprintMenuOpened;
|
|
||||||
const showSpecial = haveBlueprint && specials.length && !blueprintMenuOpened;
|
|
||||||
const showSpecialsMenu = specialMenuOpened && specials.length;
|
|
||||||
const showRolls = haveBlueprint && !blueprintMenuOpened && (!specialMenuOpened || !specials.length);
|
|
||||||
const showReset = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint;
|
|
||||||
const showMods = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint;
|
|
||||||
if (haveBlueprint) {
|
|
||||||
this.firstBPLabel = blueprintLabel;
|
|
||||||
} else {
|
|
||||||
this.firstBPLabel = 'selectBP';
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cn('select', this.props.className)}
|
|
||||||
onClick={(e) => e.stopPropagation() }
|
|
||||||
onContextMenu={stopCtxPropagation}
|
|
||||||
ref={modItem => this.modItems['modMainDiv'] = modItem}
|
|
||||||
>
|
>
|
||||||
{ showBlueprintsMenu | showSpecialsMenu ? '' : haveBlueprint ?
|
{translate(appliedBlueprint)} {translate('grade')} {appliedGrade}
|
||||||
<div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={termtip.bind(null, blueprintTt)} onMouseOut={tooltip.bind(null, null)} onClick={_toggleBlueprintsMenu} onKeyDown={ this._keyDown } ref={modItems => this.modItems[this.firstBPLabel] = modItems}>{blueprintLabel}</div> :
|
</div>
|
||||||
<div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleBlueprintsMenu} onKeyDown={ this._keyDown } ref={modItems => this.modItems[this.firstBPLabel] = modItems}>{translate('PHRASE_SELECT_BLUEPRINT')}</div> }
|
);
|
||||||
{ showBlueprintsMenu ? this._renderBlueprints(this.props, this.context) : null }
|
|
||||||
{ showSpecial & !showSpecialsMenu ? <div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: specialMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={specialTt ? termtip.bind(null, specialTt) : null} onMouseOut={specialTt ? tooltip.bind(null, null) : null} onClick={_toggleSpecialsMenu} onKeyDown={ this._keyDown }>{specialLabel}</div> : null }
|
|
||||||
{ showSpecialsMenu ? specials : null }
|
|
||||||
{ showReset ? <div tabIndex="0" className={'section-menu button-inline-menu warning'} style={{ cursor: 'pointer' }} onClick={_reset} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')} onMouseOut={tooltip.bind(null, null)}> { translate('reset') } </div> : null }
|
|
||||||
{ showRolls ?
|
|
||||||
|
|
||||||
|
if (m.getApplicableExperimentals().length) {
|
||||||
|
let specialLabel = translate('PHRASE_SELECT_SPECIAL');
|
||||||
|
let specialTt;
|
||||||
|
if (appliedExperimental) {
|
||||||
|
specialLabel = appliedExperimental;
|
||||||
|
specialTt = specialToolTip(language, m, appliedExperimental);
|
||||||
|
}
|
||||||
|
renderComponents.push(
|
||||||
|
<div className="section-menu button-inline-menu"
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onMouseOver={specialTt ? termtip.bind(null, specialTt) : null}
|
||||||
|
onMouseOut={specialTt ? tooltip.bind(null, null) : null}
|
||||||
|
onClick={this._toggleSpecialsMenu}
|
||||||
|
>{specialLabel}</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderComponents.push(
|
||||||
|
<div
|
||||||
|
className="section-menu button-inline-menu warning"
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
m.resetEngineering();
|
||||||
|
this.selectedModRef = null;
|
||||||
|
this.selectedSpecialRef = null;
|
||||||
|
tooltip(null);
|
||||||
|
this.setState({
|
||||||
|
blueprintMenuOpened: true,
|
||||||
|
blueprintProgress: undefined,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>{translate('reset')}</div>,
|
||||||
<table style={{ width: '100%', backgroundColor: 'transparent' }}>
|
<table style={{ width: '100%', backgroundColor: 'transparent' }}>
|
||||||
<tbody>
|
<tbody>
|
||||||
{ showRolls ?
|
|
||||||
<tr>
|
<tr>
|
||||||
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: false }) }> { translate('roll') }: </td>
|
<td
|
||||||
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 0 }) } style={{ cursor: 'pointer' }} onClick={_rollWorst} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')} onMouseOut={tooltip.bind(null, null)}> { translate('0%') } </td>
|
className={cn(
|
||||||
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 50 })} style={{ cursor: 'pointer' }} onClick={_rollFifty} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_FIFTY')} onMouseOut={tooltip.bind(null, null)}> { translate('50%') } </td>
|
'section-menu button-inline-menu',
|
||||||
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 100 })} style={{ cursor: 'pointer' }} onClick={_rollFull} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')} onMouseOut={tooltip.bind(null, null)}> { translate('100%') } </td>
|
{ active: false },
|
||||||
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === null || blueprintCv % 50 != 0 })} style={{ cursor: 'pointer' }} onClick={_rollRandom} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')} onMouseOut={tooltip.bind(null, null)}> { translate('random') } </td>
|
)}
|
||||||
</tr> : null }
|
>{translate('mroll')}:</td>
|
||||||
|
<td
|
||||||
|
className={cn(
|
||||||
|
'section-menu button-inline-menu',
|
||||||
|
{ active: blueprintProgress === 0 },
|
||||||
|
)} style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
m.setBlueprintProgress(0);
|
||||||
|
this.setState({ blueprintProgress: 0 });
|
||||||
|
}}
|
||||||
|
onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>{translate('0%')}</td>
|
||||||
|
<td
|
||||||
|
className={cn(
|
||||||
|
'section-menu button-inline-menu',
|
||||||
|
{ active: blueprintProgress === 0.5 },
|
||||||
|
)} style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
m.setBlueprintProgress(0.5);
|
||||||
|
this.setState({ blueprintProgress: 0.5 });
|
||||||
|
}}
|
||||||
|
onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_FIFTY')}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>{translate('50%')}</td>
|
||||||
|
<td
|
||||||
|
className={cn(
|
||||||
|
'section-menu button-inline-menu',
|
||||||
|
{ active: blueprintProgress === 1 },
|
||||||
|
)}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
m.setBlueprintProgress(1);
|
||||||
|
this.setState({ blueprintProgress: 1 });
|
||||||
|
}}
|
||||||
|
onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>{translate('100%')}</td>
|
||||||
|
<td
|
||||||
|
className={cn(
|
||||||
|
'section-menu button-inline-menu',
|
||||||
|
{ active: blueprintProgress % 0.5 !== 0 },
|
||||||
|
)}
|
||||||
|
style={{ cursor: 'pointer' }}
|
||||||
|
onClick={() => {
|
||||||
|
const blueprintProgress = Math.random();
|
||||||
|
m.setBlueprintProgress(blueprintProgress);
|
||||||
|
this.setState({ blueprintProgress });
|
||||||
|
}}
|
||||||
|
onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>{translate('random')}</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table> : null }
|
</table>,
|
||||||
{ showMods ? <hr /> : null }
|
<hr />,
|
||||||
{ showMods ?
|
<span
|
||||||
<span onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')} onMouseOut={tooltip.bind(null, null)} >
|
onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')}
|
||||||
{ this._renderModifications(this.props) }
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
</span> : null }
|
>
|
||||||
|
<table style={{ width: '100%' }}>
|
||||||
|
<tbody>
|
||||||
|
{this._renderModifications()}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cn('select', this.props.className)}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
onContextMenu={stopCtxPropagation}
|
||||||
|
>
|
||||||
|
{renderComponents}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,28 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
|
import { ShipProps } from 'ed-forge';
|
||||||
|
const { SPEED, BOOST_SPEED, ROLL, BOOST_ROLL, YAW, BOOST_YAW, PITCH, BOOST_PITCH } = ShipProps;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Movement
|
* Movement
|
||||||
*/
|
*/
|
||||||
export default class Movement extends TranslatedComponent {
|
export default class Movement extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
marker: PropTypes.string.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
boost: PropTypes.bool.isRequired,
|
boost: PropTypes.bool.isRequired,
|
||||||
eng: PropTypes.number.isRequired,
|
pips: PropTypes.object.isRequired,
|
||||||
fuel: PropTypes.number.isRequired,
|
|
||||||
cargo: PropTypes.number.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
* @param {Object} props React Component properties
|
|
||||||
*/
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render movement
|
* Render movement
|
||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { ship, boost, eng, cargo, fuel } = this.props;
|
const { ship, boost } = this.props;
|
||||||
const { language } = this.context;
|
const { language } = this.context;
|
||||||
const { formats, translate, units } = language;
|
const { formats } = language;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span id='movement'>
|
<span id='movement'>
|
||||||
@@ -57,14 +49,10 @@ export default class Movement extends TranslatedComponent {
|
|||||||
<path d="M359.5 422.4l-1.2 19.3-1.6.4-10.7-16 .2-.2 13-3.4.3.4zm-9 5l5.2 7.8.6-9.3-5.7 1.2zm-10.5 24l-13.2 8.6-2.6-9.7 15.8 1z"/>
|
<path d="M359.5 422.4l-1.2 19.3-1.6.4-10.7-16 .2-.2 13-3.4.3.4zm-9 5l5.2 7.8.6-9.3-5.7 1.2zm-10.5 24l-13.2 8.6-2.6-9.7 15.8 1z"/>
|
||||||
<path d="M342 450l.4 1.5-16.2 10.7-.4-.2-3.5-13 .3-.3L342 450zm-14.3 7.6l7.7-5-9.2-.6 1.5 5.6z"/>
|
<path d="M342 450l.4 1.5-16.2 10.7-.4-.2-3.5-13 .3-.3L342 450zm-14.3 7.6l7.7-5-9.2-.6 1.5 5.6z"/>
|
||||||
|
|
||||||
{/* Speed */}
|
<text x="470" y="30" strokeWidth='0'>{formats.int(ship.get(boost ? BOOST_SPEED : SPEED)) + 'm/s'}</text>
|
||||||
<text x="470" y="30" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcSpeed(eng, fuel, cargo, boost)) + 'm/s' : '-'}</text>
|
<text x="355" y="410" strokeWidth='0'>{formats.int(ship.get(boost ? BOOST_PITCH : PITCH)) + '°/s'}</text>
|
||||||
{/* Pitch */}
|
<text x="450" y="110" strokeWidth='0'>{formats.int(ship.get(boost ? BOOST_ROLL : ROLL)) + '°/s'}</text>
|
||||||
<text x="355" y="410" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcPitch(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
|
<text x="160" y="430" strokeWidth='0'>{formats.int(ship.get(boost ? BOOST_YAW : YAW)) + '°/s'}</text>
|
||||||
{/* Roll */}
|
|
||||||
<text x="450" y="110" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcRoll(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
|
|
||||||
{/* Yaw */}
|
|
||||||
<text x="160" y="430" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcYaw(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
|
|
||||||
</svg>
|
</svg>
|
||||||
</span>);
|
</span>);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,101 +3,36 @@ import PropTypes from 'prop-types';
|
|||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import * as Calc from '../shipyard/Calculations';
|
import * as Calc from '../shipyard/Calculations';
|
||||||
import PieChart from './PieChart';
|
import PieChart from './PieChart';
|
||||||
import { nameComparator } from '../utils/SlotFunctions';
|
|
||||||
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
|
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
|
||||||
|
import { Ship } from 'ed-forge';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
import { DAMAGE_METRICS } from 'ed-forge/lib/ship-stats';
|
||||||
|
import { clone, mapValues, mergeWith, reverse, sortBy, sum, toPairs, values } from 'lodash';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates an internationalization friendly weapon comparator that will
|
* Turns an object into a tooltip.
|
||||||
* sort by specified property (if provided) then by name/group, class, rating
|
* @param {function} translate Translate function
|
||||||
* @param {function} translate Translation function
|
* @param {object} o Map to make the tooltip from
|
||||||
* @param {function} propComparator Optional property comparator
|
* @returns {React.Component} Tooltip
|
||||||
* @param {boolean} desc Use descending order
|
|
||||||
* @return {function} Comparator function for names
|
|
||||||
*/
|
*/
|
||||||
export function weaponComparator(translate, propComparator, desc) {
|
function objToTooltip(translate, o) {
|
||||||
return (a, b) => {
|
return toPairs(o)
|
||||||
if (!desc) { // Flip A and B if ascending order
|
.filter(([k, v]) => Boolean(v))
|
||||||
let t = a;
|
.map(([k, v]) => <div key={k}>{`${translate(k)}: ${v}`}</div>);
|
||||||
a = b;
|
|
||||||
b = t;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a property comparator is provided use it first
|
|
||||||
let diff = propComparator ? propComparator(a, b) : nameComparator(translate, a, b);
|
|
||||||
|
|
||||||
if (diff) {
|
|
||||||
return diff;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Property matches so sort by name / group, then class, rating
|
|
||||||
if (a.name === b.name && a.grp === b.grp) {
|
|
||||||
if(a.class == b.class) {
|
|
||||||
return a.rating > b.rating ? 1 : -1;
|
|
||||||
}
|
|
||||||
return a.class - b.class;
|
|
||||||
}
|
|
||||||
|
|
||||||
return nameComparator(translate, a, b);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a tooltip that shows damage by type.
|
|
||||||
* @param {function} translate Translation function
|
|
||||||
* @param {Object} formats Object that holds format functions
|
|
||||||
* @param {Calc.SDps} sdpsObject Object that holds sdps split up by type
|
|
||||||
* @returns {Array} Tooltip
|
|
||||||
*/
|
|
||||||
function getSDpsTooltip(translate, formats, sdpsObject) {
|
|
||||||
return Object.keys(sdpsObject).filter(key => sdpsObject[key])
|
|
||||||
.map(key => {
|
|
||||||
return (
|
|
||||||
<div key={key}>
|
|
||||||
{translate(key) + ' ' + formats.f1(sdpsObject[key])}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a data object used by {@link PieChart} that shows damage by type.
|
* Returns a data object used by {@link PieChart} that shows damage by type.
|
||||||
* @param {function} translate Translation function
|
* @param {function} translate Translation function
|
||||||
* @param {Calc.SDps} sdpsObject Object that holds sdps split up by type
|
* @param {Calc.SDps} o Object that holds sdps split up by type
|
||||||
* @returns {Object} Data object
|
* @returns {Object} Data object
|
||||||
*/
|
*/
|
||||||
function getSDpsData(translate, sdpsObject) {
|
function objToPie(translate, o) {
|
||||||
return Object.keys(sdpsObject).map(key => {
|
return toPairs(o).map(([k, value]) => {
|
||||||
return {
|
return { label: translate(k), value };
|
||||||
value: Math.round(sdpsObject[key]),
|
|
||||||
label: translate(key)
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds all damage of `add` onto `addOn`.
|
|
||||||
* @param {Calc.SDps} addOn Object that holds sdps split up by type (will be mutated)
|
|
||||||
* @param {Calc.SDps} add Object that holds sdps split up by type
|
|
||||||
*/
|
|
||||||
function addSDps(addOn, add) {
|
|
||||||
Object.keys(addOn).map(k => addOn[k] += (add[k] ? add[k] : 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the overall sdps of an sdps object.
|
|
||||||
* @param {Calc.SDps} sdpsObject Object that holds sdps spluit up by type
|
|
||||||
*/
|
|
||||||
function sumSDps(sdpsObject) {
|
|
||||||
if (sdpsObject.total) {
|
|
||||||
return sdpsObject.total;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.keys(sdpsObject).reduce(
|
|
||||||
(acc, k) => acc + (sdpsObject[k] ? sdpsObject[k] : 0),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Offence information
|
* Offence information
|
||||||
* Offence information consists of four panels:
|
* Offence information consists of four panels:
|
||||||
@@ -108,12 +43,10 @@ function sumSDps(sdpsObject) {
|
|||||||
*/
|
*/
|
||||||
export default class Offence extends TranslatedComponent {
|
export default class Offence extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
marker: PropTypes.string.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.instanceOf(Ship).isRequired,
|
||||||
opponent: PropTypes.object.isRequired,
|
opponent: PropTypes.instanceOf(Ship).isRequired,
|
||||||
engagementrange: PropTypes.number.isRequired,
|
engagementRange: PropTypes.number.isRequired,
|
||||||
wep: PropTypes.number.isRequired,
|
|
||||||
opponentSys: PropTypes.number.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -122,146 +55,234 @@ export default class Offence extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
|
|
||||||
this._sort = this._sort.bind(this);
|
|
||||||
|
|
||||||
const damage = Calc.offenceMetrics(props.ship, props.opponent, props.wep, props.opponentSys, props.engagementrange);
|
|
||||||
this.state = {
|
this.state = {
|
||||||
predicate: 'n',
|
predicate: 'classRating',
|
||||||
desc: true,
|
desc: true,
|
||||||
damage
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the state if our properties change
|
|
||||||
* @param {Object} nextProps Incoming/Next properties
|
|
||||||
* @return {boolean} Returns true if the component should be rerendered
|
|
||||||
*/
|
|
||||||
componentWillReceiveProps(nextProps) {
|
|
||||||
if (this.props.marker != nextProps.marker || this.props.eng != nextProps.eng) {
|
|
||||||
const damage = Calc.offenceMetrics(nextProps.ship, nextProps.opponent, nextProps.wep, nextProps.opponentSys, nextProps.engagementrange);
|
|
||||||
this.setState({ damage });
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the sort order and sort
|
* Set the sort order and sort
|
||||||
* @param {string} predicate Sort predicate
|
* @param {string} predicate Sort predicate
|
||||||
*/
|
*/
|
||||||
_sortOrder(predicate) {
|
_sortOrder(predicate) {
|
||||||
let desc = this.state.desc;
|
let desc = predicate == this.state.predicate ? !this.state.desc : true;
|
||||||
|
|
||||||
if (predicate == this.state.predicate) {
|
|
||||||
desc = !desc;
|
|
||||||
} else {
|
|
||||||
desc = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._sort(predicate, desc);
|
|
||||||
this.setState({ predicate, desc });
|
this.setState({ predicate, desc });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sorts the weapon list
|
|
||||||
* @param {string} predicate Sort predicate
|
|
||||||
* @param {Boolean} desc Sort order descending
|
|
||||||
*/
|
|
||||||
_sort(predicate, desc) {
|
|
||||||
let comp = weaponComparator.bind(null, this.context.language.translate);
|
|
||||||
|
|
||||||
switch (predicate) {
|
|
||||||
case 'n': comp = comp(null, desc); break;
|
|
||||||
case 'esdpss': comp = comp((a, b) => a.sdps.shields.total - b.sdps.shields.total, desc); break;
|
|
||||||
case 'es': comp = comp((a, b) => a.effectiveness.shields.total - b.effectiveness.shields.total, desc); break;
|
|
||||||
case 'esdpsh': comp = comp((a, b) => a.sdps.armour.total - b.sdps.armour.total, desc); break;
|
|
||||||
case 'eh': comp = comp((a, b) => a.effectiveness.armour.total - b.effectiveness.armour.total, desc); break;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.state.damage.sort(comp);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render offence
|
* Render offence
|
||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { ship, opponent, wep, engagementrange } = this.props;
|
const { ship } = this.props;
|
||||||
const { language, tooltip, termtip } = this.context;
|
const { language, tooltip, termtip } = this.context;
|
||||||
const { formats, translate, units } = language;
|
const { formats, translate, units } = language;
|
||||||
const { damage } = this.state;
|
|
||||||
const sortOrder = this._sortOrder;
|
const sortOrder = this._sortOrder;
|
||||||
|
|
||||||
const pd = ship.standard[4].m;
|
const {
|
||||||
|
drained, sustained, rangeMultiplier, hardnessMultiplier, timeToDrain
|
||||||
|
} = ship.getMetrics(DAMAGE_METRICS);
|
||||||
|
const portions = {
|
||||||
|
Absolute: sustained.types.abs,
|
||||||
|
Explosive: sustained.types.expl,
|
||||||
|
Kinetic: sustained.types.kin,
|
||||||
|
Thermic: sustained.types.therm,
|
||||||
|
};
|
||||||
|
|
||||||
const opponentShields = Calc.shieldMetrics(opponent, 4);
|
const oppShield = ship.getOpponent().getShield();
|
||||||
const opponentArmour = Calc.armourMetrics(opponent);
|
const shieldMults = {
|
||||||
|
Absolute: 1,
|
||||||
|
Explosive: oppShield.explosive.damageMultiplier,
|
||||||
|
Kinetic: oppShield.kinetic.damageMultiplier,
|
||||||
|
Thermic: oppShield.thermal.damageMultiplier,
|
||||||
|
};
|
||||||
|
|
||||||
const timeToDrain = Calc.timeToDrainWep(ship, wep);
|
const oppArmour = ship.getOpponent().getArmour();
|
||||||
|
const armourMults = {
|
||||||
|
Absolute: 1,
|
||||||
|
Explosive: oppArmour.explosive.damageMultiplier,
|
||||||
|
Kinetic: oppArmour.kinetic.damageMultiplier,
|
||||||
|
Thermic: oppArmour.thermal.damageMultiplier,
|
||||||
|
};
|
||||||
|
|
||||||
|
const weapons = sortBy(ship.getHardpoints(), (m) => m.get('distributordraw'));
|
||||||
|
let rows = weapons.map((weapon) => {
|
||||||
|
const sdps = weapon.get('sustaineddamagepersecond');
|
||||||
|
const byRange = weapon.getRangeEffectiveness();
|
||||||
|
const weaponPortions = {
|
||||||
|
Absolute: weapon.get('absolutedamageportion'),
|
||||||
|
Explosive: weapon.get('explosivedamageportion'),
|
||||||
|
Kinetic: weapon.get('kineticdamageportion'),
|
||||||
|
Thermic: weapon.get('thermicdamageportion'),
|
||||||
|
};
|
||||||
|
const baseSdpsTooltip = objToTooltip(
|
||||||
|
translate,
|
||||||
|
mapValues(weaponPortions, (p) => formats.f1(sdps * p)),
|
||||||
|
);
|
||||||
|
|
||||||
let totalSEps = 0;
|
const bySys = oppShield.absolute.bySys;
|
||||||
let totalSDpsObject = { 'absolute': 0, 'explosive': 0, 'kinetic': 0, 'thermal': 0 };
|
const shieldResEfts = mergeWith(
|
||||||
let shieldsSDpsObject = { 'absolute': 0, 'explosive': 0, 'kinetic': 0, 'thermal': 0 };
|
clone(weaponPortions),
|
||||||
let armourSDpsObject = { 'absolute': 0, 'explosive': 0, 'kinetic': 0, 'thermal': 0 };
|
shieldMults,
|
||||||
|
(objV, srcV) => objV * srcV
|
||||||
|
);
|
||||||
|
const byShieldRes = sum(values(shieldResEfts));
|
||||||
|
const shieldsSdpsTooltip = objToTooltip(
|
||||||
|
translate,
|
||||||
|
mapValues(
|
||||||
|
shieldResEfts,
|
||||||
|
(mult) => formats.f1(byRange * mult * bySys * sdps),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const shieldsEftTooltip = objToTooltip(
|
||||||
|
translate,
|
||||||
|
{
|
||||||
|
range: formats.pct1(byRange),
|
||||||
|
resistance: formats.pct1(byShieldRes),
|
||||||
|
'power distributor': formats.pct1(bySys),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const shieldEft = byRange * byShieldRes * bySys;
|
||||||
|
|
||||||
const rows = [];
|
const byHardness = weapon.getArmourEffectiveness();
|
||||||
for (let i = 0; i < damage.length; i++) {
|
const armourResEfts = mergeWith(
|
||||||
const weapon = damage[i];
|
clone(weaponPortions),
|
||||||
|
armourMults,
|
||||||
|
(objV, srcV) => objV * srcV,
|
||||||
|
);
|
||||||
|
const byArmourRes = sum(values(armourResEfts));
|
||||||
|
const armourSdpsTooltip = objToTooltip(
|
||||||
|
translate,
|
||||||
|
mapValues(
|
||||||
|
armourResEfts,
|
||||||
|
(mult) => formats.f1(byRange * mult * byHardness * sdps)
|
||||||
|
),
|
||||||
|
);
|
||||||
|
const armourEftTooltip = objToTooltip(
|
||||||
|
translate,
|
||||||
|
{
|
||||||
|
range: formats.pct1(byRange),
|
||||||
|
resistance: formats.pct1(byArmourRes),
|
||||||
|
hardness: formats.pct1(byHardness),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const armourEft = byRange * byArmourRes * byHardness;
|
||||||
|
|
||||||
totalSEps += weapon.seps;
|
const bp = weapon.getBlueprint();
|
||||||
addSDps(totalSDpsObject, weapon.sdps.base);
|
const grade = weapon.getBlueprintGrade();
|
||||||
addSDps(shieldsSDpsObject, weapon.sdps.shields);
|
const exp = weapon.getExperimental();
|
||||||
addSDps(armourSDpsObject, weapon.sdps.armour);
|
let bpTitle = `${translate(bp)} ${translate('grade')} ${grade}`;
|
||||||
|
if (exp) {
|
||||||
|
bpTitle += `, ${translate(exp)}`;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
slot: weapon.getSlot(),
|
||||||
|
mount: weapon.mount,
|
||||||
|
classRating: weapon.getClassRating(),
|
||||||
|
type: weapon.readMeta('type'),
|
||||||
|
bpTitle: bp ? ` (${bpTitle})` : null,
|
||||||
|
sdps,
|
||||||
|
baseSdpsTooltip,
|
||||||
|
shieldSdps: sdps * shieldEft,
|
||||||
|
shieldEft,
|
||||||
|
shieldsSdpsTooltip,
|
||||||
|
shieldsEftTooltip,
|
||||||
|
armourSdps: sdps * armourEft,
|
||||||
|
armourEft,
|
||||||
|
armourSdpsTooltip,
|
||||||
|
armourEftTooltip,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const baseSDpsTooltipDetails = getSDpsTooltip(translate, formats, weapon.sdps.base);
|
const { predicate, desc } = this.state;
|
||||||
|
rows = sortBy(rows, (row) => row[predicate]);
|
||||||
const effectivenessShieldsTooltipDetails = [];
|
if (desc) {
|
||||||
effectivenessShieldsTooltipDetails.push(<div key='range'>{translate('range') + ' ' + formats.pct1(weapon.effectiveness.shields.range)}</div>);
|
reverse(rows);
|
||||||
effectivenessShieldsTooltipDetails.push(<div key='resistance'>{translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.shields.resistance)}</div>);
|
|
||||||
effectivenessShieldsTooltipDetails.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(weapon.effectiveness.shields.sys)}</div>);
|
|
||||||
|
|
||||||
const effectiveShieldsSDpsTooltipDetails = getSDpsTooltip(translate, formats, weapon.sdps.armour);
|
|
||||||
|
|
||||||
const effectivenessArmourTooltipDetails = [];
|
|
||||||
effectivenessArmourTooltipDetails.push(<div key='range'>{translate('range') + ' ' + formats.pct1(weapon.effectiveness.armour.range)}</div>);
|
|
||||||
effectivenessArmourTooltipDetails.push(<div key='resistance'>{translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.armour.resistance)}</div>);
|
|
||||||
effectivenessArmourTooltipDetails.push(<div key='hardness'>{translate('hardness') + ' ' + formats.pct1(weapon.effectiveness.armour.hardness)}</div>);
|
|
||||||
|
|
||||||
const effectiveArmourSDpsTooltipDetails = getSDpsTooltip(translate, formats, weapon.sdps.armour);
|
|
||||||
|
|
||||||
rows.push(
|
|
||||||
<tr key={weapon.id}>
|
|
||||||
<td className='ri'>
|
|
||||||
{weapon.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed className='icon'/></span> : null}
|
|
||||||
{weapon.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : null}
|
|
||||||
{weapon.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : null}
|
|
||||||
{weapon.classRating} {translate(weapon.name)}
|
|
||||||
{weapon.engineering ? ' (' + weapon.engineering + ')' : null }
|
|
||||||
</td>
|
|
||||||
<td className='ri'><span onMouseOver={termtip.bind(null, baseSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.base.total)}</span></td>
|
|
||||||
<td className='ri'><span onMouseOver={termtip.bind(null, effectiveShieldsSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.shields.total)}</span></td>
|
|
||||||
<td className='ri'><span onMouseOver={termtip.bind(null, effectivenessShieldsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(weapon.effectiveness.shields.total)}</span></td>
|
|
||||||
<td className='ri'><span onMouseOver={termtip.bind(null, effectiveArmourSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.armour.total)}</span></td>
|
|
||||||
<td className='ri'><span onMouseOver={termtip.bind(null, effectivenessArmourTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(weapon.effectiveness.armour.total)}</span></td>
|
|
||||||
</tr>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const totalSDps = sumSDps(totalSDpsObject);
|
const sdpsTooltip = objToTooltip(
|
||||||
const totalSDpsTooltipDetails = getSDpsTooltip(translate, formats, totalSDpsObject);
|
translate,
|
||||||
const totalSDpsData = getSDpsData(translate, totalSDpsObject);
|
mapValues(portions, (p) => formats.f1(sustained.dps * p)),
|
||||||
|
);
|
||||||
|
const sdpsPie = objToPie(
|
||||||
|
translate,
|
||||||
|
mapValues(portions, (p) => Math.round(sustained.dps * p)),
|
||||||
|
);
|
||||||
|
|
||||||
const totalShieldsSDps = sumSDps(shieldsSDpsObject);
|
const shieldSdpsSrcs = mergeWith(
|
||||||
const totalShieldsSDpsTooltipDetails = getSDpsTooltip(translate, formats, shieldsSDpsObject);
|
clone(portions),
|
||||||
const shieldsSDpsData = getSDpsData(translate, shieldsSDpsObject);
|
shieldMults,
|
||||||
|
(objV, srcV) => sustained.dps * oppShield.absolute.bySys *
|
||||||
|
rangeMultiplier * objV * srcV,
|
||||||
|
);
|
||||||
|
const shieldsSdps = sum(values(shieldSdpsSrcs));
|
||||||
|
const shieldsSdpsTooltip = objToTooltip(
|
||||||
|
translate,
|
||||||
|
mapValues(shieldSdpsSrcs, (v) => formats.f1(v)),
|
||||||
|
);
|
||||||
|
const shieldsSdpsPie = objToPie(
|
||||||
|
translate,
|
||||||
|
mapValues(shieldSdpsSrcs, (v) => Math.round(v)),
|
||||||
|
);
|
||||||
|
|
||||||
const totalArmourSDps = sumSDps(armourSDpsObject);
|
const armourSdpsSrcs = mergeWith(
|
||||||
const totalArmourSDpsTooltipDetails = getSDpsTooltip(translate, formats, armourSDpsObject);
|
clone(portions),
|
||||||
const armourSDpsData = getSDpsData(translate, armourSDpsObject);
|
armourMults,
|
||||||
|
(objV, srcV) => sustained.dps * hardnessMultiplier * rangeMultiplier *
|
||||||
|
objV * srcV,
|
||||||
|
);
|
||||||
|
const armourSdps = sum(values(armourSdpsSrcs));
|
||||||
|
const totalArmourSDpsTooltipDetails = objToTooltip(
|
||||||
|
translate,
|
||||||
|
mapValues(armourSdpsSrcs, (v) => formats.f1(v)),
|
||||||
|
);
|
||||||
|
const armourSDpsData = objToPie(
|
||||||
|
translate,
|
||||||
|
mapValues(armourSdpsSrcs, (v) => Math.round(v)),
|
||||||
|
);
|
||||||
|
|
||||||
const timeToDepleteShields = Calc.timeToDeplete(opponentShields.total, totalShieldsSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4));
|
const drainedPortions = {
|
||||||
const timeToDepleteArmour = Calc.timeToDeplete(opponentArmour.total, totalArmourSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4));
|
Absolute: drained.types.abs,
|
||||||
|
Explosive: drained.types.expl,
|
||||||
|
Kinetic: drained.types.kin,
|
||||||
|
Thermic: drained.types.therm,
|
||||||
|
};
|
||||||
|
|
||||||
|
// How much damage do we deal, before the capacitor is empty?
|
||||||
|
const armourLeft = oppArmour.armour - (timeToDrain * armourSdps);
|
||||||
|
// If we can't kill the enemy on one capacitor, factor in drained damage
|
||||||
|
let timeToDepleteArmour;
|
||||||
|
if (armourLeft > 0) {
|
||||||
|
const effectiveDrainedDps = sum(values(mergeWith(
|
||||||
|
clone(drainedPortions),
|
||||||
|
armourMults,
|
||||||
|
(objV, srcV) => objV * srcV,
|
||||||
|
))) * drained.dps * rangeMultiplier *
|
||||||
|
hardnessMultiplier;
|
||||||
|
timeToDepleteArmour = effectiveDrainedDps === 0 ? Infinity :
|
||||||
|
timeToDrain + (armourLeft / effectiveDrainedDps);
|
||||||
|
} else {
|
||||||
|
timeToDepleteArmour = oppArmour.armour / armourSdps;
|
||||||
|
}
|
||||||
|
|
||||||
|
// How much damage do we deal, before the capacitor is empty?
|
||||||
|
const shieldsLeft = oppShield.withSCBs - (timeToDrain * shieldsSdps);
|
||||||
|
// If we can't kill the enemy on one capacitor, factor in drained damage
|
||||||
|
let timeToDepleteShields;
|
||||||
|
if (shieldsLeft > 0) {
|
||||||
|
const effectiveDrainedDps = sum(values(mergeWith(
|
||||||
|
clone(drainedPortions),
|
||||||
|
shieldMults,
|
||||||
|
(objV, srcV) => objV * srcV,
|
||||||
|
))) * drained.dps * rangeMultiplier;
|
||||||
|
timeToDepleteShields = effectiveDrainedDps === 0 ? Infinity :
|
||||||
|
timeToDrain + (shieldsLeft / effectiveDrainedDps);
|
||||||
|
} else {
|
||||||
|
timeToDepleteShields = oppShield.withSCBs / shieldsSdps;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span id='offence'>
|
<span id='offence'>
|
||||||
@@ -269,28 +290,75 @@ export default class Offence extends TranslatedComponent {
|
|||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className='main'>
|
<tr className='main'>
|
||||||
<th rowSpan='2' className='sortable' onClick={sortOrder.bind(this, 'n')}>{translate('weapon')}</th>
|
<th rowSpan='2' className='sortable' onClick={sortOrder.bind(this, 'classRating')}>{translate('weapon')}</th>
|
||||||
<th colSpan='1'>{translate('overall')}</th>
|
<th colSpan='1'>{translate('overall')}</th>
|
||||||
<th colSpan='2'>{translate('opponent\'s shields')}</th>
|
<th colSpan='2'>{translate('opponent\'s shields')}</th>
|
||||||
<th colSpan='2'>{translate('opponent\'s armour')}</th>
|
<th colSpan='2'>{translate('opponent\'s armour')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')} onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'esdpss')}>{'sdps'}</th>
|
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')}
|
||||||
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')} onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'esdpss')}>{'sdps'}</th>
|
onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'sdps')}>sdps</th>
|
||||||
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_SHIELDS')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'es')}>{'eft'}</th>
|
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')}
|
||||||
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'esdpsh')}>{'sdps'}</th>
|
onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'shieldSdps')}>sdps</th>
|
||||||
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'eh')}>{'eft'}</th>
|
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_SHIELDS')}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'shieldEft')}>eft</th>
|
||||||
|
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_ARMOUR')}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'armourSdps')}>sdps</th>
|
||||||
|
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_ARMOUR')}
|
||||||
|
onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'armourEft')}>eft</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{rows}
|
{rows.map((row) => (
|
||||||
|
<tr key={row.slot}>
|
||||||
|
<td className='ri'>
|
||||||
|
{row.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed className='icon'/></span> : null}
|
||||||
|
{row.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : null}
|
||||||
|
{row.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : null}
|
||||||
|
{row.classRating} {translate(row.type)}
|
||||||
|
{row.bpTitle}
|
||||||
|
</td>
|
||||||
|
<td className='ri'>
|
||||||
|
<span onMouseOver={termtip.bind(null, row.baseSdpsTooltip)}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>{formats.f1(row.sdps)}</span></td>
|
||||||
|
<td className='ri'>
|
||||||
|
<span onMouseOver={termtip.bind(null, row.shieldsSdpsTooltip)}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>{formats.f1(row.shieldSdps)}</span></td>
|
||||||
|
<td className='ri'>
|
||||||
|
<span onMouseOver={termtip.bind(null, row.shieldsEftTooltip)}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>{formats.pct1(row.shieldEft)}</span></td>
|
||||||
|
<td className='ri'>
|
||||||
|
<span onMouseOver={termtip.bind(null, row.armourSdpsTooltip)}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>{formats.f1(row.armourSdps)}</span></td>
|
||||||
|
<td className='ri'>
|
||||||
|
<span onMouseOver={termtip.bind(null, row.armourEftTooltip)}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>{formats.pct1(row.armourEft)}</span></td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
{rows.length > 0 &&
|
{rows.length > 0 &&
|
||||||
<tr>
|
<tr>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td className='ri'><span onMouseOver={termtip.bind(null, totalSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>={formats.f1(totalSDps)}</span></td>
|
<td className='ri'>
|
||||||
<td className='ri'><span onMouseOver={termtip.bind(null, totalShieldsSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>={formats.f1(totalShieldsSDps)}</span></td>
|
<span onMouseOver={termtip.bind(null, sdpsTooltip)} onMouseOut={tooltip.bind(null, null)}>
|
||||||
|
={formats.f1(sustained.dps)}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td className='ri'>
|
||||||
|
<span onMouseOver={termtip.bind(null, shieldsSdpsTooltip)} onMouseOut={tooltip.bind(null, null)}>
|
||||||
|
={formats.f1(shieldsSdps)}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td className='ri'><span onMouseOver={termtip.bind(null, totalArmourSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>={formats.f1(totalArmourSDps)}</span></td>
|
<td className='ri'>
|
||||||
|
<span onMouseOver={termtip.bind(null, totalArmourSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>
|
||||||
|
={formats.f1(armourSdps)}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
@@ -299,22 +367,51 @@ export default class Offence extends TranslatedComponent {
|
|||||||
</div>
|
</div>
|
||||||
<div className='group quarter'>
|
<div className='group quarter'>
|
||||||
<h2>{translate('offence metrics')}</h2>
|
<h2>{translate('offence metrics')}</h2>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_DRAIN_WEP'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_DRAIN_WEP')}<br/>{timeToDrain === Infinity ? translate('never') : formats.time(timeToDrain)}</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_DRAIN_WEP'))}
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('TT_EFFECTIVE_SDPS_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_EFFECTIVE_SDPS_SHIELDS')}<br/>{formats.f1(totalShieldsSDps)}</h2>
|
onMouseOut={tooltip.bind(null, null)}>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_REMOVE_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_REMOVE_SHIELDS')}<br/>{timeToDepleteShields === Infinity ? translate('never') : formats.time(timeToDepleteShields)}</h2>
|
{translate('PHRASE_TIME_TO_DRAIN_WEP')}<br/>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('TT_EFFECTIVE_SDPS_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_EFFECTIVE_SDPS_ARMOUR')}<br/>{formats.f1(totalArmourSDps)}</h2>
|
{timeToDrain === Infinity ? translate('never') : formats.time(timeToDrain)}
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_REMOVE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_REMOVE_ARMOUR')}<br/>{timeToDepleteArmour === Infinity ? translate('never') : formats.time(timeToDepleteArmour)}</h2>
|
</h2>
|
||||||
|
<h2 onMouseOver={termtip.bind(null, translate('TT_EFFECTIVE_SDPS_SHIELDS'))}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}>
|
||||||
|
{translate('PHRASE_EFFECTIVE_SDPS_SHIELDS')}<br/>
|
||||||
|
{formats.f1(shieldsSdps)}
|
||||||
|
</h2>
|
||||||
|
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_REMOVE_SHIELDS'))}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}>
|
||||||
|
{translate('PHRASE_TIME_TO_REMOVE_SHIELDS')}<br/>
|
||||||
|
{timeToDepleteShields === Infinity ? translate('never') : formats.time(timeToDepleteShields)}
|
||||||
|
</h2>
|
||||||
|
<h2 onMouseOver={termtip.bind(null, translate('TT_EFFECTIVE_SDPS_ARMOUR'))}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}>
|
||||||
|
{translate('PHRASE_EFFECTIVE_SDPS_ARMOUR')}<br/>
|
||||||
|
{formats.f1(armourSdps)}
|
||||||
|
</h2>
|
||||||
|
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_REMOVE_ARMOUR'))}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}>
|
||||||
|
{translate('PHRASE_TIME_TO_REMOVE_ARMOUR')}<br/>
|
||||||
|
{timeToDepleteArmour === Infinity ? translate('never') : formats.time(timeToDepleteArmour)}
|
||||||
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<div className='group quarter'>
|
<div className='group quarter'>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_OVERALL_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('overall damage')}</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_OVERALL_DAMAGE'))}
|
||||||
<PieChart data={totalSDpsData} />
|
onMouseOut={tooltip.bind(null, null)}>
|
||||||
|
{translate('overall damage')}
|
||||||
|
</h2>
|
||||||
|
<PieChart data={sdpsPie} />
|
||||||
</div>
|
</div>
|
||||||
<div className='group quarter'>
|
<div className='group quarter'>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield damage sources')}</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_DAMAGE'))}
|
||||||
<PieChart data={shieldsSDpsData} />
|
onMouseOut={tooltip.bind(null, null)}>
|
||||||
|
{translate('shield damage sources')}
|
||||||
|
</h2>
|
||||||
|
<PieChart data={shieldsSdpsPie} />
|
||||||
</div>
|
</div>
|
||||||
<div className='group quarter'>
|
<div className='group quarter'>
|
||||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_ARMOUR_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('armour damage sources')}</h2>
|
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_ARMOUR_DAMAGE'))}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}>
|
||||||
|
{translate('armour damage sources')}
|
||||||
|
</h2>
|
||||||
<PieChart data={armourSDpsData} />
|
<PieChart data={armourSDpsData} />
|
||||||
</div>
|
</div>
|
||||||
</span>);
|
</span>);
|
||||||
|
|||||||
@@ -11,29 +11,24 @@ import Movement from './Movement';
|
|||||||
import Offence from './Offence';
|
import Offence from './Offence';
|
||||||
import Defence from './Defence';
|
import Defence from './Defence';
|
||||||
import WeaponDamageChart from './WeaponDamageChart';
|
import WeaponDamageChart from './WeaponDamageChart';
|
||||||
|
import Pips from '../components/Pips';
|
||||||
|
import Boost from '../components/Boost';
|
||||||
|
import Fuel from '../components/Fuel';
|
||||||
|
import Cargo from '../components/Cargo';
|
||||||
|
import ShipPicker from '../components/ShipPicker';
|
||||||
|
import EngagementRange from '../components/EngagementRange';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
import { ShipProps } from 'ed-forge';
|
||||||
|
const { CARGO_CAPACITY, FUEL_CAPACITY } = ShipProps;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Outfitting subpages
|
* Outfitting subpages
|
||||||
*/
|
*/
|
||||||
export default class OutfittingSubpages extends TranslatedComponent {
|
export default class OutfittingSubpages extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
code: PropTypes.string.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
onChange: PropTypes.func.isRequired,
|
|
||||||
buildName: PropTypes.string,
|
buildName: PropTypes.string,
|
||||||
sys: PropTypes.number.isRequired,
|
|
||||||
eng: PropTypes.number.isRequired,
|
|
||||||
wep: PropTypes.number.isRequired,
|
|
||||||
cargo: PropTypes.number.isRequired,
|
|
||||||
fuel: PropTypes.number.isRequired,
|
|
||||||
boost: PropTypes.bool.isRequired,
|
|
||||||
engagementRange: PropTypes.number.isRequired,
|
|
||||||
opponent: PropTypes.object.isRequired,
|
|
||||||
opponentBuild: PropTypes.string,
|
|
||||||
opponentSys: PropTypes.number.isRequired,
|
|
||||||
opponentEng: PropTypes.number.isRequired,
|
|
||||||
opponentWep: PropTypes.number.isRequired,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -42,13 +37,17 @@ export default class OutfittingSubpages extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this._powerTab = this._powerTab.bind(this);
|
autoBind(this);
|
||||||
this._profilesTab = this._profilesTab.bind(this);
|
|
||||||
this._offenceTab = this._offenceTab.bind(this);
|
|
||||||
this._defenceTab = this._defenceTab.bind(this);
|
|
||||||
|
|
||||||
|
this.props.ship.setOpponent(this.props.ship);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
boost: false,
|
||||||
|
cargo: props.ship.get(CARGO_CAPACITY),
|
||||||
|
fuel: props.ship.get(FUEL_CAPACITY),
|
||||||
|
pips: props.ship.getDistributorSettingsObject(),
|
||||||
tab: Persist.getOutfittingTab() || 'power',
|
tab: Persist.getOutfittingTab() || 'power',
|
||||||
|
engagementRange: 1000,
|
||||||
|
opponent: this.props.ship,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,128 +56,114 @@ export default class OutfittingSubpages extends TranslatedComponent {
|
|||||||
* @param {string} tab Tab name
|
* @param {string} tab Tab name
|
||||||
*/
|
*/
|
||||||
_showTab(tab) {
|
_showTab(tab) {
|
||||||
|
Persist.setOutfittingTab(tab);
|
||||||
this.setState({ tab });
|
this.setState({ tab });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the power tab
|
|
||||||
* @return {React.Component} Tab contents
|
|
||||||
*/
|
|
||||||
_powerTab() {
|
|
||||||
let { ship, buildName, code, onChange } = this.props;
|
|
||||||
Persist.setOutfittingTab('power');
|
|
||||||
|
|
||||||
const powerMarker = `${ship.toString()}`;
|
|
||||||
const costMarker = `${ship.toString().split('.')[0]}`;
|
|
||||||
|
|
||||||
return <div>
|
|
||||||
<PowerManagement ship={ship} code={powerMarker} onChange={onChange} />
|
|
||||||
<CostSection ship={ship} buildName={buildName} code={costMarker} />
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the profiles tab
|
|
||||||
* @return {React.Component} Tab contents
|
|
||||||
*/
|
|
||||||
_profilesTab() {
|
|
||||||
const { ship, opponent, cargo, fuel, eng, boost, engagementRange, opponentSys } = this.props;
|
|
||||||
const { translate } = this.context.language;
|
|
||||||
let realBoost = boost && ship.canBoost(cargo, fuel);
|
|
||||||
Persist.setOutfittingTab('profiles');
|
|
||||||
|
|
||||||
const engineProfileMarker = `${ship.toString()}:${cargo}:${fuel}:${eng}:${realBoost}`;
|
|
||||||
const fsdProfileMarker = `${ship.toString()}:${cargo}:${fuel}`;
|
|
||||||
const movementMarker = `${ship.topSpeed}:${ship.pitch}:${ship.roll}:${ship.yaw}:${ship.canBoost(cargo, fuel)}`;
|
|
||||||
const damageMarker = `${ship.toString()}:${opponent.toString()}:${engagementRange}:${opponentSys}`;
|
|
||||||
|
|
||||||
return <div>
|
|
||||||
<div className='group third'>
|
|
||||||
<h1>{translate('engine profile')}</h1>
|
|
||||||
<EngineProfile ship={ship} marker={engineProfileMarker} fuel={fuel} cargo={cargo} eng={eng} boost={realBoost} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='group third'>
|
|
||||||
<h1>{translate('fsd profile')}</h1>
|
|
||||||
<FSDProfile ship={ship} marker={fsdProfileMarker} fuel={fuel} cargo={cargo} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='group third'>
|
|
||||||
<h1>{translate('movement profile')}</h1>
|
|
||||||
<Movement marker={movementMarker} ship={ship} boost={boost} eng={eng} cargo={cargo} fuel={fuel} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='group half'>
|
|
||||||
<h1>{translate('damage to opponent\'s shields')}</h1>
|
|
||||||
<WeaponDamageChart marker={damageMarker} ship={ship} opponent={opponent} opponentSys={opponentSys} hull={false} engagementRange={engagementRange} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className='group half'>
|
|
||||||
<h1>{translate('damage to opponent\'s hull')}</h1>
|
|
||||||
<WeaponDamageChart marker={damageMarker} ship={ship} opponent={opponent} opponentSys={opponentSys} hull={true} engagementRange={engagementRange} />
|
|
||||||
</div>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the offence tab
|
|
||||||
* @return {React.Component} Tab contents
|
|
||||||
*/
|
|
||||||
_offenceTab() {
|
|
||||||
const { ship, sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild, opponentSys } = this.props;
|
|
||||||
Persist.setOutfittingTab('offence');
|
|
||||||
|
|
||||||
const marker = `${ship.toString()}${opponent.toString()}${opponentBuild}${engagementRange}${opponentSys}`;
|
|
||||||
|
|
||||||
return <div>
|
|
||||||
<Offence marker={marker} ship={ship} opponent={opponent} wep={wep} opponentSys={opponentSys} engagementrange={engagementRange}/>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the defence tab
|
|
||||||
* @return {React.Component} Tab contents
|
|
||||||
*/
|
|
||||||
_defenceTab() {
|
|
||||||
const { ship, sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild, opponentWep } = this.props;
|
|
||||||
Persist.setOutfittingTab('defence');
|
|
||||||
|
|
||||||
const marker = `${ship.toString()}${opponent.toString()}{opponentBuild}${engagementRange}${opponentWep}`;
|
|
||||||
|
|
||||||
return <div>
|
|
||||||
<Defence marker={marker} ship={ship} opponent={opponent} sys={sys} opponentWep={opponentWep} engagementrange={engagementRange}/>
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the section
|
* Render the section
|
||||||
* @return {React.Component} Contents
|
* @return {React.Component} Contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const tab = this.state.tab;
|
const { buildName, code, ship } = this.props;
|
||||||
const translate = this.context.language.translate;
|
const { boost, cargo, fuel, pips, tab, engagementRange, opponent } = this.state;
|
||||||
let tabSection;
|
const { translate } = this.context.language;
|
||||||
|
|
||||||
switch (tab) {
|
|
||||||
case 'power': tabSection = this._powerTab(); break;
|
|
||||||
case 'profiles': tabSection = this._profilesTab(); break;
|
|
||||||
case 'offence': tabSection = this._offenceTab(); break;
|
|
||||||
case 'defence': tabSection = this._defenceTab(); break;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
const cargoCapacity = ship.get(CARGO_CAPACITY);
|
||||||
|
const showCargoSlider = cargoCapacity > 0;
|
||||||
return (
|
return (
|
||||||
|
<div>
|
||||||
|
{/* Control of ship and opponent */}
|
||||||
|
<div className="group quarter">
|
||||||
|
<h2 style={{ verticalAlign: 'middle', textAlign: 'center' }}>
|
||||||
|
{translate('ship control')}
|
||||||
|
</h2>
|
||||||
|
<Boost boost={boost} onChange={(boost) => this.setState({ boost })} />
|
||||||
|
</div>
|
||||||
|
<div className="group quarter">
|
||||||
|
<h2 style={{ verticalAlign: 'middle', textAlign: 'center' }}>
|
||||||
|
{translate('opponent')}
|
||||||
|
</h2>
|
||||||
|
<ShipPicker ship={ship} onChange={(opponent) => this.setState({ opponent })} />
|
||||||
|
</div>
|
||||||
|
<div className={cn('group', { quarter: showCargoSlider, half: !showCargoSlider })}>
|
||||||
|
<Fuel fuelCapacity={ship.get(FUEL_CAPACITY)} fuel={fuel}
|
||||||
|
onChange={(fuel) => this.setState({ fuel })} />
|
||||||
|
</div>
|
||||||
|
{showCargoSlider ?
|
||||||
|
<div className="group quarter">
|
||||||
|
<Cargo cargoCapacity={cargoCapacity} cargo={cargo}
|
||||||
|
onChange={(cargo) => this.setState({ cargo })} />
|
||||||
|
</div> : null}
|
||||||
|
<div className="group half">
|
||||||
|
<Pips ship={ship} pips={pips} onChange={(pips) => this.setState({ pips })} />
|
||||||
|
</div>
|
||||||
|
<div className="group half">
|
||||||
|
<EngagementRange ship={ship} engagementRange={engagementRange}
|
||||||
|
onChange={(engagementRange) => this.setState({ engagementRange })} />
|
||||||
|
</div>
|
||||||
<div className='group full' style={{ minHeight: '1000px' }}>
|
<div className='group full' style={{ minHeight: '1000px' }}>
|
||||||
<table className='tabs'>
|
<table className='tabs'>
|
||||||
|
{/* Select tab section */}
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'power' })} onClick={this._showTab.bind(this, 'power')} >{translate('power and costs')}</th>
|
<th style={{ width:'25%' }} className={cn({ active: tab == 'power' })}
|
||||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'profiles' })} onClick={this._showTab.bind(this, 'profiles')} >{translate('profiles')}</th>
|
onClick={this._showTab.bind(this, 'power')}>
|
||||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'offence' })} onClick={this._showTab.bind(this, 'offence')} >{translate('offence')}</th>
|
{translate('power and costs')}
|
||||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'defence' })} onClick={this._showTab.bind(this, 'defence')} >{translate('defence')}</th>
|
</th>
|
||||||
|
<th style={{ width:'25%' }} className={cn({ active: tab == 'profiles' })}
|
||||||
|
onClick={this._showTab.bind(this, 'profiles')}>
|
||||||
|
{translate('profiles')}</th>
|
||||||
|
<th style={{ width:'25%' }} className={cn({ active: tab == 'offence' })}
|
||||||
|
onClick={this._showTab.bind(this, 'offence')}>
|
||||||
|
{translate('offence')}
|
||||||
|
</th>
|
||||||
|
<th style={{ width:'25%' }} className={cn({ active: tab == 'defence' })}
|
||||||
|
onClick={this._showTab.bind(this, 'defence')}>
|
||||||
|
{translate('tab_defence')}
|
||||||
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
</table>
|
</table>
|
||||||
{tabSection}
|
{/* Show selected tab */}
|
||||||
|
{tab == 'power' ?
|
||||||
|
<div>
|
||||||
|
<PowerManagement ship={ship} code={code} />
|
||||||
|
<CostSection ship={ship} buildName={buildName} code={code} />
|
||||||
|
</div> : null}
|
||||||
|
{tab == 'profiles' ?
|
||||||
|
<div>
|
||||||
|
<div className='group third'>
|
||||||
|
<h1>{translate('engine profile')}</h1>
|
||||||
|
<EngineProfile code={code} ship={ship} fuel={fuel} cargo={cargo} pips={pips} boost={boost} />
|
||||||
|
</div>
|
||||||
|
<div className='group third'>
|
||||||
|
<h1>{translate('fsd profile')}</h1>
|
||||||
|
<FSDProfile code={code} ship={ship} fuel={fuel} cargo={cargo} />
|
||||||
|
</div>
|
||||||
|
<div className='group third'>
|
||||||
|
<h1>{translate('movement profile')}</h1>
|
||||||
|
<Movement code={code} ship={ship} boost={boost} pips={pips} />
|
||||||
|
</div>
|
||||||
|
<div className='group third'>
|
||||||
|
<h1>{translate('damage to opponent\'s shields')}</h1>
|
||||||
|
<WeaponDamageChart code={code} ship={ship} opponentDefence={opponent.getShield()} engagementRange={engagementRange} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className='group third'>
|
||||||
|
<h1>{translate('damage to opponent\'s hull')}</h1>
|
||||||
|
<WeaponDamageChart code={code} ship={ship} opponentDefence={opponent.getArmour()} engagementRange={engagementRange} />
|
||||||
|
</div>
|
||||||
|
</div> : null}
|
||||||
|
{tab == 'offence' ?
|
||||||
|
<div>
|
||||||
|
<Offence code={code} ship={ship} opponent={opponent} engagementRange={engagementRange} />
|
||||||
|
</div> : null}
|
||||||
|
{tab == 'defence' ?
|
||||||
|
<div>
|
||||||
|
<Defence code={code} ship={ship} opponent={opponent} engagementRange={engagementRange} />
|
||||||
|
</div> : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ const LABEL_COLOUR = '#000000';
|
|||||||
* A pie chart
|
* A pie chart
|
||||||
*/
|
*/
|
||||||
export default class PieChart extends Component {
|
export default class PieChart extends Component {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
data : PropTypes.array.isRequired
|
data : PropTypes.array.isRequired
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
|
|||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { Pip } from './SvgIcons';
|
import { Pip } from './SvgIcons';
|
||||||
import { autoBind } from 'react-extras';
|
import { autoBind } from 'react-extras';
|
||||||
|
import { Ship } from 'ed-forge';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pips displays SYS/ENG/WEP pips and allows users to change them with key presses by clicking on the relevant area.
|
* Pips displays SYS/ENG/WEP pips and allows users to change them with key presses by clicking on the relevant area.
|
||||||
@@ -10,13 +11,9 @@ import { autoBind } from 'react-extras';
|
|||||||
*/
|
*/
|
||||||
export default class Pips extends TranslatedComponent {
|
export default class Pips extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
sys: PropTypes.number.isRequired,
|
ship: PropTypes.instanceOf(Ship).isRequired,
|
||||||
eng: PropTypes.number.isRequired,
|
pips: PropTypes.object.isRequired,
|
||||||
wep: PropTypes.number.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
mcSys: PropTypes.number.isRequired,
|
|
||||||
mcEng: PropTypes.number.isRequired,
|
|
||||||
mcWep: PropTypes.number.isRequired,
|
|
||||||
onChange: PropTypes.func.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,6 +24,12 @@ export default class Pips extends TranslatedComponent {
|
|||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
autoBind(this);
|
autoBind(this);
|
||||||
|
|
||||||
|
const { ship } = props;
|
||||||
|
this._incSys = this._change(ship.incSys);
|
||||||
|
this._incEng = this._change(ship.incEng);
|
||||||
|
this._incWep = this._change(ship.incWep);
|
||||||
|
this._reset = this._change(ship.pipsReset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,153 +74,42 @@ export default class Pips extends TranslatedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset the capacitor
|
* Creates a function that handles pip assignment and call `onChance`.
|
||||||
*/
|
* @param {String} cb Callback that handles the actual pip assignment
|
||||||
_reset(isMc) {
|
|
||||||
let { sys, eng, wep, mcSys, mcEng, mcWep } = this.props;
|
|
||||||
if (isMc) {
|
|
||||||
if (mcSys || mcEng || mcWep) {
|
|
||||||
sys -= mcSys;
|
|
||||||
eng -= mcEng;
|
|
||||||
wep -= mcWep;
|
|
||||||
this.props.onChange(sys, eng, wep, 0, 0, 0);
|
|
||||||
}
|
|
||||||
} else if (sys != 2 || eng != 2 || wep != 2) {
|
|
||||||
sys = eng = wep = 2;
|
|
||||||
this.props.onChange(sys + mcSys, eng + mcEng, wep + mcWep, mcSys, mcEng, mcWep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increment the SYS capacitor
|
|
||||||
*/
|
|
||||||
_incSys() {
|
|
||||||
this._inc('sys', false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increment the ENG capacitor
|
|
||||||
*/
|
|
||||||
_incEng() {
|
|
||||||
this._inc('eng', false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increment the WEP capacitor
|
|
||||||
*/
|
|
||||||
_incWep() {
|
|
||||||
this._inc('wep', false);
|
|
||||||
}
|
|
||||||
|
|
||||||
_wrapMcClick(key) {
|
|
||||||
return (event) => {
|
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
if (key == 'rst') {
|
|
||||||
this._reset(true);
|
|
||||||
} else {
|
|
||||||
this._inc(key, true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Increases a given capacitor
|
|
||||||
* @param {String} key Pip name to increase (one of 'sys', 'eng', 'wep')
|
|
||||||
* @param {Boolean} isMc True when increase is by multi crew
|
* @param {Boolean} isMc True when increase is by multi crew
|
||||||
|
* @returns {Function} Function that handles pip assigment
|
||||||
*/
|
*/
|
||||||
_inc(key, isMc) {
|
_change(cb, isMc) {
|
||||||
if (!['sys', 'eng', 'wep'].includes(key)) {
|
return () => {
|
||||||
return;
|
cb(isMc);
|
||||||
}
|
this.props.onChange(this.props.ship.getDistributorSettingsObject());
|
||||||
|
};
|
||||||
let { sys, eng, wep, mcSys, mcEng, mcWep } = this.props;
|
|
||||||
let mc = key == 'sys' ? mcSys : (key == 'eng' ? mcEng : mcWep);
|
|
||||||
let pips = this.props[key] - mc;
|
|
||||||
let other1 = key == 'sys' ? eng - mcEng : sys - mcSys;
|
|
||||||
let other2 = key == 'wep' ? eng - mcEng : wep - mcWep;
|
|
||||||
|
|
||||||
const required = Math.min(1, 4 - mc - pips);
|
|
||||||
if (isMc) {
|
|
||||||
// We can only set full pips in multi-crew also we can only set two pips
|
|
||||||
if (required > 0.5 && mcSys + mcEng + mcWep < 2) {
|
|
||||||
if (key == 'sys') {
|
|
||||||
mcSys += 1;
|
|
||||||
} else if (key == 'eng') {
|
|
||||||
mcEng += 1;
|
|
||||||
} else {
|
|
||||||
mcWep += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (required > 0) {
|
|
||||||
if (required == 0.5) {
|
|
||||||
// Take from whichever is larger
|
|
||||||
if (other1 > other2) {
|
|
||||||
other1 -= 0.5;
|
|
||||||
} else {
|
|
||||||
other2 -= 0.5;
|
|
||||||
}
|
|
||||||
pips += 0.5;
|
|
||||||
} else {
|
|
||||||
// Required is 1 - take from both if possible
|
|
||||||
if (other1 == 0) {
|
|
||||||
other2 -= 1;
|
|
||||||
} else if (other2 == 0) {
|
|
||||||
other1 -= 1;
|
|
||||||
} else {
|
|
||||||
other1 -= 0.5;
|
|
||||||
other2 -= 0.5;
|
|
||||||
}
|
|
||||||
pips += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sys = mcSys + (key == 'sys' ? pips : other1);
|
|
||||||
eng = mcEng + (key == 'eng' ? pips : (key == 'sys' ? other1 : other2));
|
|
||||||
wep = mcWep + (key == 'wep' ? pips : other2);
|
|
||||||
this.props.onChange(sys, eng, wep, mcSys, mcEng, mcWep);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set up the rendering for pips
|
* Set up the rendering for pips
|
||||||
* @param {Number} sys the SYS pips
|
|
||||||
* @param {Number} eng the ENG pips
|
|
||||||
* @param {Number} wep the WEP pips
|
|
||||||
* @param {Number} mcSys SYS pips from multi-crew
|
|
||||||
* @param {Number} mcEng ENG pips from multi-crew
|
|
||||||
* @param {Number} mcWep WEP pips from multi-crew
|
|
||||||
* @returns {Object} Object containing the rendering for the pips
|
* @returns {Object} Object containing the rendering for the pips
|
||||||
*/
|
*/
|
||||||
_renderPips(sys, eng, wep, mcSys, mcEng, mcWep) {
|
_renderPips() {
|
||||||
const pipsSvg = {
|
const pipsSvg = {
|
||||||
SYS: [],
|
Sys: [],
|
||||||
ENG: [],
|
Eng: [],
|
||||||
WEP: [],
|
Wep: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
// Multi-crew pipsSettings actually are included in the overall pip count therefore
|
for (let k in this.props.pips) {
|
||||||
// we can consider [0, sys - mcSys] as normal pipsSettings whilst [sys - mcSys, sys]
|
let { base, mc } = this.props.pips[k];
|
||||||
// are the multi-crew pipsSettings in what follows.
|
for (let i = 0; i < Math.floor(base); i++) {
|
||||||
|
pipsSvg[k].push(<Pip key={i} className='full' />);
|
||||||
let pipsSettings = {
|
|
||||||
SYS: [sys, mcSys],
|
|
||||||
ENG: [eng, mcEng],
|
|
||||||
WEP: [wep, mcWep],
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let pipName in pipsSettings) {
|
|
||||||
let [pips, mcPips] = pipsSettings[pipName];
|
|
||||||
for (let i = 0; i < Math.floor(pips - mcPips); i++) {
|
|
||||||
pipsSvg[pipName].push(<Pip key={i} className='full' />);
|
|
||||||
}
|
}
|
||||||
if (pips > Math.floor(pips)) {
|
if (base > Math.floor(base)) {
|
||||||
pipsSvg[pipName].push(<Pip className='half' key={'half'} />);
|
pipsSvg[k].push(<Pip className='half' key={'half'} />);
|
||||||
}
|
}
|
||||||
for (let i = pips - mcPips; i < Math.floor(pips); i++) {
|
for (let i = 0; i < mc; i++) {
|
||||||
pipsSvg[pipName].push(<Pip key={i} className='mc' />);
|
pipsSvg[k].push(<Pip key={base + i} className='mc' />);
|
||||||
}
|
}
|
||||||
for (let i = Math.floor(pips + 0.5); i < 4; i++) {
|
for (let i = Math.ceil(base + mc); i < 4; i++) {
|
||||||
pipsSvg[pipName].push(<Pip className='empty' key={i} />);
|
pipsSvg[k].push(<Pip className='empty' key={i} />);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,11 +121,10 @@ export default class Pips extends TranslatedComponent {
|
|||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { tooltip, termtip } = this.context;
|
const { ship } = this.props;
|
||||||
const { formats, translate, units } = this.context.language;
|
const { translate } = this.context.language;
|
||||||
const { sys, eng, wep, mcSys, mcEng, mcWep } = this.props;
|
|
||||||
|
|
||||||
const pipsSvg = this._renderPips(sys, eng, wep, mcSys, mcEng, mcWep);
|
const pipsSvg = this._renderPips();
|
||||||
return (
|
return (
|
||||||
<span id='pips'>
|
<span id='pips'>
|
||||||
<table>
|
<table>
|
||||||
@@ -241,38 +132,40 @@ export default class Pips extends TranslatedComponent {
|
|||||||
<tr>
|
<tr>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
<td className='clickable' onClick={() => this._inc('eng')}
|
<td className='clickable' onClick={this._incEng}>{pipsSvg.Eng}</td>
|
||||||
onContextMenu={this._wrapMcClick('eng')}>{pipsSvg['ENG']}</td>
|
|
||||||
<td> </td>
|
<td> </td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
<td className='clickable' onClick={this._incSys}
|
<td className='clickable' onClick={this._incSys}>{pipsSvg.Sys}</td>
|
||||||
onContextMenu={this._wrapMcClick('sys')}>{pipsSvg['SYS']}</td>
|
<td className='clickable' onClick={this._incEng}>
|
||||||
<td className='clickable' onClick={this._incEng}
|
{translate('ENG')}
|
||||||
onContextMenu={this._wrapMcClick('eng')}>{translate('ENG')}</td>
|
</td>
|
||||||
<td className='clickable' onClick={this._incWep}
|
<td className='clickable' onClick={this._incWep}>{pipsSvg.Wep}</td>
|
||||||
onContextMenu={this._wrapMcClick('wep')}>{pipsSvg['WEP']}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
<td className='clickable' onClick={this._incSys}
|
<td className='clickable' onClick={this._incSys}>
|
||||||
onContextMenu={this._wrapMcClick('sys')}>{translate('SYS')}</td>
|
{translate('SYS')}
|
||||||
<td className='clickable' onClick={this._reset.bind(this, false)}>
|
</td>
|
||||||
|
<td className='clickable' onClick={this._reset}>
|
||||||
{translate('RST')}
|
{translate('RST')}
|
||||||
</td>
|
</td>
|
||||||
<td className='clickable' onClick={this._incWep}
|
<td className='clickable' onClick={this._incWep}>
|
||||||
onContextMenu={this._wrapMcClick('wep')}>{translate('WEP')}</td>
|
{translate('WEP')}
|
||||||
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td> </td>
|
<td> </td>
|
||||||
<td> </td>
|
<td className='clickable' onClick={this._change(ship.incSys, true)}>
|
||||||
<td className='clickable secondary' onClick={this._wrapMcClick('rst')}
|
<Pip className='mc' />
|
||||||
onMouseEnter={termtip.bind(null, 'PHRASE_MULTI_CREW_CAPACITOR_POINTS')}
|
</td>
|
||||||
onMouseLeave={tooltip.bind(null, null)}>
|
<td className='clickable' onClick={this._change(ship.incEng, true)}>
|
||||||
{translate('RST')}
|
<Pip className='mc' />
|
||||||
|
</td>
|
||||||
|
<td className='clickable' onClick={this._change(ship.incWep, true)}>
|
||||||
|
<Pip className='mc' />
|
||||||
</td>
|
</td>
|
||||||
<td> </td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -4,27 +4,25 @@ import * as d3 from 'd3';
|
|||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { wrapCtxMenu } from '../utils/UtilityFunctions';
|
import { wrapCtxMenu } from '../utils/UtilityFunctions';
|
||||||
|
import { Ship } from 'ed-forge';
|
||||||
|
import { POWER_METRICS } from 'ed-forge/lib/ship-stats';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Round to avoid floating point precision errors
|
* Get the band-class.
|
||||||
* @param {Boolean} selected Band selected
|
* @param {Boolean} selected Band selected
|
||||||
* @param {number} sum Band power sum
|
* @param {Number} relDraw Relative amount of power drawn by this band and
|
||||||
* @param {number} avail Total available power
|
* all prior
|
||||||
* @return {string} CSS Class name
|
* @return {string} CSS Class name
|
||||||
*/
|
*/
|
||||||
function getClass(selected, sum, avail) {
|
function getClass(selected, relDraw) {
|
||||||
return selected ? 'secondary' : ((Math.round(sum * 100) / 100) >= avail) ? 'warning' : 'primary';
|
if (selected) {
|
||||||
}
|
return 'secondary';
|
||||||
|
} else if (relDraw >= 1) {
|
||||||
/**
|
return 'warning';
|
||||||
* Get the # label for a Priority band
|
} else {
|
||||||
* @param {number} val Priority Band Watt value
|
return 'primary';
|
||||||
* @param {number} index Priority Band index
|
}
|
||||||
* @param {Function} wattScale Watt Scale function
|
|
||||||
* @return {number} label / text
|
|
||||||
*/
|
|
||||||
function bandText(val, index, wattScale) {
|
|
||||||
return (val > 0 && wattScale(val) > 13) ? index + 1 : null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -33,10 +31,9 @@ function bandText(val, index, wattScale) {
|
|||||||
*/
|
*/
|
||||||
export default class PowerBands extends TranslatedComponent {
|
export default class PowerBands extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
bands: PropTypes.array.isRequired,
|
ship: PropTypes.instanceOf(Ship).isRequired,
|
||||||
available: PropTypes.number.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
width: PropTypes.number.isRequired,
|
width: PropTypes.number.isRequired,
|
||||||
code: PropTypes.string,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -46,20 +43,16 @@ export default class PowerBands extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
this.wattScale = d3.scaleLinear();
|
this.wattScale = d3.scaleLinear();
|
||||||
this.pctScale = d3.scaleLinear().domain([0, 1]);
|
this.pctScale = d3.scaleLinear().domain([0, 1]);
|
||||||
this.wattAxis = d3.axisTop(this.wattScale).tickSizeOuter(0).tickFormat(context.language.formats.r2);
|
this.wattAxis = d3.axisTop(this.wattScale).tickSizeOuter(0).tickFormat(context.language.formats.r2);
|
||||||
this.pctAxis = d3.axisBottom(this.pctScale).tickSizeOuter(0).tickFormat(context.language.formats.rPct);
|
this.pctAxis = d3.axisBottom(this.pctScale).tickSizeOuter(0).tickFormat(context.language.formats.rPct);
|
||||||
|
|
||||||
this._updateDimensions = this._updateDimensions.bind(this);
|
|
||||||
this._updateScales = this._updateScales.bind(this);
|
|
||||||
this._selectNone = this._selectNone.bind(this);
|
|
||||||
this._hidetip = () => this.context.tooltip();
|
this._hidetip = () => this.context.tooltip();
|
||||||
|
|
||||||
let maxBand = props.bands[props.bands.length - 1];
|
this.profile = props.ship.getMetrics(POWER_METRICS);
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
maxPwr: Math.max(props.available, maxBand.retractedSum, maxBand.deployedSum),
|
|
||||||
ret: {},
|
ret: {},
|
||||||
dep: {}
|
dep: {}
|
||||||
};
|
};
|
||||||
@@ -83,8 +76,6 @@ export default class PowerBands extends TranslatedComponent {
|
|||||||
let mRight = Math.round(140 * scale);
|
let mRight = Math.round(140 * scale);
|
||||||
let innerWidth = props.width - mLeft - mRight;
|
let innerWidth = props.width - mLeft - mRight;
|
||||||
|
|
||||||
this._updateScales(innerWidth, this.state.maxPwr, props.available);
|
|
||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
barHeight,
|
barHeight,
|
||||||
innerHeight,
|
innerHeight,
|
||||||
@@ -140,41 +131,67 @@ export default class PowerBands extends TranslatedComponent {
|
|||||||
this.setState({ dep: Object.assign({}, dep) });
|
this.setState({ dep: Object.assign({}, dep) });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update scale
|
|
||||||
* @param {number} innerWidth SVG innerwidth
|
|
||||||
* @param {number} maxPwr Maximum power level MJ (deployed or available)
|
|
||||||
* @param {number} available Available power MJ
|
|
||||||
*/
|
|
||||||
_updateScales(innerWidth, maxPwr, available) {
|
|
||||||
this.wattScale.range([0, innerWidth]).domain([0, maxPwr]).clamp(true);
|
|
||||||
this.pctScale.range([0, innerWidth]).domain([0, maxPwr / available]).clamp(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update state based on property and context changes
|
* Update state based on property and context changes
|
||||||
* @param {Object} nextProps Incoming/Next properties
|
* @param {Object} nextProps Incoming/Next properties
|
||||||
* @param {Object} nextContext Incoming/Next context
|
* @param {Object} nextContext Incoming/Next context
|
||||||
*/
|
*/
|
||||||
componentWillReceiveProps(nextProps, nextContext) {
|
componentWillReceiveProps(nextProps, nextContext) {
|
||||||
let { innerWidth, maxPwr } = this.state;
|
|
||||||
let { language, sizeRatio } = this.context;
|
let { language, sizeRatio } = this.context;
|
||||||
let maxBand = nextProps.bands[nextProps.bands.length - 1];
|
|
||||||
let nextMaxPwr = Math.max(nextProps.available, maxBand.retractedSum, maxBand.deployedSum);
|
|
||||||
|
|
||||||
if (language !== nextContext.language) {
|
if (language !== nextContext.language) {
|
||||||
this.wattAxis.tickFormat(nextContext.language.formats.r2);
|
this.wattAxis.tickFormat(nextContext.language.formats.r2);
|
||||||
this.pctAxis.tickFormat(nextContext.language.formats.rPct);
|
this.pctAxis.tickFormat(nextContext.language.formats.rPct);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (maxPwr != nextMaxPwr) { // Update Axes if max power has changed
|
if (nextProps.width != this.props.width || sizeRatio != nextContext.sizeRatio) {
|
||||||
this._updateScales(innerWidth, nextMaxPwr, nextProps.available);
|
|
||||||
this.setState({ maxPwr: nextMaxPwr });
|
|
||||||
} else if (nextProps.width != this.props.width || sizeRatio != nextContext.sizeRatio) {
|
|
||||||
this._updateDimensions(nextProps, nextContext.sizeRatio);
|
this._updateDimensions(nextProps, nextContext.sizeRatio);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assemble bands for relative consumption array.
|
||||||
|
* @param {Number[]} consumed Array of relative-consumption numbers
|
||||||
|
* @param {object} selected Object mapping selected bands to 1
|
||||||
|
* @param {Number} yOffset Offset in y-direction of the bar
|
||||||
|
* @param {Function} onClick onClick callback
|
||||||
|
* @returns {React.Component} Bands
|
||||||
|
*/
|
||||||
|
_consumedToBands(consumed, selected, yOffset, onClick) {
|
||||||
|
const { state, wattScale } = this;
|
||||||
|
const bands = [];
|
||||||
|
let consumesPrev = 0;
|
||||||
|
for (let i = 0; i < consumed.length; i++) {
|
||||||
|
consumesPrev = consumed[i - 1] || consumesPrev;
|
||||||
|
const consumes = consumed[i];
|
||||||
|
|
||||||
|
if (!consumes) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bands.push(<rect
|
||||||
|
key={'b' + i}
|
||||||
|
width={Math.ceil(Math.max(wattScale(consumes - consumesPrev), 0))}
|
||||||
|
height={state.barHeight}
|
||||||
|
x={wattScale(consumesPrev)}
|
||||||
|
y={yOffset + 1}
|
||||||
|
onClick={onClick.bind(this, i)}
|
||||||
|
className={getClass(selected[i], consumes)}
|
||||||
|
/>);
|
||||||
|
|
||||||
|
bands.push(<text
|
||||||
|
key={'t' + i}
|
||||||
|
dy='0.5em'
|
||||||
|
textAnchor='middle'
|
||||||
|
height={state.barHeight}
|
||||||
|
x={wattScale(consumesPrev) + (wattScale(consumes - consumesPrev) / 2)}
|
||||||
|
y={yOffset + (state.barHeight / 2)}
|
||||||
|
onClick={onClick.bind(this, i)}
|
||||||
|
className='primary-bg'>{i + 1}</text>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return bands;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the power bands
|
* Render the power bands
|
||||||
* @return {React.Component} Power bands
|
* @return {React.Component} Power bands
|
||||||
@@ -184,78 +201,27 @@ export default class PowerBands extends TranslatedComponent {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { wattScale, pctScale, context, props, state } = this;
|
let { pctScale, context, props, state } = this;
|
||||||
let { translate, formats } = context.language;
|
let { translate, formats } = context.language;
|
||||||
let { f2, pct1 } = formats; // wattFmt, pctFmt
|
let { f2, pct1 } = formats; // wattFmt, pctFmt
|
||||||
let { available, bands } = props;
|
let { ship } = props;
|
||||||
let { innerWidth, ret, dep } = state;
|
let { innerWidth, ret, dep, barHeight } = state;
|
||||||
let pwrWarningClass = cn('threshold', { exceeded: bands[0].retractedSum > available * 0.4 });
|
|
||||||
let deployed = [];
|
let {
|
||||||
let retracted = [];
|
consumed, generated, relativeConsumed, relativeConsumedRetracted
|
||||||
|
} = ship.getMetrics(POWER_METRICS);
|
||||||
|
let maxPwr = Math.max(consumed, generated);
|
||||||
|
let retSum = relativeConsumedRetracted[relativeConsumedRetracted.length - 1];
|
||||||
|
let depSum = relativeConsumed[relativeConsumed.length - 1];
|
||||||
|
|
||||||
|
this.wattScale.range([0, innerWidth]).domain([0, 1]).clamp(true);
|
||||||
|
this.pctScale.range([0, innerWidth]).domain([0, maxPwr / generated]).clamp(true);
|
||||||
|
|
||||||
|
let pwrWarningClass = cn('threshold', { exceeded: retSum > generated * 0.4 });
|
||||||
|
let retracted = this._consumedToBands(relativeConsumedRetracted, ret, 0, this._selectRet);
|
||||||
|
let deployed = this._consumedToBands(relativeConsumed, dep, barHeight, this._selectDep);
|
||||||
let retSelected = Object.keys(ret).length > 0;
|
let retSelected = Object.keys(ret).length > 0;
|
||||||
let depSelected = Object.keys(dep).length > 0;
|
let depSelected = Object.keys(dep).length > 0;
|
||||||
let retSum = 0;
|
|
||||||
let depSum = 0;
|
|
||||||
|
|
||||||
for (let i = 0; i < bands.length; i++) {
|
|
||||||
let b = bands[i];
|
|
||||||
retSum += (!retSelected || ret[i]) ? b.retracted : 0;
|
|
||||||
depSum += (!depSelected || dep[i]) ? b.deployed + b.retracted : 0;
|
|
||||||
|
|
||||||
if (b.retracted > 0) {
|
|
||||||
let retLbl = bandText(b.retracted, i, wattScale);
|
|
||||||
|
|
||||||
retracted.push(<rect
|
|
||||||
key={'rB' + i}
|
|
||||||
width={Math.ceil(Math.max(wattScale(b.retracted), 0))}
|
|
||||||
height={state.barHeight}
|
|
||||||
x={Math.floor(Math.max(wattScale(b.retractedSum) - wattScale(b.retracted), 0))}
|
|
||||||
y={1}
|
|
||||||
onClick={this._selectRet.bind(this, i)}
|
|
||||||
className={getClass(ret[i], b.retractedSum, available)}
|
|
||||||
/>);
|
|
||||||
|
|
||||||
if (retLbl) {
|
|
||||||
retracted.push(<text
|
|
||||||
key={'rT' + i}
|
|
||||||
dy='0.5em'
|
|
||||||
textAnchor='middle'
|
|
||||||
height={state.barHeight}
|
|
||||||
x={wattScale(b.retractedSum) - (wattScale(b.retracted) / 2)}
|
|
||||||
y={state.retY}
|
|
||||||
onClick={this._selectRet.bind(this, i)}
|
|
||||||
className='primary-bg'>{retLbl}</text>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (b.retracted > 0 || b.deployed > 0) {
|
|
||||||
let depLbl = bandText(b.deployed + b.retracted, i, wattScale);
|
|
||||||
|
|
||||||
deployed.push(<rect
|
|
||||||
key={'dB' + i}
|
|
||||||
width={Math.ceil(Math.max(wattScale(b.deployed + b.retracted), 0))}
|
|
||||||
height={state.barHeight}
|
|
||||||
x={Math.floor(Math.max(wattScale(b.deployedSum) - wattScale(b.retracted) - wattScale(b.deployed), 0))}
|
|
||||||
y={state.barHeight + 1}
|
|
||||||
onClick={this._selectDep.bind(this, i)}
|
|
||||||
className={getClass(dep[i], b.deployedSum, available)}
|
|
||||||
/>);
|
|
||||||
|
|
||||||
if (depLbl) {
|
|
||||||
deployed.push(<text
|
|
||||||
key={'dT' + i}
|
|
||||||
dy='0.5em'
|
|
||||||
textAnchor='middle'
|
|
||||||
height={state.barHeight}
|
|
||||||
x={wattScale(b.deployedSum) - ((wattScale(b.retracted) + wattScale(b.deployed)) / 2)}
|
|
||||||
y={state.depY}
|
|
||||||
onClick={this._selectDep.bind(this, i)}
|
|
||||||
className='primary-bg'>{depLbl}</text>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg style={{ marginTop: '1em', width: '100%', height: state.height }} onContextMenu={wrapCtxMenu(this._selectNone)}>
|
<svg style={{ marginTop: '1em', width: '100%', height: state.height }} onContextMenu={wrapCtxMenu(this._selectNone)}>
|
||||||
@@ -271,8 +237,8 @@ export default class PowerBands extends TranslatedComponent {
|
|||||||
<line x1={pctScale(0.4)} x2={pctScale(0.4)} y1='0' y2={state.innerHeight} className={pwrWarningClass} />
|
<line x1={pctScale(0.4)} x2={pctScale(0.4)} y1='0' y2={state.innerHeight} className={pwrWarningClass} />
|
||||||
<text dy='0.5em' x='-3' y={state.retY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'retracted')} onMouseLeave={this._hidetip}>{translate('ret')}</text>
|
<text dy='0.5em' x='-3' y={state.retY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'retracted')} onMouseLeave={this._hidetip}>{translate('ret')}</text>
|
||||||
<text dy='0.5em' x='-3' y={state.depY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'deployed', { orientation: 's', cap: 1 })} onMouseLeave={this._hidetip}>{translate('dep')}</text>
|
<text dy='0.5em' x='-3' y={state.depY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'deployed', { orientation: 's', cap: 1 })} onMouseLeave={this._hidetip}>{translate('dep')}</text>
|
||||||
<text dy='0.5em' x={innerWidth + 5} y={state.retY} className={getClass(retSelected, retSum, available)}>{f2(Math.max(0, retSum))} ({pct1(Math.max(0, retSum / available))})</text>
|
<text dy='0.5em' x={innerWidth + 5} y={state.retY} className={getClass(retSelected, retSum, generated)}>{f2(Math.max(0, retSum * generated))} ({pct1(Math.max(0, retSum))})</text>
|
||||||
<text dy='0.5em' x={innerWidth + 5} y={state.depY} className={getClass(depSelected, depSum, available)}>{f2(Math.max(0, depSum))} ({pct1(Math.max(0, depSum / available))})</text>
|
<text dy='0.5em' x={innerWidth + 5} y={state.depY} className={getClass(depSelected, depSum, generated)}>{f2(Math.max(0, depSum * generated))} ({pct1(Math.max(0, depSum))})</text>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,24 +3,49 @@ import PropTypes from 'prop-types';
|
|||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import PowerBands from './PowerBands';
|
import PowerBands from './PowerBands';
|
||||||
import { slotName, slotComparator } from '../utils/SlotFunctions';
|
|
||||||
import { Power, NoPower } from './SvgIcons';
|
import { Power, NoPower } from './SvgIcons';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
import { Ship, Module } from 'ed-forge';
|
||||||
|
|
||||||
const POWER = [
|
/**
|
||||||
null,
|
* Makes a comparison based on the order `false < undefined < true` (fut) and
|
||||||
null,
|
* maps it to `[-1, 0, 1]`.
|
||||||
<NoPower className='icon warning' />,
|
* @param {boolean} a Bool or undefined
|
||||||
<Power className='secondary-disabled' />
|
* @param {boolean} b Bool or undefined
|
||||||
];
|
* @returns {number} Comparison
|
||||||
|
*/
|
||||||
|
function futComp(a, b) {
|
||||||
|
switch (a) {
|
||||||
|
case false: return (b === false ? 0 : 1);
|
||||||
|
// The next else-expression maps false to -1 and true to 1
|
||||||
|
case undefined: return (b === undefined ? 0 : 2 * Number(b) - 1);
|
||||||
|
case true: return (b === true ? 0 : -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the enabled-icon.
|
||||||
|
* @param {boolean} enabled Is the module enabled?
|
||||||
|
* @returns {React.Component} Enabled icon.
|
||||||
|
*/
|
||||||
|
function getPowerIcon(enabled) {
|
||||||
|
if (enabled === undefined) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (enabled) {
|
||||||
|
return <Power className='secondary-disabled' />;
|
||||||
|
} else {
|
||||||
|
return <NoPower className='icon warning' />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Power Management Section
|
* Power Management Section
|
||||||
*/
|
*/
|
||||||
export default class PowerManagement extends TranslatedComponent {
|
export default class PowerManagement extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.instanceOf(Ship).isRequired,
|
||||||
code: PropTypes.string.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
onChange: PropTypes.func.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,19 +54,17 @@ export default class PowerManagement extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this._renderPowerRows = this._renderPowerRows.bind(this);
|
autoBind(this);
|
||||||
this._updateWidth = this._updateWidth.bind(this);
|
|
||||||
this._sort = this._sort.bind(this);
|
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
predicate: 'pwr',
|
predicate: 'pwr',
|
||||||
desc: false,
|
desc: true,
|
||||||
width: 0
|
width: 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the sort order and sort
|
* Set the sort order
|
||||||
* @param {string} predicate Sort predicate
|
* @param {string} predicate Sort predicate
|
||||||
*/
|
*/
|
||||||
_sortOrder(predicate) {
|
_sortOrder(predicate) {
|
||||||
@@ -53,50 +76,51 @@ export default class PowerManagement extends TranslatedComponent {
|
|||||||
desc = true;
|
desc = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._sort(this.props.ship, predicate, desc);
|
|
||||||
this.setState({ predicate, desc });
|
this.setState({ predicate, desc });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sorts the power list
|
* Sorts the power list
|
||||||
* @param {Ship} ship Ship instance
|
* @param {Module[]} modules Modules to sort
|
||||||
* @param {string} predicate Sort predicate
|
* @returns {Module[]} Sorted modules
|
||||||
* @param {Boolean} desc Sort order descending
|
|
||||||
*/
|
*/
|
||||||
_sort(ship, predicate, desc) {
|
_sortAndFilter(modules) {
|
||||||
let powerList = ship.powerList;
|
modules = modules.filter((m) => m.get('powerdraw') >= 0);
|
||||||
let comp = slotComparator.bind(null, this.context.language.translate);
|
let { translate } = this.context.language;
|
||||||
|
const { predicate, desc } = this.state;
|
||||||
|
let comp;
|
||||||
switch (predicate) {
|
switch (predicate) {
|
||||||
case 'n': comp = comp(null, desc); break;
|
case 'n': comp = (a, b) => translate(a.readMeta('type')).localeCompare(
|
||||||
case 't': comp = comp((a, b) => a.type.localeCompare(b.type), desc); break;
|
translate(b.readMeta('type'))
|
||||||
case 'pri': comp = comp((a, b) => a.priority - b.priority, desc); break;
|
); break;
|
||||||
case 'pwr': comp = comp((a, b) => a.m.getPowerUsage() - b.m.getPowerUsage(), desc); break;
|
// case 't': comp = comp((a, b) => a.type.localeCompare(b.type), desc); break;
|
||||||
case 'r': comp = comp((a, b) => ship.getSlotStatus(a) - ship.getSlotStatus(b), desc); break;
|
case 'pri': comp = (a, b) => a.getPowerPriority() - b.getPowerPriority(); break;
|
||||||
case 'd': comp = comp((a, b) => ship.getSlotStatus(a, true) - ship.getSlotStatus(b, true), desc); break;
|
case 'pwr': comp = (a, b) => a.get('powerdraw') - b.get('powerdraw'); break;
|
||||||
|
case 'r': comp = (a, b) => futComp(a.isPowered().retracted, b.isPowered().retracted); break;
|
||||||
|
case 'd': comp = (a, b) => futComp(a.isPowered().deployed, b.isPowered().deployed); break;
|
||||||
}
|
}
|
||||||
|
modules.sort(comp);
|
||||||
powerList.sort(comp);
|
if (desc) {
|
||||||
|
modules.reverse();
|
||||||
|
}
|
||||||
|
return modules;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update slot priority
|
* Creates a callback that changes the power priority for the given module
|
||||||
* @param {Object} slot Slot model
|
* based on the given delta.
|
||||||
* @param {number} inc increment / decrement
|
* @param {Module} m Module to set the priority for
|
||||||
|
* @param {Number} delta Delta to set
|
||||||
|
* @returns {Function} Callback
|
||||||
*/
|
*/
|
||||||
_priority(slot, inc) {
|
_prioCb(m, delta) {
|
||||||
if (this.props.ship.setSlotPriority(slot, slot.priority + inc)) {
|
return () => {
|
||||||
this.props.onChange();
|
const prio = m.getPowerPriority();
|
||||||
|
const newPrio = Math.max(0, prio + delta);
|
||||||
|
if (0 <= newPrio) {
|
||||||
|
m.setPowerPriority(newPrio);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle slot active/inactive
|
|
||||||
* @param {Object} slot Slot model
|
|
||||||
*/
|
|
||||||
_toggleEnabled(slot) {
|
|
||||||
this.props.ship.setSlotEnabled(slot, !slot.enabled);
|
|
||||||
this.props.onChange();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -110,37 +134,36 @@ export default class PowerManagement extends TranslatedComponent {
|
|||||||
_renderPowerRows(ship, translate, pwr, pct) {
|
_renderPowerRows(ship, translate, pwr, pct) {
|
||||||
let powerRows = [];
|
let powerRows = [];
|
||||||
|
|
||||||
for (let i = 0, l = ship.powerList.length; i < l; i++) {
|
let modules = this._sortAndFilter(ship.getModules());
|
||||||
let slot = ship.powerList[i];
|
for (let m of modules) {
|
||||||
|
|
||||||
if (slot.m && slot.m.getPowerUsage() > 0) {
|
|
||||||
let m = slot.m;
|
|
||||||
let toggleEnabled = this._toggleEnabled.bind(this, slot);
|
|
||||||
let retractedElem = null, deployedElem = null;
|
let retractedElem = null, deployedElem = null;
|
||||||
|
const flipEnabled = () => m.setEnabled();
|
||||||
if (slot.enabled) {
|
if (m.isEnabled()) {
|
||||||
retractedElem = <td className='ptr upp' onClick={toggleEnabled}>{POWER[ship.getSlotStatus(slot, false)]}</td>;
|
let powered = m.isPowered();
|
||||||
deployedElem = <td className='ptr upp' onClick={toggleEnabled}>{POWER[ship.getSlotStatus(slot, true)]}</td>;
|
retractedElem = <td className='ptr upp' onClick={flipEnabled}>{getPowerIcon(powered.retracted)}</td>;
|
||||||
|
deployedElem = <td className='ptr upp' onClick={flipEnabled}>{getPowerIcon(powered.deployed)}</td>;
|
||||||
} else {
|
} else {
|
||||||
retractedElem = <td className='ptr disabled upp' colSpan='2' onClick={toggleEnabled}>{translate('disabled')}</td>;
|
retractedElem = <td className='ptr disabled upp' colSpan='2' onClick={flipEnabled}>{translate('disabled')}</td>;
|
||||||
}
|
}
|
||||||
|
|
||||||
powerRows.push(<tr key={i} className={cn('highlight', { disabled: !slot.enabled })}>
|
const slot = m.getSlot();
|
||||||
<td className='ptr' style={{ width: '1em' }} onClick={toggleEnabled}>{m.class + m.rating}</td>
|
powerRows.push(<tr key={slot} className={cn('highlight', { disabled: !m.isEnabled() })}>
|
||||||
<td className='ptr le shorten cap' onClick={toggleEnabled}>{slotName(translate, slot)}</td>
|
<td className='ptr' style={{ width: '1em' }} onClick={flipEnabled}>{String(m.getClass()) + m.getRating()}</td>
|
||||||
<td className='ptr' onClick={toggleEnabled}><u>{translate(slot.type)}</u></td>
|
<td className='ptr le shorten cap' onClick={flipEnabled}>{translate(m.readMeta('type'))}</td>
|
||||||
|
{/* <td className='ptr' onClick={flipEnabled}><u>{translate(slot.type)}</u></td> */}
|
||||||
<td>
|
<td>
|
||||||
<span className='flip ptr btn' onClick={this._priority.bind(this, slot, -1)}>►</span>
|
<span className='flip ptr btn' onClick={this._prioCb(m, -1)}>►</span>
|
||||||
{' ' + (slot.priority + 1) + ' '}
|
{' ' + (m.getPowerPriority() + 1) + ' '}
|
||||||
<span className='ptr btn' onClick={this._priority.bind(this, slot, 1)}>►</span>
|
<span className='ptr btn' onClick={this._prioCb(m, 1)}>►</span>
|
||||||
|
</td>
|
||||||
|
<td className='ri ptr' style={{ width: '3.25em' }} onClick={flipEnabled}>{pwr(m.get('powerdraw'))}</td>
|
||||||
|
<td className='ri ptr' style={{ width: '3em' }} onClick={flipEnabled}>
|
||||||
|
<u>{pct(m.get('powerdraw') / ship.getPowerPlant().get('powercapacity'))}</u>
|
||||||
</td>
|
</td>
|
||||||
<td className='ri ptr' style={{ width: '3.25em' }} onClick={toggleEnabled}>{pwr(m.getPowerUsage())}</td>
|
|
||||||
<td className='ri ptr' style={{ width: '3em' }} onClick={toggleEnabled}><u>{pct(m.getPowerUsage() / ship.powerAvailable)}</u></td>
|
|
||||||
{retractedElem}
|
{retractedElem}
|
||||||
{deployedElem}
|
{deployedElem}
|
||||||
</tr>);
|
</tr>);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return powerRows;
|
return powerRows;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,7 +178,6 @@ export default class PowerManagement extends TranslatedComponent {
|
|||||||
* Add listeners when about to mount and sort power list
|
* Add listeners when about to mount and sort power list
|
||||||
*/
|
*/
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
this._sort(this.props.ship, this.state.predicate, this.state.desc);
|
|
||||||
this.resizeListener = this.context.onWindowResize(this._updateWidth);
|
this.resizeListener = this.context.onWindowResize(this._updateWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,17 +188,6 @@ export default class PowerManagement extends TranslatedComponent {
|
|||||||
this._updateWidth();
|
this._updateWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sort power list if the ship instance has changed
|
|
||||||
* @param {Object} nextProps Incoming/Next properties
|
|
||||||
* @param {Object} nextState Incoming/Next state
|
|
||||||
*/
|
|
||||||
componentWillUpdate(nextProps, nextState) {
|
|
||||||
if (this.props.ship != nextProps.ship) {
|
|
||||||
this._sort(nextProps.ship, nextState.predicate, nextState.desc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove listeners on unmount
|
* Remove listeners on unmount
|
||||||
*/
|
*/
|
||||||
@@ -191,39 +202,38 @@ export default class PowerManagement extends TranslatedComponent {
|
|||||||
render() {
|
render() {
|
||||||
let { ship, code } = this.props;
|
let { ship, code } = this.props;
|
||||||
let { translate, formats } = this.context.language;
|
let { translate, formats } = this.context.language;
|
||||||
let pwr = formats.f2;
|
let pp = ship.getPowerPlant();
|
||||||
let pp = ship.standard[0].m;
|
|
||||||
let sortOrder = this._sortOrder;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={node => this.node = node} className='group half' id='componentPriority'>
|
<div ref={node => this.node = node} className='group half' id='componentPriority'>
|
||||||
<table style={{ width: '100%' }}>
|
<table style={{ width: '100%' }}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className='main'>
|
<tr className='main'>
|
||||||
<th colSpan='2' className='sortable le' onClick={sortOrder.bind(this, 'n')} >{translate('module')}</th>
|
<th colSpan='2' className='sortable le' onClick={() => this._sortOrder('n')} >{translate('module')}</th>
|
||||||
<th style={{ width: '3em' }} className='sortable' onClick={sortOrder.bind(this, 't')} >{translate('type')}</th>
|
{/* <th style={{ width: '3em' }} className='sortable' onClick={() => this._sortOrder('t')} >{translate('type')}</th> */}
|
||||||
<th style={{ width: '4em' }} className='sortable' onClick={sortOrder.bind(this, 'pri')} >{translate('pri')}</th>
|
<th style={{ width: '4em' }} className='sortable' onClick={() => this._sortOrder('pri')} >{translate('pri')}</th>
|
||||||
<th colSpan='2' className='sortable' onClick={sortOrder.bind(this, 'pwr')} >{translate('PWR')}</th>
|
<th colSpan='2' className='sortable' onClick={() => this._sortOrder('pwr')} >{translate('PWR')}</th>
|
||||||
<th style={{ width: '3em' }} className='sortable' onClick={sortOrder.bind(this, 'r')} >{translate('ret')}</th>
|
<th style={{ width: '3em' }} className='sortable' onClick={() => this._sortOrder('r')} >{translate('ret')}</th>
|
||||||
<th style={{ width: '3em' }} className='sortable' onClick={sortOrder.bind(this, 'd')} >{translate('dep')}</th>
|
<th style={{ width: '3em' }} className='sortable' onClick={() => this._sortOrder('d')} >{translate('dep')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{pp.class + pp.rating}</td>
|
<td>{String(pp.getClass()) + pp.getRating()}</td>
|
||||||
<td className='le shorten cap' >{translate('pp')}</td>
|
<td className='le shorten cap' >{translate('pp')}</td>
|
||||||
<td><u >{translate('SYS')}</u></td>
|
|
||||||
<td>1</td>
|
<td>1</td>
|
||||||
<td className='ri'>{pwr(pp.getPowerGeneration())}</td>
|
<td className='ri'>{formats.f2(pp.get('powercapacity'))}</td>
|
||||||
<td className='ri'><u>100%</u></td>
|
<td className='ri'><u>100%</u></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr><td style={{ lineHeight:0 }} colSpan='8'><hr style={{ margin: '0 0 3px', background: '#ff8c0d', border: 0, height: 1 }} /></td></tr>
|
<tr><td style={{ lineHeight:0 }} colSpan='8'>
|
||||||
{this._renderPowerRows(ship, translate, pwr, formats.pct1)}
|
<hr style={{ margin: '0 0 3px', background: '#ff8c0d', border: 0, height: 1 }} />
|
||||||
|
</td></tr>
|
||||||
|
{this._renderPowerRows(ship, translate, formats.f2, formats.pct1)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<PowerBands width={this.state.width} code={code} available={pp.getPowerGeneration()} bands={ship.priorityBands} />
|
<PowerBands width={this.state.width} ship={ship} code={code} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import { Rocket } from './SvgIcons';
|
import { Rocket } from './SvgIcons';
|
||||||
import Persist from '../stores/Persist';
|
import Persist from '../stores/Persist';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
|
import { Factory, Ship } from 'ed-forge';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
import { isEqual } from 'lodash';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ship picker
|
* Ship picker
|
||||||
@@ -13,40 +15,49 @@ import cn from 'classnames';
|
|||||||
export default class ShipPicker extends TranslatedComponent {
|
export default class ShipPicker extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
onChange: PropTypes.func.isRequired,
|
onChange: PropTypes.func.isRequired,
|
||||||
ship: PropTypes.string.isRequired,
|
ship: PropTypes.instanceOf(Ship).isRequired,
|
||||||
build: PropTypes.string
|
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
|
||||||
ship: 'eagle'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* constructor
|
* constructor
|
||||||
* @param {object} props Properties react
|
* @param {object} props Properties react
|
||||||
* @param {object} context react context
|
* @param {object} context react context
|
||||||
*/
|
*/
|
||||||
constructor(props, context) { // eslint-disable-line
|
constructor(props, context) {
|
||||||
super(props);
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
this.shipOrder = Object.keys(Ships).sort();
|
this.state = {
|
||||||
this._toggleMenu = this._toggleMenu.bind(this);
|
menuOpen: false,
|
||||||
this._closeMenu = this._closeMenu.bind(this);
|
opponent: {
|
||||||
|
self: true,
|
||||||
this.state = { menuOpen: false };
|
type: props.ship.getShipType(),
|
||||||
|
stock: false,
|
||||||
|
id: undefined,
|
||||||
|
},
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update ship
|
* Update ship
|
||||||
* @param {object} ship the ship
|
* @param {boolean} self True to compare with ship itself
|
||||||
* @param {string} build the build, if present
|
* @param {object} type The ship type
|
||||||
|
* @param {boolean} stock True to compare with a stock version of given type
|
||||||
|
* @param {string} id The build's stored ID
|
||||||
*/
|
*/
|
||||||
_shipChange(ship, build) {
|
_shipChange(self, type, stock = false, id = null) {
|
||||||
this._closeMenu();
|
const opponent = { self, type, stock, id };
|
||||||
|
if (isEqual(opponent, this.state.opponent)) {
|
||||||
// Ensure that the ship has changed
|
this.setState({ menuOpen: false });
|
||||||
if (ship !== this.props.ship || build !== this.props.build) {
|
} else {
|
||||||
this.props.onChange(ship, build);
|
const { onChange } = this.props;
|
||||||
|
if (self) {
|
||||||
|
onChange(this.props.ship);
|
||||||
|
} else if (stock) {
|
||||||
|
onChange(Factory.newShip(type));
|
||||||
|
} else {
|
||||||
|
onChange(new Ship(Persist.getBuild(type, id)));
|
||||||
|
}
|
||||||
|
this.setState({ menuOpen: false, opponent });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,26 +66,41 @@ export default class ShipPicker extends TranslatedComponent {
|
|||||||
* @returns {object} the picker menu
|
* @returns {object} the picker menu
|
||||||
*/
|
*/
|
||||||
_renderPickerMenu() {
|
_renderPickerMenu() {
|
||||||
const { ship, build } = this.props;
|
const { menuOpen } = this.state;
|
||||||
const _shipChange = this._shipChange;
|
if (!menuOpen) {
|
||||||
const builds = Persist.getBuilds();
|
return null;
|
||||||
const buildList = [];
|
|
||||||
for (let shipId of this.shipOrder) {
|
|
||||||
const shipBuilds = [];
|
|
||||||
// Add stock build
|
|
||||||
const stockSelected = (ship == shipId && !build);
|
|
||||||
shipBuilds.push(<li key={shipId} className={ cn({ 'selected': stockSelected })} onClick={_shipChange.bind(this, shipId, null)}>Stock</li>);
|
|
||||||
if (builds[shipId]) {
|
|
||||||
let buildNameOrder = Object.keys(builds[shipId]).sort();
|
|
||||||
for (let buildName of buildNameOrder) {
|
|
||||||
const buildSelected = ship === shipId && build === buildName;
|
|
||||||
shipBuilds.push(<li key={shipId + '-' + buildName} className={ cn({ 'selected': buildSelected })} onClick={_shipChange.bind(this, shipId, buildName)}>{buildName}</li>);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
buildList.push(<ul key={shipId} className='block'>{Ships[shipId].properties.name}{shipBuilds}</ul>);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildList;
|
const { translate } = this.context.language;
|
||||||
|
const { self, type, stock, id } = this.state.opponent;
|
||||||
|
return <div className='menu-list' onClick={(e) => e.stopPropagation()}>
|
||||||
|
<div className='quad'>
|
||||||
|
{Factory.getAllShipTypes().sort().map((shipType) =>
|
||||||
|
<ul key={shipType} className='block'>
|
||||||
|
{translate(shipType)}
|
||||||
|
{/* Add stock build */}
|
||||||
|
<li key={shipType}
|
||||||
|
onClick={this._shipChange.bind(this, false, shipType, true)}
|
||||||
|
className={cn({ selected: stock && type === shipType })}>
|
||||||
|
{translate('stock')}
|
||||||
|
</li>
|
||||||
|
{Persist.getBuildsNamesFor(shipType).sort().map((storedId) =>
|
||||||
|
<li key={`${shipType}-${storedId}`}
|
||||||
|
onClick={this._shipChange.bind(this, false, shipType, false, storedId)}
|
||||||
|
className={ cn({ selected: type === shipType && id === storedId })}>
|
||||||
|
{storedId}
|
||||||
|
</li>)}
|
||||||
|
{/* Add ship itself */}
|
||||||
|
{(this.props.ship.getShipType() === shipType ?
|
||||||
|
<li key='self'
|
||||||
|
onClick={this._shipChange.bind(this, true, shipType)}
|
||||||
|
className={cn({ selected: self })}>
|
||||||
|
{translate('THIS_SHIP')}
|
||||||
|
</li> :
|
||||||
|
null)}
|
||||||
|
</ul>)}
|
||||||
|
</div>
|
||||||
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -85,41 +111,36 @@ export default class ShipPicker extends TranslatedComponent {
|
|||||||
this.setState({ menuOpen: !menuOpen });
|
this.setState({ menuOpen: !menuOpen });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Close the menu
|
|
||||||
*/
|
|
||||||
_closeMenu() {
|
|
||||||
const { menuOpen } = this.state;
|
|
||||||
if (menuOpen) {
|
|
||||||
this._toggleMenu();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render picker
|
* Render picker
|
||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
const { translate } = this.context.language;
|
||||||
const { formats, translate, units } = language;
|
const { ship } = this.props;
|
||||||
const { ship, build } = this.props;
|
|
||||||
const { menuOpen } = this.state;
|
const { menuOpen } = this.state;
|
||||||
|
const { self, type, stock, id } = this.state.opponent;
|
||||||
|
|
||||||
|
let label;
|
||||||
|
if (self) {
|
||||||
|
label = translate('THIS_SHIP');
|
||||||
|
} else if (stock) {
|
||||||
|
label = translate('stock');
|
||||||
|
} else {
|
||||||
|
label = id;
|
||||||
|
}
|
||||||
|
|
||||||
const shipString = ship + ': ' + (build ? build : translate('stock'));
|
|
||||||
return (
|
return (
|
||||||
<div className='shippicker' onClick={ (e) => e.stopPropagation() }>
|
<div className='shippicker' onClick={ (e) => e.stopPropagation() }>
|
||||||
<div className='menu'>
|
<div className='menu'>
|
||||||
<div className={cn('menu-header', { selected: menuOpen })} onClick={this._toggleMenu}>
|
<div className={cn('menu-header', { selected: menuOpen })} onClick={this._toggleMenu}>
|
||||||
<span><Rocket className='warning' /></span>
|
<span><Rocket className='warning' /></span>
|
||||||
<span className='menu-item-label'>{shipString}</span>
|
<span className='menu-item-label'>
|
||||||
|
{`${translate(type)}: ${label}`}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{ menuOpen ?
|
|
||||||
<div className='menu-list' onClick={ (e) => e.stopPropagation() }>
|
|
||||||
<div className='quad'>
|
|
||||||
{this._renderPickerMenu()}
|
{this._renderPickerMenu()}
|
||||||
</div>
|
</div>
|
||||||
</div> : null }
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,24 @@
|
|||||||
|
import autoBind from 'auto-bind';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import { Warning } from './SvgIcons';
|
import { Warning } from './SvgIcons';
|
||||||
import * as Calc from '../shipyard/Calculations';
|
|
||||||
|
import { ShipProps } from 'ed-forge';
|
||||||
|
const {
|
||||||
|
SPEED, BOOST_SPEED, DAMAGE_METRICS, JUMP_METRICS, SHIELD_METRICS,
|
||||||
|
ARMOUR_METRICS, CARGO_CAPACITY, FUEL_CAPACITY, UNLADEN_MASS, MAXIMUM_MASS,
|
||||||
|
MODULE_PROTECTION_METRICS, PASSENGER_CAPACITY
|
||||||
|
} = ShipProps;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ship Summary Table / Stats
|
* Ship Summary Table / Stats
|
||||||
*/
|
*/
|
||||||
export default class ShipSummaryTable extends TranslatedComponent {
|
export default class ShipSummaryTable extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
cargo: PropTypes.number.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
fuel: PropTypes.number.isRequired,
|
|
||||||
marker: PropTypes.string.isRequired,
|
|
||||||
pips: PropTypes.object.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -24,7 +27,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.didContextChange = this.didContextChange.bind(this);
|
autoBind(this);
|
||||||
this.state = {
|
this.state = {
|
||||||
shieldColour: 'blue'
|
shieldColour: 'blue'
|
||||||
};
|
};
|
||||||
@@ -35,43 +38,54 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
|||||||
* @return {React.Component} Summary table
|
* @return {React.Component} Summary table
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { ship, cargo, fuel, pips } = this.props;
|
const { ship } = this.props;
|
||||||
let { language, tooltip, termtip } = this.context;
|
let { language, tooltip, termtip } = this.context;
|
||||||
let translate = language.translate;
|
let translate = language.translate;
|
||||||
let u = language.units;
|
let u = language.units;
|
||||||
let formats = language.formats;
|
let formats = language.formats;
|
||||||
let { time, int, round, f1, f2 } = formats;
|
let { time, int, f1, f2 } = formats;
|
||||||
let hide = tooltip.bind(null, null);
|
let hide = tooltip.bind(null, null);
|
||||||
const shieldGenerator = ship.findInternalByGroup('sg') || ship.findInternalByGroup('psg');
|
|
||||||
const sgClassNames = cn({ warning: shieldGenerator && !ship.shield, muted: !shieldGenerator });
|
const speed = ship.get(SPEED);
|
||||||
const sgTooltip = shieldGenerator ? 'TT_SUMMARY_SHIELDS' : 'TT_SUMMARY_SHIELDS_NONFUNCTIONAL';
|
const shipBoost = ship.get(BOOST_SPEED);
|
||||||
const timeToDrain = Calc.timeToDrainWep(ship, 4);
|
const canThrust = 0 < speed;
|
||||||
const canThrust = ship.canThrust(cargo, ship.fuelCapacity);
|
const canBoost = canThrust && !isNaN(shipBoost);
|
||||||
const speedTooltip = canThrust ? 'TT_SUMMARY_SPEED' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
|
const speedTooltip = canThrust ? 'TT_SUMMARY_SPEED' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
|
||||||
const canBoost = ship.canBoost(cargo, ship.fuelCapacity);
|
|
||||||
const boostTooltip = canBoost ? 'TT_SUMMARY_BOOST' : canThrust ? 'TT_SUMMARY_BOOST_NONFUNCTIONAL' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
|
const 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 sgMetrics = ship.get(SHIELD_METRICS);
|
||||||
const restingHeat = Math.sqrt(((ship.standard[0].m.pgen * ship.standard[0].m.eff) / ship.heatCapacity) / 0.2);
|
const armourMetrics = ship.get(ARMOUR_METRICS);
|
||||||
const armourMetrics = Calc.armourMetrics(ship);
|
const damageMetrics = ship.get(DAMAGE_METRICS);
|
||||||
|
const moduleProtectionMetrics = ship.get(MODULE_PROTECTION_METRICS);
|
||||||
|
const timeToDrain = damageMetrics.timeToDrain[8];
|
||||||
|
|
||||||
|
const shieldGenerator = ship.getShieldGenerator();
|
||||||
|
const sgClassNames = cn({
|
||||||
|
warning: shieldGenerator && !shieldGenerator.isEnabled(),
|
||||||
|
muted: !shieldGenerator,
|
||||||
|
});
|
||||||
|
const sgTooltip = shieldGenerator ? 'TT_SUMMARY_SHIELDS' : 'TT_SUMMARY_SHIELDS_NONFUNCTIONAL';
|
||||||
|
const sgType = shieldGenerator ? shieldGenerator.readMeta('type') : undefined;
|
||||||
let shieldColour = 'blue';
|
let shieldColour = 'blue';
|
||||||
if (shieldGenerator && shieldGenerator.m.grp === 'psg') {
|
switch (sgType) {
|
||||||
shieldColour = 'green';
|
case 'biweaveshieldgen': shieldColour = 'purple'; break;
|
||||||
} else if (shieldGenerator && shieldGenerator.m.grp === 'bsg') {
|
case 'prismaticshieldgen': shieldColour = 'green'; break;
|
||||||
shieldColour = 'purple';
|
|
||||||
}
|
}
|
||||||
this.state = {
|
this.state = { shieldColour };
|
||||||
shieldColour
|
|
||||||
};
|
const jumpRangeMetrics = ship.getMetrics(JUMP_METRICS);
|
||||||
|
// TODO:
|
||||||
|
const canJump = true;
|
||||||
|
|
||||||
return <div id='summary'>
|
return <div id='summary'>
|
||||||
<div style={{display: "table", width: "100%"}}>
|
<div style={{ display: 'table', width: '100%' }}>
|
||||||
<div style={{display: "table-row"}}>
|
<div style={{ display: 'table-row' }}>
|
||||||
<table className={'summaryTable'}>
|
<table className={'summaryTable'}>
|
||||||
<thead>
|
<thead>
|
||||||
<tr className='main'>
|
<tr className='main'>
|
||||||
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canThrust }) }>{translate('speed')}</th>
|
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': speed == 0 }) }>{translate('speed')}</th>
|
||||||
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canBoost }) }>{translate('boost')}</th>
|
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canBoost }) }>{translate('boost')}</th>
|
||||||
<th colSpan={5}>{translate('jump range')}</th>
|
<th colSpan={5} className={ cn({ 'bg-warning-disabled': jumpRangeMetrics.jumpRange == 0 }) }>{translate('jump range')}</th>
|
||||||
<th rowSpan={2}>{translate('shield')}</th>
|
<th rowSpan={2}>{translate('shield')}</th>
|
||||||
<th rowSpan={2}>{translate('integrity')}</th>
|
<th rowSpan={2}>{translate('integrity')}</th>
|
||||||
<th rowSpan={2}>{translate('DPS')}</th>
|
<th rowSpan={2}>{translate('DPS')}</th>
|
||||||
@@ -85,11 +99,11 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
|||||||
<th onMouseEnter={termtip.bind(null, 'hull hardness', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('hrd')}</th>
|
<th onMouseEnter={termtip.bind(null, 'hull hardness', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('hrd')}</th>
|
||||||
<th rowSpan={2}>{translate('crew')}</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, '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 onMouseEnter={termtip.bind(null, 'TT_SUMMARY_BOOST_INTERVAL', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('boost interval')}</th>
|
||||||
<th rowSpan={2}>{translate('resting heat (Beta)')}</th>
|
<th rowSpan={2}>{translate('resting heat (Beta)')}</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th className='lft'>{translate('max')}</th>
|
<th className="lft">{translate('max')}</th>
|
||||||
<th>{translate('unladen')}</th>
|
<th>{translate('unladen')}</th>
|
||||||
<th>{translate('laden')}</th>
|
<th>{translate('laden')}</th>
|
||||||
<th>{translate('total unladen')}</th>
|
<th>{translate('total unladen')}</th>
|
||||||
@@ -101,30 +115,87 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td onMouseEnter={termtip.bind(null, speedTooltip, { cap: 0 })} onMouseLeave={hide}>{ canThrust ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, false))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
<td onMouseEnter={termtip.bind(null, speedTooltip, { cap: 0 })}
|
||||||
<td onMouseEnter={termtip.bind(null, boostTooltip, { cap: 0 })} onMouseLeave={hide}>{ canBoost ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, true))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
onMouseLeave={hide}
|
||||||
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_MAX_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.standard[2].m.getMaxFuelPerJump(), ship.standard[2].m, ship.standard[2].m.getMaxFuelPerJump(), ship))}{u.LY}</span></td>
|
>{canThrust ?
|
||||||
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</span></td>
|
<span>{int(speed)}{u['m/s']}</span> :
|
||||||
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</span></td>
|
<span className='warning'>0<Warning/></span>
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</td>
|
}</td>
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</td>
|
<td onMouseEnter={termtip.bind(null, boostTooltip, { cap: 0 })}
|
||||||
<td className={sgClassNames} onMouseEnter={termtip.bind(null, sgTooltip, { cap: 0 })} onMouseLeave={hide}>{int(ship.shield)}{u.MJ}</td>
|
onMouseLeave={hide}
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_INTEGRITY', { cap: 0 })} onMouseLeave={hide}>{int(ship.armour)}</td>
|
>{canBoost ?
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_DPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalDps)}</td>
|
<span>{int(shipBoost)}{u['m/s']}</span> :
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_EPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalEps)}</td>
|
<span className='warning'>0<Warning/></span>
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_TTD', { cap: 0 })} onMouseLeave={hide}>{timeToDrain === Infinity ? '∞' : time(timeToDrain)}</td>
|
}</td>
|
||||||
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_MAX_SINGLE_JUMP', { cap: 0 })}
|
||||||
|
onMouseLeave={hide}
|
||||||
|
>{canJump ?
|
||||||
|
// TODO:
|
||||||
|
<span>{NaN}{u.LY}</span> :
|
||||||
|
<span className='warning'>0<Warning/></span>
|
||||||
|
}</td>
|
||||||
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_SINGLE_JUMP', { cap: 0 })}
|
||||||
|
onMouseLeave={hide}
|
||||||
|
>{canJump ?
|
||||||
|
// TODO:
|
||||||
|
<span>{NaN}{u.LY}</span> :
|
||||||
|
<span className='warning'>0<Warning/></span>
|
||||||
|
}</td>
|
||||||
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_SINGLE_JUMP', { cap: 0 })}
|
||||||
|
onMouseLeave={hide}
|
||||||
|
>{canJump ?
|
||||||
|
<span>{f2(jumpRangeMetrics.jumpRange)}{u.LY}</span> :
|
||||||
|
<span className='warning'>0<Warning/></span>
|
||||||
|
}</td>
|
||||||
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_TOTAL_JUMP', { cap: 0 })}
|
||||||
|
onMouseLeave={hide}
|
||||||
|
>{canJump ?
|
||||||
|
// TODO:
|
||||||
|
<span>{NaN}{u.LY}</span> :
|
||||||
|
<span className='warning'>0 <Warning/></span>
|
||||||
|
}</td>
|
||||||
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_TOTAL_JUMP', { cap: 0 })}
|
||||||
|
onMouseLeave={hide}
|
||||||
|
>{canJump ?
|
||||||
|
<span>{f2(jumpRangeMetrics.totalRange)}{u.LY}</span> :
|
||||||
|
<span className='warning'>0<Warning/></span>
|
||||||
|
}</td>
|
||||||
|
<td className={sgClassNames}
|
||||||
|
onMouseEnter={termtip.bind(null, sgTooltip, { cap: 0 })}
|
||||||
|
onMouseLeave={hide}
|
||||||
|
>{int(sgMetrics.shieldStrength)}{u.MJ}</td>
|
||||||
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_INTEGRITY', { cap: 0 })}
|
||||||
|
onMouseLeave={hide}
|
||||||
|
>{int(armourMetrics.armour)}</td>
|
||||||
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_DPS', { cap: 0 })}
|
||||||
|
onMouseLeave={hide}
|
||||||
|
>{f1(damageMetrics.dps)}</td>
|
||||||
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_EPS', { cap: 0 })}
|
||||||
|
onMouseLeave={hide}
|
||||||
|
>{f1(damageMetrics.eps)}</td>
|
||||||
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_TTD', { cap: 0 })}
|
||||||
|
onMouseLeave={hide}
|
||||||
|
>{timeToDrain === Infinity ? '∞' : time(timeToDrain)}</td>
|
||||||
{/* <td>{f1(ship.totalHps)}</td> */}
|
{/* <td>{f1(ship.totalHps)}</td> */}
|
||||||
<td>{round(ship.cargoCapacity)}{u.T}</td>
|
<td>{ship.get(CARGO_CAPACITY)}{u.T}</td>
|
||||||
<td>{ship.passengerCapacity}</td>
|
<td>{ship.get(PASSENGER_CAPACITY)}</td>
|
||||||
<td>{round(ship.fuelCapacity)}{u.T}</td>
|
<td>{ship.get(FUEL_CAPACITY)}{u.T}</td>
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_HULL_MASS', { cap: 0 })} onMouseLeave={hide}>{ship.hullMass}{u.T}</td>
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_HULL_MASS', { cap: 0 })}
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.unladenMass)}{u.T}</td>
|
onMouseLeave={hide}
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.ladenMass)}{u.T}</td>
|
>{ship.readProp('hullmass')}{u.T}</td>
|
||||||
<td>{int(ship.hardness)}</td>
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_MASS', { cap: 0 })}
|
||||||
<td>{ship.crew}</td>
|
onMouseLeave={hide}
|
||||||
<td>{ship.masslock}</td>
|
>{int(ship.get(UNLADEN_MASS))}{u.T}</td>
|
||||||
<td>{shipBoost !== 'No Boost' ? formats.time(shipBoost) : 'No Boost'}</td>
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_MASS', { cap: 0 })}
|
||||||
<td>{formats.pct(restingHeat)}</td>
|
onMouseLeave={hide}
|
||||||
|
>{int(ship.get(MAXIMUM_MASS))}{u.T}</td>
|
||||||
|
<td>{int(ship.readProp('hardness'))}</td>
|
||||||
|
<td>{ship.readMeta('crew')}</td>
|
||||||
|
<td>{ship.readProp('masslock')}</td>
|
||||||
|
{/* TODO: boost intervall */}
|
||||||
|
<td>{NaN}</td>
|
||||||
|
{/* TODO: resting heat */}
|
||||||
|
<td>{NaN}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@@ -153,19 +224,19 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{translate(shieldGenerator && shieldGenerator.m.grp || 'No Shield')}</td>
|
<td>{translate(sgType || 'No Shield')}</td>
|
||||||
<td>{formats.pct1(ship.shieldExplRes)}</td>
|
<td>{formats.pct1(1 - sgMetrics.explosive.damageMultiplier)}</td>
|
||||||
<td>{formats.pct1(ship.shieldKinRes)}</td>
|
<td>{formats.pct1(1 - sgMetrics.kinetic.damageMultiplier)}</td>
|
||||||
<td>{formats.pct1(ship.shieldThermRes)}</td>
|
<td>{formats.pct1(1 - sgMetrics.thermal.damageMultiplier)}</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
|
|
||||||
<td>{int(ship && ship.shield > 0 ? ship.shield : 0)}{u.MJ}</td>
|
<td>{int(sgMetrics.shieldStrength || 0)}{u.MJ}</td>
|
||||||
<td>{int(ship && ship.shield > 0 ? ship.shield * ((1 / (1 - (ship.shieldExplRes)))) : 0)}{u.MJ}</td>
|
<td>{int(sgMetrics.shieldStrength / sgMetrics.explosive.damageMultiplier || 0)}{u.MJ}</td>
|
||||||
<td>{int(ship && ship.shield > 0 ? ship.shield * ((1 / (1 - (ship.shieldKinRes)))) : 0)}{u.MJ}</td>
|
<td>{int(sgMetrics.shieldStrength / sgMetrics.kinetic.damageMultiplier || 0)}{u.MJ}</td>
|
||||||
<td>{int(ship && ship.shield > 0 ? ship.shield * ((1 / (1 - (ship.shieldThermRes)))) : 0)}{u.MJ}</td>
|
<td>{int(sgMetrics.shieldStrength / sgMetrics.thermal.damageMultiplier || 0)}{u.MJ}</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
<td>{sgMetrics && sgMetrics.recover === Math.Inf ? translate('Never') : formats.time(sgMetrics.recover)}</td>
|
<td>{formats.time(sgMetrics.recover) || translate('Never')}</td>
|
||||||
<td>{sgMetrics && sgMetrics.recharge === Math.Inf ? translate('Never') : formats.time(sgMetrics.recharge)}</td>
|
<td>{formats.time(sgMetrics.recharge) || translate('Never')}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
<thead>
|
<thead>
|
||||||
@@ -192,19 +263,18 @@ export default class ShipSummaryTable extends TranslatedComponent {
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>{translate(ship && ship.bulkheads && ship.bulkheads.m && ship.bulkheads.m.name || 'No Armour')}</td>
|
<td>{translate(ship.getAlloys().readMeta('type') || 'No Armour')}</td>
|
||||||
<td>{formats.pct1(ship.hullExplRes)}</td>
|
<td>{formats.pct1(1 - armourMetrics.explosive.damageMultiplier)}</td>
|
||||||
<td>{formats.pct1(ship.hullKinRes)}</td>
|
<td>{formats.pct1(1 - armourMetrics.kinetic.damageMultiplier)}</td>
|
||||||
<td>{formats.pct1(ship.hullThermRes)}</td>
|
<td>{formats.pct1(1 - armourMetrics.thermal.damageMultiplier)}</td>
|
||||||
<td>{formats.pct1(ship.hullCausRes)}</td>
|
<td>{formats.pct1(1 - armourMetrics.caustic.damageMultiplier)}</td>
|
||||||
<td>{int(ship.armour)}</td>
|
<td>{int(armourMetrics.armour)}</td>
|
||||||
<td>{int(ship.armour * ((1 / (1 - (ship.hullExplRes)))))}</td>
|
<td>{int(armourMetrics.armour / armourMetrics.explosive.damageMultiplier)}</td>
|
||||||
<td>{int(ship.armour * ((1 / (1 - (ship.hullKinRes)))))}</td>
|
<td>{int(armourMetrics.armour / armourMetrics.kinetic.damageMultiplier)}</td>
|
||||||
<td>{int(ship.armour * ((1 / (1 - (ship.hullThermRes)))))}</td>
|
<td>{int(armourMetrics.armour / armourMetrics.thermal.damageMultiplier)}</td>
|
||||||
<td>{int(ship.armour * ((1 / (1 - (ship.hullCausRes)))))}</td>
|
<td>{int(armourMetrics.armour / armourMetrics.caustic.damageMultiplier)}</td>
|
||||||
<td>{int(armourMetrics.modulearmour)}</td>
|
<td>{int(moduleProtectionMetrics.moduleArmour)}</td>
|
||||||
<td>{int(armourMetrics.moduleprotection * 100) + '%'}</td>
|
<td>{formats.pct1(1 - moduleProtectionMetrics.moduleProtection)}</td>
|
||||||
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
|
||||||
const MARGIN_LR = 8; // Left/ Right margin
|
const MARGIN_LR = 8; // Left/ Right margin
|
||||||
|
|
||||||
@@ -7,7 +8,6 @@ const MARGIN_LR = 8; // Left/ Right margin
|
|||||||
* Horizontal Slider
|
* Horizontal Slider
|
||||||
*/
|
*/
|
||||||
export default class Slider extends React.Component {
|
export default class Slider extends React.Component {
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
axis: false,
|
axis: false,
|
||||||
min: 0,
|
min: 0,
|
||||||
@@ -32,16 +32,7 @@ export default class Slider extends React.Component {
|
|||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this._down = this._down.bind(this);
|
autoBind(this);
|
||||||
this._move = this._move.bind(this);
|
|
||||||
this._up = this._up.bind(this);
|
|
||||||
this._keyup = this._keyup.bind(this);
|
|
||||||
this._keydown = this._keydown.bind(this);
|
|
||||||
this._touchstart = this._touchstart.bind(this);
|
|
||||||
this._touchend = this._touchend.bind(this);
|
|
||||||
|
|
||||||
this._updatePercent = this._updatePercent.bind(this);
|
|
||||||
this._updateDimensions = this._updateDimensions.bind(this);
|
|
||||||
|
|
||||||
this.state = { width: 0 };
|
this.state = { width: 0 };
|
||||||
}
|
}
|
||||||
@@ -55,7 +46,6 @@ export default class Slider extends React.Component {
|
|||||||
this.left = rect.left;
|
this.left = rect.left;
|
||||||
this.width = rect.width;
|
this.width = rect.width;
|
||||||
this._move(event);
|
this._move(event);
|
||||||
this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -75,70 +65,11 @@ export default class Slider extends React.Component {
|
|||||||
* @param {Event} event DOM Event
|
* @param {Event} event DOM Event
|
||||||
*/
|
*/
|
||||||
_up(event) {
|
_up(event) {
|
||||||
this.sliderInputBox.sliderVal.focus();
|
|
||||||
clearTimeout(this.touchStartTimer);
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.left = null;
|
this.left = null;
|
||||||
this.width = null;
|
this.width = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Key up handler for keyboard.
|
|
||||||
* display the number field then set focus to it
|
|
||||||
* when "Enter" key is pressed
|
|
||||||
* @param {Event} event Keyboard event
|
|
||||||
*/
|
|
||||||
_keyup(event) {
|
|
||||||
switch (event.key) {
|
|
||||||
case 'Enter':
|
|
||||||
event.preventDefault();
|
|
||||||
this.sliderInputBox._setDisplay('block');
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Key down handler
|
|
||||||
* increment slider position by +/- 1 when right/left arrow key is pressed or held
|
|
||||||
* @param {Event} event Keyboard even
|
|
||||||
*/
|
|
||||||
_keydown(event) {
|
|
||||||
let newVal = this.props.percent * this.props.max;
|
|
||||||
switch (event.key) {
|
|
||||||
case 'ArrowRight':
|
|
||||||
newVal += 1;
|
|
||||||
if (newVal <= this.props.max) this.props.onChange(newVal / this.props.max);
|
|
||||||
return;
|
|
||||||
case 'ArrowLeft':
|
|
||||||
newVal -= 1;
|
|
||||||
if (newVal >= 0) this.props.onChange(newVal / this.props.max);
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Touch start handler
|
|
||||||
* @param {Event} event DOM Event
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
_touchstart(event) {
|
|
||||||
this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Touch end handler
|
|
||||||
* @param {Event} event DOM Event
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
_touchend(event) {
|
|
||||||
this.sliderInputBox.sliderVal.focus();
|
|
||||||
clearTimeout(this.touchStartTimer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if the user is still dragging
|
* Determine if the user is still dragging
|
||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
@@ -213,7 +144,7 @@ export default class Slider extends React.Component {
|
|||||||
let width = outerWidth - (margin * 2);
|
let width = outerWidth - (margin * 2);
|
||||||
let pctPos = width * this.props.percent;
|
let pctPos = width * this.props.percent;
|
||||||
return <div><svg
|
return <div><svg
|
||||||
onMouseUp={this._up} onMouseEnter={this._enter.bind(this)} onMouseMove={this._move} onKeyUp={this._keyup} onKeyDown={this._keydown} style={style} ref={node => this.node = node} tabIndex="0">
|
onMouseUp={this._up} onMouseEnter={this._enter.bind(this)} onMouseMove={this._move} style={style} ref={node => this.node = node} tabIndex="0">
|
||||||
<rect className='primary' style={{ opacity: 0.3 }} x={margin} y='0.25em' rx='0.3em' ry='0.3em' width={width} height='0.7em' />
|
<rect className='primary' style={{ opacity: 0.3 }} x={margin} y='0.25em' rx='0.3em' ry='0.3em' width={width} height='0.7em' />
|
||||||
<rect className='primary-disabled' x={margin} y='0.45em' rx='0.15em' ry='0.15em' width={pctPos} height='0.3em' />
|
<rect className='primary-disabled' x={margin} y='0.45em' rx='0.15em' ry='0.15em' width={pctPos} height='0.3em' />
|
||||||
<circle className='primary' r={margin} cy='0.6em' cx={pctPos + margin} />
|
<circle className='primary' r={margin} cy='0.6em' cx={pctPos + margin} />
|
||||||
@@ -224,163 +155,6 @@ export default class Slider extends React.Component {
|
|||||||
<text className='primary-disabled' y='3em' x='100%' style={{ textAnchor: 'end' }}>{max + axisUnit}</text>
|
<text className='primary-disabled' y='3em' x='100%' style={{ textAnchor: 'end' }}>{max + axisUnit}</text>
|
||||||
</g>}
|
</g>}
|
||||||
</svg>
|
</svg>
|
||||||
<TextInputBox ref={(tb) => this.sliderInputBox = tb}
|
|
||||||
onChange={this.props.onChange}
|
|
||||||
percent={this.props.percent}
|
|
||||||
axisUnit={this.props.axisUnit}
|
|
||||||
scale={this.props.scale}
|
|
||||||
max={this.props.max}
|
|
||||||
/>
|
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* New component to add keyboard support for sliders - works on all devices (desktop, iOS, Android)
|
|
||||||
**/
|
|
||||||
class TextInputBox extends React.Component {
|
|
||||||
static propTypes = {
|
|
||||||
axisUnit: PropTypes.string,// units (T, M, etc.)
|
|
||||||
max: PropTypes.number,
|
|
||||||
onChange: PropTypes.func.isRequired,// function which determins percent value
|
|
||||||
percent: PropTypes.number.isRequired,// value of slider
|
|
||||||
scale: PropTypes.number
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Determine if the user is still dragging
|
|
||||||
* @param {Object} props React Component properties
|
|
||||||
*/
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this._handleFocus = this._handleFocus.bind(this);
|
|
||||||
this._handleBlur = this._handleBlur.bind(this);
|
|
||||||
this._handleChange = this._handleChange.bind(this);
|
|
||||||
this._keyup = this._keyup.bind(this);
|
|
||||||
this.state = this._getInitialState();
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Update input value if slider changes will change props/state
|
|
||||||
* @param {Object} nextProps React Component properites
|
|
||||||
* @param {Object} nextState React Component state values
|
|
||||||
*/
|
|
||||||
componentWillReceiveProps(nextProps, nextState) {
|
|
||||||
let nextValue = nextProps.percent * nextProps.max;
|
|
||||||
// See https://stackoverflow.com/questions/32414308/updating-state-on-props-change-in-react-form
|
|
||||||
if (nextValue !== this.state.inputValue && nextValue <= nextProps.max) {
|
|
||||||
this.setState({ inputValue: nextValue });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Update slider textbox visibility/values if changes are made to slider
|
|
||||||
* @param {Object} prevProps React Component properites
|
|
||||||
* @param {Object} prevState React Component state values
|
|
||||||
*/
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
|
||||||
if (prevState.divStyle.display == 'none' && this.state.divStyle.display == 'block') {
|
|
||||||
this.enterTimer = setTimeout(() => this.sliderVal.focus(), 10);
|
|
||||||
}
|
|
||||||
if (prevProps.max !== this.props.max && this.state.inputValue > this.props.max) {
|
|
||||||
// they chose a different module
|
|
||||||
this.setState({ inputValue: this.props.max });
|
|
||||||
}
|
|
||||||
if (this.state.inputValue != prevState.inputValue && prevProps.max == this.props.max) {
|
|
||||||
this.props.onChange(this.state.inputValue / this.props.max);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Set initial state for the textbox.
|
|
||||||
* We may want to rethink this to
|
|
||||||
* try and make it a stateless component
|
|
||||||
* @returns {object} React state object with initial values set
|
|
||||||
*/
|
|
||||||
_getInitialState() {
|
|
||||||
return {
|
|
||||||
divStyle: { display:'none' },
|
|
||||||
inputStyle: { width:'4em' },
|
|
||||||
labelStyle: { marginLeft: '.1em' },
|
|
||||||
maxLength:5,
|
|
||||||
size:5,
|
|
||||||
min:0,
|
|
||||||
tabIndex:-1,
|
|
||||||
type:'number',
|
|
||||||
readOnly: true,
|
|
||||||
inputValue: this.props.percent * this.props.max
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {string} val block or none
|
|
||||||
*/
|
|
||||||
_setDisplay(val) {
|
|
||||||
this.setState({
|
|
||||||
divStyle: { display:val }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Update the input value
|
|
||||||
* when textbox gets focus
|
|
||||||
*/
|
|
||||||
_handleFocus() {
|
|
||||||
this.setState({
|
|
||||||
inputValue:this._getValue()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Update inputValue when textbox loses focus
|
|
||||||
*/
|
|
||||||
_handleBlur() {
|
|
||||||
this._setDisplay('none');
|
|
||||||
if (this.state.inputValue !== '') {
|
|
||||||
this.props.onChange(this.state.inputValue / this.props.max);
|
|
||||||
} else {
|
|
||||||
this.setState({
|
|
||||||
inputValue: this.props.percent * this.props.max
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Get the value in the text box
|
|
||||||
* @returns {number} inputValue Value of the input box
|
|
||||||
*/
|
|
||||||
_getValue() {
|
|
||||||
return this.state.inputValue;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Update and set limits on input box
|
|
||||||
* values depending on what user
|
|
||||||
* has selected
|
|
||||||
*
|
|
||||||
* @param {SyntheticEvent} event ReactJs onChange event
|
|
||||||
*/
|
|
||||||
_handleChange(event) {
|
|
||||||
if (event.target.value < 0) {
|
|
||||||
this.setState({ inputValue: 0 });
|
|
||||||
} else if (event.target.value <= this.props.max) {
|
|
||||||
this.setState({ inputValue: event.target.value });
|
|
||||||
} else {
|
|
||||||
this.setState({ inputValue: this.props.max });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Key up handler for input field.
|
|
||||||
* If user hits Enter key, blur/close the input field
|
|
||||||
* @param {Event} event Keyboard event
|
|
||||||
*/
|
|
||||||
_keyup(event) {
|
|
||||||
switch (event.key) {
|
|
||||||
case 'Enter':
|
|
||||||
this.sliderVal.blur();
|
|
||||||
return;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Get the value in the text box
|
|
||||||
* @return {React.Component} Text Input component for Slider
|
|
||||||
*/
|
|
||||||
render() {
|
|
||||||
let { axisUnit, onChange, percent, scale } = this.props;
|
|
||||||
return <div style={this.state.divStyle}><input style={this.state.inputStyle} value={this._getValue()} min={this.state.min} max={this.props.max} onChange={this._handleChange} onKeyUp={this._keyup} tabIndex={this.state.tabIndex} maxLength={this.state.maxLength} size={this.state.size} onBlur={() => {this._handleBlur();}} onFocus={() => {this._handleFocus();}} type={this.state.type} ref={(ip) => this.sliderVal = ip}/><text className="primary upp" style={this.state.labelStyle}>{this.props.axisUnit}</text></div>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,30 +2,38 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
|
import { ListModifications, Modified } from './SvgIcons';
|
||||||
import AvailableModulesMenu from './AvailableModulesMenu';
|
import AvailableModulesMenu from './AvailableModulesMenu';
|
||||||
import ModificationsMenu from './ModificationsMenu';
|
import ModificationsMenu from './ModificationsMenu';
|
||||||
import { diffDetails } from '../utils/SlotFunctions';
|
import { diffDetails } from '../utils/SlotFunctions';
|
||||||
import { wrapCtxMenu } from '../utils/UtilityFunctions';
|
import { stopCtxPropagation, wrapCtxMenu } from '../utils/UtilityFunctions';
|
||||||
|
import { blueprintTooltip } from '../utils/BlueprintFunctions';
|
||||||
|
import { Module } from 'ed-forge';
|
||||||
|
import { TYPES } from 'ed-forge/lib/data/slots';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
import { toPairs } from 'lodash';
|
||||||
|
|
||||||
|
const HARDPOINT_SLOT_LABELS = {
|
||||||
|
1: 'S',
|
||||||
|
2: 'M',
|
||||||
|
3: 'L',
|
||||||
|
4: 'H',
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract Slot
|
* Abstract Slot
|
||||||
*/
|
*/
|
||||||
export default class Slot extends TranslatedComponent {
|
export default class Slot extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
availableModules: PropTypes.func.isRequired,
|
currentMenu: PropTypes.any,
|
||||||
onSelect: PropTypes.func.isRequired,
|
hideSearch: PropTypes.bool,
|
||||||
onOpen: PropTypes.func.isRequired,
|
m: PropTypes.instanceOf(Module),
|
||||||
maxClass: PropTypes.number.isRequired,
|
|
||||||
selected: PropTypes.bool,
|
|
||||||
m: PropTypes.object,
|
|
||||||
enabled: PropTypes.bool.isRequired,
|
|
||||||
ship: PropTypes.object.isRequired,
|
|
||||||
eligible: PropTypes.object,
|
|
||||||
warning: PropTypes.func,
|
warning: PropTypes.func,
|
||||||
drag: PropTypes.func,
|
drag: PropTypes.func,
|
||||||
drop: PropTypes.func,
|
drop: PropTypes.func,
|
||||||
dropClass: PropTypes.string
|
dropClass: PropTypes.string,
|
||||||
|
propsToShow: PropTypes.object.isRequired,
|
||||||
|
onPropToggle: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,25 +42,122 @@ export default class Slot extends TranslatedComponent {
|
|||||||
*/
|
*/
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
autoBind(this);
|
||||||
this._modificationsSelected = false;
|
this.state = { menuIndex: 0 };
|
||||||
|
|
||||||
this._contextMenu = wrapCtxMenu(this._contextMenu.bind(this));
|
|
||||||
this._getMaxClassLabel = this._getMaxClassLabel.bind(this);
|
|
||||||
this._keyDown = this._keyDown.bind(this);
|
|
||||||
this.slotDiv = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Must be implemented by subclasses:
|
/**
|
||||||
// _getSlotDetails()
|
* Opens a menu while setting state.
|
||||||
|
* @param {Object} newMenuIndex New menu index
|
||||||
|
* @param {Event} event Event object
|
||||||
|
*/
|
||||||
|
_openMenu(newMenuIndex, event) {
|
||||||
|
const slotName = this.props.m.getSlot();
|
||||||
|
if (
|
||||||
|
this.props.currentMenu === slotName &&
|
||||||
|
newMenuIndex === this.state.menuIndex
|
||||||
|
) {
|
||||||
|
this.context.closeMenu();
|
||||||
|
} {
|
||||||
|
this.setState({ menuIndex: newMenuIndex });
|
||||||
|
this.context.openMenu(slotName);
|
||||||
|
}
|
||||||
|
// If we don't stop event propagation, the underlying divs also might
|
||||||
|
// get clicked which would open up other menus
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the CSS class name for the slot. Can/should be overriden
|
* Generate the slot contents
|
||||||
* as necessary.
|
* @return {React.Component} Slot contents
|
||||||
* @return {string} CSS Class name
|
|
||||||
*/
|
*/
|
||||||
_getClassNames() {
|
_getSlotDetails() {
|
||||||
|
const { m, propsToShow } = this.props;
|
||||||
|
let { termtip, tooltip, language } = this.context;
|
||||||
|
const { translate, units, formats } = language;
|
||||||
|
|
||||||
|
if (m.isEmpty()) {
|
||||||
|
return <div className="empty">
|
||||||
|
{translate(
|
||||||
|
m.isOnSlot(TYPES.MILITARY) ? 'emptyrestricted' : 'empty'
|
||||||
|
)}
|
||||||
|
</div>;
|
||||||
|
} else {
|
||||||
|
let classRating = m.getClassRating();
|
||||||
|
let { drag, drop } = this.props;
|
||||||
|
|
||||||
|
// Modifications tooltip shows blueprint and grade, if available
|
||||||
|
let modTT = translate('modified');
|
||||||
|
const blueprint = m.getBlueprint();
|
||||||
|
const experimental = m.getExperimental();
|
||||||
|
const grade = m.getBlueprintGrade();
|
||||||
|
if (blueprint) {
|
||||||
|
modTT = `${translate(blueprint)} ${translate('grade')}: ${grade}`;
|
||||||
|
if (experimental) {
|
||||||
|
modTT += `, ${translate(experimental)}`;
|
||||||
|
}
|
||||||
|
modTT = (
|
||||||
|
<div>
|
||||||
|
<div>{modTT}</div>
|
||||||
|
{blueprintTooltip(language, m)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mass = m.get('mass') || m.get('cargo') || m.get('fuel') || 0;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn('details', { disabled: !m.isEnabled() })}
|
||||||
|
draggable="true"
|
||||||
|
onDragStart={drag}
|
||||||
|
onDragEnd={drop}
|
||||||
|
>
|
||||||
|
<div className={'cb'}>
|
||||||
|
<div className={'l'}>
|
||||||
|
{classRating} {translate(m.readMeta('type'))}
|
||||||
|
{blueprint && (
|
||||||
|
<span
|
||||||
|
onMouseOver={termtip.bind(null, modTT)}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>
|
||||||
|
<Modified />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{propsToShow.mass ?
|
||||||
|
<div className={'r'}>
|
||||||
|
{formats.round(mass)}
|
||||||
|
{units.T}
|
||||||
|
</div> : null}
|
||||||
|
</div>
|
||||||
|
<div className={'cb'}>
|
||||||
|
{toPairs(propsToShow).sort().map(([prop, show]) => {
|
||||||
|
const { unit, value } = m.getFormatted(prop, true);
|
||||||
|
// Don't show mass again; it's already shown on the top right
|
||||||
|
// corner of a slot
|
||||||
|
if (!show || isNaN(value) || prop == 'mass') {
|
||||||
return null;
|
return null;
|
||||||
|
} else {
|
||||||
|
return (<div className='l'>
|
||||||
|
{translate(prop)}: {formats.round(value)}{unit}
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
{(m.getApplicableBlueprints() || []).length > 0 ? (
|
||||||
|
<div className="r">
|
||||||
|
<button onClick={this._openMenu.bind(this, 1)}
|
||||||
|
onContextMenu={stopCtxPropagation}
|
||||||
|
onMouseOver={termtip.bind(null, translate('modifications'))}
|
||||||
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
|
>
|
||||||
|
<ListModifications />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,7 +166,19 @@ export default class Slot extends TranslatedComponent {
|
|||||||
* @return {string} label
|
* @return {string} label
|
||||||
*/
|
*/
|
||||||
_getMaxClassLabel() {
|
_getMaxClassLabel() {
|
||||||
return this.props.maxClass;
|
const { m } = this.props;
|
||||||
|
let size = m.getSize();
|
||||||
|
switch (true) {
|
||||||
|
case m.getSlot() === 'armour':
|
||||||
|
return '';
|
||||||
|
case size === 0:
|
||||||
|
// This can also happen for armour but that case was handled above
|
||||||
|
return 'U';
|
||||||
|
case m.isOnSlot(TYPES.HARDPOINT):
|
||||||
|
return HARDPOINT_SLOT_LABELS[size];
|
||||||
|
default:
|
||||||
|
return size;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,25 +188,15 @@ export default class Slot extends TranslatedComponent {
|
|||||||
_contextMenu(event) {
|
_contextMenu(event) {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.props.onSelect(null,null);
|
const { m } = this.props;
|
||||||
|
m.reset();
|
||||||
|
if (this.props.currentMenu === m.getSlot()) {
|
||||||
|
this.context.closeMenu();
|
||||||
|
} else {
|
||||||
|
this.forceUpdate();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Key Down handler
|
|
||||||
* @param {SyntheticEvent} event Event
|
|
||||||
* ToDo: see if this can be moved up
|
|
||||||
* we do more or less the same thing
|
|
||||||
* in every section when Enter key is pressed
|
|
||||||
* on a focusable item
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
_keyDown(event) {
|
|
||||||
if (event.key == 'Enter') {
|
|
||||||
if(event.target.className == 'r') {
|
|
||||||
this._toggleModifications();
|
|
||||||
}
|
|
||||||
this.props.onOpen(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
/**
|
||||||
* Render the slot
|
* Render the slot
|
||||||
* @return {React.Component} The slot
|
* @return {React.Component} The slot
|
||||||
@@ -97,64 +204,42 @@ export default class Slot extends TranslatedComponent {
|
|||||||
render() {
|
render() {
|
||||||
let language = this.context.language;
|
let language = this.context.language;
|
||||||
let translate = language.translate;
|
let translate = language.translate;
|
||||||
let { ship, m, enabled, dropClass, dragOver, onOpen, onChange, selected, eligible, onSelect, warning, availableModules } = this.props;
|
let {
|
||||||
let slotDetails, modificationsMarker, menu;
|
currentMenu, m, dropClass, dragOver, warning, hideSearch, propsToShow,
|
||||||
|
onPropToggle,
|
||||||
if (!selected) {
|
} = this.props;
|
||||||
// If not selected then sure that modifications flag is unset
|
const { menuIndex } = this.state;
|
||||||
this._modificationsSelected = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m) {
|
|
||||||
slotDetails = this._getSlotDetails(m, enabled, translate, language.formats, language.units); // Must be implemented by sub classes
|
|
||||||
modificationsMarker = JSON.stringify(m);
|
|
||||||
} else {
|
|
||||||
slotDetails = <div className={'empty'}>{translate(eligible ? 'emptyrestricted' : 'empty')}</div>;
|
|
||||||
modificationsMarker = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selected) {
|
|
||||||
if (this._modificationsSelected) {
|
|
||||||
menu = <ModificationsMenu
|
|
||||||
className={this._getClassNames()}
|
|
||||||
onChange={onChange}
|
|
||||||
ship={ship}
|
|
||||||
m={m}
|
|
||||||
marker={modificationsMarker}
|
|
||||||
modButton = {this.modButton}
|
|
||||||
/>;
|
|
||||||
} else {
|
|
||||||
menu = <AvailableModulesMenu
|
|
||||||
className={this._getClassNames()}
|
|
||||||
modules={availableModules()}
|
|
||||||
shipMass={ship.hullMass}
|
|
||||||
m={m}
|
|
||||||
onSelect={onSelect}
|
|
||||||
warning={warning}
|
|
||||||
diffDetails={diffDetails.bind(ship, this.context.language)}
|
|
||||||
slotDiv = {this.slotDiv}
|
|
||||||
/>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: implement touch dragging
|
// TODO: implement touch dragging
|
||||||
|
const selected = currentMenu === m.getSlot();
|
||||||
return (
|
return (
|
||||||
<div className={cn('slot', dropClass, { selected })} onClick={onOpen} onKeyDown={this._keyDown} onContextMenu={this._contextMenu} onDragOver={dragOver} tabIndex="0" ref={slotDiv => this.slotDiv = slotDiv}>
|
<div
|
||||||
<div className='details-container'>
|
className={cn('slot', dropClass, { selected })}
|
||||||
<div className='sz'>{this._getMaxClassLabel(translate)}</div>
|
onContextMenu={this._contextMenu}
|
||||||
{slotDetails}
|
onDragOver={dragOver} tabIndex="0"
|
||||||
|
onClick={this._openMenu.bind(this, 0)}
|
||||||
|
>
|
||||||
|
<div className={cn(
|
||||||
|
'details-container',
|
||||||
|
{ warning: warning && warning(m) },
|
||||||
|
)}>
|
||||||
|
<div className="sz">{this._getMaxClassLabel(translate)}</div>
|
||||||
|
{this._getSlotDetails()}
|
||||||
</div>
|
</div>
|
||||||
{menu}
|
{selected && menuIndex === 0 &&
|
||||||
|
<AvailableModulesMenu
|
||||||
|
m={m} hideSearch={hideSearch}
|
||||||
|
onSelect={(item) => {
|
||||||
|
m.setItem(item);
|
||||||
|
this.context.closeMenu();
|
||||||
|
}}
|
||||||
|
warning={warning}
|
||||||
|
// diffDetails={diffDetails.bind(ship, this.context.language)}
|
||||||
|
/>}
|
||||||
|
{selected && menuIndex === 1 &&
|
||||||
|
<ModificationsMenu m={m} propsToShow={propsToShow}
|
||||||
|
onPropToggle={onPropToggle} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle the modifications flag when selecting the modifications icon
|
|
||||||
*/
|
|
||||||
_toggleModifications() {
|
|
||||||
this._modificationsSelected = !this._modificationsSelected;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,47 +5,33 @@ import { wrapCtxMenu } from '../utils/UtilityFunctions';
|
|||||||
import { canMount } from '../utils/SlotFunctions';
|
import { canMount } from '../utils/SlotFunctions';
|
||||||
import { Equalizer } from '../components/SvgIcons';
|
import { Equalizer } from '../components/SvgIcons';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
|
import { Ship } from 'ed-forge';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
const browser = require('detect-browser');
|
const browser = require('detect-browser');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract Slot Section
|
* Abstract Slot Section
|
||||||
*/
|
*/
|
||||||
export default class SlotSection extends TranslatedComponent {
|
export default class SlotSection extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.instanceOf(Ship),
|
||||||
onChange: PropTypes.func.isRequired,
|
|
||||||
onCargoChange: PropTypes.func.isRequired,
|
|
||||||
onFuelChange: PropTypes.func.isRequired,
|
|
||||||
code: PropTypes.string.isRequired,
|
code: PropTypes.string.isRequired,
|
||||||
togglePwr: PropTypes.func,
|
togglePwr: PropTypes.func,
|
||||||
sectionMenuRefs: PropTypes.object
|
propsToShow: PropTypes.object.isRequired,
|
||||||
|
onPropToggle: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
* @param {Object} context React Component context
|
|
||||||
* @param {string} sectionId Section DOM Id
|
|
||||||
* @param {string} sectionName Section name
|
* @param {string} sectionName Section name
|
||||||
*/
|
*/
|
||||||
constructor(props, context, sectionId, sectionName) {
|
constructor(props, sectionName) {
|
||||||
super(props);
|
super(props);
|
||||||
this.sectionId = sectionId;
|
autoBind(this);
|
||||||
this.sectionName = sectionName;
|
|
||||||
this.ssHeadRef = null;
|
this.sectionName = sectionName;
|
||||||
|
|
||||||
this.sectionRefArr = this.props.sectionMenuRefs[this.sectionId] = [];
|
|
||||||
this.sectionRefArr['selectedRef'] = null;
|
|
||||||
this._getSlots = this._getSlots.bind(this);
|
|
||||||
this._selectModule = this._selectModule.bind(this);
|
|
||||||
this._getSectionMenu = this._getSectionMenu.bind(this);
|
|
||||||
this._contextMenu = this._contextMenu.bind(this);
|
|
||||||
this._drop = this._drop.bind(this);
|
|
||||||
this._dragOverNone = this._dragOverNone.bind(this);
|
|
||||||
this._close = this._close.bind(this);
|
|
||||||
this._keyDown = this._keyDown.bind(this);
|
|
||||||
this._handleSectionFocus = this._handleSectionFocus.bind(this);
|
|
||||||
this.state = {};
|
this.state = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,82 +41,6 @@ export default class SlotSection extends TranslatedComponent {
|
|||||||
// _contextMenu()
|
// _contextMenu()
|
||||||
// componentDidUpdate(prevProps)
|
// componentDidUpdate(prevProps)
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO: May either need to send the function to be triggered when Enter key is pressed, or else
|
|
||||||
* may need a separate keyDown handler for each subclass (StandardSlotSection, HardpointSlotSection, etc.)
|
|
||||||
* ex: _keyDown(_keyDownfn, event)
|
|
||||||
*
|
|
||||||
* @param {SyntheticEvent} event KeyDown event
|
|
||||||
*/
|
|
||||||
_keyDown(event) {
|
|
||||||
if (event.key == 'Enter') {
|
|
||||||
event.stopPropagation();
|
|
||||||
if (event.currentTarget.nodeName === 'H1') {
|
|
||||||
this._openMenu(this.sectionName, event);
|
|
||||||
} else {
|
|
||||||
event.currentTarget.click();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (event.key == 'Tab') {
|
|
||||||
if (event.shiftKey) {
|
|
||||||
if ((event.currentTarget === this.sectionRefArr[this.firstRefId]) && this.sectionRefArr[this.lastRefId]) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.sectionRefArr[this.lastRefId].focus();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if ((event.currentTarget === this.sectionRefArr[this.lastRefId]) && this.sectionRefArr[this.firstRefId]) {
|
|
||||||
event.preventDefault();
|
|
||||||
this.sectionRefArr[this.firstRefId].focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set focus on appropriate Slot Section Menu element
|
|
||||||
* @param {Object} focusPrevProps prevProps for componentDidUpdate() from ...SlotSection.jsx
|
|
||||||
* @param {String} firstRef id of the first ref in ...SlotSection.jsx
|
|
||||||
* @param {String} lastRef id of the last ref in ...SlotSection.jsx
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
_handleSectionFocus(focusPrevProps, firstRef, lastRef) {
|
|
||||||
if (this.selectedRefId !== null && this.sectionRefArr[this.selectedRefId]) {
|
|
||||||
// set focus on the previously selected option for the currently open section menu
|
|
||||||
this.sectionRefArr[this.selectedRefId].focus();
|
|
||||||
} else if (this.sectionRefArr[firstRef] && this.sectionRefArr[firstRef] != null) {
|
|
||||||
// set focus on the first option in the currently open section menu if none have been selected previously
|
|
||||||
this.sectionRefArr[firstRef].focus();
|
|
||||||
} else if (this.props.currentMenu == null && focusPrevProps.currentMenu == this.sectionName && this.sectionRefArr['ssHeadRef']) {
|
|
||||||
// set focus on the section menu header when section menu is closed
|
|
||||||
this.sectionRefArr['ssHeadRef'].focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Open a menu
|
|
||||||
* @param {string} menu Menu name
|
|
||||||
* @param {SyntheticEvent} event Event
|
|
||||||
*/
|
|
||||||
_openMenu(menu, event) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
if (this.props.currentMenu === menu) {
|
|
||||||
menu = null;
|
|
||||||
}
|
|
||||||
this.context.openMenu(menu);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mount/Use the specified module in the slot
|
|
||||||
* @param {Object} slot Slot
|
|
||||||
* @param {Object} m Selected module
|
|
||||||
*/
|
|
||||||
_selectModule(slot, m) {
|
|
||||||
this.props.ship.use(slot, m, false);
|
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Slot Drag Handler
|
* Slot Drag Handler
|
||||||
* @param {object} originSlot Origin slot model
|
* @param {object} originSlot Origin slot model
|
||||||
@@ -195,10 +105,18 @@ export default class SlotSection extends TranslatedComponent {
|
|||||||
if (targetSlot && canMount(this.props.ship, targetSlot, m.grp, m.class)) {
|
if (targetSlot && canMount(this.props.ship, targetSlot, m.grp, m.class)) {
|
||||||
const mCopy = m.clone();
|
const mCopy = m.clone();
|
||||||
this.props.ship.use(targetSlot, mCopy, false);
|
this.props.ship.use(targetSlot, mCopy, false);
|
||||||
|
let experimentalNum = this.props.ship.hardpoints
|
||||||
|
.filter(s => s.m && s.m.experimental).length;
|
||||||
|
// Remove the module on the last slot if we now exceed the number of
|
||||||
|
// experimentals allowed
|
||||||
|
if (m.experimental && 4 < experimentalNum) {
|
||||||
|
this.props.ship.updateStats(originSlot, null, originSlot.m);
|
||||||
|
originSlot.m = null; // Empty the slot
|
||||||
|
originSlot.discountedCost = 0;
|
||||||
|
}
|
||||||
// Copy power info
|
// Copy power info
|
||||||
targetSlot.enabled = originSlot.enabled;
|
targetSlot.enabled = originSlot.enabled;
|
||||||
targetSlot.priority = originSlot.priority;
|
targetSlot.priority = originSlot.priority;
|
||||||
this.props.onChange();
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Store power info
|
// Store power info
|
||||||
@@ -227,7 +145,6 @@ export default class SlotSection extends TranslatedComponent {
|
|||||||
targetSlot.enabled = targetEnabled;
|
targetSlot.enabled = targetEnabled;
|
||||||
targetSlot.priority = targetPriority;
|
targetSlot.priority = targetPriority;
|
||||||
}
|
}
|
||||||
this.props.onChange();
|
|
||||||
this.props.ship
|
this.props.ship
|
||||||
.updatePowerGenerated()
|
.updatePowerGenerated()
|
||||||
.updatePowerUsed()
|
.updatePowerUsed()
|
||||||
@@ -273,6 +190,17 @@ export default class SlotSection extends TranslatedComponent {
|
|||||||
return 'ineligible'; // Cannot be dropped / invalid drop slot
|
return 'ineligible'; // Cannot be dropped / invalid drop slot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_open(newMenu, event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
const { currentMenu } = this.props;
|
||||||
|
if (currentMenu === newMenu) {
|
||||||
|
this.context.closeMenu();
|
||||||
|
} else {
|
||||||
|
this.context.openMenu(newMenu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close current menu
|
* Close current menu
|
||||||
*/
|
*/
|
||||||
@@ -289,14 +217,13 @@ export default class SlotSection extends TranslatedComponent {
|
|||||||
render() {
|
render() {
|
||||||
let translate = this.context.language.translate;
|
let translate = this.context.language.translate;
|
||||||
let sectionMenuOpened = this.props.currentMenu === this.sectionName;
|
let sectionMenuOpened = this.props.currentMenu === this.sectionName;
|
||||||
let open = this._openMenu.bind(this, this.sectionName);
|
|
||||||
let ctx = wrapCtxMenu(this._contextMenu);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div id={this.sectionId} className={'group'} onDragLeave={this._dragOverNone}>
|
<div className="group" onDragLeave={this._dragOverNone}>
|
||||||
<div className={cn('section-menu', { selected: sectionMenuOpened })} onClick={open} onContextMenu={ctx}>
|
<div className={cn('section-menu', { selected: sectionMenuOpened })}
|
||||||
<h1 tabIndex="0" onKeyDown={this._keyDown} ref={ssHead => this.sectionRefArr['ssHeadRef'] = ssHead}>{translate(this.sectionName)} <Equalizer/></h1>
|
onContextMenu={wrapCtxMenu(this._contextMenu)} onClick={this._open.bind(this, this.sectionName)}>
|
||||||
{sectionMenuOpened ? this._getSectionMenu(translate, this.props.ship) : null }
|
<h1 tabIndex="0">{translate(this.sectionName)}<Equalizer/></h1>
|
||||||
|
{sectionMenuOpened && this._getSectionMenu()}
|
||||||
</div>
|
</div>
|
||||||
{this._getSlots()}
|
{this._getSlots()}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,162 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import cn from 'classnames';
|
|
||||||
import Persist from '../stores/Persist';
|
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
|
||||||
import { diffDetails } from '../utils/SlotFunctions';
|
|
||||||
import AvailableModulesMenu from './AvailableModulesMenu';
|
|
||||||
import ModificationsMenu from './ModificationsMenu';
|
|
||||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
|
||||||
import { ListModifications, Modified } from './SvgIcons';
|
|
||||||
import { Modifications } from 'coriolis-data/dist';
|
|
||||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
|
||||||
import { blueprintTooltip } from '../utils/BlueprintFunctions';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Standard Slot
|
|
||||||
*/
|
|
||||||
export default class StandardSlot extends TranslatedComponent {
|
|
||||||
|
|
||||||
static propTypes = {
|
|
||||||
slot: PropTypes.object,
|
|
||||||
modules: PropTypes.array.isRequired,
|
|
||||||
onSelect: PropTypes.func.isRequired,
|
|
||||||
onOpen: PropTypes.func.isRequired,
|
|
||||||
onChange: PropTypes.func.isRequired,
|
|
||||||
ship: PropTypes.object.isRequired,
|
|
||||||
selected: PropTypes.bool,
|
|
||||||
warning: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Construct the slot
|
|
||||||
* @param {object} props Object properties
|
|
||||||
*/
|
|
||||||
constructor(props) {
|
|
||||||
super(props);
|
|
||||||
this._modificationsSelected = false;
|
|
||||||
this._keyDown = this._keyDown.bind(this);
|
|
||||||
this.modButton = null;
|
|
||||||
this.slotDiv = null;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Handle Enter key
|
|
||||||
* @param {SyntheticEvent} event KeyDown event
|
|
||||||
*/
|
|
||||||
_keyDown(event) {
|
|
||||||
if (event.key == 'Enter') {
|
|
||||||
if(event.target.className == 'r') {
|
|
||||||
this._toggleModifications();
|
|
||||||
}
|
|
||||||
this.props.onOpen(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the slot
|
|
||||||
* @return {React.Component} Slot component
|
|
||||||
*/
|
|
||||||
render() {
|
|
||||||
let { termtip, tooltip } = this.context;
|
|
||||||
let { translate, formats, units } = this.context.language;
|
|
||||||
let { modules, slot, selected, warning, onSelect, onChange, ship } = this.props;
|
|
||||||
let m = slot.m;
|
|
||||||
let classRating = m.class + m.rating;
|
|
||||||
let menu;
|
|
||||||
let validMods = m == null || !Modifications.modules[m.grp] ? [] : (Modifications.modules[m.grp].modifications || []);
|
|
||||||
if (m && m.name && m.name === 'Guardian Hybrid Power Plant') {
|
|
||||||
validMods = [];
|
|
||||||
}
|
|
||||||
if (m && m.name && m.name === 'Guardian Power Distributor') {
|
|
||||||
validMods = [];
|
|
||||||
}
|
|
||||||
let showModuleResistances = Persist.showModuleResistances();
|
|
||||||
let mass = m.getMass() || m.cargo || m.fuel || 0;
|
|
||||||
|
|
||||||
// Modifications tooltip shows blueprint and grade, if available
|
|
||||||
let modTT = translate('modified');
|
|
||||||
if (m && m.blueprint && m.blueprint.name) {
|
|
||||||
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
|
|
||||||
if (m.blueprint.special && m.blueprint.special.id >= 0) {
|
|
||||||
modTT += ', ' + translate(m.blueprint.special.name);
|
|
||||||
}
|
|
||||||
modTT = (
|
|
||||||
<div>
|
|
||||||
<div>{modTT}</div>
|
|
||||||
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!selected) {
|
|
||||||
// If not selected then sure that modifications flag is unset
|
|
||||||
this._modificationsSelected = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const modificationsMarker = JSON.stringify(m);
|
|
||||||
|
|
||||||
if (selected) {
|
|
||||||
if (this._modificationsSelected) {
|
|
||||||
menu = <ModificationsMenu
|
|
||||||
className='standard'
|
|
||||||
onChange={onChange}
|
|
||||||
ship={ship}
|
|
||||||
m={m}
|
|
||||||
marker={modificationsMarker}
|
|
||||||
modButton = {this.modButton}
|
|
||||||
/>;
|
|
||||||
} else {
|
|
||||||
menu = <AvailableModulesMenu
|
|
||||||
className='standard'
|
|
||||||
modules={modules}
|
|
||||||
shipMass={ModuleUtils.isShieldGenerator(m.grp) ? ship.hullMass : ship.unladenMass}
|
|
||||||
m={m}
|
|
||||||
onSelect={onSelect}
|
|
||||||
warning={warning}
|
|
||||||
diffDetails={diffDetails.bind(ship, this.context.language)}
|
|
||||||
slotDiv = {this.slotDiv}
|
|
||||||
/>;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen} onKeyDown={this._keyDown} onContextMenu={stopCtxPropagation} tabIndex="0" ref={ slotDiv => this.slotDiv = slotDiv }>
|
|
||||||
<div className={cn('details-container', { warning: warning && warning(slot.m), disabled: m.grp !== 'bh' && !slot.enabled })}>
|
|
||||||
<div className={'sz'}>{m.grp == 'bh' ? m.name.charAt(0) : slot.maxClass}</div>
|
|
||||||
<div>
|
|
||||||
<div className={'l'}>{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(mass)}{units.T}</div>
|
|
||||||
<div/>
|
|
||||||
<div className={'cb'}>
|
|
||||||
{ m.getMinMass() ? <div className='l'>{translate('minimum mass')}: {formats.int(m.getMinMass())}{units.T}</div> : null }
|
|
||||||
{ m.getOptMass() ? <div className='l'>{translate('optimal mass')}: {formats.int(m.getOptMass())}{units.T}</div> : null }
|
|
||||||
{ m.getMaxMass() ? <div className='l'>{translate('max mass')}: {formats.int(m.getMaxMass())}{units.T}</div> : null }
|
|
||||||
{ m.getOptMul() ? <div className='l'>{translate('optimal multiplier')}: {formats.rPct(m.getOptMul())}</div> : null }
|
|
||||||
{ m.getRange() ? <div className='l'>{translate('range', m.grp)}: {formats.f2(m.getRange())}{units.km}</div> : null }
|
|
||||||
{ m.time ? <div className='l'>{translate('time')}: {formats.time(m.time)}</div> : null }
|
|
||||||
{ m.getThermalEfficiency() ? <div className='l'>{translate('efficiency')}: {formats.f2(m.getThermalEfficiency())}</div> : null }
|
|
||||||
{ m.getPowerGeneration() > 0 ? <div className='l'>{translate('pgen')}: {formats.f1(m.getPowerGeneration())}{units.MW}</div> : null }
|
|
||||||
{ m.getMaxFuelPerJump() ? <div className='l'>{translate('max')} {translate('fuel')}: {formats.f1(m.getMaxFuelPerJump())}{units.T}</div> : null }
|
|
||||||
{ m.getWeaponsCapacity() ? <div className='l'>{translate('WEP')}: {formats.f1(m.getWeaponsCapacity())}{units.MJ} / {formats.f1(m.getWeaponsRechargeRate())}{units.MW}</div> : null }
|
|
||||||
{ m.getSystemsCapacity() ? <div className='l'>{translate('SYS')}: {formats.f1(m.getSystemsCapacity())}{units.MJ} / {formats.f1(m.getSystemsRechargeRate())}{units.MW}</div> : null }
|
|
||||||
{ m.getEnginesCapacity() ? <div className='l'>{translate('ENG')}: {formats.f1(m.getEnginesCapacity())}{units.MJ} / {formats.f1(m.getEnginesRechargeRate())}{units.MW}</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 }
|
|
||||||
{ 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>
|
|
||||||
</div>
|
|
||||||
{menu}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle the modifications flag when selecting the modifications icon
|
|
||||||
*/
|
|
||||||
_toggleModifications() {
|
|
||||||
this._modificationsSelected = !this._modificationsSelected;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +1,33 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import SlotSection from './SlotSection';
|
import SlotSection from './SlotSection';
|
||||||
import StandardSlot from './StandardSlot';
|
import Slot from './Slot';
|
||||||
import Module from '../shipyard/Module';
|
import Module from '../shipyard/Module';
|
||||||
import * as ShipRoles from '../shipyard/ShipRoles';
|
import * as ShipRoles from '../shipyard/ShipRoles';
|
||||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
import autoBind from 'auto-bind';
|
||||||
|
import { stopCtxPropagation, moduleGet } from '../utils/UtilityFunctions';
|
||||||
|
import { ShipProps } from 'ed-forge';
|
||||||
|
const { CONSUMED_RETR, LADEN_MASS } = ShipProps;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Standard Slot section
|
* Standard Slot section
|
||||||
*/
|
*/
|
||||||
export default class StandardSlotSection extends SlotSection {
|
export default class StandardSlotSection extends SlotSection {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
* @param {Object} context React Component context
|
* @param {Object} context React Component context
|
||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context, 'standard', 'core internal');
|
super(props, 'core internal');
|
||||||
this._optimizeStandard = this._optimizeStandard.bind(this);
|
autoBind(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 = '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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use the lightest/optimal available standard modules
|
* Use the lightest/optimal available standard modules
|
||||||
*/
|
*/
|
||||||
_optimizeStandard() {
|
_optimizeStandard() {
|
||||||
this.selectedRefId = 'maxjump';
|
|
||||||
this.props.ship.useLightestStandard();
|
this.props.ship.useLightestStandard();
|
||||||
this.props.onChange();
|
|
||||||
this.props.onCargoChange(this.props.ship.cargoCapacity);
|
|
||||||
this.props.onFuelChange(this.props.ship.fuelCapacity);
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,12 +37,7 @@ export default class StandardSlotSection extends SlotSection {
|
|||||||
* @param {integer} bulkheadIndex Bulkhead to use see Constants.BulkheadNames
|
* @param {integer} bulkheadIndex Bulkhead to use see Constants.BulkheadNames
|
||||||
*/
|
*/
|
||||||
_multiPurpose(shielded, bulkheadIndex) {
|
_multiPurpose(shielded, bulkheadIndex) {
|
||||||
this.selectedRefId = 'multipurpose';
|
|
||||||
if (bulkheadIndex === 2) this.selectedRefId = 'combat';
|
|
||||||
ShipRoles.multiPurpose(this.props.ship, shielded, bulkheadIndex);
|
ShipRoles.multiPurpose(this.props.ship, shielded, bulkheadIndex);
|
||||||
this.props.onChange();
|
|
||||||
this.props.onCargoChange(this.props.ship.cargoCapacity);
|
|
||||||
this.props.onFuelChange(this.props.ship.fuelCapacity);
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,132 +46,16 @@ export default class StandardSlotSection extends SlotSection {
|
|||||||
* @param {Boolean} shielded True if shield generator should be included
|
* @param {Boolean} shielded True if shield generator should be included
|
||||||
*/
|
*/
|
||||||
_optimizeCargo(shielded) {
|
_optimizeCargo(shielded) {
|
||||||
this.selectedRefId = 'trader';
|
|
||||||
ShipRoles.trader(this.props.ship, shielded);
|
ShipRoles.trader(this.props.ship, shielded);
|
||||||
this.props.onChange();
|
|
||||||
this.props.onCargoChange(this.props.ship.cargoCapacity);
|
|
||||||
this.props.onFuelChange(this.props.ship.fuelCapacity);
|
|
||||||
this._close();
|
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
|
* Miner Build
|
||||||
* @param {Boolean} shielded True if shield generator should be included
|
* @param {Boolean} shielded True if shield generator should be included
|
||||||
*/
|
*/
|
||||||
_optimizeMiner(shielded) {
|
_optimizeMiner(shielded) {
|
||||||
this.selectedRefId = 'miner';
|
|
||||||
ShipRoles.miner(this.props.ship, shielded);
|
ShipRoles.miner(this.props.ship, shielded);
|
||||||
this.props.onChange();
|
|
||||||
this.props.onCargoChange(this.props.ship.cargoCapacity);
|
|
||||||
this.props.onFuelChange(this.props.ship.fuelCapacity);
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,12 +64,7 @@ export default class StandardSlotSection extends SlotSection {
|
|||||||
* @param {Boolean} planetary True if Planetary Vehicle Hangar (PVH) should be included
|
* @param {Boolean} planetary True if Planetary Vehicle Hangar (PVH) should be included
|
||||||
*/
|
*/
|
||||||
_optimizeExplorer(planetary) {
|
_optimizeExplorer(planetary) {
|
||||||
this.selectedRefId = 'explorer';
|
|
||||||
if (planetary) this.selectedRefId = 'planetary';
|
|
||||||
ShipRoles.explorer(this.props.ship, planetary);
|
ShipRoles.explorer(this.props.ship, planetary);
|
||||||
this.props.onChange();
|
|
||||||
this.props.onCargoChange(this.props.ship.cargoCapacity);
|
|
||||||
this.props.onFuelChange(this.props.ship.fuelCapacity);
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,22 +72,7 @@ export default class StandardSlotSection extends SlotSection {
|
|||||||
* Racer role
|
* Racer role
|
||||||
*/
|
*/
|
||||||
_optimizeRacer() {
|
_optimizeRacer() {
|
||||||
this.selectedRefId = 'racer';
|
|
||||||
ShipRoles.racer(this.props.ship);
|
ShipRoles.racer(this.props.ship);
|
||||||
this.props.onChange();
|
|
||||||
this.props.onCargoChange(this.props.ship.cargoCapacity);
|
|
||||||
this.props.onFuelChange(this.props.ship.fuelCapacity);
|
|
||||||
this._close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use the specified bulkhead
|
|
||||||
* @param {Object} bulkhead Bulkhead module details
|
|
||||||
*/
|
|
||||||
_selectBulkhead(bulkhead) {
|
|
||||||
this.props.ship.useBulkhead(bulkhead.index);
|
|
||||||
this.context.tooltip();
|
|
||||||
this.props.onChange();
|
|
||||||
this._close();
|
this._close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -249,112 +83,48 @@ export default class StandardSlotSection extends SlotSection {
|
|||||||
this._optimizeStandard();
|
this._optimizeStandard();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new slot for a given module.
|
||||||
|
* @param {Module} m Module to create the slot for
|
||||||
|
* @param {function} warning Warning callback
|
||||||
|
* @return {React.Component} Slot component
|
||||||
|
*/
|
||||||
|
_mkSlot(m, warning) {
|
||||||
|
const { currentMenu, propsToShow, onPropToggle } = this.props;
|
||||||
|
return <Slot key={m.getSlot()} m={m} warning={warning} hideSearch={true}
|
||||||
|
currentMenu={currentMenu} propsToShow={propsToShow} onPropToggle={onPropToggle}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate the slot React Components
|
* Generate the slot React Components
|
||||||
* @return {Array} Array of Slots
|
* @return {Array} Array of Slots
|
||||||
*/
|
*/
|
||||||
_getSlots() {
|
_getSlots() {
|
||||||
let { ship, currentMenu, cargo, fuel } = this.props;
|
const { ship } = this.props;
|
||||||
let slots = new Array(8);
|
const fsd = ship.getFSD();
|
||||||
let open = this._openMenu;
|
return [
|
||||||
let select = this._selectModule;
|
this._mkSlot(ship.getAlloys()),
|
||||||
let st = ship.standard;
|
this._mkSlot(
|
||||||
let avail = ship.getAvailableModules().standard;
|
ship.getPowerPlant(),
|
||||||
let bh = ship.bulkheads;
|
(m) => moduleGet(m, 'powercapacity') < ship.get(CONSUMED_RETR),
|
||||||
|
),
|
||||||
slots[0] = <StandardSlot
|
this._mkSlot(
|
||||||
key='bh'
|
ship.getThrusters(),
|
||||||
slot={bh}
|
(m) => moduleGet(m, 'enginemaximalmass') < ship.get(LADEN_MASS),
|
||||||
modules={ship.getAvailableModules().bulkheads}
|
),
|
||||||
onOpen={open.bind(this, bh)}
|
this._mkSlot(fsd),
|
||||||
onSelect={this._selectBulkhead}
|
this._mkSlot(
|
||||||
selected={currentMenu == bh}
|
ship.getPowerDistributor(),
|
||||||
onChange={this.props.onChange}
|
(m) => moduleGet(m, 'enginescapacity') <= ship.readProp('boostenergy'),
|
||||||
ship={ship}
|
),
|
||||||
/>;
|
this._mkSlot(ship.getLifeSupport()),
|
||||||
|
this._mkSlot(ship.getSensors()),
|
||||||
slots[1] = <StandardSlot
|
this._mkSlot(
|
||||||
key='pp'
|
ship.getCoreFuelTank(),
|
||||||
slot={st[0]}
|
(m) => moduleGet(m, 'fuel') < fsd.get('maxfuel')
|
||||||
modules={avail[0]}
|
),
|
||||||
onOpen={open.bind(this, st[0])}
|
];
|
||||||
onSelect={select.bind(this, st[0])}
|
|
||||||
selected={currentMenu == st[0]}
|
|
||||||
onChange={this.props.onChange}
|
|
||||||
ship={ship}
|
|
||||||
warning={m => m instanceof Module ? m.getPowerGeneration() < ship.powerRetracted : m.pgen < ship.powerRetracted}
|
|
||||||
/>;
|
|
||||||
|
|
||||||
slots[2] = <StandardSlot
|
|
||||||
key='th'
|
|
||||||
slot={st[1]}
|
|
||||||
modules={avail[1]}
|
|
||||||
onOpen={open.bind(this, st[1])}
|
|
||||||
onSelect={select.bind(this, st[1])}
|
|
||||||
selected={currentMenu == st[1]}
|
|
||||||
onChange={this.props.onChange}
|
|
||||||
ship={ship}
|
|
||||||
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]}
|
|
||||||
modules={avail[2]}
|
|
||||||
onOpen={open.bind(this, st[2])}
|
|
||||||
onSelect={select.bind(this, st[2])}
|
|
||||||
onChange={this.props.onChange}
|
|
||||||
ship={ship}
|
|
||||||
selected={currentMenu == st[2]}
|
|
||||||
/>;
|
|
||||||
|
|
||||||
slots[4] = <StandardSlot
|
|
||||||
key='ls'
|
|
||||||
slot={st[3]}
|
|
||||||
modules={avail[3]}
|
|
||||||
onOpen={open.bind(this, st[3])}
|
|
||||||
onSelect={select.bind(this, st[3])}
|
|
||||||
onChange={this.props.onChange}
|
|
||||||
ship={ship}
|
|
||||||
selected={currentMenu == st[3]}
|
|
||||||
/>;
|
|
||||||
|
|
||||||
slots[5] = <StandardSlot
|
|
||||||
key='pd'
|
|
||||||
slot={st[4]}
|
|
||||||
modules={avail[4]}
|
|
||||||
onOpen={open.bind(this, st[4])}
|
|
||||||
onSelect={select.bind(this, st[4])}
|
|
||||||
selected={currentMenu == st[4]}
|
|
||||||
onChange={this.props.onChange}
|
|
||||||
ship={ship}
|
|
||||||
warning={m => m instanceof Module ? m.getEnginesCapacity() <= ship.boostEnergy : m.engcap <= ship.boostEnergy}
|
|
||||||
/>;
|
|
||||||
|
|
||||||
slots[6] = <StandardSlot
|
|
||||||
key='ss'
|
|
||||||
slot={st[5]}
|
|
||||||
modules={avail[5]}
|
|
||||||
onOpen={open.bind(this, st[5])}
|
|
||||||
onSelect={select.bind(this, st[5])}
|
|
||||||
selected={currentMenu == st[5]}
|
|
||||||
onChange={this.props.onChange}
|
|
||||||
ship={ship}
|
|
||||||
/>;
|
|
||||||
|
|
||||||
slots[7] = <StandardSlot
|
|
||||||
key='ft'
|
|
||||||
slot={st[6]}
|
|
||||||
modules={avail[6]}
|
|
||||||
onOpen={open.bind(this, st[6])}
|
|
||||||
onSelect={select.bind(this, st[6])}
|
|
||||||
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
|
|
||||||
/>;
|
|
||||||
|
|
||||||
return slots;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -362,38 +132,22 @@ export default class StandardSlotSection extends SlotSection {
|
|||||||
* @param {Function} translate Translate function
|
* @param {Function} translate Translate function
|
||||||
* @return {React.Component} Section menu
|
* @return {React.Component} Section menu
|
||||||
*/
|
*/
|
||||||
_getSectionMenu(translate) {
|
_getSectionMenu() {
|
||||||
let planetaryDisabled = this.props.ship.internal.length < 4;
|
const { translate } = this.context.language;
|
||||||
if (this.state.showDW2 === true) {
|
|
||||||
return this._showDW2Menu(translate);
|
|
||||||
}
|
|
||||||
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
|
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='lc' tabIndex="0" onClick={this._optimizeStandard} onKeyDown={this._keyDown}
|
<li className='lc' tabIndex="0" onClick={this._optimizeStandard}>{translate('Maximize Jump Range')}</li>
|
||||||
ref={smRef => this.sectionRefArr['maxjump'] = smRef}>{translate('Maximize Jump Range')}</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('roles')}</div>
|
<div className='select-group cap'>{translate('roles')}</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='lc' tabIndex="0" onClick={this._multiPurpose.bind(this, false, 0)} onKeyDown={this._keyDown}
|
<li className='lc' tabIndex="0" onClick={this._multiPurpose.bind(this, false, 0)}>{translate('Multi-purpose')}</li>
|
||||||
ref={smRef => this.sectionRefArr['multipurpose'] = smRef}>{translate('Multi-purpose')}</li>
|
<li className='lc' tabIndex="0" onClick={this._multiPurpose.bind(this, true, 2)}>{translate('Combat')}</li>
|
||||||
<li className='lc' tabIndex="0" onClick={this._multiPurpose.bind(this, true, 2)} onKeyDown={this._keyDown}
|
<li className='lc' tabIndex="0" onClick={this._optimizeCargo.bind(this, true)}>{translate('Trader')}</li>
|
||||||
ref={smRef => this.sectionRefArr['combat'] = smRef}>{translate('Combat')}</li>
|
<li className='lc' tabIndex="0" onClick={this._optimizeExplorer.bind(this, false)}>{translate('Explorer')}</li>
|
||||||
<li className='lc' tabIndex="0" onClick={this._optimizeCargo.bind(this, true)} onKeyDown={this._keyDown}
|
<li className='lc' tabIndex="0" onClick={this._optimizeExplorer.bind(this, true)}>{translate('Planetary Explorer')}</li>
|
||||||
ref={smRef => this.sectionRefArr['trader'] = smRef}>{translate('Trader')}</li>
|
<li className='lc' tabIndex="0" onClick={this._optimizeMiner.bind(this, true)}>{translate('Miner')}</li>
|
||||||
<li className='lc' tabIndex="0" onClick={this._optimizeExplorer.bind(this, false)} onKeyDown={this._keyDown}
|
<li className='lc' tabIndex="0" onClick={this._optimizeRacer.bind(this)}>{translate('Racer')}</li>
|
||||||
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>
|
</ul>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import TranslatedComponent from './TranslatedComponent';
|
|||||||
* Document Root Tooltip
|
* Document Root Tooltip
|
||||||
*/
|
*/
|
||||||
export default class Tooltip extends TranslatedComponent {
|
export default class Tooltip extends TranslatedComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
rect: PropTypes.object.isRequired,
|
rect: PropTypes.object.isRequired,
|
||||||
options: PropTypes.object
|
options: PropTypes.object
|
||||||
@@ -127,5 +126,4 @@ export default class Tooltip extends TranslatedComponent {
|
|||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { shallowEqual } from '../utils/UtilityFunctions';
|
|||||||
* Abstract Translated Component
|
* Abstract Translated Component
|
||||||
*/
|
*/
|
||||||
export default class TranslatedComponent extends React.Component {
|
export default class TranslatedComponent extends React.Component {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
language: PropTypes.object.isRequired,
|
language: PropTypes.object.isRequired,
|
||||||
sizeRatio: PropTypes.number.isRequired,
|
sizeRatio: PropTypes.number.isRequired,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import SlotSection from './SlotSection';
|
import SlotSection from './SlotSection';
|
||||||
import HardpointSlot from './HardpointSlot';
|
import Slot from './Slot';
|
||||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility Slot Section
|
* Utility Slot Section
|
||||||
@@ -10,28 +11,16 @@ export default class UtilitySlotSection extends SlotSection {
|
|||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
* @param {Object} props React Component properties
|
* @param {Object} props React Component properties
|
||||||
* @param {Object} context React Component context
|
|
||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props) {
|
||||||
super(props, context, 'utility', 'utility mounts');
|
super(props, 'utility mounts');
|
||||||
this._empty = this._empty.bind(this);
|
autoBind(this);
|
||||||
this.selectedRefId = null;
|
|
||||||
this.firstRefId = 'emptyall';
|
|
||||||
this.lastRefId = 'po';
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Handle focus if the component updates
|
|
||||||
* @param {Object} prevProps React Component properties
|
|
||||||
*/
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
this._handleSectionFocus(prevProps,this.firstRefId, this.lastRefId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Empty all utility slots and close the menu
|
* Empty all utility slots and close the menu
|
||||||
*/
|
*/
|
||||||
_empty() {
|
_empty() {
|
||||||
this.selectedRefId = this.firstRefId;
|
|
||||||
this.props.ship.emptyUtility();
|
this.props.ship.emptyUtility();
|
||||||
this.props.onChange();
|
this.props.onChange();
|
||||||
this._close();
|
this._close();
|
||||||
@@ -45,9 +34,6 @@ export default class UtilitySlotSection extends SlotSection {
|
|||||||
* @param {Synthetic} event Event
|
* @param {Synthetic} event Event
|
||||||
*/
|
*/
|
||||||
_use(group, rating, name, event) {
|
_use(group, rating, name, event) {
|
||||||
this.selectedRefId = group;
|
|
||||||
if (rating !== null) this.selectedRefId += '-' + rating;
|
|
||||||
|
|
||||||
this.props.ship.useUtility(group, rating, name, event.getModifierState('Alt'));
|
this.props.ship.useUtility(group, rating, name, event.getModifierState('Alt'));
|
||||||
this.props.onChange();
|
this.props.onChange();
|
||||||
this._close();
|
this._close();
|
||||||
@@ -66,32 +52,25 @@ export default class UtilitySlotSection extends SlotSection {
|
|||||||
*/
|
*/
|
||||||
_getSlots() {
|
_getSlots() {
|
||||||
let slots = [];
|
let slots = [];
|
||||||
let { ship, currentMenu } = this.props;
|
let { ship, currentMenu, propsToShow, onPropToggle } = this.props;
|
||||||
let hardpoints = ship.hardpoints;
|
|
||||||
let { originSlot, targetSlot } = this.state;
|
let { originSlot, targetSlot } = this.state;
|
||||||
let availableModules = ship.getAvailableModules();
|
|
||||||
|
|
||||||
for (let i = 0, l = hardpoints.length; i < l; i++) {
|
for (let h of ship.getUtilities(undefined, true)) {
|
||||||
let h = hardpoints[i];
|
slots.push(<Slot
|
||||||
if (h.maxClass === 0) {
|
key={h.object.Slot}
|
||||||
slots.push(<HardpointSlot
|
maxClass={h.getSize()}
|
||||||
key={i}
|
|
||||||
maxClass={h.maxClass}
|
|
||||||
availableModules={() => availableModules.getHps(h.maxClass)}
|
|
||||||
onOpen={this._openMenu.bind(this,h)}
|
|
||||||
onSelect={this._selectModule.bind(this, h)}
|
|
||||||
onChange={this.props.onChange}
|
onChange={this.props.onChange}
|
||||||
selected={currentMenu == h}
|
currentMenu={currentMenu}
|
||||||
drag={this._drag.bind(this, h)}
|
drag={this._drag.bind(this, h)}
|
||||||
dragOver={this._dragOverSlot.bind(this, h)}
|
dragOver={this._dragOverSlot.bind(this, h)}
|
||||||
drop={this._drop}
|
drop={this._drop}
|
||||||
dropClass={this._dropClass(h, originSlot, targetSlot)}
|
dropClass={this._dropClass(h, originSlot, targetSlot)}
|
||||||
ship={ship}
|
m={h}
|
||||||
m={h.m}
|
|
||||||
enabled={h.enabled ? true : false}
|
enabled={h.enabled ? true : false}
|
||||||
|
propsToShow={propsToShow}
|
||||||
|
onPropToggle={onPropToggle}
|
||||||
/>);
|
/>);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return slots;
|
return slots;
|
||||||
}
|
}
|
||||||
@@ -101,33 +80,34 @@ export default class UtilitySlotSection extends SlotSection {
|
|||||||
* @param {Function} translate Translate function
|
* @param {Function} translate Translate function
|
||||||
* @return {React.Component} Section menu
|
* @return {React.Component} Section menu
|
||||||
*/
|
*/
|
||||||
_getSectionMenu(translate) {
|
_getSectionMenu() {
|
||||||
|
const { translate } = this.context.language;
|
||||||
let _use = this._use;
|
let _use = this._use;
|
||||||
|
|
||||||
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
|
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='lc' tabIndex='0' onClick={this._empty} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['emptyall'] = smRef}>{translate('empty all')}</li>
|
<li className='lc' tabIndex='0' onClick={this._empty}>{translate('empty all')}</li>
|
||||||
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
|
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('sb')}</div>
|
<div className='select-group cap'>{translate('sb')}</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'A', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-A'] = smRef}>A</li>
|
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'A', null)}>A</li>
|
||||||
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'B', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-B'] = smRef}>B</li>
|
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'B', null)}>B</li>
|
||||||
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'C', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-C'] = smRef}>C</li>
|
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'C', null)}>C</li>
|
||||||
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'D', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-D'] = smRef}>D</li>
|
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'D', null)}>D</li>
|
||||||
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'E', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-E'] = smRef}>E</li>
|
<li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'E', null)}>E</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('hs')}</div>
|
<div className='select-group cap'>{translate('hs')}</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'hs', null, 'Heat Sink Launcher')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['hs'] = smRef}>{translate('Heat Sink Launcher')}</li>
|
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'hs', null, 'Heat Sink Launcher')}>{translate('Heat Sink Launcher')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('ch')}</div>
|
<div className='select-group cap'>{translate('ch')}</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'ch', null, 'Chaff Launcher')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ch'] = smRef}>{translate('Chaff Launcher')}</li>
|
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'ch', null, 'Chaff Launcher')}>{translate('Chaff Launcher')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('po')}</div>
|
<div className='select-group cap'>{translate('po')}</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'po', null, 'Point Defence')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['po'] = smRef}>{translate('Point Defence')}</li>
|
<li className='lc' tabIndex='0' onClick={_use.bind(this, 'po', null, 'Point Defence')}>{translate('Point Defence')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,193 +3,99 @@ import PropTypes from 'prop-types';
|
|||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import LineChart from '../components/LineChart';
|
import LineChart from '../components/LineChart';
|
||||||
import * as Calc from '../shipyard/Calculations';
|
import * as Calc from '../shipyard/Calculations';
|
||||||
|
import { moduleReduce } from 'ed-forge/lib/helper';
|
||||||
|
import { chain, keys, mapValues, values } from 'lodash';
|
||||||
|
|
||||||
const DAMAGE_DEALT_COLORS = ['#FFFFFF', '#FF0000', '#00FF00', '#7777FF', '#FFFF00', '#FF00FF', '#00FFFF', '#777777'];
|
const DAMAGE_DEALT_COLORS = ['#FFFFFF', '#FF0000', '#00FF00', '#7777FF', '#FFFF00', '#FF00FF', '#00FFFF', '#777777'];
|
||||||
|
const PORTION_MAPPINGS = {
|
||||||
|
'absolute': 'absolutedamageportion',
|
||||||
|
'explosive': 'explosivedamageportion',
|
||||||
|
'kinetic': 'kineticdamageportion',
|
||||||
|
'thermal': 'thermicdamageportion',
|
||||||
|
};
|
||||||
|
const MULTS = keys(PORTION_MAPPINGS);
|
||||||
|
|
||||||
|
// TODO: help with this in ed-forge
|
||||||
|
/**
|
||||||
|
* .
|
||||||
|
* @param {Object} opponentDefence .
|
||||||
|
* @returns {Object} .
|
||||||
|
*/
|
||||||
|
function defenceToMults(opponentDefence) {
|
||||||
|
return chain(opponentDefence)
|
||||||
|
.pick(MULTS)
|
||||||
|
.mapKeys((v, k) => PORTION_MAPPINGS[k])
|
||||||
|
.mapValues((resistanceProfile) => resistanceProfile.damageMultiplier)
|
||||||
|
.value();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Weapon damage chart
|
* Weapon damage chart
|
||||||
*/
|
*/
|
||||||
export default class WeaponDamageChart extends TranslatedComponent {
|
export default class WeaponDamageChart extends TranslatedComponent {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
|
code: PropTypes.string.isRequired,
|
||||||
ship: PropTypes.object.isRequired,
|
ship: PropTypes.object.isRequired,
|
||||||
opponent: PropTypes.object.isRequired,
|
opponentDefence: PropTypes.object.isRequired,
|
||||||
hull: PropTypes.bool.isRequired,
|
|
||||||
engagementRange: PropTypes.number.isRequired,
|
engagementRange: PropTypes.number.isRequired,
|
||||||
opponentSys: PropTypes.number.isRequired,
|
|
||||||
marker: PropTypes.string.isRequired
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
* @param {Object} props React Component properties
|
|
||||||
* @param {Object} context React Component context
|
|
||||||
*/
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the initial weapons state
|
|
||||||
*/
|
|
||||||
componentWillMount() {
|
|
||||||
const weaponNames = this._weaponNames(this.props.ship, this.context);
|
|
||||||
const opponentShields = Calc.shieldMetrics(this.props.opponent, this.props.opponentSys);
|
|
||||||
const opponentArmour = Calc.armourMetrics(this.props.opponent);
|
|
||||||
const maxRange = this._calcMaxRange(this.props.ship);
|
|
||||||
const maxDps = this._calcMaxSDps(this.props.ship, this.props.opponent, opponentShields, opponentArmour);
|
|
||||||
|
|
||||||
this.setState({ maxRange, maxDps, weaponNames, opponentShields, opponentArmour, calcSDpsFunc: this._calcSDps.bind(this, this.props.ship, weaponNames, this.props.opponent, opponentShields, opponentArmour, this.props.hull) });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the updated weapons state if our ship changes
|
|
||||||
* @param {Object} nextProps Incoming/Next properties
|
|
||||||
* @param {Object} nextContext Incoming/Next conext
|
|
||||||
* @return {boolean} Returns true if the component should be rerendered
|
|
||||||
*/
|
|
||||||
componentWillReceiveProps(nextProps, nextContext) {
|
|
||||||
if (nextProps.marker != this.props.marker) {
|
|
||||||
const weaponNames = this._weaponNames(nextProps.ship, nextContext);
|
|
||||||
const opponentShields = Calc.shieldMetrics(nextProps.opponent, nextProps.opponentSys);
|
|
||||||
const opponentArmour = Calc.armourMetrics(nextProps.opponent);
|
|
||||||
const maxRange = this._calcMaxRange(nextProps.ship);
|
|
||||||
const maxDps = this._calcMaxSDps(nextProps.ship, nextProps.opponent, opponentShields, opponentArmour);
|
|
||||||
this.setState({ weaponNames,
|
|
||||||
opponentShields,
|
|
||||||
opponentArmour,
|
|
||||||
maxRange,
|
|
||||||
maxDps,
|
|
||||||
calcSDpsFunc: this._calcSDps.bind(this, nextProps.ship, weaponNames, nextProps.opponent, opponentShields, opponentArmour, nextProps.hull)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the maximum range of a ship's weapons
|
|
||||||
* @param {Object} ship The ship
|
|
||||||
* @returns {int} The maximum range, in metres
|
|
||||||
*/
|
|
||||||
_calcMaxRange(ship) {
|
|
||||||
let maxRange = 1000; // Minimum
|
|
||||||
for (let i = 0; i < ship.hardpoints.length; i++) {
|
|
||||||
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
|
|
||||||
const thisRange = ship.hardpoints[i].m.getRange();
|
|
||||||
if (thisRange > maxRange) {
|
|
||||||
maxRange = thisRange;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return maxRange;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the maximum sustained single-weapon DPS for this ship
|
|
||||||
* @param {Object} ship The ship
|
|
||||||
* @param {Object} opponent The opponent ship
|
|
||||||
* @param {Object} opponentShields The opponent's shields
|
|
||||||
* @param {Object} opponentArmour The opponent's armour
|
|
||||||
* @return {number} The maximum sustained single-weapon DPS
|
|
||||||
*/
|
|
||||||
_calcMaxSDps(ship, opponent, opponentShields, opponentArmour) {
|
|
||||||
// Additional information to allow effectiveness calculations
|
|
||||||
let maxSDps = 0;
|
|
||||||
for (let i = 0; i < ship.hardpoints.length; i++) {
|
|
||||||
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
|
|
||||||
const m = ship.hardpoints[i].m;
|
|
||||||
|
|
||||||
const sustainedDps = Calc._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, 0);
|
|
||||||
const thisSDps = sustainedDps.damage.armour.total > sustainedDps.damage.shields.total ? sustainedDps.damage.armour.total : sustainedDps.damage.shields.total;
|
|
||||||
if (thisSDps > maxSDps) {
|
|
||||||
maxSDps = thisSDps;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return maxSDps;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtain the weapon names for this ship
|
|
||||||
* @param {Object} ship The ship
|
|
||||||
* @param {Object} context The context
|
|
||||||
* @return {array} The weapon names
|
|
||||||
*/
|
|
||||||
_weaponNames(ship, context) {
|
|
||||||
const translate = context.language.translate;
|
|
||||||
let names = [];
|
|
||||||
let num = 1;
|
|
||||||
for (let i = 0; i < ship.hardpoints.length; i++) {
|
|
||||||
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
|
|
||||||
const m = ship.hardpoints[i].m;
|
|
||||||
let name = '' + num++ + ': ' + m.class + m.rating + (m.missile ? '/' + m.missile : '') + ' ' + translate(m.name || m.grp);
|
|
||||||
let engineering;
|
|
||||||
if (m.blueprint && m.blueprint.name) {
|
|
||||||
engineering = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
|
|
||||||
if (m.blueprint.special && m.blueprint.special.id) {
|
|
||||||
engineering += ', ' + translate(m.blueprint.special.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (engineering) {
|
|
||||||
name = name + ' (' + engineering + ')';
|
|
||||||
}
|
|
||||||
names.push(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return names;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate the per-weapon sustained DPS for this ship against another ship at a given range
|
|
||||||
* @param {Object} ship The ship
|
|
||||||
* @param {Object} weaponNames The names of the weapons for which to calculate DPS
|
|
||||||
* @param {Object} opponent The target
|
|
||||||
* @param {Object} opponentShields The opponent's shields
|
|
||||||
* @param {Object} opponentArmour The opponent's armour
|
|
||||||
* @param {bool} hull true if to calculate against hull, false if to calculate against shields
|
|
||||||
* @param {Object} engagementRange The engagement range
|
|
||||||
* @return {array} The array of weapon DPS
|
|
||||||
*/
|
|
||||||
_calcSDps(ship, weaponNames, opponent, opponentShields, opponentArmour, hull, engagementRange) {
|
|
||||||
let results = {};
|
|
||||||
let weaponNum = 0;
|
|
||||||
for (let i = 0; i < ship.hardpoints.length; i++) {
|
|
||||||
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
|
|
||||||
const m = ship.hardpoints[i].m;
|
|
||||||
const sustainedDps = Calc._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementRange);
|
|
||||||
results[weaponNames[weaponNum++]] = hull ? sustainedDps.damage.armour.total : sustainedDps.damage.shields.total;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render damage dealt
|
* Render damage dealt
|
||||||
* @return {React.Component} contents
|
* @return {React.Component} contents
|
||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
const { language } = this.context;
|
||||||
const { formats, translate, units } = language;
|
const { translate } = language;
|
||||||
const { maxRange } = this.state;
|
const { code, ship, opponentDefence, engagementRange } = this.props;
|
||||||
const { ship, opponent, engagementRange } = this.props;
|
|
||||||
|
|
||||||
const sortOrder = this._sortOrder;
|
const hardpoints = ship.getHardpoints();
|
||||||
const onCollapseExpand = this._onCollapseExpand;
|
const hardpointsMap = chain(hardpoints)
|
||||||
|
.map((m) => [m.getSlot(), m])
|
||||||
const code = `${ship.toString()}:${opponent.toString()}`;
|
.fromPairs()
|
||||||
|
.value();
|
||||||
|
const mults = defenceToMults(opponentDefence);
|
||||||
|
const cb = (range) => {
|
||||||
|
return mapValues(
|
||||||
|
hardpointsMap,
|
||||||
|
(m) => {
|
||||||
|
const sdps = m.get('sustaineddamagepersecond', true);
|
||||||
|
const resistanceMul = chain(mults)
|
||||||
|
.toPairs()
|
||||||
|
.map((pair) => {
|
||||||
|
const [k, mul] = pair;
|
||||||
|
return m.get(k, true) * mul;
|
||||||
|
})
|
||||||
|
.sum()
|
||||||
|
.value();
|
||||||
|
|
||||||
|
const falloff = m.get('damagefalloffrange', true);
|
||||||
|
const rangeMul = Math.min(1, Math.max(0,
|
||||||
|
1 - (range - falloff) / (m.get('maximumrange', true) - falloff)
|
||||||
|
));
|
||||||
|
return sdps * resistanceMul * rangeMul;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<LineChart
|
<LineChart
|
||||||
xMax={maxRange}
|
xMin={0}
|
||||||
yMax={this.state.maxDps}
|
xMax={moduleReduce(
|
||||||
|
hardpoints, 'maximumrange', true, (a, v) => Math.max(a, v), 1000,
|
||||||
|
)}
|
||||||
|
yMin={0}
|
||||||
|
// Factor in highest damage multiplier to get a safe upper bound
|
||||||
|
yMax={Math.max(1, ...values(mults)) * moduleReduce(
|
||||||
|
hardpoints, 'sustaineddamagepersecond', true, (a, v) => Math.max(a, v), 0,
|
||||||
|
)}
|
||||||
xLabel={translate('range')}
|
xLabel={translate('range')}
|
||||||
xUnit={translate('m')}
|
xUnit={translate('m')}
|
||||||
yLabel={translate('sdps')}
|
yLabel={translate('sustaineddamagepersecond')}
|
||||||
series={this.state.weaponNames}
|
series={hardpoints.map((m) => m.getSlot())}
|
||||||
xMark={this.props.engagementRange}
|
xMark={engagementRange}
|
||||||
colors={DAMAGE_DEALT_COLORS}
|
colors={DAMAGE_DEALT_COLORS}
|
||||||
func={this.state.calcSDpsFunc}
|
func={cb}
|
||||||
points={200}
|
points={200}
|
||||||
code={code}
|
code={code}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import * as IT from './it';
|
|||||||
import * as RU from './ru';
|
import * as RU from './ru';
|
||||||
import * as PL from './pl';
|
import * as PL from './pl';
|
||||||
import * as PT from './pt';
|
import * as PT from './pt';
|
||||||
|
import * as CN from './cn';
|
||||||
import * as d3 from 'd3';
|
import * as d3 from 'd3';
|
||||||
|
|
||||||
let fallbackTerms = EN.terms;
|
let fallbackTerms = EN.terms;
|
||||||
@@ -27,6 +28,7 @@ export function getLanguage(langCode) {
|
|||||||
case 'ru': lang = RU; break;
|
case 'ru': lang = RU; break;
|
||||||
case 'pl': lang = PL; break;
|
case 'pl': lang = PL; break;
|
||||||
case 'pt': lang = PT; break;
|
case 'pt': lang = PT; break;
|
||||||
|
case 'cn': lang = CN; break;
|
||||||
default:
|
default:
|
||||||
lang = EN;
|
lang = EN;
|
||||||
}
|
}
|
||||||
@@ -94,5 +96,6 @@ export const Languages = {
|
|||||||
fr: 'Français',
|
fr: 'Français',
|
||||||
ru: 'ру́сский',
|
ru: 'ру́сский',
|
||||||
pl: 'polski',
|
pl: 'polski',
|
||||||
pt: 'português'
|
pt: 'português',
|
||||||
|
cn: '中文'
|
||||||
};
|
};
|
||||||
|
|||||||
16
src/app/i18n/cn.js
Normal file
16
src/app/i18n/cn.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
export const formats = {
|
||||||
|
decimal: '.',
|
||||||
|
thousands: ',',
|
||||||
|
grouping: [3],
|
||||||
|
currency: ['¥', ''],
|
||||||
|
dateTime: '%a %b %e %X %Y',
|
||||||
|
date: '%Y年%m月%d日',
|
||||||
|
time: '%H:%M:%S',
|
||||||
|
periods: ['AM', 'PM'],
|
||||||
|
days: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
|
||||||
|
shortDays: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
|
||||||
|
months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
|
||||||
|
shortMonths: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']
|
||||||
|
};
|
||||||
|
|
||||||
|
export { default as terms } from './cn.json';
|
||||||
405
src/app/i18n/cn.json
Normal file
405
src/app/i18n/cn.json
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -63,7 +63,7 @@
|
|||||||
"TT_SUMMARY_SPEED": "With full fuel tank and 4 pips to ENG",
|
"TT_SUMMARY_SPEED": "With full fuel tank and 4 pips to ENG",
|
||||||
"TT_SUMMARY_SPEED_NONFUNCTIONAL": "Thrusters powered off or over maximum mass with full fuel and cargo loads",
|
"TT_SUMMARY_SPEED_NONFUNCTIONAL": "Thrusters powered off or over maximum mass with full fuel and cargo loads",
|
||||||
"TT_SUMMARY_BOOST": "With full fuel tank and 4 pips to ENG",
|
"TT_SUMMARY_BOOST": "With full fuel tank and 4 pips to ENG",
|
||||||
"TT_SUMMARY_BOOST_TIME": "Time between each boost with 4 pips to ENG",
|
"TT_SUMMARY_BOOST_INTERVAL": "Time between each boost with 4 pips to ENG",
|
||||||
"TT_SUMMARY_BOOST_NONFUNCTIONAL": "Power distributor not able to supply enough power to boost",
|
"TT_SUMMARY_BOOST_NONFUNCTIONAL": "Power distributor not able to supply enough power to boost",
|
||||||
"TT_SUMMARY_SHIELDS": "Raw shield strength, including boosters",
|
"TT_SUMMARY_SHIELDS": "Raw shield strength, including boosters",
|
||||||
"TT_SUMMARY_SHIELDS_SCB": "Raw shield strength, including boosters and SCBs",
|
"TT_SUMMARY_SHIELDS_SCB": "Raw shield strength, including boosters and SCBs",
|
||||||
@@ -81,6 +81,8 @@
|
|||||||
"TT_SUMMARY_UNLADEN_TOTAL_JUMP": "Farthest possible range with no cargo, a full fuel tank, and jumping as far as possible each time",
|
"TT_SUMMARY_UNLADEN_TOTAL_JUMP": "Farthest possible range with no cargo, a full fuel tank, and jumping as far as possible each time",
|
||||||
"TT_SUMMARY_LADEN_TOTAL_JUMP": "Farthest possible range with full cargo, a full fuel tank, and jumping as far as possible each time",
|
"TT_SUMMARY_LADEN_TOTAL_JUMP": "Farthest possible range with full cargo, a full fuel tank, and jumping as far as possible each time",
|
||||||
"HELP_MODIFICATIONS_MENU": "Click on a number to enter a new value, or drag along the bar for small changes",
|
"HELP_MODIFICATIONS_MENU": "Click on a number to enter a new value, or drag along the bar for small changes",
|
||||||
|
"PHRASE_FAIL_EDENGINEER": "Failed to send to EDEngineer (Launch EDEngineer and make sure the API is started then refresh the page.)",
|
||||||
|
"PHRASE_FIREFOX_EDENGINEER": "Sending to EDEngineer is not compatible with Firefox's security settings. Please try again with Chrome.",
|
||||||
"am": "Auto Field-Maintenance Unit",
|
"am": "Auto Field-Maintenance Unit",
|
||||||
"bh": "Bulkheads",
|
"bh": "Bulkheads",
|
||||||
"bl": "Beam Laser",
|
"bl": "Beam Laser",
|
||||||
@@ -151,6 +153,7 @@
|
|||||||
"sfn": "Shutdown Field Neutraliser",
|
"sfn": "Shutdown Field Neutraliser",
|
||||||
"sg": "Shield Generator",
|
"sg": "Shield Generator",
|
||||||
"ss": "Surface Scanners",
|
"ss": "Surface Scanners",
|
||||||
|
"sua": "Supercruise Assist",
|
||||||
"t": "thrusters",
|
"t": "thrusters",
|
||||||
"tp": "Torpedo Pylon",
|
"tp": "Torpedo Pylon",
|
||||||
"ul": "Burst Laser",
|
"ul": "Burst Laser",
|
||||||
@@ -172,6 +175,7 @@
|
|||||||
"ammunition": "Ammo",
|
"ammunition": "Ammo",
|
||||||
"secs": "s",
|
"secs": "s",
|
||||||
"rebuildsperbay": "Rebuilds per bay",
|
"rebuildsperbay": "Rebuilds per bay",
|
||||||
|
"mroll": "Roll",
|
||||||
"worst": "Worst",
|
"worst": "Worst",
|
||||||
"average": "Average",
|
"average": "Average",
|
||||||
"random": "Random",
|
"random": "Random",
|
||||||
@@ -201,7 +205,7 @@
|
|||||||
"internal protection": "Internal protection",
|
"internal protection": "Internal protection",
|
||||||
"external protection": "External protection",
|
"external protection": "External protection",
|
||||||
"engagement range": "Engagement range",
|
"engagement range": "Engagement range",
|
||||||
"boost time": "Boost time",
|
"boost interval": "Boost intervall",
|
||||||
"total": "Total",
|
"total": "Total",
|
||||||
"ammo": "Ammunition maximum",
|
"ammo": "Ammunition maximum",
|
||||||
"boot": "Boot time",
|
"boot": "Boot time",
|
||||||
@@ -320,6 +324,7 @@
|
|||||||
"never": "never",
|
"never": "never",
|
||||||
"stock": "stock",
|
"stock": "stock",
|
||||||
"boost": "boost",
|
"boost": "boost",
|
||||||
|
"tab_defence": "defence",
|
||||||
"federation rank 1": "Recruit",
|
"federation rank 1": "Recruit",
|
||||||
"federation rank 2": "Cadet",
|
"federation rank 2": "Cadet",
|
||||||
"federation rank 3": "Midshipman",
|
"federation rank 3": "Midshipman",
|
||||||
|
|||||||
@@ -59,7 +59,7 @@
|
|||||||
"empty all": "vide tout",
|
"empty all": "vide tout",
|
||||||
"Enter Name": "Entrer nom",
|
"Enter Name": "Entrer nom",
|
||||||
"Explorer": "explorateur",
|
"Explorer": "explorateur",
|
||||||
"fastest range": "gamme la plus rapide",
|
"farthest range": "gamme la plus rapide",
|
||||||
"fuel": "carburant",
|
"fuel": "carburant",
|
||||||
"fuel level": "niveau de carburant",
|
"fuel level": "niveau de carburant",
|
||||||
"full tank": "Réservoir plein",
|
"full tank": "Réservoir plein",
|
||||||
|
|||||||
@@ -299,7 +299,7 @@
|
|||||||
"edit data": "Редактирование",
|
"edit data": "Редактирование",
|
||||||
"empty all": "пусто все",
|
"empty all": "пусто все",
|
||||||
"Enter Name": "Введите имя",
|
"Enter Name": "Введите имя",
|
||||||
"fastest range": "быстрый диапазон",
|
"farthest range": "быстрый диапазон",
|
||||||
"fuel level": "уровень топлива",
|
"fuel level": "уровень топлива",
|
||||||
"full tank": "Полный бак",
|
"full tank": "Полный бак",
|
||||||
"internal compartments": "внутренние отсеки",
|
"internal compartments": "внутренние отсеки",
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import PropTypes from 'prop-types';
|
|||||||
* Unexpected Error page / block
|
* Unexpected Error page / block
|
||||||
*/
|
*/
|
||||||
export default class ErrorDetails extends React.Component {
|
export default class ErrorDetails extends React.Component {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
route: PropTypes.object.isRequired,
|
route: PropTypes.object.isRequired,
|
||||||
language: PropTypes.object.isRequired
|
language: PropTypes.object.isRequired
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import Page from './Page';
|
|||||||
import Router from '../Router';
|
import Router from '../Router';
|
||||||
import Persist from '../stores/Persist';
|
import Persist from '../stores/Persist';
|
||||||
import * as Utils from '../utils/UtilityFunctions';
|
import * as Utils from '../utils/UtilityFunctions';
|
||||||
import Ship from '../shipyard/Ship';
|
import { Factory, Ship } from 'ed-forge';
|
||||||
|
import { STATE_EVENT, OBJECT_EVENT } from 'ed-forge/lib/Ship';
|
||||||
import * as _ from 'lodash';
|
import * as _ from 'lodash';
|
||||||
import { toDetailedBuild } from '../shipyard/Serializer';
|
import { toDetailedBuild } from '../shipyard/Serializer';
|
||||||
import { outfitURL } from '../utils/UrlGenerators';
|
import { outfitURL } from '../utils/UrlGenerators';
|
||||||
@@ -38,16 +39,77 @@ import ModalExport from '../components/ModalExport';
|
|||||||
import ModalPermalink from '../components/ModalPermalink';
|
import ModalPermalink from '../components/ModalPermalink';
|
||||||
import ModalShoppingList from '../components/ModalShoppingList';
|
import ModalShoppingList from '../components/ModalShoppingList';
|
||||||
import ModalOrbis from '../components/ModalOrbis';
|
import ModalOrbis from '../components/ModalOrbis';
|
||||||
|
import autoBind from 'auto-bind';
|
||||||
|
import { assign } from 'lodash';
|
||||||
|
|
||||||
/**
|
const SHOW_BY_DEFAULT = {
|
||||||
* Document Title Generator
|
'cabincapacity': true,
|
||||||
* @param {String} shipName Ship Name
|
'causticresistance': true,
|
||||||
* @param {String} buildName Build Name
|
'explosiveresistance': true,
|
||||||
* @return {String} Document title
|
'kineticresistance': true,
|
||||||
*/
|
'thermicresistance': true,
|
||||||
function getTitle(shipName, buildName) {
|
'heatefficiency': true,
|
||||||
return buildName ? buildName : shipName;
|
'powercapacity': true,
|
||||||
}
|
'integrity': true,
|
||||||
|
'engineminimalmass': true,
|
||||||
|
'engineoptimalmass': true,
|
||||||
|
'enginemaximalmass': true,
|
||||||
|
'engineoptperformance': true,
|
||||||
|
'fsdoptimalmass': true,
|
||||||
|
'maxfuel': true,
|
||||||
|
'dronelifetime': true,
|
||||||
|
'weaponscapacity': true,
|
||||||
|
'weaponsrecharge': true,
|
||||||
|
'systemscapacity': true,
|
||||||
|
'systemsrecharge': true,
|
||||||
|
'enginescapacity': true,
|
||||||
|
'enginesrecharge': true,
|
||||||
|
'range': true,
|
||||||
|
'shieldgenmaximalmass': true,
|
||||||
|
'shieldgenminimalmass': true,
|
||||||
|
'shieldgenoptimalmass': true,
|
||||||
|
'brokenregenrate': true,
|
||||||
|
'regenrate': true,
|
||||||
|
'shieldgenstrength': true,
|
||||||
|
'ammomaximum': true,
|
||||||
|
'afmrepaircapacity': true,
|
||||||
|
'fsdinterdictorfacinglimit': true,
|
||||||
|
'fuelscooprate': true,
|
||||||
|
'bays': true,
|
||||||
|
'rebuildsperbay': true,
|
||||||
|
'maximumrange': true,
|
||||||
|
'maxactivedrones': true,
|
||||||
|
'refinerybins': true,
|
||||||
|
'shieldbankduration': true,
|
||||||
|
'shieldbankspinup': true,
|
||||||
|
'shieldbankreinforcement': true,
|
||||||
|
'defencemodifierhealthaddition': true,
|
||||||
|
'protection': true,
|
||||||
|
'dronehackingtime': true,
|
||||||
|
'defencemodifiershieldaddition': true,
|
||||||
|
'jumpboost': true,
|
||||||
|
'damagepersecond': true,
|
||||||
|
'damage': true,
|
||||||
|
'energypersecond': true,
|
||||||
|
'heatpersecond': true,
|
||||||
|
'sustaineddamagepersecond': true,
|
||||||
|
'damageperenergy': true,
|
||||||
|
'rateoffire': true,
|
||||||
|
'maximumrange': true,
|
||||||
|
'damagefalloffrange': true,
|
||||||
|
'armourpenetration': true,
|
||||||
|
'sustainedenergypersecond': true,
|
||||||
|
'sustainedheatpersecond': true,
|
||||||
|
'ammoclipsize': true,
|
||||||
|
'ammomaximum': true,
|
||||||
|
'reloadtime': true,
|
||||||
|
'shotspeed': true,
|
||||||
|
'defencemodifiershieldmultiplier': true,
|
||||||
|
'scannerrange': true,
|
||||||
|
'scannertimetoscan': true,
|
||||||
|
'maxangle': true,
|
||||||
|
'mass': true,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The Outfitting Page
|
* The Outfitting Page
|
||||||
@@ -60,17 +122,8 @@ export default class OutfittingPage extends Page {
|
|||||||
*/
|
*/
|
||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
// window.Perf = Perf;
|
autoBind(this);
|
||||||
this.state = this._initState(props, context);
|
this.state = this._initState(props, context);
|
||||||
this._keyDown = this._keyDown.bind(this);
|
|
||||||
this._exportBuild = this._exportBuild.bind(this);
|
|
||||||
this._pipsUpdated = this._pipsUpdated.bind(this);
|
|
||||||
this._boostUpdated = this._boostUpdated.bind(this);
|
|
||||||
this._cargoUpdated = this._cargoUpdated.bind(this);
|
|
||||||
this._fuelUpdated = this._fuelUpdated.bind(this);
|
|
||||||
this._opponentUpdated = this._opponentUpdated.bind(this);
|
|
||||||
this._engagementRangeUpdated = this._engagementRangeUpdated.bind(this);
|
|
||||||
this._sectionMenuRefs = {};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -82,68 +135,36 @@ export default class OutfittingPage extends Page {
|
|||||||
_initState(props, context) {
|
_initState(props, context) {
|
||||||
let params = context.route.params;
|
let params = context.route.params;
|
||||||
let shipId = params.ship;
|
let shipId = params.ship;
|
||||||
let code = params.code;
|
|
||||||
let buildName = params.bn;
|
let buildName = params.bn;
|
||||||
let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults
|
|
||||||
let savedCode = Persist.getBuild(shipId, buildName);
|
let savedCode = Persist.getBuild(shipId, buildName);
|
||||||
if (!data) {
|
let code = params.code || savedCode;
|
||||||
return { error: { message: 'Ship not found: ' + shipId } };
|
// Create a new Ship instance
|
||||||
}
|
const ship = code ? new Ship(code) : Factory.newShip(shipId);
|
||||||
let ship = new Ship(shipId, data.properties, data.slots); // Create a new Ship instance
|
ship.on(STATE_EVENT, this._shipUpdated);
|
||||||
if (code) {
|
ship.on(OBJECT_EVENT, this._shipUpdated);
|
||||||
ship.buildFrom(code); // Populate modules from serialized 'code' URL param
|
|
||||||
} else {
|
|
||||||
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);
|
|
||||||
return {
|
return {
|
||||||
error: null,
|
error: null,
|
||||||
title: this._getTitle(buildName),
|
|
||||||
costTab: Persist.getCostTab() || 'costs',
|
costTab: Persist.getCostTab() || 'costs',
|
||||||
buildName,
|
buildName,
|
||||||
newBuildName: buildName,
|
newBuildName: buildName,
|
||||||
shipId,
|
|
||||||
ship,
|
ship,
|
||||||
code,
|
code: ship.compress(),
|
||||||
savedCode,
|
savedCode,
|
||||||
sys,
|
propsToShow: assign({}, SHOW_BY_DEFAULT),
|
||||||
eng,
|
|
||||||
wep,
|
|
||||||
mcSys,
|
|
||||||
mcEng,
|
|
||||||
mcWep,
|
|
||||||
boost,
|
|
||||||
fuel,
|
|
||||||
cargo,
|
|
||||||
opponent,
|
|
||||||
opponentBuild,
|
|
||||||
opponentSys,
|
|
||||||
opponentEng,
|
|
||||||
opponentWep,
|
|
||||||
engagementRange
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get this pages title for the browser.
|
||||||
|
* @returns {string} Page title
|
||||||
|
*/
|
||||||
|
_getTitle() {
|
||||||
|
const { buildName } = this.state;
|
||||||
|
const { translate } = this.context.language;
|
||||||
|
return buildName || translate(this.ship.getShipType());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle build name change and update state
|
* Handle build name change and update state
|
||||||
* @param {SyntheticEvent} event React Event
|
* @param {SyntheticEvent} event React Event
|
||||||
@@ -153,10 +174,12 @@ export default class OutfittingPage extends Page {
|
|||||||
newBuildName: event.target.value
|
newBuildName: event.target.value
|
||||||
};
|
};
|
||||||
|
|
||||||
if (Persist.hasBuild(this.state.shipId, stateChanges.newBuildName)) {
|
const { ship } = this.state;
|
||||||
|
const shipId = ship.getShipType();
|
||||||
|
if (Persist.hasBuild(shipId, stateChanges.newBuildName)) {
|
||||||
stateChanges.savedCode = Persist.getBuild(
|
stateChanges.savedCode = Persist.getBuild(
|
||||||
this.state.shipId,
|
shipId,
|
||||||
stateChanges.newBuildName
|
stateChanges.newBuildName,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
stateChanges.savedCode = null;
|
stateChanges.savedCode = null;
|
||||||
@@ -168,170 +191,13 @@ export default class OutfittingPage extends Page {
|
|||||||
/**
|
/**
|
||||||
* Update the control part of the route
|
* Update the control part of the route
|
||||||
*/
|
*/
|
||||||
_updateRouteOnControlChange() {
|
_updateRoute() {
|
||||||
const { ship, shipId, buildName } = this.state;
|
const { ship } = this.state;
|
||||||
const code = this._fullCode(ship);
|
const code = ship.compress();
|
||||||
this._updateRoute(shipId, buildName, code);
|
this._setRoute();
|
||||||
this.setState({ code });
|
this.setState({ code });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide a full code for this ship, including any additions due to the outfitting page
|
|
||||||
* @param {Object} ship the ship
|
|
||||||
* @param {number} fuel the fuel carried by the ship (if different from that in state)
|
|
||||||
* @param {number} cargo the cargo carried by the ship (if different from that in state)
|
|
||||||
* @returns {string} the code for this ship
|
|
||||||
*/
|
|
||||||
_fullCode(ship, fuel, cargo) {
|
|
||||||
return `${ship.toString()}.${LZString.compressToBase64(
|
|
||||||
this._controlCode(fuel, cargo)
|
|
||||||
)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Obtain the control information from the build code
|
|
||||||
* @param {Object} ship The ship
|
|
||||||
* @param {string} code The build code
|
|
||||||
* @returns {Object} The control information
|
|
||||||
*/
|
|
||||||
_obtainControlFromCode(ship, code) {
|
|
||||||
// Defaults
|
|
||||||
let sys = 2;
|
|
||||||
let eng = 2;
|
|
||||||
let wep = 2;
|
|
||||||
let mcSys = 0;
|
|
||||||
let mcEng = 0;
|
|
||||||
let mcWep = 0;
|
|
||||||
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 opponentSys = 2;
|
|
||||||
let opponentEng = 2;
|
|
||||||
let opponentWep = 2;
|
|
||||||
let opponentBuild;
|
|
||||||
let engagementRange = 1000;
|
|
||||||
|
|
||||||
// Obtain updates from code, if available
|
|
||||||
if (code) {
|
|
||||||
const parts = code.split('.');
|
|
||||||
if (parts.length >= 5) {
|
|
||||||
// We have control information in the code
|
|
||||||
const control = LZString.decompressFromBase64(
|
|
||||||
Utils.fromUrlSafe(parts[4])
|
|
||||||
).split('/');
|
|
||||||
sys = parseFloat(control[0]);
|
|
||||||
eng = parseFloat(control[1]);
|
|
||||||
wep = parseFloat(control[2]);
|
|
||||||
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
|
|
||||||
);
|
|
||||||
if (control[7] && Persist.getBuild(shipId, control[7])) {
|
|
||||||
// Ship is a particular build
|
|
||||||
const opponentCode = Persist.getBuild(shipId, control[7]);
|
|
||||||
opponent.buildFrom(opponentCode);
|
|
||||||
opponentBuild = control[7];
|
|
||||||
if (opponentBuild) {
|
|
||||||
// Obtain opponent's sys/eng/wep pips from their code
|
|
||||||
const opponentParts = opponentCode.split('.');
|
|
||||||
if (opponentParts.length >= 5) {
|
|
||||||
const opponentControl = LZString.decompressFromBase64(
|
|
||||||
Utils.fromUrlSafe(opponentParts[4])
|
|
||||||
).split('/');
|
|
||||||
opponentSys = parseFloat(opponentControl[0]) || opponentSys;
|
|
||||||
opponentEng = parseFloat(opponentControl[1]) || opponentEng;
|
|
||||||
opponentWep = parseFloat(opponentControl[2]) || opponentWep;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Ship is a stock build
|
|
||||||
opponent.buildWith(Ships[shipId].defaults);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
engagementRange = parseInt(control[8]) || engagementRange;
|
|
||||||
|
|
||||||
// Multi-crew pips were introduced later on so assign default values
|
|
||||||
// because those values might not be present.
|
|
||||||
mcSys = parseInt(control[9]) || mcSys;
|
|
||||||
mcEng = parseInt(control[10]) || mcEng;
|
|
||||||
mcWep = parseInt(control[11]) || mcWep;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
sys,
|
|
||||||
eng,
|
|
||||||
wep,
|
|
||||||
mcSys,
|
|
||||||
mcEng,
|
|
||||||
mcWep,
|
|
||||||
boost,
|
|
||||||
fuel,
|
|
||||||
cargo,
|
|
||||||
opponent,
|
|
||||||
opponentBuild,
|
|
||||||
opponentSys,
|
|
||||||
opponentEng,
|
|
||||||
opponentWep,
|
|
||||||
engagementRange
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggered when pips have been updated. Multi-crew pips are already included
|
|
||||||
* in sys, eng and wep but mcSys, mcEng and mcWep make clear where each pip
|
|
||||||
* comes from.
|
|
||||||
* @param {number} sys SYS pips
|
|
||||||
* @param {number} eng ENG pips
|
|
||||||
* @param {number} wep WEP pips
|
|
||||||
* @param {number} mcSys SYS pips from multi-crew
|
|
||||||
* @param {number} mcEng ENG pips from multi-crew
|
|
||||||
* @param {number} mcWep WEP pips from multi-crew
|
|
||||||
*/
|
|
||||||
_pipsUpdated(sys, eng, wep, mcSys, mcEng, mcWep) {
|
|
||||||
this.setState({ sys, eng, wep, mcSys, mcEng, mcWep }, () =>
|
|
||||||
this._updateRouteOnControlChange()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggered when boost has been updated
|
|
||||||
* @param {boolean} boost true if boosting
|
|
||||||
*/
|
|
||||||
_boostUpdated(boost) {
|
|
||||||
this.setState({ boost }, () => this._updateRouteOnControlChange());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggered when fuel has been updated
|
|
||||||
* @param {number} fuel the amount of fuel, in T
|
|
||||||
*/
|
|
||||||
_fuelUpdated(fuel) {
|
|
||||||
this.setState({ fuel }, () => this._updateRouteOnControlChange());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggered when cargo has been updated
|
|
||||||
* @param {number} cargo the amount of cargo, in T
|
|
||||||
*/
|
|
||||||
_cargoUpdated(cargo) {
|
|
||||||
this.setState({ cargo }, () => this._updateRouteOnControlChange());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggered when engagement range has been updated
|
* Triggered when engagement range has been updated
|
||||||
* @param {number} engagementRange the engagement range, in m
|
* @param {number} engagementRange the engagement range, in m
|
||||||
@@ -387,47 +253,35 @@ export default class OutfittingPage extends Page {
|
|||||||
opponentEng,
|
opponentEng,
|
||||||
opponentWep
|
opponentWep
|
||||||
},
|
},
|
||||||
() => this._updateRouteOnControlChange()
|
() => this._updateRoute()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
_propToShowToggled(propertyName, newStatus) {
|
||||||
* Set the control code for this outfitting page
|
const { propsToShow } = this.state;
|
||||||
* @param {number} fuel the fuel carried by the ship (if different from that in state)
|
if (newStatus === propsToShow[propertyName]) {
|
||||||
* @param {number} cargo the cargo carried by the ship (if different from that in state)
|
return;
|
||||||
* @returns {string} The control code
|
}
|
||||||
*/
|
if (newStatus) {
|
||||||
_controlCode(fuel, cargo) {
|
propsToShow[propertyName] = true;
|
||||||
const {
|
} else {
|
||||||
sys,
|
delete propsToShow[propertyName];
|
||||||
eng,
|
}
|
||||||
wep,
|
this.setState({ propsToShow: assign({}, propsToShow) });
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Save the current build
|
* Save the current build
|
||||||
*/
|
*/
|
||||||
_saveBuild() {
|
_saveBuild() {
|
||||||
const { ship, buildName, newBuildName, shipId } = this.state;
|
const { ship, buildName, newBuildName } = this.state;
|
||||||
|
const shipId = ship.getShipType();
|
||||||
|
|
||||||
// If this is a stock ship the code won't be set, so ensure that we have it
|
// 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.compress();
|
||||||
|
|
||||||
Persist.saveBuild(shipId, newBuildName, code);
|
Persist.saveBuild(shipId, newBuildName, code);
|
||||||
this._updateRoute(shipId, newBuildName, code);
|
this._setRoute();
|
||||||
|
|
||||||
let opponent, opponentBuild, opponentSys, opponentEng, opponentWep;
|
let opponent, opponentBuild, opponentSys, opponentEng, opponentWep;
|
||||||
if (
|
if (
|
||||||
@@ -460,7 +314,6 @@ export default class OutfittingPage extends Page {
|
|||||||
opponentSys,
|
opponentSys,
|
||||||
opponentEng,
|
opponentEng,
|
||||||
opponentWep,
|
opponentWep,
|
||||||
title: this._getTitle(newBuildName)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -468,11 +321,12 @@ export default class OutfittingPage extends Page {
|
|||||||
* Rename the current build
|
* Rename the current build
|
||||||
*/
|
*/
|
||||||
_renameBuild() {
|
_renameBuild() {
|
||||||
const { code, buildName, newBuildName, shipId, ship } = this.state;
|
const { code, buildName, newBuildName, ship } = this.state;
|
||||||
|
const shipId = ship.getShipType();
|
||||||
if (buildName != newBuildName && newBuildName.length) {
|
if (buildName != newBuildName && newBuildName.length) {
|
||||||
Persist.deleteBuild(shipId, buildName);
|
Persist.deleteBuild(shipId, buildName);
|
||||||
Persist.saveBuild(shipId, newBuildName, code);
|
Persist.saveBuild(shipId, newBuildName, code);
|
||||||
this._updateRoute(shipId, newBuildName, code);
|
this._setRoute();
|
||||||
this.setState({
|
this.setState({
|
||||||
buildName: newBuildName,
|
buildName: newBuildName,
|
||||||
code,
|
code,
|
||||||
@@ -493,50 +347,19 @@ export default class OutfittingPage extends Page {
|
|||||||
* Reset build to Stock/Factory defaults
|
* Reset build to Stock/Factory defaults
|
||||||
*/
|
*/
|
||||||
_resetBuild() {
|
_resetBuild() {
|
||||||
const { ship, shipId, buildName } = this.state;
|
let { ship } = this.state;
|
||||||
// Rebuild ship
|
// Rebuild ship
|
||||||
ship.buildWith(Ships[shipId].defaults);
|
ship = Factory.newShip(ship.getShipType());
|
||||||
// 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
|
// Update state, and refresh the ship
|
||||||
this.setState(
|
this.setState({ ship, code: undefined }, () => this._setRoute());
|
||||||
{
|
|
||||||
sys,
|
|
||||||
eng,
|
|
||||||
wep,
|
|
||||||
mcSys,
|
|
||||||
mcEng,
|
|
||||||
mcWep,
|
|
||||||
boost,
|
|
||||||
fuel,
|
|
||||||
cargo,
|
|
||||||
opponent,
|
|
||||||
opponentBuild,
|
|
||||||
engagementRange
|
|
||||||
},
|
|
||||||
() => this._updateRoute(shipId, buildName, code)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete the build
|
* Delete the build
|
||||||
*/
|
*/
|
||||||
_deleteBuild() {
|
_deleteBuild() {
|
||||||
const { shipId, buildName } = this.state;
|
const { ship, buildName } = this.state;
|
||||||
|
const shipId = ship.getShipType();
|
||||||
Persist.deleteBuild(shipId, buildName);
|
Persist.deleteBuild(shipId, buildName);
|
||||||
|
|
||||||
let opponentBuild;
|
let opponentBuild;
|
||||||
@@ -549,7 +372,7 @@ export default class OutfittingPage extends Page {
|
|||||||
} else {
|
} else {
|
||||||
opponentBuild = this.state.opponentBuild;
|
opponentBuild = this.state.opponentBuild;
|
||||||
}
|
}
|
||||||
Router.go(outfitURL(this.state.shipId));
|
Router.go(outfitURL(shipId));
|
||||||
|
|
||||||
this.setState({ opponentBuild });
|
this.setState({ opponentBuild });
|
||||||
}
|
}
|
||||||
@@ -573,43 +396,12 @@ export default class OutfittingPage extends Page {
|
|||||||
* Called when the code for the ship has been updated, to synchronise the rest of the data
|
* Called when the code for the ship has been updated, to synchronise the rest of the data
|
||||||
*/
|
*/
|
||||||
_codeUpdated() {
|
_codeUpdated() {
|
||||||
const { code, ship, shipId, buildName } = this.state;
|
const { ship, code, buildName } = this.state;
|
||||||
|
const shipId = ship.getShipType();
|
||||||
|
|
||||||
// Rebuild ship from the code
|
|
||||||
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(
|
this.setState(
|
||||||
{
|
{ ship: new Ship(code), },
|
||||||
sys,
|
() => this._setRoute(),
|
||||||
eng,
|
|
||||||
wep,
|
|
||||||
mcSys,
|
|
||||||
mcEng,
|
|
||||||
mcWep,
|
|
||||||
boost,
|
|
||||||
fuel,
|
|
||||||
cargo,
|
|
||||||
opponent,
|
|
||||||
opponentBuild,
|
|
||||||
engagementRange
|
|
||||||
},
|
|
||||||
() => this._updateRoute(shipId, buildName, code)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -617,23 +409,11 @@ export default class OutfittingPage extends Page {
|
|||||||
* Called when the ship has been updated, to set the code and then update accordingly
|
* Called when the ship has been updated, to set the code and then update accordingly
|
||||||
*/
|
*/
|
||||||
_shipUpdated() {
|
_shipUpdated() {
|
||||||
let { ship, shipId, buildName, cargo, fuel } = this.state;
|
let { ship } = this.state;
|
||||||
if (cargo > ship.cargoCapacity) {
|
const code = ship.compress();
|
||||||
cargo = ship.cargoCapacity;
|
|
||||||
}
|
|
||||||
if (fuel > ship.fuelCapacity) {
|
|
||||||
fuel = ship.fuelCapacity;
|
|
||||||
}
|
|
||||||
const code = this._fullCode(ship, fuel, cargo);
|
|
||||||
// Only update the state if this really has been updated
|
// Only update the state if this really has been updated
|
||||||
if (
|
if (this.state.code != code) {
|
||||||
this.state.code != code ||
|
this.setState({ code }, () => this._setRoute());
|
||||||
this.state.cargo != cargo ||
|
|
||||||
this.state.fuel != fuel
|
|
||||||
) {
|
|
||||||
this.setState({ code, cargo, fuel }, () =>
|
|
||||||
this._updateRoute(shipId, buildName, code)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -643,8 +423,9 @@ export default class OutfittingPage extends Page {
|
|||||||
* @param {string} buildName Current build name
|
* @param {string} buildName Current build name
|
||||||
* @param {string} code Serialized ship 'code'
|
* @param {string} code Serialized ship 'code'
|
||||||
*/
|
*/
|
||||||
_updateRoute(shipId, buildName, code) {
|
_setRoute() {
|
||||||
Router.replace(outfitURL(shipId, code, buildName));
|
const { ship, code, buildName } = this.state;
|
||||||
|
Router.replace(outfitURL(ship.getShipType(), code, buildName));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -747,123 +528,93 @@ export default class OutfittingPage extends Page {
|
|||||||
*/
|
*/
|
||||||
renderPage() {
|
renderPage() {
|
||||||
let state = this.state,
|
let state = this.state,
|
||||||
{ language, termtip, tooltip, sizeRatio, onWindowResize } = this.context,
|
{ language, termtip, tooltip, sizeRatio } = this.context,
|
||||||
{ translate, units, formats } = language,
|
{ translate } = language,
|
||||||
{
|
{
|
||||||
ship,
|
ship,
|
||||||
code,
|
code,
|
||||||
savedCode,
|
savedCode,
|
||||||
buildName,
|
buildName,
|
||||||
newBuildName,
|
newBuildName,
|
||||||
sys,
|
// opponent,
|
||||||
eng,
|
// opponentBuild,
|
||||||
wep,
|
// opponentSys,
|
||||||
mcSys,
|
// opponentEng,
|
||||||
mcEng,
|
// opponentWep,
|
||||||
mcWep,
|
// engagementRange
|
||||||
boost,
|
|
||||||
fuel,
|
|
||||||
cargo,
|
|
||||||
opponent,
|
|
||||||
opponentBuild,
|
|
||||||
opponentSys,
|
|
||||||
opponentEng,
|
|
||||||
opponentWep,
|
|
||||||
engagementRange
|
|
||||||
} = state,
|
} = state,
|
||||||
hide = tooltip.bind(null, null),
|
hide = tooltip.bind(null, null),
|
||||||
menu = this.props.currentMenu,
|
menu = this.props.currentMenu,
|
||||||
shipUpdated = this._shipUpdated,
|
|
||||||
canSave = (newBuildName || buildName) && code !== savedCode,
|
canSave = (newBuildName || buildName) && code !== savedCode,
|
||||||
canRename = buildName && newBuildName && buildName != newBuildName,
|
canRename = buildName && newBuildName && buildName != newBuildName,
|
||||||
canReload = savedCode && canSave;
|
canReload = savedCode && canSave;
|
||||||
|
|
||||||
// Code can be blank for a default loadout. Prefix it with the ship name to ensure that changes in default ships is picked up
|
// const requirements = Ships[ship.id].requirements;
|
||||||
code = ship.name + (code || '');
|
// let requirementElements = [];
|
||||||
|
// /**
|
||||||
|
// * Render the requirements for a ship / etc
|
||||||
|
// * @param {string} className Class names
|
||||||
|
// * @param {string} textKey The key for translating
|
||||||
|
// * @param {String} tooltipTextKey Tooltip key
|
||||||
|
// */
|
||||||
|
// 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>
|
||||||
|
// );
|
||||||
|
// } else {
|
||||||
|
// requirementElements.push(
|
||||||
|
// <div
|
||||||
|
// key={textKey}
|
||||||
|
// className={className}
|
||||||
|
// onMouseEnter={termtip.bind(null, tooltipTextKey)}
|
||||||
|
// onMouseLeave={hide}
|
||||||
|
// >
|
||||||
|
// {translate(textKey)}
|
||||||
|
// </div>
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
// Markers are used to propagate state changes without requiring a deep comparison of the ship, as that takes a long time
|
// if (requirements) {
|
||||||
const _sStr = ship.getStandardString();
|
// requirements.federationRank &&
|
||||||
const _iStr = ship.getInternalString();
|
// renderRequirement(
|
||||||
const _hStr = ship.getHardpointsString();
|
// 'federation',
|
||||||
const _pStr = `${ship.getPowerEnabledString()}${ship.getPowerPrioritiesString()}`;
|
// 'federation rank ' + requirements.federationRank,
|
||||||
const _mStr = ship.getModificationsString();
|
// 'federation rank required'
|
||||||
|
// );
|
||||||
const standardSlotMarker = `${ship.name}${_sStr}${_pStr}${_mStr}${
|
// requirements.empireRank &&
|
||||||
ship.ladenMass
|
// renderRequirement(
|
||||||
}${cargo}${fuel}`;
|
// 'empire',
|
||||||
const internalSlotMarker = `${ship.name}${_iStr}${_pStr}${_mStr}`;
|
// 'empire rank ' + requirements.empireRank,
|
||||||
const hardpointsSlotMarker = `${ship.name}${_hStr}${_pStr}${_mStr}`;
|
// 'empire rank required'
|
||||||
const boostMarker = `${ship.canBoost(cargo, fuel)}`;
|
// );
|
||||||
const shipSummaryMarker = `${
|
// requirements.horizons &&
|
||||||
ship.name
|
// renderRequirement('horizons', 'horizons', 'horizons required');
|
||||||
}${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`;
|
// requirements.horizonsEarlyAdoption &&
|
||||||
|
// renderRequirement(
|
||||||
const requirements = Ships[ship.id].requirements;
|
// 'horizons',
|
||||||
let requirementElements = [];
|
// 'horizons early adoption',
|
||||||
/**
|
// 'horizons early adoption required'
|
||||||
* Render the requirements for a ship / etc
|
// );
|
||||||
* @param {string} className Class names
|
// }
|
||||||
* @param {string} textKey The key for translating
|
|
||||||
* @param {String} tooltipTextKey Tooltip key
|
|
||||||
*/
|
|
||||||
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>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
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'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -872,8 +623,8 @@ export default class OutfittingPage extends Page {
|
|||||||
style={{ fontSize: sizeRatio * 0.9 + 'em' }}
|
style={{ fontSize: sizeRatio * 0.9 + 'em' }}
|
||||||
>
|
>
|
||||||
<div id="overview">
|
<div id="overview">
|
||||||
<h1>{ship.name}</h1>
|
<h1>{ship.getShipType()}</h1>
|
||||||
<div id="requirements">{requirementElements}</div>
|
{/* <div id="requirements">{requirementElements}</div> */}
|
||||||
<div id="build">
|
<div id="build">
|
||||||
<input
|
<input
|
||||||
value={newBuildName || ''}
|
value={newBuildName || ''}
|
||||||
@@ -933,7 +684,7 @@ export default class OutfittingPage extends Page {
|
|||||||
<Download className="lg" />
|
<Download className="lg" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={this._eddbShoppingList}
|
// onClick={this._eddbShoppingList}
|
||||||
onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_LIST')}
|
onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_LIST')}
|
||||||
onMouseOut={hide}
|
onMouseOut={hide}
|
||||||
>
|
>
|
||||||
@@ -954,7 +705,7 @@ export default class OutfittingPage extends Page {
|
|||||||
<OrbisIcon className="lg" />
|
<OrbisIcon className="lg" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={this._genShoppingList}
|
// onClick={this._genShoppingList}
|
||||||
onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_MATS')}
|
onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_MATS')}
|
||||||
onMouseOut={hide}
|
onMouseOut={hide}
|
||||||
>
|
>
|
||||||
@@ -964,58 +715,18 @@ export default class OutfittingPage extends Page {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Main tables */}
|
{/* Main tables */}
|
||||||
<ShipSummaryTable
|
<ShipSummaryTable ship={ship} code={code} />
|
||||||
ship={ship}
|
<StandardSlotSection ship={ship} code={code} currentMenu={menu}
|
||||||
fuel={fuel}
|
propsToShow={propsToShow} onPropToggle={this._propToShowToggled} />
|
||||||
cargo={cargo}
|
<InternalSlotSection ship={ship} code={code} currentMenu={menu}
|
||||||
marker={shipSummaryMarker}
|
propsToShow={propsToShow} onPropToggle={this._propToShowToggled} />
|
||||||
pips={{
|
<HardpointSlotSection ship={ship} code={code} currentMenu={menu}
|
||||||
sys: this.state.sys,
|
propsToShow={propsToShow} onPropToggle={this._propToShowToggled} />
|
||||||
wep: this.state.wep,
|
<UtilitySlotSection ship={ship} code={code} currentMenu={menu}
|
||||||
eng: this.state.eng
|
propsToShow={propsToShow} onPropToggle={this._propToShowToggled} />
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<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 */}
|
{/* Control of ship and opponent */}
|
||||||
<div className="group quarter">
|
{/* <div className="group quarter">
|
||||||
<div className="group half">
|
<div className="group half">
|
||||||
<h2 style={{ verticalAlign: 'middle', textAlign: 'left' }}>
|
<h2 style={{ verticalAlign: 'middle', textAlign: 'left' }}>
|
||||||
{translate('ship control')}
|
{translate('ship control')}
|
||||||
@@ -1044,7 +755,6 @@ export default class OutfittingPage extends Page {
|
|||||||
<div className="group quarter">
|
<div className="group quarter">
|
||||||
<Fuel
|
<Fuel
|
||||||
fuelCapacity={ship.fuelCapacity}
|
fuelCapacity={ship.fuelCapacity}
|
||||||
fuel={fuel}
|
|
||||||
onChange={this._fuelUpdated}
|
onChange={this._fuelUpdated}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -1052,7 +762,6 @@ export default class OutfittingPage extends Page {
|
|||||||
{ship.cargoCapacity > 0 ? (
|
{ship.cargoCapacity > 0 ? (
|
||||||
<Cargo
|
<Cargo
|
||||||
cargoCapacity={ship.cargoCapacity}
|
cargoCapacity={ship.cargoCapacity}
|
||||||
cargo={cargo}
|
|
||||||
onChange={this._cargoUpdated}
|
onChange={this._cargoUpdated}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -1077,10 +786,10 @@ export default class OutfittingPage extends Page {
|
|||||||
engagementRange={engagementRange}
|
engagementRange={engagementRange}
|
||||||
onChange={this._engagementRangeUpdated}
|
onChange={this._engagementRangeUpdated}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div> */}
|
||||||
|
|
||||||
{/* Tabbed subpages */}
|
{/* Tabbed subpages */}
|
||||||
<OutfittingSubpages
|
{/* <OutfittingSubpages
|
||||||
ship={ship}
|
ship={ship}
|
||||||
code={code}
|
code={code}
|
||||||
buildName={buildName}
|
buildName={buildName}
|
||||||
@@ -1097,7 +806,7 @@ export default class OutfittingPage extends Page {
|
|||||||
opponentSys={opponentSys}
|
opponentSys={opponentSys}
|
||||||
opponentEng={opponentEng}
|
opponentEng={opponentEng}
|
||||||
opponentWep={opponentWep}
|
opponentWep={opponentWep}
|
||||||
/>
|
/> */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { shallowEqual } from '../utils/UtilityFunctions';
|
|||||||
* Abstract/Base Page
|
* Abstract/Base Page
|
||||||
*/
|
*/
|
||||||
export default class Page extends React.Component {
|
export default class Page extends React.Component {
|
||||||
|
|
||||||
static contextTypes = {
|
static contextTypes = {
|
||||||
closeMenu: PropTypes.func.isRequired,
|
closeMenu: PropTypes.func.isRequired,
|
||||||
hideModal: PropTypes.func.isRequired,
|
hideModal: PropTypes.func.isRequired,
|
||||||
@@ -50,19 +49,6 @@ export default class Page extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Pages are 'pure' components that only render when props, state, or context changes.
|
|
||||||
* This method performs a shallow comparison to determine change.
|
|
||||||
*
|
|
||||||
* @param {Object} np Next/Incoming properties
|
|
||||||
* @param {Object} ns Next/Incoming state
|
|
||||||
* @param {Object} nc Next/Incoming context
|
|
||||||
* @return {Boolean} True if props, state, or context has changed
|
|
||||||
*/
|
|
||||||
shouldComponentUpdate(np, ns, nc) {
|
|
||||||
return !shallowEqual(this.props, np) || !shallowEqual(this.state, ns) || !shallowEqual(this.context, nc);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the window title upon mount
|
* Update the window title upon mount
|
||||||
*/
|
*/
|
||||||
@@ -97,5 +83,4 @@ export default class Page extends React.Component {
|
|||||||
}
|
}
|
||||||
return this.renderPage();
|
return this.renderPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,102 +1,81 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Page from './Page';
|
import Page from './Page';
|
||||||
import { Ships } from 'coriolis-data/dist';
|
|
||||||
import cn from 'classnames';
|
import cn from 'classnames';
|
||||||
import Ship from '../shipyard/Ship';
|
import { Factory } from 'ed-forge';
|
||||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
import { JUMP_METRICS } from 'ed-forge/lib/ship-stats';
|
||||||
import { SizeMap } from '../shipyard/Constants';
|
import { SizeMap } from '../shipyard/Constants';
|
||||||
import Link from '../components/Link';
|
import Link from '../components/Link';
|
||||||
|
|
||||||
/**
|
|
||||||
* Counts the hardpoints by class/size
|
|
||||||
* @param {Object} slot Hardpoint Slot model
|
|
||||||
*/
|
|
||||||
function countHp(slot) {
|
|
||||||
this.hp[slot.maxClass]++;
|
|
||||||
this.hpCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Counts the internal slots and aggregated properties
|
|
||||||
* @param {Object} slot Internal Slots
|
|
||||||
*/
|
|
||||||
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.intCount++;
|
|
||||||
this.maxCargo += crEligible ?
|
|
||||||
ModuleUtils.findInternal('cr', slot.maxClass, 'E').cargo :
|
|
||||||
0;
|
|
||||||
|
|
||||||
// if no eligiblity, then assume pce
|
|
||||||
let passSlotType = null;
|
|
||||||
let passSlotRating = null;
|
|
||||||
if (!slot.eligible || slot.eligible.pce) {
|
|
||||||
passSlotType = 'pce';
|
|
||||||
passSlotRating = 'E';
|
|
||||||
} else if (slot.eligible.pci) {
|
|
||||||
passSlotType = 'pci';
|
|
||||||
passSlotRating = 'D';
|
|
||||||
} else if (slot.eligible.pcm) {
|
|
||||||
passSlotType = 'pcm';
|
|
||||||
passSlotRating = 'C';
|
|
||||||
} else if (slot.eligible.pcq) {
|
|
||||||
passSlotType = 'pcq';
|
|
||||||
passSlotRating = 'B';
|
|
||||||
}
|
|
||||||
let passengerBay = passSlotType ?
|
|
||||||
ModuleUtils.findMaxInternal(passSlotType, slot.maxClass, passSlotRating) :
|
|
||||||
null;
|
|
||||||
this.maxPassengers += passengerBay ? passengerBay.passengers : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate Ship summary and aggregated properties
|
* Generate Ship summary and aggregated properties
|
||||||
* @param {String} shipId Ship Id
|
* @param {String} shipId Ship Id
|
||||||
* @param {Object} shipData Ship Default Data
|
|
||||||
* @return {Object} Ship summary and aggregated properties
|
* @return {Object} Ship summary and aggregated properties
|
||||||
*/
|
*/
|
||||||
function shipSummary(shipId, shipData) {
|
function shipSummary(shipId) {
|
||||||
|
// Build Ship
|
||||||
|
let ship = Factory.newShip(shipId);
|
||||||
|
|
||||||
|
let coreSizes = ship.readMeta('coreSizes');
|
||||||
|
let { jumpRange, totalRange } = ship.getMetrics(JUMP_METRICS);
|
||||||
let summary = {
|
let summary = {
|
||||||
|
agility: ship.readProp('pitch') + ship.readProp('yaw') + ship.readProp('roll'),
|
||||||
|
baseArmour: ship.readProp('basearmour'),
|
||||||
|
baseShieldStrength: ship.readProp('baseshieldstrength'),
|
||||||
|
boost: ship.readProp('boost'),
|
||||||
|
class: ship.readMeta('class'),
|
||||||
|
crew: ship.readMeta('crew'),
|
||||||
id: shipId,
|
id: shipId,
|
||||||
|
hardness: ship.readProp('hardness'),
|
||||||
hpCount: 0,
|
hpCount: 0,
|
||||||
|
hullMass: ship.readProp('hullmass'),
|
||||||
intCount: 0,
|
intCount: 0,
|
||||||
beta: shipData.beta,
|
masslock: ship.readProp('masslock'),
|
||||||
maxCargo: 0,
|
|
||||||
maxPassengers: 0,
|
|
||||||
hp: [0, 0, 0, 0, 0], // Utility, Small, Medium, Large, Huge
|
hp: [0, 0, 0, 0, 0], // Utility, Small, Medium, Large, Huge
|
||||||
int: [0, 0, 0, 0, 0, 0, 0, 0], // Sizes 1 - 8
|
int: [0, 0, 0, 0, 0, 0, 0, 0], // Sizes 1 - 8
|
||||||
standard: shipData.slots.standard,
|
jumpRange,
|
||||||
agility:
|
pitch: ship.readProp('pitch'),
|
||||||
shipData.properties.pitch +
|
retailCost: ship.readMeta('retailCost'),
|
||||||
shipData.properties.yaw +
|
roll: ship.readProp('roll'),
|
||||||
shipData.properties.roll
|
speed: ship.readProp('speed'),
|
||||||
|
standard: [
|
||||||
|
'powerplant',
|
||||||
|
'mainengines',
|
||||||
|
'frameshiftdrive',
|
||||||
|
'lifesupport',
|
||||||
|
'powerdistributor',
|
||||||
|
'radar',
|
||||||
|
'fueltank'
|
||||||
|
].map(k => coreSizes[k]),
|
||||||
|
totalRange,
|
||||||
|
yaw: ship.readProp('yaw'),
|
||||||
};
|
};
|
||||||
Object.assign(summary, shipData.properties);
|
|
||||||
let ship = new Ship(shipId, shipData.properties, shipData.slots);
|
|
||||||
|
|
||||||
// Build Ship
|
// Count Hardpoints by class
|
||||||
ship.buildWith(shipData.defaults); // Populate with stock/default components
|
ship.getHardpoints(undefined, true).forEach(hardpoint => {
|
||||||
ship.hardpoints.forEach(countHp.bind(summary)); // Count Hardpoints by class
|
summary.hp[hardpoint.getSize()]++;
|
||||||
ship.internal.forEach(countInt.bind(summary)); // Count Internal Compartments by class
|
summary.hpCount++;
|
||||||
summary.retailCost = ship.totalCost; // Record Stock/Default/retail cost
|
});
|
||||||
ship.optimizeMass({ pd: '1D' }); // Optimize Mass with 1D PD for maximum possible jump range
|
// Count Internal Compartments by class
|
||||||
summary.maxJumpRange = ship.unladenRange; // Record Jump Range
|
let maxCargo = 0, maxPassengers = 0;
|
||||||
|
ship.getInternals(undefined, true).forEach(internal => {
|
||||||
|
const size = String(internal.getSize());
|
||||||
|
summary.int[size]++;
|
||||||
|
summary.intCount++;
|
||||||
|
|
||||||
// Best thrusters
|
// Try cargo racks
|
||||||
let th;
|
try {
|
||||||
if (ship.standard[1].maxClass === 3) {
|
internal.setItem('cargorack', size);
|
||||||
th = 'tz';
|
maxCargo += internal.get('cargo');
|
||||||
} else if (ship.standard[1].maxClass === 2) {
|
} catch {}
|
||||||
th = 'u0';
|
// Try economy cabins
|
||||||
} else {
|
try {
|
||||||
th = ship.standard[1].maxClass + 'A';
|
internal.setItem('passengercabins', size < '6' ? size : '6', '1');
|
||||||
}
|
maxPassengers += internal.get('cabincapacity');
|
||||||
|
} catch {}
|
||||||
|
});
|
||||||
|
|
||||||
ship.optimizeMass({ th, fsd: '2D', ft: '1C' }); // Optmize mass with Max Thrusters
|
summary.maxCargo = maxCargo;
|
||||||
summary.topSpeed = ship.topSpeed;
|
summary.maxPassengers = maxPassengers;
|
||||||
summary.topBoost = ship.topBoost;
|
|
||||||
summary.baseArmour = ship.armour;
|
|
||||||
|
|
||||||
return summary;
|
return summary;
|
||||||
}
|
}
|
||||||
@@ -117,21 +96,23 @@ export default class ShipyardPage extends Page {
|
|||||||
|
|
||||||
if (!ShipyardPage.cachedShipSummaries) {
|
if (!ShipyardPage.cachedShipSummaries) {
|
||||||
ShipyardPage.cachedShipSummaries = [];
|
ShipyardPage.cachedShipSummaries = [];
|
||||||
for (let s in Ships) {
|
for (let s of Factory.getAllShipTypes()) {
|
||||||
ShipyardPage.cachedShipSummaries.push(shipSummary(s, Ships[s]));
|
ShipyardPage.cachedShipSummaries.push(shipSummary(s));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
title: 'Coriolis EDCD Edition - Shipyard',
|
title: 'Coriolis EDCD Edition - Shipyard',
|
||||||
shipPredicate: 'name',
|
shipPredicate: 'id',
|
||||||
shipDesc: true,
|
shipDesc: true,
|
||||||
shipSummaries: ShipyardPage.cachedShipSummaries
|
shipSummaries: ShipyardPage.cachedShipSummaries,
|
||||||
|
compare: {},
|
||||||
|
groupCompared: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Higlight the current ship in the table
|
* Higlight the current ship in the table on mouse over
|
||||||
* @param {String} shipId Ship Id
|
* @param {String} shipId Ship Id
|
||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
*/
|
*/
|
||||||
@@ -140,6 +121,24 @@ export default class ShipyardPage extends Page {
|
|||||||
this.setState({ shipId });
|
this.setState({ shipId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle compare highlighting for ships in the table
|
||||||
|
* @param {String} shipId Ship Id
|
||||||
|
*/
|
||||||
|
_toggleCompare(shipId) {
|
||||||
|
let compare = this.state.compare;
|
||||||
|
compare[shipId] = !compare[shipId];
|
||||||
|
this.setState({ compare });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle grouping of compared ships in the table
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_toggleGroupCompared() {
|
||||||
|
this.setState({ groupCompared: !this.state.groupCompared });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update state with the specified sort predicates
|
* Update state with the specified sort predicates
|
||||||
* @param {String} shipPredicate Sort predicate - property name
|
* @param {String} shipPredicate Sort predicate - property name
|
||||||
@@ -180,24 +179,27 @@ export default class ShipyardPage extends Page {
|
|||||||
style={{ height: '1.5em' }}
|
style={{ height: '1.5em' }}
|
||||||
className={cn({
|
className={cn({
|
||||||
highlighted: noTouch && this.state.shipId === s.id,
|
highlighted: noTouch && this.state.shipId === s.id,
|
||||||
|
comparehighlight: this.state.compare[s.id],
|
||||||
})}
|
})}
|
||||||
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
|
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
|
||||||
|
onClick={() => this._toggleCompare(s.id)}
|
||||||
>
|
>
|
||||||
<td className="ri">{s.manufacturer}</td>
|
|
||||||
<td className="ri">{fInt(s.retailCost)}</td>
|
<td className="ri">{fInt(s.retailCost)}</td>
|
||||||
<td className="ri cap">{translate(SizeMap[s.class])}</td>
|
<td className="ri cap">{translate(SizeMap[s.class])}</td>
|
||||||
<td className="ri">{fInt(s.crew)}</td>
|
<td className="ri">{fInt(s.crew)}</td>
|
||||||
<td className="ri">{s.masslock}</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.hullMass)}</td>
|
||||||
|
<td className="ri">{fInt(s.agility)}</td>
|
||||||
<td className="ri">{fInt(s.speed)}</td>
|
<td className="ri">{fInt(s.speed)}</td>
|
||||||
<td className="ri">{fInt(s.boost)}</td>
|
<td className="ri">{fInt(s.boost)}</td>
|
||||||
|
<td className="ri">{fInt(s.pitch)}</td>
|
||||||
|
<td className="ri">{fInt(s.yaw)}</td>
|
||||||
|
<td className="ri">{fInt(s.roll)}</td>
|
||||||
|
<td className="ri">{fRound(s.jumpRange)}</td>
|
||||||
|
<td className="ri">{fRound(s.totalRange)}</td>
|
||||||
|
<td className="ri">{fInt(s.hardness)}</td>
|
||||||
<td className="ri">{fInt(s.baseArmour)}</td>
|
<td className="ri">{fInt(s.baseArmour)}</td>
|
||||||
<td className="ri">{fInt(s.baseShieldStrength)}</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.maxCargo)}</td>
|
||||||
<td className="ri">{fInt(s.maxPassengers)}</td>
|
<td className="ri">{fInt(s.maxPassengers)}</td>
|
||||||
<td className="cn">{s.standard[0]}</td>
|
<td className="cn">{s.standard[0]}</td>
|
||||||
@@ -233,8 +235,7 @@ export default class ShipyardPage extends Page {
|
|||||||
let { translate, formats, units } = language;
|
let { translate, formats, units } = language;
|
||||||
let hide = this.context.tooltip.bind(null, null);
|
let hide = this.context.tooltip.bind(null, null);
|
||||||
let fInt = formats.int;
|
let fInt = formats.int;
|
||||||
let fRound = formats.round;
|
let { shipSummaries, shipPredicate, shipPredicateIndex, compare, groupCompared } = this.state;
|
||||||
let { shipSummaries, shipPredicate, shipPredicateIndex } = this.state;
|
|
||||||
let sortShips = (predicate, index) =>
|
let sortShips = (predicate, index) =>
|
||||||
this._sortShips.bind(this, predicate, index);
|
this._sortShips.bind(this, predicate, index);
|
||||||
|
|
||||||
@@ -267,8 +268,17 @@ export default class ShipyardPage extends Page {
|
|||||||
valB = val;
|
valB = val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (groupCompared) {
|
||||||
|
if (compare[a.id] && !compare[b.id]) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (!compare[a.id] && compare[b.id]) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (valA == valB) {
|
if (valA == valB) {
|
||||||
if (a.name > b.name) {
|
if (a.id > b.id) {
|
||||||
return 1;
|
return 1;
|
||||||
} else {
|
} else {
|
||||||
return -1;
|
return -1;
|
||||||
@@ -298,11 +308,13 @@ export default class ShipyardPage extends Page {
|
|||||||
style={{ height: '1.5em' }}
|
style={{ height: '1.5em' }}
|
||||||
className={cn({
|
className={cn({
|
||||||
highlighted: noTouch && this.state.shipId === s.id,
|
highlighted: noTouch && this.state.shipId === s.id,
|
||||||
|
comparehighlight: this.state.compare[s.id],
|
||||||
})}
|
})}
|
||||||
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
|
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
|
||||||
|
onClick={() => this._toggleCompare(s.id)}
|
||||||
>
|
>
|
||||||
<td className="le">
|
<td className="le">
|
||||||
<Link href={'/outfit/' + s.id}>{s.name} {s.beta === true ? '(Beta)' : null}</Link>
|
<Link href={'/outfit/' + s.id}>{s.id} {s.beta === true ? '(Beta)' : null}</Link>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
);
|
);
|
||||||
@@ -311,23 +323,15 @@ export default class ShipyardPage extends Page {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page" style={{ fontSize: sizeRatio + 'em' }}>
|
<div className="page" style={{ fontSize: sizeRatio + 'em' }}>
|
||||||
<div
|
<div className="content-wrapper">
|
||||||
style={{
|
<div className="shipyard-table-wrapper">
|
||||||
whiteSpace: 'nowrap',
|
<table style={{ width: '12em', position: 'absolute', zIndex: 1 }} className="shipyard-table">
|
||||||
margin: '0 auto',
|
|
||||||
fontSize: '0.8em',
|
|
||||||
position: 'relative',
|
|
||||||
display: 'inline-block',
|
|
||||||
maxWidth: '100%'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<table style={{ width: '12em', position: 'absolute', zIndex: 1 }}>
|
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th className="le rgt"> </th>
|
<th className="le rgt"> </th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr className="main">
|
<tr className="main">
|
||||||
<th className="sortable le rgt" onClick={sortShips('name')}>
|
<th className="sortable le rgt" onClick={sortShips('id')}>
|
||||||
{translate('ship')}
|
{translate('ship')}
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -339,114 +343,91 @@ export default class ShipyardPage extends Page {
|
|||||||
{shipRows}
|
{shipRows}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<div style={{ overflowX: 'scroll', maxWidth: '100%' }}>
|
<div style={{ overflowX: 'auto', maxWidth: '100%' }}>
|
||||||
<table style={{ marginLeft: 'calc(12em - 1px)', zIndex: 0 }}>
|
<table style={{ marginLeft: 'calc(12em - 1px)', zIndex: 0 }} className="shipyard-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="main">
|
<tr className="main">
|
||||||
<th
|
{/* First all headers that spread out over three rows */}
|
||||||
rowSpan={3}
|
{/* cost placeholder */}
|
||||||
className="sortable"
|
|
||||||
onClick={sortShips('manufacturer')}
|
|
||||||
>
|
|
||||||
{translate('manufacturer')}
|
|
||||||
</th>
|
|
||||||
<th> </th>
|
<th> </th>
|
||||||
<th
|
<th rowSpan={3} className="sortable" onClick={sortShips('class')}>
|
||||||
rowSpan={3}
|
|
||||||
className="sortable"
|
|
||||||
onClick={sortShips('class')}
|
|
||||||
>
|
|
||||||
{translate('size')}
|
{translate('size')}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th rowSpan={3} className="sortable" onClick={sortShips('crew')}>
|
||||||
rowSpan={3}
|
|
||||||
className="sortable"
|
|
||||||
onClick={sortShips('crew')}
|
|
||||||
>
|
|
||||||
{translate('crew')}
|
{translate('crew')}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th rowSpan={3} className="sortable"
|
||||||
rowSpan={3}
|
|
||||||
className="sortable"
|
|
||||||
onMouseEnter={termtip.bind(null, 'mass lock factor')}
|
onMouseEnter={termtip.bind(null, 'mass lock factor')}
|
||||||
onMouseLeave={hide}
|
onMouseLeave={hide} onClick={sortShips('masslock')}>
|
||||||
onClick={sortShips('masslock')}
|
|
||||||
>
|
|
||||||
{translate('MLF')}
|
{translate('MLF')}
|
||||||
</th>
|
</th>
|
||||||
<th
|
{/* hull mass placeholder */}
|
||||||
rowSpan={3}
|
<th> </th>
|
||||||
className="sortable"
|
<th colSpan={6}>{translate('agility')}</th>
|
||||||
onClick={sortShips('agility')}
|
<th colSpan={2}>{translate('travel')}</th>
|
||||||
>
|
<th colSpan={3}>{translate('defence')}</th>
|
||||||
{translate('agility')}
|
{/* cargo placeholder */}
|
||||||
</th>
|
<th> </th>
|
||||||
<th
|
{/* pax placeholder */}
|
||||||
rowSpan={3}
|
|
||||||
className="sortable"
|
|
||||||
onMouseEnter={termtip.bind(null, 'hardness')}
|
|
||||||
onMouseLeave={hide}
|
|
||||||
onClick={sortShips('hardness')}
|
|
||||||
>
|
|
||||||
{translate('hrd')}
|
|
||||||
</th>
|
|
||||||
<th> </th>
|
<th> </th>
|
||||||
<th colSpan={4}>{translate('base')}</th>
|
|
||||||
<th colSpan={5}>{translate('max')}</th>
|
|
||||||
<th className="lft" colSpan={7} />
|
<th className="lft" colSpan={7} />
|
||||||
<th className="lft" colSpan={5} />
|
<th className="lft" colSpan={5} />
|
||||||
<th className="lft" colSpan={8} />
|
<th className="lft" colSpan={8} />
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th
|
{/* Now all headers in a second-row */}
|
||||||
className="sortable lft"
|
<th className="sortable lft" onClick={sortShips('retailCost')}>
|
||||||
onClick={sortShips('retailCost')}
|
|
||||||
>
|
|
||||||
{translate('cost')}
|
{translate('cost')}
|
||||||
</th>
|
</th>
|
||||||
<th className="sortable lft" onClick={sortShips('hullMass')}>
|
<th className="sortable lft" onClick={sortShips('hullMass')}>
|
||||||
{translate('hull')}
|
{translate('hull')}
|
||||||
</th>
|
</th>
|
||||||
<th className="sortable lft" onClick={sortShips('speed')}>
|
<th className="sortable lft" onClick={sortShips('agility')}>
|
||||||
|
{translate('rating')}
|
||||||
|
</th>
|
||||||
|
<th className="sortable" onClick={sortShips('speed')}>
|
||||||
{translate('speed')}
|
{translate('speed')}
|
||||||
</th>
|
</th>
|
||||||
<th className="sortable" onClick={sortShips('boost')}>
|
<th className="sortable" onClick={sortShips('boost')}>
|
||||||
{translate('boost')}
|
{translate('boost')}
|
||||||
</th>
|
</th>
|
||||||
|
<th className="sortable" onClick={sortShips('pitch')}>
|
||||||
|
{translate('pitch')}
|
||||||
|
</th>
|
||||||
|
<th className="sortable" onClick={sortShips('yaw')}>
|
||||||
|
{translate('yaw')}
|
||||||
|
</th>
|
||||||
|
<th className="sortable" onClick={sortShips('roll')}>
|
||||||
|
{translate('roll')}
|
||||||
|
</th>
|
||||||
|
<th className="sortable lft" onClick={sortShips('jumpRange')}>
|
||||||
|
{translate('jump')}
|
||||||
|
</th>
|
||||||
|
<th className="sortable" onClick={sortShips('totalRange')}>
|
||||||
|
{translate('range')}
|
||||||
|
</th>
|
||||||
|
<th className="sortable lft" onMouseEnter={termtip.bind(null, 'hardness')}
|
||||||
|
onMouseLeave={hide} onClick={sortShips('hardness')}>
|
||||||
|
{translate('hrd')}
|
||||||
|
</th>
|
||||||
<th className="sortable" onClick={sortShips('baseArmour')}>
|
<th className="sortable" onClick={sortShips('baseArmour')}>
|
||||||
{translate('armour')}
|
{translate('armour')}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th className="sortable" onClick={sortShips('baseShieldStrength')}>
|
||||||
className="sortable"
|
|
||||||
onClick={sortShips('baseShieldStrength')}
|
|
||||||
>
|
|
||||||
{translate('shields')}
|
{translate('shields')}
|
||||||
</th>
|
</th>
|
||||||
|
|
||||||
<th className="sortable lft" onClick={sortShips('topSpeed')}>
|
<th className="sortable lft" onClick={sortShips('maxCargo')}>
|
||||||
{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')}
|
{translate('cargo')}
|
||||||
</th>
|
</th>
|
||||||
<th className="sortable" onClick={sortShips('maxPassengers')} onMouseEnter={termtip.bind(null, 'passenger capacity')}
|
<th className="sortable lft" onClick={sortShips('maxPassengers')}
|
||||||
onMouseLeave={hide}>
|
onMouseEnter={termtip.bind(null, 'passenger capacity')} onMouseLeave={hide}>
|
||||||
{translate('pax')}
|
{translate('pax')}
|
||||||
</th>
|
</th>
|
||||||
<th className="lft" colSpan={7}>
|
<th className="lft" colSpan={7}>
|
||||||
{translate('core module classes')}
|
{translate('core module classes')}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th colSpan={5} className="sortable lft" onClick={sortShips('hpCount')}>
|
||||||
colSpan={5}
|
|
||||||
className="sortable lft"
|
|
||||||
onClick={sortShips('hpCount')}
|
|
||||||
>
|
|
||||||
{translate('hardpoints')}
|
{translate('hardpoints')}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
@@ -458,6 +439,7 @@ export default class ShipyardPage extends Page {
|
|||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
{/* Third row headers, i.e., units */}
|
||||||
<th
|
<th
|
||||||
className="sortable lft"
|
className="sortable lft"
|
||||||
onClick={sortShips('retailCost')}
|
onClick={sortShips('retailCost')}
|
||||||
@@ -467,12 +449,31 @@ export default class ShipyardPage extends Page {
|
|||||||
<th className="sortable lft" onClick={sortShips('hullMass')}>
|
<th className="sortable lft" onClick={sortShips('hullMass')}>
|
||||||
{units.T}
|
{units.T}
|
||||||
</th>
|
</th>
|
||||||
<th className="sortable lft" onClick={sortShips('speed')}>
|
{/* agility rating placeholder */}
|
||||||
|
<th className="lft"> </th>
|
||||||
|
<th className="sortable" onClick={sortShips('speed')}>
|
||||||
{units['m/s']}
|
{units['m/s']}
|
||||||
</th>
|
</th>
|
||||||
<th className="sortable" onClick={sortShips('boost')}>
|
<th className="sortable" onClick={sortShips('boost')}>
|
||||||
{units['m/s']}
|
{units['m/s']}
|
||||||
</th>
|
</th>
|
||||||
|
<th className="sortable" onClick={sortShips('pitch')}>
|
||||||
|
{units['°/s']}
|
||||||
|
</th>
|
||||||
|
<th className="sortable" onClick={sortShips('yaw')}>
|
||||||
|
{units['°/s']}
|
||||||
|
</th>
|
||||||
|
<th className="sortable" onClick={sortShips('roll')}>
|
||||||
|
{units['°/s']}
|
||||||
|
</th>
|
||||||
|
<th className="sortable lft" onClick={sortShips('jumpRange')}>
|
||||||
|
{units.LY}
|
||||||
|
</th>
|
||||||
|
<th className="sortable" onClick={sortShips('totalRange')}>
|
||||||
|
{units.LY}
|
||||||
|
</th>
|
||||||
|
<th className="lft"> </th>
|
||||||
|
{/* armour placeholder */}
|
||||||
<th> </th>
|
<th> </th>
|
||||||
<th
|
<th
|
||||||
className="sortable"
|
className="sortable"
|
||||||
@@ -480,73 +481,37 @@ export default class ShipyardPage extends Page {
|
|||||||
>
|
>
|
||||||
{units.MJ}
|
{units.MJ}
|
||||||
</th>
|
</th>
|
||||||
<th className="sortable lft" onClick={sortShips('topSpeed')}>
|
<th className="sortable lft" onClick={sortShips('maxCargo')}>
|
||||||
{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}
|
{units.T}
|
||||||
</th>
|
</th>
|
||||||
<th> </th>
|
{/* pax placeholder */}
|
||||||
<th
|
<th className="lft"> </th>
|
||||||
className="sortable lft"
|
<th className="sortable lft" onMouseEnter={termtip.bind(null, 'power plant')}
|
||||||
onMouseEnter={termtip.bind(null, 'power plant')}
|
onMouseLeave={hide} onClick={sortShips('standard', 0)}>
|
||||||
onMouseLeave={hide}
|
|
||||||
onClick={sortShips('standard', 0)}
|
|
||||||
>
|
|
||||||
{'pp'}
|
{'pp'}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th className="sortable" onMouseEnter={termtip.bind(null, 'thrusters')}
|
||||||
className="sortable"
|
onMouseLeave={hide} onClick={sortShips('standard', 1)}>
|
||||||
onMouseEnter={termtip.bind(null, 'thrusters')}
|
|
||||||
onMouseLeave={hide}
|
|
||||||
onClick={sortShips('standard', 1)}
|
|
||||||
>
|
|
||||||
{'th'}
|
{'th'}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th className="sortable" onMouseEnter={termtip.bind(null, 'frame shift drive')}
|
||||||
className="sortable"
|
onMouseLeave={hide} onClick={sortShips('standard', 2)}>
|
||||||
onMouseEnter={termtip.bind(null, 'frame shift drive')}
|
|
||||||
onMouseLeave={hide}
|
|
||||||
onClick={sortShips('standard', 2)}
|
|
||||||
>
|
|
||||||
{'fsd'}
|
{'fsd'}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th className="sortable" onMouseEnter={termtip.bind(null, 'life support')}
|
||||||
className="sortable"
|
onMouseLeave={hide} onClick={sortShips('standard', 3)}>
|
||||||
onMouseEnter={termtip.bind(null, 'life support')}
|
|
||||||
onMouseLeave={hide}
|
|
||||||
onClick={sortShips('standard', 3)}
|
|
||||||
>
|
|
||||||
{'ls'}
|
{'ls'}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th className="sortable" onMouseEnter={termtip.bind(null, 'power distriubtor')}
|
||||||
className="sortable"
|
onMouseLeave={hide} onClick={sortShips('standard', 4)}>
|
||||||
onMouseEnter={termtip.bind(null, 'power distriubtor')}
|
|
||||||
onMouseLeave={hide}
|
|
||||||
onClick={sortShips('standard', 4)}
|
|
||||||
>
|
|
||||||
{'pd'}
|
{'pd'}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th className="sortable" onMouseEnter={termtip.bind(null, 'sensors')}
|
||||||
className="sortable"
|
onMouseLeave={hide} onClick={sortShips('standard', 5)}>
|
||||||
onMouseEnter={termtip.bind(null, 'sensors')}
|
|
||||||
onMouseLeave={hide}
|
|
||||||
onClick={sortShips('standard', 5)}
|
|
||||||
>
|
|
||||||
{'s'}
|
{'s'}
|
||||||
</th>
|
</th>
|
||||||
<th
|
<th className="sortable" onMouseEnter={termtip.bind(null, 'fuel tank')}
|
||||||
className="sortable"
|
onMouseLeave={hide} onClick={sortShips('standard', 6)}>
|
||||||
onMouseEnter={termtip.bind(null, 'fuel tank')}
|
|
||||||
onMouseLeave={hide}
|
|
||||||
onClick={sortShips('standard', 6)}
|
|
||||||
>
|
|
||||||
{'ft'}
|
{'ft'}
|
||||||
</th>
|
</th>
|
||||||
<th className="sortable lft" onClick={sortShips('hp', 1)}>
|
<th className="sortable lft" onClick={sortShips('hp', 1)}>
|
||||||
@@ -597,6 +562,10 @@ export default class ShipyardPage extends Page {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="table-tools" >
|
||||||
|
<label><input type="checkbox" checked={this.state.groupCompared} onClick={() => this._toggleGroupCompared()}/>Group highlighted ships</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export function jumpRange(mass, fsd, fuel, ship) {
|
|||||||
let jumpAddition = 0;
|
let jumpAddition = 0;
|
||||||
if (ship) {
|
if (ship) {
|
||||||
for (const module of ship.internal) {
|
for (const module of ship.internal) {
|
||||||
if (module && module.m && module.m.grp === 'gfsb') {
|
if (module && module.m && module.m.grp === 'gfsb' && ship.getSlotStatus(module) == 3) {
|
||||||
jumpAddition += module.m.getJumpBoost();
|
jumpAddition += module.m.getJumpBoost();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -400,7 +400,7 @@ export function shieldMetrics(ship, sys) {
|
|||||||
let shieldAddition = 0;
|
let shieldAddition = 0;
|
||||||
if (ship) {
|
if (ship) {
|
||||||
for (const module of ship.internal) {
|
for (const module of ship.internal) {
|
||||||
if (module && module.m && module.m.grp === 'gsrp') {
|
if (module && module.m && module.m.grp === 'gsrp' && module.enabled) {
|
||||||
shieldAddition += module.m.getShieldAddition();
|
shieldAddition += module.m.getShieldAddition();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -465,6 +465,7 @@ export function shieldMetrics(ship, sys) {
|
|||||||
boosters: boostersStrength,
|
boosters: boostersStrength,
|
||||||
addition: shieldAddition,
|
addition: shieldAddition,
|
||||||
cells: ship.shieldCells,
|
cells: ship.shieldCells,
|
||||||
|
summary: generatorStrength + boostersStrength + shieldAddition,
|
||||||
total: generatorStrength + boostersStrength + ship.shieldCells + shieldAddition,
|
total: generatorStrength + boostersStrength + ship.shieldCells + shieldAddition,
|
||||||
recover,
|
recover,
|
||||||
recharge,
|
recharge,
|
||||||
@@ -573,7 +574,7 @@ export function armourMetrics(ship) {
|
|||||||
// };
|
// };
|
||||||
// Armour from HRPs and module armour from MRPs
|
// Armour from HRPs and module armour from MRPs
|
||||||
for (let slot of ship.internal) {
|
for (let slot of ship.internal) {
|
||||||
if (slot.m && (slot.m.grp === 'hr' || slot.m.grp === 'ghrp' || slot.m.grp == 'mahr')) {
|
if (slot.m && slot.enabled && (slot.m.grp === 'hr' || slot.m.grp === 'ghrp' || slot.m.grp == 'mahr')) {
|
||||||
armourReinforcement += slot.m.getHullReinforcement();
|
armourReinforcement += slot.m.getHullReinforcement();
|
||||||
// Hull boost for HRPs is applied against the ship's base armour
|
// Hull boost for HRPs is applied against the ship's base armour
|
||||||
armourReinforcement += ship.baseArmour * slot.m.getModValue('hullboost') / 10000;
|
armourReinforcement += ship.baseArmour * slot.m.getModValue('hullboost') / 10000;
|
||||||
@@ -585,7 +586,7 @@ export function armourMetrics(ship) {
|
|||||||
hullThermDmg = hullThermDmg * (1 - slot.m.getThermalResistance());
|
hullThermDmg = hullThermDmg * (1 - slot.m.getThermalResistance());
|
||||||
hullCausDmg = hullCausDmg * (1 - slot.m.getCausticResistance());
|
hullCausDmg = hullCausDmg * (1 - slot.m.getCausticResistance());
|
||||||
}
|
}
|
||||||
if (slot.m && (slot.m.grp == 'mrp' || slot.m.grp == 'gmrp')) {
|
if (slot.m && slot.enabled && (slot.m.grp == 'mrp' || slot.m.grp == 'gmrp')) {
|
||||||
moduleArmour += slot.m.getIntegrity();
|
moduleArmour += slot.m.getIntegrity();
|
||||||
moduleProtection = moduleProtection * (1 - slot.m.getProtection());
|
moduleProtection = moduleProtection * (1 - slot.m.getProtection());
|
||||||
}
|
}
|
||||||
@@ -1014,7 +1015,10 @@ export function timeToDrainWep(ship, wep) {
|
|||||||
*/
|
*/
|
||||||
export function timeToDeplete(amount, dps, eps, capacity, recharge) {
|
export function timeToDeplete(amount, dps, eps, capacity, recharge) {
|
||||||
const drainPerSecond = eps - recharge;
|
const drainPerSecond = eps - recharge;
|
||||||
if (drainPerSecond <= 0) {
|
// If there is nothing to remove, we're don instantly
|
||||||
|
if (!amount) {
|
||||||
|
return 0;
|
||||||
|
} if (drainPerSecond <= 0) {
|
||||||
// Simple result
|
// Simple result
|
||||||
return amount / dps;
|
return amount / dps;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export const ModuleGroupToName = {
|
|||||||
ghrp: 'Guardian Hull Reinforcement Package',
|
ghrp: 'Guardian Hull Reinforcement Package',
|
||||||
gmrp: 'Guardian Module Reinforcement Package',
|
gmrp: 'Guardian Module Reinforcement Package',
|
||||||
mahr: 'Meta Alloy Hull Reinforcement Package',
|
mahr: 'Meta Alloy Hull Reinforcement Package',
|
||||||
|
sua: 'Supercruise Assist',
|
||||||
|
|
||||||
// Hard Points
|
// Hard Points
|
||||||
bl: 'Beam Laser',
|
bl: 'Beam Laser',
|
||||||
@@ -206,7 +207,7 @@ export const ShipFacets = [
|
|||||||
i: 9
|
i: 9
|
||||||
},
|
},
|
||||||
{ // 10
|
{ // 10
|
||||||
title: 'fastest range',
|
title: 'farthest range',
|
||||||
props: ['unladenFastestRange', 'ladenFastestRange'],
|
props: ['unladenFastestRange', 'ladenFastestRange'],
|
||||||
lbls: ['unladen', 'laden'],
|
lbls: ['unladen', 'laden'],
|
||||||
unit: 'LY',
|
unit: 'LY',
|
||||||
|
|||||||
@@ -59,14 +59,7 @@ export default class Module {
|
|||||||
} else if (modification.method === 'overwrite') {
|
} else if (modification.method === 'overwrite') {
|
||||||
result = modifierActions[name];
|
result = modifierActions[name];
|
||||||
} else {
|
} else {
|
||||||
// rate of fire is special, as it's really burst interval. Handle that here
|
result = (((1 + result / multiplier) * (1 + modifierActions[name])) - 1) * multiplier;
|
||||||
let mod = null;
|
|
||||||
if (name === 'rof') {
|
|
||||||
mod = 1 / (1 + modifierActions[name]) - 1;
|
|
||||||
} else {
|
|
||||||
mod = modifierActions[name];
|
|
||||||
}
|
|
||||||
result = (((1 + result / multiplier) * (1 + mod)) - 1) * multiplier;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -105,14 +98,7 @@ export default class Module {
|
|||||||
} else if (modification.method === 'overwrite') {
|
} else if (modification.method === 'overwrite') {
|
||||||
value = null;
|
value = null;
|
||||||
} else {
|
} else {
|
||||||
// rate of fire is special, as it's really burst interval. Handle that here
|
value = ((value / 10000 + 1) / (1 + modifierActions[name]) - 1) * 10000;
|
||||||
let mod = null;
|
|
||||||
if (name === 'rof') {
|
|
||||||
mod = 1 / (1 + modifierActions[name]) - 1;
|
|
||||||
} else {
|
|
||||||
mod = modifierActions[name];
|
|
||||||
}
|
|
||||||
value = ((value / 10000 + 1) / (1 + mod) - 1) * 10000;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,6 +117,13 @@ export default class Module {
|
|||||||
* @return {Number} The value queried
|
* @return {Number} The value queried
|
||||||
*/
|
*/
|
||||||
get(name, modified = true) {
|
get(name, modified = true) {
|
||||||
|
if (name == 'rof' && isNaN(this[name])) {
|
||||||
|
let fireint = this['fireint'];
|
||||||
|
if (!isNaN(fireint)) {
|
||||||
|
this['rof'] = 1 / fireint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let val;
|
let val;
|
||||||
if (modified) {
|
if (modified) {
|
||||||
val = this._getModifiedValue(name);
|
val = this._getModifiedValue(name);
|
||||||
@@ -166,14 +159,20 @@ export default class Module {
|
|||||||
modValue = value - baseValue;
|
modValue = value - baseValue;
|
||||||
} else if (name === 'shieldboost' || name === 'hullboost') {
|
} else if (name === 'shieldboost' || name === 'hullboost') {
|
||||||
modValue = (1 + value) / (1 + baseValue) - 1;
|
modValue = (1 + value) / (1 + baseValue) - 1;
|
||||||
|
} else if (name === 'rof') {
|
||||||
|
let burst = this.get('burst', true) || 1;
|
||||||
|
let burstInt = 1 / (this.get('burstrof', true) / 1);
|
||||||
|
|
||||||
|
let interval = burst / value;
|
||||||
|
let newFireint = (interval - (burst - 1) * burstInt);
|
||||||
|
modValue = newFireint / this['fireint'] - 1;
|
||||||
} else { // multiplicative
|
} else { // multiplicative
|
||||||
modValue = value / baseValue - 1;
|
modValue = baseValue == 0 ? 0 : value / baseValue - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (modification.type === 'percentage') {
|
if (modification.type === 'percentage') {
|
||||||
modValue = modValue * 10000;
|
modValue = modValue * 10000;
|
||||||
} else if (modification.type === 'numeric' && name !== 'burst' &&
|
} else if (modification.type === 'numeric') {
|
||||||
name !== 'burstrof') {
|
|
||||||
modValue = modValue * 100;
|
modValue = modValue * 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,7 +190,14 @@ export default class Module {
|
|||||||
*/
|
*/
|
||||||
getPretty(name, modified = true, places = 2) {
|
getPretty(name, modified = true, places = 2) {
|
||||||
const formattingOptions = STATS_FORMATTING[name];
|
const formattingOptions = STATS_FORMATTING[name];
|
||||||
let val = this.get(name, modified) || 0;
|
let val;
|
||||||
|
if (formattingOptions && formattingOptions.synthetic) {
|
||||||
|
val = (this[formattingOptions.synthetic]).call(this, modified);
|
||||||
|
} else {
|
||||||
|
val = this.get(name, modified);
|
||||||
|
}
|
||||||
|
val = val || 0;
|
||||||
|
|
||||||
if (formattingOptions && formattingOptions.format.startsWith('pct')) {
|
if (formattingOptions && formattingOptions.format.startsWith('pct')) {
|
||||||
return 100 * val;
|
return 100 * val;
|
||||||
}
|
}
|
||||||
@@ -250,12 +256,17 @@ export default class Module {
|
|||||||
} else if (name === 'shieldboost' || name === 'hullboost') {
|
} else if (name === 'shieldboost' || name === 'hullboost') {
|
||||||
result = (1 + result) * (1 + modValue) - 1;
|
result = (1 + result) * (1 + modValue) - 1;
|
||||||
} else {
|
} else {
|
||||||
|
// Rate of fire modifiers are special as they actually are modifiers
|
||||||
|
// for fire interval. Translate them accordingly here:
|
||||||
|
if (name == 'rof') {
|
||||||
|
modValue = 1 / (1 + modValue) - 1;
|
||||||
|
}
|
||||||
result = result * (1 + modValue);
|
result = result * (1 + modValue);
|
||||||
}
|
}
|
||||||
} else if (name === 'burstrof') {
|
} else if (name === 'burstrof' || name === 'burst') {
|
||||||
// Burst and burst rate of fire are special, as it can not exist but
|
// Burst and burst rate of fire are special, as it can not exist but
|
||||||
// have a modification
|
// have a modification
|
||||||
result = modValue / 100;
|
result = modValue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,11 +288,7 @@ export default class Module {
|
|||||||
formatModifiedValue(name, language, unit, val) {
|
formatModifiedValue(name, language, unit, val) {
|
||||||
const formattingOptions = STATS_FORMATTING[name];
|
const formattingOptions = STATS_FORMATTING[name];
|
||||||
if (val === undefined) {
|
if (val === undefined) {
|
||||||
if (formattingOptions && formattingOptions.synthetic) {
|
val = this.getPretty(name, true);
|
||||||
val = (this[formattingOptions.synthetic]).call(this, true);
|
|
||||||
} else {
|
|
||||||
val = this._getModifiedValue(name);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val = val || 0;
|
val = val || 0;
|
||||||
@@ -351,15 +358,19 @@ export default class Module {
|
|||||||
|
|
||||||
if (formattingOptions && formattingOptions.change) {
|
if (formattingOptions && formattingOptions.change) {
|
||||||
let changeFormatting = formattingOptions.change;
|
let changeFormatting = formattingOptions.change;
|
||||||
let baseVal = this[name];
|
let baseVal = this[name] || 0;
|
||||||
let absVal = this._getModifiedValue(name);
|
let absVal = this._getModifiedValue(name);
|
||||||
if (changeFormatting === 'additive') {
|
if (changeFormatting === 'additive') {
|
||||||
val = absVal - baseVal;
|
val = absVal - baseVal;
|
||||||
} else if (changeFormatting === 'multiplicative') {
|
} else if (changeFormatting === 'multiplicative') {
|
||||||
val = absVal / baseVal - 1;
|
val = absVal / baseVal - 1;
|
||||||
}
|
}
|
||||||
|
if (Modifications.modifications[name].method === 'overwrite') {
|
||||||
|
val *= 100;
|
||||||
|
} else {
|
||||||
val *= 10000;
|
val *= 10000;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return val;
|
return val;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -572,20 +583,9 @@ export default class Module {
|
|||||||
* @return {Number} the falloff of this module
|
* @return {Number} the falloff of this module
|
||||||
*/
|
*/
|
||||||
getFalloff(modified = true) {
|
getFalloff(modified = true) {
|
||||||
if (!modified) {
|
|
||||||
const range = this.getRange(false);
|
|
||||||
const falloff = this.get('falloff', false);
|
|
||||||
return (falloff > range ? range : falloff);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Falloff from range is mapped to range
|
// Falloff from range is mapped to range
|
||||||
if (this.mods && this.mods['fallofffromrange']) {
|
if (modified && this.mods && this.mods['fallofffromrange']) {
|
||||||
return this.getRange();
|
return this.getRange();
|
||||||
// Need to find out if we have a focused modification, in which case our
|
|
||||||
// falloff is scaled to range
|
|
||||||
} else if (this.blueprint && this.blueprint.name === 'Focused') {
|
|
||||||
const rangeMod = this.getModValue('range') / 10000;
|
|
||||||
return this.falloff * (1 + rangeMod);
|
|
||||||
// Standard falloff calculation
|
// Standard falloff calculation
|
||||||
} else {
|
} else {
|
||||||
const range = this.getRange();
|
const range = this.getRange();
|
||||||
@@ -639,15 +639,6 @@ export default class Module {
|
|||||||
return this.get('protection', modified);
|
return this.get('protection', modified);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the delay for this module
|
|
||||||
* @param {Boolean} [modified=true] Whether to take modifications into account
|
|
||||||
* @return {Number} the delay of this module
|
|
||||||
*/
|
|
||||||
getDelay(modified = true) {
|
|
||||||
return this.get('delay', modified);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the duration for this module
|
* Get the duration for this module
|
||||||
* @param {Boolean} [modified=true] Whether to take modifications into account
|
* @param {Boolean} [modified=true] Whether to take modifications into account
|
||||||
@@ -703,8 +694,7 @@ export default class Module {
|
|||||||
let result = 0;
|
let result = 0;
|
||||||
if (this['maxmass']) {
|
if (this['maxmass']) {
|
||||||
result = this['maxmass'];
|
result = this['maxmass'];
|
||||||
// max mass is only modified for non-shield boosters
|
if (result && modified) {
|
||||||
if (result && modified && this.grp !== 'sg') {
|
|
||||||
let mult = this.getModValue('optmass') / 10000;
|
let mult = this.getModValue('optmass') / 10000;
|
||||||
if (mult) { result = result * (1 + mult); }
|
if (mult) { result = result * (1 + mult); }
|
||||||
}
|
}
|
||||||
@@ -793,24 +783,6 @@ export default class Module {
|
|||||||
return this.get('distdraw', modified);
|
return this.get('distdraw', modified);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the thermal load for this module
|
|
||||||
* @param {Boolean} [modified=true] Whether to take modifications into account
|
|
||||||
* @return {Number} the thermal load of this module
|
|
||||||
*/
|
|
||||||
getThermalLoad(modified = true) {
|
|
||||||
return this.get('thermload', modified);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the rounds per shot for this module
|
|
||||||
* @param {Boolean} [modified=true] Whether to take modifications into account
|
|
||||||
* @return {Number} the rounds per shot of this module
|
|
||||||
*/
|
|
||||||
getRoundsPerShot(modified = true) {
|
|
||||||
return this.get('roundspershot', modified);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the DPS for this module
|
* Get the DPS for this module
|
||||||
* @param {Boolean} [modified=true] Whether to take modifications into account
|
* @param {Boolean} [modified=true] Whether to take modifications into account
|
||||||
@@ -835,26 +807,40 @@ export default class Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the SDPS for this module
|
* Return the factor that gets applied when calculating certain "sustained"
|
||||||
|
* values, e.g. `SDPS = this.getSustainedFactor() * DPS`.
|
||||||
* @param {Boolean} [modified=true] Whether to take modifications into account
|
* @param {Boolean} [modified=true] Whether to take modifications into account
|
||||||
* @return {Number} The SDPS of this module
|
|
||||||
*/
|
*/
|
||||||
getSDps(modified = true) {
|
getSustainedFactor(modified = true) {
|
||||||
let dps = this.getDps(modified);
|
|
||||||
if (this.getClip(modified)) {
|
|
||||||
let clipSize = this.getClip(modified);
|
let clipSize = this.getClip(modified);
|
||||||
|
if (clipSize) {
|
||||||
// If auto-loader is applied, effective clip size will be nearly doubled
|
// If auto-loader is applied, effective clip size will be nearly doubled
|
||||||
// as you get one reload for every two shots fired.
|
// as you get one reload for every two shots fired.
|
||||||
if (this.blueprint && this.blueprint.special && this.blueprint.special.edname === 'special_auto_loader' && modified) {
|
if (this.blueprint && this.blueprint.special && this.blueprint.special.edname === 'special_auto_loader' && modified) {
|
||||||
clipSize += clipSize - 1;
|
clipSize += clipSize - 1;
|
||||||
}
|
}
|
||||||
let timeToDeplete = clipSize / this.getRoF(modified);
|
|
||||||
return dps * timeToDeplete / (timeToDeplete + this.getReload(modified));
|
let burstSize = this.get('burst', modified) || 1;
|
||||||
|
let rof = this.getRoF(modified);
|
||||||
|
// rof averages burstfire + pause until next interval but for sustained
|
||||||
|
// rof we need to take another burst without pause into account
|
||||||
|
let burstOverhead = (burstSize - 1) / (this.get('burstrof', modified) || 1);
|
||||||
|
let srof = clipSize / ((clipSize - burstSize) / rof + burstOverhead + this.getReload(modified));
|
||||||
|
return srof / rof;
|
||||||
} else {
|
} else {
|
||||||
return dps;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the SDPS for this module
|
||||||
|
* @param {Boolean} [modified=true] Whether to take modifications into account
|
||||||
|
* @return {Number} The SDPS of this module
|
||||||
|
*/
|
||||||
|
getSDps(modified = true) {
|
||||||
|
return this.getDps(modified) * this.getSustainedFactor(modified);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the EPS for this module
|
* Get the EPS for this module
|
||||||
* @param {Boolean} [modified=true] Whether to take modifications into account
|
* @param {Boolean} [modified=true] Whether to take modifications into account
|
||||||
@@ -876,7 +862,7 @@ export default class Module {
|
|||||||
*/
|
*/
|
||||||
getHps(modified = true) {
|
getHps(modified = true) {
|
||||||
// HPS is a synthetic value
|
// HPS is a synthetic value
|
||||||
let heat = this.getThermalLoad(modified);
|
let heat = this.get('thermload', modified);
|
||||||
// We don't use rpshot here as dist draw is per combined shot
|
// We don't use rpshot here as dist draw is per combined shot
|
||||||
let rof = this.getRoF(modified) || 1;
|
let rof = this.getRoF(modified) || 1;
|
||||||
|
|
||||||
@@ -913,24 +899,6 @@ export default class Module {
|
|||||||
return this.get('reload', modified);
|
return this.get('reload', modified);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the burst size for this module
|
|
||||||
* @param {Boolean} [modified=true] Whether to take modifications into account
|
|
||||||
* @return {Number} the burst size of this module
|
|
||||||
*/
|
|
||||||
getBurst(modified = true) {
|
|
||||||
return this.get('burst', modified);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the burst rate of fire for this module
|
|
||||||
* @param {Boolean} [modified=true] Whether to take modifications into account
|
|
||||||
* @return {Number} the burst rate of fire of this module
|
|
||||||
*/
|
|
||||||
getBurstRoF(modified = true) {
|
|
||||||
return this.get('burstrof', modified);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the rate of fire for this module.
|
* Get the rate of fire for this module.
|
||||||
* The rate of fire is a combination value, and needs to take in to account
|
* The rate of fire is a combination value, and needs to take in to account
|
||||||
@@ -941,8 +909,8 @@ export default class Module {
|
|||||||
* @return {Number} the rate of fire for this module
|
* @return {Number} the rate of fire for this module
|
||||||
*/
|
*/
|
||||||
getRoF(modified = true) {
|
getRoF(modified = true) {
|
||||||
const burst = this.getBurst(modified) || 1;
|
const burst = this.get('burst', modified) || 1;
|
||||||
const burstRoF = this.getBurstRoF(modified) || 1;
|
const burstRoF = this.get('burstrof', modified) || 1;
|
||||||
const intRoF = this.get('rof', modified);
|
const intRoF = this.get('rof', modified);
|
||||||
|
|
||||||
return burst / (((burst - 1) / burstRoF) + 1 / intRoF);
|
return burst / (((burst - 1) / burstRoF) + 1 / intRoF);
|
||||||
|
|||||||
@@ -164,13 +164,13 @@ export default class ModuleSet {
|
|||||||
/**
|
/**
|
||||||
* Finds the lightest usable Shield Generator
|
* Finds the lightest usable Shield Generator
|
||||||
* @param {number} hullMass Ship hull mass
|
* @param {number} hullMass Ship hull mass
|
||||||
* @param {string} rating The optional rating of the shield
|
* @return {Object} Thruster
|
||||||
* @return {Object} Shield Generator
|
|
||||||
*/
|
*/
|
||||||
lightestShieldGenerator(hullMass, rating) {
|
lightestShieldGenerator(hullMass) {
|
||||||
let sg = this.internal.sg[0];
|
let sg = this.internal.sg[0];
|
||||||
|
|
||||||
for (let s of this.internal.sg) {
|
for (let s of this.internal.sg) {
|
||||||
if ((!rating || rating === s.rating) && s.mass <= sg.mass && s.maxmass > hullMass) {
|
if (s.mass < sg.mass && s.maxmass > hullMass) {
|
||||||
sg = s;
|
sg = s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import { Ships, Modifications } from 'coriolis-data/dist';
|
|||||||
import { chain } from 'lodash';
|
import { chain } from 'lodash';
|
||||||
const zlib = require('zlib');
|
const zlib = require('zlib');
|
||||||
|
|
||||||
const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs', 'fh', 'gfsb'];
|
const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs', 'fh', 'gfsb', 'dc'];
|
||||||
|
|
||||||
// Constants for modifications struct
|
// Constants for modifications struct
|
||||||
const SLOT_ID_DONE = -1;
|
const SLOT_ID_DONE = -1;
|
||||||
@@ -1308,7 +1308,7 @@ export default class Ship {
|
|||||||
let fsd = this.standard[2].m; // Frame Shift Drive;
|
let fsd = this.standard[2].m; // Frame Shift Drive;
|
||||||
let { unladenMass, fuelCapacity } = this;
|
let { unladenMass, fuelCapacity } = this;
|
||||||
this.unladenRange = this.calcUnladenRange(); // Includes fuel weight for jump
|
this.unladenRange = this.calcUnladenRange(); // Includes fuel weight for jump
|
||||||
this.fullTankRange = Calc.jumpRange(unladenMass + fuelCapacity, fsd, this); // Full Tank
|
this.fullTankRange = Calc.jumpRange(unladenMass + fuelCapacity, fsd, fuelCapacity, this); // Full Tank
|
||||||
this.ladenRange = this.calcLadenRange(); // Includes full tank and caro
|
this.ladenRange = this.calcLadenRange(); // Includes full tank and caro
|
||||||
this.unladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity, fsd, fuelCapacity, this);
|
this.unladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity, fsd, fuelCapacity, this);
|
||||||
this.ladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity + this.cargoCapacity, fsd, fuelCapacity, this);
|
this.ladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity + this.cargoCapacity, fsd, fuelCapacity, this);
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
import * as ModuleUtils from './ModuleUtils';
|
import * as ModuleUtils from './ModuleUtils';
|
||||||
import { Modifications } from 'coriolis-data/dist';
|
|
||||||
import { canMount } from '../utils/SlotFunctions';
|
import { canMount } from '../utils/SlotFunctions';
|
||||||
import { getBlueprint, setPercent } from '../utils/BlueprintFunctions';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Standard / typical role for multi-purpose or combat (if shielded with better bulkheads)
|
* Standard / typical role for multi-purpose or combat (if shielded with better bulkheads)
|
||||||
@@ -16,7 +14,7 @@ export function multiPurpose(ship, shielded, bulkheadIndex) {
|
|||||||
.useBulkhead(bulkheadIndex);
|
.useBulkhead(bulkheadIndex);
|
||||||
|
|
||||||
if (shielded) {
|
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
|
if (canMount(ship, slot, 'sg')) { // Assuming largest slot can hold an eligible shield
|
||||||
ship.use(slot, ModuleUtils.findInternal('sg', slot.maxClass, 'A'));
|
ship.use(slot, ModuleUtils.findInternal('sg', slot.maxClass, 'A'));
|
||||||
ship.setSlotEnabled(slot, true);
|
ship.setSlotEnabled(slot, true);
|
||||||
@@ -26,577 +24,6 @@ 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
|
* Trader Role
|
||||||
* @param {Ship} ship Ship instance
|
* @param {Ship} ship Ship instance
|
||||||
@@ -609,7 +36,7 @@ export function trader(ship, shielded, standardOpts) {
|
|||||||
let sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
|
let sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
|
||||||
ship.useStandard('A')
|
ship.useStandard('A')
|
||||||
.use(ship.standard[3], ModuleUtils.standard(3, ship.standard[3].maxClass + 'D')) // D Life Support
|
.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 Power Plant
|
.use(ship.standard[1], ModuleUtils.standard(1, ship.standard[1].maxClass + 'D')) // D Life Support
|
||||||
.use(ship.standard[4], ModuleUtils.standard(4, ship.standard[4].maxClass + 'D')) // D Life Support
|
.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
|
.use(ship.standard[5], ModuleUtils.standard(5, ship.standard[5].maxClass + 'D')); // D Sensors
|
||||||
|
|
||||||
@@ -618,7 +45,7 @@ export function trader(ship, shielded, standardOpts) {
|
|||||||
.filter(a => (!a.eligible) || a.eligible.sg)
|
.filter(a => (!a.eligible) || a.eligible.sg)
|
||||||
.filter(a => a.maxClass >= sg.class)
|
.filter(a => a.maxClass >= sg.class)
|
||||||
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
|
.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
|
if (canMount(ship, slot, 'sg')) { // Assuming largest slot can hold an eligible shield
|
||||||
const shield = ModuleUtils.findInternal('sg', slot.maxClass, 'A');
|
const shield = ModuleUtils.findInternal('sg', slot.maxClass, 'A');
|
||||||
if (shield && shield.maxmass > ship.hullMass) {
|
if (shield && shield.maxmass > ship.hullMass) {
|
||||||
@@ -842,7 +269,7 @@ export function miner(ship, shielded) {
|
|||||||
|
|
||||||
// Dual mining lasers of highest possible class; remove anything else
|
// Dual mining lasers of highest possible class; remove anything else
|
||||||
const miningLaserOrder = [2, 3, 4, 1, 0];
|
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);
|
return miningLaserOrder.indexOf(a.maxClass) - miningLaserOrder.indexOf(b.maxClass);
|
||||||
});
|
});
|
||||||
for (let s of miningLaserHardpoints) {
|
for (let s of miningLaserHardpoints) {
|
||||||
@@ -856,7 +283,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
|
// 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)
|
const miningLaserDps = ship.hardpoints.filter(h => h.m != null)
|
||||||
.reduce(function (a, b) {
|
.reduce(function(a, b) {
|
||||||
return a + b.m.getDps();
|
return a + b.m.getDps();
|
||||||
}, 0);
|
}, 0);
|
||||||
// Find out how many internal slots we have, and their potential cargo size
|
// Find out how many internal slots we have, and their potential cargo size
|
||||||
@@ -887,7 +314,7 @@ export function miner(ship, shielded) {
|
|||||||
|
|
||||||
// Power distributor to power the mining lasers indefinitely
|
// Power distributor to power the mining lasers indefinitely
|
||||||
const wepRateRequired = ship.hardpoints.filter(h => h.m != null)
|
const wepRateRequired = ship.hardpoints.filter(h => h.m != null)
|
||||||
.reduce(function (a, b) {
|
.reduce(function(a, b) {
|
||||||
return a + b.m.getEps();
|
return a + b.m.getEps();
|
||||||
}, 0);
|
}, 0);
|
||||||
standardOpts.pd = ship.getAvailableModules().matchingPowerDist({ weprate: wepRateRequired }).id;
|
standardOpts.pd = ship.getAvailableModules().matchingPowerDist({ weprate: wepRateRequired }).id;
|
||||||
|
|||||||
@@ -26,8 +26,8 @@ export const STATS_FORMATTING = {
|
|||||||
'ammo': { 'format': 'int', },
|
'ammo': { 'format': 'int', },
|
||||||
'boot': { 'format': 'int', 'unit': 'secs' },
|
'boot': { 'format': 'int', 'unit': 'secs' },
|
||||||
'brokenregen': { 'format': 'round1', 'unit': 'ps' },
|
'brokenregen': { 'format': 'round1', 'unit': 'ps' },
|
||||||
'burst': { 'format': 'int' },
|
'burst': { 'format': 'int', 'change': 'additive' },
|
||||||
'burstrof': { 'format': 'round1', 'unit': 'ps' },
|
'burstrof': { 'format': 'round1', 'unit': 'ps', 'change': 'additive' },
|
||||||
'causres': { 'format': 'pct' },
|
'causres': { 'format': 'pct' },
|
||||||
'clip': { 'format': 'int' },
|
'clip': { 'format': 'int' },
|
||||||
'damage': { 'format': 'round' },
|
'damage': { 'format': 'round' },
|
||||||
@@ -61,7 +61,7 @@ export const STATS_FORMATTING = {
|
|||||||
'ranget': { 'format': 'f1', 'unit': 's' },
|
'ranget': { 'format': 'f1', 'unit': 's' },
|
||||||
'regen': { 'format': 'round1', 'unit': 'ps' },
|
'regen': { 'format': 'round1', 'unit': 'ps' },
|
||||||
'reload': { 'format': 'int', 'unit': 's' },
|
'reload': { 'format': 'int', 'unit': 's' },
|
||||||
'rof': { 'format': 'round1', 'unit': 'ps' },
|
'rof': { 'format': 'round1', 'unit': 'ps', 'synthetic': 'getRoF', 'higherbetter': true },
|
||||||
'angle': { 'format': 'round1', 'unit': 'ang' },
|
'angle': { 'format': 'round1', 'unit': 'ang' },
|
||||||
'scanrate': { 'format': 'int' },
|
'scanrate': { 'format': 'int' },
|
||||||
'scantime': { 'format': 'round1', 'unit': 's' },
|
'scantime': { 'format': 'round1', 'unit': 's' },
|
||||||
|
|||||||
33
src/app/shipyard/StatsMapping.js
Normal file
33
src/app/shipyard/StatsMapping.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { Module } from 'ed-forge';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a resistance value of a module as
|
||||||
|
* @param {Module} module Module to set the property
|
||||||
|
* @param {string} prop Property name; must end with 'resistance'
|
||||||
|
* @param {number} val Resistance value to set
|
||||||
|
*/
|
||||||
|
function setterResToEff(module, prop, val) {
|
||||||
|
module.set(
|
||||||
|
prop.replace('resistance', 'effectiveness'),
|
||||||
|
1 - val / 100,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SHOW = {
|
||||||
|
causticeffectiveness: {
|
||||||
|
as: 'causticresistance',
|
||||||
|
setter: setterResToEff,
|
||||||
|
},
|
||||||
|
explosiveeffectiveness: {
|
||||||
|
as: 'explosiveresistance',
|
||||||
|
setter: setterResToEff,
|
||||||
|
},
|
||||||
|
kineticeffectiveness: {
|
||||||
|
as: 'kineticresistance',
|
||||||
|
setter: setterResToEff,
|
||||||
|
},
|
||||||
|
thermiceffectiveness: {
|
||||||
|
as: 'thermicresistance',
|
||||||
|
setter: setterResToEff,
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -1,60 +1,43 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Modifications } from 'coriolis-data/dist';
|
import { Modifications } from 'coriolis-data/dist';
|
||||||
|
import { Module } from 'ed-forge';
|
||||||
|
import { getBlueprintInfo, getExperimentalInfo } from 'ed-forge/lib/data/blueprints';
|
||||||
|
import { entries, keys, uniq } from 'lodash';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a tooltip with details of a blueprint's specials
|
* Generate a tooltip with details of a blueprint's specials
|
||||||
* @param {Object} translate The translate object
|
* @param {Object} language The translate object
|
||||||
* @param {Object} blueprint The blueprint at the required grade
|
* @param {Module} m The module to compare with
|
||||||
* @param {string} grp The group of the module
|
|
||||||
* @param {Object} m The module to compare with
|
|
||||||
* @param {string} specialName The name of the special
|
* @param {string} specialName The name of the special
|
||||||
* @returns {Object} The react components
|
* @returns {Object} The react components
|
||||||
*/
|
*/
|
||||||
export function specialToolTip(translate, blueprint, grp, m, specialName) {
|
export function specialToolTip(language, m, specialName) {
|
||||||
const effects = [];
|
const { formats, translate } = language;
|
||||||
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> </td>
|
|
||||||
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'}
|
|
||||||
style={{ textAlign: 'right' }}>{current}{symbol}</td>
|
|
||||||
<td> </td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<table width='100%'>
|
<table width='100%'>
|
||||||
<tbody>
|
<tbody>
|
||||||
{effects}
|
{entries(getExperimentalInfo(specialName).features).map(
|
||||||
|
([prop, feats]) => {
|
||||||
|
const { max, only } = feats;
|
||||||
|
if (only && !m.getItem().match(only)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { value, unit, beneficial } = m.getModifierFormatted(prop);
|
||||||
|
// If the product of value and min/max is positive, both values
|
||||||
|
// point into the same direction, i.e. positive/negative.
|
||||||
|
const specialBeneficial = (value * max) > 0 === beneficial;
|
||||||
|
|
||||||
|
return <tr key={prop + '_specialTT'}>
|
||||||
|
<td style={{ textAlign: 'left' }}>{translate(prop)}</td>
|
||||||
|
<td> </td>
|
||||||
|
<td className={specialBeneficial ? 'secondary' : 'warning'}
|
||||||
|
style={{ textAlign: 'right' }}>{formats.round(max * 100)}{unit}</td>
|
||||||
|
<td> </td>
|
||||||
|
</tr>;
|
||||||
|
}
|
||||||
|
)}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -62,154 +45,23 @@ export function specialToolTip(translate, blueprint, grp, m, specialName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a tooltip with details of a blueprint's effects
|
* Generate a tooltip with details and preview of a blueprint's effects
|
||||||
* @param {Object} translate The translate object
|
* @param {Object} language The language object
|
||||||
* @param {Object} blueprint The blueprint at the required grade
|
* @param {Module} m The module to compare with
|
||||||
* @param {Array} engineers The engineers supplying this blueprint
|
* @param {string} previewBP Blueprint to preview
|
||||||
* @param {string} grp The group of the module
|
* @param {number} previewGrade Grade to preview
|
||||||
* @param {Object} m The module to compare with
|
|
||||||
* @returns {Object} The react components
|
* @returns {Object} The react components
|
||||||
*/
|
*/
|
||||||
export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
|
export function blueprintTooltip(language, m, previewBP, previewGrade) {
|
||||||
const effects = [];
|
const { translate, formats } = language;
|
||||||
if (!blueprint || !blueprint.features) {
|
const blueprint = previewBP || m.getBlueprint();
|
||||||
return undefined;
|
const grade = previewGrade || m.getBlueprintGrade();
|
||||||
}
|
if (!blueprint) {
|
||||||
for (const feature in blueprint.features) {
|
return null;
|
||||||
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> </td>
|
|
||||||
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
|
|
||||||
<td> </td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// We also add in any benefits from specials that aren't covered above
|
const bpFeatures = getBlueprintInfo(blueprint).features[grade];
|
||||||
if (m.blueprint && m.blueprint.special) {
|
const features = uniq(m.getModifiedProperties().concat(keys(bpFeatures)));
|
||||||
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> </td>
|
|
||||||
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
|
|
||||||
<td> </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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -218,68 +70,45 @@ export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
|
|||||||
<tr>
|
<tr>
|
||||||
<td>{translate('feature')}</td>
|
<td>{translate('feature')}</td>
|
||||||
<td>{translate('worst')}</td>
|
<td>{translate('worst')}</td>
|
||||||
{m ? <td>{translate('current')}</td> : null }
|
<td>{translate('current')}</td>
|
||||||
<td>{translate('best')}</td>
|
<td>{translate('best')}</td>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{effects}
|
{features.map((prop) => {
|
||||||
|
const { min, max, only } = bpFeatures[prop] || {};
|
||||||
|
// Skip this property if it doesn't apply to this module
|
||||||
|
if (only && !m.getItem().match(only)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { value, unit, beneficial } = m.getModifierFormatted(prop);
|
||||||
|
if (!bpFeatures[prop] && !value) {
|
||||||
|
// Can happen for exported synthetics
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// If the product of value and min/max is positive, both values
|
||||||
|
// point into the same direction, i.e. positive/negative.
|
||||||
|
const minBeneficial = (value * min) > 0 === beneficial;
|
||||||
|
const maxBeneficial = (value * max) > 0 === beneficial;
|
||||||
|
return (<tr key={prop}>
|
||||||
|
<td style={{ textAlign: 'left' }}>{translate(prop)}</td>
|
||||||
|
<td className={!min ? '' : minBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>
|
||||||
|
{!isNaN(min) && formats.round(min * 100)}{!isNaN(min) && unit}
|
||||||
|
</td>
|
||||||
|
<td className={!value ? '' : beneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>
|
||||||
|
{formats.round(value || 0)}{unit}
|
||||||
|
</td>
|
||||||
|
<td className={!max ? '' : maxBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>
|
||||||
|
{!isNaN(max) && formats.round(max * 100)}{!isNaN(max) && unit}
|
||||||
|
</td>
|
||||||
|
</tr>);
|
||||||
|
})}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</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>
|
</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
|
* Get a blueprint with a given name and an optional module
|
||||||
* @param {string} name The name of the blueprint
|
* @param {string} name The name of the blueprint
|
||||||
@@ -296,122 +125,3 @@ export function getBlueprint(name, module) {
|
|||||||
const blueprint = JSON.parse(JSON.stringify(found));
|
const blueprint = JSON.parse(JSON.stringify(found));
|
||||||
return blueprint;
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -249,6 +249,13 @@ function _addModifications(module, modifiers, blueprint, grade, specialModificat
|
|||||||
if (!modifiers) return;
|
if (!modifiers) return;
|
||||||
let special;
|
let special;
|
||||||
if (specialModifications) {
|
if (specialModifications) {
|
||||||
|
if (specialModifications == 'special_plasma_slug') {
|
||||||
|
if (module.symbol.match(/PlasmaAccelerator/i)) {
|
||||||
|
specialModifications = 'special_plasma_slug_pa';
|
||||||
|
} else {
|
||||||
|
specialModifications = 'special_plasma_slug_cooled';
|
||||||
|
}
|
||||||
|
}
|
||||||
special = Modifications.specials[specialModifications];
|
special = Modifications.specials[specialModifications];
|
||||||
}
|
}
|
||||||
// Add the blueprint definition, grade and special
|
// Add the blueprint definition, grade and special
|
||||||
@@ -274,6 +281,9 @@ function _addModifications(module, modifiers, blueprint, grade, specialModificat
|
|||||||
if (value === Infinity) {
|
if (value === Infinity) {
|
||||||
value = modifiers[i].Value * 100;
|
value = modifiers[i].Value * 100;
|
||||||
}
|
}
|
||||||
|
if (modifiers[i].Label.search('DamageFalloffRange') >= 0) {
|
||||||
|
value = (modifiers[i].Value / module.range - 1) * 100;
|
||||||
|
}
|
||||||
if (modifiers[i].Label.search('Resistance') >= 0) {
|
if (modifiers[i].Label.search('Resistance') >= 0) {
|
||||||
value = (modifiers[i].Value * 100) - (modifiers[i].OriginalValue * 100);
|
value = (modifiers[i].Value * 100) - (modifiers[i].OriginalValue * 100);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Persist from '../stores/Persist';
|
import Persist from '../stores/Persist';
|
||||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||||
|
import Module from '../shipyard/Module';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Determine if a slot on a ship can mount a module of a particular class and group
|
* Determine if a slot on a ship can mount a module of a particular class and group
|
||||||
@@ -139,20 +140,21 @@ function diff(format, mVal, mmVal) {
|
|||||||
export function diffDetails(language, m, mm) {
|
export function diffDetails(language, m, mm) {
|
||||||
let { formats, translate, units } = language;
|
let { formats, translate, units } = language;
|
||||||
let propDiffs = [];
|
let propDiffs = [];
|
||||||
|
m = new Module(m);
|
||||||
|
|
||||||
// Module-specific items
|
// Module-specific items
|
||||||
|
|
||||||
if (m.grp === 'pp') {
|
if (m.grp === 'pp') {
|
||||||
let mPowerGeneration = m.pgen || 0;
|
let mPowerGeneration = m.getPowerGeneration() || 0;
|
||||||
let mmPowerGeneration = mm ? mm.getPowerGeneration() : 0;
|
let mmPowerGeneration = mm ? mm.getPowerGeneration() : 0;
|
||||||
if (mPowerGeneration != mmPowerGeneration) propDiffs.push(<div key='pgen'>{translate('pgen')}: <span className={diffClass(mPowerGeneration, mmPowerGeneration)}>{diff(formats.round, mPowerGeneration, mmPowerGeneration)}{units.MJ}</span></div>);
|
if (mPowerGeneration != mmPowerGeneration) propDiffs.push(<div key='pgen'>{translate('pgen')}: <span className={diffClass(mPowerGeneration, mmPowerGeneration)}>{diff(formats.round, mPowerGeneration, mmPowerGeneration)}{units.MW}</span></div>);
|
||||||
} else {
|
} else {
|
||||||
let mPowerUsage = m.power || 0;
|
let mPowerUsage = m.getPowerUsage() || 0;
|
||||||
let mmPowerUsage = mm ? mm.getPowerUsage() || 0 : 0;
|
let mmPowerUsage = mm ? mm.getPowerUsage() || 0 : 0;
|
||||||
if (mPowerUsage != mmPowerUsage) propDiffs.push(<div key='power'>{translate('power')}: <span className={diffClass(mPowerUsage, mmPowerUsage, true)}>{diff(formats.round, mPowerUsage, mmPowerUsage)}{units.MJ}</span></div>);
|
if (mPowerUsage != mmPowerUsage) propDiffs.push(<div key='power'>{translate('power')}: <span className={diffClass(mPowerUsage, mmPowerUsage, true)}>{diff(formats.round, mPowerUsage, mmPowerUsage)}{units.MW}</span></div>);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mDps = m.damage * (m.rpshot || 1) * (m.rof || 1);
|
let mDps = m.getDps() || 0;
|
||||||
let mmDps = mm ? mm.getDps() || 0 : 0;
|
let mmDps = mm ? mm.getDps() || 0 : 0;
|
||||||
if (mDps && mDps != mmDps) propDiffs.push(<div key='dps'>{translate('dps')}: <span className={diffClass(mmDps, mDps, true)}>{diff(formats.round, mDps, mmDps)}</span></div>);
|
if (mDps && mDps != mmDps) propDiffs.push(<div key='dps'>{translate('dps')}: <span className={diffClass(mmDps, mDps, true)}>{diff(formats.round, mDps, mmDps)}</span></div>);
|
||||||
|
|
||||||
@@ -164,7 +166,7 @@ export function diffDetails(language, m, mm) {
|
|||||||
|
|
||||||
if (mAffectsShield) {
|
if (mAffectsShield) {
|
||||||
if (m.grp == 'sb') { // Both m and mm must be utility modules if this is true
|
if (m.grp == 'sb') { // Both m and mm must be utility modules if this is true
|
||||||
newShield = this.calcShieldStrengthWith(null, m.shieldboost - (mm ? mm.getShieldBoost() || 0 : 0));
|
newShield = this.calcShieldStrengthWith(null, m.getShieldBoost() - (mm ? mm.getShieldBoost() || 0 : 0));
|
||||||
} else {
|
} else {
|
||||||
newShield = this.calcShieldStrengthWith(m);
|
newShield = this.calcShieldStrengthWith(m);
|
||||||
}
|
}
|
||||||
@@ -179,7 +181,7 @@ export function diffDetails(language, m, mm) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (m.grp === 'mrp') {
|
if (m.grp === 'mrp') {
|
||||||
let mProtection = m.protection;
|
let mProtection = m.getProtection();
|
||||||
let mmProtection = mm ? mm.getProtection() || 0 : 0;
|
let mmProtection = mm ? mm.getProtection() || 0 : 0;
|
||||||
if (mProtection != mmProtection) {
|
if (mProtection != mmProtection) {
|
||||||
propDiffs.push(<div key='protection'>{translate('protection')}: <span className={diffClass(mmProtection, mProtection, true)}>{diff(formats.pct, mProtection, mmProtection)}</span></div>);
|
propDiffs.push(<div key='protection'>{translate('protection')}: <span className={diffClass(mmProtection, mProtection, true)}>{diff(formats.pct, mProtection, mmProtection)}</span></div>);
|
||||||
@@ -187,7 +189,7 @@ export function diffDetails(language, m, mm) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (m.grp === 'hr') {
|
if (m.grp === 'hr') {
|
||||||
let mHullReinforcement = m.hullreinforcement;
|
let mHullReinforcement = m.getHullReinforcement();
|
||||||
let mmHullReinforcement = mm ? mm.getHullReinforcement() || 0 : 0;
|
let mmHullReinforcement = mm ? mm.getHullReinforcement() || 0 : 0;
|
||||||
if (mHullReinforcement && mHullReinforcement != mmHullReinforcement) propDiffs.push(<div key='hullreinforcement'>{translate('hullreinforcement')}: <span className={diffClass(mmHullReinforcement, mHullReinforcement, true)}>{diff(formats.round, mHullReinforcement, mmHullReinforcement)}</span></div>);
|
if (mHullReinforcement && mHullReinforcement != mmHullReinforcement) propDiffs.push(<div key='hullreinforcement'>{translate('hullreinforcement')}: <span className={diffClass(mmHullReinforcement, mHullReinforcement, true)}>{diff(formats.round, mHullReinforcement, mmHullReinforcement)}</span></div>);
|
||||||
}
|
}
|
||||||
@@ -219,7 +221,7 @@ export function diffDetails(language, m, mm) {
|
|||||||
let mmCost = mm ? mm.cost : 0;
|
let mmCost = mm ? mm.cost : 0;
|
||||||
if (mCost != mmCost) propDiffs.push(<div key='cost'>{translate('cost')}: <span className={diffClass(mCost, mmCost, true) }>{formats.int(mCost ? Math.round(mCost * (1 - Persist.getModuleDiscount())) : 0)}{units.CR}</span></div>);
|
if (mCost != mmCost) propDiffs.push(<div key='cost'>{translate('cost')}: <span className={diffClass(mCost, mmCost, true) }>{formats.int(mCost ? Math.round(mCost * (1 - Persist.getModuleDiscount())) : 0)}{units.CR}</span></div>);
|
||||||
|
|
||||||
let mMass = m.mass || 0;
|
let mMass = m.getMass() || 0;
|
||||||
let mmMass = mm ? mm.getMass() : 0;
|
let mmMass = mm ? mm.getMass() : 0;
|
||||||
if (mMass != mmMass) propDiffs.push(<div key='mass'>{translate('mass')}: <span className={diffClass(mMass, mmMass, true)}>{diff(formats.round, mMass, mmMass)}{units.T}</span></div>);
|
if (mMass != mmMass) propDiffs.push(<div key='mass'>{translate('mass')}: <span className={diffClass(mMass, mmMass, true)}>{diff(formats.round, mMass, mmMass)}{units.T}</span></div>);
|
||||||
|
|
||||||
@@ -240,7 +242,7 @@ export function diffDetails(language, m, mm) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mIntegrity = m.integrity || 0;
|
let mIntegrity = m.getIntegrity() || 0;
|
||||||
let mmIntegrity = mm ? mm.getIntegrity() || 0 : 0;
|
let mmIntegrity = mm ? mm.getIntegrity() || 0 : 0;
|
||||||
if (mIntegrity != mmIntegrity) {
|
if (mIntegrity != mmIntegrity) {
|
||||||
propDiffs.push(<div key='integrity'>{translate('integrity')}: <span className={diffClass(mmIntegrity, mIntegrity, true)}>{diff(formats.round, mIntegrity, mmIntegrity)}</span></div>);
|
propDiffs.push(<div key='integrity'>{translate('integrity')}: <span className={diffClass(mmIntegrity, mIntegrity, true)}>{diff(formats.round, mIntegrity, mmIntegrity)}</span></div>);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Module } from 'ed-forge';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps the callback/context menu handler such that the default
|
* Wraps the callback/context menu handler such that the default
|
||||||
@@ -83,3 +84,18 @@ export function isEmpty(obj) {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches a property from either a Module or a moduleInfo object
|
||||||
|
* @param {Object} m Either a Module or a moduleInfo object
|
||||||
|
* @param {string} property Property name
|
||||||
|
* @returns {number} Property value
|
||||||
|
*/
|
||||||
|
export function moduleGet(m, property) {
|
||||||
|
if (m instanceof Module) {
|
||||||
|
return m.get(property);
|
||||||
|
} else {
|
||||||
|
// Assume its a moduleInfo object
|
||||||
|
return m.props[property];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,6 +2,13 @@
|
|||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Coriolis EDCD Edition</title>
|
<title>Coriolis EDCD Edition</title>
|
||||||
|
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
|
||||||
|
<script>
|
||||||
|
(adsbygoogle = window.adsbygoogle || []).push({
|
||||||
|
google_ad_client: "ca-pub-3709458261881414",
|
||||||
|
enable_page_level_ads: true
|
||||||
|
});
|
||||||
|
</script>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="stylesheet" href="<%= htmlWebpackPlugin.files.css[0] %>">
|
<link rel="stylesheet" href="<%= htmlWebpackPlugin.files.css[0] %>">
|
||||||
<!-- Standard headers -->
|
<!-- Standard headers -->
|
||||||
@@ -32,31 +39,17 @@
|
|||||||
window.CORIOLIS_DATE = '<%- htmlWebpackPlugin.options.date.toISOString().slice(0, 10) %>';
|
window.CORIOLIS_DATE = '<%- htmlWebpackPlugin.options.date.toISOString().slice(0, 10) %>';
|
||||||
window.BUGSNAG_VERSION = '<%- htmlWebpackPlugin.options.version + '-' + htmlWebpackPlugin.options.date.toISOString() %>';
|
window.BUGSNAG_VERSION = '<%- htmlWebpackPlugin.options.version + '-' + htmlWebpackPlugin.options.date.toISOString() %>';
|
||||||
</script>
|
</script>
|
||||||
<% if (htmlWebpackPlugin.options.uaTracking) { %>
|
<!-- Global site tag (gtag.js) - Google Analytics -->
|
||||||
|
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-55840909-18"></script>
|
||||||
<script>
|
<script>
|
||||||
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
|
window.dataLayer = window.dataLayer || [];
|
||||||
ga('create', '<%- htmlWebpackPlugin.options.uaTracking %>', 'auto');
|
function gtag(){dataLayer.push(arguments);}
|
||||||
ga('send', 'pageview');
|
gtag('js', new Date());
|
||||||
</script>
|
|
||||||
<script async src='https://www.google-analytics.com/analytics.js'></script>
|
gtag('config', 'UA-55840909-18');
|
||||||
<% } %>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<!-- Piwik -->
|
|
||||||
<!-- <script type="text/javascript">
|
|
||||||
var _paq = _paq || [];
|
|
||||||
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
|
|
||||||
_paq.push(["setCookieDomain", "*.coriolis.edcd.io"]);
|
|
||||||
_paq.push(['trackPageView']);
|
|
||||||
_paq.push(['enableLinkTracking']);
|
|
||||||
(function() {
|
|
||||||
var u="//stats.isadankme.me/";
|
|
||||||
_paq.push(['setTrackerUrl', u+'piwik.php']);
|
|
||||||
_paq.push(['setSiteId', '4']);
|
|
||||||
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
|
|
||||||
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
|
|
||||||
})();
|
|
||||||
</script>-->
|
|
||||||
<!-- End Piwik Code -->
|
|
||||||
|
|
||||||
<!-- Bugsnag -->
|
<!-- Bugsnag -->
|
||||||
<script src="https://d2wy8f7a9ursnm.cloudfront.net/v5.0.0/bugsnag.min.js"></script>
|
<script src="https://d2wy8f7a9ursnm.cloudfront.net/v5.0.0/bugsnag.min.js"></script>
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
@bgDarken: 40%;
|
@bgDarken: 40%;
|
||||||
@disabledDarken: 15%;
|
@disabledDarken: 15%;
|
||||||
@bgTransparency: 10%;
|
@bgTransparency: 10%;
|
||||||
|
@bgHighlight: 5%;
|
||||||
|
@fgHighlight: 10%;
|
||||||
|
|
||||||
// Foreground colors
|
// Foreground colors
|
||||||
@fg: #CCC;
|
@fg: #CCC;
|
||||||
@@ -21,9 +23,14 @@
|
|||||||
@bgBlack: #000;
|
@bgBlack: #000;
|
||||||
@primary-bg: fadeout(darken(@primary, 47%), 15%);
|
@primary-bg: fadeout(darken(@primary, 47%), 15%);
|
||||||
@alt-primary-bg: fadeout(darken(@primary, 42%), 15%); // Lighter brown background
|
@alt-primary-bg: fadeout(darken(@primary, 42%), 15%); // Lighter brown background
|
||||||
@secondary-bg: fadeout(darken(@secondary, @bgDarken), @bgTransparency); // Brown background
|
@secondary-bg: fadeout(darken(@secondary, @bgDarken), @bgTransparency); // Blue background
|
||||||
@warning-bg: fadeout(darken(@warning, @bgDarken), @bgTransparency); // Dark Red
|
@warning-bg: fadeout(darken(@warning, @bgDarken), @bgTransparency); // Dark Red
|
||||||
|
|
||||||
|
@alt-primary-bg-highlighted: lighten(@alt-primary-bg, @bgHighlight);
|
||||||
|
@fg-highlighted: lighten(@fg, @fgHighlight);
|
||||||
|
@primary-darker: darken(@primary, 30%);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.fg {
|
.fg {
|
||||||
color: @fg;
|
color: @fg;
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ textarea {
|
|||||||
width:100%;
|
width:100%;
|
||||||
min-height: 10em;
|
min-height: 10em;
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
user-select: auto;
|
user-select: text;
|
||||||
margin:2em 0;
|
margin:2em 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,11 +170,8 @@ select {
|
|||||||
list-style: none;
|
list-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.hardpoint {
|
.hardpoint {
|
||||||
.c {
|
|
||||||
width: 4.5em;
|
width: 4.5em;
|
||||||
padding: 0.1em 0.2em;
|
padding: 0.1em 0.2em;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,3 +50,44 @@ a.ship {
|
|||||||
float: right;
|
float: right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.shipyard-table-wrapper {
|
||||||
|
white-space: nowrap;
|
||||||
|
margin: 0 auto;
|
||||||
|
font-size: 0.8em;
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
table.shipyard-table{
|
||||||
|
tbody tr.comparehighlight{
|
||||||
|
background-color: @secondary-bg;
|
||||||
|
color: @fg-highlighted;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.shipyard-table-wrapper {
|
||||||
|
border-bottom: 1px solid @primary-darker;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shipyard-table-wrapper div .shipyard-table td:last-child {
|
||||||
|
border-right: 1px solid @primary-darker;
|
||||||
|
}
|
||||||
|
.shipyard-table-wrapper > .shipyard-table td:first-child {
|
||||||
|
border-left: 1px solid @primary-darker;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-wrapper{
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-tools{
|
||||||
|
text-align: left;
|
||||||
|
color: @primary;
|
||||||
|
label{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ tbody tr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.no-touch &.highlight:hover, .no-touch &.highlighted {
|
.no-touch &.highlight:hover, .no-touch &.highlighted {
|
||||||
background-color: @warning-bg;
|
background-color: @alt-primary-bg-highlighted; //@warning-bg;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:nth-child(odd){
|
&:nth-child(odd){
|
||||||
@@ -84,3 +84,5 @@ td {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
19
src/sw.js
19
src/sw.js
@@ -7,8 +7,8 @@ if (workbox) {
|
|||||||
workbox.routing.registerNavigationRoute('/index.html');
|
workbox.routing.registerNavigationRoute('/index.html');
|
||||||
|
|
||||||
workbox.routing.registerRoute(
|
workbox.routing.registerRoute(
|
||||||
new RegExp('/(.*?)'),
|
/\.(?:png|jpg|jpeg|svg|gif)$/,
|
||||||
workbox.strategies.staleWhileRevalidate({
|
new workbox.strategies.CacheFirst({
|
||||||
plugins: [
|
plugins: [
|
||||||
new workbox.cacheableResponse.Plugin({
|
new workbox.cacheableResponse.Plugin({
|
||||||
statuses: [0, 200]
|
statuses: [0, 200]
|
||||||
@@ -17,9 +17,16 @@ if (workbox) {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
workbox.routing.registerRoute(
|
||||||
|
/\.(?:js|css)$/,
|
||||||
|
new workbox.strategies.StaleWhileRevalidate({
|
||||||
|
cacheName: 'static-resources',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
workbox.routing.registerRoute(
|
workbox.routing.registerRoute(
|
||||||
new RegExp('https://fonts.(?:googleapis|gstatic).com/(.*)'),
|
new RegExp('https://fonts.(?:googleapis|gstatic).com/(.*)'),
|
||||||
workbox.strategies.cacheFirst({
|
new workbox.strategies.CacheFirst({
|
||||||
cacheName: 'google-fonts',
|
cacheName: 'google-fonts',
|
||||||
plugins: [
|
plugins: [
|
||||||
new workbox.expiration.Plugin({
|
new workbox.expiration.Plugin({
|
||||||
@@ -31,12 +38,6 @@ if (workbox) {
|
|||||||
]
|
]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
try {
|
|
||||||
workbox.googleAnalytics.initialize();
|
|
||||||
} catch (e) {
|
|
||||||
console.log('Probably an ad-blocker');
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
console.log('Boo! Workbox didn\'t load 😬');
|
console.log('Boo! Workbox didn\'t load 😬');
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user