mirror of
https://github.com/EDCD/coriolis.git
synced 2025-12-10 15:15:34 +00:00
Compare commits
53 Commits
master
...
c07cfc6e70
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
6
.babelrc
6
.babelrc
@@ -7,8 +7,6 @@
|
|||||||
"@babel/plugin-syntax-dynamic-import",
|
"@babel/plugin-syntax-dynamic-import",
|
||||||
"@babel/plugin-syntax-import-meta",
|
"@babel/plugin-syntax-import-meta",
|
||||||
["@babel/plugin-proposal-class-properties", { "loose": true }],
|
["@babel/plugin-proposal-class-properties", { "loose": true }],
|
||||||
"@babel/plugin-proposal-do-expressions",
|
|
||||||
"@babel/plugin-proposal-function-bind",
|
|
||||||
"@babel/plugin-proposal-json-strings",
|
"@babel/plugin-proposal-json-strings",
|
||||||
[
|
[
|
||||||
"@babel/plugin-proposal-decorators",
|
"@babel/plugin-proposal-decorators",
|
||||||
@@ -30,7 +28,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"@babel/plugin-proposal-nullish-coalescing-operator",
|
"@babel/plugin-proposal-nullish-coalescing-operator",
|
||||||
["@babel/plugin-proposal-private-methods", { "loose": true }],
|
"@babel/plugin-proposal-do-expressions",
|
||||||
["@babel/plugin-proposal-private-property-in-object", { "loose": true }]
|
"@babel/plugin-proposal-function-bind"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,77 @@
|
|||||||
Dockerfile
|
|
||||||
.dockerignore
|
|
||||||
.gitignore
|
|
||||||
README.md
|
|
||||||
|
|
||||||
build
|
|
||||||
node_modules
|
node_modules
|
||||||
|
npm-debug.log
|
||||||
|
### Node template
|
||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# TypeScript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# next.js build output
|
||||||
|
.next
|
||||||
|
|
||||||
|
# nuxt.js build output
|
||||||
|
.nuxt
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
.serverless
|
||||||
|
|
||||||
|
|||||||
14
.gitattributes
vendored
14
.gitattributes
vendored
@@ -1,14 +0,0 @@
|
|||||||
# Set the default behavior, in case people don't have core.autocrlf set, in order to prevent line ending inconsistency.
|
|
||||||
* text=auto
|
|
||||||
|
|
||||||
# Explicitly declare text files you want to always be normalized and converted
|
|
||||||
# to native line endings on checkout.
|
|
||||||
*.jsx text
|
|
||||||
*.js text
|
|
||||||
|
|
||||||
# Declare files that will always have CRLF line endings on checkout.
|
|
||||||
# *.sln text eol=crlf
|
|
||||||
|
|
||||||
# Denote all files that are truly binary and should not be modified.
|
|
||||||
*.png binary
|
|
||||||
*.jpg binary
|
|
||||||
1
.github/FUNDING.yml
vendored
1
.github/FUNDING.yml
vendored
@@ -1 +0,0 @@
|
|||||||
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [Brighter-Applications]
|
|
||||||
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
|
||||||
16
.travis.yml
Normal file
16
.travis.yml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
language: node_js
|
||||||
|
notifications:
|
||||||
|
email: false
|
||||||
|
sudo: false
|
||||||
|
node_js:
|
||||||
|
- "4.8.1"
|
||||||
|
cache:
|
||||||
|
directories:
|
||||||
|
- node_modules
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- git clone https://github.com/EDCD/coriolis-data.git ../coriolis-data
|
||||||
|
|
||||||
|
script:
|
||||||
|
- npm run lint
|
||||||
|
- npm test
|
||||||
50
Dockerfile
50
Dockerfile
@@ -1,25 +1,35 @@
|
|||||||
#syntax=docker/dockerfile:1.4
|
### STAGE 1: Build ###
|
||||||
# Run this from within this directory. Change the location of coriolis-data repo and image name/tag as needed.
|
FROM node:9.11.1-alpine as builder
|
||||||
# docker buildx build --build-context data=../coriolis-data --tag coriolis .
|
ARG branch=develop
|
||||||
|
ENV BRANCH=$branch
|
||||||
|
WORKDIR /src/app
|
||||||
|
RUN mkdir -p /src/app/coriolis
|
||||||
|
RUN mkdir -p /src/app/coriolis-data
|
||||||
|
|
||||||
FROM node:18-alpine
|
RUN apk add --update git
|
||||||
|
|
||||||
# TODO: For a production build, we may want to just build the bundle and copy that in. No need for local copy of source.
|
COPY . /src/app/coriolis
|
||||||
WORKDIR /app
|
|
||||||
ADD . .
|
|
||||||
COPY --from=data . /coriolis-data/
|
|
||||||
|
|
||||||
# Git is required before install if any modules (like coriolis-data) are loaded from github
|
RUN npm i -g npm
|
||||||
RUN apk update
|
|
||||||
RUN apk add git
|
|
||||||
|
|
||||||
WORKDIR /app/coriolis-data
|
# Set up coriolis-data
|
||||||
RUN npm install
|
WORKDIR /src/app/coriolis-data
|
||||||
WORKDIR /app
|
RUN git clone https://github.com/EDCD/coriolis-data.git .
|
||||||
RUN npm install
|
RUN git checkout ${BRANCH}
|
||||||
# Bundle for production config with webpack & log
|
RUN npm install --no-package-lock
|
||||||
RUN npm run build > >(tee -a stdout.log) 2> >(tee -a stderr.log >&2)
|
RUN npm start
|
||||||
|
|
||||||
# Optimally, this will start a static asset server like nginx/apache. Currently, this will start dev webpack server.
|
# Set up coriolis
|
||||||
CMD ["npm", "start"]
|
WORKDIR /src/app/coriolis
|
||||||
EXPOSE 3300
|
RUN git checkout ${BRANCH}
|
||||||
|
RUN npm install --no-package-lock
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
|
||||||
|
### STAGE 2: Production Environment ###
|
||||||
|
FROM fholzer/nginx-brotli as web
|
||||||
|
COPY nginx.conf /etc/nginx/nginx.conf
|
||||||
|
COPY --from=builder /src/app/coriolis/build /usr/share/nginx/html
|
||||||
|
WORKDIR /usr/share/nginx/html
|
||||||
|
EXPOSE 80
|
||||||
|
CMD ["nginx", "-c", "/etc/nginx/nginx.conf", "-g", "daemon off;"]
|
||||||
|
|||||||
@@ -1,23 +0,0 @@
|
|||||||
#syntax=docker/dockerfile:1.4
|
|
||||||
# Run this from within this directory. Change the location of coriolis-data repo and image name/tag as needed.
|
|
||||||
# docker buildx build --build-context data=../coriolis-data --tag coriolis -f ./Dockerfile-dev .
|
|
||||||
|
|
||||||
FROM node:18-alpine
|
|
||||||
|
|
||||||
WORKDIR /app
|
|
||||||
ADD . .
|
|
||||||
COPY --from=data . /coriolis-data/
|
|
||||||
|
|
||||||
# Install git & any other desired in-container dev tools
|
|
||||||
# Git is required before install if any modules (like coriolis-data) are loaded from github
|
|
||||||
RUN apk update
|
|
||||||
RUN apk add git
|
|
||||||
|
|
||||||
WORKDIR /app/coriolis-data
|
|
||||||
RUN npm install
|
|
||||||
WORKDIR /app
|
|
||||||
RUN npm install
|
|
||||||
|
|
||||||
|
|
||||||
CMD ["npm", "start"]
|
|
||||||
EXPOSE 3300
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
#syntax=docker/dockerfile:1.4
|
|
||||||
# Run this from within this directory. Change the location of coriolis-data repo and image name/tag as needed.
|
|
||||||
# docker buildx build --build-context data=../coriolis-data --tag coriolis:0.0.7-local-prod -f Dockerfile-local-prod .
|
|
||||||
# docker run -d -p 80:8080 coriolis:0.0.7-local-prod
|
|
||||||
|
|
||||||
FROM node:18-alpine
|
|
||||||
|
|
||||||
# TODO: For a production build, we may want to just build the bundle and copy that in. No need for local copy of source.
|
|
||||||
WORKDIR /app
|
|
||||||
ADD . .
|
|
||||||
# COPY --from=data . /coriolis-data/
|
|
||||||
|
|
||||||
# Git is required before install if any modules (like coriolis-data is now referenced in the package.json) are loaded from github
|
|
||||||
RUN apk update
|
|
||||||
RUN apk add git
|
|
||||||
|
|
||||||
# WORKDIR /app/coriolis-data
|
|
||||||
# RUN npm install
|
|
||||||
# WORKDIR /app
|
|
||||||
# RUN npm install
|
|
||||||
# Bundle for production config with webpack & log
|
|
||||||
# In this version of the dockerfile, I'm deferring automated webpack build so I can monitor a manual build
|
|
||||||
# RUN npm run build > >(tee -a stdout.log) 2> >(tee -a stderr.log >&2)
|
|
||||||
|
|
||||||
RUN npm install -g http-server
|
|
||||||
|
|
||||||
# Optimally, this will start a static asset server like nginx/apache. Currently, this will start dev webpack server.
|
|
||||||
# CMD ["http-server", "/app/build", "-c-1"]
|
|
||||||
CMD ["/bin/ash"]
|
|
||||||
# CMD [""]
|
|
||||||
|
|
||||||
EXPOSE 8080
|
|
||||||
77
README.md
77
README.md
@@ -1,57 +1,60 @@
|
|||||||
[](https://discord.gg/0uwCh6R62aPRjk9w)
|
 [](https://travis-ci.org/EDCD/coriolis) [](https://discord.gg/0uwCh6R62aPRjk9w)
|
||||||
|
|
||||||
## About
|
## About
|
||||||
|
|
||||||
The Coriolis project was inspired by E:D Shipyard and, of course, [Elite Dangerous](http://www.elitedangerous.com). The ultimate goal of Coriolis is to provide rich features to support in-game play and planning while engaging the E:D community to support its development.
|
The Coriolis project was inspired by [E:D Shipyard](http://www.edshipyard.com/) and, of course, [Elite Dangerous](http://www.elitedangerous.com). The ultimate goal of Coriolis is to provide rich features to support in-game play and planning while engaging the E:D community to support its development.
|
||||||
|
|
||||||
Coriolis was created using assets and imagery from Elite: Dangerous, with the permission of Frontier Developments plc, for non-commercial purposes. It is not endorsed by nor reflects the views or opinions of Frontier Developments and no employee of Frontier Developments was involved in the making of it.
|
Coriolis was created using assets and imagery from Elite: Dangerous, with the permission of Frontier Developments plc, for non-commercial purposes. It is not endorsed by nor reflects the views or opinions of Frontier Developments and no employee of Frontier Developments was involved in the making of it.
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
- [Submit issues](https://github.com/EDCD/coriolis/issues)
|
Please [submit issues](https://github.com/EDCD/coriolis/issues), or better yet [pull requests](https://github.com/EDCD/coriolis/pulls) for any corrections or additions to the database or the code.
|
||||||
- [Submit pull requests](https://github.com/EDCD/coriolis/pulls) targetting `develop` branch
|
|
||||||
- Chat to us on [Discord](https://discord.gg/0uwCh6R62aPRjk9w)!
|
### Translations
|
||||||
|
|
||||||
|
Please use the OneSky translation site to suggest new translations: http://edcd-coriolis.oneskyapp.com
|
||||||
|
These will be merged regularly by the project manager.
|
||||||
|
|
||||||
|
### Feature Requests, Suggestions & Bugs
|
||||||
|
|
||||||
|
Chat to us on [Discord](https://discord.gg/0uwCh6R62aPRjk9w)!
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
This release includes the ability to run the app as a Docker container.
|
See the [Developer's Guide](https://github.com/EDCD/coriolis/wiki/Developing-for-Coriolis) in the wiki.
|
||||||
```sh
|
|
||||||
> git clone https://github.com/EDCD/coriolis.git
|
|
||||||
> git clone https://github.com/EDCD/coriolis-data.git
|
|
||||||
> cd coriolis
|
|
||||||
> docker buildx build --build-context data=../coriolis-data --tag coriolis .
|
|
||||||
> docker run -d -p 3300:3300 coriolis
|
|
||||||
```
|
|
||||||
|
|
||||||
Or to run an instance of coriolis without Docker Desktop, perform the following steps in a shell:
|
Also see [the documentation site.](https://coriolis.willb.info/)
|
||||||
```sh
|
|
||||||
> git clone https://github.com/EDCD/coriolis.git
|
|
||||||
> git clone https://github.com/EDCD/coriolis-data.git
|
|
||||||
> cd ./coriolis-data
|
|
||||||
> npm install
|
|
||||||
> cd ../coriolis
|
|
||||||
> npm install
|
|
||||||
> npm start
|
|
||||||
```
|
|
||||||
|
|
||||||
You will then have a development server running on `localhost:3300`.
|
|
||||||
|
|
||||||
### Ship and Module Database
|
### Ship and Module Database
|
||||||
|
|
||||||
See the [Data wiki](https://github.com/EDCD/coriolis-data/wiki) for details on structure, etc.
|
See the [Data wiki](https://github.com/cmmcleod/coriolis-data/wiki) for details on structure, etc.
|
||||||
|
|
||||||
## Deployment
|
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.
|
||||||
|
|
||||||
Follow the steps for [Development](#development) as above, but instead
|
## License
|
||||||
of `npm start` you'll want to:
|
|
||||||
|
|
||||||
```sh
|
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
|
||||||
> npm run build
|
[terms and conditions](https://www.frontierstore.net/terms-and-conditions/).
|
||||||
```
|
|
||||||
|
|
||||||
this will result in a `build/` directory being created containing all the necessary files.
|
The code (Javascript, CSS, HTML, and SVG files only) specificially for Coriolis.io is released under the MIT License.
|
||||||
|
|
||||||
After this you need to serve the files in some manner.
|
Copyright (c) 2015 Coriolis.io, Colin McLeod
|
||||||
Either configure your webserver to make the actual `build/` directory
|
|
||||||
visible on the web, or alternatively copy it to somewhere to serve it
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
from.
|
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.
|
||||||
|
|||||||
@@ -1,50 +1,50 @@
|
|||||||
{
|
{
|
||||||
"type_6_transporter": {
|
"type_6_transporter": {
|
||||||
"Cargo": "A0p0tdFal8d8s8f4-----04040303430101-.Iw18UA==.Aw18UA==.",
|
"Cargo": "A0p0tdFal8d8s8f4-----04040303430101.Iw1/kA==.Aw1/kA==.",
|
||||||
"Miner": "A0p5tdFal8d8s8f42l2l---040403451q0101-.Iw18UA==.Aw18UA==.",
|
"Miner": "A0p5tdFal8d8s8f42l2l---040403451q0101.Iw1/kA==.Aw1/kA==.",
|
||||||
"Hopper": "A0p0tdFal8d0s8f41717---030302024300--.Iw18UA==.Aw18UA==."
|
"Hopper": "A0p0tdFal8d0s8f41717---030302024300-.Iw1/kA==.Aw1/kA==."
|
||||||
},
|
},
|
||||||
"type_7_transport": {
|
"type_7_transport": {
|
||||||
"Cargo": "A0p0tiFfliddsdf5--------0505040403480101--.Iw18eQ==.Aw18eQ==.",
|
"Cargo": "A0p0tiFfliddsdf5--------0505040403480101.Iw18aQ==.Aw18aQ==.",
|
||||||
"Miner": "A0pdtiFflid8sdf5--2l2l----0505041v03450000--.Iw18eQ==.Aw18eQ==."
|
"Miner": "A0pdtiFflid8sdf5--2l2l----0505041v03450000.Iw18aQ==.Aw18aQ==."
|
||||||
},
|
},
|
||||||
"federal_dropship": {
|
"federal_dropship": {
|
||||||
"Cargo": "A0pdtiFflnddsif4-1717------05040448--020201-.Iw18RQ==.Aw18RQ==."
|
"Cargo": "A0pdtiFflnddsif4-1717------05040448--020201.Iw18eQ==.Aw18eQ==."
|
||||||
},
|
},
|
||||||
"asp": {
|
"asp": {
|
||||||
"Miner": "A2pftfFflidfskf50s0s24242l2l---04054a1q02022o27-.Iw18eQ==.Aw18eQ==."
|
"Miner": "A2pftfFflidfskf50s0s24242l2l---04054a1q02022o27.Iw18WQ==.Aw18WQ==."
|
||||||
},
|
},
|
||||||
"imperial_clipper": {
|
"imperial_clipper": {
|
||||||
"Cargo": "A0p5tiFflndisnf4--0s0s----0605450302020101-.Iw18WQ==.Aw18WQ==.",
|
"Cargo": "A0p5tiFflndisnf4--0s0s----0605450302020101.Iw18aQ==.Aw18aQ==.",
|
||||||
"Dream": "A2pktkFflndpskf40v0v0s0s0404040n4k5n5d2b29292o--.AwRj4yWU1Yg=.CwBhCYy6YRigzPIA.",
|
"Dream": "A2pktkFflndpskf40v0v0s0s0404040n4k5n5d2b29292o-.AwRj4yWU1I==.CwBhCYy6YRigzLIA.",
|
||||||
"Current": "A0patkFflndfskf4-----------------.AwRj4yWU1Yg=.CwBhCYy6YRigzPIA."
|
"Current": "A0patkFflndfskf4----------------.AwRj4yWU1I==.CwBhCYy6YRigzLIA."
|
||||||
},
|
},
|
||||||
"type_9_heavy": {
|
"type_9_heavy": {
|
||||||
"Current": "A0patsFklndnsif6---------0706054a0303020224--.AwRj4yo5iA==.EwBhEYy6d6g=."
|
"Current": "A0patsFklndnsif6---------0706054a0303020224.AwRj4yoo.EwBhEYy6dsg=."
|
||||||
},
|
},
|
||||||
"python": {
|
"python": {
|
||||||
"Cargo": "A0patnFflidsssf5---------050505040448020201-.Iw18eAMQ.Aw18RQ==.",
|
"Cargo": "A0patnFflidsssf5---------050505040448020201.Iw18eQ==.Aw18eQ==.",
|
||||||
"Miner": "A0pktkFflidpspf50v0v0v2m2m0404--050505Ce4a1v02022o-.Iw18eAMQ.IwBhBYy6dkCYRA==.",
|
"Miner": "A0pktkFflidpspf50v0v0v2m2m0404--050505Ce4a1v02022o.Iw18eQ==.IwBhBYy6dkCYg===.",
|
||||||
"Dream": "A2pptkFfliduspf50v0v0v27270404040m5n5n4f2d2d032t0201-.Iw1+gDByUA==.EwBhEYy6e0VEA===.",
|
"Dream": "A2pptkFfliduspf50v0v0v27270404040m5n5n4f2d2d032t0201.Iw1+gDBxA===.EwBhEYy6e0WEA===.",
|
||||||
"Missile": "A0pttoFjljdystf52f2g2d2ePh----04044j03---00--.Iw18eAMQ.Aw18RQ==."
|
"Missile": "A0pttoFjljdystf52f2g2d2ePh----04044j03---002h.Iw18eQ==.Aw18eQ==."
|
||||||
},
|
},
|
||||||
"anaconda": {
|
"anaconda": {
|
||||||
"Dream": "A4putpFklndzsuf52c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d04-0303326b-.AwRj4yo5dzhA.MwBhCYy6duvARhEA.",
|
"Dream": "A4putpFklndzsuf52c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d04-0303326b.AwRj4yo5dyg=.MwBhCYy6duvARiA=.",
|
||||||
"Cargo": "A0patnFklndnsxf5----------------06050505040404-45030301-.Iw18ZUAxA===.Aw18ZXEA.",
|
"Cargo": "A0patnFklndnsxf5----------------06050505040404-45030301.Iw18ZVA=.Aw18ZVA=.",
|
||||||
"Current": "A0patnFklndksxf5----------------06050505040404-03034524-.Iw18ZUAxA===.Aw18ZXEA.",
|
"Current": "A0patnFklndksxf5----------------06050505040404-03034524.Iw18ZVA=.Aw18ZVA=.",
|
||||||
"Explorer": "A0patnFklndksxf5--------0202------f7050505040s37--2i4524-.AwRj4yVKJ9jCA===.AwhMIyumQRgkA===.",
|
"Explorer": "A0patnFklndksxf5--------0202------f7050505040s37-2f2i4524.AwRj4yVKJ9hA.AwhMIyumQRhEA===.",
|
||||||
"Test": "A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b-.Iw18ZUAxA===.Aw18ZXEA."
|
"Test": "A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.Iw18ZVA=.Aw18ZVA=."
|
||||||
},
|
},
|
||||||
"diamondback_explorer": {
|
"diamondback_explorer": {
|
||||||
"Explorer": "A0p0tdFfldddsdf5---0202--320p432i----.AwRj4zTZaA==.AwiMIyqo."
|
"Explorer": "A0p0tdFfldddsdf5---0202--320p432i2f-.AwRj4zTYg===.AwiMIyoo."
|
||||||
},
|
},
|
||||||
"vulture": {
|
"vulture": {
|
||||||
"Bounty Hunter": "A3patcFalddksff31e1e0404-0l4a-5d27662j--.AwRj4z2Gg===.MwBhBYy6oJmAjLMQ."
|
"Bounty Hunter": "A3patcFalddksff31e1e0404-0l4a-5d27662j.AwRj4z2I.MwBhBYy6oJmAjLIA."
|
||||||
},
|
},
|
||||||
"fer_de_lance": {
|
"fer_de_lance": {
|
||||||
"Attack": "A2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27--.Iw18aAMQ.CwBhrSu8EZxEA===."
|
"Attack": "A2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.CwBhrSu8EZyA."
|
||||||
},
|
},
|
||||||
"eagle": {
|
"eagle": {
|
||||||
"Figther": "A4p0t5F5l3d5s5f20p0p24-4053-2j---.Iw18gDJQ.Aw19kA==."
|
"Figther": "A4p0t5F5l3d5s5f20p0p24-4053-2j-.Iw18kA==.Aw18kA==."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,366 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"header": {
|
|
||||||
"appName": "Inara",
|
|
||||||
"appVersion": "1.0",
|
|
||||||
"appURL": "https:\/\/inara.cz\/cmdr-fleet\/123\/123\/",
|
|
||||||
"appCustomProperties": {
|
|
||||||
"inaraCommanderID": 123,
|
|
||||||
"inaraShipID": 123
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"Ship": "krait_mkii",
|
|
||||||
"ShipID": 7,
|
|
||||||
"ShipName": "pancake hammer",
|
|
||||||
"ShipIdent": "PH-01",
|
|
||||||
"HullValue": 44160710,
|
|
||||||
"ModulesValue": 111274094,
|
|
||||||
"Rebuy": 7771743,
|
|
||||||
"Modules": [
|
|
||||||
{
|
|
||||||
"Slot": "largehardpoint1",
|
|
||||||
"Item": "hpt_mininglaser_fixed_small",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "largehardpoint2",
|
|
||||||
"Item": "hpt_cannon_gimbal_large",
|
|
||||||
"On": true,
|
|
||||||
"Engineering": {
|
|
||||||
"BlueprintName": "weapon_overcharged",
|
|
||||||
"Level": 2,
|
|
||||||
"Quality": 1,
|
|
||||||
"ExperimentalEffect": "special_auto_loader"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "largehardpoint3",
|
|
||||||
"Item": "hpt_cannon_gimbal_large",
|
|
||||||
"On": true,
|
|
||||||
"Engineering": {
|
|
||||||
"BlueprintName": "weapon_overcharged",
|
|
||||||
"Level": 2,
|
|
||||||
"Quality": 1,
|
|
||||||
"ExperimentalEffect": "special_auto_loader"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "mediumhardpoint1",
|
|
||||||
"Item": "hpt_basicmissilerack_fixed_medium",
|
|
||||||
"On": true,
|
|
||||||
"Engineering": {
|
|
||||||
"BlueprintName": "weapon_highcapacity",
|
|
||||||
"Level": 5,
|
|
||||||
"Quality": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "mediumhardpoint2",
|
|
||||||
"Item": "hpt_basicmissilerack_fixed_medium",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "tinyhardpoint1",
|
|
||||||
"Item": "hpt_heatsinklauncher_turret_tiny",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "tinyhardpoint2",
|
|
||||||
"Item": "hpt_cloudscanner_size0_class3",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "tinyhardpoint3",
|
|
||||||
"Item": "hpt_shieldbooster_size0_class5",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "tinyhardpoint4",
|
|
||||||
"Item": "hpt_shieldbooster_size0_class5",
|
|
||||||
"On": true,
|
|
||||||
"Priority": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot01_size6",
|
|
||||||
"Item": "int_cargorack_size6_class1",
|
|
||||||
"On": true,
|
|
||||||
"Priority": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot02_size6",
|
|
||||||
"Item": "int_cargorack_size6_class1",
|
|
||||||
"On": true,
|
|
||||||
"Priority": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot03_size5",
|
|
||||||
"Item": "int_guardianfsdbooster_size5",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot04_size5",
|
|
||||||
"Item": "int_fighterbay_size5_class1",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot05_size4",
|
|
||||||
"Item": "int_shieldgenerator_size4_class5",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot06_size3",
|
|
||||||
"Item": "int_dronecontrol_collection_size3_class4",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot07_size3",
|
|
||||||
"Item": "int_dronecontrol_collection_size3_class4",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot08_size2",
|
|
||||||
"Item": "int_refinery_size2_class2",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot09_size1",
|
|
||||||
"Item": "int_dronecontrol_prospector_size1_class4",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "powerplant",
|
|
||||||
"Item": "int_powerplant_size7_class5",
|
|
||||||
"On": true,
|
|
||||||
"Priority": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "mainengines",
|
|
||||||
"Item": "int_engine_size6_class5",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "frameshiftdrive",
|
|
||||||
"Item": "int_hyperdrive_size5_class5",
|
|
||||||
"On": true,
|
|
||||||
"Engineering": {
|
|
||||||
"BlueprintName": "fsd_longrange",
|
|
||||||
"Level": 2,
|
|
||||||
"Quality": 0.861
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "lifesupport",
|
|
||||||
"Item": "int_lifesupport_size4_class2",
|
|
||||||
"On": true,
|
|
||||||
"Priority": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "powerdistributor",
|
|
||||||
"Item": "int_powerdistributor_size7_class5",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "radar",
|
|
||||||
"Item": "int_sensors_size6_class2",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "fueltank",
|
|
||||||
"Item": "int_fueltank_size5_class3",
|
|
||||||
"On": true,
|
|
||||||
"Priority": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "armour",
|
|
||||||
"Item": "krait_mkii_armour_grade3",
|
|
||||||
"On": true,
|
|
||||||
"Priority": 1,
|
|
||||||
"Engineering": {
|
|
||||||
"BlueprintName": "armour_heavyduty",
|
|
||||||
"Level": 5,
|
|
||||||
"Quality": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"header": {
|
|
||||||
"appName": "Inara",
|
|
||||||
"appVersion": "1.0",
|
|
||||||
"appURL": "https:\/\/inara.cz\/cmdr-fleet\/123\/123\/",
|
|
||||||
"appCustomProperties": {
|
|
||||||
"inaraCommanderID": 123,
|
|
||||||
"inaraShipID": 123
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"Ship": "diamondbackxl",
|
|
||||||
"ShipID": 11,
|
|
||||||
"ShipName": "star Hopper",
|
|
||||||
"ShipIdent": "PH-02",
|
|
||||||
"HullValue": 1615649,
|
|
||||||
"ModulesValue": 16981039,
|
|
||||||
"Rebuy": 929837,
|
|
||||||
"Modules": [
|
|
||||||
{
|
|
||||||
"Slot": "tinyhardpoint1",
|
|
||||||
"Item": "hpt_heatsinklauncher_turret_tiny",
|
|
||||||
"On": true,
|
|
||||||
"Value": 3072
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot01_size4",
|
|
||||||
"Item": "int_fuelscoop_size4_class5",
|
|
||||||
"On": true,
|
|
||||||
"Priority": 3,
|
|
||||||
"Value": 2862364
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot02_size4",
|
|
||||||
"Item": "int_guardianfsdbooster_size4",
|
|
||||||
"On": true,
|
|
||||||
"Value": 2847499
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot03_size3",
|
|
||||||
"Item": "int_shieldgenerator_size3_class2",
|
|
||||||
"On": true,
|
|
||||||
"Value": 18812,
|
|
||||||
"Engineering": {
|
|
||||||
"BlueprintName": "shieldgenerator_thermic",
|
|
||||||
"Level": 3,
|
|
||||||
"Quality": 1,
|
|
||||||
"ExperimentalEffect": "special_shield_health"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot04_size3",
|
|
||||||
"Item": "int_repairer_size3_class5",
|
|
||||||
"On": true,
|
|
||||||
"Value": 2302911
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot05_size2",
|
|
||||||
"Item": "int_buggybay_size2_class2",
|
|
||||||
"On": true,
|
|
||||||
"Priority": 3,
|
|
||||||
"Value": 21600
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot06_size2",
|
|
||||||
"Item": "int_cargorack_size2_class1",
|
|
||||||
"On": true,
|
|
||||||
"Priority": 1,
|
|
||||||
"Value": 2852
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot07_size1",
|
|
||||||
"Item": "int_supercruiseassist",
|
|
||||||
"On": true,
|
|
||||||
"Priority": 3,
|
|
||||||
"Value": 9121
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot08_size1",
|
|
||||||
"Item": "int_detailedsurfacescanner_tiny",
|
|
||||||
"On": true,
|
|
||||||
"Value": 250000,
|
|
||||||
"Engineering": {
|
|
||||||
"BlueprintName": "sensor_expanded",
|
|
||||||
"Level": 5,
|
|
||||||
"Quality": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "powerplant",
|
|
||||||
"Item": "int_powerplant_size4_class5",
|
|
||||||
"On": true,
|
|
||||||
"Priority": 1,
|
|
||||||
"Value": 1441233,
|
|
||||||
"Engineering": {
|
|
||||||
"BlueprintName": "powerplant_boosted",
|
|
||||||
"Level": 1,
|
|
||||||
"Quality": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "mainengines",
|
|
||||||
"Item": "int_engine_size4_class5",
|
|
||||||
"On": true,
|
|
||||||
"Value": 1610080,
|
|
||||||
"Engineering": {
|
|
||||||
"BlueprintName": "engine_dirty",
|
|
||||||
"Level": 5,
|
|
||||||
"Quality": 1,
|
|
||||||
"ExperimentalEffect": "special_engine_lightweight"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "frameshiftdrive",
|
|
||||||
"Item": "int_hyperdrive_size5_class5",
|
|
||||||
"On": true,
|
|
||||||
"Value": 5103953,
|
|
||||||
"Engineering": {
|
|
||||||
"BlueprintName": "fsd_longrange",
|
|
||||||
"Level": 5,
|
|
||||||
"Quality": 1,
|
|
||||||
"ExperimentalEffect": "special_fsd_lightweight"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "lifesupport",
|
|
||||||
"Item": "int_lifesupport_size3_class2",
|
|
||||||
"On": true,
|
|
||||||
"Value": 10133,
|
|
||||||
"Engineering": {
|
|
||||||
"BlueprintName": "misc_lightweight",
|
|
||||||
"Level": 3,
|
|
||||||
"Quality": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "powerdistributor",
|
|
||||||
"Item": "int_powerdistributor_size4_class5",
|
|
||||||
"On": true,
|
|
||||||
"Value": 389022,
|
|
||||||
"Engineering": {
|
|
||||||
"BlueprintName": "powerdistributor_highfrequency",
|
|
||||||
"Level": 4,
|
|
||||||
"Quality": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "radar",
|
|
||||||
"Item": "int_sensors_size3_class2",
|
|
||||||
"On": true,
|
|
||||||
"Value": 10133,
|
|
||||||
"Engineering": {
|
|
||||||
"BlueprintName": "sensor_lightweight",
|
|
||||||
"Level": 5,
|
|
||||||
"Quality": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "fueltank",
|
|
||||||
"Item": "int_fueltank_size5_class3",
|
|
||||||
"On": true,
|
|
||||||
"Priority": 1,
|
|
||||||
"Value": 97754
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "armour",
|
|
||||||
"Item": "diamondbackxl_armour_grade1",
|
|
||||||
"On": true,
|
|
||||||
"Priority": 1,
|
|
||||||
"Engineering": {
|
|
||||||
"BlueprintName": "armour_heavyduty",
|
|
||||||
"Level": 5,
|
|
||||||
"Quality": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"krait_mkii": {
|
|
||||||
"Imported pancake hammer": "A2pptkFflidussf52l1o1o2g2g020g040405051Ofr45C9C91oP3.Iw18eQ==.AwRgzKIkA===."
|
|
||||||
},
|
|
||||||
"diamondback_explorer": {
|
|
||||||
"Imported star Hopper": "A0pataFflddfsdf5---02---321P430iv6013w2i.Iw18SQ==.AwRm44GYpKg=."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,188 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"header": {
|
|
||||||
"appName": "Inara",
|
|
||||||
"appVersion": "1.0",
|
|
||||||
"appURL": "https:\/\/inara.cz\/cmdr-fleet\/123\/123\/",
|
|
||||||
"appCustomProperties": {
|
|
||||||
"inaraCommanderID": 123,
|
|
||||||
"inaraShipID": 123
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"data": {
|
|
||||||
"Ship": "krait_mkii",
|
|
||||||
"ShipID": 7,
|
|
||||||
"ShipName": "pancake hammer",
|
|
||||||
"ShipIdent": "PH-01",
|
|
||||||
"HullValue": 44160710,
|
|
||||||
"ModulesValue": 111274094,
|
|
||||||
"Rebuy": 7771743,
|
|
||||||
"Modules": [
|
|
||||||
{
|
|
||||||
"Slot": "largehardpoint1",
|
|
||||||
"Item": "hpt_mininglaser_fixed_small",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "largehardpoint2",
|
|
||||||
"Item": "hpt_cannon_gimbal_large",
|
|
||||||
"On": true,
|
|
||||||
"Engineering": {
|
|
||||||
"BlueprintName": "weapon_overcharged",
|
|
||||||
"Level": 2,
|
|
||||||
"Quality": 1,
|
|
||||||
"ExperimentalEffect": "special_auto_loader"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "largehardpoint3",
|
|
||||||
"Item": "hpt_cannon_gimbal_large",
|
|
||||||
"On": true,
|
|
||||||
"Engineering": {
|
|
||||||
"BlueprintName": "weapon_overcharged",
|
|
||||||
"Level": 2,
|
|
||||||
"Quality": 1,
|
|
||||||
"ExperimentalEffect": "special_auto_loader"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "mediumhardpoint1",
|
|
||||||
"Item": "hpt_basicmissilerack_fixed_medium",
|
|
||||||
"On": true,
|
|
||||||
"Engineering": {
|
|
||||||
"BlueprintName": "weapon_highcapacity",
|
|
||||||
"Level": 5,
|
|
||||||
"Quality": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "mediumhardpoint2",
|
|
||||||
"Item": "hpt_basicmissilerack_fixed_medium",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "tinyhardpoint1",
|
|
||||||
"Item": "hpt_heatsinklauncher_turret_tiny",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "tinyhardpoint2",
|
|
||||||
"Item": "hpt_cloudscanner_size0_class3",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "tinyhardpoint3",
|
|
||||||
"Item": "hpt_shieldbooster_size0_class5",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "tinyhardpoint4",
|
|
||||||
"Item": "hpt_shieldbooster_size0_class5",
|
|
||||||
"On": true,
|
|
||||||
"Priority": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot01_size6",
|
|
||||||
"Item": "int_cargorack_size6_class1",
|
|
||||||
"On": true,
|
|
||||||
"Priority": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot02_size6",
|
|
||||||
"Item": "int_cargorack_size6_class1",
|
|
||||||
"On": true,
|
|
||||||
"Priority": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot03_size5",
|
|
||||||
"Item": "int_guardianfsdbooster_size5",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot04_size5",
|
|
||||||
"Item": "int_fighterbay_size5_class1",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot05_size4",
|
|
||||||
"Item": "int_shieldgenerator_size4_class5",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot06_size3",
|
|
||||||
"Item": "int_dronecontrol_collection_size3_class4",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot07_size3",
|
|
||||||
"Item": "int_dronecontrol_collection_size3_class4",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot08_size2",
|
|
||||||
"Item": "int_refinery_size2_class2",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "slot09_size1",
|
|
||||||
"Item": "int_dronecontrol_prospector_size1_class4",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "powerplant",
|
|
||||||
"Item": "int_powerplant_size7_class5",
|
|
||||||
"On": true,
|
|
||||||
"Priority": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "mainengines",
|
|
||||||
"Item": "int_engine_size6_class5",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "frameshiftdrive",
|
|
||||||
"Item": "int_hyperdrive_size5_class5",
|
|
||||||
"On": true,
|
|
||||||
"Engineering": {
|
|
||||||
"BlueprintName": "fsd_longrange",
|
|
||||||
"Level": 2,
|
|
||||||
"Quality": 0.861
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "lifesupport",
|
|
||||||
"Item": "int_lifesupport_size4_class2",
|
|
||||||
"On": true,
|
|
||||||
"Priority": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "powerdistributor",
|
|
||||||
"Item": "int_powerdistributor_size7_class5",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "radar",
|
|
||||||
"Item": "int_sensors_size6_class2",
|
|
||||||
"On": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "fueltank",
|
|
||||||
"Item": "int_fueltank_size5_class3",
|
|
||||||
"On": true,
|
|
||||||
"Priority": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"Slot": "armour",
|
|
||||||
"Item": "krait_mkii_armour_grade3",
|
|
||||||
"On": true,
|
|
||||||
"Priority": 1,
|
|
||||||
"Engineering": {
|
|
||||||
"BlueprintName": "armour_heavyduty",
|
|
||||||
"Level": 5,
|
|
||||||
"Quality": 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -18,13 +18,13 @@ describe('Import Modal', function() {
|
|||||||
const mockContext = {
|
const mockContext = {
|
||||||
language: getLanguage('en'),
|
language: getLanguage('en'),
|
||||||
sizeRatio: 1,
|
sizeRatio: 1,
|
||||||
openMenu: jest.fn(),
|
openMenu: jest.genMockFunction(),
|
||||||
closeMenu: jest.fn(),
|
closeMenu: jest.genMockFunction(),
|
||||||
showModal: jest.fn(),
|
showModal: jest.genMockFunction(),
|
||||||
hideModal: jest.fn(),
|
hideModal: jest.genMockFunction(),
|
||||||
tooltip: jest.fn(),
|
tooltip: jest.genMockFunction(),
|
||||||
termtip: jest.fn(),
|
termtip: jest.genMockFunction(),
|
||||||
onWindowResize: jest.fn()
|
onWindowResize: jest.genMockFunction()
|
||||||
};
|
};
|
||||||
|
|
||||||
let modal, render, ContextProvider = Utils.createContextProvider(mockContext);
|
let modal, render, ContextProvider = Utils.createContextProvider(mockContext);
|
||||||
@@ -110,25 +110,21 @@ describe('Import Modal', function() {
|
|||||||
it('catches an invalid backup', function() {
|
it('catches an invalid backup', function() {
|
||||||
const importData = require('./fixtures/valid-backup');
|
const importData = require('./fixtures/valid-backup');
|
||||||
let invalidImportData = Object.assign({}, importData);
|
let invalidImportData = Object.assign({}, importData);
|
||||||
// Remove Asp Miner build used in comparison
|
//invalidImportData.builds.asp = null; // Remove Asp Miner build used in comparison
|
||||||
delete(invalidImportData.builds.asp);
|
delete(invalidImportData.builds.asp);
|
||||||
|
|
||||||
pasteText('"this is not valid"');
|
pasteText('"this is not valid"');
|
||||||
expect(modal.state.importValid).toBeFalsy();
|
expect(modal.state.importValid).toBeFalsy();
|
||||||
expect(modal.state.errorMsg).toEqual('Must be an object or array!');
|
expect(modal.state.errorMsg).toEqual('Must be an object or array!');
|
||||||
|
|
||||||
pasteText('{ "builds": "Should not be a string" }');
|
pasteText('{ "builds": "Should not be a string" }');
|
||||||
expect(modal.state.importValid).toBeFalsy();
|
expect(modal.state.importValid).toBeFalsy();
|
||||||
expect(modal.state.errorMsg).toEqual('builds must be an object!');
|
expect(modal.state.errorMsg).toEqual('builds must be an object!');
|
||||||
|
pasteText(JSON.stringify(importData).replace('anaconda', 'invalid_ship'));
|
||||||
pasteText(JSON.stringify(importData).replace(/anaconda/g, 'invalid_ship'));
|
|
||||||
expect(modal.state.importValid).toBeFalsy();
|
expect(modal.state.importValid).toBeFalsy();
|
||||||
expect(Object.keys(modal.state.builds)).not.toContain('anaconda');
|
expect(modal.state.errorMsg).toEqual('"invalid_ship" is not a valid Ship Id!');
|
||||||
|
|
||||||
pasteText(JSON.stringify(importData).replace('Dream', ''));
|
pasteText(JSON.stringify(importData).replace('Dream', ''));
|
||||||
expect(modal.state.importValid).toBeFalsy();
|
expect(modal.state.importValid).toBeFalsy();
|
||||||
expect(Object.keys(modal.state.builds.imperial_clipper).length).toEqual(3);
|
expect(modal.state.errorMsg).toEqual('Imperial Clipper build "" must be a string at least 1 character long!');
|
||||||
|
|
||||||
pasteText(JSON.stringify(invalidImportData));
|
pasteText(JSON.stringify(invalidImportData));
|
||||||
expect(modal.state.importValid).toBeFalsy();
|
expect(modal.state.importValid).toBeFalsy();
|
||||||
expect(modal.state.errorMsg).toEqual('asp build "Miner" data is missing!');
|
expect(modal.state.errorMsg).toEqual('asp build "Miner" data is missing!');
|
||||||
@@ -148,7 +144,7 @@ describe('Import Modal', function() {
|
|||||||
expect(modal.state.singleBuild).toBe(true);
|
expect(modal.state.singleBuild).toBe(true);
|
||||||
clickProceed();
|
clickProceed();
|
||||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b-.AwRj4zNLeI%3D%3D.CwBhCYzBGW9qCTSqq5JA.&bn=Test%20My%20Ship');
|
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.AwRj4zNLaA%3D%3D.CwBhCYzBGW9qCTSqq5xA.&bn=Test%20My%20Ship');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('catches an invalid build', function() {
|
it('catches an invalid build', function() {
|
||||||
@@ -173,7 +169,7 @@ describe('Import Modal', function() {
|
|||||||
expect(modal.state.singleBuild).toBe(true);
|
expect(modal.state.singleBuild).toBe(true);
|
||||||
clickProceed();
|
clickProceed();
|
||||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b-.AwRj4zNLeI%3D%3D.CwBhCYzBGW9qCTSqq5JA.H4sIAAAAAAAAE2MUe8HMwPD%2FPwMAAGvB0AkAAAA%3D&bn=Test%20My%20Ship');
|
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.AwRj4zNLaA%3D%3D.CwBhCYzBGW9qCTSqq5xA.H4sIAAAAAAAAA2MUe8HMwPD%2FPwMAAGvB0AkAAAA%3D&bn=Test%20My%20Ship');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -190,7 +186,7 @@ describe('Import Modal', function() {
|
|||||||
expect(modal.state.singleBuild).toBe(true);
|
expect(modal.state.singleBuild).toBe(true);
|
||||||
clickProceed();
|
clickProceed();
|
||||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/asp?code=A0pftiFflfddsnf5------020202033c044002v6-2i-.AwRj4yvYg%3D%3D%3D.CwRgDBldHn5A.H4sIAAAAAAAAE2P858DAwPCXEUhwHPvx%2F78YG5AltB7I%2F8%2F0TwImJboDSPJ%2F%2B%2Ff%2Fv%2FKlX%2F%2F%2Fi3AwMTBIfARK%2FGf%2BJwVSxArStVAYqOjvz%2F%2F%2FJVo5GRhE2IBc4SKQSSz%2FDGEmCa398P8%2F%2F2%2BgTf%2F%2FA7kMAExxqlSAAAAA&bn=Multi-purpose%20Asp%20Explorer');
|
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/asp?code=A0pftiFflfddsnf5------020202033c044002v62f2i.AwRj4yvI.CwRgDBldHnJA.H4sIAAAAAAAAA2P858DAwPCXEUhwHPvx%2F78YG5AltB7I%2F8%2F0TwImJboDSPJ%2F%2B%2Ff%2Fv%2FKlX%2F%2F%2Fi3AwMTBIfARK%2FGf%2BJwVSxArStVAYqOjvz%2F%2F%2FJVo5GRhE2IBc4SKQSSz%2FDGEmCa398P8%2F%2F2%2BgTf%2F%2FA7kMAExxqlSAAAAA&bn=Multi-purpose%20Asp%20Explorer');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('imports a valid v4 build with modifications', function() {
|
it('imports a valid v4 build with modifications', function() {
|
||||||
@@ -202,11 +198,11 @@ describe('Import Modal', function() {
|
|||||||
expect(modal.state.singleBuild).toBe(true);
|
expect(modal.state.singleBuild).toBe(true);
|
||||||
clickProceed();
|
clickProceed();
|
||||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/imperial_courier?code=A0patzF5l0das8f31a1a270202000e402t0101----.AwRj4zOYg%3D%3D%3D.CwRgDBldLuZA.H4sIAAAAAAAAE12OPUvDYBSFT1OTfkRJjUkbbC3Yj8mlODgUISAtdOlety5ODv0Vgji7O7kJ%2FgzBQX%2BEY7Gg0NKhfY%2FnHQLFDBdynufe9%2BRMCmCb06g29oCgacjiRx6gY6oWKUT8UgLaszqQfHmSnpVFN1uSeXNsJVcj%2FA2EHlZkspIUpUc6UjTXGT85qwHuSEuVc%2F16r99kDQeSSjvSbSjpyUpNK10uJJ3aYqk6smwm1lQ9bOxw71TMm8VanEqq9JW1r3Qo%2BREOLnQHvbWmb7rZIu5VLIyGQGOukPv%2F0WQk5LeEAjPOUDwtAP6bShy2HKAz0HPO%2B5KsP25I79O2I7LvD%2Bz4Il1XAQAA&bn=Multi-purpose%20Imperial%20Courier');
|
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/imperial_courier?code=A0patzF5l0das8f31a1a270202000e402t0101-2f.AwRj4zKA.CwRgDBldLiQ%3D.H4sIAAAAAAAAA12OPUvDYBSFT1OTfkRJjUkbbC3Yj8mlODgUISAtdOlety5ODv0Vgji7O7kJ%2FgzBQX%2BEY7Gg0NKhfY%2FnHQLFDBdynufe9%2BRMCmCb06g29oCgacjiRx6gY6oWKUT8UgLaszqQfHmSnpVFN1uSeXNsJVcj%2FA2EHlZkspIUpUc6UjTXGT85qwHuSEuVc%2F16r99kDQeSSjvSbSjpyUpNK10uJJ3aYqk6smwm1lQ9bOxw71TMm8VanEqq9JW1r3Qo%2BREOLnQHvbWmb7rZIu5VLIyGQGOukPv%2F0WQk5LeEAjPOUDwtAP6bShy2HKAz0HPO%2B5KsP25I79O2I7LvD%2Bz4Il1XAQAA&bn=Multi-purpose%20Imperial%20Courier');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Import Detailed Builds Array', function() {
|
describe('Import Detaild Builds Array', function() {
|
||||||
|
|
||||||
beforeEach(reset);
|
beforeEach(reset);
|
||||||
|
|
||||||
@@ -244,7 +240,7 @@ describe('Import Modal', function() {
|
|||||||
expect(modal.state.singleBuild).toBe(true);
|
expect(modal.state.singleBuild).toBe(true);
|
||||||
clickProceed();
|
clickProceed();
|
||||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/federal_corvette?code=A2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifr--v66g--.AwRj4zNapI%3D%3D.CwRgDBldUExuBiIlWIA%3D.&bn=Imported%20Federal%20Corvette');
|
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/federal_corvette?code=A2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifr--v66g2f.AwRj4zNaqA%3D%3D.CwRgDBldUExuBiIlUA%3D%3D.H4sIAAAAAAAAA12STy8DURTFb1szU53Ga8dg2qqqDmJDIoKFxJImumYjVrVqfAALC4lNbcUnkLCoDbEQu0bSlQVhI8JHsJBIQ73rXMkwMYuT9%2Bb87nl%2F7ovoRSL6ikD6TYNINZg5XsWUo7pfrBikr2USlRyXyDuLAhr6ZHanNLOzD5tjOiskysk5dOBvfTB7bjeRW0MNG3ohSBq1bKKxKwyLLUAjmwjpPu4wJx4xVbNI57heDfbUKUAy2xaRUQZpllHoHMHxKqjhhF4LgjtJiFHDmqbrEeVnUJOax7%2FSdRfRwBNotv9wo5kAuZMD2egKyDYcdYl1OBki6z%2BZQjaFnBPyFCM1LefF%2BcgrY0es9FKwbW8ZYj9gmBbxRVRdglMh6BNqnwsk4ouoO4HSIehNoBuBRHwR1QOmsBvHmk6IfMbd2fdCEka%2BjNSexPWGoEkcyX6CnxbxRZQtd%2BPpym%2B31xFtn0iSFPkf%2BBkttZlzB9KDFyBuFRfAGV0Ogoff8SSsCfjjD5hGWtLIwZB%2FgX5Zt%2BLHMI9My7sp6nzgZzekswTxVvCOkq%2FSXqb%2F3zfLxh6HrwIAAA%3D%3D&bn=Imported%20Federal%20Corvette');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('imports a valid companion API build', function() {
|
it('imports a valid companion API build', function() {
|
||||||
@@ -256,7 +252,7 @@ describe('Import Modal', function() {
|
|||||||
expect(modal.state.singleBuild).toBe(true);
|
expect(modal.state.singleBuild).toBe(true);
|
||||||
clickProceed();
|
clickProceed();
|
||||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/beluga?code=A0pktsFplCdpsnf70t0t2727270004040404043c4fmimlmm04mc0iv62i--.AwRj4yusg%3D%3D%3D.CwRgDBldHi8IWIA%3D.&bn=Imported%20Beluga%20Liner');
|
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/beluga?code=A0pktsFplCdpsnf70t0t2727270004040404043c4fmimlmm04mc0iv62i2f.AwRj4yukg%3D%3D%3D.CwRgDBldHi8IUA%3D%3D.H4sIAAAAAAAAA2P8Z8%2FAwPCXEUiIKTMxMPCv%2F%2Ff%2FP8cFIPGf6Z8YTEr0GjMDg%2FJWICERBOTzn%2Fn7%2F7%2FIO5Ai5n9SIEWsQEIoSxAolfbt%2F3%2BJPk4GBhE7YQYGYVmgcuVnf4Aq%2FwOVAAAyiFctbgAAAA%3D%3D&bn=Imported%20Beluga%20Liner');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('imports a valid companion API build', function() {
|
it('imports a valid companion API build', function() {
|
||||||
@@ -268,7 +264,7 @@ describe('Import Modal', function() {
|
|||||||
expect(modal.state.singleBuild).toBe(true);
|
expect(modal.state.singleBuild).toBe(true);
|
||||||
clickProceed();
|
clickProceed();
|
||||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/type_7_transport?code=A0patfFflidasdf5----0404040005050504044d2402--.AwRj4yoo.CwRgDBlVK7HjEA%3D%3D.&bn=Imported%20Type-7%20Transporter');
|
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/type_7_transport?code=A0patfFflidasdf5----0404040005050504044d2402.AwRj4yrI.CwRgDBlVK7EiA%3D%3D%3D.&bn=Imported%20Type-7%20Transporter');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('imports a valid companion API build', function() {
|
it('imports a valid companion API build', function() {
|
||||||
@@ -280,7 +276,7 @@ describe('Import Modal', function() {
|
|||||||
expect(modal.state.singleBuild).toBe(true);
|
expect(modal.state.singleBuild).toBe(true);
|
||||||
clickProceed();
|
clickProceed();
|
||||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/cobra_mk_iii?code=A0p0tdFaldd3sdf4------34----2i--.AwRj4yqA.CwRgDMYExrezBig%3D.&bn=Imported%20Cobra%20Mk%20III');
|
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/cobra_mk_iii?code=A0p0tdFaldd3sdf4------34---2f2i.AwRj4yKA.CwRgDMYExrezBUg%3D.&bn=Imported%20Cobra%20Mk%20III');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -328,41 +324,4 @@ describe('Import Modal', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Imports SLEF data', () => {
|
|
||||||
beforeEach(reset);
|
|
||||||
|
|
||||||
it('imports a single valid SLEF build', () => {
|
|
||||||
const importData = require('./fixtures/slef-single-build.json');
|
|
||||||
pasteText(JSON.stringify(importData));
|
|
||||||
|
|
||||||
expect(modal.state.importValid).toBeTruthy();
|
|
||||||
expect(modal.state.errorMsg).toEqual(null);
|
|
||||||
expect(modal.state.singleBuild).toBe(true);
|
|
||||||
clickProceed();
|
|
||||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
|
||||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/krait_mkii?code=A2pptkFflidussf52l1o1o2g2g020g040405051Ofr45C9C91oP3.Iw18eQ%3D%3D.AwRgzKIkA%3D%3D%3D.&bn=Imported%20pancake%20hammer');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('imports multiple SLEF builds', () => {
|
|
||||||
const importData = require('./fixtures/slef-multiple-builds.json');
|
|
||||||
const expectedBuilds = require('./fixtures/slef-multiple-expected-builds.json');
|
|
||||||
pasteText(JSON.stringify(importData));
|
|
||||||
|
|
||||||
expect(modal.state.importValid).toBeTruthy();
|
|
||||||
expect(modal.state.errorMsg).toEqual(null);
|
|
||||||
expect(modal.state.singleBuild).toBe(false);
|
|
||||||
clickProceed();
|
|
||||||
expect(modal.state.processed).toBeTruthy();
|
|
||||||
clickImport();
|
|
||||||
|
|
||||||
const builds = Persist.getBuilds();
|
|
||||||
|
|
||||||
for (const shipModel in builds) {
|
|
||||||
for (const buildName in builds[shipModel]) {
|
|
||||||
expect(builds[shipModel][buildName])
|
|
||||||
.toEqual(expectedBuilds[shipModel][buildName]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
11
devServer.js
11
devServer.js
@@ -3,13 +3,24 @@ var WebpackDevServer = require("webpack-dev-server");
|
|||||||
var config = require('./webpack.config.dev');
|
var config = require('./webpack.config.dev');
|
||||||
|
|
||||||
new WebpackDevServer(webpack(config), {
|
new WebpackDevServer(webpack(config), {
|
||||||
|
publicPath: config.output.publicPath,
|
||||||
hot: true,
|
hot: true,
|
||||||
|
disableHostCheck: true,
|
||||||
headers: { "Access-Control-Allow-Origin": "*" },
|
headers: { "Access-Control-Allow-Origin": "*" },
|
||||||
historyApiFallback: {
|
historyApiFallback: {
|
||||||
rewrites: [
|
rewrites: [
|
||||||
// For some reason connect-history-api-fallback does not allow '.' in the URL for history fallback...
|
// For some reason connect-history-api-fallback does not allow '.' in the URL for history fallback...
|
||||||
{ from: /\/outfit\//, to: '/index.html' }
|
{ from: /\/outfit\//, to: '/index.html' }
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
stats: {
|
||||||
|
assets: true,
|
||||||
|
colors: true,
|
||||||
|
version: false,
|
||||||
|
hash: false,
|
||||||
|
timings: true,
|
||||||
|
chunks: false,
|
||||||
|
chunkModules: false
|
||||||
}
|
}
|
||||||
}).listen(3300, "0.0.0.0", function (err, result) {
|
}).listen(3300, "0.0.0.0", function (err, result) {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
|||||||
56
docker-compose.yml
Normal file
56
docker-compose.yml
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
version: '2.2'
|
||||||
|
|
||||||
|
services:
|
||||||
|
coriolis_prod:
|
||||||
|
image: edcd/coriolis:master
|
||||||
|
build:
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
branch: master
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||||
|
networks:
|
||||||
|
- web
|
||||||
|
labels:
|
||||||
|
- "traefik.docker.network=web"
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.basic.frontend.rule=Host:coriolis.io,coriolis.edcd.io"
|
||||||
|
- "traefik.basic.port=80"
|
||||||
|
- "traefik.basic.protocol=http"
|
||||||
|
|
||||||
|
coriolis_dev:
|
||||||
|
image: edcd/coriolis:develop
|
||||||
|
build:
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
args:
|
||||||
|
branch: develop
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||||
|
networks:
|
||||||
|
- web
|
||||||
|
labels:
|
||||||
|
- "traefik.docker.network=web"
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.basic.frontend.rule=Host:beta.coriolis.io,beta.coriolis.edcd.io"
|
||||||
|
- "traefik.basic.port=80"
|
||||||
|
- "traefik.basic.protocol=http"
|
||||||
|
|
||||||
|
coriolis_dw2:
|
||||||
|
image: edcd/coriolis:dw2
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- ./nginx.conf:/etc/nginx/nginx.conf
|
||||||
|
networks:
|
||||||
|
- web
|
||||||
|
labels:
|
||||||
|
- "traefik.docker.network=web"
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.basic.frontend.rule=Host:dw2.coriolis.io"
|
||||||
|
- "traefik.basic.port=80"
|
||||||
|
- "traefik.basic.protocol=http"
|
||||||
|
|
||||||
|
networks:
|
||||||
|
web:
|
||||||
|
external: true
|
||||||
96
nginx.conf
Normal file
96
nginx.conf
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
worker_processes 1;
|
||||||
|
user nobody nobody;
|
||||||
|
error_log /tmp/error.log;
|
||||||
|
pid /tmp/nginx.pid;
|
||||||
|
|
||||||
|
events {
|
||||||
|
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
http {
|
||||||
|
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
sendfile on;
|
||||||
|
client_body_temp_path /tmp/client_body;
|
||||||
|
fastcgi_temp_path /tmp/fastcgi_temp;
|
||||||
|
proxy_temp_path /tmp/proxy_temp;
|
||||||
|
scgi_temp_path /tmp/scgi_temp;
|
||||||
|
uwsgi_temp_path /tmp/uwsgi_temp;
|
||||||
|
access_log /tmp/access.log;
|
||||||
|
error_log /tmp/error.log;
|
||||||
|
|
||||||
|
# https://nginx.org/en/docs/http/ngx_http_gzip_module.html
|
||||||
|
# Enable gzip compression.
|
||||||
|
# Default: off
|
||||||
|
gzip off;
|
||||||
|
|
||||||
|
# Compression level (1-9).
|
||||||
|
# 5 is a perfect compromise between size and CPU usage, offering about
|
||||||
|
# 75% reduction for most ASCII files (almost identical to level 9).
|
||||||
|
# Default: 1
|
||||||
|
gzip_comp_level 5;
|
||||||
|
|
||||||
|
# Don't compress anything that's already small and unlikely to shrink much
|
||||||
|
# if at all (the default is 20 bytes, which is bad as that usually leads to
|
||||||
|
# larger files after gzipping).
|
||||||
|
# Default: 20
|
||||||
|
gzip_min_length 256;
|
||||||
|
|
||||||
|
# Compress data even for clients that are connecting to us via proxies,
|
||||||
|
# identified by the "Via" header (required for CloudFront).
|
||||||
|
# Default: off
|
||||||
|
gzip_proxied any;
|
||||||
|
|
||||||
|
# Tell proxies to cache both the gzipped and regular version of a resource
|
||||||
|
# whenever the client's Accept-Encoding capabilities header varies;
|
||||||
|
# Avoids the issue where a non-gzip capable client (which is extremely rare
|
||||||
|
# today) would display gibberish if their proxy gave them the gzipped version.
|
||||||
|
# Default: off
|
||||||
|
gzip_vary on;
|
||||||
|
|
||||||
|
# Compress all output labeled with one of the following MIME-types.
|
||||||
|
# text/html is always compressed by gzip module.
|
||||||
|
# Default: text/html
|
||||||
|
gzip_types *;
|
||||||
|
brotli on;
|
||||||
|
# brotli_static on;
|
||||||
|
brotli_types *;
|
||||||
|
# This should be turned on if you are going to have pre-compressed copies (.gz) of
|
||||||
|
# static files available. If not it should be left off as it will cause extra I/O
|
||||||
|
# for the check. It is best if you enable this in a location{} block for
|
||||||
|
# a specific directory, or on an individual server{} level.
|
||||||
|
# gzip_static on;
|
||||||
|
keepalive_timeout 3000;
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
listen [::]:80;
|
||||||
|
index index.html;
|
||||||
|
server_name localhost;
|
||||||
|
root /usr/share/nginx/html;
|
||||||
|
autoindex on;
|
||||||
|
|
||||||
|
location ~* \.(?:manifest|appcache|html?|xml|json|css|js|map|jpg|jpeg|gif|png|ico|svg|eot|ttf|woff|woff2)$ {
|
||||||
|
add_header Access-Control-Allow-Origin *;
|
||||||
|
add_header Access-Control-Allow-Credentials true;
|
||||||
|
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
|
||||||
|
add_header Access-Control-Allow-Headers 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
|
||||||
|
access_log off;
|
||||||
|
}
|
||||||
|
location /service-worker.js {
|
||||||
|
expires -1;
|
||||||
|
add_header Access-Control-Allow-Origin *;
|
||||||
|
add_header Access-Control-Allow-Credentials true;
|
||||||
|
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
|
||||||
|
add_header Access-Control-Allow-Headers 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
|
||||||
|
access_log off;
|
||||||
|
}
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ /index.html =404;
|
||||||
|
}
|
||||||
|
location /iframe.html {
|
||||||
|
try_files $uri $uri/ /iframe.html =404;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31037
package-lock.json
generated
31037
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
95
package.json
95
package.json
@@ -1,19 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "coriolis_shipyard",
|
"name": "coriolis_shipyard",
|
||||||
"version": "3.0.1",
|
"version": "3.0.0",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/EDCD/coriolis"
|
"url": "https://github.com/EDCD/coriolis"
|
||||||
},
|
},
|
||||||
"homepage": "https://coriolis.io",
|
"homepage": "https://coriolis.io",
|
||||||
"bugs": "https://github.com/EDCD/coriolis/issues",
|
"bugs": "https://github.com/EDCD/coriolis/issues",
|
||||||
"contributors": [
|
|
||||||
{ "name": "cmdrmcdonald" },
|
|
||||||
{ "name": "willb321" },
|
|
||||||
{ "name": "felixlinker" }
|
|
||||||
],
|
|
||||||
"private": true,
|
"private": true,
|
||||||
"engine": "node >= 10.13.0",
|
"engine": "node >= 4.8.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"extract-translations": "grep -hroE \"(translate\\('[^']+'\\))|(tip.bind\\(null, '[^']+')\" src/* | grep -oE \"'[^']+'\" | grep -oE \"[^']+\" | sort -u -f",
|
"extract-translations": "grep -hroE \"(translate\\('[^']+'\\))|(tip.bind\\(null, '[^']+')\" src/* | grep -oE \"'[^']+'\" | grep -oE \"[^']+\" | sort -u -f",
|
||||||
@@ -23,8 +18,7 @@
|
|||||||
"test": "jest",
|
"test": "jest",
|
||||||
"prod-serve": "nginx -p $(pwd) -c nginx.conf",
|
"prod-serve": "nginx -p $(pwd) -c nginx.conf",
|
||||||
"prod-stop": "kill -QUIT $(cat nginx.pid)",
|
"prod-stop": "kill -QUIT $(cat nginx.pid)",
|
||||||
"buildfresh": "rimraf node_modules && rm package-lock.json && npm install && npm run build > >(tee -a stdout.log) 2> >(tee -a stderr.log >&2)",
|
"build": "npm run clean && cross-env NODE_ENV=production webpack -p --config webpack.config.prod.js",
|
||||||
"build": "npm run clean && cross-env NODE_ENV=production webpack --config webpack.config.prod.js",
|
|
||||||
"rsync": "rsync -ae \"ssh -i $CORIOLIS_PEM\" --delete build/ $CORIOLIS_USER@$CORIOLIS_HOST:~/wwws",
|
"rsync": "rsync -ae \"ssh -i $CORIOLIS_PEM\" --delete build/ $CORIOLIS_USER@$CORIOLIS_HOST:~/wwws",
|
||||||
"deploy": "npm run lint && npm test && npm run build && npm run rsync"
|
"deploy": "npm run lint && npm test && npm run build && npm run rsync"
|
||||||
},
|
},
|
||||||
@@ -61,7 +55,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.20.12",
|
"@babel/core": "^7.0.0",
|
||||||
"@babel/plugin-proposal-class-properties": "^7.0.0",
|
"@babel/plugin-proposal-class-properties": "^7.0.0",
|
||||||
"@babel/plugin-proposal-decorators": "^7.0.0",
|
"@babel/plugin-proposal-decorators": "^7.0.0",
|
||||||
"@babel/plugin-proposal-do-expressions": "^7.0.0",
|
"@babel/plugin-proposal-do-expressions": "^7.0.0",
|
||||||
@@ -80,12 +74,15 @@
|
|||||||
"@babel/plugin-syntax-import-meta": "^7.0.0",
|
"@babel/plugin-syntax-import-meta": "^7.0.0",
|
||||||
"@babel/preset-env": "^7.0.0",
|
"@babel/preset-env": "^7.0.0",
|
||||||
"@babel/preset-react": "^7.0.0",
|
"@babel/preset-react": "^7.0.0",
|
||||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
"appcache-webpack-plugin": "^1.4.0",
|
||||||
|
"babel-core": "^7.0.0-bridge.0",
|
||||||
|
"babel-eslint": "^10.0.1",
|
||||||
|
"babel-jest": "^23.6.0",
|
||||||
"babel-loader": "^8.0.0",
|
"babel-loader": "^8.0.0",
|
||||||
"copy-webpack-plugin": "^10.2.4",
|
"copy-webpack-plugin": "^4.5.2",
|
||||||
"create-react-class": "^15.6.3",
|
"create-react-class": "^15.6.3",
|
||||||
"cross-env": "^5.2.0",
|
"cross-env": "^5.2.0",
|
||||||
"css-loader": "^6.7.3",
|
"css-loader": "^1.0.0",
|
||||||
"d3-selection": "^1.3.2",
|
"d3-selection": "^1.3.2",
|
||||||
"esdoc": "^1.1.0",
|
"esdoc": "^1.1.0",
|
||||||
"esdoc-custom-theme": "^1.4.2",
|
"esdoc-custom-theme": "^1.4.2",
|
||||||
@@ -96,66 +93,56 @@
|
|||||||
"esdoc-standard-plugin": "^1.0.0",
|
"esdoc-standard-plugin": "^1.0.0",
|
||||||
"eslint": "^5.6.0",
|
"eslint": "^5.6.0",
|
||||||
"eslint-plugin-react": "^7.11.1",
|
"eslint-plugin-react": "^7.11.1",
|
||||||
"expose-loader": "^3.1.0",
|
"expose-loader": "^0.7.5",
|
||||||
"express": "^4.18.2",
|
"express": "^4.16.3",
|
||||||
"html-webpack-plugin": "^5.5.0",
|
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||||
"jsen": "^0.6.6",
|
"file-loader": "^2.0.0",
|
||||||
|
"html-webpack-plugin": "^3.0.7",
|
||||||
|
"jest-cli": "^23.6.0",
|
||||||
|
"jsen": "^0.6.4",
|
||||||
"json-loader": "^0.5.4",
|
"json-loader": "^0.5.4",
|
||||||
"less": "^3.8.1",
|
"less": "^3.8.1",
|
||||||
"less-loader": "^11.1.0",
|
"less-loader": "^4.1.0",
|
||||||
"mini-css-extract-plugin": "^2.7.2",
|
"react-addons-perf": "^15.4.2",
|
||||||
"react-container-dimensions": "^1.4.1",
|
"react-container-dimensions": "^1.4.1",
|
||||||
"react-testutils-additions": "^15.0.0",
|
"react-testutils-additions": "^16.0.0",
|
||||||
"react-transition-group": "^2.5.0",
|
"react-transition-group": "^2.5.0",
|
||||||
"rimraf": "^4.1.2",
|
"rimraf": "^2.6.1",
|
||||||
"rollup": "^3.17.2",
|
"rollup": "^0.66.2",
|
||||||
"style-loader": "^3.3.1",
|
"rollup-plugin-node-resolve": "^3.4.0",
|
||||||
"uglify-js": "^3.17.4",
|
"style-loader": "^0.23.0",
|
||||||
"webpack": "^5.75.0",
|
"uglify-js": "^3.4.9",
|
||||||
"webpack-cli": "^5.0.1",
|
"url-loader": "^1.1.1",
|
||||||
"webpack-dev-server": "^4.11.1",
|
"webpack": "^4.20.2",
|
||||||
"webpack-merge": "^5.8.0",
|
"webpack-bugsnag-plugins": "^1.2.2",
|
||||||
"webpack-notifier": "^1.15.0",
|
"webpack-cli": "^3.1.1",
|
||||||
"workbox-cacheable-response": "^6.5.4",
|
"webpack-dev-server": "^3.1.9",
|
||||||
"workbox-expiration": "^6.5.4",
|
"webpack-notifier": "^1.6.0",
|
||||||
"workbox-precaching": "^6.5.4",
|
"workbox-webpack-plugin": "^3.6.1"
|
||||||
"workbox-routing": "^6.5.4",
|
|
||||||
"workbox-strategies": "^6.5.4",
|
|
||||||
"workbox-webpack-plugin": "^6.5.4"
|
|
||||||
},
|
},
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"assert": "^1.5.0",
|
"@babel/polyfill": "^7.0.0",
|
||||||
"auto-bind": "^5.0.1",
|
"auto-bind": "^2.1.1",
|
||||||
"base64url": "^3.0.1",
|
|
||||||
"browserify-zlib-next": "^1.0.1",
|
"browserify-zlib-next": "^1.0.1",
|
||||||
"buffer": "^5.7.0",
|
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"constants-browserify": "^1.0.0",
|
|
||||||
"core-js": "^3.28.0",
|
|
||||||
"coriolis-data": "../coriolis-data",
|
"coriolis-data": "../coriolis-data",
|
||||||
"crypto-browserify": "^3.12.0",
|
|
||||||
"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",
|
||||||
"https-browserify": "^1.0.0",
|
|
||||||
"lodash": "^4.17.11",
|
"lodash": "^4.17.11",
|
||||||
"lz-string": "^1.4.4",
|
"lz-string": "^1.4.4",
|
||||||
"os-browserify": "^0.3.0",
|
"pako": "^1.0.6",
|
||||||
"pako": "^2.1.0",
|
|
||||||
"path-browserify": "^1.0.1",
|
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"react": "^15.6.2",
|
"react": "^15.5.4",
|
||||||
"react-dom": "^15.6.2",
|
"react-dom": "^15.5.4",
|
||||||
|
"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": "^4.0.3",
|
"react-number-editor": "^4.0.3",
|
||||||
"recharts": "^1.2.0",
|
"recharts": "^1.2.0",
|
||||||
"register-service-worker": "^1.7.2",
|
"register-service-worker": "^1.5.2",
|
||||||
"stream-browserify": "^3.0.0",
|
"superagent": "^3.8.3"
|
||||||
"stream-http": "^3.2.0",
|
|
||||||
"superagent": "^3.8.3",
|
|
||||||
"url": "^0.11.0",
|
|
||||||
"vm-browserify": "^1.1.2"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import nodeResolve from "@rollup/plugin-node-resolve";
|
import nodeResolve from "rollup-plugin-node-resolve";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
entry: "d3-funcs.js",
|
entry: "d3-funcs.js",
|
||||||
|
|||||||
@@ -214,7 +214,7 @@ Options -MultiViews
|
|||||||
# </Files>
|
# </Files>
|
||||||
|
|
||||||
AddType application/x-web-app-manifest+json webapp
|
AddType application/x-web-app-manifest+json webapp
|
||||||
# AddType text/cache-manifest appcache manifest
|
AddType text/cache-manifest appcache manifest
|
||||||
|
|
||||||
# Media files
|
# Media files
|
||||||
AddType audio/mp4 f4a f4b m4a
|
AddType audio/mp4 f4a f4b m4a
|
||||||
|
|||||||
@@ -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,8 +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');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,18 +58,17 @@ 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 = {
|
||||||
noTouch: !('ontouchstart' in window || navigator.msMaxTouchPoints || navigator.maxTouchPoints),
|
noTouch: !('ontouchstart' in window || navigator.msMaxTouchPoints || navigator.maxTouchPoints),
|
||||||
page: null,
|
page: null,
|
||||||
// Announcements must have an expiry date in format "YYYY-MM-DDTHH:MM:SSZ"
|
announcements: [],
|
||||||
announcements: [{expiry: "2025-04-10T00:00:00Z", text: "Corsair added"}],
|
|
||||||
language: getLanguage(Persist.getLangCode()),
|
language: getLanguage(Persist.getLangCode()),
|
||||||
route: {},
|
route: {},
|
||||||
sizeRatio: Persist.getSizeRatio()
|
sizeRatio: Persist.getSizeRatio()
|
||||||
};
|
};
|
||||||
|
this._getAnnouncements();
|
||||||
Router('', (r) => this._setPage(ShipyardPage, r));
|
Router('', (r) => this._setPage(ShipyardPage, r));
|
||||||
Router('/import?', (r) => this._importBuild(r));
|
Router('/import?', (r) => this._importBuild(r));
|
||||||
Router('/import/:data', (r) => this._importBuild(r));
|
Router('/import/:data', (r) => this._importBuild(r));
|
||||||
@@ -94,44 +89,22 @@ 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.from(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);
|
|
||||||
let ship, importString;
|
|
||||||
if (json) {
|
|
||||||
if (json.length && json[0].data) { // SLEF
|
|
||||||
if (json.length > 1) { // Multiple builds, open modal
|
|
||||||
importString = data;
|
|
||||||
} else { // Single build, import directly
|
|
||||||
ship = JournalUtils.shipFromLoadoutJSON(json[0].data);
|
|
||||||
}
|
|
||||||
} else { // not SLEF
|
|
||||||
if (json.modules) {
|
|
||||||
ship = CompanionApiUtils.shipFromJson(json);
|
|
||||||
} else if (json.Modules) {
|
|
||||||
ship = JournalUtils.shipFromLoadoutJSON(json);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (ship) {
|
|
||||||
r.params.ship = ship.id;
|
|
||||||
r.params.code = ship.toString();
|
|
||||||
this._setPage(OutfittingPage, r);
|
this._setPage(OutfittingPage, r);
|
||||||
} else if (importString) {
|
|
||||||
this._setPage(ShipyardPage, r);
|
|
||||||
this._showModal(<ModalImport importString={data}/>);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const fullUrl = window.location.href;
|
this._onError('Failed to import ship', r.path, 0, 0, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (fullUrl.length >= 2083) {
|
async _getAnnouncements() {
|
||||||
err = 'URL Length = ' + fullUrl.length;
|
try {
|
||||||
this._onError('Failed to import ship - Potential URL Length issue', r.path, 0, 0, err);
|
const announces = await request.get('https://orbis.zone/api/announcement')
|
||||||
}
|
.query({ showInCoriolis: true });
|
||||||
else {
|
this.setState({ announcements: announces.body });
|
||||||
this._onError('Failed to import ship - Unknown Reason', r.path, 0, 0, err);
|
} catch (err) {
|
||||||
}
|
console.error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,6 +131,13 @@ export default class Coriolis extends React.Component {
|
|||||||
*/
|
*/
|
||||||
_onError(msg, scriptUrl, line, col, errObj) {
|
_onError(msg, scriptUrl, line, col, errObj) {
|
||||||
console && console.error && console.error(arguments); // eslint-disable-line no-console
|
console && console.error && console.error(arguments); // eslint-disable-line no-console
|
||||||
|
if (errObj) {
|
||||||
|
if (errObj instanceof Error) {
|
||||||
|
bugsnagClient.notify(errObj); // eslint-disable-line
|
||||||
|
} else if (errObj instanceof String) {
|
||||||
|
bugsnagClient.notify(msg, errObj); // eslint-disable-line
|
||||||
|
}
|
||||||
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
error: <ErrorDetails error={{ message: msg, details: { scriptUrl, line, col, error: JSON.stringify(errObj) } }}/>,
|
error: <ErrorDetails error={{ message: msg, details: { scriptUrl, line, col, error: JSON.stringify(errObj) } }}/>,
|
||||||
page: null,
|
page: null,
|
||||||
@@ -401,18 +381,18 @@ export default class Coriolis extends React.Component {
|
|||||||
*/
|
*/
|
||||||
render() {
|
render() {
|
||||||
let currentMenu = this.state.currentMenu;
|
let currentMenu = this.state.currentMenu;
|
||||||
|
|
||||||
return <div style={{ minHeight: '100%' }} onClick={this._closeMenu}
|
return <div style={{ minHeight: '100%' }} onClick={this._closeMenu}
|
||||||
className={this.state.noTouch ? 'no-touch' : null}>
|
className={this.state.noTouch ? 'no-touch' : null}>
|
||||||
<Header announcements={this.state.announcements} appCacheUpdate={this.state.appCacheUpdate}
|
<Header announcements={this.state.announcements} appCacheUpdate={this.state.appCacheUpdate}
|
||||||
currentMenu={currentMenu}/>
|
currentMenu={currentMenu}/>
|
||||||
<div className="announcement-container">{this.state.announcements.map(a => <Announcement
|
<div className="announcement-container">{this.state.announcements.map(a => <Announcement
|
||||||
text={a.text}/>)}</div>
|
text={a.message}/>)}</div>
|
||||||
{this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) :
|
{this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) :
|
||||||
<NotFoundPage/>}
|
<NotFoundPage/>}
|
||||||
{this.state.modal}
|
{this.state.modal}
|
||||||
{this.state.tooltip}
|
{this.state.tooltip}
|
||||||
<footer>
|
<footer>
|
||||||
|
|
||||||
<div className="right cap">
|
<div className="right cap">
|
||||||
<a href="https://github.com/EDCD/coriolis" target="_blank" rel="noopener noreferrer"
|
<a href="https://github.com/EDCD/coriolis" target="_blank" rel="noopener noreferrer"
|
||||||
title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>
|
title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ Router.go = function(path, state) {
|
|||||||
gaTrack(path);
|
gaTrack(path);
|
||||||
let ctx = new Context(path, state);
|
let ctx = new Context(path, state);
|
||||||
Router.dispatch(ctx);
|
Router.dispatch(ctx);
|
||||||
|
|
||||||
if (!ctx.unhandled) {
|
if (!ctx.unhandled) {
|
||||||
if (isStandAlone()) {
|
if (isStandAlone()) {
|
||||||
Persist.setState(ctx);
|
Persist.setState(ctx);
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import autoBind from 'auto-bind';
|
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,129 +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 { CoriolisLogo, 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',
|
|
||||||
'mlc': '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',
|
|
||||||
'advmc': 'projectiles',
|
|
||||||
'axmc': 'experimental',
|
|
||||||
'axmce': 'experimental',
|
|
||||||
'ntp': 'experimental',
|
|
||||||
'fc': 'projectiles',
|
|
||||||
'rfl': 'experimental',
|
|
||||||
'pa': 'projectiles',
|
|
||||||
'rg': 'projectiles',
|
|
||||||
'mr': 'ordnance',
|
|
||||||
'amr': 'ordnance',
|
|
||||||
'axmr': 'experimental',
|
|
||||||
'axmre': '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',
|
|
||||||
|
|
||||||
// Assists
|
|
||||||
'dc': 'flight assists',
|
|
||||||
'sua': 'flight assists',
|
|
||||||
// Stabilizers
|
|
||||||
'ews': 'weapon stabilizers',
|
|
||||||
};
|
|
||||||
// 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', 'mlc'],
|
|
||||||
'passenger cabins': ['pce', 'pci', 'pcm', 'pcq'],
|
|
||||||
'rf': ['rf'],
|
|
||||||
'shields': ['sg', 'bsg', 'psg', 'scb'],
|
|
||||||
'structural reinforcement': ['hr', 'mrp'],
|
|
||||||
'flight assists': ['dc', 'sua'],
|
|
||||||
|
|
||||||
// Hardpoints
|
|
||||||
'lasers': ['pl', 'ul', 'bl'],
|
|
||||||
'projectiles': ['mc', 'advmc', 'c', 'fc', 'pa', 'rg'],
|
|
||||||
'ordnance': ['mr', 'amr', 'tp', 'nl'],
|
|
||||||
// Utilities
|
|
||||||
'sb': ['sb'],
|
|
||||||
'hs': ['hs'],
|
|
||||||
'csl': ['csl'],
|
|
||||||
'defence': ['ch', 'po', 'ec'],
|
|
||||||
'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners
|
|
||||||
// Experimental
|
|
||||||
'experimental': ['axmc', 'axmce', 'axmr', 'axmre', 'ntp','rfl', 'tbrfl', 'tbsc', 'tbem', 'xs', 'sfn', 'rcpl', 'dtl', 'rsl', 'mahr',],
|
|
||||||
'weapon stabilizers': ['ews'],
|
|
||||||
// Guardian
|
|
||||||
'guardian': ['gpp', 'gpd', 'gpc', 'ggc', 'gsrp', 'gfsb', 'ghrp', 'gmrp', 'gsc'],
|
|
||||||
|
|
||||||
'mining': ['ml', 'scl', 'pwa', 'sdm', 'abl'],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -131,15 +22,11 @@ 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,
|
||||||
ship: PropTypes.object.isRequired,
|
|
||||||
warning: PropTypes.func,
|
warning: PropTypes.func,
|
||||||
firstSlotId: PropTypes.string,
|
|
||||||
lastSlotId: PropTypes.string,
|
|
||||||
activeSlotId: PropTypes.string,
|
|
||||||
slotDiv: PropTypes.object
|
slotDiv: PropTypes.object
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -150,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.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -163,207 +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, onSelect, modules, ship } = 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(
|
||||||
ship,
|
groupBy(modules, (info) => info.meta.group),
|
||||||
translate,
|
(infos) => groupBy(infos, (info) => info.meta.type),
|
||||||
m,
|
|
||||||
warning,
|
|
||||||
(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) {
|
);
|
||||||
// If this is a missing module/weapon, skip it
|
|
||||||
if (m.grp == "mh" || m.grp == "mm"){
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
list.push(<div ref={(elem) => this.groupElem = elem} key={category}
|
|
||||||
className={'select-category upp'}>{translate(category)}</div>);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (category == "mh" || category == "mm"){
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
return { list, currentGroup, fuzzy, trackingFocus: false };
|
||||||
// Show category header as well as group header
|
|
||||||
if (!categoryHeader) {
|
|
||||||
if (category == "mh" || category == "mm"){
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
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]));
|
|
||||||
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';
|
|
||||||
}
|
|
||||||
let special = '';
|
|
||||||
if (typeof(i.special) !== 'undefined') {
|
|
||||||
special = `(${translate(i.special)})`;
|
|
||||||
}
|
|
||||||
const fuzz = { grp, m: i, name: `${i.class}${i.rating}${mount ? ' ' + mount : ''} ${translate(grp)} ${translate(special)}` };
|
|
||||||
fuzzy.push(fuzz);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let trackingFocus = false;
|
|
||||||
return { list, currentGroup, fuzzy, trackingFocus };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return Is expiremental capacity reached
|
|
||||||
* @return {boolean} Is experimental capacity reached
|
|
||||||
*/
|
|
||||||
_experimentalCapacityReached() {
|
|
||||||
const ship = this.props.ship;
|
|
||||||
const ews = ship.internal.filter(o => o.m && o.m.grp === 'ews');
|
|
||||||
let expCap;
|
|
||||||
|
|
||||||
if(ews.length < 1){
|
|
||||||
expCap = 4;
|
|
||||||
} else{
|
|
||||||
expCap = ews[0].m.class == 3 ? 5 : 6;
|
|
||||||
}
|
|
||||||
|
|
||||||
return expCap <= this.props.ship.hardpoints.filter(o => o.m && o.m.experimental).length;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate React Components for Module Group
|
* Generate React Components for Module Group
|
||||||
* @param {Ship} ship Ship the selection is for
|
|
||||||
* @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 {function} onSelect Select/Mount callback
|
|
||||||
* @param {string} grp Group name
|
|
||||||
* @param {Array} modules Available modules
|
* @param {Array} modules Available modules
|
||||||
* @return {React.Component} Available Module Group contents
|
* @return {React.Component} Available Module Group contents
|
||||||
*/
|
*/
|
||||||
_buildGroup(ship, translate, mountedModule, warningFunc, onSelect, grp, modules) {
|
_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');
|
||||||
// If m.grp is mh or mm, or m.symbol contains 'Missing' skip it
|
if (meta.experimental && !mountedModule.readMeta('experimental')) {
|
||||||
if (m.grp == 'mh' || m.grp == 'mm' || (typeof(m.symbol) !== 'undefined' && m.symbol.includes("Missing"))) {
|
disabled =
|
||||||
// If this is a missing module, skip it
|
4 <=
|
||||||
continue;
|
ship.getHardpoints().filter((m) => m.readMeta('experimental'))
|
||||||
|
.length;
|
||||||
}
|
}
|
||||||
let mount = null;
|
|
||||||
let disabled = false;
|
|
||||||
prevName = m.name;
|
|
||||||
if (ModuleUtils.isShieldGenerator(m.grp)) {
|
|
||||||
// Shield generators care about maximum hull mass
|
|
||||||
disabled = ship.hullMass > m.maxmass;
|
|
||||||
// If the mounted module is experimental as well, we can replace it so
|
|
||||||
// the maximum does not apply
|
|
||||||
} else if (m.experimental && (!mountedModule || !mountedModule.experimental)) {
|
|
||||||
disabled = this._experimentalCapacityReached();
|
|
||||||
} else if (m.grp === 'mlc' && (!mountedModule || mountedModule.grp !== 'mlc')) {
|
|
||||||
disabled = 1 <= ship.internal.filter(o => o.m && o.m.grp === 'mlc').length;
|
|
||||||
}
|
|
||||||
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),
|
||||||
@@ -371,70 +152,69 @@ 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('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.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;
|
||||||
}
|
}
|
||||||
const mountedModule = this.props.m;
|
|
||||||
return (
|
return (
|
||||||
<FuzzySearch
|
<FuzzySearch
|
||||||
list={this.state.fuzzy}
|
list={this.state.fuzzy}
|
||||||
@@ -446,20 +226,11 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
|||||||
onSelect={e => this.props.onSelect.bind(null, e.m)()}
|
onSelect={e => this.props.onSelect.bind(null, e.m)()}
|
||||||
resultsTemplate={(props, state, styles, clickHandler) => {
|
resultsTemplate={(props, state, styles, clickHandler) => {
|
||||||
return state.results.map((val, i) => {
|
return state.results.map((val, i) => {
|
||||||
let disabled;
|
|
||||||
|
|
||||||
if(val.m.experimental && (!mountedModule || !mountedModule.experimental)) {
|
|
||||||
disabled = this._experimentalCapacityReached();
|
|
||||||
} else{
|
|
||||||
disabled = false;
|
|
||||||
}
|
|
||||||
const handler = disabled ? null : () => clickHandler(i);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={i}
|
key={i}
|
||||||
className={cn('lc', {disabled})}
|
className={'lc'}
|
||||||
onClick={handler}
|
onClick={() => clickHandler(i)}
|
||||||
>
|
>
|
||||||
{val.name}
|
{val.name}
|
||||||
</div>
|
</div>
|
||||||
@@ -504,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
|
||||||
@@ -549,69 +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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Sort multi limpet controllers by name
|
|
||||||
if (a.grp === 'mlc') {
|
|
||||||
if (a.name[0] <= b.name[0]) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (a.name[0] > b.name[0]) {
|
|
||||||
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._inaraShoppingList.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(-1 * 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(-1 * 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>
|
||||||
@@ -328,17 +182,66 @@ export default class CostSection extends TranslatedComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Open up a window for inara with a shopping list of our retrofit components
|
* Open up a window for EDDB with a shopping list of our retrofit components
|
||||||
*/
|
*/
|
||||||
_inaraShoppingList() {
|
_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://inara.cz/inapi/corisearch.php?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._inaraShoppingList} 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 { opponent, 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 = opponent.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,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,152 +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.getEps() * m.getSustainedFactor())}{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.getHps() * m.getSustainedFactor())})</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('wep_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.getInfo() ? <div className='l'>{translate(m.getInfo())}</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>
|
</ul>
|
||||||
<div className='select-group cap'>{translate('rg')}</div>
|
<div className='select-group cap'>{translate('rg')}</div>
|
||||||
<ul>
|
<ul>
|
||||||
<li className='lc' tabIndex='0' onClick={_fill.bind(this, 'rg', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['rg-F'] = smRef}>{translate('rg')}</li>
|
<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>
|
</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>;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { Ships } from 'coriolis-data/dist';
|
|||||||
import Persist from '../stores/Persist';
|
import Persist from '../stores/Persist';
|
||||||
import { toDetailedExport } from '../shipyard/Serializer';
|
import { toDetailedExport } from '../shipyard/Serializer';
|
||||||
import Ship from '../shipyard/Ship';
|
import Ship from '../shipyard/Ship';
|
||||||
|
import ModalBatchOrbis from './ModalBatchOrbis';
|
||||||
import ModalDeleteAll from './ModalDeleteAll';
|
import ModalDeleteAll from './ModalDeleteAll';
|
||||||
import ModalExport from './ModalExport';
|
import ModalExport from './ModalExport';
|
||||||
import ModalHelp from './ModalHelp';
|
import ModalHelp from './ModalHelp';
|
||||||
@@ -55,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
|
||||||
@@ -240,6 +240,43 @@ export default class Header extends TranslatedComponent {
|
|||||||
/>);
|
/>);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads all ship-builds to orbis
|
||||||
|
* @param {e} e Event
|
||||||
|
*/
|
||||||
|
_uploadAllBuildsToOrbis(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const data = Persist.getBuilds();
|
||||||
|
let postObject = [];
|
||||||
|
for (const ship in data) {
|
||||||
|
for (const code in data[ship]) {
|
||||||
|
const shipModel = ship;
|
||||||
|
if (!shipModel) {
|
||||||
|
throw 'No such ship found: "' + ship + '"';
|
||||||
|
}
|
||||||
|
const shipTemplate = Ships[shipModel];
|
||||||
|
const shipPostObject = {};
|
||||||
|
let shipInstance = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots);
|
||||||
|
shipInstance.buildWith(null);
|
||||||
|
shipInstance.buildFrom(data[ship][code]);
|
||||||
|
shipPostObject.coriolisId = shipInstance.id;
|
||||||
|
shipPostObject.coriolisShip = shipInstance;
|
||||||
|
|
||||||
|
shipPostObject.coriolisShip.url = window.location.origin + outfitURL(shipModel, data[ship][code], code);
|
||||||
|
shipPostObject.title = code || shipInstance.id;
|
||||||
|
shipPostObject.description = code || shipInstance.id;
|
||||||
|
shipPostObject.ShipName = shipInstance.id;
|
||||||
|
shipPostObject.Ship = shipInstance.id;
|
||||||
|
postObject.push(shipPostObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(postObject);
|
||||||
|
|
||||||
|
this.context.showModal(<ModalBatchOrbis
|
||||||
|
ships={postObject}
|
||||||
|
/>);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show export modal with detailed export
|
* Show export modal with detailed export
|
||||||
* @param {SyntheticEvent} e Event
|
* @param {SyntheticEvent} e Event
|
||||||
@@ -388,18 +425,14 @@ export default class Header extends TranslatedComponent {
|
|||||||
if (this.props.announcements) {
|
if (this.props.announcements) {
|
||||||
announcements = [];
|
announcements = [];
|
||||||
for (let announce of this.props.announcements) {
|
for (let announce of this.props.announcements) {
|
||||||
// Announcement has expired, skip it
|
announcements.push(<Announcement text={announce.message} />);
|
||||||
if (Date.now() > Date.parse(announce.expiry)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Add announcements which have not expired to the menu
|
|
||||||
announcements.push(<Announcement text={announce.text} />);
|
|
||||||
announcements.push(<hr/>);
|
announcements.push(<hr/>);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div className='menu-list' onClick={ (e) => e.stopPropagation() } style={{ whiteSpace: 'nowrap' }}>
|
<div className='menu-list' onClick={ (e) => e.stopPropagation() } style={{ whiteSpace: 'nowrap' }}>
|
||||||
{announcements}
|
{announcements}
|
||||||
|
<hr />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -462,6 +495,7 @@ export default class Header extends TranslatedComponent {
|
|||||||
{translate('builds')} & {translate('comparisons')}
|
{translate('builds')} & {translate('comparisons')}
|
||||||
<li><Link href="#" className='block' onClick={this._showBackup.bind(this)}>{translate('backup')}</Link></li>
|
<li><Link href="#" className='block' onClick={this._showBackup.bind(this)}>{translate('backup')}</Link></li>
|
||||||
<li><Link href="#" className='block' onClick={this._showDetailedExport.bind(this)}>{translate('detailed export')}</Link></li>
|
<li><Link href="#" className='block' onClick={this._showDetailedExport.bind(this)}>{translate('detailed export')}</Link></li>
|
||||||
|
<li><Link href="#" className='block' onClick={this._uploadAllBuildsToOrbis.bind(this)}>{translate('upload all builds to orbis')}</Link></li>
|
||||||
<li><Link href="#" className='block' onClick={this._showImport.bind(this)}>{translate('import')}</Link></li>
|
<li><Link href="#" className='block' onClick={this._showImport.bind(this)}>{translate('import')}</Link></li>
|
||||||
<li><Link href="#" className='block' onClick={this._showDeleteAll.bind(this)}>{translate('delete all')}</Link></li>
|
<li><Link href="#" className='block' onClick={this._showDeleteAll.bind(this)}>{translate('delete all')}</Link></li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -573,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}
|
||||||
@@ -604,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.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.getInfo() ? <div className='l'>{translate(m.getInfo())}</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>;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export default class JumpRange extends TranslatedComponent {
|
|||||||
const fuel = this.state.fuelLevel * ship.fuelCapacity;
|
const fuel = this.state.fuelLevel * ship.fuelCapacity;
|
||||||
|
|
||||||
// Obtain the jump range
|
// Obtain the jump range
|
||||||
return Calc.jumpRange(ship.unladenMass + cargo, fsd, fuel, ship);
|
return Calc.jumpRange(ship.unladenMass + fuel + cargo, fsd, fuel, ship);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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>;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
92
src/app/components/ModalBatchOrbis.jsx
Normal file
92
src/app/components/ModalBatchOrbis.jsx
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import request from 'superagent';
|
||||||
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
|
import { orbisUpload } from '../utils/ShortenUrl';
|
||||||
|
import Persist from '../stores/Persist';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permalink modal
|
||||||
|
*/
|
||||||
|
export default class ModalBatchOrbis extends TranslatedComponent {
|
||||||
|
static propTypes = {
|
||||||
|
ships: PropTypes.any.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param {Object} props React Component properties
|
||||||
|
*/
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
orbisCreds: Persist.getOrbisCreds(),
|
||||||
|
resp: ''
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send ship to Orbis.zone
|
||||||
|
* @param {SyntheticEvent} e React Event
|
||||||
|
* @return {Promise} Promise sending post request to orbis
|
||||||
|
*/
|
||||||
|
sendToOrbis(e) {
|
||||||
|
let agent;
|
||||||
|
try {
|
||||||
|
agent = request.agent(); // apparently this crashes somehow
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
if (!agent) {
|
||||||
|
agent = request;
|
||||||
|
}
|
||||||
|
const API_ORBIS = 'https://orbis.zone/api/builds/add/batch';
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
try {
|
||||||
|
agent
|
||||||
|
.post(API_ORBIS)
|
||||||
|
.withCredentials()
|
||||||
|
.redirects(0)
|
||||||
|
.set('Content-Type', 'application/json')
|
||||||
|
.send(this.props.ships)
|
||||||
|
.end((err, response) => {
|
||||||
|
console.log(response);
|
||||||
|
if (err) {
|
||||||
|
console.error(err);
|
||||||
|
this.setState({ resp: response.text });
|
||||||
|
reject('Bad Request');
|
||||||
|
} else {
|
||||||
|
this.setState({ resp: 'All builds uploaded. Check https://orbis.zone' });
|
||||||
|
resolve('All builds uploaded. Check https://orbis.zone');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
reject(e.message ? e.message : e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the modal
|
||||||
|
* @return {React.Component} Modal Content
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
let translate = this.context.language.translate;
|
||||||
|
this.sendToOrbis = this.sendToOrbis.bind(this);
|
||||||
|
|
||||||
|
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
|
||||||
|
<h2>{translate('permalink')}</h2>
|
||||||
|
<br/>
|
||||||
|
<a className='button' href="https://orbis.zone/api/auth">Log in / signup to Orbis</a>
|
||||||
|
<br/><br/>
|
||||||
|
<h3 >{translate('success')}</h3>
|
||||||
|
<input value={this.state.resp} readOnly size={25} onFocus={ (e) => e.target.select() }/>
|
||||||
|
<br/><br/>
|
||||||
|
<p>Orbis.zone is currently in a trial period, and may be wiped at any time as development progresses. Some elements are also still placeholders.</p>
|
||||||
|
<button className={'l cb dismiss cap'} disabled={!!this.state.failed} onClick={this.sendToOrbis}>{translate('PHASE_UPLOAD_ORBIS')}</button>
|
||||||
|
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ import * as ModuleUtils from '../shipyard/ModuleUtils';
|
|||||||
import { fromDetailedBuild } from '../shipyard/Serializer';
|
import { fromDetailedBuild } from '../shipyard/Serializer';
|
||||||
import { Download } from './SvgIcons';
|
import { Download } from './SvgIcons';
|
||||||
import { outfitURL } from '../utils/UrlGenerators';
|
import { outfitURL } from '../utils/UrlGenerators';
|
||||||
import { shipFromJson, shipModelFromJson } from '../utils/CompanionApiUtils';
|
import * as CompanionApiUtils from '../utils/CompanionApiUtils';
|
||||||
import { shipFromLoadoutJSON } from '../utils/JournalUtils';
|
|
||||||
|
|
||||||
const zlib = require('pako');
|
const zlib = require('pako');
|
||||||
|
|
||||||
@@ -86,10 +85,7 @@ function detailedJsonToBuild(detailedBuild) {
|
|||||||
* Import Modal
|
* Import Modal
|
||||||
*/
|
*/
|
||||||
export default class ModalImport extends TranslatedComponent {
|
export default class ModalImport extends TranslatedComponent {
|
||||||
|
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
importString: PropTypes.string, // Optional: Default data for import modal
|
|
||||||
builds: PropTypes.object, // Optional: Import object
|
builds: PropTypes.object, // Optional: Import object
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -108,7 +104,7 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
shipDiscount: null,
|
shipDiscount: null,
|
||||||
moduleDiscount: null,
|
moduleDiscount: null,
|
||||||
errorMsg: null,
|
errorMsg: null,
|
||||||
importString: props.importString || null,
|
importString: null,
|
||||||
importValid: false,
|
importValid: false,
|
||||||
insurance: null
|
insurance: null
|
||||||
};
|
};
|
||||||
@@ -132,7 +128,7 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
if (data && data.Ship && data.Modules) {
|
if (data && data.Ship && data.Modules) {
|
||||||
const deflated = zlib.deflate(JSON.stringify(data), { to: 'string' });
|
const deflated = zlib.deflate(JSON.stringify(data), { to: 'string' });
|
||||||
let compressed = btoa(deflated);
|
let compressed = btoa(deflated);
|
||||||
this.setState({loadoutEvent: compressed});
|
this.setState({ loadoutEvent: compressed });
|
||||||
} else {
|
} else {
|
||||||
throw 'Loadout event must contain Ship and Modules';
|
throw 'Loadout event must contain Ship and Modules';
|
||||||
}
|
}
|
||||||
@@ -216,8 +212,8 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
* @throws {string} if parse/import fails
|
* @throws {string} if parse/import fails
|
||||||
*/
|
*/
|
||||||
_importCompanionApiBuild(build) {
|
_importCompanionApiBuild(build) {
|
||||||
const shipModel = shipModelFromJson(build);
|
const shipModel = CompanionApiUtils.shipModelFromJson(build);
|
||||||
const ship = shipFromJson(build);
|
const ship = CompanionApiUtils.shipFromJson(build);
|
||||||
|
|
||||||
let builds = {};
|
let builds = {};
|
||||||
builds[shipModel] = {};
|
builds[shipModel] = {};
|
||||||
@@ -323,30 +319,6 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
this.setState({ builds, singleBuild: true });
|
this.setState({ builds, singleBuild: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Import SLEF formatted builds. Sets state to a map of the builds on success
|
|
||||||
* and flags if there was only a single build.
|
|
||||||
*
|
|
||||||
* @param {string} importData - Array of the list of builds.
|
|
||||||
* @throws {string} If parse / import fails
|
|
||||||
*/
|
|
||||||
_importSlefBuilds(importData) {
|
|
||||||
const builds = importData.reduce((memo, { data }) => {
|
|
||||||
const shipModel = shipModelFromJson(data);
|
|
||||||
const ship = shipFromLoadoutJSON(data);
|
|
||||||
const shipTemplate = Ships[shipModel];
|
|
||||||
const shipName = data.ShipName || shipTemplate.properties.name;
|
|
||||||
|
|
||||||
const key = `Imported ${shipName}`;
|
|
||||||
memo[shipModel] = {};
|
|
||||||
memo[shipModel][key] = ship.toString();
|
|
||||||
|
|
||||||
return memo;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
this.setState({ builds, singleBuild: Object.keys(builds).length === 1 });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate the import string / text box contents
|
* Validate the import string / text box contents
|
||||||
* @param {SyntheticEvent} event Event
|
* @param {SyntheticEvent} event Event
|
||||||
@@ -381,10 +353,8 @@ export default class ModalImport extends TranslatedComponent {
|
|||||||
throw 'Must be an object or array!';
|
throw 'Must be an object or array!';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (importData?.[0]?.header?.appName) { // has SLEF envelope?
|
if (importData.modules != null && importData.modules.Armour != null) { // Only the companion API has this information
|
||||||
this._importSlefBuilds(importData);
|
this._importCompanionApiBuild(importData); // Single sihp definition
|
||||||
} else if (importData.modules != null && importData.modules.Armour != null) { // Only the companion API has this information
|
|
||||||
this._importCompanionApiBuild(importData); // Single ship definition
|
|
||||||
} else if (importData.ship != null && importData.ship.modules != null && importData.ship.modules.Armour != null) { // Only the companion API has this information
|
} else if (importData.ship != null && importData.ship.modules != null && importData.ship.modules.Armour != null) { // Only the companion API has this information
|
||||||
this._importCompanionApiBuild(importData.ship); // Complete API dump
|
this._importCompanionApiBuild(importData.ship); // Complete API dump
|
||||||
} else if (importData instanceof Array) { // Must be detailed export json
|
} else if (importData instanceof Array) { // Must be detailed export json
|
||||||
|
|||||||
140
src/app/components/ModalOrbis.jsx
Normal file
140
src/app/components/ModalOrbis.jsx
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
|
import { orbisUpload } from '../utils/ShortenUrl';
|
||||||
|
import Persist from '../stores/Persist';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Permalink modal
|
||||||
|
*/
|
||||||
|
export default class ModalOrbis extends TranslatedComponent {
|
||||||
|
static propTypes = {
|
||||||
|
ship: PropTypes.any.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
* @param {Object} props React Component properties
|
||||||
|
*/
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
orbisCreds: Persist.getOrbisCreds(),
|
||||||
|
orbisUrl: '...',
|
||||||
|
ship: this.props.ship,
|
||||||
|
authenticatedStatus: 'Checking...'
|
||||||
|
};
|
||||||
|
this.orbisCategory = this.orbisCategory.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send ship to Orbis.zone
|
||||||
|
* @param {SyntheticEvent} e React Event
|
||||||
|
*/
|
||||||
|
sendToOrbis(e) {
|
||||||
|
const target = e.target;
|
||||||
|
target.disabled = true;
|
||||||
|
this.setState({ orbisUrl: 'Sending...' }, () => {
|
||||||
|
orbisUpload(this.props.ship, this.state.orbisCreds)
|
||||||
|
.then(orbisUrl => {
|
||||||
|
target.disabled = false;
|
||||||
|
this.setState({ orbisUrl });
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
target.disabled = false;
|
||||||
|
this.setState({ orbisUrl: 'Error - ' + err });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Orbis.zone auth status
|
||||||
|
* @returns {Object} auth status
|
||||||
|
*/
|
||||||
|
getOrbisAuthStatus() {
|
||||||
|
return fetch('https://orbis.zone/api/checkauth', {
|
||||||
|
credentials: 'include',
|
||||||
|
mode: 'cors'
|
||||||
|
})
|
||||||
|
.then(data => data.json())
|
||||||
|
.then(res => {
|
||||||
|
this.setState({ authenticatedStatus: res.status || res.error });
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
this.setState({ authenticatedStatus: err.message });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for changing cmdr name
|
||||||
|
* @param {SyntheticEvent} e React Event
|
||||||
|
*/
|
||||||
|
orbisPasswordHandler(e) {
|
||||||
|
let password = e.target.value;
|
||||||
|
this.setState({ orbisCreds: { email: this.state.orbisCreds.email, password } }, () => {
|
||||||
|
Persist.setOrbisCreds(this.state.orbisCreds);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for changing cmdr name
|
||||||
|
* @param {SyntheticEvent} e React Event
|
||||||
|
*/
|
||||||
|
orbisUsername(e) {
|
||||||
|
let orbisUsername = e.target.value;
|
||||||
|
this.setState({ orbisCreds: { email: orbisUsername, password: this.state.orbisCreds.password } }, () => {
|
||||||
|
Persist.setOrbisCreds(this.state.orbisCreds);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for changing category
|
||||||
|
* @param {SyntheticEvent} e React Event
|
||||||
|
*/
|
||||||
|
orbisCategory(e) {
|
||||||
|
let ship = this.state.ship;
|
||||||
|
let cat = e.target.value;
|
||||||
|
ship.category = cat;
|
||||||
|
this.setState({ ship });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the modal
|
||||||
|
* @return {React.Component} Modal Content
|
||||||
|
*/
|
||||||
|
render() {
|
||||||
|
let translate = this.context.language.translate;
|
||||||
|
this.orbisPasswordHandler = this.orbisPasswordHandler.bind(this);
|
||||||
|
this.orbisUsername = this.orbisUsername.bind(this);
|
||||||
|
this.sendToOrbis = this.sendToOrbis.bind(this);
|
||||||
|
this.getOrbisAuthStatus();
|
||||||
|
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
|
||||||
|
<h2>{translate('upload to orbis')}</h2>
|
||||||
|
<br/>
|
||||||
|
<label>Orbis auth status: </label>
|
||||||
|
<input value={this.state.authenticatedStatus} readOnly size={25} onFocus={ (e) => e.target.select() }/>
|
||||||
|
<br/><br/>
|
||||||
|
<a className='button' href="https://orbis.zone/api/auth">Log in / signup to Orbis</a>
|
||||||
|
<br/><br/>
|
||||||
|
<h3>Category</h3>
|
||||||
|
<select onChange={this.orbisCategory}>
|
||||||
|
<option value="">No Category</option>
|
||||||
|
<option>Combat</option>
|
||||||
|
<option>Mining</option>
|
||||||
|
<option>Trading</option>
|
||||||
|
<option>Exploration</option>
|
||||||
|
<option>Passenger Liner</option>
|
||||||
|
<option>PvP</option>
|
||||||
|
</select>
|
||||||
|
<br/><br/>
|
||||||
|
<h3 >{translate('Orbis link')}</h3>
|
||||||
|
<input value={this.state.orbisUrl} readOnly size={25} onFocus={ (e) => e.target.select() }/>
|
||||||
|
<br/><br/>
|
||||||
|
<p>Orbis.zone is currently in a trial period, and may be wiped at any time as development progresses. Some elements are also still placeholders.</p>
|
||||||
|
<button className={'l cb dismiss cap'} disabled={!!this.state.failed} onClick={this.sendToOrbis}>{translate('PHASE_UPLOAD_ORBIS')}</button>
|
||||||
|
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
};
|
};
|
||||||
@@ -34,18 +33,6 @@ export default class ModalPermalink extends TranslatedComponent {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Copy the shortened URL to the clipboard
|
|
||||||
* @param {Event} e Click event
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
copyShortLink() {
|
|
||||||
let copyText = document.getElementById("shortenedUrl");
|
|
||||||
// Copy the text inside the shortendUrl input to the clipboard
|
|
||||||
copyText.select();
|
|
||||||
document.execCommand("copy");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Render the modal
|
* Render the modal
|
||||||
* @return {React.Component} Modal Content
|
* @return {React.Component} Modal Content
|
||||||
@@ -54,17 +41,15 @@ export default class ModalPermalink extends TranslatedComponent {
|
|||||||
let translate = this.context.language.translate;
|
let translate = this.context.language.translate;
|
||||||
|
|
||||||
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
|
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
|
||||||
<h3>{translate('permalink')}</h3>
|
<h2>{translate('permalink')}</h2>
|
||||||
<br/>
|
<br/>
|
||||||
<h3>{translate('URL')}</h3>
|
<h3>{translate('URL')}</h3>
|
||||||
<input value={this.props.url} size={40} readOnly onFocus={ (e) => e.target.select() }/>
|
<input value={this.props.url} size={40} readOnly onFocus={ (e) => e.target.select() }/>
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
<h3 >{translate('shortened')}</h3>
|
<h3 >{translate('shortened')}</h3>
|
||||||
<input id={'shortenedUrl'} value={this.state.shortenedUrl} readOnly size={25} onFocus={ (e) => e.target.select() }/><button className={'cb dismiss cap'} onClick={this.copyShortLink}>{translate('copy to clipboard')}</button>
|
<input value={this.state.shortenedUrl} readOnly size={25} onFocus={ (e) => e.target.select() }/>
|
||||||
<br/><br/>
|
<br/><br/>
|
||||||
<hr />
|
<p>s.orbis.zone is the new URL shortener domain, old eddp.co urls are considered end of life and could go down at any moment. Sorry for any inconvenience.</p>
|
||||||
<p>s.orbis.zone is the URL shortener domain. These links should persist indefinitely going forward. If for some reason there is a problem with the link shortening process, please report it in the EDCD Discord Server.</p>
|
|
||||||
<hr />
|
|
||||||
<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,17 +3,13 @@ import PropTypes from 'prop-types';
|
|||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import request from 'superagent';
|
import request from 'superagent';
|
||||||
import Persist from '../stores/Persist';
|
import Persist from '../stores/Persist';
|
||||||
const zlib = require('zlib');
|
|
||||||
const base64url = require('base64url');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
buildName: PropTypes.string
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,6 +55,7 @@ export default class ModalShoppingList extends TranslatedComponent {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (module.m.blueprint.special) {
|
if (module.m.blueprint.special) {
|
||||||
|
console.log(module.m.blueprint.special);
|
||||||
blueprints.push({ uuid: module.m.blueprint.special.uuid, number: 1 });
|
blueprints.push({ uuid: module.m.blueprint.special.uuid, number: 1 });
|
||||||
}
|
}
|
||||||
for (const g in module.m.blueprint.grades) {
|
for (const g in module.m.blueprint.grades) {
|
||||||
@@ -91,10 +88,8 @@ export default class ModalShoppingList extends TranslatedComponent {
|
|||||||
request
|
request
|
||||||
.get('http://localhost:44405/commanders')
|
.get('http://localhost:44405/commanders')
|
||||||
.end((err, res) => {
|
.end((err, res) => {
|
||||||
this.display = 'block';
|
|
||||||
if (err) {
|
if (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
this.display = 'none';
|
|
||||||
return this.setState({ failed: true });
|
return this.setState({ failed: true });
|
||||||
}
|
}
|
||||||
const cmdrs = JSON.parse(res.text);
|
const cmdrs = JSON.parse(res.text);
|
||||||
@@ -150,113 +145,6 @@ export default class ModalShoppingList extends TranslatedComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fix issues with the item name for bulkheads when sending to EDOMH
|
|
||||||
* @param {*} ship Ship object
|
|
||||||
* @param {*} item Item name
|
|
||||||
* @returns updated item name
|
|
||||||
*/
|
|
||||||
fixArmourItemNameForEDOMH(ship, item) {
|
|
||||||
// The module blueprint fdname contains "Armour_" it's a bulkhead and we need to pre-populate the item field with the correct name from the ship object
|
|
||||||
switch (ship.bulkheads.m.name){
|
|
||||||
case "Lightweight Alloy":
|
|
||||||
item = ship.id + "_Armour_Grade1";
|
|
||||||
break;
|
|
||||||
case "Reinforced Alloy":
|
|
||||||
item = ship.id + "_Armour_Grade2";
|
|
||||||
break;
|
|
||||||
case "Military Grade Composite":
|
|
||||||
item = ship.id + "_Armour_Grade3";
|
|
||||||
break;
|
|
||||||
case "Mirrored Surface Composite":
|
|
||||||
item = ship.id + "_Armour_Mirrored";
|
|
||||||
break;
|
|
||||||
case "Reactive Surface Composite":
|
|
||||||
item = ship.id + "_Armour_Reactive";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send all blueprints to EDOMH. This is a modified copy of registerBPs because this.state.blueprints was empty when I tried to modify sendToEDEng and I couldn't figure out why
|
|
||||||
* @param {Event} event React event
|
|
||||||
*/
|
|
||||||
sendToEDOMH(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
const ship = this.props.ship;
|
|
||||||
const buildName = this.props.buildName;
|
|
||||||
let blueprints = [];
|
|
||||||
|
|
||||||
//create the json
|
|
||||||
for (const module of ship.costList) {
|
|
||||||
if (module.type === 'SHIP') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (module.m && module.m.blueprint) {
|
|
||||||
if (!module.m.blueprint.grade || !module.m.blueprint.grades) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (module.m.blueprint.special) {
|
|
||||||
let item = "";
|
|
||||||
// If the module blueprint fdname contains "Armour_" it's a bulkhead and we need to pre-populate the item field with the correct name from the ship object
|
|
||||||
if (module.m.blueprint.fdname.includes("Armour_")) {
|
|
||||||
item = this.fixArmourItemNameForEDOMH(ship, item)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
item = module.m.symbol;
|
|
||||||
}
|
|
||||||
|
|
||||||
blueprints.push({
|
|
||||||
"item": item,
|
|
||||||
"blueprint": module.m.blueprint.special.edname
|
|
||||||
});
|
|
||||||
}
|
|
||||||
for (let g in module.m.blueprint.grades) {
|
|
||||||
if (!module.m.blueprint.grades.hasOwnProperty(g)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// We only want the grade that the module is currently at, not every grade up to that point
|
|
||||||
if (Number(g) !== module.m.blueprint.grade) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let item = "";
|
|
||||||
// If the module blueprint fdname contains "Armour_" it's a bulkhead and we need to pre-populate the item field with the correct name from the ship object
|
|
||||||
if (module.m.blueprint.fdname.includes("Armour_")) {
|
|
||||||
item = this.fixArmourItemNameForEDOMH(ship, item)
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
item = module.m.symbol;
|
|
||||||
}
|
|
||||||
blueprints.push({
|
|
||||||
"item": item,
|
|
||||||
"blueprint": module.m.blueprint.fdname,
|
|
||||||
"grade": module.m.blueprint.grade,
|
|
||||||
"highestGradePercentage":1.0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let shipName = buildName + " - " + ship.name;
|
|
||||||
|
|
||||||
//create JSON to encode
|
|
||||||
let baseJson = {
|
|
||||||
"version":1,
|
|
||||||
"name": shipName, // TO-DO: Import build name and put that here correctly
|
|
||||||
"items": blueprints
|
|
||||||
}
|
|
||||||
|
|
||||||
let JSONString = JSON.stringify(baseJson)
|
|
||||||
let deflated = zlib.deflateSync(JSONString)
|
|
||||||
|
|
||||||
//actually encode
|
|
||||||
let link = base64url.encode(deflated)
|
|
||||||
link = "edomh://coriolis/?" + link;
|
|
||||||
|
|
||||||
window.open(link, "_self")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert mats object to string
|
* Convert mats object to string
|
||||||
*/
|
*/
|
||||||
@@ -271,15 +159,14 @@ export default class ModalShoppingList extends TranslatedComponent {
|
|||||||
if (!module.m.blueprint.grade || !module.m.blueprint.grades) {
|
if (!module.m.blueprint.grade || !module.m.blueprint.grades) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (let g in module.m.blueprint.grades) {
|
for (const g in module.m.blueprint.grades) {
|
||||||
if (!module.m.blueprint.grades.hasOwnProperty(g)) {
|
if (!module.m.blueprint.grades.hasOwnProperty(g)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Ignore grades higher than the grade selected
|
if (g > module.m.blueprint.grade) {
|
||||||
if (Number(g) > module.m.blueprint.grade) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
for (let i in module.m.blueprint.grades[g].components) {
|
for (const i in module.m.blueprint.grades[g].components) {
|
||||||
if (!module.m.blueprint.grades[g].components.hasOwnProperty(i)) {
|
if (!module.m.blueprint.grades[g].components.hasOwnProperty(i)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -290,18 +177,6 @@ export default class ModalShoppingList extends TranslatedComponent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (module.m.blueprint.special) {
|
|
||||||
for (const j in module.m.blueprint.special.components) {
|
|
||||||
if (!module.m.blueprint.special.components.hasOwnProperty(j)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (mats[j]) {
|
|
||||||
mats[j] += module.m.blueprint.special.components[j];
|
|
||||||
} else {
|
|
||||||
mats[j] = module.m.blueprint.special.components[j];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let matsString = '';
|
let matsString = '';
|
||||||
@@ -353,48 +228,34 @@ export default class ModalShoppingList extends TranslatedComponent {
|
|||||||
const compatible = this.checkBrowserIsCompatible();
|
const compatible = this.checkBrowserIsCompatible();
|
||||||
this.cmdrChangeHandler = this.cmdrChangeHandler.bind(this);
|
this.cmdrChangeHandler = this.cmdrChangeHandler.bind(this);
|
||||||
this.sendToEDEng = this.sendToEDEng.bind(this);
|
this.sendToEDEng = this.sendToEDEng.bind(this);
|
||||||
this.sendToEDOMH = this.sendToEDOMH.bind(this);
|
|
||||||
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
|
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
|
||||||
<h3>{translate('PHRASE_SHOPPING_MATS')}</h3>
|
<h2>{translate('PHRASE_SHOPPING_MATS')}</h2>
|
||||||
|
<label>{translate('Grade 1 rolls ')}</label>
|
||||||
|
<input id={1} type={'number'} min={0} defaultValue={this.state.matsPerGrade[1]} onChange={this.changeHandler} />
|
||||||
|
<br/>
|
||||||
|
<label>{translate('Grade 2 rolls ')}</label>
|
||||||
|
<input id={2} type={'number'} min={0} defaultValue={this.state.matsPerGrade[2]} onChange={this.changeHandler} />
|
||||||
|
<br/>
|
||||||
|
<label>{translate('Grade 3 rolls ')}</label>
|
||||||
|
<input id={3} type={'number'} min={0} value={this.state.matsPerGrade[3]} onChange={this.changeHandler} />
|
||||||
|
<br/>
|
||||||
|
<label>{translate('Grade 4 rolls ')}</label>
|
||||||
|
<input id={4} type={'number'} min={0} value={this.state.matsPerGrade[4]} onChange={this.changeHandler} />
|
||||||
|
<br/>
|
||||||
|
<label>{translate('Grade 5 rolls ')}</label>
|
||||||
|
<input id={5} type={'number'} min={0} value={this.state.matsPerGrade[5]} onChange={this.changeHandler} />
|
||||||
<div>
|
<div>
|
||||||
<p>{translate('PHRASE_DIFFERENT_ROLLS')}</p>
|
|
||||||
<label>{translate('G1')}</label>
|
|
||||||
<input className={'groll'} id={1} type={'number'} min={0} defaultValue={this.state.matsPerGrade[1]} onChange={this.changeHandler} />
|
|
||||||
| <label>{translate('G2')}</label>
|
|
||||||
<input className={'groll'} id={2} type={'number'} min={0} defaultValue={this.state.matsPerGrade[2]} onChange={this.changeHandler} />
|
|
||||||
| <label>{translate('G3')}</label>
|
|
||||||
<input className={'groll'} id={3} type={'number'} min={0} value={this.state.matsPerGrade[3]} onChange={this.changeHandler} />
|
|
||||||
| <label>{translate('G4')}</label>
|
|
||||||
<input className={'groll'} id={4} type={'number'} min={0} value={this.state.matsPerGrade[4]} onChange={this.changeHandler} />
|
|
||||||
| <label>{translate('G5')}</label>
|
|
||||||
<input className={'groll'} id={5} type={'number'} min={0} value={this.state.matsPerGrade[5]} onChange={this.changeHandler} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<p>{translate('PHRASE_ALL_MODULES_ALL_ROLLS')}</p>
|
|
||||||
<textarea className='cb json' readOnly value={this.state.matsList} />
|
<textarea className='cb json' readOnly value={this.state.matsList} />
|
||||||
<p>{translate('PHRASE_FOR_FINER_CONTROL')}</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
<label hidden={!compatible} className={'l cap'}>{translate('CMDR Name')}</label>
|
||||||
<div id='edengineer' display={this.display} hidden={!!this.state.failed && !compatible}>
|
<br/>
|
||||||
<hr />
|
<select hidden={!compatible} className={'cmdr-select l cap'} onChange={this.cmdrChangeHandler} defaultValue={this.state.cmdrName}>
|
||||||
<h3>ED Engineer</h3>
|
|
||||||
<h4 hidden={compatible} id={'browserbad'} className={'l'}>{translate('PHRASE_FIREFOX_EDENGINEER')}</h4>
|
|
||||||
<h4 hidden={!this.state.failed} id={'failed'} className={'l'}>{translate('PHRASE_FAILED_TO_FIND_EDENGINEER')}</h4>
|
|
||||||
<label for='cmdr-select' hidden={!!this.state.failed || !compatible} className={'l cap'}>{translate('CMDR Name:')}</label>
|
|
||||||
<select id='cmdr-select' hidden={!!this.state.failed || !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'}>{translate('PHRASE_FAIL_EDENGINEER')}</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>
|
||||||
</div>
|
|
||||||
<div id='edomh'>
|
|
||||||
<hr />
|
|
||||||
<h3>ED Odyssey Materials Helper</h3>
|
|
||||||
<p>{translate('PHRASE_ENSURE_EDOMH')}</p>
|
|
||||||
<button className={'l cb dismiss cap'} onClick={this.sendToEDOMH}>{translate('Send to EDOMH')}</button>
|
|
||||||
</div>
|
|
||||||
<hr />
|
|
||||||
<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,131 +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 { isChangeValueBeneficial } from '../utils/BlueprintFunctions';
|
import { Module } from 'ed-forge';
|
||||||
import { Modifications } from 'coriolis-data/dist';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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;
|
||||||
let isOverwrite = Modifications.modifications[name].method === 'overwrite';
|
|
||||||
|
|
||||||
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>
|
||||||
isChangeValueBeneficial(name, modValue) ? 'secondary' : 'warning' :
|
|
||||||
''
|
|
||||||
}>
|
|
||||||
{formats.f2(modValue / 100) || 0}{isOverwrite ? '' : '%'}
|
|
||||||
</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,123 +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);
|
|
||||||
|
|
||||||
// console.log(props.m.blueprint)
|
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
|
||||||
@@ -165,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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -371,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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -408,106 +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();
|
||||||
let bprintSearchName;
|
break;
|
||||||
|
case specialMenuOpened:
|
||||||
|
renderComponents = this._renderSpecials();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Since the first case didn't apply, there is a blueprint applied so
|
||||||
|
// we render the modifications
|
||||||
|
let blueprintTt = blueprintTooltip(language, m, appliedBlueprint, appliedGrade);
|
||||||
|
|
||||||
// If the fdname is Weapon_Overcharged, we need to check if it's an MC
|
renderComponents.push(
|
||||||
if (m.blueprint && m.blueprint.fdname) {
|
<div style={{ cursor: 'pointer' }} key="blueprintsMenu"
|
||||||
// Set the bprintSearchName value to the fdname of the blueprint for this module
|
className="section-menu button-inline-menu"
|
||||||
bprintSearchName = m.blueprint.fdname;
|
onMouseOver={termtip.bind(null, blueprintTt)}
|
||||||
if (m.blueprint.fdname === 'Weapon_Overcharged') {
|
onMouseOut={tooltip.bind(null, null)}
|
||||||
// If the module is a MultiCannon, we need to fix the blueprint search name, else it will find the Laser Weapon_Overcharged Blueprint and not the MC Weapon_Overcharged Blueprint
|
onClick={this._toggleBlueprintsMenu}
|
||||||
if (m.symbol.match(/MultiCannon/i)) {
|
|
||||||
// console.log(Modifications.modules[m.grp].blueprints['MC_Overcharged']);
|
|
||||||
// console.log(m.blueprint.fdname);
|
|
||||||
bprintSearchName = 'MC_Overcharged';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// TODO: Fix this to actually find the correct blueprint.
|
|
||||||
if (!m.blueprint || !m.blueprint.name || !m.blueprint.fdname || !Modifications.modules[m.grp].blueprints || !Modifications.modules[m.grp].blueprints[bprintSearchName]) {
|
|
||||||
this.props.ship.clearModuleBlueprint(m);
|
|
||||||
this.props.ship.clearModuleSpecial(m);
|
|
||||||
}
|
|
||||||
if (m.blueprint && m.blueprint.name && Modifications.modules[m.grp].blueprints[bprintSearchName].grades[m.blueprint.grade]) {
|
|
||||||
blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
|
|
||||||
haveBlueprint = true;
|
|
||||||
// console.log(haveBlueprint);
|
|
||||||
blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], Modifications.modules[m.grp].blueprints[bprintSearchName].grades[m.blueprint.grade].engineers, m.grp);
|
|
||||||
blueprintCv = getPercent(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
let specialLabel;
|
|
||||||
let specialTt;
|
|
||||||
if (m.blueprint && m.blueprint.special) {
|
|
||||||
specialLabel = translate(m.blueprint.special.name);
|
|
||||||
specialTt = specialToolTip(translate, m.blueprint.grades[m.blueprint.grade], m.grp, m, m.blueprint.special.edname);
|
|
||||||
} else {
|
|
||||||
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('mroll') }: </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,151 +55,196 @@ 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 damage = ship.getMetrics(DAMAGE_METRICS);
|
||||||
|
const portions = {
|
||||||
|
Absolute: damage.types.abs,
|
||||||
|
Explosive: damage.types.expl,
|
||||||
|
Kinetic: damage.types.kin,
|
||||||
|
Thermic: damage.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,
|
||||||
|
};
|
||||||
|
|
||||||
|
let rows = [];
|
||||||
|
for (let weapon of ship.getHardpoints()) {
|
||||||
|
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) {
|
||||||
const baseSDpsTooltipDetails = getSDpsTooltip(translate, formats, weapon.sdps.base);
|
bpTitle += `, ${translate(exp)}`;
|
||||||
|
}
|
||||||
const effectivenessShieldsTooltipDetails = [];
|
rows.push({
|
||||||
effectivenessShieldsTooltipDetails.push(<div key='range'>{translate('range') + ' ' + formats.pct1(weapon.effectiveness.shields.range)}</div>);
|
slot: weapon.getSlot(),
|
||||||
effectivenessShieldsTooltipDetails.push(<div key='resistance'>{translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.shields.resistance)}</div>);
|
mount: weapon.mount,
|
||||||
effectivenessShieldsTooltipDetails.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(weapon.effectiveness.shields.sys)}</div>);
|
classRating: weapon.getClassRating(),
|
||||||
|
type: weapon.readMeta('type'),
|
||||||
const effectiveShieldsSDpsTooltipDetails = getSDpsTooltip(translate, formats, weapon.sdps.armour);
|
bpTitle: bp ? ` (${bpTitle})` : null,
|
||||||
|
sdps,
|
||||||
const effectivenessArmourTooltipDetails = [];
|
baseSdpsTooltip,
|
||||||
effectivenessArmourTooltipDetails.push(<div key='range'>{translate('range') + ' ' + formats.pct1(weapon.effectiveness.armour.range)}</div>);
|
shieldSdps: sdps * shieldEft,
|
||||||
effectivenessArmourTooltipDetails.push(<div key='resistance'>{translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.armour.resistance)}</div>);
|
shieldEft,
|
||||||
effectivenessArmourTooltipDetails.push(<div key='hardness'>{translate('hardness') + ' ' + formats.pct1(weapon.effectiveness.armour.hardness)}</div>);
|
shieldsSdpsTooltip,
|
||||||
|
shieldsEftTooltip,
|
||||||
const effectiveArmourSDpsTooltipDetails = getSDpsTooltip(translate, formats, weapon.sdps.armour);
|
armourSdps: sdps * armourEft,
|
||||||
|
armourEft,
|
||||||
rows.push(
|
armourSdpsTooltip,
|
||||||
<tr key={weapon.id}>
|
armourEftTooltip,
|
||||||
<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}
|
const { predicate, desc } = this.state;
|
||||||
{weapon.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : null}
|
rows = sortBy(rows, (row) => row[predicate]);
|
||||||
{weapon.classRating} {translate(weapon.name)}
|
if (desc) {
|
||||||
{weapon.engineering ? ' (' + weapon.engineering + ')' : null }
|
reverse(rows);
|
||||||
</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>{formats.f1(weapon.effectiveness.shields.dpe)}</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>
|
|
||||||
|
|
||||||
<td className='ri'><span>{formats.f1(weapon.effectiveness.armour.dpe)}</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(damage.sustained.dps * p)),
|
||||||
|
);
|
||||||
|
const sdpsPie = objToPie(
|
||||||
|
translate,
|
||||||
|
mapValues(portions, (p) => Math.round(damage.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) => damage.sustained.dps * oppShield.absolute.bySys *
|
||||||
|
damage.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) => damage.sustained.dps * damage.hardnessMultiplier *
|
||||||
|
damage.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 pd = ship.getPowerDistributor();
|
||||||
const timeToDepleteArmour = Calc.timeToDeplete(opponentArmour.total, totalArmourSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4));
|
const timeToDrain = damage.sustained.timeToDrain[ship.getDistributorSettings().Wep];
|
||||||
|
// const timeToDepleteShields = Calc.timeToDeplete(opponentShields.total, shieldsSdps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4));
|
||||||
|
// const timeToDepleteArmour = Calc.timeToDeplete(opponentArmour.total, armourSdps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span id='offence'>
|
<span id='offence'>
|
||||||
@@ -274,35 +252,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='3'>{translate('opponent\'s shields')}</th>
|
<th colSpan='2'>{translate('opponent\'s shields')}</th>
|
||||||
<th colSpan='3'>{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')}
|
||||||
|
onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'shieldSdps')}>sdps</th>
|
||||||
<th className='sortable'>{'dpe'}</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, 'esdpsh')}>{'sdps'}</th>
|
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_ARMOUR')}
|
||||||
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'eh')}>{'eft'}</th>
|
onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'armourSdps')}>sdps</th>
|
||||||
|
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_ARMOUR')}
|
||||||
<th className='sortable'>{'dpe'}</th>
|
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)}>
|
||||||
<td></td>
|
={formats.f1(damage.sustained.dps)}
|
||||||
<td></td>
|
</span>
|
||||||
<td className='ri'><span onMouseOver={termtip.bind(null, totalArmourSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>={formats.f1(totalArmourSDps)}</span></td>
|
</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(armourSdps)}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
<td></td>
|
<td></td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
@@ -311,22 +329,53 @@ 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/>
|
||||||
|
ToDo
|
||||||
|
{/* {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/>
|
||||||
|
ToDo
|
||||||
|
{/* {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('tab_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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ import React from 'react';
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import TranslatedComponent from './TranslatedComponent';
|
import TranslatedComponent from './TranslatedComponent';
|
||||||
import { Pip } from './SvgIcons';
|
import { Pip } from './SvgIcons';
|
||||||
import autoBind from 'auto-bind';
|
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 'auto-bind';
|
|||||||
*/
|
*/
|
||||||
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,53 +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>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this is a Guardian Shield Reinforcement Package or Guardian Hull Reinforcement Package, or Guardian Module Reinforcement Package, it cannot change priority
|
const slot = m.getSlot();
|
||||||
let priorityField;
|
powerRows.push(<tr key={slot} className={cn('highlight', { disabled: !m.isEnabled() })}>
|
||||||
if (m.symbol) {
|
<td className='ptr' style={{ width: '1em' }} onClick={flipEnabled}>{String(m.getClass()) + m.getRating()}</td>
|
||||||
if (m.symbol.match(/GuardianShield/i) || m.symbol.match(/GuardianHull/i) || m.symbol.match(/GuardianModule/i)) {
|
<td className='ptr le shorten cap' onClick={flipEnabled}>{translate(m.readMeta('type'))}</td>
|
||||||
priorityField = <td>1</td>;
|
{/* <td className='ptr' onClick={flipEnabled}><u>{translate(slot.type)}</u></td> */}
|
||||||
} else {
|
<td>
|
||||||
priorityField = <td>
|
<span className='flip ptr btn' onClick={this._prioCb(m, -1)}>►</span>
|
||||||
<span className='flip ptr btn' onClick={this._priority.bind(this, slot, -1)}>►</span>
|
{' ' + (m.getPowerPriority() + 1) + ' '}
|
||||||
{' ' + (slot.priority + 1) + ' '}
|
<span className='ptr btn' onClick={this._prioCb(m, 1)}>►</span>
|
||||||
<span className='ptr btn' onClick={this._priority.bind(this, slot, 1)}>►</span>
|
</td>
|
||||||
</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>
|
||||||
else {
|
</td>
|
||||||
priorityField = <td>
|
|
||||||
<span className='flip ptr btn' onClick={this._priority.bind(this, slot, -1)}>►</span>
|
|
||||||
{' ' + (slot.priority + 1) + ' '}
|
|
||||||
<span className='ptr btn' onClick={this._priority.bind(this, slot, 1)}>►</span>
|
|
||||||
</td>;
|
|
||||||
}
|
|
||||||
powerRows.push(<tr key={i} className={cn('highlight', { disabled: !slot.enabled })}>
|
|
||||||
<td className='ptr' style={{ width: '1em' }} onClick={toggleEnabled}>{m.class + m.rating}</td>
|
|
||||||
<td className='ptr le shorten cap' onClick={toggleEnabled}>{slotName(translate, slot)}</td>
|
|
||||||
<td className='ptr' onClick={toggleEnabled}><u>{translate(slot.type)}</u></td>
|
|
||||||
{priorityField}
|
|
||||||
<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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,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
|
||||||
*/
|
*/
|
||||||
@@ -207,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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ 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 autoBind from 'auto-bind';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ship picker
|
* Ship picker
|
||||||
@@ -26,13 +27,9 @@ export default class ShipPicker extends TranslatedComponent {
|
|||||||
* @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._toggleMenu = this._toggleMenu.bind(this);
|
|
||||||
this._closeMenu = this._closeMenu.bind(this);
|
|
||||||
|
|
||||||
this.state = { menuOpen: false };
|
this.state = { menuOpen: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,46 +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 canJump = ship.getSlotStatus(ship.standard[2]) == 3;
|
|
||||||
const sgMetrics = Calc.shieldMetrics(ship, pips.sys);
|
const sgMetrics = ship.get(SHIELD_METRICS);
|
||||||
const distBoost = canBoost ? Calc.calcBoost(ship) : 'No Boost';
|
const armourMetrics = ship.get(ARMOUR_METRICS);
|
||||||
//const shipBoost = ship.boostInterval(ship)
|
const damageMetrics = ship.get(DAMAGE_METRICS);
|
||||||
const restingHeat = Math.sqrt(((ship.standard[0].m.pgen * ship.standard[0].m.eff) / ship.heatCapacity) / 0.2);
|
const moduleProtectionMetrics = ship.get(MODULE_PROTECTION_METRICS);
|
||||||
const armourMetrics = Calc.armourMetrics(ship);
|
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 onMouseEnter={termtip.bind(null, 'TT_SUMMARY_BOOST_INTERVALS', { cap: 0 })}colSpan={2} className={ cn({ 'bg-warning-disabled': !canBoost }) }>{translate('boost int')}</th>
|
<th colSpan={5} className={ cn({ 'bg-warning-disabled': jumpRangeMetrics.jumpRange == 0 }) }>{translate('jump range')}</th>
|
||||||
<th colSpan={5} className={ cn({ 'bg-warning-disabled': !canJump }) }>{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>
|
||||||
@@ -88,16 +99,15 @@ 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_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 onMouseEnter={termtip.bind(null, 'TT_SUMMARY_BOOST_INTERVAL', { cap: 0 })} onMouseLeave={hide}>{translate('distro')}</th>
|
<th className="lft">{translate('max')}</th>
|
||||||
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_SHIP_BOOST_INTERVAL', { cap: 0 })} onMouseLeave={hide}>{translate('ship')}</th>
|
<th>{translate('unladen')}</th>
|
||||||
<th className={ cn({ 'lft': true, 'bg-warning-disabled': !canJump }) }>{translate('max')}</th>
|
<th>{translate('laden')}</th>
|
||||||
<th className={ cn({ 'bg-warning-disabled': !canJump }) }>{translate('unladen')}</th>
|
<th>{translate('total unladen')}</th>
|
||||||
<th className={ cn({ 'bg-warning-disabled': !canJump }) }>{translate('laden')}</th>
|
<th>{translate('total laden')}</th>
|
||||||
<th className={ cn({ 'bg-warning-disabled': !canJump }) }>{translate('total unladen')}</th>
|
|
||||||
<th className={ cn({ 'bg-warning-disabled': !canJump }) }>{translate('total laden')}</th>
|
|
||||||
<th className='lft'>{translate('hull')}</th>
|
<th className='lft'>{translate('hull')}</th>
|
||||||
<th>{translate('unladen')}</th>
|
<th>{translate('unladen')}</th>
|
||||||
<th>{translate('laden')}</th>
|
<th>{translate('laden')}</th>
|
||||||
@@ -105,31 +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>{distBoost !== 'No Boost' ? formats.time(distBoost) : 'No Boost'}</td>
|
>{canThrust ?
|
||||||
<td>{ship.boostInt && ship.boostInt !== 'undefined' ? formats.time(ship.boostInt) : 0 }</td>
|
<span>{int(speed)}{u['m/s']}</span> :
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_MAX_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{ canJump ? <span>{ f2(Calc.jumpRange(ship.unladenMass - ship.fuelCapacity + ship.standard[2].m.getMaxFuelPerJump(), ship.standard[2].m, ship.standard[2].m.getMaxFuelPerJump(), ship))}{u.LY}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
<span className='warning'>0<Warning/></span>
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{ canJump ? <span>{f2(Calc.jumpRange(ship.unladenMass, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
}</td>
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{ canJump ? <span>{f2(Calc.jumpRange(ship.unladenMass + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
<td onMouseEnter={termtip.bind(null, boostTooltip, { cap: 0 })}
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{ canJump ? <span>{f2(Calc.totalJumpRange(ship.unladenMass, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
onMouseLeave={hide}
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{ canJump ? <span>{f2(Calc.totalJumpRange(ship.unladenMass + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</span> : <span className='warning'>0 <Warning/></span> }</td>
|
>{canBoost ?
|
||||||
<td className={sgClassNames} onMouseEnter={termtip.bind(null, sgTooltip, { cap: 0 })} onMouseLeave={hide}>{int(ship.shield)}{u.MJ}</td>
|
<span>{int(shipBoost)}{u['m/s']}</span> :
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_INTEGRITY', { cap: 0 })} onMouseLeave={hide}>{int(ship.armour)}</td>
|
<span className='warning'>0<Warning/></span>
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_DPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalDps)}</td>
|
}</td>
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_EPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalEps)}</td>
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_MAX_SINGLE_JUMP', { cap: 0 })}
|
||||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_TTD', { cap: 0 })} onMouseLeave={hide}>{timeToDrain === Infinity ? '∞' : time(timeToDrain)}</td>
|
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>{formats.pct(restingHeat)}</td>
|
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_MASS', { cap: 0 })}
|
||||||
|
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>
|
||||||
@@ -158,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 && sgMetrics.summary > 0 ? sgMetrics.summary : 0)}{u.MJ}</td>
|
<td>{int(sgMetrics.shieldStrength || 0)}{u.MJ}</td>
|
||||||
<td>{int(ship && sgMetrics.summary > 0 ? sgMetrics.summary / sgMetrics.explosive.base : 0)}{u.MJ}</td>
|
<td>{int(sgMetrics.shieldStrength / sgMetrics.explosive.damageMultiplier || 0)}{u.MJ}</td>
|
||||||
<td>{int(ship && sgMetrics.summary ? sgMetrics.summary / sgMetrics.kinetic.base : 0)}{u.MJ}</td>
|
<td>{int(sgMetrics.shieldStrength / sgMetrics.kinetic.damageMultiplier || 0)}{u.MJ}</td>
|
||||||
<td>{int(ship && sgMetrics.summary ? sgMetrics.summary / sgMetrics.thermal.base : 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>
|
||||||
@@ -197,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(armourMetrics.total)}</td>
|
<td>{int(armourMetrics.armour)}</td>
|
||||||
<td>{int(armourMetrics.total / armourMetrics.explosive.total)}</td>
|
<td>{int(armourMetrics.armour / armourMetrics.explosive.damageMultiplier)}</td>
|
||||||
<td>{int(armourMetrics.total/ armourMetrics.kinetic.total)}</td>
|
<td>{int(armourMetrics.armour / armourMetrics.kinetic.damageMultiplier)}</td>
|
||||||
<td>{int(armourMetrics.total / armourMetrics.thermal.total)}</td>
|
<td>{int(armourMetrics.armour / armourMetrics.thermal.damageMultiplier)}</td>
|
||||||
<td>{int(armourMetrics.total/ armourMetrics.caustic.total)}</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>
|
||||||
|
|||||||
@@ -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 { REG_MILITARY_SLOT, REG_HARDPOINT_SLOT } 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.getSlot().match(REG_MILITARY_SLOT) ? '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 Boolean(m.getSlot().match(REG_HARDPOINT_SLOT)):
|
||||||
|
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,73 +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,
|
||||||
let missing = false;
|
onPropToggle,
|
||||||
|
} = this.props;
|
||||||
if (!selected) {
|
const { menuIndex } = this.state;
|
||||||
// If not selected then sure that modifications flag is unset
|
|
||||||
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);
|
|
||||||
if(typeof m.grp !== 'undefined' || m.grp !== null) {
|
|
||||||
if(m.grp == "mh" || m.grp == "mm") {
|
|
||||||
missing = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} 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()}
|
|
||||||
m={m}
|
|
||||||
ship={ship}
|
|
||||||
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
|
||||||
{
|
className={cn('slot', dropClass, { selected })}
|
||||||
// If missing module/hardpoint, set the div container to warning status.
|
onContextMenu={this._contextMenu}
|
||||||
}
|
onDragOver={dragOver} tabIndex="0"
|
||||||
<div className={ missing === true ? 'details-container warning' : 'details-container'}>
|
onClick={this._openMenu.bind(this, 0)}
|
||||||
<div className='sz'>{this._getMaxClassLabel(translate)}</div>
|
>
|
||||||
{slotDetails}
|
<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
|
||||||
@@ -207,7 +117,6 @@ export default class SlotSection extends TranslatedComponent {
|
|||||||
// 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
|
||||||
@@ -236,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()
|
||||||
@@ -282,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
|
||||||
*/
|
*/
|
||||||
@@ -298,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,168 +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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If this is a missing module, therefore has the 'info' field, set the warning value on the module to be true when loaded.
|
|
||||||
if (m.info) {
|
|
||||||
warning = () => true;
|
|
||||||
}
|
|
||||||
|
|
||||||
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}
|
|
||||||
m={m}
|
|
||||||
ship={ship}
|
|
||||||
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} {m.getInfo() ? translate(m.ukName) : 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('minmass')}: {formats.int(m.getMinMass())}{units.T}</div> : null }
|
|
||||||
{ m.getOptMass() ? <div className='l'>{translate('optmass')}: {formats.int(m.getOptMass())}{units.T}</div> : null }
|
|
||||||
{ m.getMaxMass() ? <div className='l'>{translate('maxmass')}: {formats.int(m.getMaxMass())}{units.T}</div> : null }
|
|
||||||
{ m.getOptMul() ? <div className='l'>{translate('optmul')}: {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 }
|
|
||||||
{ m.getInfo() ? <div className='l'>{translate(m.getInfo())}</div> : null }
|
|
||||||
{ m.getInfo() ? <div className='r'></div> : 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,46 +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.selectedRefId = null;
|
|
||||||
this.firstRefId = 'maxjump';
|
|
||||||
this.lastRefId = 'racer';
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,11 +46,7 @@ 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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,11 +55,7 @@ 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
|
||||||
*/
|
*/
|
||||||
_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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,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();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,113 +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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -243,23 +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;
|
||||||
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} ref={smRef => this.sectionRefArr['maxjump'] = smRef}>{translate('Maximize Jump Range')}</li>
|
<li className='lc' tabIndex="0" onClick={this._optimizeStandard}>{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} ref={smRef => this.sectionRefArr['multipurpose'] = smRef}>{translate('Multi-purpose')}</li>
|
<li className='lc' tabIndex="0" onClick={this._multiPurpose.bind(this, false, 0)}>{translate('Multi-purpose')}</li>
|
||||||
<li className='lc' tabIndex="0" onClick={this._multiPurpose.bind(this, true, 2)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['combat'] = smRef}>{translate('Combat')}</li>
|
<li className='lc' tabIndex="0" onClick={this._multiPurpose.bind(this, true, 2)}>{translate('Combat')}</li>
|
||||||
<li className='lc' tabIndex="0" onClick={this._optimizeCargo.bind(this, true)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['trader'] = smRef}>{translate('Trader')}</li>
|
<li className='lc' tabIndex="0" onClick={this._optimizeCargo.bind(this, true)}>{translate('Trader')}</li>
|
||||||
<li className='lc' tabIndex="0" onClick={this._optimizeExplorer.bind(this, false)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['explorer'] = smRef}>{translate('Explorer')}</li>
|
<li className='lc' tabIndex="0" onClick={this._optimizeExplorer.bind(this, false)}>{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._optimizeExplorer.bind(this, true)}>{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._optimizeMiner.bind(this, true)}>{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._optimizeRacer.bind(this)}>{translate('Racer')}</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -247,8 +247,7 @@ export class OrbisIcon extends SvgIcon {
|
|||||||
<path d="m155.34 679.12 173.25-190.21-15.626-13.721-170.9 190.4zm31.01 31.714 202.41-169.1-16.418-14.417-198.76 170.43z"/>
|
<path d="m155.34 679.12 173.25-190.21-15.626-13.721-170.9 190.4zm31.01 31.714 202.41-169.1-16.418-14.417-198.76 170.43z"/>
|
||||||
<path d="m702.66 178.87-173.25 190.21 15.625 13.721 170.9-190.4zm-31.01-31.714-202.41 169.1 16.418 14.417 198.76-170.43z" />
|
<path d="m702.66 178.87-173.25 190.21 15.625 13.721 170.9-190.4zm-31.01-31.714-202.41 169.1 16.418 14.417 198.76-170.43z" />
|
||||||
<rect transform="matrix(-.7071 -.7071 .7071 -.7071 429.34 1036.2)" x="387.09" y="420.77" width="84.379" height="16.859" />
|
<rect transform="matrix(-.7071 -.7071 .7071 -.7071 429.34 1036.2)" x="387.09" y="420.77" width="84.379" height="16.859" />
|
||||||
</g>
|
</g>);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ 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 CN from './cn';
|
||||||
import * as KO from './ko';
|
|
||||||
import * as d3 from 'd3';
|
import * as d3 from 'd3';
|
||||||
|
|
||||||
let fallbackTerms = EN.terms;
|
let fallbackTerms = EN.terms;
|
||||||
@@ -30,7 +29,6 @@ export function getLanguage(langCode) {
|
|||||||
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;
|
case 'cn': lang = CN; break;
|
||||||
case 'ko': lang = KO; break;
|
|
||||||
default:
|
default:
|
||||||
lang = EN;
|
lang = EN;
|
||||||
}
|
}
|
||||||
@@ -99,6 +97,5 @@ export const Languages = {
|
|||||||
ru: 'ру́сский',
|
ru: 'ру́сский',
|
||||||
pl: 'polski',
|
pl: 'polski',
|
||||||
pt: 'português',
|
pt: 'português',
|
||||||
cn: '中文',
|
cn: '中文'
|
||||||
ko: '한국어'
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -398,7 +398,6 @@
|
|||||||
"No modded components.": "没有改装的部件",
|
"No modded components.": "没有改装的部件",
|
||||||
"Sending...": "发送中...",
|
"Sending...": "发送中...",
|
||||||
"Send to EDEngineer": "发送至EDEngineer",
|
"Send to EDEngineer": "发送至EDEngineer",
|
||||||
"Send to EDOMH": "发送至EDOMH",
|
|
||||||
"PHASE_UPLOAD_ORBIS": "上传到orbis.zone(测试阶段)",
|
"PHASE_UPLOAD_ORBIS": "上传到orbis.zone(测试阶段)",
|
||||||
"orbis username": "orbis.zone的Email或用户名",
|
"orbis username": "orbis.zone的Email或用户名",
|
||||||
"orbis password": "orbis.zone的密码",
|
"orbis password": "orbis.zone的密码",
|
||||||
|
|||||||
@@ -14,4 +14,3 @@ export const formats = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export { default as terms } from './de.json';
|
export { default as terms } from './de.json';
|
||||||
|
|
||||||
|
|||||||
@@ -133,7 +133,7 @@
|
|||||||
"pv": "Planetenfahrzeug-Hangar",
|
"pv": "Planetenfahrzeug-Hangar",
|
||||||
"rf": "Raffinerie",
|
"rf": "Raffinerie",
|
||||||
"rg": "Schienenkanone",
|
"rg": "Schienenkanone",
|
||||||
"rsl": "Steuerung Forschungsdrohne",
|
"rsl": "Steuerung Aufklärungsdrohne",
|
||||||
"s": "Sensoren",
|
"s": "Sensoren",
|
||||||
"sb": "Schildverstärker",
|
"sb": "Schildverstärker",
|
||||||
"sc": "Himmelskörperscanner",
|
"sc": "Himmelskörperscanner",
|
||||||
@@ -146,7 +146,7 @@
|
|||||||
"ul": "Salvenlaser",
|
"ul": "Salvenlaser",
|
||||||
"ws": "FS-Sogwolkenscanner",
|
"ws": "FS-Sogwolkenscanner",
|
||||||
"rpl": "Steuerung Reparaturdrohne",
|
"rpl": "Steuerung Reparaturdrohne",
|
||||||
"rcpl": "Steuerung Aufklärungsdrohne",
|
"rcpl": "Recon Limpet Controller",
|
||||||
"hrd": "Hüllenhärte",
|
"hrd": "Hüllenhärte",
|
||||||
"pax": "Pass",
|
"pax": "Pass",
|
||||||
"axmc": "AX-Mehrfachgeschütz",
|
"axmc": "AX-Mehrfachgeschütz",
|
||||||
@@ -162,7 +162,7 @@
|
|||||||
"life support": "Lebenserhaltung",
|
"life support": "Lebenserhaltung",
|
||||||
"power plant": "Kraftwerk",
|
"power plant": "Kraftwerk",
|
||||||
"thrusters": "Antriebe",
|
"thrusters": "Antriebe",
|
||||||
"power distributor": "Energieverteiler",
|
"power distriubtor": "Energieverteiler",
|
||||||
"sensors": "Sensoren",
|
"sensors": "Sensoren",
|
||||||
"bins": "Behältnisse",
|
"bins": "Behältnisse",
|
||||||
"bays": "Slots",
|
"bays": "Slots",
|
||||||
|
|||||||
@@ -26,9 +26,6 @@
|
|||||||
"PHRASE_NO_SPECIAL": "No experimental effect",
|
"PHRASE_NO_SPECIAL": "No experimental effect",
|
||||||
"PHRASE_SHOPPING_LIST": "Stations that sell this build",
|
"PHRASE_SHOPPING_LIST": "Stations that sell this build",
|
||||||
"PHRASE_SHOPPING_MATS": "Materials needed for this build",
|
"PHRASE_SHOPPING_MATS": "Materials needed for this build",
|
||||||
"PHRASE_DIFFERENT_ROLLS": "Material requirements will differ, based on the number of rolls per grade, per module.",
|
|
||||||
"PHRASE_FOR_FINER_CONTROL": "For finer control over rolls per grade/module, a more accurate list of required mats and help in finding those mats, please consider using the export options below to ED Odyssey Materials Helper, or ED Engineer, or use the Crafting Lists feature on inara.cz.",
|
|
||||||
"PHRASE_ALL_MODULES_ALL_ROLLS": "The list below, assumes standard to G5 Engineered (approx 80% - 90%) with the rolls above, for ALL engineered modules in the build. You can adjust the number of rolls above for each grade, however it will still apply to all engineered modules in the build.",
|
|
||||||
"PHRASE_REFIT_SHOPPING_LIST": "Stations that sell required modules",
|
"PHRASE_REFIT_SHOPPING_LIST": "Stations that sell required modules",
|
||||||
"PHRASE_TOTAL_EFFECTIVE_SHIELD": "Total amount of damage that can be taken from each damage type, if using all shield cells",
|
"PHRASE_TOTAL_EFFECTIVE_SHIELD": "Total amount of damage that can be taken from each damage type, if using all shield cells",
|
||||||
"PHRASE_TIME_TO_LOSE_SHIELDS": "Shields will hold for",
|
"PHRASE_TIME_TO_LOSE_SHIELDS": "Shields will hold for",
|
||||||
@@ -66,17 +63,15 @@
|
|||||||
"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_SHIP_BOOST_INTERVAL": "Time each boost lasts on this ship",
|
"TT_SUMMARY_BOOST_INTERVAL": "Time between each boost with 4 pips to ENG",
|
||||||
"TT_SUMMARY_BOOST_INTERVAL": "Time between boosts with 4 pips to ENG",
|
|
||||||
"TT_SUMMARY_BOOST_INTERVALS": "If DISTRO is less than or equal to SHIP, the ship can boost continuously",
|
|
||||||
"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",
|
||||||
"TT_SUMMARY_SHIELDS_NONFUNCTIONAL": "No shield generator or shield generator powered off",
|
"TT_SUMMARY_SHIELDS_NONFUNCTIONAL": "No shield generator or shield generator powered off",
|
||||||
"TT_SUMMARY_INTEGRITY": "Ship integrity, including bulkheads and hull reinforcement packages",
|
"TT_SUMMARY_INTEGRITY": "Ship integrity, including bulkheads and hull reinforcement packages",
|
||||||
"TT_SUMMARY_HULL_MASS": "Mass of the hull prior to any modules being installed",
|
"TT_SUMMARY_HULL_MASS": "Mass of the hull prior to any modules being installed",
|
||||||
"TT_SUMMARY_UNLADEN_MASS": "Mass of the hull and modules prior to any cargo or passengers",
|
"TT_SUMMARY_UNLADEN_MASS": "Mass of the hull and modules prior to any fuel or cargo",
|
||||||
"TT_SUMMARY_LADEN_MASS": "Mass of the hull and modules with full fuel, cargo, passengers, etc.",
|
"TT_SUMMARY_LADEN_MASS": "Mass of the hull and modules with full fuel and cargo",
|
||||||
"TT_SUMMARY_DPS": "Damage per second with all weapons firing",
|
"TT_SUMMARY_DPS": "Damage per second with all weapons firing",
|
||||||
"TT_SUMMARY_EPS": "WEP capacitor consumed per second with all weapons firing",
|
"TT_SUMMARY_EPS": "WEP capacitor consumed per second with all weapons firing",
|
||||||
"TT_SUMMARY_TTD": "Time to drain WEP capacitor with all weapons firing and 4 pips to WEP",
|
"TT_SUMMARY_TTD": "Time to drain WEP capacitor with all weapons firing and 4 pips to WEP",
|
||||||
@@ -86,10 +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_ENSURE_EDOMH": "Ensure ED Odyssey Material Helper is installed and registered to handle edomh:// urls, else this button will do nothing!",
|
"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.",
|
"PHRASE_FIREFOX_EDENGINEER": "Sending to EDEngineer is not compatible with Firefox's security settings. Please try again with Chrome.",
|
||||||
"PHRASE_FAILED_TO_FIND_EDENGINEER": "Failed to find ED Engineer API. Please ensure it is running and try again.",
|
|
||||||
"MISSING_MODULES": "Missing Modules",
|
|
||||||
"am": "Auto Field-Maintenance Unit",
|
"am": "Auto Field-Maintenance Unit",
|
||||||
"bh": "Bulkheads",
|
"bh": "Bulkheads",
|
||||||
"bl": "Beam Laser",
|
"bl": "Beam Laser",
|
||||||
@@ -100,7 +93,6 @@
|
|||||||
"ch": "Chaff Launcher",
|
"ch": "Chaff Launcher",
|
||||||
"cr": "Cargo Rack",
|
"cr": "Cargo Rack",
|
||||||
"cs": "Manifest Scanner",
|
"cs": "Manifest Scanner",
|
||||||
"csl": "Caustic Sink Launcher",
|
|
||||||
"dc": "Docking Computer",
|
"dc": "Docking Computer",
|
||||||
"ec": "Electronic Countermeasure",
|
"ec": "Electronic Countermeasure",
|
||||||
"fc": "Fragment Cannon",
|
"fc": "Fragment Cannon",
|
||||||
@@ -116,18 +108,10 @@
|
|||||||
"kw": "Kill Warrant Scanner",
|
"kw": "Kill Warrant Scanner",
|
||||||
"ls": "Life Support",
|
"ls": "Life Support",
|
||||||
"mc": "Multi-cannon",
|
"mc": "Multi-cannon",
|
||||||
"mh": "Missing Weapon/Utility",
|
|
||||||
"mm": "Missing Module",
|
|
||||||
"advmc": "Multi-cannon (Advanced)",
|
|
||||||
"axmc": "AX Multi-cannon",
|
"axmc": "AX Multi-cannon",
|
||||||
"axmce": "AX Multi-cannon (Enhanced)",
|
|
||||||
"ml": "Mining Laser",
|
"ml": "Mining Laser",
|
||||||
"mlc": "Multi Limpet Controller",
|
|
||||||
"mr": "Missile Rack",
|
"mr": "Missile Rack",
|
||||||
"amr": "Missile Rack (Advanced)",
|
|
||||||
"axmr": "AX Missile Rack",
|
"axmr": "AX Missile Rack",
|
||||||
"axmre": "AX Missile Rack (Enhanced)",
|
|
||||||
"ews": "Experimental Weapon Stabilizer",
|
|
||||||
"mrp": "Module Reinforcement Package",
|
"mrp": "Module Reinforcement Package",
|
||||||
"nl": "Mine Launcher",
|
"nl": "Mine Launcher",
|
||||||
"pa": "Plasma Accelerator",
|
"pa": "Plasma Accelerator",
|
||||||
@@ -172,10 +156,8 @@
|
|||||||
"sua": "Supercruise Assist",
|
"sua": "Supercruise Assist",
|
||||||
"t": "thrusters",
|
"t": "thrusters",
|
||||||
"tp": "Torpedo Pylon",
|
"tp": "Torpedo Pylon",
|
||||||
"ntp": "Nanite Torpedo Pylon",
|
|
||||||
"ul": "Burst Laser",
|
"ul": "Burst Laser",
|
||||||
"Send To EDEngineer": "Send To EDEngineer",
|
"Send To EDEngineer": "Send To EDEngineer",
|
||||||
"Send To EDOMH": "Send To EDOMH",
|
|
||||||
"ws": "Frame Shift Wake Scanner",
|
"ws": "Frame Shift Wake Scanner",
|
||||||
"rpl": "Repair Limpet Controller",
|
"rpl": "Repair Limpet Controller",
|
||||||
"rcpl": "Recon Limpet Controller",
|
"rcpl": "Recon Limpet Controller",
|
||||||
@@ -223,10 +205,9 @@
|
|||||||
"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 interval": "Boost interval",
|
"boost interval": "Boost intervall",
|
||||||
"total": "Total",
|
"total": "Total",
|
||||||
"ammo": "Ammunition maximum",
|
"ammo": "Ammunition maximum",
|
||||||
"info": "Info",
|
|
||||||
"boot": "Boot time",
|
"boot": "Boot time",
|
||||||
"hacktime": "Hack time",
|
"hacktime": "Hack time",
|
||||||
"brokenregen": "Broken regeneration rate",
|
"brokenregen": "Broken regeneration rate",
|
||||||
@@ -276,10 +257,8 @@
|
|||||||
"thermres": "Thermal resistance",
|
"thermres": "Thermal resistance",
|
||||||
"wepcap": "Weapons capacity",
|
"wepcap": "Weapons capacity",
|
||||||
"weprate": "Weapons recharge rate",
|
"weprate": "Weapons recharge rate",
|
||||||
"minmass": "Minimum mass",
|
|
||||||
"minmass_sg": "Minimum hull mass",
|
"minmass_sg": "Minimum hull mass",
|
||||||
"optmass_sg": "Optimal hull mass",
|
"optmass_sg": "Optimal hull mass",
|
||||||
"maxmass": "Maximum mass",
|
|
||||||
"maxmass_sg": "Maximum hull mass",
|
"maxmass_sg": "Maximum hull mass",
|
||||||
"minmul_sg": "Minimum strength",
|
"minmul_sg": "Minimum strength",
|
||||||
"optmul_sg": "Optimal strength",
|
"optmul_sg": "Optimal strength",
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
export const formats = {
|
|
||||||
decimal: '.',
|
|
||||||
thousands: ',',
|
|
||||||
grouping: [3],
|
|
||||||
currency: ['₩', ''],
|
|
||||||
dateTime: '%a %b %e %X %Y',
|
|
||||||
date: '%Y/%m/%d',
|
|
||||||
time: '%H:%M:%S',
|
|
||||||
periods: ['오전', '오후'],
|
|
||||||
days: ['일요일', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일'],
|
|
||||||
shortDays: ['일', '월', '화', '수', '목', '금', '토'],
|
|
||||||
months: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'],
|
|
||||||
shortMonths: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월']
|
|
||||||
};
|
|
||||||
|
|
||||||
export { default as terms } from './ko.json';
|
|
||||||
File diff suppressed because one or more lines are too long
@@ -2,15 +2,15 @@ export const formats = {
|
|||||||
decimal: ',',
|
decimal: ',',
|
||||||
thousands: '.',
|
thousands: '.',
|
||||||
grouping: [3],
|
grouping: [3],
|
||||||
currency: ['$', ''],
|
currency: ['', ' €'],
|
||||||
dateTime: '%A, %e de %B de %Y, %X',
|
dateTime: '%A, %e de %B de %Y, %X',
|
||||||
date: '%d/%m/%Y',
|
date: '%d/%m/%Y',
|
||||||
time: '%H:%M:%S',
|
time: '%H:%M:%S',
|
||||||
periods: ['AM', 'PM'],
|
periods: ['AM', 'PM'],
|
||||||
days: ['domingo', 'segunda', 'terça', 'quarta', 'quinta', 'sexta', 'sábado'],
|
days: ['domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado'],
|
||||||
shortDays: ['dom', 'seg', 'ter', 'qua', 'qui', 'sex', 'sab'],
|
shortDays: ['dom', 'lun', 'mar', 'mié', 'jue', 'vie', 'sáb'],
|
||||||
months: ['janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho', 'julho', 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro'],
|
months: ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'],
|
||||||
shortMonths: ['jan', 'fev', 'mar', 'abr', 'mai', 'jun', 'jul', 'ago', 'set', 'out', 'nov', 'dez']
|
shortMonths: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic']
|
||||||
};
|
};
|
||||||
|
|
||||||
export { default as terms } from './pt.json';
|
export { default as terms } from './pt.json';
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -5,7 +5,7 @@
|
|||||||
"PHRASE_EXPORT_DESC": "Детальный JSON-экспорт вашей сборки для использования в других местах и инструментах",
|
"PHRASE_EXPORT_DESC": "Детальный JSON-экспорт вашей сборки для использования в других местах и инструментах",
|
||||||
"PHRASE_FASTEST_RANGE": "Последовательные прыжки максимальной дальности",
|
"PHRASE_FASTEST_RANGE": "Последовательные прыжки максимальной дальности",
|
||||||
"PHRASE_IMPORT": "Для импорта вставьте код в эту форму",
|
"PHRASE_IMPORT": "Для импорта вставьте код в эту форму",
|
||||||
"PHRASE_LADEN": "Масса корабля с учетом топлива и грузов",
|
"PHRASE_LADEN": "Масса корабля с учётом топлива и грузов",
|
||||||
"PHRASE_NO_BUILDS": "Нечего сравнивать",
|
"PHRASE_NO_BUILDS": "Нечего сравнивать",
|
||||||
"PHRASE_NO_RETROCH": "Нет ранних версий сборки",
|
"PHRASE_NO_RETROCH": "Нет ранних версий сборки",
|
||||||
"PHRASE_SELECT_BUILDS": "Выберите конфигурацию для сравнения",
|
"PHRASE_SELECT_BUILDS": "Выберите конфигурацию для сравнения",
|
||||||
@@ -14,20 +14,17 @@
|
|||||||
"PHRASE_UNLADEN": "Масса корабля без учета топлива и грузов",
|
"PHRASE_UNLADEN": "Масса корабля без учета топлива и грузов",
|
||||||
"PHRASE_UPDATE_RDY": "Доступна новая версия. Нажмите для обновления.",
|
"PHRASE_UPDATE_RDY": "Доступна новая версия. Нажмите для обновления.",
|
||||||
"PHRASE_ENGAGEMENT_RANGE": "Дистанция между кораблём и целью",
|
"PHRASE_ENGAGEMENT_RANGE": "Дистанция между кораблём и целью",
|
||||||
"PHRASE_SELECT_BLUEPRINT": "Нажмите чтобы выбрать чертеж",
|
"PHRASE_SELECT_BLUEPRINT": "Нажмите чтобы выбрать чертёж",
|
||||||
"PHRASE_BLUEPRINT_WORST": "Худшие основные значения для чертежа",
|
"PHRASE_BLUEPRINT_WORST": "Худшие основные значения для чертежа",
|
||||||
"PHRASE_BLUEPRINT_FIFTY": "50% значения для чертежа",
|
|
||||||
"PHRASE_BLUEPRINT_SEVEN_FIVE": "75% значения для чертежа",
|
|
||||||
"PHRASE_BLUEPRINT_RANDOM": "Случайный выбор между худшими и лучшими значениями для этого чертежа",
|
"PHRASE_BLUEPRINT_RANDOM": "Случайный выбор между худшими и лучшими значениями для этого чертежа",
|
||||||
"PHRASE_BLUEPRINT_BEST": "Лучшие основные значения для чертежа",
|
"PHRASE_BLUEPRINT_BEST": "Лучшие основные значения для чертежа",
|
||||||
"PHRASE_BLUEPRINT_EXTREME": "Лучшие положительные и худшие отрицательные основные значения для чертежа",
|
"PHRASE_BLUEPRINT_EXTREME": "Лучшие положительные и худшие отрицательные основные значения для чертежа",
|
||||||
"PHRASE_BLUEPRINT_RESET": "Сбросить все модификаторы и чертеж",
|
"PHRASE_BLUEPRINT_RESET": "Убрать все изменения и чертёж",
|
||||||
"PHRASE_SELECT_SPECIAL": "Нажмите, чтобы выбрать экспериментальный эффект",
|
"PHRASE_SELECT_SPECIAL": "Нажмите, чтобы выбрать экспериментальный эффект",
|
||||||
"PHRASE_NO_SPECIAL": "Без экспериментального эффекта",
|
"PHRASE_NO_SPECIAL": "Без экспериментального эффекта",
|
||||||
"PHRASE_SHOPPING_LIST": "Станции, на которых продают эту сборку",
|
"PHRASE_SHOPPING_LIST": "Станции, что продают эту сборку",
|
||||||
"PHRASE_SHOPPING_MATS": "Материалы которые нужны для сборки",
|
"PHRASE_REFIT_SHOPPING_LIST": "Станции, что продают необходимые модули",
|
||||||
"PHRASE_REFIT_SHOPPING_LIST": "Станции, на которых продают необходимые модули",
|
"PHRASE_TOTAL_EFFECTIVE_SHIELD": "Общий урон, что может быть нанесён в каждым типе, если используются все щитонакопители",
|
||||||
"PHRASE_TOTAL_EFFECTIVE_SHIELD": "Общий урон, что может быть нанесен в каждым типе, если используются все щитонакопители",
|
|
||||||
"PHRASE_TIME_TO_LOSE_SHIELDS": "Щиты продержатся",
|
"PHRASE_TIME_TO_LOSE_SHIELDS": "Щиты продержатся",
|
||||||
"PHRASE_TIME_TO_RECOVER_SHIELDS": "Щиты восстановятся за",
|
"PHRASE_TIME_TO_RECOVER_SHIELDS": "Щиты восстановятся за",
|
||||||
"PHRASE_TIME_TO_RECHARGE_SHIELDS": "Щиты будут заряжены за",
|
"PHRASE_TIME_TO_RECHARGE_SHIELDS": "Щиты будут заряжены за",
|
||||||
@@ -39,89 +36,76 @@
|
|||||||
"PHRASE_TIME_TO_LOSE_ARMOUR": "Броня продержится",
|
"PHRASE_TIME_TO_LOSE_ARMOUR": "Броня продержится",
|
||||||
"PHRASE_MODULE_PROTECTION_EXTERNAL": "Защита гнёзд",
|
"PHRASE_MODULE_PROTECTION_EXTERNAL": "Защита гнёзд",
|
||||||
"PHRASE_MODULE_PROTECTION_INTERNAL": "Защита всех остальных модулей",
|
"PHRASE_MODULE_PROTECTION_INTERNAL": "Защита всех остальных модулей",
|
||||||
"PHRASE_OVERALL_DAMAGE": "Разбивка источников устойчивого УвС",
|
"PHRASE_SHIELD_DAMAGE": "Подробности источников поддерживаемого ДПС против щитов",
|
||||||
"PHRASE_SHIELD_DAMAGE": "Подробности источников поддерживаемого УвС против щитов",
|
"PHRASE_ARMOUR_DAMAGE": "Подробности источников поддерживаемого ДПС против брони",
|
||||||
"PHRASE_ARMOUR_DAMAGE": "Подробности источников поддерживаемого УвС против брони",
|
|
||||||
"PHRASE_TIME_TO_REMOVE_SHIELDS": "Снимет щиты за",
|
"PHRASE_TIME_TO_REMOVE_SHIELDS": "Снимет щиты за",
|
||||||
"PHRASE_MULTI_CREW_CAPACITOR_POINTS": "Щелкните правой кновкой мыши чтобы объединить в группу.",
|
|
||||||
"TT_TIME_TO_REMOVE_SHIELDS": "Непрерывным огнём из всех орудий",
|
"TT_TIME_TO_REMOVE_SHIELDS": "Непрерывным огнём из всех орудий",
|
||||||
"PHRASE_TIME_TO_REMOVE_ARMOUR": "Снимет броню за",
|
"PHRASE_TIME_TO_REMOVE_ARMOUR": "Снимет броню за",
|
||||||
"TT_TIME_TO_REMOVE_ARMOUR": "Непрерывным огнём из всех орудий",
|
"TT_TIME_TO_REMOVE_ARMOUR": "Непрерывным огнём из всех орудий",
|
||||||
"PHRASE_TIME_TO_DRAIN_WEP": "Опустошит ОРУ за",
|
"PHRASE_TIME_TO_DRAIN_WEP": "Опустошит ОРУЖ за",
|
||||||
"TT_TIME_TO_DRAIN_WEP": "Время, за которое опустошится аккумулятор ОРУ при стрельбе из всех орудий",
|
"TT_TIME_TO_DRAIN_WEP": "Время, за которое опустошится аккумулятор ОРУЖ при стрельбе из всех орудий",
|
||||||
"TT_TIME_TO_LOSE_SHIELDS": "Против поддерживаемой стрельбы из всех орудий противника",
|
"TT_TIME_TO_LOSE_SHIELDS": "Против поддерживаемой стрельбы из всех орудий противника",
|
||||||
"TT_TIME_TO_LOSE_ARMOUR": "Против поддерживаемой стрельбы из всех орудий противника",
|
"TT_TIME_TO_LOSE_ARMOUR": "Против поддерживаемой стрельбы из всех орудий противника",
|
||||||
"TT_MODULE_ARMOUR": "Броня, защищающая модули от урона",
|
"TT_MODULE_ARMOUR": "Броня, защищающая модули от урона",
|
||||||
"TT_MODULE_PROTECTION_EXTERNAL": "Процент урона, перенаправленного от гнезд на наборы для усиления модулей",
|
"TT_MODULE_PROTECTION_EXTERNAL": "Процент урона, перенаправленного от гнёзд на наборы для усиления модулей",
|
||||||
"TT_MODULE_PROTECTION_INTERNAL": "Процент урона, перенаправленного от модулей вне гнёзд на наборы для усиления модулей",
|
"TT_MODULE_PROTECTION_INTERNAL": "Процент урона, перенаправленного от модулей вне гнёзд на наборы для усиления модулей",
|
||||||
"TT_EFFECTIVE_SDPS_SHIELDS": "Реальный поддерживаемый УвС пока ёмкость ОРУ не пуста",
|
"TT_EFFECTIVE_SDPS_SHIELDS": "Реальный поддерживаемый ДПС пока аккумулятор ОРУЖ не пуст",
|
||||||
"TT_EFFECTIVENESS_SHIELDS": "Эффективность в сравнении с попаданием по цели с 0-сопротивляемостью без пунктов в СИС на 0 метрах",
|
"TT_EFFECTIVENESS_SHIELDS": "Эффективность в сравнении с попаданием по цели с 0-сопротивляемостью без пунктов в СИС на 0 метрах",
|
||||||
"TT_EFFECTIVE_SDPS_ARMOUR": "Реальный поддерживаемый УвС пока аккумулятор ОРУ не пуст",
|
"TT_EFFECTIVE_SDPS_ARMOUR": "Реальный поддерживаемый ДПС пока аккумулятор ОРУЖ не пуст",
|
||||||
"TT_EFFECTIVENESS_ARMOUR": "Эффективность в сравнении с попаданием по цели с 0-сопротивляемостью на 0 метрах",
|
"TT_EFFECTIVENESS_ARMOUR": "Эффективность в сравнении с попаданием по цели с 0-сопротивляемостью на 0 метрах",
|
||||||
"PHRASE_EFFECTIVE_SDPS_SHIELDS": "ПУвС против щитов",
|
"PHRASE_EFFECTIVE_SDPS_SHIELDS": "ПДПС против щитов",
|
||||||
"PHRASE_EFFECTIVE_SDPS_ARMOUR": "ПУвС против брони",
|
"PHRASE_EFFECTIVE_SDPS_ARMOUR": "ПДПС против брони",
|
||||||
"TT_SUMMARY_SPEED": "С полным топливным баком и 4 пунктами в ДВГ",
|
"TT_SUMMARY_SPEED": "С полным топливным баком и 4 пунктами в ДВИ",
|
||||||
"TT_SUMMARY_SPEED_NONFUNCTIONAL": "Маневровые двигатели выключены или превышена максимальная масса с топливом и грузом",
|
"TT_SUMMARY_SPEED_NONFUNCTIONAL": "Маневровые двигатели выключены или превышена максимальная масса с топливом и грузом",
|
||||||
"TT_SUMMARY_BOOST": "С полным топливным баком и 4 пунктами в ДВГ",
|
"TT_SUMMARY_BOOST": "С полным топливным баком и 4 пунктами в ДВИ",
|
||||||
"TT_SUMMARY_BOOST_INTERVAL": "Время заполнения с 4 пунктами в СИС",
|
|
||||||
"TT_SUMMARY_BOOST_NONFUNCTIONAL": "Распределитель питания не может обеспечить достаточно энергии для форсажа",
|
"TT_SUMMARY_BOOST_NONFUNCTIONAL": "Распределитель питания не может обеспечить достаточно энергии для форсажа",
|
||||||
"TT_SUMMARY_SHIELDS": "Чистая сила щита, включая усилители",
|
"TT_SUMMARY_SHIELDS": "Чистая сила щита, включая усилители",
|
||||||
"TT_SUMMARY_SHIELDS_SCB": "Прочность щита, включая бустеры и щитонакопители",
|
|
||||||
"TT_SUMMARY_SHIELDS_NONFUNCTIONAL": "Шитогенератор отсутствует или выключен",
|
"TT_SUMMARY_SHIELDS_NONFUNCTIONAL": "Шитогенератор отсутствует или выключен",
|
||||||
"TT_SUMMARY_INTEGRITY": "Целостность корабля, включая переборки и наборы для усиления корпуса",
|
"TT_SUMMARY_INTEGRITY": "Целостность корабля, включая переборки и наборы для усиления корпуса",
|
||||||
"TT_SUMMARY_HULL_MASS": "Масса корпуса без каких-либо модулей",
|
"TT_SUMMARY_HULL_MASS": "Масса корпуса без каких-либо модулей",
|
||||||
"TT_SUMMARY_UNLADEN_MASS": "Масса корпуса и модулей без топлива и груза",
|
"TT_SUMMARY_UNLADEN_MASS": "Масса корпуса и модулей без топлива и груза",
|
||||||
"TT_SUMMARY_LADEN_MASS": "Масса корпуса и модулей с топливом и грузом",
|
"TT_SUMMARY_LADEN_MASS": "Масса корпуса и модулей с топливом и грузом",
|
||||||
"TT_SUMMARY_DPS": "Урон в секунду при стрельбе из всех орудий",
|
"TT_SUMMARY_DPS": "Урон в секунду при стрельбе из всех орудий",
|
||||||
"TT_SUMMARY_EPS": "Расход аккумулятора ОРУ в секунду при стрельбе из всех орудий",
|
"TT_SUMMARY_EPS": "Расход аккумулятора ОРУЖ в секунду при стрельбе из всех орудий",
|
||||||
"TT_SUMMARY_TTD": "Время расхода аккумулятора ОРУ при стрельбе из всех орудий и с 4 пунктами в ОРУ",
|
"TT_SUMMARY_TTD": "Время расхода аккумулятора ОРУЖ при стрельбе из всех орудий и с 4 пунктами в ОРУЖ",
|
||||||
"TT_SUMMARY_MAX_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с топливом, достаточным только на сам прыжок",
|
"TT_SUMMARY_MAX_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с топливом, достаточным только на сам прыжок",
|
||||||
"TT_SUMMARY_UNLADEN_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с полным топливным баком",
|
"TT_SUMMARY_UNLADEN_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с полным топливным баком",
|
||||||
"TT_SUMMARY_LADEN_SINGLE_JUMP": "Самый дальний возможный прыжок с полным грузовым отсеком и с полным топливным баком",
|
"TT_SUMMARY_LADEN_SINGLE_JUMP": "Самый дальний возможный прыжок с полным грузовым отсеком и с полным топливным баком",
|
||||||
"TT_SUMMARY_UNLADEN_TOTAL_JUMP": "Самая дальняя общая дистанция без груза, с полным топливным баком и при прыжках на максимальное расстояние",
|
"TT_SUMMARY_UNLADEN_TOTAL_JUMP": "Самая дальняя общая дистанция без груза, с полным топливным баком и при прыжках на максимальное расстояние",
|
||||||
"TT_SUMMARY_LADEN_TOTAL_JUMP": "Самая дальняя общая дистанция с полным грузовым отсеком, с полным топливным баком и при прыжках на максимальное расстояние",
|
"TT_SUMMARY_LADEN_TOTAL_JUMP": "Самая дальняя общая дистанция с полным грузовым отсеком, с полным топливным баком и при прыжках на максимальное расстояние",
|
||||||
"HELP_MODIFICATIONS_MENU": "Нажмите на номер, чтобы ввести новое значение, или потяните вдоль полосы для малых изменений",
|
"HELP_MODIFICATIONS_MENU": "Нажмите на номер, чтобы ввести новое значение, или потяните вдоль полосы для малых изменений",
|
||||||
"PHRASE_FAIL_EDENGINEER": "Не удалось отправить в EDEngineer (запустите EDEngineer и убедитесь, что API запущен, затем обновите страницу).",
|
|
||||||
"PHRASE_FIREFOX_EDENGINEER": "Отправка в EDEngineer несовместима с настройками безопасности Firefox. Пожалуйста, попробуйте еще раз в Google Chrome.",
|
|
||||||
"am": "Блок Автом. Полевого Ремонта",
|
"am": "Блок Автом. Полевого Ремонта",
|
||||||
"bh": "Переборки",
|
"bh": "Переборки",
|
||||||
"bl": "Пучковый лазер",
|
"bl": "Пучковый лазер",
|
||||||
"bsg": "Двухпоточный щитогенератор",
|
"bsg": "Двухпоточный щитогенератор",
|
||||||
"c": "Пушка",
|
"c": "Орудие",
|
||||||
"causres": "Сопротивление едк.урону",
|
"cc": "Контроллер магнитного снаряда для сбора",
|
||||||
"Caustic resistance": "Сопротивление едк.урону",
|
|
||||||
"cc": "Контроллер дронов-сборщиков",
|
|
||||||
"ch": "Разбрасыватель дипольных отражателей",
|
"ch": "Разбрасыватель дипольных отражателей",
|
||||||
"cr": "Грузовой стеллаж",
|
"cr": "Грузовой стеллаж",
|
||||||
"cs": "Сканер содержимого",
|
"cs": "Сканер содержимого",
|
||||||
"csl": "Антикор катапульта",
|
|
||||||
"dc": "Стыковочный компьютер",
|
"dc": "Стыковочный компьютер",
|
||||||
"ec": "Радиоэлектронное подавление",
|
"ec": "Электр. противодействие",
|
||||||
"fc": "Залповое орудие",
|
"fc": "Залповое орудие",
|
||||||
"fh": "Ангар для истребителя",
|
"fh": "Ангар для истребителя",
|
||||||
"fi": "FSD-перехватчик",
|
"fi": "FSD-перехватчик",
|
||||||
"fs": "Топливозаборник",
|
"fs": "Топливозаборник",
|
||||||
"fsd": "Рамочно-сместительный двигатель",
|
"fsd": "Рамочно-сместительный двигатель",
|
||||||
"ft": "Топливный бак",
|
"ft": "Топливный бак",
|
||||||
"fx": "Контроллер дронов-заправщиков",
|
"fx": "Контроллер магнитного снаряда для топлива",
|
||||||
"hb": "Контроллер дронов-взломщиков трюмов",
|
"hb": "Контроллер магнитного снаряда для взлома трюма",
|
||||||
"hr": "Набор для усиления корпуса",
|
"hr": "Набор для усиления корпуса",
|
||||||
"hs": "Теплоотводная катапульта",
|
"hs": "Теплоотводная катапульта",
|
||||||
"kw": "Сканер преступников",
|
"kw": "Сканер преступников",
|
||||||
"ls": "Система жизнеобеспечения",
|
"ls": "Система жизнеобеспечения",
|
||||||
"mc": "Многоствольное орудие",
|
"mc": "Многоствольное орудие",
|
||||||
"axmc": "Многоствольное орудие АИ",
|
|
||||||
"ml": "Проходочный лазер",
|
"ml": "Проходочный лазер",
|
||||||
"mr": "Блок ракет",
|
"mr": "Ракетный лоток",
|
||||||
"axmr": "Блок ракет АИ",
|
|
||||||
"ews": "Стабилизатор экспериментального вооружения",
|
|
||||||
"mrp": "Набор для усиления модуля",
|
"mrp": "Набор для усиления модуля",
|
||||||
"nl": "Мины",
|
"nl": "Мины",
|
||||||
"pa": "Ускоритель плазмы",
|
"pa": "Ускоритель плазмы",
|
||||||
"pas": "Комплект для сближения с планетой",
|
"pas": "Комплект для сближения с планетой",
|
||||||
"pc": "Контроллер дронов-геологоразведчиков",
|
"pc": "Контроллер магнитного снаряда для геологоразведки",
|
||||||
"pce": "Каюта пассажира эконом-класса",
|
"pce": "Каюта пассажира эконом-класса",
|
||||||
"passenger capacity": "Количество пассажиров",
|
|
||||||
"pci": "Каюта пассажира бизнес-класса",
|
"pci": "Каюта пассажира бизнес-класса",
|
||||||
"pcm": "Каюта пассажира первого класса",
|
"pcm": "Каюта пассажира первого класса",
|
||||||
"pcq": "Каюта пассажира класса люкс",
|
"pcq": "Каюта пассажира класса люкс",
|
||||||
@@ -129,65 +113,33 @@
|
|||||||
"pl": "Импульсный лазер",
|
"pl": "Импульсный лазер",
|
||||||
"po": "Точечная оборона",
|
"po": "Точечная оборона",
|
||||||
"pp": "Силовая установка",
|
"pp": "Силовая установка",
|
||||||
"gpp": "Силовая установка Стражей",
|
|
||||||
"gpd": "Гибридный распределитель питания Стражей",
|
|
||||||
"gpc": "Плазменная пушка Стражей",
|
|
||||||
"ggc": "Пушка Гаусса Стражей",
|
|
||||||
"gsrp": "Набор для усиления щита Стражей",
|
|
||||||
"gfsb": "Ускоритель FSD Стражей",
|
|
||||||
"ghrp": "Набор для усиления корпуса Стражей",
|
|
||||||
"gmrp": "Набор для усиления модуля Стражей",
|
|
||||||
"pwa": "Анализатор импульсных волн",
|
|
||||||
"abl": "Абразивный бластер",
|
|
||||||
"scl": "Пусковая установка для сейсмических снарядов",
|
|
||||||
"sdm": "Вытесняющая ракета для добычи глубинных залежей",
|
|
||||||
"tbsc": "Шоковое орудие",
|
|
||||||
"gsc": "Осколочное орудие Стражей",
|
|
||||||
"psg": "Призматический щитогенератор",
|
"psg": "Призматический щитогенератор",
|
||||||
"pv": "Гараж для планетарного транспорта",
|
"pv": "Гараж для планетарного транспорта",
|
||||||
"rf": "Очиститель",
|
"rf": "Устройство переработки",
|
||||||
"rfl": "Зенитная установка (снаряды с дистанционным подрывом)",
|
"rg": "Электромагнитная пушка",
|
||||||
"rg": "Рельсотрон",
|
|
||||||
"rsl": "Контроллер дронов-исследователей",
|
|
||||||
"s": "Сенсоры",
|
"s": "Сенсоры",
|
||||||
"sb": "Усилитель щита",
|
"sb": "Усилитель щита",
|
||||||
"sc": "Сканер обнаружения",
|
"sc": "Сканер обнаружения",
|
||||||
"scb": "Щитонакопитель",
|
"scb": "Щитонакопитель",
|
||||||
"sfn": "Нейтрализатор отключающего поля",
|
|
||||||
"sg": "Щитогенератор",
|
"sg": "Щитогенератор",
|
||||||
"ss": "Сканер поверхностей",
|
"ss": "Сканер поверхностей",
|
||||||
"sua": "Помощь в гиперкрейсерском режиме",
|
|
||||||
"t": "Маневровые двигатели",
|
"t": "Маневровые двигатели",
|
||||||
"tp": "Торпедная установка",
|
"tp": "Торпедная стойка",
|
||||||
"ul": "Пульсирующий лазер",
|
"ul": "Пульсирующие лазеры",
|
||||||
"Send To EDEngineer": "Отправить в EDEngineer",
|
|
||||||
"Send To EDOMH": "Отправить в EDOMH",
|
|
||||||
"ws": "Сканер следа FSD",
|
"ws": "Сканер следа FSD",
|
||||||
"rpl": "Контроллер дронов-ремонтников",
|
|
||||||
"rcpl": "Контроллер дронов-разведчиков",
|
|
||||||
"xs": "Ксено-сканер",
|
|
||||||
"tbem": "Блок энзимных ракет",
|
|
||||||
"tbrfl": "Установка для стрельбы стреловидными снарядами с дистанционным подрывом",
|
|
||||||
"dtl": "Контроллер дронов-очистителей",
|
|
||||||
"mlc": "Мультиконтроллер",
|
|
||||||
"mahr": "Метасплавное усиление корпуса",
|
|
||||||
"emptyrestricted": "пусто (ограниченно)",
|
"emptyrestricted": "пусто (ограниченно)",
|
||||||
"damage dealt to": "Урон нанесён",
|
"damage dealt to": "Урон нанесён",
|
||||||
"damage received from": "Урон получен от",
|
"damage received from": "Урон получен от",
|
||||||
"against shields": "Против щитов",
|
"against shields": "Против щитов",
|
||||||
"against hull": "Против корпуса",
|
"against hull": "Против корпуса",
|
||||||
"total effective shield": "Общая эффективность щита",
|
"total effective shield": "Общие эффективные щиты",
|
||||||
"ammunition": "Припасы",
|
"ammunition": "Припасы",
|
||||||
"secs": "с",
|
"secs": "с",
|
||||||
"bays": "Ячейки",
|
"rebuildsperbay": "Построек за полосу",
|
||||||
"rebuildsperbay": "Истребителей в ячейке",
|
|
||||||
"mroll": "Roll",
|
|
||||||
"feature": "Свойство",
|
|
||||||
"worst": "Худшее",
|
"worst": "Худшее",
|
||||||
"average": "Среднее",
|
"average": "Среднее",
|
||||||
"random": "Случайное",
|
"random": "Случайное",
|
||||||
"best": "Лучшее",
|
"best": "Лучшее",
|
||||||
"current": "Текущее",
|
|
||||||
"extreme": "Экстремальное",
|
"extreme": "Экстремальное",
|
||||||
"reset": "Сброс",
|
"reset": "Сброс",
|
||||||
"dpe": "Урон на МДж энергии",
|
"dpe": "Урон на МДж энергии",
|
||||||
@@ -196,8 +148,6 @@
|
|||||||
"dpssdps": "Урон в секунду (поддерживаемый урон в секунду)",
|
"dpssdps": "Урон в секунду (поддерживаемый урон в секунду)",
|
||||||
"eps": "Энергия в секунду",
|
"eps": "Энергия в секунду",
|
||||||
"epsseps": "Энергия в секунду (поддерживаемая энергия в секунду)",
|
"epsseps": "Энергия в секунду (поддерживаемая энергия в секунду)",
|
||||||
"fallofffromrange": "Спад",
|
|
||||||
"falloff": "Спад",
|
|
||||||
"hps": "Нагрев в секунду",
|
"hps": "Нагрев в секунду",
|
||||||
"hpsshps": "Нагрев в секунду (поддерживаемый нагрев в секунду)",
|
"hpsshps": "Нагрев в секунду (поддерживаемый нагрев в секунду)",
|
||||||
"damage by": "Урон",
|
"damage by": "Урон",
|
||||||
@@ -205,7 +155,7 @@
|
|||||||
"shield cells": "Щитонакопители",
|
"shield cells": "Щитонакопители",
|
||||||
"recovery": "включение",
|
"recovery": "включение",
|
||||||
"recharge": "перезарядка",
|
"recharge": "перезарядка",
|
||||||
"engine pips": "Ячейки питания на ДВГ",
|
"engine pips": "Пункты в двигателе",
|
||||||
"4b": "4 пункта и Форсаж",
|
"4b": "4 пункта и Форсаж",
|
||||||
"speed": "скорость",
|
"speed": "скорость",
|
||||||
"pitch": "Тангаж",
|
"pitch": "Тангаж",
|
||||||
@@ -214,15 +164,13 @@
|
|||||||
"internal protection": "Внутренняя защита",
|
"internal protection": "Внутренняя защита",
|
||||||
"external protection": "Внешняя защита",
|
"external protection": "Внешняя защита",
|
||||||
"engagement range": "Боевое расстояние",
|
"engagement range": "Боевое расстояние",
|
||||||
"boost interval": "Интервал повыш.",
|
|
||||||
"total": "Всего",
|
"total": "Всего",
|
||||||
"ammo": "Макс. боекомплект",
|
"ammo": "Боекомплект",
|
||||||
"boot": "Время загрузки",
|
"boot": "Время загрузки",
|
||||||
"hacktime": "Время взлома",
|
|
||||||
"brokenregen": "Скорость восстановления при пробое",
|
"brokenregen": "Скорость восстановления при пробое",
|
||||||
"burst": "Длина очереди",
|
"burst": "Длина очереди",
|
||||||
"burstrof": "Скорострельность очереди",
|
"burstrof": "Скорострельность очереди",
|
||||||
"clip": "Размер боекомплекта",
|
"clip": "Боекомплект",
|
||||||
"damage": "Урон",
|
"damage": "Урон",
|
||||||
"distdraw": "Тяга распределителя",
|
"distdraw": "Тяга распределителя",
|
||||||
"duration": "Продолжительность",
|
"duration": "Продолжительность",
|
||||||
@@ -251,16 +199,11 @@
|
|||||||
"rof": "Скорострельность",
|
"rof": "Скорострельность",
|
||||||
"angle": "Угол сканера",
|
"angle": "Угол сканера",
|
||||||
"scanrate": "Скорость сканера",
|
"scanrate": "Скорость сканера",
|
||||||
"proberadius": "Радиус зонда",
|
|
||||||
"scantime": "Время сканирования",
|
"scantime": "Время сканирования",
|
||||||
"scan range": "Дальность",
|
|
||||||
"max angle": "Макс. угол",
|
|
||||||
"shield": "Щит",
|
"shield": "Щит",
|
||||||
"armour": "Броня",
|
|
||||||
"shieldboost": "Усиление щитов",
|
"shieldboost": "Усиление щитов",
|
||||||
"shieldreinforcement": "Усилитель щита",
|
"shieldreinforcement": "Усилитель щита",
|
||||||
"shotspeed": "Скорость выстрела",
|
"shotspeed": "Скорость выстрела",
|
||||||
"shotdmg": "Урон за выстрел(DPS)",
|
|
||||||
"spinup": "Время раскрутки",
|
"spinup": "Время раскрутки",
|
||||||
"syscap": "Ресурс систем",
|
"syscap": "Ресурс систем",
|
||||||
"sysrate": "Перезарядка систем",
|
"sysrate": "Перезарядка систем",
|
||||||
@@ -291,12 +234,9 @@
|
|||||||
"explosive": "Взрывч.",
|
"explosive": "Взрывч.",
|
||||||
"kinetic": "Механич.",
|
"kinetic": "Механич.",
|
||||||
"thermal": "Тепл.",
|
"thermal": "Тепл.",
|
||||||
"caustic": "Едкий",
|
|
||||||
"generator": "Генератор",
|
"generator": "Генератор",
|
||||||
"boosters": "Усилители",
|
"boosters": "Усилители",
|
||||||
"cells": "Ячейки",
|
"cells": "Ячейки",
|
||||||
"shield addition": "Добавления к щиту",
|
|
||||||
"jump addition": "Добавления к прыжку",
|
|
||||||
"bulkheads": "Переборки",
|
"bulkheads": "Переборки",
|
||||||
"reinforcement": "Усилители",
|
"reinforcement": "Усилители",
|
||||||
"power and costs": "Энергия и стоимость",
|
"power and costs": "Энергия и стоимость",
|
||||||
@@ -310,7 +250,7 @@
|
|||||||
"damage to opponent's shields": "Урон щиту противника",
|
"damage to opponent's shields": "Урон щиту противника",
|
||||||
"damage to opponent's hull": "Урон корпусу противника",
|
"damage to opponent's hull": "Урон корпусу противника",
|
||||||
"offence": "Нападение",
|
"offence": "Нападение",
|
||||||
"defence": "Защита",
|
"defence": "Оборона",
|
||||||
"shield metrics": "Данные щита",
|
"shield metrics": "Данные щита",
|
||||||
"raw shield strength": "Чистая мощность щита",
|
"raw shield strength": "Чистая мощность щита",
|
||||||
"shield sources": "Ресурсы щита",
|
"shield sources": "Ресурсы щита",
|
||||||
@@ -325,70 +265,28 @@
|
|||||||
"defence metrics": "Данные обороны",
|
"defence metrics": "Данные обороны",
|
||||||
"fuel carried": "Топливо на борту",
|
"fuel carried": "Топливо на борту",
|
||||||
"cargo carried": "Груз на борту",
|
"cargo carried": "Груз на борту",
|
||||||
"ship control": "Управление кораблем",
|
"ship control": "Управление кораблём",
|
||||||
"opponent": "Противник",
|
"opponent": "Противник",
|
||||||
"opponent's shields": "Щит противника",
|
"opponent's shields": "Щит противника",
|
||||||
"opponent's armour": "Броня противника",
|
"opponent's armour": "Броня противника",
|
||||||
"overall damage": "общий урон",
|
|
||||||
"overall": "общий",
|
|
||||||
"shield damage sources": "источники урона по щиту",
|
"shield damage sources": "источники урона по щиту",
|
||||||
"armour damage sources": "источники урона по броне",
|
"armour damage sources": "источники урона по броне",
|
||||||
"never": "Никогда",
|
"never": "Никогда",
|
||||||
"stock": "базовый",
|
"stock": "базовый",
|
||||||
"boost": "форсаж",
|
"boost": "форсаж",
|
||||||
"tab_defence": "защита",
|
|
||||||
"federation rank 1": "Рекрут",
|
|
||||||
"federation rank 2": "Кадет",
|
|
||||||
"federation rank 3": "Гардемарин",
|
|
||||||
"federation rank 4": "Старшина",
|
|
||||||
"federation rank 5": "Главный старшина",
|
|
||||||
"federation rank 6": "Уорент-офицер",
|
|
||||||
"federation rank 7": "Энсин",
|
|
||||||
"federation rank 8": "Лейтенант",
|
|
||||||
"federation rank 9": "Капитан-лейтенант",
|
|
||||||
"federation rank 10": "Начальник гарнизона",
|
|
||||||
"federation rank 11": "Командир корабля",
|
|
||||||
"federation rank 12": "Контр-адмирал",
|
|
||||||
"federation rank 13": "Вице-адмирал",
|
|
||||||
"federation rank 14": "Адмирал",
|
|
||||||
"federation rank required": "Минимальный ранг федерации для покупки",
|
|
||||||
"empire rank 1": "Чужак",
|
|
||||||
"empire rank 2": "Крепостной",
|
|
||||||
"empire rank 3": "Мастер",
|
|
||||||
"empire rank 4": "Оруженосец",
|
|
||||||
"empire rank 5": "Рыцарь",
|
|
||||||
"empire rank 6": "Лорд",
|
|
||||||
"empire rank 7": "Барон",
|
|
||||||
"empire rank 8": "Виконт",
|
|
||||||
"empire rank 9": "Граф",
|
|
||||||
"empire rank 10": "Эрл",
|
|
||||||
"empire rank 11": "Маркиз",
|
|
||||||
"empire rank 12": "Герцог",
|
|
||||||
"empire rank 13": "Принц",
|
|
||||||
"empire rank 14": "Король",
|
|
||||||
"empire rank required": "Минимальный ранг империи для покупки",
|
|
||||||
"kg": "кг",
|
|
||||||
"kg/s": "кг/с",
|
|
||||||
"km": "км",
|
|
||||||
"m": "м",
|
|
||||||
"MJ": "МДж",
|
|
||||||
"MW": "МВт",
|
|
||||||
"T": "т",
|
|
||||||
"°/s": "°/с",
|
|
||||||
"/s": "/с",
|
"/s": "/с",
|
||||||
"/min": "/мин",
|
|
||||||
"m/s": "м/с",
|
"m/s": "м/с",
|
||||||
"Ls": "Св.с",
|
"Ls": "Св.сек",
|
||||||
"LY": "Св.лет",
|
"LY": "Св.лет",
|
||||||
"CR": "КР.",
|
"CR": "кр.",
|
||||||
"S": "М",
|
"S": "M",
|
||||||
"M": "С",
|
"M": "С",
|
||||||
"L": "Б",
|
"L": "б",
|
||||||
"H": "О",
|
"H": "O",
|
||||||
"U": "В",
|
"U": "B",
|
||||||
"small": "Малый",
|
"small": "Малый",
|
||||||
"medium": "Средний",
|
"medium": "Средний",
|
||||||
"large": "Большой",
|
"large": "большой",
|
||||||
"alpha": "Альфа",
|
"alpha": "Альфа",
|
||||||
"beta": "Бета",
|
"beta": "Бета",
|
||||||
"standard": "Стандартный",
|
"standard": "Стандартный",
|
||||||
@@ -406,24 +304,23 @@
|
|||||||
"full tank": "Полный бак",
|
"full tank": "Полный бак",
|
||||||
"internal compartments": "внутренние отсеки",
|
"internal compartments": "внутренние отсеки",
|
||||||
"jump range": "Дальность прыжка",
|
"jump range": "Дальность прыжка",
|
||||||
"mass lock factor": "Коэффициент гравитационного захвата",
|
"mass lock factor": "Масс. блок",
|
||||||
"max mass": "Максимальная масса",
|
"max mass": "Максимальная масса",
|
||||||
"minimum mass": "Минимальная масса",
|
|
||||||
"optimal mass": "Оптимальная масса",
|
|
||||||
"net cost": "разница в цене",
|
"net cost": "разница в цене",
|
||||||
"none created": "не создано",
|
"none created": "не создано",
|
||||||
"refuel time": "Время дозаправки",
|
"refuel time": "Время дозаправки",
|
||||||
"retrofit from": "модификация от",
|
"retrofit from": "модификация от",
|
||||||
"T-Load": "Тепл.",
|
"T-Load": "Тепл.",
|
||||||
"utility mounts": "Вспомогательное оборудование",
|
"utility mounts": "Вспомогательное оборудование",
|
||||||
"about": "О сайте",
|
"about": "О ...",
|
||||||
"action": "Действие",
|
"action": "Действие",
|
||||||
"added": "Добавлено",
|
"added": "Добавлено",
|
||||||
|
"armour": "Броня",
|
||||||
"available": "доступно",
|
"available": "доступно",
|
||||||
"backup": "Резервная копия",
|
"backup": "Резервная копия",
|
||||||
"bins": "контейнеры",
|
"bins": "контейнеры",
|
||||||
"build": "сборка",
|
"build": "cборка",
|
||||||
"builds": "сборки",
|
"builds": "cборки",
|
||||||
"buy": "купить",
|
"buy": "купить",
|
||||||
"cancel": "отменить",
|
"cancel": "отменить",
|
||||||
"cargo": "Груз",
|
"cargo": "Груз",
|
||||||
@@ -439,21 +336,21 @@
|
|||||||
"deployed": "Открыты",
|
"deployed": "Открыты",
|
||||||
"disabled": "Отключено",
|
"disabled": "Отключено",
|
||||||
"discount": "Скидка",
|
"discount": "Скидка",
|
||||||
"DPS": "УвС",
|
"DPS": "УВС",
|
||||||
"efficiency": "Эффективность",
|
"efficiency": "Эффективность",
|
||||||
"empty": "пусто",
|
"empty": "пусто",
|
||||||
"ENG": "ДВГ",
|
"ENG": "ДВИ",
|
||||||
"export": "Экспорт",
|
"export": "Экспорт",
|
||||||
"forum": "Форум",
|
"forum": "Форум",
|
||||||
"fuel": "Топл.",
|
"fuel": "Топливо",
|
||||||
"hardpoints": "Орудийные порты",
|
"hardpoints": "Орудийные порты",
|
||||||
"hull": "Корпус",
|
"hull": "Корпус",
|
||||||
"import": "импортировать ",
|
"import": "импортировать ",
|
||||||
"insurance": "Страховка",
|
"insurance": "Страховка",
|
||||||
"jumps": "Прыжков",
|
"jumps": "Прыжков",
|
||||||
"laden": "Груж",
|
"laden": "Груженый",
|
||||||
"language": "Язык",
|
"language": "Язык",
|
||||||
"maneuverability": "Манёвренность",
|
"maneuverability": "Маневренность",
|
||||||
"max": "Макс",
|
"max": "Макс",
|
||||||
"no": "Нет",
|
"no": "Нет",
|
||||||
"pen": "ПБ",
|
"pen": "ПБ",
|
||||||
@@ -464,15 +361,13 @@
|
|||||||
"rate": "скорость",
|
"rate": "скорость",
|
||||||
"rename": "Переименовать",
|
"rename": "Переименовать",
|
||||||
"repair": "Починка",
|
"repair": "Починка",
|
||||||
"ret": "Убр",
|
"ret": "Убр.",
|
||||||
"retracted": "Убрано",
|
"retracted": "Убрано",
|
||||||
"ROF": "В\/с",
|
"ROF": "В/сек",
|
||||||
"save": "Сохранить",
|
"save": "Сохранить",
|
||||||
"sell": "Продать",
|
"sell": "Продать",
|
||||||
"settings": "Настройки",
|
"settings": "Настройки",
|
||||||
"shields": "Щиты",
|
"shields": "Щиты",
|
||||||
"No Shield": "Нет щита",
|
|
||||||
"Never": "Никогда",
|
|
||||||
"ship": "Корабль",
|
"ship": "Корабль",
|
||||||
"ships": "Корабли",
|
"ships": "Корабли",
|
||||||
"shortened": "Укороченный",
|
"shortened": "Укороченный",
|
||||||
@@ -482,296 +377,8 @@
|
|||||||
"SYS": "СИС",
|
"SYS": "СИС",
|
||||||
"time": "Время",
|
"time": "Время",
|
||||||
"type": "Тип",
|
"type": "Тип",
|
||||||
"unladen": "Пуст",
|
"unladen": "Пустой",
|
||||||
"URL": "Ссылка",
|
"URL": "Ссылка",
|
||||||
"WEP": "ОРУ",
|
"WEP": "ОРУЖ",
|
||||||
"yes": "Да",
|
"yes": "Да"
|
||||||
"crew": "экипаж",
|
|
||||||
"jump": "прыж",
|
|
||||||
"pax": "псж",
|
|
||||||
"RST": "СБР",
|
|
||||||
"grade": "уровень",
|
|
||||||
"total laden": "всего груж",
|
|
||||||
"total unladen": "всего пуст",
|
|
||||||
"module": "модуль",
|
|
||||||
"announcements": "объявления",
|
|
||||||
"resistance": "сопротивление",
|
|
||||||
"Lightweight Alloy": "Лёгкие сплавы",
|
|
||||||
"base": "базовые",
|
|
||||||
"core module classes": "основные модули",
|
|
||||||
"Group highlighted ships": "Сгруппировать выделенные корабли",
|
|
||||||
"tooltips": "всплывающие подсказки",
|
|
||||||
"module resistances": "сопротивление модулей",
|
|
||||||
"power plant": "силовая установка",
|
|
||||||
"thrusters": "маневровые двигатели",
|
|
||||||
"frame shift drive": "рамочно-сместительный двигатель",
|
|
||||||
"life support": "система жизнеобеспечения",
|
|
||||||
"power distributor": "распределитель питания",
|
|
||||||
"sensors": "сенсоры",
|
|
||||||
"fuel tank": "топливный бак",
|
|
||||||
"resting heat (Beta)": "тепло покоя (бета)",
|
|
||||||
"hull hardness": "Прочность корпуса",
|
|
||||||
"weapon": "оружие",
|
|
||||||
"maximum speed": "максимальная скорость",
|
|
||||||
"maximum range": "максимальная дальность",
|
|
||||||
"shortlink": "короткая ссылка",
|
|
||||||
"guardian": "Стражи",
|
|
||||||
"engineers": "инженеры",
|
|
||||||
"component": "компонент",
|
|
||||||
"amount": "кол-во",
|
|
||||||
"core internal": "основное оборуднование",
|
|
||||||
"optional internal": "доп. оборудование",
|
|
||||||
"Heat Sink Launcher": "Теплоотводная катапульта",
|
|
||||||
"scanners": "сканеры",
|
|
||||||
"experimental": "экспериментальное",
|
|
||||||
"mining": "добыча ресурсов",
|
|
||||||
"lasers": "лазеры",
|
|
||||||
"ordnance": "артиллерия",
|
|
||||||
"projectiles": "с боеприпасами",
|
|
||||||
"hangars": "ангары",
|
|
||||||
"limpet controllers": "контроллеры дронов",
|
|
||||||
"passenger cabins": "каюты пассажиров",
|
|
||||||
"structural reinforcement": "усиление конструктива",
|
|
||||||
"flight assists": "помощь в полёте",
|
|
||||||
"modifications": "модификации",
|
|
||||||
"wep_reload": "перезарядка",
|
|
||||||
"optimal multiplier": "оптимальный усилитель",
|
|
||||||
"Cargo Hatch": "Грузовой люк",
|
|
||||||
"Chaff Launcher": "Разбрасыватель дипольных отражателей",
|
|
||||||
"Point Defence": "Точечная оборона",
|
|
||||||
"Electronic Countermeasure": "Радиоэлектронное подавление",
|
|
||||||
"Xeno Scanner": "Ксено-сканер",
|
|
||||||
"Shutdown Field Neutraliser": "Нейтрализатор отключающего поля",
|
|
||||||
"Disruptor": "«Диверсант»",
|
|
||||||
"Pacifier": "«Миротворец»",
|
|
||||||
"Advanced Plasma Accelerator": "Улучшенный ускоритель плазмы",
|
|
||||||
"Cytoscrambler": "«Дезинтегратор»",
|
|
||||||
"Retributor": "«Каратель»",
|
|
||||||
"Enforcer": "«Убийца»",
|
|
||||||
"Imperial Hammer": "«Имперский молот»",
|
|
||||||
"Rocket Propelled FSD Disruptor": "Ракетный FSD-разрушитель",
|
|
||||||
"Pack-Hound": "«Гончие»",
|
|
||||||
"Shock Mine Launcher": "Установщик шоковых мин",
|
|
||||||
"Mining Lance": "«Копьё шахтера»",
|
|
||||||
"Corrosion Resistant": "Коррозийно-устойчивый стеллаж",
|
|
||||||
"Standard Docking Computer": "Стандартный стыковочный компьютер",
|
|
||||||
"Advanced Docking Computer": "Улучшенный стыковочный компьютер",
|
|
||||||
"Detailed Surface Scanner": "Подробный сканер поверхности",
|
|
||||||
"Supercruise Assist": "Помощь в гиперкрейсерском режиме",
|
|
||||||
"Guardian Power Distributor": "Рапределитель питания Стражей",
|
|
||||||
"Enhanced Performance": "Усиленные маневровые двигатели",
|
|
||||||
"Guardian Hybrid Power Plant": "Гибридная силовая установка Стражей",
|
|
||||||
"Reinforced Alloy": "Укреплённые сплавы",
|
|
||||||
"Military Grade Composite": "Композит военного класса",
|
|
||||||
"Mirrored Surface Composite": "Композит с зеркальной поверхностью",
|
|
||||||
"Reactive Surface Composite": "Композит с реактивной поверхностью",
|
|
||||||
"Proto Light Alloys": "Опытные лёгкие сплавы",
|
|
||||||
"Ammo capacity": "Вместимость магазина",
|
|
||||||
"Lightweight": "Облегчённый",
|
|
||||||
"Reinforced": "Усиленный",
|
|
||||||
"Shielded": "Защищённый",
|
|
||||||
"Fast scan": "Быстрое сканирование",
|
|
||||||
"Long range": "Дальнего действия",
|
|
||||||
"Wide angle": "Широкоугольный",
|
|
||||||
"Blast resistant": "Взрывостойкий",
|
|
||||||
"Heavy duty": "Надёжный",
|
|
||||||
"Kinetic resistant": "Противокинетический",
|
|
||||||
"Resistance augmented": "С универсальной защитой",
|
|
||||||
"Thermal resistant": "Термостойкий",
|
|
||||||
"Thermo Block": "Блокировка тепла",
|
|
||||||
"Force Block": "Усиленная блокировка",
|
|
||||||
"Blast Block": "Блокировка взрыва",
|
|
||||||
"Flow Control": "Контроль интенсивности",
|
|
||||||
"Double Braced": "Двойная прочность",
|
|
||||||
"Super Capacitors": "Суперконденсаторы",
|
|
||||||
"Efficient": "Эффективный",
|
|
||||||
"Focused": "Точный",
|
|
||||||
"Overcharged": "Усиленный",
|
|
||||||
"Rapid fire": "Скорострельный",
|
|
||||||
"Short range": "Ближнего действия",
|
|
||||||
"Sturdy": "Прочный",
|
|
||||||
"Oversized": "Сверхразмер",
|
|
||||||
"Stripped Down": "Урезанный вариант",
|
|
||||||
"Phasing sequence": "Последовательность фазирования",
|
|
||||||
"Concordant sequence": "Последовательность координации",
|
|
||||||
"Scramble spectrum": "Отключающая сетка",
|
|
||||||
"Thermal shock": "Тепловой удар",
|
|
||||||
"Emissive munitions": "Эмиссионные припасы",
|
|
||||||
"Multi-servos": "Сервосистема",
|
|
||||||
"Inertial impact": "Инерционный удар",
|
|
||||||
"Thermal vent": "Теплоотдача",
|
|
||||||
"Regeneration sequence": "Последовательность восстановления",
|
|
||||||
"Thermal conduit": "Проводник тепла",
|
|
||||||
"High capacity": "Вместительный магазин",
|
|
||||||
"Incendiary rounds": "Зажигательные припасы",
|
|
||||||
"Auto loader": "Автоматическая система заряжения",
|
|
||||||
"Smart rounds": "Умные боеприпасы",
|
|
||||||
"Corrosive shell": "Разъедающие припасы",
|
|
||||||
"Force shell": "Усиленные снаряды",
|
|
||||||
"High yield shell": "Снаряд большой мощности",
|
|
||||||
"Dispersal field": "Рассеивающее поле",
|
|
||||||
"Thermal cascade": "Термический залп",
|
|
||||||
"Double shot": "Двойной выстрел",
|
|
||||||
"Dazzle shell": "Ослепляющие снаряды",
|
|
||||||
"Screening shell": "Заслоняющие снаряды",
|
|
||||||
"Drag munitions": "Замедляющие боеприпасы",
|
|
||||||
"Target lock breaker": "Генератор помех для системы захвата цели",
|
|
||||||
"Plasma Slug": "Плазменный рельсовый снаряд",
|
|
||||||
"Feedback Cascade": "Ответный запл",
|
|
||||||
"Super Penetrator": "Модуль сверхпробития",
|
|
||||||
"Overload munitions": "Вызывающие перезагрузку боеприпасы",
|
|
||||||
"Penetrator payload": "Бронебойные снаряды",
|
|
||||||
"Penetrator Payload": "Бронебойные снаряды",
|
|
||||||
"FSD interrupt": "Помеха для FSD",
|
|
||||||
"Penetrator Munitions": "Бронебойные боеголовки",
|
|
||||||
"Mass lock munition": "Боеприпасы с гравитационным захватом",
|
|
||||||
"Mass Lock Munition": "Боеприпасы с гравитационным захватом",
|
|
||||||
"Reverberating cascade": "Отражённый залп",
|
|
||||||
"Shift-lock canister": "Рамоблокирующая кассета",
|
|
||||||
"Ion disruptor": "Ионный дестабилизатор",
|
|
||||||
"Radiant Canister": "Светящаяся кассета",
|
|
||||||
"Expanded capture arc": "Расширенная дуга захвата",
|
|
||||||
"Enhanced low power": "Улучшенное энергосбережение",
|
|
||||||
"Fast Charge": "Быстрый заряд",
|
|
||||||
"Multi-weave": "Мультипрошивка",
|
|
||||||
"Hi-Cap": "Высокая ёмкость",
|
|
||||||
"Lo-draw": "Пониженное потребление",
|
|
||||||
"Rapid charge": "Быстрая зарядка",
|
|
||||||
"Specialised": "Адаптивный",
|
|
||||||
"Boss Cells": "Босс-ячейки",
|
|
||||||
"Recycling Cell": "Рециркуляционная ячейка",
|
|
||||||
"Reflective Plating": "Отражающая броня",
|
|
||||||
"Angled Plating": "Угловая броня",
|
|
||||||
"Layered Plating": "Многослойная броня",
|
|
||||||
"Deep Plating": "Утолщённая броня",
|
|
||||||
"Expanded Probe Scanning Radius": "Увеличение радиуса сканирования зондов",
|
|
||||||
"High charge capacity": "Высокоёмкий",
|
|
||||||
"Charge enhanced": "Быстрозаряжающийся",
|
|
||||||
"Engine focused": "Фокус на двигатель",
|
|
||||||
"System focused": "Фокус на систему",
|
|
||||||
"Weapon focused": "Фокус на орудия",
|
|
||||||
"Super Conduits": "Сверхпроводники",
|
|
||||||
"Cluster Capacitors": "Кассетные конденсаторы",
|
|
||||||
"Increased range": "Увеличенная дальность",
|
|
||||||
"Faster boot sequence": "Ускорение запуска",
|
|
||||||
"Deep Charge": "Заряд повышенной мощности",
|
|
||||||
"Thermal Spread": "Рассеивание тепла",
|
|
||||||
"Mass Manager": "Распределитель гравитации",
|
|
||||||
"Dirty": "«Грязная» донастройка",
|
|
||||||
"Clean": "«Чистая» донастройка",
|
|
||||||
"Drag Drives": "Ускорители",
|
|
||||||
"Drive Distributors": "Распределители тяги",
|
|
||||||
"Armoured": "Бронированный",
|
|
||||||
"Low emissions": "Малое излучение",
|
|
||||||
"Monstered": "Монстрация",
|
|
||||||
"roles": "роли",
|
|
||||||
"Maximize Jump Range": "Максимизировать дальность прыжка",
|
|
||||||
"Multi-purpose": "Многоцелевой",
|
|
||||||
"Combat": "Боец",
|
|
||||||
"Trader": "Торговец",
|
|
||||||
"Explorer": "Исследователь",
|
|
||||||
"Planetary Explorer": "Исследователь планет",
|
|
||||||
"Miner": "Старатель",
|
|
||||||
"Racer": "Гонщик",
|
|
||||||
|
|
||||||
"Aberrant Shield Pattern Analysis": "Анализ аномального поведения щита",
|
|
||||||
"Abnormal Compact Emissions Data": "Аномальные компактные данные об излучении",
|
|
||||||
"Aerogel": "Аэрогель",
|
|
||||||
"Adaptive Encryptors Capture": "Захват адаптивного шифровальщика",
|
|
||||||
"Anomalous Bulk Scan Data": "Аномальный массив данных сканирования",
|
|
||||||
"Anomalous FSD Telemetry": "Аномальная телеметрия FSD",
|
|
||||||
"Antimony": "Сурьма",
|
|
||||||
"Arsenic": "Мышьяк",
|
|
||||||
"Atypical Disrupted Wake Echoes": "Атипичное эхо поврежденного следа",
|
|
||||||
"Atypical Encryption Archives": "Нетипичные архивы шифрования",
|
|
||||||
"Basic Conductors": "Простые проводники",
|
|
||||||
"Bio-Mechanical Conduits": "Биомеханические энергопроводники",
|
|
||||||
"Biotech Conductors": "Биотехнические проводники",
|
|
||||||
"Boron": "Бор",
|
|
||||||
"Cadmium": "Кадмий",
|
|
||||||
"Carbon": "Углерод",
|
|
||||||
"Carbon Fibre Plating": "Углеволоконная броня",
|
|
||||||
"Chemical Distillery": "Оборудование для перегонки химикатов",
|
|
||||||
"Chemical Manipulators": "Манипуляторы для работы с химикатами",
|
|
||||||
"Chemical Processors": "Оборудование для химобработки",
|
|
||||||
"Chromium": "Хром",
|
|
||||||
"Classified Scan Databanks": "Засекреченные базы данных сканирования",
|
|
||||||
"Classified Scan Fragment": "Засекреченные фрагменты данных сканирования",
|
|
||||||
"Compound Shielding": "Многоступенчатая защита",
|
|
||||||
"Conductive Ceramics": "Проводящая керамика",
|
|
||||||
"Conductive Components": "Проводящие компоненты",
|
|
||||||
"Conductive Polymers": "Проводящие полимеры",
|
|
||||||
"Configurable Components": "Настраиваемые компоненты",
|
|
||||||
"Core Dynamics Composites": "Композиты Core Dynamics",
|
|
||||||
"Cracked Industrial Firmware": "Взломанные промышленные микропрограммы",
|
|
||||||
"Datamined Wake Exceptions": "Исключения из глубинного анализа данных следа",
|
|
||||||
"Decoded Emission Data": "Расшифрованные данные об излучении",
|
|
||||||
"Distorted Shield Cycle Recordings": "Повреждённые цикличные записи щита",
|
|
||||||
"Divergent Scan Data": "Неформатные данные сканирования",
|
|
||||||
"Eccentric Hyperspace Trajectories": "Аномальные траектории в гиперпространстве",
|
|
||||||
"Electrochemical Arrays": "Электрохимические массивы",
|
|
||||||
"Exceptional Scrambled Emission Data": "Исключительные зашифрованные данные об излучении",
|
|
||||||
"Exquisite Focus Crystals": "Отборные фокусировочные кристаллы",
|
|
||||||
"Flawed Focus Crystals": "Поврежденные фокусировочные кристаллы",
|
|
||||||
"Focus Crystals": "Фокусировочные кристаллы",
|
|
||||||
"Galvanising Alloys": "Сплавы для гальванизации",
|
|
||||||
"Germanium": "Германий",
|
|
||||||
"Grid Resistors": "Наборные резисторы",
|
|
||||||
"Heat Conduction Wiring": "Теплопроводящие провода",
|
|
||||||
"Heat Dispersion Plate": "Теплорассеивающая пластина",
|
|
||||||
"Heat Exchangers": "Теплообменные агрегаты",
|
|
||||||
"Heat Vanes": "Тепловые заслонки",
|
|
||||||
"High Density Composites": "Высокоплотностные композиты",
|
|
||||||
"Hybrid Capacitors": "Гибридные конденсаторы",
|
|
||||||
"Imperial Shielding": "Имперская защита",
|
|
||||||
"Improvised Components": "Кустарные компоненты",
|
|
||||||
"Inconsistent Shield Soak Analysis": "Неполный анализ поглощения щита",
|
|
||||||
"Iron": "Железо",
|
|
||||||
"Irregular Emission Data": "Нестандартные данные об излучении",
|
|
||||||
"Manganese": "Марганец",
|
|
||||||
"Mechanical Components": "Механические компоненты",
|
|
||||||
"Mechanical Equipment": "Механическое оборудование",
|
|
||||||
"Mechanical Scrap": "Механические отходы",
|
|
||||||
"Mercury": "Ртуть",
|
|
||||||
"Military Grade Alloys": "Сплавы военного назначения",
|
|
||||||
"Military Supercapacitors": "Военные суперконденсаторы",
|
|
||||||
"Modified Consumer Firmware": "Изменённые пользовательские микропрограммы",
|
|
||||||
"Modified Embedded Firmware": "Изменённые встроенные микропрограммы",
|
|
||||||
"Molybdenum": "Молибден",
|
|
||||||
"Nickel": "Никель",
|
|
||||||
"Niobium": "Ниобий",
|
|
||||||
"Open Symmetric Keys": "Открытые симметричные ключи",
|
|
||||||
"Pharmaceutical Isolators": "Фармацевтические изоляционные материалы",
|
|
||||||
"Phase Alloys": "Фазовые сплавы",
|
|
||||||
"Phosphorus": "Фосфор",
|
|
||||||
"Polymer Capacitors": "Полимерные конденсаторы",
|
|
||||||
"Precipitated Alloys": "Осаждённые сплавы",
|
|
||||||
"Proprietary Composites": "Патентованные композиты",
|
|
||||||
"Proto Heat Radiators": "Прототипы теплоизлучателей",
|
|
||||||
"Proto Radiolic Alloys": "Сплавы для изготовления зондов",
|
|
||||||
"Refined Focus Crystals": "Обработанные фокусировочные кристаллы",
|
|
||||||
"Ruthenium": "Рутений",
|
|
||||||
"Salvaged Alloys": "Захваченные сплавы",
|
|
||||||
"Security Firmware Patch": "Обновление для защитной микропрограммы",
|
|
||||||
"Selenium": "Селен",
|
|
||||||
"Shield Emitters": "Щитоизлучатели",
|
|
||||||
"Shielding Sensors": "Сенсоры системы экранирования",
|
|
||||||
"Specialised Legacy Firmware": "Специальные микропрограммы предыдущего поколения",
|
|
||||||
"Strange Wake Solutions": "Странные расчёты следа",
|
|
||||||
"Sulphur": "Сера",
|
|
||||||
"Tagged Encryption Codes": "Меченные шифровальные коды",
|
|
||||||
"Technetium": "Технеций",
|
|
||||||
"Tellurium": "Теллур",
|
|
||||||
"Thermic Alloys": "Термические сплавы",
|
|
||||||
"Tin": "Олово",
|
|
||||||
"Tungsten": "Вольфрам",
|
|
||||||
"Unexpected Emission Data": "Неожиданные данные об излучении",
|
|
||||||
"Unidentified Scan Archives": "Неопознанные архивы сканирования",
|
|
||||||
"Untypical Shield Scans": "Нетипичные данные сканирования щитов",
|
|
||||||
"Unusual Encrypted Files": "Особые зашифрованные файлы",
|
|
||||||
"Vanadium": "Ванадий",
|
|
||||||
"Worn Shield Emitters": "Изношенные щитоизлучатели",
|
|
||||||
"Yttrium": "Иттрий",
|
|
||||||
"Zinc": "Цинк",
|
|
||||||
"Zirconium": "Цирконий"
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
|
import '@babel/polyfill';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { render } from 'react-dom';
|
import { render } from 'react-dom';
|
||||||
import '../less/app.less';
|
import '../less/app.less';
|
||||||
import Coriolis from './Coriolis';
|
import Coriolis from './Coriolis';
|
||||||
|
// import TapEventPlugin from 'react/lib/TapEventPlugin';
|
||||||
|
// import EventPluginHub from 'react/lib/EventPluginHub';
|
||||||
|
|
||||||
|
// onTouchTap not ready for primetime yet, too many issues with preventing default
|
||||||
|
// EventPluginHub.injection.injectEventPluginsByName({ TapEventPlugin });
|
||||||
|
|
||||||
render(<Coriolis />, document.getElementById('coriolis'));
|
render(<Coriolis />, document.getElementById('coriolis'));
|
||||||
|
|||||||
@@ -33,27 +33,24 @@ export default class AboutPage extends Page {
|
|||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
This is now the only active version of the Coriolis project. The original author has handed over the maintenance of the project to the {' '}
|
This is a clone of the Coriolis project, whose original author is
|
||||||
|
currently unable to maintain it. This clone is maintained by the{' '}
|
||||||
<a href="http://edcd.github.io/">EDCD community</a>.
|
<a href="http://edcd.github.io/">EDCD community</a>.
|
||||||
</p>
|
</p>
|
||||||
<h3>Expectations</h3>
|
|
||||||
<p>
|
<p>
|
||||||
Although every attempt is made to update the data as soon as possible, following the release of new modules and ships, there may be a delay, of up-to a few days, before the data is available. Wherever possible, the current maintainers aim to keep this delay to a minimum. Please be aware that the project maintainers are volunteers and have real lives to attend to, so please be patient. If you would like to help with the maintenance of the project, please see the link to the EDCD Discord Server below, where you can get involved.
|
To recover your builds, go to{' '}
|
||||||
|
<a href="https://coriolis.io/" target="_blank">
|
||||||
|
https://coriolis.io/
|
||||||
|
</a>
|
||||||
|
, backup your builds (Settings / Backup), copy the text, return here
|
||||||
|
and import (Settings / Import).
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
There are, some missing modules from the time where the project was essentially not being maintained. These modules are gradually being added to the Coriolis database as and when the maintainers have the time to do so.
|
The Coriolis project was inspired by{' '}
|
||||||
</p>
|
<a href="http://www.edshipyard.com/" target="_blank">
|
||||||
<p>
|
E:D Shipyard
|
||||||
Please check the {' '} <a href="https://github.com/EDCD/coriolis/issues/" target="_blank" >Github Issues List</a> for any specific modules you cannot find and see if there is an open request for them. If not, please feel free to open a new issue, however, please note that there is an existing issue open for the addition of pre-engineered modules, so please do not open a new issue for these.
|
</a>{' '}
|
||||||
</p>
|
and, of course,{' '}
|
||||||
<h3>Donations</h3>
|
|
||||||
<p>
|
|
||||||
If you would like to donate to the project, in order to help with the costs of hosting and maintainence, please see the link to the {' '}
|
|
||||||
<a href="https://github.com/Brighter-Applications/coriolis" target="_blank">Current Maintainers version of the Git Repository</a> and use the 'Sponsor' button at the top of the page.
|
|
||||||
</p>
|
|
||||||
<h3>History</h3>
|
|
||||||
<p>
|
|
||||||
The Coriolis project was inspired by 'E:D Shipyard' (Now Defunct) and, of course,{' '}
|
|
||||||
<a href="http://www.elitedangerous.com" target="_blank">
|
<a href="http://www.elitedangerous.com" target="_blank">
|
||||||
Elite Dangerous
|
Elite Dangerous
|
||||||
</a>
|
</a>
|
||||||
@@ -97,6 +94,39 @@ export default class AboutPage extends Page {
|
|||||||
</a>
|
</a>
|
||||||
.
|
.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<h3>Supporting Coriolis</h3>
|
||||||
|
<p>
|
||||||
|
Coriolis is an open source project, and I work on it in my free time.
|
||||||
|
I have set up a patreon at{' '}
|
||||||
|
<a href="https://www.patreon.com/coriolis_elite">
|
||||||
|
patreon.com/coriolis_elite
|
||||||
|
</a>
|
||||||
|
, which will be used to keep Coriolis up to date and the servers
|
||||||
|
running.
|
||||||
|
</p>
|
||||||
|
<form
|
||||||
|
action="https://www.paypal.com/cgi-bin/webscr"
|
||||||
|
method="post"
|
||||||
|
target="_top"
|
||||||
|
>
|
||||||
|
<input type="hidden" name="cmd" value="_s-xclick" />
|
||||||
|
<input type="hidden" name="hosted_button_id" value="SJBKT2SWEEU68" />
|
||||||
|
<input
|
||||||
|
type="image"
|
||||||
|
src="https://www.paypalobjects.com/en_AU/i/btn/btn_donate_SM.gif"
|
||||||
|
border="0"
|
||||||
|
name="submit"
|
||||||
|
alt="PayPal – The safer, easier way to pay online!"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
alt=""
|
||||||
|
border="0"
|
||||||
|
src="https://www.paypalobjects.com/en_AU/i/scr/pixel.gif"
|
||||||
|
width="1"
|
||||||
|
height="1"
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -33,7 +32,7 @@ export default class ErrorDetails extends React.Component {
|
|||||||
<div style={{ marginTop: '2em' }}>
|
<div style={{ marginTop: '2em' }}>
|
||||||
<div><span className='warning'>Browser:</span> {window.navigator.userAgent}</div>
|
<div><span className='warning'>Browser:</span> {window.navigator.userAgent}</div>
|
||||||
<div><span className='warning'>Path:</span> {this.context.route.canonicalPath}</div>
|
<div><span className='warning'>Path:</span> {this.context.route.canonicalPath}</div>
|
||||||
<div><span className='warning'>Error:</span> {ed["error"] || 'Unknown'}</div>
|
<div><span className='warning'>Error:</span> {error.type || 'Unknown'}</div>
|
||||||
<div className='warning'>Details:</div>
|
<div className='warning'>Details:</div>
|
||||||
<div><pre>{typeof ed == 'object' ? Object.keys(ed).map((e) => `${e}: ${ed[e]}\n`) : ed}</pre></div>
|
<div><pre>{typeof ed == 'object' ? Object.keys(ed).map((e) => `${e}: ${ed[e]}\n`) : ed}</pre></div>
|
||||||
</div>
|
</div>
|
||||||
@@ -42,67 +41,15 @@ export default class ErrorDetails extends React.Component {
|
|||||||
|
|
||||||
const importerror = ed && ed.scriptUrl && ed.scriptUrl.indexOf('/import') != -1;
|
const importerror = ed && ed.scriptUrl && ed.scriptUrl.indexOf('/import') != -1;
|
||||||
|
|
||||||
if (ed['error'].match(/URL Length/i)) {
|
|
||||||
return <div className='error'>
|
return <div className='error'>
|
||||||
<h1>Jameson, we have a problem..</h1>
|
<h1>Jameson, we have a problem..</h1>
|
||||||
<h1><small>{error.message}</small></h1>
|
<h1><small>{error.message}</small></h1>
|
||||||
It looks as though you've encountered a URL Length issue for your browser.
|
<br/>
|
||||||
|
{importerror ? <div>If you are attempting to import a ship from EDDI or EDMC and are seeing a 'Z_BUF_ERROR' it means that the URL has not been provided correctly. This is a common problem when using Microsoft Internet Explorer or Microsoft Edge, and you should use another browser instead.</div> : null }
|
||||||
<br /><br />
|
|
||||||
|
|
||||||
This is a known issue with Internet Explorer and Edge, as well as Google Chrome, all of which only support 2083 characters and your URL is:
|
|
||||||
|
|
||||||
<br /><br />
|
|
||||||
|
|
||||||
{ed["error"]} characters.
|
|
||||||
|
|
||||||
<br /><br />
|
|
||||||
|
|
||||||
Please try using another browser first, before reporting an issue, such as Firefox which supports 65,536 characters or Safari, which supports 80,000 characters.
|
|
||||||
|
|
||||||
<br /><br />
|
|
||||||
|
|
||||||
Don't copy the URL from Explorer, Edge or Chrome, as they will have truncated it and the data string will be incorrect. You'll need to change your default browser settings, so that when you click the link, it opens in the browser you want to use.
|
|
||||||
<br /><br />
|
|
||||||
If you're already using Firefox, which supports up to 65,536 characters or Safari, which supports up to 80,000 characters, please see the data output below.
|
|
||||||
<br/><br />
|
|
||||||
<h3>Data Output</h3>
|
|
||||||
{content}
|
|
||||||
</div>;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return <div className='error'>
|
|
||||||
<h1>Jameson, we have a problem..</h1>
|
|
||||||
<h1><small>{error.message}</small></h1>
|
|
||||||
Import Error handling has been improved, but still isn't perfect.
|
|
||||||
|
|
||||||
<br /><br />
|
|
||||||
|
|
||||||
If you're seeing this page, we may have failed to handle the errors in your import correctly. Please check the common import failures list and then the data output below, specifically the 'scriptUrl:' section if it's there and then if you feel confident enough, please check the github issues page linked below and see if there is a similar issue already logged. If not, please create a new issue with the data below. If you're not confident, please ask for help on the Coriolis Channel of the EDCD Discord server.
|
|
||||||
|
|
||||||
<br /><br />
|
|
||||||
<h3>Common Import Failures</h3>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
Previously, most failures were a result of missing modules in Coriolis, although this is rarer now since the import system was improved. If you're using a module in game and you know it isn't in Coriolis, this could be the problem, please check the data output section below.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
Incorrect import strings generated by third party apps do still occur, please check the data output below and if the import was from something like EDMC, please check you're using the latest version.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
You've hit a 'maximum URL Length' for your browser. This is a known issue with Internet Explorer and Edge, as well as Google Chrome, all of which only support 2083 characters. Please try using another browser first, before reporting an issue, such as Firefox which supports 65,536 characters or Safari, which supports 80,000 characters. Don't copy the URL from Explorer, Edge or Chrome, as they will have truncated it and the data string will be incorrect. You'll need to change your default browser settings, so that when you click the link, it opens in the browser you want to use.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
{importerror ? <div>If you are attempting to import a ship from EDDI or EDMC and are seeing a 'Z_BUF_ERROR' it means that the URL has not been understood correctly by the browser. This is a common problem when using Microsoft Internet Explorer or Microsoft Edge, and you should use another browser instead.</div> : null }
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<br/>
|
<br/>
|
||||||
<div>Please note that this site uses Google Analytics to track performance and usage. If you are blocking cookies, for example using Ghostery, please disable blocking for this site and try again.</div>
|
<div>Please note that this site uses Google Analytics to track performance and usage. If you are blocking cookies, for example using Ghostery, please disable blocking for this site and try again.</div>
|
||||||
<br/>
|
<br/>
|
||||||
<h3>Data Output</h3>
|
|
||||||
{content}
|
{content}
|
||||||
</div>;
|
</div>;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -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,
|
||||||
@@ -62,14 +61,6 @@ export default class Page extends React.Component {
|
|||||||
*/
|
*/
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
document.title = this.state.title || 'Coriolis';
|
document.title = this.state.title || 'Coriolis';
|
||||||
try {
|
|
||||||
(window.adsbygoogle = window.adsbygoogle || []).push({
|
|
||||||
google_ad_client: "ca-pub-3709458261881414",
|
|
||||||
enable_page_level_ads: true
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,5 +83,4 @@ export default class Page extends React.Component {
|
|||||||
}
|
}
|
||||||
return this.renderPage();
|
return this.renderPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,102 +1,80 @@
|
|||||||
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 { 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 summary = {
|
let summary = {
|
||||||
|
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,
|
manufacturer: ship.readMeta('manufacturer'),
|
||||||
|
masslock: ship.readProp('masslock'),
|
||||||
maxCargo: 0,
|
maxCargo: 0,
|
||||||
maxPassengers: 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,
|
retailCost: ship.readMeta('retailCost'),
|
||||||
|
speed: ship.readProp('speed'),
|
||||||
|
standard: [
|
||||||
|
'powerplant',
|
||||||
|
'mainengines',
|
||||||
|
'frameshiftdrive',
|
||||||
|
'lifesupport',
|
||||||
|
'powerdistributor',
|
||||||
|
'radar',
|
||||||
|
'fueltank'
|
||||||
|
].map(k => coreSizes[k]),
|
||||||
agility:
|
agility:
|
||||||
shipData.properties.pitch +
|
ship.readProp('pitch') +
|
||||||
shipData.properties.yaw +
|
ship.readProp('yaw') +
|
||||||
shipData.properties.roll
|
ship.readProp('roll')
|
||||||
};
|
};
|
||||||
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
|
ship.getInternals(undefined, true).forEach(internal => {
|
||||||
|
summary.int[internal.getSize()]++;
|
||||||
|
summary.intCount++;
|
||||||
|
});
|
||||||
|
// ship.optimizeMass({ pd: '1D' }); // Optimize Mass with 1D PD for maximum possible jump range
|
||||||
|
summary.maxJumpRange = -1; // ship.unladenRange; // Record Jump Range
|
||||||
|
|
||||||
// Best thrusters
|
// Best thrusters
|
||||||
let th;
|
// let th;
|
||||||
if (ship.standard[1].maxClass === 3) {
|
// if (ship.standard[1].maxClass === 3) {
|
||||||
th = 'tz';
|
// th = 'tz';
|
||||||
} else if (ship.standard[1].maxClass === 2) {
|
// } else if (ship.standard[1].maxClass === 2) {
|
||||||
th = 'u0';
|
// th = 'u0';
|
||||||
} else {
|
// } else {
|
||||||
th = ship.standard[1].maxClass + 'A';
|
// th = ship.standard[1].maxClass + 'A';
|
||||||
}
|
// }
|
||||||
|
|
||||||
ship.optimizeMass({ th, fsd: '2D', ft: '1C' }); // Optmize mass with Max Thrusters
|
// ship.optimizeMass({ th, fsd: '2D', ft: '1C' }); // Optmize mass with Max Thrusters
|
||||||
summary.topSpeed = ship.topSpeed;
|
summary.topSpeed = -1; // ship.topSpeed;
|
||||||
summary.topBoost = ship.topBoost;
|
summary.topBoost = -1; // ship.topBoost;
|
||||||
summary.baseArmour = ship.armour;
|
|
||||||
|
|
||||||
return summary;
|
return summary;
|
||||||
}
|
}
|
||||||
@@ -117,14 +95,14 @@ 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: {},
|
compare: {},
|
||||||
@@ -157,7 +135,7 @@ export default class ShipyardPage extends Page {
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_toggleGroupCompared() {
|
_toggleGroupCompared() {
|
||||||
this.setState({groupCompared: !this.state.groupCompared})
|
this.setState({ groupCompared: !this.state.groupCompared });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -205,6 +183,7 @@ export default class ShipyardPage extends Page {
|
|||||||
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
|
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
|
||||||
onClick={() => this._toggleCompare(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>
|
||||||
@@ -254,7 +233,6 @@ 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, compare, groupCompared } = this.state;
|
||||||
let sortShips = (predicate, index) =>
|
let sortShips = (predicate, index) =>
|
||||||
this._sortShips.bind(this, predicate, index);
|
this._sortShips.bind(this, predicate, index);
|
||||||
@@ -298,7 +276,7 @@ export default class ShipyardPage extends Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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;
|
||||||
@@ -334,7 +312,7 @@ export default class ShipyardPage extends Page {
|
|||||||
onClick={() => this._toggleCompare(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>
|
||||||
);
|
);
|
||||||
@@ -342,16 +320,16 @@ export default class ShipyardPage extends Page {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page" style={{fontSize: sizeRatio + 'em'}}>
|
<div className="page" style={{ fontSize: sizeRatio + 'em' }}>
|
||||||
<div className="content-wrapper">
|
<div className="content-wrapper">
|
||||||
<div className="shipyard-table-wrapper">
|
<div className="shipyard-table-wrapper">
|
||||||
<table style={{width: '12em', position: 'absolute', zIndex: 1}} className="shipyard-table">
|
<table style={{ width: '12em', position: 'absolute', zIndex: 1 }} className="shipyard-table">
|
||||||
<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>
|
||||||
@@ -367,6 +345,13 @@ export default class ShipyardPage extends Page {
|
|||||||
<table style={{ marginLeft: 'calc(12em - 1px)', zIndex: 0 }} className="shipyard-table">
|
<table style={{ marginLeft: 'calc(12em - 1px)', zIndex: 0 }} className="shipyard-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr className="main">
|
<tr className="main">
|
||||||
|
<th
|
||||||
|
rowSpan={3}
|
||||||
|
className="sortable"
|
||||||
|
onClick={sortShips('manufacturer')}
|
||||||
|
>
|
||||||
|
{translate('manufacturer')}
|
||||||
|
</th>
|
||||||
<th> </th>
|
<th> </th>
|
||||||
<th
|
<th
|
||||||
rowSpan={3}
|
rowSpan={3}
|
||||||
@@ -544,7 +529,7 @@ export default class ShipyardPage extends Page {
|
|||||||
</th>
|
</th>
|
||||||
<th
|
<th
|
||||||
className="sortable"
|
className="sortable"
|
||||||
onMouseEnter={termtip.bind(null, 'power distributor')}
|
onMouseEnter={termtip.bind(null, 'power distriubtor')}
|
||||||
onMouseLeave={hide}
|
onMouseLeave={hide}
|
||||||
onClick={sortShips('standard', 4)}
|
onClick={sortShips('standard', 4)}
|
||||||
>
|
>
|
||||||
@@ -615,7 +600,7 @@ export default class ShipyardPage extends Page {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="table-tools" >
|
<div className="table-tools" >
|
||||||
<label><input type="checkbox" checked={this.state.groupCompared} onClick={() => this._toggleGroupCompared()}/>{translate('Group highlighted ships')}</label>
|
<label><input type="checkbox" checked={this.state.groupCompared} onClick={() => this._toggleGroupCompared()}/>Group highlighted ships</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ export function jumpRange(mass, fsd, fuel, ship) {
|
|||||||
const fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass;
|
const fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass;
|
||||||
let jumpAddition = 0;
|
let jumpAddition = 0;
|
||||||
if (ship) {
|
if (ship) {
|
||||||
mass += ship.reserveFuelCapacity || 0;
|
|
||||||
for (const module of ship.internal) {
|
for (const module of ship.internal) {
|
||||||
if (module && module.m && module.m.grp === 'gfsb' && ship.getSlotStatus(module) == 3) {
|
if (module && module.m && module.m.grp === 'gfsb' && ship.getSlotStatus(module) == 3) {
|
||||||
jumpAddition += module.m.getJumpBoost();
|
jumpAddition += module.m.getJumpBoost();
|
||||||
@@ -341,30 +340,63 @@ export function shieldMetrics(ship, sys) {
|
|||||||
const maxSysResistance = this.sysResistance(4);
|
const maxSysResistance = this.sysResistance(4);
|
||||||
|
|
||||||
let shield = {};
|
let shield = {};
|
||||||
|
const dimReturnLine = (res) => 1 - (1 - res) * 0.7;
|
||||||
|
|
||||||
const shieldGeneratorSlot = ship.findInternalByGroup('sg');
|
const shieldGeneratorSlot = ship.findInternalByGroup('sg');
|
||||||
if (shieldGeneratorSlot && shieldGeneratorSlot.enabled && shieldGeneratorSlot.m) {
|
if (shieldGeneratorSlot && shieldGeneratorSlot.enabled && shieldGeneratorSlot.m) {
|
||||||
const shieldGenerator = shieldGeneratorSlot.m;
|
const shieldGenerator = shieldGeneratorSlot.m;
|
||||||
|
let res = {
|
||||||
|
kin: shieldGenerator.kinres,
|
||||||
|
therm: shieldGenerator.thermres,
|
||||||
|
expl: shieldGenerator.explres
|
||||||
|
};
|
||||||
// Boosters
|
// Boosters
|
||||||
let boost = 1;
|
let boost = 1;
|
||||||
let boosterExplDmg = 1;
|
let boosterExplDmg = 1;
|
||||||
let boosterKinDmg = 1;
|
let boosterKinDmg = 1;
|
||||||
let boosterThermDmg = 1;
|
let boosterThermDmg = 1;
|
||||||
|
// const explDim = dimReturnLine(shieldGenerator.explres);
|
||||||
|
// const thermDim = dimReturnLine(shieldGenerator.thermres);
|
||||||
|
// const kinDim = dimReturnLine(shieldGenerator.kinres);
|
||||||
for (let slot of ship.hardpoints) {
|
for (let slot of ship.hardpoints) {
|
||||||
if (slot.enabled && slot.m && slot.m.grp == 'sb') {
|
if (slot.enabled && slot.m && slot.m.grp == 'sb') {
|
||||||
boost += slot.m.getShieldBoost();
|
boost += slot.m.getShieldBoost();
|
||||||
|
res.expl += slot.m.getExplosiveResistance();
|
||||||
|
res.kin += slot.m.getKineticResistance();
|
||||||
|
res.therm += slot.m.getThermalResistance();
|
||||||
boosterExplDmg = boosterExplDmg * (1 - slot.m.getExplosiveResistance());
|
boosterExplDmg = boosterExplDmg * (1 - slot.m.getExplosiveResistance());
|
||||||
boosterKinDmg = boosterKinDmg * (1 - slot.m.getKineticResistance());
|
boosterKinDmg = boosterKinDmg * (1 - slot.m.getKineticResistance());
|
||||||
boosterThermDmg = boosterThermDmg * (1 - slot.m.getThermalResistance());
|
boosterThermDmg = boosterThermDmg * (1 - slot.m.getThermalResistance());
|
||||||
}
|
}
|
||||||
|
if (slot.m && slot.m.grp == 'gsrp') {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Calculate diminishing returns for boosters
|
// Calculate diminishing returns for boosters
|
||||||
// Diminishing returns not currently in-game
|
// Diminishing returns not currently in-game
|
||||||
// boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5);
|
// boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5);
|
||||||
|
|
||||||
|
|
||||||
// Remove base shield generator strength
|
// Remove base shield generator strength
|
||||||
boost -= 1;
|
boost -= 1;
|
||||||
|
|
||||||
|
// if (res.expl > explDim) {
|
||||||
|
// const overage = (res.expl - explDim) * 0.5;
|
||||||
|
// res.expl = explDim + overage;
|
||||||
|
// boosterExplDmg = explDim + overage;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (res.therm > thermDim) {
|
||||||
|
// const overage = (res.therm - thermDim) * 0.5;
|
||||||
|
// res.therm = thermDim + overage;
|
||||||
|
// boosterThermDmg = thermDim + overage;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (res.kin > kinDim) {
|
||||||
|
// const overage = (res.kin - kinDim) * 0.5;
|
||||||
|
// res.kin = kinDim + overage;
|
||||||
|
// boosterKinDmg = kinDim + overage;
|
||||||
|
// }
|
||||||
let shieldAddition = 0;
|
let shieldAddition = 0;
|
||||||
if (ship) {
|
if (ship) {
|
||||||
for (const module of ship.internal) {
|
for (const module of ship.internal) {
|
||||||
@@ -383,8 +415,7 @@ export function shieldMetrics(ship, sys) {
|
|||||||
|
|
||||||
// Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes
|
// Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes
|
||||||
// 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration
|
// 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration
|
||||||
let capacitorDrain = (shieldGenerator.getBrokenRegenerationRate() * shieldGenerator.getDistDraw()) - sysRechargeRate;
|
let capacitorDrain = (shieldGenerator.getBrokenRegenerationRate() * 0.6) - sysRechargeRate;
|
||||||
|
|
||||||
let capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain;
|
let capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain;
|
||||||
|
|
||||||
let recover = 16;
|
let recover = 16;
|
||||||
@@ -400,7 +431,7 @@ export function shieldMetrics(ship, sys) {
|
|||||||
recover = Math.Infinity;
|
recover = Math.Infinity;
|
||||||
} else {
|
} else {
|
||||||
// Recover remaining shields at the rate of the power distributor's recharge
|
// Recover remaining shields at the rate of the power distributor's recharge
|
||||||
recover += remainingShieldToRecover / (sysRechargeRate / shieldGenerator.getDistDraw());
|
recover += remainingShieldToRecover / (sysRechargeRate / 0.6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,7 +440,7 @@ export function shieldMetrics(ship, sys) {
|
|||||||
|
|
||||||
// Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes
|
// Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes
|
||||||
// 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration
|
// 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration
|
||||||
capacitorDrain = (shieldGenerator.getRegenerationRate() * shieldGenerator.getDistDraw()) - sysRechargeRate;
|
capacitorDrain = (shieldGenerator.getRegenerationRate() * 0.6) - sysRechargeRate;
|
||||||
capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain;
|
capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain;
|
||||||
|
|
||||||
let recharge = 0;
|
let recharge = 0;
|
||||||
@@ -425,7 +456,7 @@ export function shieldMetrics(ship, sys) {
|
|||||||
recharge = Math.Inf;
|
recharge = Math.Inf;
|
||||||
} else {
|
} else {
|
||||||
// Recharge remaining shields at the rate of the power distributor's recharge
|
// Recharge remaining shields at the rate of the power distributor's recharge
|
||||||
recharge += remainingShieldToRecharge / (sysRechargeRate / shieldGenerator.getDistDraw());
|
recharge += remainingShieldToRecharge / (sysRechargeRate / 0.6);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -467,7 +498,7 @@ export function shieldMetrics(ship, sys) {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
let sgExplosiveDmg = 1 - shieldGenerator.getExplosiveResistance();
|
let sgExplosiveDmg = 1 - shieldGenerator.getExplosiveResistance();
|
||||||
let sgSbExplosiveDmg = diminishingReturnsShields(sgExplosiveDmg, sgExplosiveDmg * boosterExplDmg);
|
let sgSbExplosiveDmg = diminishDamageMult(sgExplosiveDmg * 0.7, (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg);
|
||||||
/** @type {ShieldDamageMults} */
|
/** @type {ShieldDamageMults} */
|
||||||
shield.explosive = {
|
shield.explosive = {
|
||||||
generator: sgExplosiveDmg,
|
generator: sgExplosiveDmg,
|
||||||
@@ -479,7 +510,7 @@ export function shieldMetrics(ship, sys) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let sgKineticDmg = 1 - shieldGenerator.getKineticResistance();
|
let sgKineticDmg = 1 - shieldGenerator.getKineticResistance();
|
||||||
let sgSbKineticDmg = diminishingReturnsShields(sgKineticDmg, sgKineticDmg * boosterKinDmg);
|
let sgSbKineticDmg = diminishDamageMult(sgKineticDmg * 0.7, (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg);
|
||||||
/** @type {ShieldDamageMults} */
|
/** @type {ShieldDamageMults} */
|
||||||
shield.kinetic = {
|
shield.kinetic = {
|
||||||
generator: sgKineticDmg,
|
generator: sgKineticDmg,
|
||||||
@@ -491,7 +522,7 @@ export function shieldMetrics(ship, sys) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let sgThermalDmg = 1 - shieldGenerator.getThermalResistance();
|
let sgThermalDmg = 1 - shieldGenerator.getThermalResistance();
|
||||||
let sgSbThermalDmg = diminishingReturnsShields(sgThermalDmg , sgThermalDmg * boosterThermDmg);
|
let sgSbThermalDmg = diminishDamageMult(sgThermalDmg * 0.7, (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg);
|
||||||
/** @type {ShieldDamageMults} */
|
/** @type {ShieldDamageMults} */
|
||||||
shield.thermal = {
|
shield.thermal = {
|
||||||
generator: sgThermalDmg,
|
generator: sgThermalDmg,
|
||||||
@@ -531,20 +562,29 @@ export function armourMetrics(ship) {
|
|||||||
let moduleArmour = 0;
|
let moduleArmour = 0;
|
||||||
let moduleProtection = 1;
|
let moduleProtection = 1;
|
||||||
const bulkheads = ship.bulkheads.m;
|
const bulkheads = ship.bulkheads.m;
|
||||||
let hullExplDmgs = [];
|
let hullExplDmg = 1;
|
||||||
let hullKinDmgs = [];
|
let hullKinDmg = 1;
|
||||||
let hullThermDmgs = [];
|
let hullThermDmg = 1;
|
||||||
let hullCausDmgs = [];
|
let hullCausDmg = 1;
|
||||||
|
// const dimReturnLine = (res) => 1 - (1 - res) * 0.7;
|
||||||
|
// let res = {
|
||||||
|
// kin: 0,
|
||||||
|
// therm: 0,
|
||||||
|
// expl: 0
|
||||||
|
// };
|
||||||
// Armour from HRPs and module armour from MRPs
|
// Armour from HRPs and module armour from MRPs
|
||||||
for (let slot of ship.internal) {
|
for (let slot of ship.internal) {
|
||||||
if (slot.m && slot.enabled && (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;
|
||||||
hullExplDmgs.push(1 - slot.m.getExplosiveResistance());
|
// res.expl += slot.m.getExplosiveResistance();
|
||||||
hullKinDmgs.push(1 - slot.m.getKineticResistance());
|
// res.kin += slot.m.getKineticResistance();
|
||||||
hullThermDmgs.push(1 - slot.m.getThermalResistance());
|
// res.therm += slot.m.getThermalResistance();
|
||||||
hullCausDmgs.push(1 - slot.m.getCausticResistance());
|
hullExplDmg = hullExplDmg * (1 - slot.m.getExplosiveResistance());
|
||||||
|
hullKinDmg = hullKinDmg * (1 - slot.m.getKineticResistance());
|
||||||
|
hullThermDmg = hullThermDmg * (1 - slot.m.getThermalResistance());
|
||||||
|
hullCausDmg = hullCausDmg * (1 - slot.m.getCausticResistance());
|
||||||
}
|
}
|
||||||
if (slot.m && slot.enabled && (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();
|
||||||
@@ -553,6 +593,27 @@ export function armourMetrics(ship) {
|
|||||||
}
|
}
|
||||||
moduleProtection = 1 - moduleProtection;
|
moduleProtection = 1 - moduleProtection;
|
||||||
|
|
||||||
|
// const explDim = dimReturnLine(bulkheads.explres);
|
||||||
|
// const thermDim = dimReturnLine(bulkheads.thermres);
|
||||||
|
// const kinDim = dimReturnLine(bulkheads.kinres);
|
||||||
|
// if (res.expl > explDim) {
|
||||||
|
// const overage = (res.expl - explDim) * 0.5;
|
||||||
|
// res.expl = explDim + overage;
|
||||||
|
// hullExplDmg = explDim + overage;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (res.therm > thermDim) {
|
||||||
|
// const overage = (res.therm - thermDim) * 0.5;
|
||||||
|
// res.therm = thermDim + overage;
|
||||||
|
// hullThermDmg = thermDim + overage;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (res.kin > kinDim) {
|
||||||
|
// const overage = (res.kin - kinDim) * 0.5;
|
||||||
|
// res.kin = kinDim + overage;
|
||||||
|
// hullKinDmg = kinDim + overage;
|
||||||
|
// }
|
||||||
|
|
||||||
const armour = {
|
const armour = {
|
||||||
bulkheads: armourBulkheads,
|
bulkheads: armourBulkheads,
|
||||||
reinforcement: armourReinforcement,
|
reinforcement: armourReinforcement,
|
||||||
@@ -569,8 +630,8 @@ export function armourMetrics(ship) {
|
|||||||
total: 1
|
total: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
let armourExplDmg = 1 - ship.bulkheads.m.getExplosiveResistance();
|
let armourExplDmg = diminishDamageMult(0.7, 1 - ship.bulkheads.m.getExplosiveResistance());
|
||||||
let armourReinforcedExplDmg = diminishingReturnsArmour(armourExplDmg, ...hullExplDmgs);
|
let armourReinforcedExplDmg = diminishDamageMult(0.7, (1 - ship.bulkheads.m.getExplosiveResistance()) * hullExplDmg);
|
||||||
armour.explosive = {
|
armour.explosive = {
|
||||||
bulkheads: armourExplDmg,
|
bulkheads: armourExplDmg,
|
||||||
reinforcement: armourReinforcedExplDmg / armourExplDmg,
|
reinforcement: armourReinforcedExplDmg / armourExplDmg,
|
||||||
@@ -578,8 +639,8 @@ export function armourMetrics(ship) {
|
|||||||
res: 1 - armourReinforcedExplDmg
|
res: 1 - armourReinforcedExplDmg
|
||||||
};
|
};
|
||||||
|
|
||||||
let armourKinDmg = 1 - ship.bulkheads.m.getKineticResistance();
|
let armourKinDmg = diminishDamageMult(0.7, 1 - ship.bulkheads.m.getKineticResistance());
|
||||||
let armourReinforcedKinDmg = diminishingReturnsArmour(armourKinDmg, ...hullKinDmgs);
|
let armourReinforcedKinDmg = diminishDamageMult(0.7, (1 - ship.bulkheads.m.getKineticResistance()) * hullKinDmg);
|
||||||
armour.kinetic = {
|
armour.kinetic = {
|
||||||
bulkheads: armourKinDmg,
|
bulkheads: armourKinDmg,
|
||||||
reinforcement: armourReinforcedKinDmg / armourKinDmg,
|
reinforcement: armourReinforcedKinDmg / armourKinDmg,
|
||||||
@@ -587,8 +648,8 @@ export function armourMetrics(ship) {
|
|||||||
res: 1 - armourReinforcedKinDmg
|
res: 1 - armourReinforcedKinDmg
|
||||||
};
|
};
|
||||||
|
|
||||||
let armourThermDmg = 1 - ship.bulkheads.m.getThermalResistance();
|
let armourThermDmg = diminishDamageMult(0.7, 1 - ship.bulkheads.m.getThermalResistance());
|
||||||
let armourReinforcedThermDmg = diminishingReturnsArmour(armourThermDmg, ...hullThermDmgs);
|
let armourReinforcedThermDmg = diminishDamageMult(0.7, (1 - ship.bulkheads.m.getThermalResistance()) * hullThermDmg);
|
||||||
armour.thermal = {
|
armour.thermal = {
|
||||||
bulkheads: armourThermDmg,
|
bulkheads: armourThermDmg,
|
||||||
reinforcement: armourReinforcedThermDmg / armourThermDmg,
|
reinforcement: armourReinforcedThermDmg / armourThermDmg,
|
||||||
@@ -596,8 +657,8 @@ export function armourMetrics(ship) {
|
|||||||
res: 1 - armourReinforcedThermDmg
|
res: 1 - armourReinforcedThermDmg
|
||||||
};
|
};
|
||||||
|
|
||||||
let armourCausDmg = 1 - ship.bulkheads.m.getCausticResistance();
|
let armourCausDmg = diminishDamageMult(0.7, 1 - ship.bulkheads.m.getCausticResistance());
|
||||||
let armourReinforcedCausDmg = diminishingReturnsArmour(armourCausDmg, ...hullCausDmgs);
|
let armourReinforcedCausDmg = diminishDamageMult(0.7, (1 - ship.bulkheads.m.getCausticResistance()) * hullCausDmg);
|
||||||
armour.caustic = {
|
armour.caustic = {
|
||||||
bulkheads: armourCausDmg,
|
bulkheads: armourCausDmg,
|
||||||
reinforcement: armourReinforcedCausDmg / armourCausDmg,
|
reinforcement: armourReinforcedCausDmg / armourCausDmg,
|
||||||
@@ -843,14 +904,12 @@ export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour
|
|||||||
shields: {
|
shields: {
|
||||||
range: 1,
|
range: 1,
|
||||||
sys: opponentHasShields ? opponentShields.absolute.sys : 1,
|
sys: opponentHasShields ? opponentShields.absolute.sys : 1,
|
||||||
resistance: 1,
|
resistance: 1
|
||||||
dpe: 1
|
|
||||||
},
|
},
|
||||||
armour: {
|
armour: {
|
||||||
range: 1,
|
range: 1,
|
||||||
hardness: 1,
|
hardness: 1,
|
||||||
resistance: 1,
|
resistance: 1
|
||||||
dpe: 1
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -910,19 +969,11 @@ export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour
|
|||||||
weapon.damage.shields.total = weapon.damage.shields.absolute + weapon.damage.shields.explosive + weapon.damage.shields.kinetic + weapon.damage.shields.thermal;
|
weapon.damage.shields.total = weapon.damage.shields.absolute + weapon.damage.shields.explosive + weapon.damage.shields.kinetic + weapon.damage.shields.thermal;
|
||||||
weapon.damage.armour.total = weapon.damage.armour.absolute + weapon.damage.armour.explosive + weapon.damage.armour.kinetic + weapon.damage.armour.thermal;
|
weapon.damage.armour.total = weapon.damage.armour.absolute + weapon.damage.armour.explosive + weapon.damage.armour.kinetic + weapon.damage.armour.thermal;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
weapon.effectiveness.shields.resistance *= shieldsResistance;
|
weapon.effectiveness.shields.resistance *= shieldsResistance;
|
||||||
weapon.effectiveness.armour.resistance *= armourResistance;
|
weapon.effectiveness.armour.resistance *= armourResistance;
|
||||||
|
|
||||||
|
|
||||||
weapon.effectiveness.shields.total = weapon.effectiveness.shields.range * weapon.effectiveness.shields.sys * weapon.effectiveness.shields.resistance;
|
weapon.effectiveness.shields.total = weapon.effectiveness.shields.range * weapon.effectiveness.shields.sys * weapon.effectiveness.shields.resistance;
|
||||||
weapon.effectiveness.armour.total = weapon.effectiveness.armour.range * weapon.effectiveness.armour.resistance * weapon.effectiveness.armour.hardness;
|
weapon.effectiveness.armour.total = weapon.effectiveness.armour.range * weapon.effectiveness.armour.resistance * weapon.effectiveness.armour.hardness;
|
||||||
|
|
||||||
weapon.effectiveness.shields.dpe = weapon.damage.shields.total / m.getEps() / m.getSustainedFactor();
|
|
||||||
weapon.effectiveness.armour.dpe = weapon.damage.armour.total / m.getEps() / m.getSustainedFactor();
|
|
||||||
|
|
||||||
|
|
||||||
return weapon;
|
return weapon;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -986,50 +1037,15 @@ export function timeToDeplete(amount, dps, eps, capacity, recharge) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether diminishing returns should be applied to shield damage
|
* Applies diminishing returns to resistances.
|
||||||
* multipliers and does so if necessary.
|
* @param {number} diminishFrom The base resistance up to which no diminishing returns are applied.
|
||||||
* @param {number} shieldMult Damage multiplier of shield generator
|
* @param {number} damageMult Resistance as damage multiplier
|
||||||
* @param {number} combinedMult Damage multiplier of shields and shield boosters
|
* @returns {number} Actual damage multiplier
|
||||||
* @returns {number} Overall damage multiplier
|
|
||||||
*/
|
*/
|
||||||
export function diminishingReturnsShields(shieldMult, combinedMult) {
|
export function diminishDamageMult(diminishFrom, damageMult) {
|
||||||
let max = shieldMult * 0.7;
|
if (damageMult > diminishFrom) {
|
||||||
if (combinedMult < max) {
|
return damageMult;
|
||||||
return mapIntoDiminishingRange(max / 2, max, combinedMult);
|
|
||||||
} else {
|
} else {
|
||||||
return combinedMult;
|
return (diminishFrom / 2) + 0.5 * damageMult;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether diminishing returns should be applied to armour damage
|
|
||||||
* multipliers and does so if necessary.
|
|
||||||
* @param {...any} mults Damage multipliers of alloys and hull reinforcement
|
|
||||||
* packages
|
|
||||||
* @returns {number} Overall damage multiplier
|
|
||||||
*/
|
|
||||||
export function diminishingReturnsArmour(...mults) {
|
|
||||||
let max = Math.min(0.7, ...mults);
|
|
||||||
let combined = mults.reduce((aggr, v) => aggr * v);
|
|
||||||
let diminished = mapIntoDiminishingRange(0.35, max, combined);
|
|
||||||
if (diminished < 0.7) {
|
|
||||||
return diminished;
|
|
||||||
} else {
|
|
||||||
return combined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Applies diminishing returns to a damage multiplier. Effictively, the range
|
|
||||||
* [`0`, `max`]` is mapped into the range [`min`, `max`] for the value `now`.
|
|
||||||
* It can also happen, that `now` is outside of the range [`min`, `max`], then
|
|
||||||
* `now` is actually improved, i.e. enlarged.
|
|
||||||
* @param {number} min Best theoretical damage multiplier
|
|
||||||
* @param {number} max Damage multiplier from which diminishing returns start to
|
|
||||||
* be applied
|
|
||||||
* @param {number} now The current damage multiplier
|
|
||||||
* @returns {number} Remapped damage multiplier
|
|
||||||
*/
|
|
||||||
export function mapIntoDiminishingRange(min, max, now) {
|
|
||||||
return min + (max - min) * (now / max);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ export const ModuleGroupToName = {
|
|||||||
pd: 'Power Distributor',
|
pd: 'Power Distributor',
|
||||||
s: 'Sensors',
|
s: 'Sensors',
|
||||||
ft: 'Fuel Tank',
|
ft: 'Fuel Tank',
|
||||||
|
pas: 'Planetary Approach Suite',
|
||||||
|
|
||||||
// Internal
|
// Internal
|
||||||
fs: 'Fuel Scoop',
|
fs: 'Fuel Scoop',
|
||||||
@@ -57,9 +58,6 @@ export const ModuleGroupToName = {
|
|||||||
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',
|
sua: 'Supercruise Assist',
|
||||||
mlc: "Multi Limpet Controller",
|
|
||||||
rpl: "Repair Limpet Controller",
|
|
||||||
pas: 'Planetary Approach Suite',
|
|
||||||
|
|
||||||
// Hard Points
|
// Hard Points
|
||||||
bl: 'Beam Laser',
|
bl: 'Beam Laser',
|
||||||
@@ -77,15 +75,11 @@ export const ModuleGroupToName = {
|
|||||||
nl: 'Mine Launcher',
|
nl: 'Mine Launcher',
|
||||||
ml: 'Mining Laser',
|
ml: 'Mining Laser',
|
||||||
mr: 'Missile Rack',
|
mr: 'Missile Rack',
|
||||||
amr: 'Missile Rack (Advanced)',
|
|
||||||
axmr: 'AX Missile Rack',
|
axmr: 'AX Missile Rack',
|
||||||
axmre: 'AX Missile Rack (Enhanced)',
|
|
||||||
pa: 'Plasma Accelerator',
|
pa: 'Plasma Accelerator',
|
||||||
po: 'Point Defence',
|
po: 'Point Defence',
|
||||||
mc: 'Multi-cannon',
|
mc: 'Multi-cannon',
|
||||||
advmc: 'Multi-cannon (Advanced)',
|
|
||||||
axmc: 'AX Multi-cannon',
|
axmc: 'AX Multi-cannon',
|
||||||
axmce: 'AX Multi-cannon (Enhanced)',
|
|
||||||
pl: 'Pulse Laser',
|
pl: 'Pulse Laser',
|
||||||
rg: 'Rail Gun',
|
rg: 'Rail Gun',
|
||||||
sb: 'Shield Booster',
|
sb: 'Shield Booster',
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export default class Module {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a value for a given modification ID
|
* Set a value for a given modification ID
|
||||||
* @param {String} name The name of the modification
|
* @param {Number} name The name of the modification
|
||||||
* @param {object} value The value of the modification. If it is a numeric value then it should be an integer scaled so that -2.34% == -234
|
* @param {object} value The value of the modification. If it is a numeric value then it should be an integer scaled so that -2.34% == -234
|
||||||
* @param {Boolean} valueiswithspecial true if the value includes the special effect (when coming from a UI component)
|
* @param {Boolean} valueiswithspecial true if the value includes the special effect (when coming from a UI component)
|
||||||
*/
|
*/
|
||||||
@@ -81,13 +81,7 @@ export default class Module {
|
|||||||
if (!this.origVals) {
|
if (!this.origVals) {
|
||||||
this.origVals = {};
|
this.origVals = {};
|
||||||
}
|
}
|
||||||
if (!valueiswithspecial) {
|
if (valueiswithspecial && this.blueprint && this.blueprint.special) {
|
||||||
// Resistance modifiers scale with the base value.
|
|
||||||
if (name === 'kinres' || name === 'thermres' || name === 'causres' || name === 'explres') {
|
|
||||||
let baseValue = this.get(name, false);
|
|
||||||
value = (1 - baseValue) * value;
|
|
||||||
}
|
|
||||||
} else if (valueiswithspecial && this.blueprint && this.blueprint.special) {
|
|
||||||
// This module has a special effect, see if we need to alter the stored value
|
// This module has a special effect, see if we need to alter the stored value
|
||||||
const modifierActions = Modifications.modifierActions[this.blueprint.special.edname];
|
const modifierActions = Modifications.modifierActions[this.blueprint.special.edname];
|
||||||
if (modifierActions && modifierActions[name]) {
|
if (modifierActions && modifierActions[name]) {
|
||||||
@@ -119,7 +113,7 @@ export default class Module {
|
|||||||
/**
|
/**
|
||||||
* Helper to obtain a module's value.
|
* Helper to obtain a module's value.
|
||||||
* @param {String} name The name of the modifier to obtain
|
* @param {String} name The name of the modifier to obtain
|
||||||
* @param {Boolean} modified Whether to return the raw or modified value
|
* @param {Number} modified Whether to return the raw or modified value
|
||||||
* @return {Number} The value queried
|
* @return {Number} The value queried
|
||||||
*/
|
*/
|
||||||
get(name, modified = true) {
|
get(name, modified = true) {
|
||||||
@@ -439,15 +433,6 @@ export default class Module {
|
|||||||
return this.get('integrity', modified);
|
return this.get('integrity', modified);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the info of this module
|
|
||||||
* @param {Boolean} [modified=false] Whether to take modifications into account
|
|
||||||
* @return {String} the info of this module
|
|
||||||
*/
|
|
||||||
getInfo(modified = false) {
|
|
||||||
return (modified && this.getModValue('info')) || this.info;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the mass of this module
|
* Get the mass of this module
|
||||||
* @param {Boolean} [modified=true] Whether to take modifications into account
|
* @param {Boolean} [modified=true] Whether to take modifications into account
|
||||||
@@ -709,7 +694,7 @@ export default class Module {
|
|||||||
let result = 0;
|
let result = 0;
|
||||||
if (this['maxmass']) {
|
if (this['maxmass']) {
|
||||||
result = this['maxmass'];
|
result = this['maxmass'];
|
||||||
if (result && modified && !ModuleUtils.isShieldGenerator(this['grp'])) {
|
if (result && modified) {
|
||||||
let mult = this.getModValue('optmass') / 10000;
|
let mult = this.getModValue('optmass') / 10000;
|
||||||
if (mult) { result = result * (1 + mult); }
|
if (mult) { result = result * (1 + mult); }
|
||||||
}
|
}
|
||||||
@@ -927,9 +912,8 @@ export default class Module {
|
|||||||
const burst = this.get('burst', modified) || 1;
|
const burst = this.get('burst', modified) || 1;
|
||||||
const burstRoF = this.get('burstrof', modified) || 1;
|
const burstRoF = this.get('burstrof', modified) || 1;
|
||||||
const intRoF = this.get('rof', modified);
|
const intRoF = this.get('rof', modified);
|
||||||
const charge = this.get('charge', modified) || 0;
|
|
||||||
|
|
||||||
return burst / (((burst - 1) / burstRoF) + 1 / intRoF + charge);
|
return burst / (((burst - 1) / burstRoF) + 1 / intRoF);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -13,19 +13,6 @@ function filter(arr, maxClass, minClass, mass) {
|
|||||||
return arr.filter(m => m.class <= maxClass && m.class >= minClass && (m.maxmass === undefined || mass <= m.maxmass));
|
return arr.filter(m => m.class <= maxClass && m.class >= minClass && (m.maxmass === undefined || mass <= m.maxmass));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter SCO Modules to only return legal size.
|
|
||||||
* @param {Array} arr Array of available FSD modules.
|
|
||||||
* @param {number} maxSize Maximum allowable size for SCO modules.
|
|
||||||
* @return {Array} Subset of modules filtered based on legal size amd type.
|
|
||||||
*/
|
|
||||||
function sco_filter(arr, maxSize) {
|
|
||||||
return arr.filter(module => {
|
|
||||||
return !(module.hasOwnProperty('name') && module['name'] === "Frame Shift Drive (SCO)" && module['class'] < maxSize);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The available module set for a specific ship
|
* The available module set for a specific ship
|
||||||
*/
|
*/
|
||||||
@@ -54,7 +41,6 @@ export default class ModuleSet {
|
|||||||
|
|
||||||
this.standard[0] = filter(stnd.pp, maxStandardArr[0], 0, mass); // Power Plant
|
this.standard[0] = filter(stnd.pp, maxStandardArr[0], 0, mass); // Power Plant
|
||||||
this.standard[2] = filter(stnd.fsd, maxStandardArr[2], 0, mass); // FSD
|
this.standard[2] = filter(stnd.fsd, maxStandardArr[2], 0, mass); // FSD
|
||||||
this.standard[2] = sco_filter(this.standard[2], maxStandardArr[2]) // FSD - Filter SCO Modules
|
|
||||||
this.standard[4] = filter(stnd.pd, maxStandardArr[4], 0, mass); // Power Distributor
|
this.standard[4] = filter(stnd.pd, maxStandardArr[4], 0, mass); // Power Distributor
|
||||||
this.standard[6] = filter(stnd.ft, maxStandardArr[6], 0, mass); // Fuel Tank
|
this.standard[6] = filter(stnd.ft, maxStandardArr[6], 0, mass); // Fuel Tank
|
||||||
// Thrusters, filter modules by class only (to show full list of ratings for that class)
|
// Thrusters, filter modules by class only (to show full list of ratings for that class)
|
||||||
|
|||||||
@@ -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', 'dc', 'ews'];
|
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;
|
||||||
@@ -140,7 +140,7 @@ export default class Ship {
|
|||||||
*/
|
*/
|
||||||
canBoost(cargo, fuel) {
|
canBoost(cargo, fuel) {
|
||||||
return this.canThrust(cargo, fuel) && // Thrusters operational
|
return this.canThrust(cargo, fuel) && // Thrusters operational
|
||||||
this.standard[4].m.getEnginesCapacity() >= this.boostEnergy; // PD capacitor is sufficient for boost
|
this.standard[4].m.getEnginesCapacity() > this.boostEnergy; // PD capacitor is sufficient for boost
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -492,18 +492,25 @@ export default class Ship {
|
|||||||
* @param {Object} m The module to change
|
* @param {Object} m The module to change
|
||||||
* @param {Object} name The name of the modification to change
|
* @param {Object} name The name of the modification to change
|
||||||
* @param {Number} value The new value of the modification. The value of the modification is scaled to provide two decimal places of precision in an integer. For example 1.23% is stored as 123
|
* @param {Number} value The new value of the modification. The value of the modification is scaled to provide two decimal places of precision in an integer. For example 1.23% is stored as 123
|
||||||
* @param {boolean} isAbsolute True if value is an absolute value and not a modification value
|
* @param {bool} sentfromui True if this update was sent from the UI
|
||||||
|
* @param {bool} isAbsolute True if value is an absolute value and not a
|
||||||
|
* modification value
|
||||||
*/
|
*/
|
||||||
setModification(m, name, value, isAbsolute = false) {
|
setModification(m, name, value, sentfromui, isAbsolute) {
|
||||||
if (isNaN(value)) {
|
if (isNaN(value)) {
|
||||||
// Value passed is invalid; reset it to 0
|
// Value passed is invalid; reset it to 0
|
||||||
value = 0;
|
value = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAbsolute) {
|
if (isAbsolute) {
|
||||||
m.setPretty(name, value, isAbsolute);
|
m.setPretty(name, value, sentfromui);
|
||||||
} else {
|
} else {
|
||||||
m.setModValue(name, value, false);
|
// Resistance modifiers scale with the base value
|
||||||
|
if (name == 'kinres' || name == 'thermres' || name == 'causres' || name == 'explres') {
|
||||||
|
let baseValue = m.get(name, false);
|
||||||
|
value = (1 - baseValue) * value;
|
||||||
|
}
|
||||||
|
m.setModValue(name, value, sentfromui);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle special cases
|
// Handle special cases
|
||||||
@@ -536,7 +543,7 @@ export default class Ship {
|
|||||||
this.recalculateArmour();
|
this.recalculateArmour();
|
||||||
} else if (name === 'shieldreinforcement') {
|
} else if (name === 'shieldreinforcement') {
|
||||||
this.recalculateShieldCells();
|
this.recalculateShieldCells();
|
||||||
} else if (name === 'burst' || name === 'burstrof' || name === 'clip' || name === 'damage' || name === 'distdraw' || name === 'jitter' || name === 'piercing' || name === 'range' || name === 'reload' || name === 'rof' || name === 'thermload') {
|
} else if (name === 'burst' || name == 'burstrof' || name === 'clip' || name === 'damage' || name === 'distdraw' || name === 'jitter' || name === 'piercing' || name === 'range' || name === 'reload' || name === 'rof' || name === 'thermload') {
|
||||||
this.recalculateDps();
|
this.recalculateDps();
|
||||||
this.recalculateHps();
|
this.recalculateHps();
|
||||||
this.recalculateEps();
|
this.recalculateEps();
|
||||||
@@ -1210,11 +1217,12 @@ export default class Ship {
|
|||||||
.value();
|
.value();
|
||||||
|
|
||||||
// Update global stats
|
// Update global stats
|
||||||
this.unladenMass = unladenMass + fuelCapacity;
|
this.unladenMass = unladenMass;
|
||||||
this.cargoCapacity = cargoCapacity;
|
this.cargoCapacity = cargoCapacity;
|
||||||
this.fuelCapacity = fuelCapacity;
|
this.fuelCapacity = fuelCapacity;
|
||||||
this.passengerCapacity = passengerCapacity;
|
this.passengerCapacity = passengerCapacity;
|
||||||
this.ladenMass = unladenMass + fuelCapacity + cargoCapacity;
|
this.ladenMass = unladenMass + fuelCapacity + cargoCapacity;
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
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,
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -15,6 +15,7 @@ const LS_KEY_SIZE_RATIO = 'sizeRatio';
|
|||||||
const LS_KEY_TOOLTIPS = 'tooltips';
|
const LS_KEY_TOOLTIPS = 'tooltips';
|
||||||
const LS_KEY_MODULE_RESISTANCES = 'moduleResistances';
|
const LS_KEY_MODULE_RESISTANCES = 'moduleResistances';
|
||||||
const LS_KEY_ROLLS = 'matsPerGrade';
|
const LS_KEY_ROLLS = 'matsPerGrade';
|
||||||
|
const LS_KEY_ORBIS = 'orbis';
|
||||||
|
|
||||||
let LS;
|
let LS;
|
||||||
|
|
||||||
@@ -94,6 +95,7 @@ export class Persist extends EventEmitter {
|
|||||||
let buildJson = _get(LS_KEY_BUILDS);
|
let buildJson = _get(LS_KEY_BUILDS);
|
||||||
let comparisonJson = _get(LS_KEY_COMPARISONS);
|
let comparisonJson = _get(LS_KEY_COMPARISONS);
|
||||||
|
|
||||||
|
this.orbisCreds = _get(LS_KEY_ORBIS) || { email: '', password: '' };
|
||||||
this.onStorageChange = this.onStorageChange.bind(this);
|
this.onStorageChange = this.onStorageChange.bind(this);
|
||||||
this.langCode = _getString(LS_KEY_LANG) || 'en';
|
this.langCode = _getString(LS_KEY_LANG) || 'en';
|
||||||
this.insurance = insurance && Insurance[insurance.toLowerCase()] !== undefined ? insurance : 'standard';
|
this.insurance = insurance && Insurance[insurance.toLowerCase()] !== undefined ? insurance : 'standard';
|
||||||
@@ -108,9 +110,9 @@ export class Persist extends EventEmitter {
|
|||||||
this.matsPerGrade = matsPerGrade || {
|
this.matsPerGrade = matsPerGrade || {
|
||||||
1: 2,
|
1: 2,
|
||||||
2: 2,
|
2: 2,
|
||||||
3: 3,
|
3: 4,
|
||||||
4: 4,
|
4: 4,
|
||||||
5: 5
|
5: 10
|
||||||
};
|
};
|
||||||
this.cmdrName = cmdrName || { selected: '', cmdrs: [] };
|
this.cmdrName = cmdrName || { selected: '', cmdrs: [] };
|
||||||
this.tooltipsEnabled = tips === null ? true : tips;
|
this.tooltipsEnabled = tips === null ? true : tips;
|
||||||
@@ -167,6 +169,10 @@ export class Persist extends EventEmitter {
|
|||||||
this.matsPerGrade = JSON.parse(newValue);
|
this.matsPerGrade = JSON.parse(newValue);
|
||||||
this.emit('matsPerGrade', this.matsPerGrade);
|
this.emit('matsPerGrade', this.matsPerGrade);
|
||||||
break;
|
break;
|
||||||
|
case LS_KEY_ORBIS:
|
||||||
|
this.orbisCreds = JSON.parse(newValue);
|
||||||
|
this.emit('orbis', this.orbisCreds);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// On JSON.Parse Error - don't sync or do anything
|
// On JSON.Parse Error - don't sync or do anything
|
||||||
@@ -192,6 +198,24 @@ export class Persist extends EventEmitter {
|
|||||||
this.emit('language', langCode);
|
this.emit('language', langCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current orbis.zone credentials
|
||||||
|
* @return {String} language code
|
||||||
|
*/
|
||||||
|
getOrbisCreds() {
|
||||||
|
return this.orbisCreds;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update and save the orbis.zone credentials
|
||||||
|
* @param {Object} creds object with username and password properties.
|
||||||
|
*/
|
||||||
|
setOrbisCreds(creds) {
|
||||||
|
this.langCode = creds;
|
||||||
|
_put(LS_KEY_ORBIS, creds);
|
||||||
|
this.emit('orbis', creds);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show tooltips setting
|
* Show tooltips setting
|
||||||
* @param {boolean} show Optional - update setting
|
* @param {boolean} show Optional - update setting
|
||||||
|
|||||||
@@ -1,72 +1,43 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Modifications } from 'coriolis-data/dist';
|
import { Modifications } from 'coriolis-data/dist';
|
||||||
import { STATS_FORMATTING } from '../shipyard/StatsFormatting';
|
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 description = [];
|
const { formats, translate } = language;
|
||||||
const effects = [];
|
|
||||||
if (!blueprint || !blueprint.features) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
if (m) {
|
|
||||||
// We also add in any benefits from specials that aren't covered above
|
|
||||||
if (m.blueprint) {
|
|
||||||
if (specialName) {
|
|
||||||
if (Modifications.specials[specialName].description) {
|
|
||||||
description.push(
|
|
||||||
<div className={'success'} style={{ maxWidth: 350, padding: 5, marginBottom: 10 }}>
|
|
||||||
{Modifications.specials[specialName].description}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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>
|
||||||
{description}
|
|
||||||
<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>
|
||||||
@@ -74,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>
|
||||||
@@ -230,87 +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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Is the change as shown beneficial?
|
|
||||||
* @param {string} feature The name of the feature
|
|
||||||
* @param {number} value The value of the feature as percentage change
|
|
||||||
* @returns True if the value is beneficial
|
|
||||||
*/
|
|
||||||
export function isChangeValueBeneficial(feature, value) {
|
|
||||||
let changeHigherBetter = STATS_FORMATTING[feature].higherbetter;
|
|
||||||
if (changeHigherBetter === undefined) {
|
|
||||||
return isValueBeneficial(feature, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changeHigherBetter) {
|
|
||||||
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
|
||||||
@@ -318,141 +116,12 @@ export function isChangeValueBeneficial(feature, value) {
|
|||||||
* @returns {Object} The matching blueprint
|
* @returns {Object} The matching blueprint
|
||||||
*/
|
*/
|
||||||
export function getBlueprint(name, module) {
|
export function getBlueprint(name, module) {
|
||||||
// Special case for multi-cannons. Conflicting 'Weapon_Overcharged' Blueprints exist due to FD's naming conventions. If this blueprint is for a multi-cannon, we need to use the correct blueprint.
|
|
||||||
if (name === 'weapon_overcharged') {
|
|
||||||
if (module.symbol.match(/MultiCannon/i)) {
|
|
||||||
name = 'mc_overcharged';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (name === 'Weapon_Overcharged') {
|
|
||||||
if (module.symbol.match(/MultiCannon/i)) {
|
|
||||||
name = 'MC_Overcharged';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Start with a copy of the blueprint
|
// Start with a copy of the blueprint
|
||||||
const findMod = val => Object.keys(Modifications.blueprints).find(elem => elem.toString().toLowerCase().search(val.toString().toLowerCase().replace(/(OutfittingFieldType_|persecond)/igm, '')) >= 0);
|
const findMod = val => Object.keys(Modifications.blueprints).find(elem => elem.toString().toLowerCase().search(val.toString().toLowerCase().replace(/(OutfittingFieldType_|persecond)/igm, '')) >= 0);
|
||||||
const found = Modifications.blueprints[findMod(name)];
|
const found = Modifications.blueprints[findMod(name)];
|
||||||
if (!found || !found.fdname) {
|
if (!found || !found.fdname) {
|
||||||
console.error('Blueprint not found:', name);
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
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;
|
|
||||||
setQualityCB(m.blueprint, mult, (featureName, value) => ship.setModification(m, featureName, value));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the blueprint quality and fires a callback for each property affected.
|
|
||||||
* @param {Object} blueprint The ship for which to perform the modifications
|
|
||||||
* @param {Number} quality The quality to apply - float number 0 to 1.
|
|
||||||
* @param {Function} cb The Callback to run for each property. Function (featureName, value)
|
|
||||||
*/
|
|
||||||
export function setQualityCB(blueprint, quality, cb) {
|
|
||||||
// Pick given value as multiplier
|
|
||||||
const features = blueprint.grades[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]) * quality);
|
|
||||||
} else {
|
|
||||||
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * quality);
|
|
||||||
}
|
|
||||||
} 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]) * quality);
|
|
||||||
} else {
|
|
||||||
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * quality);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Modifications.modifications[featureName].type == 'percentage') {
|
|
||||||
value = value * 10000;
|
|
||||||
} else if (Modifications.modifications[featureName].type == 'numeric') {
|
|
||||||
value = value * 100;
|
|
||||||
}
|
|
||||||
|
|
||||||
cb(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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 (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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ export const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
|
|||||||
'BelugaLiner': 'beluga',
|
'BelugaLiner': 'beluga',
|
||||||
'CobraMkIII': 'cobra_mk_iii',
|
'CobraMkIII': 'cobra_mk_iii',
|
||||||
'CobraMkIV': 'cobra_mk_iv',
|
'CobraMkIV': 'cobra_mk_iv',
|
||||||
'CobraMkV': 'cobramkv',
|
|
||||||
'Corsair': 'imperial_corsair',
|
|
||||||
'Cutter': 'imperial_cutter',
|
'Cutter': 'imperial_cutter',
|
||||||
'DiamondBackXL': 'diamondback_explorer',
|
'DiamondBackXL': 'diamondback_explorer',
|
||||||
'DiamondBack': 'diamondback',
|
'DiamondBack': 'diamondback',
|
||||||
@@ -33,15 +31,12 @@ export const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
|
|||||||
'Independant_Trader': 'keelback',
|
'Independant_Trader': 'keelback',
|
||||||
'Krait_MkII': 'krait_mkii',
|
'Krait_MkII': 'krait_mkii',
|
||||||
'Mamba': 'mamba',
|
'Mamba': 'mamba',
|
||||||
'Mandalay': 'mandalay',
|
|
||||||
'Krait_Light': 'krait_phantom',
|
'Krait_Light': 'krait_phantom',
|
||||||
'Orca': 'orca',
|
'Orca': 'orca',
|
||||||
'Python': 'python',
|
'Python': 'python',
|
||||||
'Python_nx': 'python_nx',
|
|
||||||
'SideWinder': 'sidewinder',
|
'SideWinder': 'sidewinder',
|
||||||
'Type6': 'type_6_transporter',
|
'Type6': 'type_6_transporter',
|
||||||
'Type7': 'type_7_transport',
|
'Type7': 'type_7_transport',
|
||||||
'Type8': 'type_8_transport',
|
|
||||||
'Type9': 'type_9_heavy',
|
'Type9': 'type_9_heavy',
|
||||||
'Type9_Military': 'type_10_defender',
|
'Type9_Military': 'type_10_defender',
|
||||||
'TypeX': 'alliance_chieftain',
|
'TypeX': 'alliance_chieftain',
|
||||||
|
|||||||
@@ -4,23 +4,7 @@ import { Ships } from 'coriolis-data/dist';
|
|||||||
import Module from '../shipyard/Module';
|
import Module from '../shipyard/Module';
|
||||||
import { Modules } from 'coriolis-data/dist';
|
import { Modules } from 'coriolis-data/dist';
|
||||||
import { Modifications } from 'coriolis-data/dist';
|
import { Modifications } from 'coriolis-data/dist';
|
||||||
import { getBlueprint, setQualityCB } from './BlueprintFunctions';
|
import { getBlueprint } from './BlueprintFunctions';
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if an imported module is valid
|
|
||||||
* @param {Object} module the module to check
|
|
||||||
* @param {Object} moduleType the type of module to check
|
|
||||||
* @return {boolean} true if the module is valid
|
|
||||||
*/
|
|
||||||
function _isValidImportedModule(module, moduleType) {
|
|
||||||
// First of all, has the _moduleFromFdName function returned 'null'?
|
|
||||||
if (!module){
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Obtain a module given its FD Name
|
* Obtain a module given its FD Name
|
||||||
@@ -111,93 +95,52 @@ export function shipFromLoadoutJSON(json) {
|
|||||||
throw 'Unknown bulkheads "' + module.Item + '"';
|
throw 'Unknown bulkheads "' + module.Item + '"';
|
||||||
}
|
}
|
||||||
ship.bulkheads.enabled = true;
|
ship.bulkheads.enabled = true;
|
||||||
if (module.Engineering) _addModifications(ship.bulkheads.m, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
|
if (module.Engineering) _addModifications(ship.bulkheads.m, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
|
||||||
break;
|
break;
|
||||||
case 'powerplant':
|
case 'powerplant':
|
||||||
let powerplant = _moduleFromFdName(module.Item);
|
const powerplant = _moduleFromFdName(module.Item);
|
||||||
// Check the powerplant returned is valid
|
|
||||||
if (!_isValidImportedModule(powerplant, 'powerplant'))
|
|
||||||
{
|
|
||||||
powerplant = _moduleFromFdName('Int_Missing_Powerplant');
|
|
||||||
module.Engineering = null;
|
|
||||||
}
|
|
||||||
ship.use(ship.standard[0], powerplant, true);
|
ship.use(ship.standard[0], powerplant, true);
|
||||||
ship.standard[0].enabled = module.On;
|
ship.standard[0].enabled = module.On;
|
||||||
ship.standard[0].priority = module.Priority;
|
ship.standard[0].priority = module.Priority;
|
||||||
if (module.Engineering) _addModifications(powerplant, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
|
if (module.Engineering) _addModifications(powerplant, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
|
||||||
break;
|
break;
|
||||||
case 'mainengines':
|
case 'mainengines':
|
||||||
let thrusters = _moduleFromFdName(module.Item);
|
const thrusters = _moduleFromFdName(module.Item);
|
||||||
// Check the thrusters returned is valid
|
|
||||||
if (!_isValidImportedModule(thrusters, 'thrusters'))
|
|
||||||
{
|
|
||||||
thrusters = _moduleFromFdName('Int_Missing_Engine');
|
|
||||||
module.Engineering = null;
|
|
||||||
}
|
|
||||||
ship.use(ship.standard[1], thrusters, true);
|
ship.use(ship.standard[1], thrusters, true);
|
||||||
ship.standard[1].enabled = module.On;
|
ship.standard[1].enabled = module.On;
|
||||||
ship.standard[1].priority = module.Priority;
|
ship.standard[1].priority = module.Priority;
|
||||||
if (module.Engineering) _addModifications(thrusters, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
|
if (module.Engineering) _addModifications(thrusters, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
|
||||||
break;
|
break;
|
||||||
case 'frameshiftdrive':
|
case 'frameshiftdrive':
|
||||||
let frameshiftdrive = _moduleFromFdName(module.Item);
|
const frameshiftdrive = _moduleFromFdName(module.Item);
|
||||||
// Check the frameshiftdrive returned is valid
|
|
||||||
if (!_isValidImportedModule(frameshiftdrive, 'frameshiftdrive'))
|
|
||||||
{
|
|
||||||
frameshiftdrive = _moduleFromFdName('Int_Missing_Hyperdrive');
|
|
||||||
module.Engineering = null;
|
|
||||||
}
|
|
||||||
ship.use(ship.standard[2], frameshiftdrive, true);
|
ship.use(ship.standard[2], frameshiftdrive, true);
|
||||||
ship.standard[2].enabled = module.On;
|
ship.standard[2].enabled = module.On;
|
||||||
ship.standard[2].priority = module.Priority;
|
ship.standard[2].priority = module.Priority;
|
||||||
if (module.Engineering) _addModifications(frameshiftdrive, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
|
if (module.Engineering) _addModifications(frameshiftdrive, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
|
||||||
break;
|
break;
|
||||||
case 'lifesupport':
|
case 'lifesupport':
|
||||||
let lifesupport = _moduleFromFdName(module.Item);
|
const lifesupport = _moduleFromFdName(module.Item);
|
||||||
// Check the lifesupport returned is valid
|
|
||||||
if (!_isValidImportedModule(lifesupport, 'lifesupport'))
|
|
||||||
{
|
|
||||||
lifesupport = _moduleFromFdName('Int_Missing_LifeSupport');
|
|
||||||
module.Engineering = null;
|
|
||||||
}
|
|
||||||
ship.use(ship.standard[3], lifesupport, true);
|
ship.use(ship.standard[3], lifesupport, true);
|
||||||
ship.standard[3].enabled = module.On === true;
|
ship.standard[3].enabled = module.On === true;
|
||||||
ship.standard[3].priority = module.Priority;
|
ship.standard[3].priority = module.Priority;
|
||||||
if (module.Engineering) _addModifications(lifesupport, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
|
if (module.Engineering) _addModifications(lifesupport, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
|
||||||
break;
|
break;
|
||||||
case 'powerdistributor':
|
case 'powerdistributor':
|
||||||
let powerdistributor = _moduleFromFdName(module.Item);
|
const powerdistributor = _moduleFromFdName(module.Item);
|
||||||
// Check the powerdistributor returned is valid
|
|
||||||
if (!_isValidImportedModule(powerdistributor, 'powerdistributor'))
|
|
||||||
{
|
|
||||||
powerdistributor = _moduleFromFdName('Int_Missing_PowerDistributor');
|
|
||||||
module.Engineering = null;
|
|
||||||
}
|
|
||||||
ship.use(ship.standard[4], powerdistributor, true);
|
ship.use(ship.standard[4], powerdistributor, true);
|
||||||
ship.standard[4].enabled = module.On;
|
ship.standard[4].enabled = module.On;
|
||||||
ship.standard[4].priority = module.Priority;
|
ship.standard[4].priority = module.Priority;
|
||||||
if (module.Engineering) _addModifications(powerdistributor, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
|
if (module.Engineering) _addModifications(powerdistributor, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
|
||||||
break;
|
break;
|
||||||
case 'radar':
|
case 'radar':
|
||||||
let sensors = _moduleFromFdName(module.Item);
|
const sensors = _moduleFromFdName(module.Item);
|
||||||
// Check the sensors returned is valid
|
|
||||||
if (!_isValidImportedModule(sensors, 'sensors'))
|
|
||||||
{
|
|
||||||
sensors = _moduleFromFdName('Int_Missing_Sensors');
|
|
||||||
module.Engineering = null;
|
|
||||||
}
|
|
||||||
ship.use(ship.standard[5], sensors, true);
|
ship.use(ship.standard[5], sensors, true);
|
||||||
ship.standard[5].enabled = module.On;
|
ship.standard[5].enabled = module.On;
|
||||||
ship.standard[5].priority = module.Priority;
|
ship.standard[5].priority = module.Priority;
|
||||||
if (module.Engineering) _addModifications(sensors, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
|
if (module.Engineering) _addModifications(sensors, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
|
||||||
break;
|
break;
|
||||||
case 'fueltank':
|
case 'fueltank':
|
||||||
let fueltank = _moduleFromFdName(module.Item);
|
const fueltank = _moduleFromFdName(module.Item);
|
||||||
// Check the fueltank returned is valid
|
|
||||||
if (!_isValidImportedModule(fueltank, 'fueltank'))
|
|
||||||
{
|
|
||||||
fueltank = _moduleFromFdName('Int_Missing_FuelTank');
|
|
||||||
}
|
|
||||||
ship.use(ship.standard[6], fueltank, true);
|
ship.use(ship.standard[6], fueltank, true);
|
||||||
ship.standard[6].enabled = true;
|
ship.standard[6].enabled = true;
|
||||||
ship.standard[6].priority = 0;
|
ship.standard[6].priority = 0;
|
||||||
@@ -225,30 +168,15 @@ export function shipFromLoadoutJSON(json) {
|
|||||||
const hardpointSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === hardpointName.toLowerCase());
|
const hardpointSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === hardpointName.toLowerCase());
|
||||||
if (!hardpointSlot) {
|
if (!hardpointSlot) {
|
||||||
// This can happen with old imports that don't contain new hardpoints
|
// This can happen with old imports that don't contain new hardpoints
|
||||||
|
} else if (!hardpointSlot) {
|
||||||
|
// No module
|
||||||
} else {
|
} else {
|
||||||
hardpoint = _moduleFromFdName(hardpointSlot.Item);
|
hardpoint = _moduleFromFdName(hardpointSlot.Item);
|
||||||
// Check the hardpoint module returned is valid
|
|
||||||
if (!_isValidImportedModule(hardpoint, 'hardpoint')){
|
|
||||||
// Check if it's a Utility or Hardpoint
|
|
||||||
if (hardpointSlot.Slot.toLowerCase().search(/tiny/))
|
|
||||||
{
|
|
||||||
// Use the missing_hardpoint module 'Missing Hardpoint' which will inform the user that the module is missing
|
|
||||||
hardpoint = _moduleFromFdName('Hpt_Missing_Hardpoint');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Use the missing_hardpoint module 'Missing Utility' which will inform the user that the module is missing
|
|
||||||
hardpoint = _moduleFromFdName('Hpt_Missing_Utility');
|
|
||||||
}
|
|
||||||
ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true);
|
|
||||||
ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On;
|
|
||||||
ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority;
|
|
||||||
} else {
|
|
||||||
ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true);
|
ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true);
|
||||||
ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On;
|
ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On;
|
||||||
ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority;
|
ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority;
|
||||||
modsToAdd.push({ coriolisMod: hardpoint, json: hardpointSlot });
|
modsToAdd.push({ coriolisMod: hardpoint, json: hardpointSlot });
|
||||||
}
|
}
|
||||||
}
|
|
||||||
hardpointArrayNum++;
|
hardpointArrayNum++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -261,17 +189,13 @@ export function shipFromLoadoutJSON(json) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name == 'Military' : false;
|
const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name == 'Military' : false;
|
||||||
const isPlanetary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name == 'PlanetaryApproachSuite' : false;
|
|
||||||
|
|
||||||
// The internal slot might be a standard or a military slot, or a planetary slot. Military and Planetary slots have a different naming system
|
// The internal slot might be a standard or a military slot. Military slots have a different naming system
|
||||||
let internalSlot = null;
|
let internalSlot = null;
|
||||||
if (isMilitary) {
|
if (isMilitary) {
|
||||||
const internalName = 'Military0' + militarySlotNum;
|
const internalName = 'Military0' + militarySlotNum;
|
||||||
internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
|
internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
|
||||||
militarySlotNum++;
|
militarySlotNum++;
|
||||||
} else if (isPlanetary) {
|
|
||||||
const internalName = 'PlanetaryApproachSuite';
|
|
||||||
internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
|
|
||||||
} else {
|
} else {
|
||||||
// Slot numbers are not contiguous so handle skips.
|
// Slot numbers are not contiguous so handle skips.
|
||||||
for (; internalSlot === null && internalSlotNum < 99; internalSlotNum++) {
|
for (; internalSlot === null && internalSlotNum < 99; internalSlotNum++) {
|
||||||
@@ -290,28 +214,17 @@ export function shipFromLoadoutJSON(json) {
|
|||||||
// This can happen with old imports that don't contain new slots
|
// This can happen with old imports that don't contain new slots
|
||||||
} else {
|
} else {
|
||||||
const internalJson = internalSlot;
|
const internalJson = internalSlot;
|
||||||
let internal = _moduleFromFdName(internalJson.Item);
|
const internal = _moduleFromFdName(internalJson.Item);
|
||||||
// Check the internal module returned is valid
|
|
||||||
if (!_isValidImportedModule(internal, 'internal'))
|
|
||||||
{
|
|
||||||
internal = _moduleFromFdName('Int_Missing_Module');
|
|
||||||
ship.use(ship.internal[i], internal, true);
|
|
||||||
ship.internal[i].enabled = internalJson.On === true;
|
|
||||||
ship.internal[i].priority = internalJson.Priority;
|
|
||||||
//throw 'Unknown internal module: "' + module.Item + '"';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
ship.use(ship.internal[i], internal, true);
|
ship.use(ship.internal[i], internal, true);
|
||||||
ship.internal[i].enabled = internalJson.On === true;
|
ship.internal[i].enabled = internalJson.On === true;
|
||||||
ship.internal[i].priority = internalJson.Priority;
|
ship.internal[i].priority = internalJson.Priority;
|
||||||
modsToAdd.push({ coriolisMod: internal, json: internalSlot });
|
modsToAdd.push({ coriolisMod: internal, json: internalSlot });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for (const i of modsToAdd) {
|
for (const i of modsToAdd) {
|
||||||
if (i.json.Engineering) {
|
if (i.json.Engineering) {
|
||||||
_addModifications(i.coriolisMod, i.json.Engineering.Modifiers, i.json.Engineering.Quality, i.json.Engineering.BlueprintName, i.json.Engineering.Level, i.json.Engineering.ExperimentalEffect);
|
_addModifications(i.coriolisMod, i.json.Engineering.Modifiers, i.json.Engineering.BlueprintName, i.json.Engineering.Level, i.json.Engineering.ExperimentalEffect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// We don't have any information on it so guess it's priority 5 and disabled
|
// We don't have any information on it so guess it's priority 5 and disabled
|
||||||
@@ -328,13 +241,12 @@ export function shipFromLoadoutJSON(json) {
|
|||||||
* Add the modifications for a module
|
* Add the modifications for a module
|
||||||
* @param {Module} module the module
|
* @param {Module} module the module
|
||||||
* @param {Object} modifiers the modifiers
|
* @param {Object} modifiers the modifiers
|
||||||
* @param {float} quality quality of the modifiers 0 to 1
|
|
||||||
* @param {Object} blueprint the blueprint of the modification
|
* @param {Object} blueprint the blueprint of the modification
|
||||||
* @param {Object} grade the grade of the modification
|
* @param {Object} grade the grade of the modification
|
||||||
* @param {Object} specialModifications special modification
|
* @param {Object} specialModifications special modification
|
||||||
*/
|
*/
|
||||||
function _addModifications(module, modifiers, quality, blueprint, grade, specialModifications) {
|
function _addModifications(module, modifiers, blueprint, grade, specialModifications) {
|
||||||
if (!modifiers && !quality) return;
|
if (!modifiers) return;
|
||||||
let special;
|
let special;
|
||||||
if (specialModifications) {
|
if (specialModifications) {
|
||||||
if (specialModifications == 'special_plasma_slug') {
|
if (specialModifications == 'special_plasma_slug') {
|
||||||
@@ -349,7 +261,6 @@ function _addModifications(module, modifiers, quality, blueprint, grade, special
|
|||||||
// Add the blueprint definition, grade and special
|
// Add the blueprint definition, grade and special
|
||||||
if (blueprint) {
|
if (blueprint) {
|
||||||
module.blueprint = getBlueprint(blueprint, module);
|
module.blueprint = getBlueprint(blueprint, module);
|
||||||
|
|
||||||
if (grade) {
|
if (grade) {
|
||||||
module.blueprint.grade = Number(grade);
|
module.blueprint.grade = Number(grade);
|
||||||
}
|
}
|
||||||
@@ -357,7 +268,6 @@ function _addModifications(module, modifiers, quality, blueprint, grade, special
|
|||||||
module.blueprint.special = special;
|
module.blueprint.special = special;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (modifiers) {
|
|
||||||
for (const i in modifiers) {
|
for (const i in modifiers) {
|
||||||
// Some special modifications
|
// Some special modifications
|
||||||
// Look up the modifiers to find what we need to do
|
// Look up the modifiers to find what we need to do
|
||||||
@@ -390,7 +300,4 @@ function _addModifications(module, modifiers, quality, blueprint, grade, special
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (quality) {
|
|
||||||
setQualityCB(module.blueprint, quality, (featureName, value) => module.setModValue(featureName, value, false));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ function orbisShorten(url, success, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const API_ORBIS = 'https://api.orbis.zone/ships';
|
const API_ORBIS = 'https://orbis.zone/api/builds/add';
|
||||||
/**
|
/**
|
||||||
* Upload to Orbis
|
* Upload to Orbis
|
||||||
* @param {object} ship The URL to shorten
|
* @param {object} ship The URL to shorten
|
||||||
|
|||||||
@@ -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,14 +2,19 @@
|
|||||||
<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 -->
|
||||||
<meta name="description" content="A ship builder, outfitting and comparison
|
<meta name="description" content="A ship builder, outfitting and comparison
|
||||||
tool for Elite Dangerous">
|
tool for Elite Dangerous">
|
||||||
<meta name="mobile-web-app-capable" content="yes">
|
<meta name="mobile-web-app-capable" content="yes">
|
||||||
|
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0,
|
<meta name="viewport" content="width=device-width, initial-scale=1.0,
|
||||||
maximum-scale=1.0, user-scalable=0">
|
maximum-scale=1.0, user-scalable=0">
|
||||||
<link rel="manifest" href="/manifest.json">
|
<link rel="manifest" href="/manifest.json">
|
||||||
@@ -27,7 +32,7 @@
|
|||||||
<meta name="msapplication-TileImage" content="/mstile-144x144.png">
|
<meta name="msapplication-TileImage" content="/mstile-144x144.png">
|
||||||
<meta name="msapplication-config" content="/browserconfig.xml">
|
<meta name="msapplication-config" content="/browserconfig.xml">
|
||||||
<meta name="theme-color" content="#000000">
|
<meta name="theme-color" content="#000000">
|
||||||
<!-- <script data-ad-client="ca-pub-3709458261881414" async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script> -->
|
|
||||||
<script>
|
<script>
|
||||||
window.CORIOLIS_GAPI_KEY = '<%- htmlWebpackPlugin.options.gapiKey %>';
|
window.CORIOLIS_GAPI_KEY = '<%- htmlWebpackPlugin.options.gapiKey %>';
|
||||||
window.CORIOLIS_VERSION = '<%- htmlWebpackPlugin.options.version %>';
|
window.CORIOLIS_VERSION = '<%- htmlWebpackPlugin.options.version %>';
|
||||||
@@ -40,12 +45,21 @@
|
|||||||
window.dataLayer = window.dataLayer || [];
|
window.dataLayer = window.dataLayer || [];
|
||||||
function gtag(){dataLayer.push(arguments);}
|
function gtag(){dataLayer.push(arguments);}
|
||||||
gtag('js', new Date());
|
gtag('js', new Date());
|
||||||
|
|
||||||
gtag('config', 'UA-55840909-18');
|
gtag('config', 'UA-55840909-18');
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- Bugsnag -->
|
||||||
|
<script src="https://d2wy8f7a9ursnm.cloudfront.net/v5.0.0/bugsnag.min.js"></script>
|
||||||
|
<script>
|
||||||
|
window.bugsnagClient = bugsnag('ba9fae819372850fb660755341fa6ef5', {appVersion: window.BUGSNAG_VERSION || undefined})
|
||||||
|
window.Bugsnag = window.bugsnagClient
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body style="background-color:#000;">
|
<body style="background-color:#000;">
|
||||||
<section id="coriolis">
|
<section id="coriolis"></section>
|
||||||
|
|
||||||
</section>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -183,5 +183,4 @@ footer {
|
|||||||
.announcement {
|
.announcement {
|
||||||
border: 1px @secondary solid;
|
border: 1px @secondary solid;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
margin: 5px;
|
|
||||||
}
|
}
|
||||||
@@ -9,12 +9,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ul {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
pre {
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
white-space: -moz-pre-wrap;
|
white-space: -moz-pre-wrap;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user