Compare commits

..

20 Commits

Author SHA1 Message Date
William Blythe
9abb4f6d05 Merge branch 'develop' into dw2 2019-01-09 09:24:29 +11:00
willyb321
38038fcc32 fix a bug 2018-12-05 07:34:02 +11:00
William Blythe
9fa740f54e more work 2018-12-04 12:01:08 +11:00
William Blythe
d37d69c3a7 add some more guards on ships 2018-11-30 10:59:32 +11:00
William Blythe
78d8779641 add some guards on ships 2018-11-30 10:55:05 +11:00
Willyb321
352023f0fb Start work on roles 2018-11-30 09:08:26 +11:00
willyb321
3ea194d43e Merge branch 'develop' into dw2 2018-11-30 06:56:16 +11:00
willyb321
c6269192f0 mor edw2 2018-11-30 06:52:47 +11:00
willyb321
841e6c3348 More dw2 work, update roles etc 2018-11-30 06:37:09 +11:00
willyb321
0febc581d1 Merge branch 'develop' into dw2 2018-11-30 06:25:41 +11:00
Willyb321
dc6e398526 fighter hangar 2018-11-30 06:23:41 +11:00
Willyb321
cb24c1fc69 more dw2 work 2018-11-30 06:16:21 +11:00
William Blythe
90ad9de831 more dw2 2018-11-28 14:23:01 +11:00
William Blythe
9360b1d574 more dw2 work 2018-11-28 11:31:09 +11:00
William Blythe
08d2573d1f internals 2018-11-28 09:42:21 +11:00
Willyb321
427b9af7de up to shields 2018-11-28 08:25:05 +11:00
William Blythe
318d06d9f9 dw2 build work 2018-11-20 12:18:16 +11:00
William Blythe
1f3c3726f2 Merge branch 'dw2' of github.com:EDCD/coriolis into dw2 2018-11-20 09:29:08 +11:00
William
71beda3a6c Update docker-compose.yml 2018-11-20 08:36:16 +11:00
William
1d6644b531 Update docker-compose.yml 2018-11-20 08:34:53 +11:00
66 changed files with 2336 additions and 2550 deletions

77
.dockerignore Normal file
View File

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

34
Dockerfile Normal file
View File

@@ -0,0 +1,34 @@
### STAGE 1: Build ###
FROM node:9.11.1-alpine as builder
ARG branch=develop
ENV BRANCH=$branch
WORKDIR /src/app
RUN mkdir -p /src/app/coriolis
RUN mkdir -p /src/app/coriolis-data
RUN apk add --update git
COPY . /src/app/coriolis
RUN npm i -g npm
# Set up coriolis-data
WORKDIR /src/app/coriolis-data
RUN git clone https://github.com/EDCD/coriolis-data.git .
RUN git checkout ${BRANCH}
RUN npm install --no-package-lock
RUN npm start
# Set up coriolis
WORKDIR /src/app/coriolis
RUN 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;"]

View File

@@ -1,24 +0,0 @@
All Data and [associated JSON](https://github.com/EDCD/coriolis-data) files are intellectual property and copyright of Frontier Developments plc ('Frontier', 'Frontier Developments') and are subject to their
[terms and conditions](https://www.frontierstore.net/terms-and-conditions/).
The code (Javascript, CSS, HTML, and SVG files only) specificially for Coriolis.io is released under the MIT License.
Copyright (c) 2015 Coriolis.io, Colin McLeod
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software (Javascript, CSS, HTML, and SVG files only), and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -10,6 +10,11 @@ Coriolis was created using assets and imagery from Elite: Dangerous, with the pe
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.
### 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)!
@@ -18,12 +23,12 @@ Chat to us on [Discord](https://discord.gg/0uwCh6R62aPRjk9w)!
See the [Developer's Guide](https://github.com/EDCD/coriolis/wiki/Developing-for-Coriolis) in the wiki.
Also see [the documentation site.](https://coriolis.willb.info/)
### Ship and Module Database
See the [Data wiki](https://github.com/cmmcleod/coriolis-data/wiki) for details on structure, etc.
You can find hosted and compiled versions of these data-jsons under https://coriolis.io/data/ and https://beta.coriolis.io/data/.
You might want to load these as depedency instead of reyling on the npm-dependency.
## License

View File

@@ -1,50 +1,50 @@
{
"type_6_transporter": {
"Cargo": "A0p0tdFal8d8s8f4-----04040303430101-.Iw18UA==.Aw18UA==.",
"Miner": "A0p5tdFal8d8s8f42l2l---040403451q0101-.Iw18UA==.Aw18UA==.",
"Hopper": "A0p0tdFal8d0s8f41717---030302024300--.Iw18UA==.Aw18UA==."
"Cargo": "A0p0tdFal8d8s8f4-----04040303430101.Iw1/kA==.Aw1/kA==.",
"Miner": "A0p5tdFal8d8s8f42l2l---040403451q0101.Iw1/kA==.Aw1/kA==.",
"Hopper": "A0p0tdFal8d0s8f41717---030302024300-.Iw1/kA==.Aw1/kA==."
},
"type_7_transport": {
"Cargo": "A0p0tiFfliddsdf5--------0505040403480101--.Iw18eQ==.Aw18eQ==.",
"Miner": "A0pdtiFflid8sdf5--2l2l----0505041v03450000--.Iw18eQ==.Aw18eQ==."
"Cargo": "A0p0tiFfliddsdf5--------0505040403480101.Iw18aQ==.Aw18aQ==.",
"Miner": "A0pdtiFflid8sdf5--2l2l----0505041v03450000.Iw18aQ==.Aw18aQ==."
},
"federal_dropship": {
"Cargo": "A0pdtiFflnddsif4-1717------05040448--020201-.Iw18RQ==.Aw18RQ==."
"Cargo": "A0pdtiFflnddsif4-1717------05040448--020201.Iw18eQ==.Aw18eQ==."
},
"asp": {
"Miner": "A2pftfFflidfskf50s0s24242l2l---04054a1q02022o27-.Iw18eQ==.Aw18eQ==."
"Miner": "A2pftfFflidfskf50s0s24242l2l---04054a1q02022o27.Iw18WQ==.Aw18WQ==."
},
"imperial_clipper": {
"Cargo": "A0p5tiFflndisnf4--0s0s----0605450302020101-.Iw18WQ==.Aw18WQ==.",
"Dream": "A2pktkFflndpskf40v0v0s0s0404040n4k5n5d2b29292o--.AwRj4yWU1Yg=.CwBhCYy6YRigzPIA.",
"Current": "A0patkFflndfskf4-----------------.AwRj4yWU1Yg=.CwBhCYy6YRigzPIA."
"Cargo": "A0p5tiFflndisnf4--0s0s----0605450302020101.Iw18aQ==.Aw18aQ==.",
"Dream": "A2pktkFflndpskf40v0v0s0s0404040n4k5n5d2b29292o-.AwRj4yWU1I==.CwBhCYy6YRigzLIA.",
"Current": "A0patkFflndfskf4----------------.AwRj4yWU1I==.CwBhCYy6YRigzLIA."
},
"type_9_heavy": {
"Current": "A0patsFklndnsif6---------0706054a0303020224--.AwRj4yo5iA==.EwBhEYy6d6g=."
"Current": "A0patsFklndnsif6---------0706054a0303020224.AwRj4yoo.EwBhEYy6dsg=."
},
"python": {
"Cargo": "A0patnFflidsssf5---------050505040448020201-.Iw18eAMQ.Aw18RQ==.",
"Miner": "A0pktkFflidpspf50v0v0v2m2m0404--050505Ce4a1v02022o-.Iw18eAMQ.IwBhBYy6dkCYRA==.",
"Dream": "A2pptkFfliduspf50v0v0v27270404040m5n5n4f2d2d032t0201-.Iw1+gDByUA==.EwBhEYy6e0VEA===.",
"Missile": "A0pttoFjljdystf52f2g2d2ePh----04044j03---00--.Iw18eAMQ.Aw18RQ==."
"Cargo": "A0patnFflidsssf5---------050505040448020201.Iw18eQ==.Aw18eQ==.",
"Miner": "A0pktkFflidpspf50v0v0v2m2m0404--050505Ce4a1v02022o.Iw18eQ==.IwBhBYy6dkCYg===.",
"Dream": "A2pptkFfliduspf50v0v0v27270404040m5n5n4f2d2d032t0201.Iw1+gDBxA===.EwBhEYy6e0WEA===.",
"Missile": "A0pttoFjljdystf52f2g2d2ePh----04044j03---002h.Iw18eQ==.Aw18eQ==."
},
"anaconda": {
"Dream": "A4putpFklndzsuf52c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d04-0303326b-.AwRj4yo5dzhA.MwBhCYy6duvARhEA.",
"Cargo": "A0patnFklndnsxf5----------------06050505040404-45030301-.Iw18ZUAxA===.Aw18ZXEA.",
"Current": "A0patnFklndksxf5----------------06050505040404-03034524-.Iw18ZUAxA===.Aw18ZXEA.",
"Explorer": "A0patnFklndksxf5--------0202------f7050505040s37--2i4524-.AwRj4yVKJ9jCA===.AwhMIyumQRgkA===.",
"Test": "A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b-.Iw18ZUAxA===.Aw18ZXEA."
"Dream": "A4putpFklndzsuf52c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d04-0303326b.AwRj4yo5dyg=.MwBhCYy6duvARiA=.",
"Cargo": "A0patnFklndnsxf5----------------06050505040404-45030301.Iw18ZVA=.Aw18ZVA=.",
"Current": "A0patnFklndksxf5----------------06050505040404-03034524.Iw18ZVA=.Aw18ZVA=.",
"Explorer": "A0patnFklndksxf5--------0202------f7050505040s37-2f2i4524.AwRj4yVKJ9hA.AwhMIyumQRhEA===.",
"Test": "A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.Iw18ZVA=.Aw18ZVA=."
},
"diamondback_explorer": {
"Explorer": "A0p0tdFfldddsdf5---0202--320p432i----.AwRj4zTZaA==.AwiMIyqo."
"Explorer": "A0p0tdFfldddsdf5---0202--320p432i2f-.AwRj4zTYg===.AwiMIyoo."
},
"vulture": {
"Bounty Hunter": "A3patcFalddksff31e1e0404-0l4a-5d27662j--.AwRj4z2Gg===.MwBhBYy6oJmAjLMQ."
"Bounty Hunter": "A3patcFalddksff31e1e0404-0l4a-5d27662j.AwRj4z2I.MwBhBYy6oJmAjLIA."
},
"fer_de_lance": {
"Attack": "A2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27--.Iw18aAMQ.CwBhrSu8EZxEA===."
"Attack": "A2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.CwBhrSu8EZyA."
},
"eagle": {
"Figther": "A4p0t5F5l3d5s5f20p0p24-4053-2j---.Iw18gDJQ.Aw19kA==."
"Figther": "A4p0t5F5l3d5s5f20p0p24-4053-2j-.Iw18kA==.Aw18kA==."
}
}

View File

@@ -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
}
}
]
}
}
]

View File

@@ -1,8 +0,0 @@
{
"krait_mkii": {
"Imported pancake hammer": "A2pptkFflidussf52l1o1o2g2g020g040405051Ofr45C9C91oP3.Iw18eQ==.AwRgzKIkA===."
},
"diamondback_explorer": {
"Imported star Hopper": "A0pataFflddfsdf5---02---321P430iv6013w2i.Iw18SQ==.AwRm44GYpKg=."
}
}

View File

@@ -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
}
}
]
}
}
]

View File

@@ -18,13 +18,13 @@ describe('Import Modal', function() {
const mockContext = {
language: getLanguage('en'),
sizeRatio: 1,
openMenu: jest.fn(),
closeMenu: jest.fn(),
showModal: jest.fn(),
hideModal: jest.fn(),
tooltip: jest.fn(),
termtip: jest.fn(),
onWindowResize: jest.fn()
openMenu: jest.genMockFunction(),
closeMenu: jest.genMockFunction(),
showModal: jest.genMockFunction(),
hideModal: jest.genMockFunction(),
tooltip: jest.genMockFunction(),
termtip: jest.genMockFunction(),
onWindowResize: jest.genMockFunction()
};
let modal, render, ContextProvider = Utils.createContextProvider(mockContext);
@@ -110,25 +110,21 @@ describe('Import Modal', function() {
it('catches an invalid backup', function() {
const importData = require('./fixtures/valid-backup');
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);
pasteText('"this is not valid"');
expect(modal.state.importValid).toBeFalsy();
expect(modal.state.errorMsg).toEqual('Must be an object or array!');
pasteText('{ "builds": "Should not be a string" }');
expect(modal.state.importValid).toBeFalsy();
expect(modal.state.errorMsg).toEqual('builds must be an object!');
pasteText(JSON.stringify(importData).replace(/anaconda/g, 'invalid_ship'));
pasteText(JSON.stringify(importData).replace('anaconda', 'invalid_ship'));
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', ''));
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));
expect(modal.state.importValid).toBeFalsy();
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);
clickProceed();
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() {
@@ -173,7 +169,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
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);
clickProceed();
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() {
@@ -202,11 +198,11 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
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);
@@ -244,7 +240,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
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() {
@@ -256,7 +252,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
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() {
@@ -268,7 +264,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
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() {
@@ -280,7 +276,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
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]);
}
}
});
});
});

51
docker-compose.yml Normal file
View File

@@ -0,0 +1,51 @@
version: '2.2'
services:
coriolis_prod:
image: edcd/coriolis:master
restart: always
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
networks:
- web
labels:
- "traefik.docker.network=web"
- "traefik.enable=true"
- "traefik.basic.frontend.rule=Host:coriolis.io,coriolis.edcd.io"
- "traefik.basic.port=80"
- "traefik.basic.protocol=http"
coriolis_dev:
image: edcd/coriolis:develop
restart: always
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
networks:
- web
labels:
- "traefik.docker.network=web"
- "traefik.enable=true"
- "traefik.basic.frontend.rule=Host:beta.coriolis.io,beta.coriolis.edcd.io"
- "traefik.basic.port=80"
- "traefik.basic.protocol=http"
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
View 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;
}
}
}

View File

@@ -22,7 +22,6 @@ import ComparisonPage from './pages/ComparisonPage';
import ShipyardPage from './pages/ShipyardPage';
import ErrorDetails from './pages/ErrorDetails';
const zlib = require('pako');
const request = require('superagent');
@@ -73,6 +72,7 @@ export default class Coriolis extends React.Component {
route: {},
sizeRatio: Persist.getSizeRatio()
};
this._getAnnouncements();
Router('', (r) => this._setPage(ShipyardPage, r));
Router('/import?', (r) => this._importBuild(r));
Router('/import/:data', (r) => this._importBuild(r));
@@ -97,35 +97,30 @@ export default class Coriolis extends React.Component {
const json = JSON.parse(data);
console.info('Ship import data: ');
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);
} else if (importString) {
this._setPage(ShipyardPage, r);
this._showModal(<ModalImport importString={data}/>);
let ship;
if (json && json.modules) {
ship = CompanionApiUtils.shipFromJson(json);
} else if (json && json.Modules) {
ship = JournalUtils.shipFromLoadoutJSON(json);
}
r.params.ship = ship.id;
r.params.code = ship.toString();
this._setPage(OutfittingPage, r)
} catch (err) {
this._onError('Failed to import ship', r.path, 0, 0, err);
}
}
async _getAnnouncements() {
try {
const announces = await request.get('https://orbis.zone/api/announcement')
.query({ showInCoriolis: true });
this.setState({ announcements: announces.body });
} catch (err) {
console.error(err)
}
}
/**
* Updates / Sets the page and route context
* @param {[type]} page The page to be shown
@@ -399,18 +394,18 @@ export default class Coriolis extends React.Component {
*/
render() {
let currentMenu = this.state.currentMenu;
return <div style={{ minHeight: '100%' }} onClick={this._closeMenu}
className={this.state.noTouch ? 'no-touch' : null}>
<Header announcements={this.state.announcements} appCacheUpdate={this.state.appCacheUpdate}
currentMenu={currentMenu}/>
<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 }) :
<NotFoundPage/>}
{this.state.modal}
{this.state.tooltip}
<footer>
<div className="right cap">
<a href="https://github.com/EDCD/coriolis" target="_blank" rel="noopener noreferrer"
title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>

View File

@@ -1,5 +1,7 @@
import Persist from './stores/Persist';
import ReactGA from 'react-ga';
ReactGA.initialize('UA-55840909-18');
let standalone = undefined;
/**
@@ -72,7 +74,6 @@ Router.go = function(path, state) {
gaTrack(path);
let ctx = new Context(path, state);
Router.dispatch(ctx);
if (!ctx.unhandled) {
if (isStandAlone()) {
Persist.setState(ctx);
@@ -258,8 +259,16 @@ Route.prototype.match = function(path, params) {
* @param {string} path Path to track
*/
function gaTrack(path) {
const _paq = window._paq || [];
_paq.push(['trackPageView']);
const match = path.match(/\/outfit\/(.*)(\?code=.*)/);
if (match) {
if (match[1]) {
ReactGA.ga('set', 'contentGroup1', match[1]);
}
if (match[2]) {
ReactGA.ga('set', 'contentGroup2', match[2]);
}
}
ReactGA.pageview(path);
}
/**

View File

@@ -78,11 +78,7 @@ const GRPCAT = {
// Mining
'scl': 'mining',
'pwa': 'mining',
'sdm': 'mining',
// Assists
'dc': 'flight assists',
'sua': 'flight assists',
'sdm': 'mining'
};
// Order here is the order in which items will be shown in the modules menu
const CATEGORIES = {
@@ -97,8 +93,7 @@ const CATEGORIES = {
'rf': ['rf'],
'shields': ['sg', 'bsg', 'psg', 'scb'],
'structural reinforcement': ['hr', 'mrp'],
'flight assists': ['dc', 'sua'],
'dc': ['dc'],
// Hardpoints
'lasers': ['pl', 'ul', 'bl'],
'projectiles': ['mc', 'c', 'fc', 'pa', 'rg'],
@@ -126,7 +121,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
onSelect: PropTypes.func.isRequired,
diffDetails: PropTypes.func,
m: PropTypes.object,
ship: PropTypes.object.isRequired,
shipMass: PropTypes.number,
warning: PropTypes.func,
firstSlotId: PropTypes.string,
lastSlotId: PropTypes.string,
@@ -134,6 +129,10 @@ export default class AvailableModulesMenu extends TranslatedComponent {
slotDiv: PropTypes.object
};
static defaultProps = {
shipMass: 0
};
/**
* Constructor
* @param {Object} props React Component properties
@@ -155,15 +154,15 @@ export default class AvailableModulesMenu extends TranslatedComponent {
*/
_initState(props, context) {
let translate = context.language.translate;
let { m, warning, onSelect, modules, ship } = props;
let { m, warning, shipMass, onSelect, modules, firstSlotId, lastSlotId } = props;
let list, currentGroup;
let buildGroup = this._buildGroup.bind(
this,
ship,
translate,
m,
warning,
shipMass - (m && m.mass ? m.mass : 0),
(m, event) => {
this._hideDiff(event);
onSelect(m);
@@ -251,16 +250,18 @@ export default class AvailableModulesMenu extends TranslatedComponent {
/**
* 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 {Function} warningFunc Warning function
* @param {number} mass Mass
* @param {function} onSelect Select/Mount callback
* @param {string} grp Group name
* @param {Array} modules Available modules
* @param {string} firstSlotId id of first slot item
* @param {string} lastSlotId id of last slot item
* @return {React.Component} Available Module Group contents
*/
_buildGroup(ship, translate, mountedModule, warningFunc, onSelect, grp, modules) {
_buildGroup(translate, mountedModule, warningFunc, mass, onSelect, grp, modules, firstSlotId, lastSlotId) {
let prevClass = null, prevRating = null, prevName;
let elems = [];
@@ -281,11 +282,10 @@ export default class AvailableModulesMenu extends TranslatedComponent {
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 = 4 <= ship.hardpoints.filter(o => o.m && o.m.experimental).length;
disabled = mass > m.maxmass;
} else if (m.maxmass) {
// Thrusters care about total mass
disabled = mass + m.mass > m.maxmass;
}
let active = mountedModule && mountedModule.id === m.id;
let classes = cn(m.name ? 'lc' : 'c', {

View File

@@ -306,8 +306,8 @@ export default class CostSection extends TranslatedComponent {
<tr className='main'>
<th colSpan='2' className='sortable le' onClick={this._sortCostBy.bind(this,'m')}>
{translate('module')}
{shipDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} ${formats.pct(-1 * shipDiscount)}]`}</u> : null}
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} ${formats.pct(-1 * moduleDiscount)}]`}</u> : null}
{shipDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} -${formats.pct(shipDiscount)}]`}</u> : null}
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
</th>
<th className='sortable le' onClick={this._sortCostBy.bind(this, 'cr')} >{translate('credits')}</th>
</tr>

View File

@@ -52,12 +52,12 @@ export default class Defence extends TranslatedComponent {
* @return {React.Component} contents
*/
render() {
const { opponent, sys, opponentWep } = this.props;
const { ship, sys, opponentWep } = this.props;
const { language, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { shield, armour, shielddamage, armourdamage } = this.state;
const pd = opponent.standard[4].m;
const pd = ship.standard[4].m;
const shieldSourcesData = [];
const effectiveShieldData = [];

View File

@@ -104,10 +104,10 @@ export default class HardpointSlot extends Slot {
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}
<span>({formats.round1((m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()))}{u.MW})</span> : null}</div> : null}
{m.getHps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'hpsshps' : 'hps')}
onMouseOut={tooltip.bind(null, null)}>{translate('HPS')}: {formats.round1(m.getHps())} {m.getClip() ?
<span>({formats.round1(m.getHps() * m.getSustainedFactor())})</span> : null}</div> : null}
<span>({formats.round1((m.getClip() * m.getHps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()))})</span> : null}</div> : null}
{m.getDps() && m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, 'dpe')}
onMouseOut={tooltip.bind(null, null)}>{translate('DPE')}: {formats.f1(m.getDps() / m.getEps())}</div> : null}
{m.getRoF() ? <div className={'l'} onMouseOver={termtip.bind(null, 'rof')}
@@ -121,7 +121,7 @@ export default class HardpointSlot extends Slot {
{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.getReload() ? <div className={'l'}>{translate('reload')}: {formats.round(m.getReload())}{u.s}</div> : null}
{m.getShotSpeed() ? <div
className={'l'}>{translate('shotspeed')}: {formats.int(m.getShotSpeed())}{u.mps}</div> : null}
{m.getPiercing() ? <div className={'l'}>{translate('piercing')}: {formats.int(m.getPiercing())}</div> : null}

View File

@@ -149,14 +149,14 @@ export default class HardpointSlotSection extends SlotSection {
<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>
</ul>
<div className='select-group cap'>{translate('rg')}</div>
<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>
</ul>
<div className='select-group cap'>{translate('nl')}</div>
<ul>
<li className='lc' tabIndex='0' onClick={_fill.bind(this, 'nl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['nl-F'] = smRef}>{translate('nl')}</li>
</ul>
<div className='select-group cap'>{translate('ggc')}</div>
<ul>
<li className='lc' tabIndex='0' onClick={_fill.bind(this, 'ggc', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ggc-F'] = smRef}>{translate('ggc')}</li>
</ul>
<div className='select-group cap'>{translate('rfl')}</div>
<ul>
<li className='c' tabIndex='0' onClick={_fill.bind(this, 'rfl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['rfl-F'] = smRef}><MountFixed className='lg'/></li>

View File

@@ -10,6 +10,7 @@ import { Ships } from 'coriolis-data/dist';
import Persist from '../stores/Persist';
import { toDetailedExport } from '../shipyard/Serializer';
import Ship from '../shipyard/Ship';
import ModalBatchOrbis from './ModalBatchOrbis';
import ModalDeleteAll from './ModalDeleteAll';
import ModalExport from './ModalExport';
import ModalHelp from './ModalHelp';
@@ -240,6 +241,43 @@ export default class Header extends TranslatedComponent {
/>);
};
/**
* Uploads all ship-builds to orbis
* @param {e} e Event
*/
_uploadAllBuildsToOrbis(e) {
e.preventDefault();
const data = Persist.getBuilds();
let postObject = [];
for (const ship in data) {
for (const code in data[ship]) {
const shipModel = ship;
if (!shipModel) {
throw 'No such ship found: "' + ship + '"';
}
const shipTemplate = Ships[shipModel];
const shipPostObject = {};
let shipInstance = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots);
shipInstance.buildWith(null);
shipInstance.buildFrom(data[ship][code]);
shipPostObject.coriolisId = shipInstance.id;
shipPostObject.coriolisShip = shipInstance;
shipPostObject.coriolisShip.url = window.location.origin + outfitURL(shipModel, data[ship][code], code);
shipPostObject.title = code || shipInstance.id;
shipPostObject.description = code || shipInstance.id;
shipPostObject.ShipName = shipInstance.id;
shipPostObject.Ship = shipInstance.id;
postObject.push(shipPostObject);
}
}
console.log(postObject);
this.context.showModal(<ModalBatchOrbis
ships={postObject}
/>);
}
/**
* Show export modal with detailed export
* @param {SyntheticEvent} e Event
@@ -311,7 +349,7 @@ export default class Header extends TranslatedComponent {
_getShipsMenu() {
let shipList = [];
for (let s of this.shipOrder) {
for (let s in Ships) {
shipList.push(<ActiveLink key={s} href={outfitURL(s)} className='block'>{Ships[s].properties.name}</ActiveLink>);
}
@@ -388,10 +426,7 @@ export default class Header extends TranslatedComponent {
if (this.props.announcements) {
announcements = [];
for (let announce of this.props.announcements) {
if (announce.expiry < Date.now()) {
continue;
}
announcements.push(<Announcement text={announce.text} />);
announcements.push(<Announcement text={announce.message} />);
announcements.push(<hr/>);
}
}
@@ -461,6 +496,7 @@ export default class Header extends TranslatedComponent {
{translate('builds')} & {translate('comparisons')}
<li><Link href="#" className='block' onClick={this._showBackup.bind(this)}>{translate('backup')}</Link></li>
<li><Link href="#" className='block' onClick={this._showDetailedExport.bind(this)}>{translate('detailed export')}</Link></li>
<li><Link href="#" className='block' onClick={this._uploadAllBuildsToOrbis.bind(this)}>{translate('upload all builds to orbis')}</Link></li>
<li><Link href="#" className='block' onClick={this._showImport.bind(this)}>{translate('import')}</Link></li>
<li><Link href="#" className='block' onClick={this._showDeleteAll.bind(this)}>{translate('delete all')}</Link></li>
</ul>

View File

@@ -78,6 +78,7 @@ export default class InternalSlot extends Slot {
{ m.rangeLS ? <div className={'l'}>{translate('range')}: {m.rangeLS}{u.Ls}</div> : null }
{ m.rangeLS === null ? <div className={'l'}>{u.Ls}</div> : null }
{ m.rangeRating ? <div className={'l'}>{translate('range')}: {m.rangeRating}</div> : null }
{ m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
{ m.passengers ? <div className={'l'}>{translate('passengers')}: {m.passengers}</div> : null }
{ m.getRegenerationRate() ? <div className='l'>{translate('regen')}: {formats.round1(m.getRegenerationRate())}{u.ps}</div> : null }
{ m.getBrokenRegenerationRate() ? <div className='l'>{translate('brokenregen')}: {formats.round1(m.getBrokenRegenerationRate())}{u.ps}</div> : null }

View File

@@ -0,0 +1,93 @@
import React from 'react';
import PropTypes from 'prop-types';
import request from 'superagent';
import TranslatedComponent from './TranslatedComponent';
import { orbisUpload } from '../utils/ShortenUrl';
import Persist from '../stores/Persist';
/**
* Permalink modal
*/
export default class ModalBatchOrbis extends TranslatedComponent {
static propTypes = {
ships: PropTypes.any.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this.state = {
orbisCreds: Persist.getOrbisCreds(),
resp: ''
};
}
/**
* Send ship to Orbis.zone
* @param {SyntheticEvent} e React Event
* @return {Promise} Promise sending post request to orbis
*/
sendToOrbis(e) {
let agent;
try {
agent = request.agent(); // apparently this crashes somehow
} catch (e) {
console.error(e);
}
if (!agent) {
agent = request;
}
const API_ORBIS = 'https://orbis.zone/api/builds/add/batch';
return new Promise((resolve, reject) => {
try {
agent
.post(API_ORBIS)
.withCredentials()
.redirects(0)
.set('Content-Type', 'application/json')
.send(this.props.ships)
.end((err, response) => {
console.log(response);
if (err) {
console.error(err);
this.setState({ resp: response.text });
reject('Bad Request');
} else {
this.setState({ resp: 'All builds uploaded. Check https://orbis.zone' });
resolve('All builds uploaded. Check https://orbis.zone');
}
});
} catch (e) {
console.log(e);
reject(e.message ? e.message : e);
}
});
}
/**
* Render the modal
* @return {React.Component} Modal Content
*/
render() {
let translate = this.context.language.translate;
this.sendToOrbis = this.sendToOrbis.bind(this);
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
<h2>{translate('permalink')}</h2>
<br/>
<a className='button' href="https://orbis.zone/api/auth">Log in / signup to Orbis</a>
<br/><br/>
<h3 >{translate('success')}</h3>
<input value={this.state.resp} readOnly size={25} onFocus={ (e) => e.target.select() }/>
<br/><br/>
<p>Orbis.zone is currently in a trial period, and may be wiped at any time as development progresses. Some elements are also still placeholders.</p>
<button className={'l cb dismiss cap'} disabled={!!this.state.failed} onClick={this.sendToOrbis}>{translate('PHASE_UPLOAD_ORBIS')}</button>
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
</div>;
}
}

View File

@@ -11,10 +11,7 @@ import * as ModuleUtils from '../shipyard/ModuleUtils';
import { fromDetailedBuild } from '../shipyard/Serializer';
import { Download } from './SvgIcons';
import { outfitURL } from '../utils/UrlGenerators';
import { shipFromJson, shipModelFromJson } from '../utils/CompanionApiUtils';
import { shipFromLoadoutJSON } from '../utils/JournalUtils';
const zlib = require('pako');
import * as CompanionApiUtils from '../utils/CompanionApiUtils';
const textBuildRegex = new RegExp('^\\[([\\w \\-]+)\\]\n');
const lineRegex = new RegExp('^([\\dA-Z]{1,2}): (\\d)([A-I])[/]?([FGT])?([SD])? ([\\w\\- ]+)');
@@ -89,7 +86,6 @@ export default class ModalImport extends TranslatedComponent {
static propTypes = {
importString: PropTypes.string, // Optional: Default data for import modal
builds: PropTypes.object, // Optional: Import object
};
@@ -103,12 +99,11 @@ export default class ModalImport extends TranslatedComponent {
this.state = {
builds: props.builds,
canEdit: !props.builds,
loadoutEvent: null,
comparisons: null,
shipDiscount: null,
moduleDiscount: null,
errorMsg: null,
importString: props.importString || null,
importString: null,
importValid: false,
insurance: null
};
@@ -116,28 +111,12 @@ export default class ModalImport extends TranslatedComponent {
this._process = this._process.bind(this);
this._import = this._import.bind(this);
this._importBackup = this._importBackup.bind(this);
this._importLoadout = this._importLoadout.bind(this);
this._importDetailedArray = this._importDetailedArray.bind(this);
this._importTextBuild = this._importTextBuild.bind(this);
this._importCompanionApiBuild = this._importCompanionApiBuild.bind(this);
this._validateImport = this._validateImport.bind(this);
}
/**
* Import a Loadout event from Elite: Dangerous journal files
* @param {Object} data Loadout event
* @throws {string} If import fails
*/
_importLoadout(data) {
if (data && data.Ship && data.Modules) {
const deflated = zlib.deflate(JSON.stringify(data), { to: 'string' });
let compressed = btoa(deflated);
this.setState({loadoutEvent: compressed});
} else {
throw 'Loadout event must contain Ship and Modules';
}
}
/**
* Import a Coriolis backup
* @param {Object} importData Backup Data
@@ -180,7 +159,7 @@ export default class ModalImport extends TranslatedComponent {
}
// Check for module discount
if (!isNaN(importData.moduleDiscount)) {
this.setState({ moduleDiscount: importData.moduleDiscount * 1 });
this.setState({ shipDiscount: importData.moduleDiscount * 1 });
}
if (typeof importData.insurance == 'string') {
@@ -216,8 +195,8 @@ export default class ModalImport extends TranslatedComponent {
* @throws {string} if parse/import fails
*/
_importCompanionApiBuild(build) {
const shipModel = shipModelFromJson(build);
const ship = shipFromJson(build);
const shipModel = CompanionApiUtils.shipModelFromJson(build);
const ship = CompanionApiUtils.shipFromJson(build);
let builds = {};
builds[shipModel] = {};
@@ -323,30 +302,6 @@ export default class ModalImport extends TranslatedComponent {
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
* @param {SyntheticEvent} event Event
@@ -381,10 +336,8 @@ export default class ModalImport extends TranslatedComponent {
throw 'Must be an object or array!';
}
if (importData?.[0]?.header?.appName) { // has SLEF envelope?
this._importSlefBuilds(importData);
} else if (importData.modules != null && importData.modules.Armour != null) { // Only the companion API has this information
this._importCompanionApiBuild(importData); // Single ship definition
if (importData.modules != null && importData.modules.Armour != null) { // Only the companion API has this information
this._importCompanionApiBuild(importData); // Single sihp definition
} 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
} else if (importData instanceof Array) { // Must be detailed export json
@@ -392,14 +345,12 @@ export default class ModalImport extends TranslatedComponent {
} else if (importData.ship && typeof importData.name !== undefined) { // Using JSON from a single ship build export
this._importDetailedArray([importData]); // Convert to array with singleobject
this.setState({ singleBuild: true });
} else if (importData.Modules != null && importData.Modules[0] != null) {
this._importLoadout(importData);
} else { // Using Backup JSON
this._importBackup(importData);
}
}
} catch (e) {
console.log(e);
// console.log(e.stack);
this.setState({ errorMsg: (typeof e == 'string') ? e : 'Cannot Parse the data!' });
return;
}
@@ -413,10 +364,6 @@ export default class ModalImport extends TranslatedComponent {
_process() {
let builds = null, comparisons = null;
if (this.state.loadoutEvent) {
return Router.go(`/import?data=${this.state.loadoutEvent}`);
}
// If only importing a single build go straight to the outfitting page
if (this.state.singleBuild) {
builds = this.state.builds;
@@ -533,7 +480,7 @@ export default class ModalImport extends TranslatedComponent {
if (!state.processed) {
importStage = (
<div>
<textarea spellCheck={false} className='cb json' ref={node => this.importField = node} onChange={this._validateImport} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} />
<textarea className='cb json' ref={node => this.importField = node} onChange={this._validateImport} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} />
<button id='proceed' className='l cap' onClick={this._process} disabled={!state.importValid} >{translate('proceed')}</button>
<div className='l warning' style={{ marginLeft:'3em' }}>{state.errorMsg}</div>
</div>
@@ -570,7 +517,7 @@ export default class ModalImport extends TranslatedComponent {
{comparisonRows}
</tbody>
</table>
);
);
}
if(this.state.canEdit) {

View File

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

View File

@@ -109,18 +109,17 @@ export default class ModalShoppingList extends TranslatedComponent {
*/
sendToEDEng(event) {
event.preventDefault();
let translate = this.context.language.translate;
const target = event.target;
target.disabled = this.state.blueprints.length > 0;
if (this.state.blueprints.length === 0) {
target.innerText = translate('No modded components.');
target.innerText = 'No modded components.';
target.disabled = true;
setTimeout(() => {
target.innerText = translate('Send to EDEngineer');
target.innerText = 'Send to EDEngineer';
target.disabled = false;
}, 3000);
} else {
target.innerText = translate('Sending...');
target.innerText = 'Sending...';
}
let countSent = 0;
let countTotal = this.state.blueprints.length;
@@ -140,7 +139,7 @@ export default class ModalShoppingList extends TranslatedComponent {
countSent++;
if (countSent === countTotal) {
target.disabled = false;
target.innerText = translate('Send to EDEngineer');
target.innerText = 'Send to EDEngineer';
}
});
}
@@ -231,32 +230,32 @@ export default class ModalShoppingList extends TranslatedComponent {
this.sendToEDEng = this.sendToEDEng.bind(this);
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
<h2>{translate('PHRASE_SHOPPING_MATS')}</h2>
<label>{translate('Grade 1 rolls ')}</label>
<label>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>
<label>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>
<label>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>
<label>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>
<label>Grade 5 rolls </label>
<input id={5} type={'number'} min={0} value={this.state.matsPerGrade[5]} onChange={this.changeHandler} />
<div>
<textarea className='cb json' readOnly value={this.state.matsList} />
</div>
<label hidden={!compatible} className={'l cap'}>{translate('CMDR Name')}</label>
<label hidden={!compatible} className={'l cap'}>CMDR Name </label>
<br/>
<select hidden={!compatible} className={'cmdr-select l cap'} onChange={this.cmdrChangeHandler} defaultValue={this.state.cmdrName}>
{this.state.cmdrs.map(e => <option key={e}>{e}</option>)}
</select>
<br/>
<p hidden={!this.state.failed} id={'failed'} className={'l'}>{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>
<p hidden={!this.state.failed} id={'failed'} className={'l'}>Failed to send to EDEngineer (Launch EDEngineer and make sure the API is started then refresh the page.)</p>
<p hidden={compatible} id={'browserbad'} className={'l'}>Sending to EDEngineer is not compatible with Firefox's security settings. Please try again with Chrome.</p>
<button className={'l cb dismiss cap'} disabled={!!this.state.failed || !compatible} onClick={this.sendToEDEng}>{translate('Send To EDEngineer')}</button>
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
</div>;
}

View File

@@ -3,8 +3,7 @@ import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import cn from 'classnames';
import NumberEditor from 'react-number-editor';
import { isChangeValueBeneficial } from '../utils/BlueprintFunctions';
import { Modifications } from 'coriolis-data/dist';
import { isValueBeneficial } from '../utils/BlueprintFunctions';
/**
* Modification
@@ -80,7 +79,6 @@ export default class Modification extends TranslatedComponent {
let { translate, formats, units } = this.context.language;
let { m, name } = this.props;
let modValue = m.getChange(name);
let isOverwrite = Modifications.modifications[name].method === 'overwrite';
if (name === 'damagedist') {
// We don't show damage distribution
@@ -119,10 +117,10 @@ export default class Modification extends TranslatedComponent {
</td>
<td style={{ textAlign: 'center' }} className={
modValue ?
isChangeValueBeneficial(name, modValue) ? 'secondary' : 'warning' :
isValueBeneficial(name, modValue) ? 'secondary' : 'warning' :
''
}>
{formats.f2(modValue / 100) || 0}{isOverwrite ? '' : '%'}
{formats.f2(modValue / 100) || 0}%
</td>
</tr>
</tbody>

View File

@@ -478,7 +478,7 @@ export default class ModificationsMenu extends TranslatedComponent {
<tbody>
{ showRolls ?
<tr>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: false }) }> { translate('mroll') }: </td>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: false }) }> { translate('roll') }: </td>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 0 }) } style={{ cursor: 'pointer' }} onClick={_rollWorst} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')} onMouseOut={tooltip.bind(null, null)}> { translate('0%') } </td>
<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>
<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>

View File

@@ -243,13 +243,8 @@ export default class Offence extends TranslatedComponent {
<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>);
}
@@ -276,20 +271,15 @@ export default class Offence extends TranslatedComponent {
<tr className='main'>
<th rowSpan='2' className='sortable' onClick={sortOrder.bind(this, 'n')}>{translate('weapon')}</th>
<th colSpan='1'>{translate('overall')}</th>
<th colSpan='3'>{translate('opponent\'s shields')}</th>
<th colSpan='3'>{translate('opponent\'s armour')}</th>
<th colSpan='2'>{translate('opponent\'s shields')}</th>
<th colSpan='2'>{translate('opponent\'s armour')}</th>
</tr>
<tr>
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')} onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'esdpss')}>{'sdps'}</th>
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')} onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'esdpss')}>{'sdps'}</th>
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_SHIELDS')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'es')}>{'eft'}</th>
<th className='sortable'>{'dpe'}</th>
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'esdpsh')}>{'sdps'}</th>
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'eh')}>{'eft'}</th>
<th className='sortable'>{'dpe'}</th>
</tr>
</thead>
<tbody>
@@ -300,10 +290,8 @@ export default class Offence extends TranslatedComponent {
<td className='ri'><span onMouseOver={termtip.bind(null, totalSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>={formats.f1(totalSDps)}</span></td>
<td className='ri'><span onMouseOver={termtip.bind(null, totalShieldsSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>={formats.f1(totalShieldsSDps)}</span></td>
<td></td>
<td></td>
<td className='ri'><span onMouseOver={termtip.bind(null, totalArmourSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>={formats.f1(totalArmourSDps)}</span></td>
<td></td>
<td></td>
</tr>
}
</tbody>

View File

@@ -174,7 +174,7 @@ export default class OutfittingSubpages extends TranslatedComponent {
<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 == '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>
<th style={{ width:'25%' }} className={cn({ active: tab == 'defence' })} onClick={this._showTab.bind(this, 'defence')} >{translate('defence')}</th>
</tr>
</thead>
</table>

View File

@@ -50,7 +50,6 @@ export default class ShipSummaryTable extends TranslatedComponent {
const speedTooltip = canThrust ? 'TT_SUMMARY_SPEED' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
const canBoost = ship.canBoost(cargo, ship.fuelCapacity);
const boostTooltip = canBoost ? 'TT_SUMMARY_BOOST' : canThrust ? 'TT_SUMMARY_BOOST_NONFUNCTIONAL' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
const canJump = ship.getSlotStatus(ship.standard[2]) == 3;
const sgMetrics = Calc.shieldMetrics(ship, pips.sys);
const shipBoost = canBoost ? Calc.calcBoost(ship) : 'No Boost';
const restingHeat = Math.sqrt(((ship.standard[0].m.pgen * ship.standard[0].m.eff) / ship.heatCapacity) / 0.2);
@@ -72,7 +71,7 @@ export default class ShipSummaryTable extends TranslatedComponent {
<tr className='main'>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canThrust }) }>{translate('speed')}</th>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canBoost }) }>{translate('boost')}</th>
<th colSpan={5} className={ cn({ 'bg-warning-disabled': !canJump }) }>{translate('jump range')}</th>
<th colSpan={5}>{translate('jump range')}</th>
<th rowSpan={2}>{translate('shield')}</th>
<th rowSpan={2}>{translate('integrity')}</th>
<th rowSpan={2}>{translate('DPS')}</th>
@@ -86,15 +85,15 @@ export default class ShipSummaryTable extends TranslatedComponent {
<th onMouseEnter={termtip.bind(null, 'hull hardness', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('hrd')}</th>
<th rowSpan={2}>{translate('crew')}</th>
<th onMouseEnter={termtip.bind(null, 'mass lock factor', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th>
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_BOOST_INTERVAL', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('boost interval')}</th>
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_BOOST_TIME', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('boost time')}</th>
<th rowSpan={2}>{translate('resting heat (Beta)')}</th>
</tr>
<tr>
<th className={ cn({ 'lft': true, 'bg-warning-disabled': !canJump }) }>{translate('max')}</th>
<th className={ cn({ 'bg-warning-disabled': !canJump }) }>{translate('unladen')}</th>
<th className={ cn({ 'bg-warning-disabled': !canJump }) }>{translate('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('max')}</th>
<th>{translate('unladen')}</th>
<th>{translate('laden')}</th>
<th>{translate('total unladen')}</th>
<th>{translate('total laden')}</th>
<th className='lft'>{translate('hull')}</th>
<th>{translate('unladen')}</th>
<th>{translate('laden')}</th>
@@ -104,11 +103,11 @@ export default class ShipSummaryTable extends TranslatedComponent {
<tr>
<td onMouseEnter={termtip.bind(null, speedTooltip, { cap: 0 })} onMouseLeave={hide}>{ canThrust ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, false))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td onMouseEnter={termtip.bind(null, boostTooltip, { cap: 0 })} onMouseLeave={hide}>{ canBoost ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, true))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_MAX_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{ canJump ? <span>{ f2(Calc.jumpRange(ship.unladenMass + 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>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{ canJump ? <span>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{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(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{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 ? <span>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{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(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_MAX_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.standard[2].m.getMaxFuelPerJump(), ship.standard[2].m, ship.standard[2].m.getMaxFuelPerJump(), ship))}{u.LY}</span></td>
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</span></td>
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</span></td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</td>
<td className={sgClassNames} onMouseEnter={termtip.bind(null, sgTooltip, { cap: 0 })} onMouseLeave={hide}>{int(ship.shield)}{u.MJ}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_INTEGRITY', { cap: 0 })} onMouseLeave={hide}>{int(ship.armour)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_DPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalDps)}</td>
@@ -160,10 +159,10 @@ export default class ShipSummaryTable extends TranslatedComponent {
<td>{formats.pct1(ship.shieldThermRes)}</td>
<td></td>
<td>{int(ship && sgMetrics.summary > 0 ? sgMetrics.summary : 0)}{u.MJ}</td>
<td>{int(ship && sgMetrics.summary > 0 ? sgMetrics.summary / sgMetrics.explosive.base : 0)}{u.MJ}</td>
<td>{int(ship && sgMetrics.summary ? sgMetrics.summary / sgMetrics.kinetic.base : 0)}{u.MJ}</td>
<td>{int(ship && sgMetrics.summary ? sgMetrics.summary / sgMetrics.thermal.base : 0)}{u.MJ}</td>
<td>{int(ship && ship.shield > 0 ? ship.shield : 0)}{u.MJ}</td>
<td>{int(ship && ship.shield > 0 ? ship.shield * ((1 / (1 - (ship.shieldExplRes)))) : 0)}{u.MJ}</td>
<td>{int(ship && ship.shield > 0 ? ship.shield * ((1 / (1 - (ship.shieldKinRes)))) : 0)}{u.MJ}</td>
<td>{int(ship && ship.shield > 0 ? ship.shield * ((1 / (1 - (ship.shieldThermRes)))) : 0)}{u.MJ}</td>
<td></td>
<td>{sgMetrics && sgMetrics.recover === Math.Inf ? translate('Never') : formats.time(sgMetrics.recover)}</td>
<td>{sgMetrics && sgMetrics.recharge === Math.Inf ? translate('Never') : formats.time(sgMetrics.recharge)}</td>
@@ -198,11 +197,11 @@ export default class ShipSummaryTable extends TranslatedComponent {
<td>{formats.pct1(ship.hullKinRes)}</td>
<td>{formats.pct1(ship.hullThermRes)}</td>
<td>{formats.pct1(ship.hullCausRes)}</td>
<td>{int(armourMetrics.total)}</td>
<td>{int(armourMetrics.total / armourMetrics.explosive.total)}</td>
<td>{int(armourMetrics.total/ armourMetrics.kinetic.total)}</td>
<td>{int(armourMetrics.total / armourMetrics.thermal.total)}</td>
<td>{int(armourMetrics.total/ armourMetrics.caustic.total)}</td>
<td>{int(ship.armour)}</td>
<td>{int(ship.armour * ((1 / (1 - (ship.hullExplRes)))))}</td>
<td>{int(ship.armour * ((1 / (1 - (ship.hullKinRes)))))}</td>
<td>{int(ship.armour * ((1 / (1 - (ship.hullThermRes)))))}</td>
<td>{int(ship.armour * ((1 / (1 - (ship.hullCausRes)))))}</td>
<td>{int(armourMetrics.modulearmour)}</td>
<td>{int(armourMetrics.moduleprotection * 100) + '%'}</td>

View File

@@ -127,8 +127,8 @@ export default class Slot extends TranslatedComponent {
menu = <AvailableModulesMenu
className={this._getClassNames()}
modules={availableModules()}
shipMass={ship.hullMass}
m={m}
ship={ship}
onSelect={onSelect}
warning={warning}
diffDetails={diffDetails.bind(ship, this.context.language)}

View File

@@ -195,15 +195,6 @@ export default class SlotSection extends TranslatedComponent {
if (targetSlot && canMount(this.props.ship, targetSlot, m.grp, m.class)) {
const mCopy = m.clone();
this.props.ship.use(targetSlot, mCopy, false);
let experimentalNum = this.props.ship.hardpoints
.filter(s => s.m && s.m.experimental).length;
// Remove the module on the last slot if we now exceed the number of
// experimentals allowed
if (m.experimental && 4 < experimentalNum) {
this.props.ship.updateStats(originSlot, null, originSlot.m);
originSlot.m = null; // Empty the slot
originSlot.discountedCost = 0;
}
// Copy power info
targetSlot.enabled = originSlot.enabled;
targetSlot.priority = originSlot.priority;

View File

@@ -109,8 +109,8 @@ export default class StandardSlot extends TranslatedComponent {
menu = <AvailableModulesMenu
className='standard'
modules={modules}
shipMass={ModuleUtils.isShieldGenerator(m.grp) ? ship.hullMass : ship.unladenMass}
m={m}
ship={ship}
onSelect={onSelect}
warning={warning}
diffDetails={diffDetails.bind(ship, this.context.language)}

View File

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

View File

@@ -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="m702.66 178.87-173.25 190.21 15.625 13.721 170.9-190.4zm-31.01-31.714-202.41 169.1 16.418 14.417 198.76-170.43z" />
<rect transform="matrix(-.7071 -.7071 .7071 -.7071 429.34 1036.2)" x="387.09" y="420.77" width="84.379" height="16.859" />
</g>
);
</g>);
}
}

View File

@@ -7,7 +7,6 @@ import * as IT from './it';
import * as RU from './ru';
import * as PL from './pl';
import * as PT from './pt';
import * as CN from './cn';
import * as d3 from 'd3';
let fallbackTerms = EN.terms;
@@ -28,7 +27,6 @@ export function getLanguage(langCode) {
case 'ru': lang = RU; break;
case 'pl': lang = PL; break;
case 'pt': lang = PT; break;
case 'cn': lang = CN; break;
default:
lang = EN;
}
@@ -96,6 +94,5 @@ export const Languages = {
fr: 'Français',
ru: 'ру́сский',
pl: 'polski',
pt: 'português',
cn: '中文'
pt: 'português'
};

View File

@@ -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: ['AM', 'PM'],
days: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
shortDays: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
shortMonths: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']
};
export { default as terms } from './cn.json';

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -63,7 +63,7 @@
"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_BOOST": "With full fuel tank and 4 pips to ENG",
"TT_SUMMARY_BOOST_INTERVAL": "Time between each boost with 4 pips to ENG",
"TT_SUMMARY_BOOST_TIME": "Time between each boost with 4 pips to ENG",
"TT_SUMMARY_BOOST_NONFUNCTIONAL": "Power distributor not able to supply enough power to boost",
"TT_SUMMARY_SHIELDS": "Raw shield strength, including boosters",
"TT_SUMMARY_SHIELDS_SCB": "Raw shield strength, including boosters and SCBs",
@@ -81,8 +81,6 @@
"TT_SUMMARY_UNLADEN_TOTAL_JUMP": "Farthest possible range with no cargo, a full fuel tank, and jumping as far as possible each time",
"TT_SUMMARY_LADEN_TOTAL_JUMP": "Farthest possible range with full cargo, a full fuel tank, and jumping as far as possible each time",
"HELP_MODIFICATIONS_MENU": "Click on a number to enter a new value, or drag along the bar for small changes",
"PHRASE_FAIL_EDENGINEER": "Failed to send to EDEngineer (Launch EDEngineer and make sure the API is started then refresh the page.)",
"PHRASE_FIREFOX_EDENGINEER": "Sending to EDEngineer is not compatible with Firefox's security settings. Please try again with Chrome.",
"am": "Auto Field-Maintenance Unit",
"bh": "Bulkheads",
"bl": "Beam Laser",
@@ -153,7 +151,6 @@
"sfn": "Shutdown Field Neutraliser",
"sg": "Shield Generator",
"ss": "Surface Scanners",
"sua": "Supercruise Assist",
"t": "thrusters",
"tp": "Torpedo Pylon",
"ul": "Burst Laser",
@@ -175,7 +172,6 @@
"ammunition": "Ammo",
"secs": "s",
"rebuildsperbay": "Rebuilds per bay",
"mroll": "Roll",
"worst": "Worst",
"average": "Average",
"random": "Random",
@@ -205,7 +201,7 @@
"internal protection": "Internal protection",
"external protection": "External protection",
"engagement range": "Engagement range",
"boost interval": "Boost interval",
"boost time": "Boost time",
"total": "Total",
"ammo": "Ammunition maximum",
"boot": "Boot time",
@@ -324,7 +320,6 @@
"never": "never",
"stock": "stock",
"boost": "boost",
"tab_defence": "defence",
"federation rank 1": "Recruit",
"federation rank 2": "Cadet",
"federation rank 3": "Midshipman",

View File

@@ -59,7 +59,7 @@
"empty all": "vide tout",
"Enter Name": "Entrer nom",
"Explorer": "explorateur",
"farthest range": "gamme la plus rapide",
"fastest range": "gamme la plus rapide",
"fuel": "carburant",
"fuel level": "niveau de carburant",
"full tank": "Réservoir plein",

View File

@@ -2,15 +2,15 @@ export const formats = {
decimal: ',',
thousands: '.',
grouping: [3],
currency: ['$', ''],
currency: ['', ''],
dateTime: '%A, %e de %B de %Y, %X',
date: '%d/%m/%Y',
time: '%H:%M:%S',
periods: ['AM', 'PM'],
days: ['domingo', 'segunda', 'terça', 'quarta', 'quinta', 'sexta', 'sábado'],
shortDays: ['dom', 'seg', 'ter', 'qua', 'qui', 'sex', 'sab'],
months: ['janeiro', 'fevereiro', 'março', 'abril', 'maio', 'junho', 'julho', 'agosto', 'setembro', 'outubro', 'novembro', 'dezembro'],
shortMonths: ['jan', 'fev', 'mar', 'abr', 'mai', 'jun', 'jul', 'ago', 'set', 'out', 'nov', 'dez']
days: ['domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado'],
shortDays: ['dom', 'lun', 'mar', 'mié', 'jue', 'vie', 'sáb'],
months: ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'],
shortMonths: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic']
};
export { default as terms } from './pt.json';

File diff suppressed because one or more lines are too long

View File

@@ -16,16 +16,13 @@
"PHRASE_ENGAGEMENT_RANGE": "Дистанция между кораблём и целью",
"PHRASE_SELECT_BLUEPRINT": "Нажмите чтобы выбрать чертёж",
"PHRASE_BLUEPRINT_WORST": "Худшие основные значения для чертежа",
"PHRASE_BLUEPRINT_FIFTY": "50% значения для чертежа",
"PHRASE_BLUEPRINT_SEVEN_FIVE": "75% значения для чертежа",
"PHRASE_BLUEPRINT_RANDOM": "Случайный выбор между худшими и лучшими значениями для этого чертежа",
"PHRASE_BLUEPRINT_BEST": "Лучшие основные значения для чертежа",
"PHRASE_BLUEPRINT_EXTREME": "Лучшие положительные и худшие отрицательные основные значения для чертежа",
"PHRASE_BLUEPRINT_RESET": "Сбросить все модификаторы и чертеж",
"PHRASE_BLUEPRINT_RESET": "Убрать все изменения и чертёж",
"PHRASE_SELECT_SPECIAL": "Нажмите, чтобы выбрать экспериментальный эффект",
"PHRASE_NO_SPECIAL": "Без экспериментального эффекта",
"PHRASE_SHOPPING_LIST": "Станции, что продают эту сборку",
"PHRASE_SHOPPING_MATS": "Материалы которые нужны для сборки",
"PHRASE_REFIT_SHOPPING_LIST": "Станции, что продают необходимые модули",
"PHRASE_TOTAL_EFFECTIVE_SHIELD": "Общий урон, что может быть нанесён в каждым типе, если используются все щитонакопители",
"PHRASE_TIME_TO_LOSE_SHIELDS": "Щиты продержатся",
@@ -39,11 +36,9 @@
"PHRASE_TIME_TO_LOSE_ARMOUR": "Броня продержится",
"PHRASE_MODULE_PROTECTION_EXTERNAL": "Защита гнёзд",
"PHRASE_MODULE_PROTECTION_INTERNAL": "Защита всех остальных модулей",
"PHRASE_OVERALL_DAMAGE": "Разбивка источников устойчивого ДПС",
"PHRASE_SHIELD_DAMAGE": "Подробности источников поддерживаемого ДПС против щитов",
"PHRASE_ARMOUR_DAMAGE": "Подробности источников поддерживаемого ДПС против брони",
"PHRASE_TIME_TO_REMOVE_SHIELDS": "Снимет щиты за",
"PHRASE_MULTI_CREW_CAPACITOR_POINTS": "Щелкните правой кновкой мыши чтобы объединить в группу.",
"TT_TIME_TO_REMOVE_SHIELDS": "Непрерывным огнём из всех орудий",
"PHRASE_TIME_TO_REMOVE_ARMOUR": "Снимет броню за",
"TT_TIME_TO_REMOVE_ARMOUR": "Непрерывным огнём из всех орудий",
@@ -63,10 +58,8 @@
"TT_SUMMARY_SPEED": "С полным топливным баком и 4 пунктами в ДВИ",
"TT_SUMMARY_SPEED_NONFUNCTIONAL": "Маневровые двигатели выключены или превышена максимальная масса с топливом и грузом",
"TT_SUMMARY_BOOST": "С полным топливным баком и 4 пунктами в ДВИ",
"TT_SUMMARY_BOOST_INTERVAL": "Время заполнения с 4 пунктами в СИС",
"TT_SUMMARY_BOOST_NONFUNCTIONAL": "Распределитель питания не может обеспечить достаточно энергии для форсажа",
"TT_SUMMARY_SHIELDS": "Чистая сила щита, включая усилители",
"TT_SUMMARY_SHIELDS_SCB": "Прочность щита, включая бустеры и SCB",
"TT_SUMMARY_SHIELDS_NONFUNCTIONAL": "Шитогенератор отсутствует или выключен",
"TT_SUMMARY_INTEGRITY": "Целостность корабля, включая переборки и наборы для усиления корпуса",
"TT_SUMMARY_HULL_MASS": "Масса корпуса без каких-либо модулей",
@@ -81,14 +74,11 @@
"TT_SUMMARY_UNLADEN_TOTAL_JUMP": "Самая дальняя общая дистанция без груза, с полным топливным баком и при прыжках на максимальное расстояние",
"TT_SUMMARY_LADEN_TOTAL_JUMP": "Самая дальняя общая дистанция с полным грузовым отсеком, с полным топливным баком и при прыжках на максимальное расстояние",
"HELP_MODIFICATIONS_MENU": "Нажмите на номер, чтобы ввести новое значение, или потяните вдоль полосы для малых изменений",
"PHRASE_FAIL_EDENGINEER": "Не удалось отправить в EDEngineer (запустите EDEngineer и убедитесь, что API запущен, затем обновите страницу).",
"PHRASE_FIREFOX_EDENGINEER": "Отправка в EDEngineer несовместима с настройками безопасности Firefox. Пожалуйста, попробуйте еще раз в Google Chrome.",
"am": "Блок Автом. Полевого Ремонта",
"bh": "Переборки",
"bl": "Пучковый лазер",
"bsg": "Двухпоточный щитогенератор",
"c": "Орудие",
"causres": "Caustic resistance",
"cc": "Контроллер магнитного снаряда для сбора",
"ch": "Разбрасыватель дипольных отражателей",
"cr": "Грузовой стеллаж",
@@ -108,17 +98,14 @@
"kw": "Сканер преступников",
"ls": "Система жизнеобеспечения",
"mc": "Многоствольное орудие",
"axmc": "Многоствольное орудие АИ",
"ml": "Проходочный лазер",
"mr": "Ракетный лоток",
"axmr": "Блок ракет АИ",
"mrp": "Набор для усиления модуля",
"nl": "Мины",
"pa": "Ускоритель плазмы",
"pas": "Комплект для сближения с планетой",
"pc": "Контроллер магнитного снаряда для геологоразведки",
"pce": "Каюта пассажира эконом-класса",
"passenger capacity": "количество пассажиров",
"pci": "Каюта пассажира бизнес-класса",
"pcm": "Каюта пассажира первого класса",
"pcq": "Каюта пассажира класса люкс",
@@ -126,46 +113,20 @@
"pl": "Импульсный лазер",
"po": "Точечная оборона",
"pp": "Силовая установка",
"gpp": "Силовая установка Cтражей",
"gpd": "Гибридный распределитель питания Стражей",
"gpc": "Плазменная пушка Стражей",
"ggc": "Пушка Гаусса Стражей",
"gsrp": "Набор для усиления щита Стражей",
"gfsb": "Ускоритель FSD Стражей",
"ghrp": "Набор для усиления корпуса Стражей",
"gmrp": "Набор для усиления модуля Стражей",
"pwa": "Анализатор импульсных волн",
"abl": "Абразивный бластер",
"scl": "Пусковая установка для сейсмических снарядов",
"sdm": "Вытесняющая ракета для добычи глубинных залежей",
"tbsc": "Шоковое орудие",
"gsc": "Осколочное орудие Стражей",
"psg": "Призматический щитогенератор",
"pv": "Гараж для планетарного транспорта",
"rf": "Устройство переработки",
"rfl": "Зенитная установка (снаряды с дистанционным подрывом)",
"rg": "Электромагнитная пушка",
"rsl": "Дроны-исследователи",
"s": "Сенсоры",
"sb": "Усилитель щита",
"sc": "Сканер обнаружения",
"scb": "Щитонакопитель",
"sfn": "Нейтрализатор глушащего поля",
"sg": "Щитогенератор",
"ss": "Сканер поверхностей",
"sua": "Помощь в гиперкрейсерском режиме",
"t": "Маневровые двигатели",
"tp": "Торпедная стойка",
"ul": "Пульсирующие лазеры",
"Send To EDEngineer": "Отправить в EDEngineer",
"ws": "Сканер следа FSD",
"rpl": "Дроны-ремонтники",
"rcpl": "ДРоны-разведчики",
"xs": "Сканер «инопланетянин»",
"tbem": "Блок энзимных ракет",
"tbrfl": "Установка для стрельбы стреловидными снарядами с дистанционным подрывом",
"dtl": "Дроны-очистители",
"mahr": "Набор для усиления корпуса из Метасплава",
"emptyrestricted": "пусто (ограниченно)",
"damage dealt to": "Урон нанесён",
"damage received from": "Урон получен от",
@@ -175,7 +136,6 @@
"ammunition": "Припасы",
"secs": "с",
"rebuildsperbay": "Построек за полосу",
"mroll": "Roll",
"worst": "Худшее",
"average": "Среднее",
"random": "Случайное",
@@ -188,7 +148,6 @@
"dpssdps": "Урон в секунду (поддерживаемый урон в секунду)",
"eps": "Энергия в секунду",
"epsseps": "Энергия в секунду (поддерживаемая энергия в секунду)",
"fallofffromrange": "Спад",
"hps": "Нагрев в секунду",
"hpsshps": "Нагрев в секунду (поддерживаемый нагрев в секунду)",
"damage by": "Урон",
@@ -205,15 +164,13 @@
"internal protection": "Внутренняя защита",
"external protection": "Внешняя защита",
"engagement range": "Боевое расстояние",
"boost interval": "Интервал повышения",
"total": "Всего",
"ammo": "Макс. боекомплект",
"ammo": "Боекомплект",
"boot": "Время загрузки",
"hacktime": "Время взлома",
"brokenregen": "Скорость восстановления при пробое",
"burst": "Длина очереди",
"burstrof": "Скорострельность очереди",
"clip": "Размер боекомплекта",
"clip": "Боекомплект",
"damage": "Урон",
"distdraw": "Тяга распределителя",
"duration": "Продолжительность",
@@ -242,14 +199,11 @@
"rof": "Скорострельность",
"angle": "Угол сканера",
"scanrate": "Скорость сканера",
"proberadius": "Радиус зонда",
"scantime": "Время сканирования",
"shield": "Щит",
"armour": "Броня",
"shieldboost": "Усиление щитов",
"shieldreinforcement": "Усилитель щита",
"shotspeed": "Скорость выстрела",
"shotdmg": "Урон за выстрел(DPS)",
"spinup": "Время раскрутки",
"syscap": "Ресурс систем",
"sysrate": "Перезарядка систем",
@@ -280,12 +234,9 @@
"explosive": "Взрывч.",
"kinetic": "Механич.",
"thermal": "Тепл.",
"caustic": "Каустик",
"generator": "Генератор",
"boosters": "Усилители",
"cells": "Ячейки",
"shield addition": "ДОбавления к щиту",
"jump addition": "ДОбавления к прыжку",
"bulkheads": "Переборки",
"reinforcement": "Усилители",
"power and costs": "Энергия и стоимость",
@@ -318,45 +269,13 @@
"opponent": "Противник",
"opponent's shields": "Щит противника",
"opponent's armour": "Броня противника",
"overall damage": "overall damage",
"shield damage sources": "источники урона по щиту",
"armour damage sources": "источники урона по броне",
"never": "Никогда",
"stock": "базовый",
"boost": "форсаж",
"tab_defence": "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": "Минимальный ранг империи для покупки",
"\/s": "\/с",
"m\/s": "м\/с",
"/s": "/с",
"m/s": "м/с",
"Ls": "Св.сек",
"LY": "Св.лет",
"CR": "кр.",
@@ -380,7 +299,7 @@
"edit data": "Редактирование",
"empty all": "пусто все",
"Enter Name": "Введите имя",
"farthest range": "быстрый диапазон",
"fastest range": "быстрый диапазон",
"fuel level": "уровень топлива",
"full tank": "Полный бак",
"internal compartments": "внутренние отсеки",
@@ -396,6 +315,7 @@
"about": "О ...",
"action": "Действие",
"added": "Добавлено",
"armour": "Броня",
"available": "доступно",
"backup": "Резервная копия",
"bins": "контейнеры",
@@ -443,7 +363,7 @@
"repair": "Починка",
"ret": "Убр.",
"retracted": "Убрано",
"ROF": "В\/сек",
"ROF": "В/сек",
"save": "Сохранить",
"sell": "Продать",
"settings": "Настройки",

View File

@@ -94,6 +94,39 @@ export default class AboutPage extends Page {
</a>
.
</p>
<h3>Supporting Coriolis</h3>
<p>
Coriolis is an open source project, and I work on it in my free time.
I have set up a patreon at{' '}
<a href="https://www.patreon.com/coriolis_elite">
patreon.com/coriolis_elite
</a>
, which will be used to keep Coriolis up to date and the servers
running.
</p>
<form
action="https://www.paypal.com/cgi-bin/webscr"
method="post"
target="_top"
>
<input type="hidden" name="cmd" value="_s-xclick" />
<input type="hidden" name="hosted_button_id" value="SJBKT2SWEEU68" />
<input
type="image"
src="https://www.paypalobjects.com/en_AU/i/btn/btn_donate_SM.gif"
border="0"
name="submit"
alt="PayPal The safer, easier way to pay online!"
/>
<img
alt=""
border="0"
src="https://www.paypalobjects.com/en_AU/i/scr/pixel.gif"
width="1"
height="1"
/>
</form>
</div>
);
}

View File

@@ -19,6 +19,7 @@ import {
LinkIcon,
ShoppingIcon,
MatIcon,
OrbisIcon
} from '../components/SvgIcons';
import LZString from 'lz-string';
import ShipSummaryTable from '../components/ShipSummaryTable';
@@ -36,6 +37,7 @@ import OutfittingSubpages from '../components/OutfittingSubpages';
import ModalExport from '../components/ModalExport';
import ModalPermalink from '../components/ModalPermalink';
import ModalShoppingList from '../components/ModalShoppingList';
import ModalOrbis from '../components/ModalOrbis';
/**
* Document Title Generator
@@ -678,6 +680,23 @@ export default class OutfittingPage extends Page {
this.context.showModal(<ModalPermalink url={window.location.href} />);
}
/**
* Generate Orbis link
*/
_genOrbis() {
const data = {};
const ship = this.state.ship;
ship.coriolisId = ship.id;
data.coriolisShip = ship;
data.url = window.location.href;
data.title = this.state.buildName || ship.id;
data.description = this.state.buildName || ship.id;
data.ShipName = ship.id;
data.Ship = ship.id;
console.log(data);
this.context.showModal(<ModalOrbis ship={data} />);
}
/**
* Open up a window for EDDB with a shopping list of our components
*/
@@ -927,6 +946,13 @@ export default class OutfittingPage extends Page {
>
<LinkIcon className="lg" />
</button>
<button
onClick={this._genOrbis}
onMouseOver={termtip.bind(null, 'PHASE_UPLOAD_ORBIS')}
onMouseOut={hide}
>
<OrbisIcon className="lg" />
</button>
<button
onClick={this._genShoppingList}
onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_MATS')}

View File

@@ -50,6 +50,19 @@ export default class Page extends React.Component {
}
}
/**
* Pages are 'pure' components that only render when props, state, or context changes.
* This method performs a shallow comparison to determine change.
*
* @param {Object} np Next/Incoming properties
* @param {Object} ns Next/Incoming state
* @param {Object} nc Next/Incoming context
* @return {Boolean} True if props, state, or context has changed
*/
shouldComponentUpdate(np, ns, nc) {
return !shallowEqual(this.props, np) || !shallowEqual(this.state, ns) || !shallowEqual(this.context, nc);
}
/**
* Update the window title upon mount
*/
@@ -62,14 +75,6 @@ export default class Page extends React.Component {
*/
componentDidMount() {
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) {
}
}
/**

View File

@@ -126,14 +126,12 @@ export default class ShipyardPage extends Page {
title: 'Coriolis EDCD Edition - Shipyard',
shipPredicate: 'name',
shipDesc: true,
shipSummaries: ShipyardPage.cachedShipSummaries,
compare: {},
groupCompared: false,
shipSummaries: ShipyardPage.cachedShipSummaries
};
}
/**
* Higlight the current ship in the table on mouse over
* Higlight the current ship in the table
* @param {String} shipId Ship Id
* @param {SyntheticEvent} event Event
*/
@@ -142,24 +140,6 @@ export default class ShipyardPage extends Page {
this.setState({ shipId });
}
/**
* Toggle compare highlighting for ships in the table
* @param {String} shipId Ship Id
*/
_toggleCompare(shipId) {
let compare = this.state.compare;
compare[shipId] = !compare[shipId];
this.setState({ compare });
}
/**
* Toggle grouping of compared ships in the table
* @private
*/
_toggleGroupCompared() {
this.setState({groupCompared: !this.state.groupCompared})
}
/**
* Update state with the specified sort predicates
* @param {String} shipPredicate Sort predicate - property name
@@ -200,11 +180,10 @@ export default class ShipyardPage extends Page {
style={{ height: '1.5em' }}
className={cn({
highlighted: noTouch && this.state.shipId === s.id,
comparehighlight: this.state.compare[s.id],
})}
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
onClick={() => this._toggleCompare(s.id)}
>
<td className="ri">{s.manufacturer}</td>
<td className="ri">{fInt(s.retailCost)}</td>
<td className="ri cap">{translate(SizeMap[s.class])}</td>
<td className="ri">{fInt(s.crew)}</td>
@@ -255,7 +234,7 @@ export default class ShipyardPage extends Page {
let hide = this.context.tooltip.bind(null, null);
let fInt = formats.int;
let fRound = formats.round;
let { shipSummaries, shipPredicate, shipPredicateIndex, compare, groupCompared } = this.state;
let { shipSummaries, shipPredicate, shipPredicateIndex } = this.state;
let sortShips = (predicate, index) =>
this._sortShips.bind(this, predicate, index);
@@ -288,15 +267,6 @@ export default class ShipyardPage extends Page {
valB = val;
}
if (groupCompared) {
if (compare[a.id] && !compare[b.id]) {
return -1;
}
if (!compare[a.id] && compare[b.id]) {
return 1;
}
}
if (valA == valB) {
if (a.name > b.name) {
return 1;
@@ -328,10 +298,8 @@ export default class ShipyardPage extends Page {
style={{ height: '1.5em' }}
className={cn({
highlighted: noTouch && this.state.shipId === s.id,
comparehighlight: this.state.compare[s.id],
})}
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
onClick={() => this._toggleCompare(s.id)}
>
<td className="le">
<Link href={'/outfit/' + s.id}>{s.name} {s.beta === true ? '(Beta)' : null}</Link>
@@ -342,10 +310,18 @@ export default class ShipyardPage extends Page {
}
return (
<div className="page" style={{fontSize: sizeRatio + 'em'}}>
<div className="content-wrapper">
<div className="shipyard-table-wrapper">
<table style={{width: '12em', position: 'absolute', zIndex: 1}} className="shipyard-table">
<div className="page" style={{ fontSize: sizeRatio + 'em' }}>
<div
style={{
whiteSpace: 'nowrap',
margin: '0 auto',
fontSize: '0.8em',
position: 'relative',
display: 'inline-block',
maxWidth: '100%'
}}
>
<table style={{ width: '12em', position: 'absolute', zIndex: 1 }}>
<thead>
<tr>
<th className="le rgt">&nbsp;</th>
@@ -363,10 +339,17 @@ export default class ShipyardPage extends Page {
{shipRows}
</tbody>
</table>
<div style={{ overflowX: 'auto', maxWidth: '100%' }}>
<table style={{ marginLeft: 'calc(12em - 1px)', zIndex: 0 }} className="shipyard-table">
<div style={{ overflowX: 'scroll', maxWidth: '100%' }}>
<table style={{ marginLeft: 'calc(12em - 1px)', zIndex: 0 }}>
<thead>
<tr className="main">
<th
rowSpan={3}
className="sortable"
onClick={sortShips('manufacturer')}
>
{translate('manufacturer')}
</th>
<th>&nbsp;</th>
<th
rowSpan={3}
@@ -614,10 +597,6 @@ export default class ShipyardPage extends Page {
</table>
</div>
</div>
<div className="table-tools" >
<label><input type="checkbox" checked={this.state.groupCompared} onClick={() => this._toggleGroupCompared()}/>Group highlighted ships</label>
</div>
</div>
</div>
);
}

View File

@@ -14,9 +14,8 @@ export function jumpRange(mass, fsd, fuel, ship) {
const fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass;
let jumpAddition = 0;
if (ship) {
mass += ship.reserveFuelCapacity || 0;
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') {
jumpAddition += module.m.getJumpBoost();
}
}
@@ -341,34 +340,67 @@ export function shieldMetrics(ship, sys) {
const maxSysResistance = this.sysResistance(4);
let shield = {};
const dimReturnLine = (res) => 1 - (1 - res) * 0.7;
const shieldGeneratorSlot = ship.findInternalByGroup('sg');
if (shieldGeneratorSlot && shieldGeneratorSlot.enabled && shieldGeneratorSlot.m) {
const shieldGenerator = shieldGeneratorSlot.m;
let res = {
kin: shieldGenerator.kinres,
therm: shieldGenerator.thermres,
expl: shieldGenerator.explres
};
// Boosters
let boost = 1;
let boosterExplDmg = 1;
let boosterKinDmg = 1;
let boosterThermDmg = 1;
// const explDim = dimReturnLine(shieldGenerator.explres);
// const thermDim = dimReturnLine(shieldGenerator.thermres);
// const kinDim = dimReturnLine(shieldGenerator.kinres);
for (let slot of ship.hardpoints) {
if (slot.enabled && slot.m && slot.m.grp == 'sb') {
boost += slot.m.getShieldBoost();
res.expl += slot.m.getExplosiveResistance();
res.kin += slot.m.getKineticResistance();
res.therm += slot.m.getThermalResistance();
boosterExplDmg = boosterExplDmg * (1 - slot.m.getExplosiveResistance());
boosterKinDmg = boosterKinDmg * (1 - slot.m.getKineticResistance());
boosterThermDmg = boosterThermDmg * (1 - slot.m.getThermalResistance());
}
if (slot.m && slot.m.grp == 'gsrp') {
}
}
// Calculate diminishing returns for boosters
// Diminishing returns not currently in-game
// boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5);
// Remove base shield generator strength
boost -= 1;
// if (res.expl > explDim) {
// const overage = (res.expl - explDim) * 0.5;
// res.expl = explDim + overage;
// boosterExplDmg = explDim + overage;
// }
//
// if (res.therm > thermDim) {
// const overage = (res.therm - thermDim) * 0.5;
// res.therm = thermDim + overage;
// boosterThermDmg = thermDim + overage;
// }
//
// if (res.kin > kinDim) {
// const overage = (res.kin - kinDim) * 0.5;
// res.kin = kinDim + overage;
// boosterKinDmg = kinDim + overage;
// }
let shieldAddition = 0;
if (ship) {
for (const module of ship.internal) {
if (module && module.m && module.m.grp === 'gsrp' && module.enabled) {
if (module && module.m && module.m.grp === 'gsrp') {
shieldAddition += module.m.getShieldAddition();
}
}
@@ -433,7 +465,6 @@ export function shieldMetrics(ship, sys) {
boosters: boostersStrength,
addition: shieldAddition,
cells: ship.shieldCells,
summary: generatorStrength + boostersStrength + shieldAddition,
total: generatorStrength + boostersStrength + ship.shieldCells + shieldAddition,
recover,
recharge,
@@ -466,7 +497,7 @@ export function shieldMetrics(ship, sys) {
*/
let sgExplosiveDmg = 1 - shieldGenerator.getExplosiveResistance();
let sgSbExplosiveDmg = diminishingReturnsShields(sgExplosiveDmg, sgExplosiveDmg * boosterExplDmg);
let sgSbExplosiveDmg = diminishDamageMult(sgExplosiveDmg * 0.7, (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg);
/** @type {ShieldDamageMults} */
shield.explosive = {
generator: sgExplosiveDmg,
@@ -478,7 +509,7 @@ export function shieldMetrics(ship, sys) {
};
let sgKineticDmg = 1 - shieldGenerator.getKineticResistance();
let sgSbKineticDmg = diminishingReturnsShields(sgKineticDmg, sgKineticDmg * boosterKinDmg);
let sgSbKineticDmg = diminishDamageMult(sgKineticDmg * 0.7, (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg);
/** @type {ShieldDamageMults} */
shield.kinetic = {
generator: sgKineticDmg,
@@ -490,7 +521,7 @@ export function shieldMetrics(ship, sys) {
};
let sgThermalDmg = 1 - shieldGenerator.getThermalResistance();
let sgSbThermalDmg = diminishingReturnsShields(sgThermalDmg , sgThermalDmg * boosterThermDmg);
let sgSbThermalDmg = diminishDamageMult(sgThermalDmg * 0.7, (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg);
/** @type {ShieldDamageMults} */
shield.thermal = {
generator: sgThermalDmg,
@@ -530,28 +561,58 @@ export function armourMetrics(ship) {
let moduleArmour = 0;
let moduleProtection = 1;
const bulkheads = ship.bulkheads.m;
let hullExplDmgs = [];
let hullKinDmgs = [];
let hullThermDmgs = [];
let hullCausDmgs = [];
let hullExplDmg = 1;
let hullKinDmg = 1;
let hullThermDmg = 1;
let hullCausDmg = 1;
// const dimReturnLine = (res) => 1 - (1 - res) * 0.7;
// let res = {
// kin: 0,
// therm: 0,
// expl: 0
// };
// Armour from HRPs and module armour from MRPs
for (let slot of ship.internal) {
if (slot.m && slot.enabled && (slot.m.grp === 'hr' || slot.m.grp === 'ghrp' || slot.m.grp == 'mahr')) {
if (slot.m && (slot.m.grp === 'hr' || slot.m.grp === 'ghrp' || slot.m.grp == 'mahr')) {
armourReinforcement += slot.m.getHullReinforcement();
// Hull boost for HRPs is applied against the ship's base armour
armourReinforcement += ship.baseArmour * slot.m.getModValue('hullboost') / 10000;
hullExplDmgs.push(1 - slot.m.getExplosiveResistance());
hullKinDmgs.push(1 - slot.m.getKineticResistance());
hullThermDmgs.push(1 - slot.m.getThermalResistance());
hullCausDmgs.push(1 - slot.m.getCausticResistance());
// res.expl += slot.m.getExplosiveResistance();
// res.kin += slot.m.getKineticResistance();
// res.therm += slot.m.getThermalResistance();
hullExplDmg = hullExplDmg * (1 - slot.m.getExplosiveResistance());
hullKinDmg = hullKinDmg * (1 - slot.m.getKineticResistance());
hullThermDmg = hullThermDmg * (1 - slot.m.getThermalResistance());
hullCausDmg = hullCausDmg * (1 - slot.m.getCausticResistance());
}
if (slot.m && slot.enabled && (slot.m.grp == 'mrp' || slot.m.grp == 'gmrp')) {
if (slot.m && (slot.m.grp == 'mrp' || slot.m.grp == 'gmrp')) {
moduleArmour += slot.m.getIntegrity();
moduleProtection = moduleProtection * (1 - slot.m.getProtection());
}
}
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 = {
bulkheads: armourBulkheads,
reinforcement: armourReinforcement,
@@ -568,8 +629,8 @@ export function armourMetrics(ship) {
total: 1
};
let armourExplDmg = 1 - ship.bulkheads.m.getExplosiveResistance();
let armourReinforcedExplDmg = diminishingReturnsArmour(armourExplDmg, ...hullExplDmgs);
let armourExplDmg = diminishDamageMult(0.7, 1 - ship.bulkheads.m.getExplosiveResistance());
let armourReinforcedExplDmg = diminishDamageMult(0.7, (1 - ship.bulkheads.m.getExplosiveResistance()) * hullExplDmg);
armour.explosive = {
bulkheads: armourExplDmg,
reinforcement: armourReinforcedExplDmg / armourExplDmg,
@@ -577,8 +638,8 @@ export function armourMetrics(ship) {
res: 1 - armourReinforcedExplDmg
};
let armourKinDmg = 1 - ship.bulkheads.m.getKineticResistance();
let armourReinforcedKinDmg = diminishingReturnsArmour(armourKinDmg, ...hullKinDmgs);
let armourKinDmg = diminishDamageMult(0.7, 1 - ship.bulkheads.m.getKineticResistance());
let armourReinforcedKinDmg = diminishDamageMult(0.7, (1 - ship.bulkheads.m.getKineticResistance()) * hullKinDmg);
armour.kinetic = {
bulkheads: armourKinDmg,
reinforcement: armourReinforcedKinDmg / armourKinDmg,
@@ -586,8 +647,8 @@ export function armourMetrics(ship) {
res: 1 - armourReinforcedKinDmg
};
let armourThermDmg = 1 - ship.bulkheads.m.getThermalResistance();
let armourReinforcedThermDmg = diminishingReturnsArmour(armourThermDmg, ...hullThermDmgs);
let armourThermDmg = diminishDamageMult(0.7, 1 - ship.bulkheads.m.getThermalResistance());
let armourReinforcedThermDmg = diminishDamageMult(0.7, (1 - ship.bulkheads.m.getThermalResistance()) * hullThermDmg);
armour.thermal = {
bulkheads: armourThermDmg,
reinforcement: armourReinforcedThermDmg / armourThermDmg,
@@ -595,8 +656,8 @@ export function armourMetrics(ship) {
res: 1 - armourReinforcedThermDmg
};
let armourCausDmg = 1 - ship.bulkheads.m.getCausticResistance();
let armourReinforcedCausDmg = diminishingReturnsArmour(armourCausDmg, ...hullCausDmgs);
let armourCausDmg = diminishDamageMult(0.7, 1 - ship.bulkheads.m.getCausticResistance());
let armourReinforcedCausDmg = diminishDamageMult(0.7, (1 - ship.bulkheads.m.getCausticResistance()) * hullCausDmg);
armour.caustic = {
bulkheads: armourCausDmg,
reinforcement: armourReinforcedCausDmg / armourCausDmg,
@@ -842,14 +903,12 @@ export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour
shields: {
range: 1,
sys: opponentHasShields ? opponentShields.absolute.sys : 1,
resistance: 1,
dpe: 1
resistance: 1
},
armour: {
range: 1,
hardness: 1,
resistance: 1,
dpe: 1
resistance: 1
}
}
};
@@ -909,19 +968,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.armour.total = weapon.damage.armour.absolute + weapon.damage.armour.explosive + weapon.damage.armour.kinetic + weapon.damage.armour.thermal;
weapon.effectiveness.shields.resistance *= shieldsResistance;
weapon.effectiveness.armour.resistance *= armourResistance;
weapon.effectiveness.shields.total = weapon.effectiveness.shields.range * weapon.effectiveness.shields.sys * weapon.effectiveness.shields.resistance;
weapon.effectiveness.armour.total = weapon.effectiveness.armour.range * weapon.effectiveness.armour.resistance * weapon.effectiveness.armour.hardness;
weapon.effectiveness.shields.dpe = weapon.damage.shields.total / m.getEps();
weapon.effectiveness.armour.dpe = weapon.damage.armour.total / m.getEps();
return weapon;
}
@@ -963,10 +1014,7 @@ export function timeToDrainWep(ship, wep) {
*/
export function timeToDeplete(amount, dps, eps, capacity, recharge) {
const drainPerSecond = eps - recharge;
// If there is nothing to remove, we're don instantly
if (!amount) {
return 0;
} if (drainPerSecond <= 0) {
if (drainPerSecond <= 0) {
// Simple result
return amount / dps;
} else {
@@ -985,50 +1033,15 @@ export function timeToDeplete(amount, dps, eps, capacity, recharge) {
}
/**
* Checks whether diminishing returns should be applied to shield damage
* multipliers and does so if necessary.
* @param {number} shieldMult Damage multiplier of shield generator
* @param {number} combinedMult Damage multiplier of shields and shield boosters
* @returns {number} Overall damage multiplier
* Applies diminishing returns to resistances.
* @param {number} diminishFrom The base resistance up to which no diminishing returns are applied.
* @param {number} damageMult Resistance as damage multiplier
* @returns {number} Actual damage multiplier
*/
export function diminishingReturnsShields(shieldMult, combinedMult) {
let max = shieldMult * 0.7;
if (combinedMult < max) {
return mapIntoDiminishingRange(max / 2, max, combinedMult);
export function diminishDamageMult(diminishFrom, damageMult) {
if (damageMult > diminishFrom) {
return damageMult;
} 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);
}

View File

@@ -57,7 +57,6 @@ export const ModuleGroupToName = {
ghrp: 'Guardian Hull Reinforcement Package',
gmrp: 'Guardian Module Reinforcement Package',
mahr: 'Meta Alloy Hull Reinforcement Package',
sua: 'Supercruise Assist',
// Hard Points
bl: 'Beam Laser',
@@ -207,7 +206,7 @@ export const ShipFacets = [
i: 9
},
{ // 10
title: 'farthest range',
title: 'fastest range',
props: ['unladenFastestRange', 'ladenFastestRange'],
lbls: ['unladen', 'laden'],
unit: 'LY',

View File

@@ -1,8 +1,7 @@
import * as ModuleUtils from './ModuleUtils';
import { Modifications } from 'coriolis-data/dist';
import React from 'react';
import { SI_PREFIXES, STATS_FORMATTING } from './StatsFormatting';
import { includes } from 'lodash';
import { STATS_FORMATTING, SI_PREFIXES } from './StatsFormatting';
/**
* Module - active module in a ship's buildout
@@ -42,7 +41,8 @@ export default class Module {
* @return {object} The value of the modification. If it is a numeric value then it is returned as an integer value scaled so that 1.23% == 123
*/
getModValue(name, raw) {
let result = this.mods && this.mods[name] ? this.mods[name] : null;
let baseVal = this[name];
let result = this.mods && this.mods[name] ? this.mods[name] : null;
if ((!raw) && this.blueprint && this.blueprint.special) {
// This module has a special effect, see if we need to alter our returned value
@@ -51,8 +51,7 @@ export default class Module {
// this special effect modifies our returned value
const modification = Modifications.modifications[name];
const multiplier = modification.type === 'percentage' ? 10000 : 100;
if (includes(['explres', 'kinres', 'thermres', 'causres'], name)) {
if (name === 'explres' || name === 'kinres' || name === 'thermres' || name === 'causres') {
// Apply resistance modding mechanisms to special effects subsequently
result = result + modifierActions[name] * (1 - (this[name] + result / multiplier)) * 100;
} else if (modification.method === 'additive') {
@@ -60,7 +59,14 @@ export default class Module {
} else if (modification.method === 'overwrite') {
result = modifierActions[name];
} else {
result = result + modifierActions[name] * multiplier;
// rate of fire is special, as it's really burst interval. Handle that here
let mod = null;
if (name === 'rof') {
mod = 1 / (1 + modifierActions[name]) - 1;
} else {
mod = modifierActions[name];
}
result = (((1 + result / multiplier) * (1 + mod)) - 1) * multiplier;
}
}
}
@@ -99,7 +105,14 @@ export default class Module {
} else if (modification.method === 'overwrite') {
value = null;
} else {
value = ((value / 10000 + 1) / (1 + modifierActions[name]) - 1) * 10000;
// rate of fire is special, as it's really burst interval. Handle that here
let mod = null;
if (name === 'rof') {
mod = 1 / (1 + modifierActions[name]) - 1;
} else {
mod = modifierActions[name];
}
value = ((value / 10000 + 1) / (1 + mod) - 1) * 10000;
}
}
}
@@ -118,13 +131,6 @@ export default class Module {
* @return {Number} The value queried
*/
get(name, modified = true) {
if (name == 'rof' && isNaN(this[name])) {
let fireint = this['fireint'];
if (!isNaN(fireint)) {
this['rof'] = 1 / fireint;
}
}
let val;
if (modified) {
val = this._getModifiedValue(name);
@@ -160,20 +166,14 @@ export default class Module {
modValue = value - baseValue;
} else if (name === 'shieldboost' || name === 'hullboost') {
modValue = (1 + value) / (1 + baseValue) - 1;
} else if (name === 'rof') {
let burst = this.get('burst', true) || 1;
let burstInt = 1 / (this.get('burstrof', true) / 1);
let interval = burst / value;
let newFireint = (interval - (burst - 1) * burstInt);
modValue = newFireint / this['fireint'] - 1;
} else { // multiplicative
modValue = baseValue == 0 ? 0 : value / baseValue - 1;
modValue = value / baseValue - 1;
}
if (modification.type === 'percentage') {
modValue = modValue * 10000;
} else if (modification.type === 'numeric') {
} else if (modification.type === 'numeric' && name !== 'burst' &&
name !== 'burstrof') {
modValue = modValue * 100;
}
@@ -191,14 +191,7 @@ export default class Module {
*/
getPretty(name, modified = true, places = 2) {
const formattingOptions = STATS_FORMATTING[name];
let val;
if (formattingOptions && formattingOptions.synthetic) {
val = (this[formattingOptions.synthetic]).call(this, modified);
} else {
val = this.get(name, modified);
}
val = val || 0;
let val = this.get(name, modified) || 0;
if (formattingOptions && formattingOptions.format.startsWith('pct')) {
return 100 * val;
}
@@ -257,17 +250,12 @@ export default class Module {
} else if (name === 'shieldboost' || name === 'hullboost') {
result = (1 + result) * (1 + modValue) - 1;
} else {
// Rate of fire modifiers are special as they actually are modifiers
// for fire interval. Translate them accordingly here:
if (name == 'rof') {
modValue = 1 / (1 + modValue) - 1;
}
result = result * (1 + modValue);
}
} else if (name === 'burstrof' || name === 'burst') {
} else if (name === 'burstrof') {
// Burst and burst rate of fire are special, as it can not exist but
// have a modification
result = modValue;
result = modValue / 100;
}
}
}
@@ -289,7 +277,11 @@ export default class Module {
formatModifiedValue(name, language, unit, val) {
const formattingOptions = STATS_FORMATTING[name];
if (val === undefined) {
val = this.getPretty(name, true);
if (formattingOptions && formattingOptions.synthetic) {
val = (this[formattingOptions.synthetic]).call(this, true);
} else {
val = this._getModifiedValue(name);
}
}
val = val || 0;
@@ -359,18 +351,14 @@ export default class Module {
if (formattingOptions && formattingOptions.change) {
let changeFormatting = formattingOptions.change;
let baseVal = this[name] || 0;
let baseVal = this[name];
let absVal = this._getModifiedValue(name);
if (changeFormatting === 'additive') {
val = absVal - baseVal;
} else if (changeFormatting === 'multiplicative') {
val = absVal / baseVal - 1;
}
if (Modifications.modifications[name].method === 'overwrite') {
val *= 100;
} else {
val *= 10000;
}
val *= 10000;
}
return val;
}
@@ -584,9 +572,20 @@ export default class Module {
* @return {Number} the falloff of this module
*/
getFalloff(modified = true) {
if (!modified) {
const range = this.getRange(false);
const falloff = this.get('falloff', false);
return (falloff > range ? range : falloff);
}
// Falloff from range is mapped to range
if (modified && this.mods && this.mods['fallofffromrange']) {
if (this.mods && this.mods['fallofffromrange']) {
return this.getRange();
// Need to find out if we have a focused modification, in which case our
// falloff is scaled to range
} else if (this.blueprint && this.blueprint.name === 'Focused') {
const rangeMod = this.getModValue('range') / 10000;
return this.falloff * (1 + rangeMod);
// Standard falloff calculation
} else {
const range = this.getRange();
@@ -640,6 +639,15 @@ export default class Module {
return this.get('protection', modified);
}
/**
* Get the delay for this module
* @param {Boolean} [modified=true] Whether to take modifications into account
* @return {Number} the delay of this module
*/
getDelay(modified = true) {
return this.get('delay', modified);
}
/**
* Get the duration for this module
* @param {Boolean} [modified=true] Whether to take modifications into account
@@ -695,7 +703,8 @@ export default class Module {
let result = 0;
if (this['maxmass']) {
result = this['maxmass'];
if (result && modified && !ModuleUtils.isShieldGenerator(this['grp'])) {
// max mass is only modified for non-shield boosters
if (result && modified && this.grp !== 'sg') {
let mult = this.getModValue('optmass') / 10000;
if (mult) { result = result * (1 + mult); }
}
@@ -784,6 +793,24 @@ export default class Module {
return this.get('distdraw', modified);
}
/**
* Get the thermal load for this module
* @param {Boolean} [modified=true] Whether to take modifications into account
* @return {Number} the thermal load of this module
*/
getThermalLoad(modified = true) {
return this.get('thermload', modified);
}
/**
* Get the rounds per shot for this module
* @param {Boolean} [modified=true] Whether to take modifications into account
* @return {Number} the rounds per shot of this module
*/
getRoundsPerShot(modified = true) {
return this.get('roundspershot', modified);
}
/**
* Get the DPS for this module
* @param {Boolean} [modified=true] Whether to take modifications into account
@@ -807,39 +834,25 @@ export default class Module {
return this.getDps(modified) / this.getEps(modified);
}
/**
* Return the factor that gets applied when calculating certain "sustained"
* values, e.g. `SDPS = this.getSustainedFactor() * DPS`.
* @param {Boolean} [modified=true] Whether to take modifications into account
*/
getSustainedFactor(modified = true) {
let clipSize = this.getClip(modified);
if (clipSize) {
// If auto-loader is applied, effective clip size will be nearly doubled
// as you get one reload for every two shots fired.
if (this.blueprint && this.blueprint.special && this.blueprint.special.edname === 'special_auto_loader' && modified) {
clipSize += clipSize - 1;
}
let burstSize = this.get('burst', modified) || 1;
let rof = this.getRoF(modified);
// rof averages burstfire + pause until next interval but for sustained
// rof we need to take another burst without pause into account
let burstOverhead = (burstSize - 1) / (this.get('burstrof', modified) || 1);
let srof = clipSize / ((clipSize - burstSize) / rof + burstOverhead + this.getReload(modified));
return srof / rof;
} else {
return 1;
}
}
/**
* Get the SDPS for this module
* @param {Boolean} [modified=true] Whether to take modifications into account
* @return {Number} The SDPS of this module
*/
getSDps(modified = true) {
return this.getDps(modified) * this.getSustainedFactor(modified);
let dps = this.getDps(modified);
if (this.getClip(modified)) {
let clipSize = this.getClip(modified);
// If auto-loader is applied, effective clip size will be nearly doubled
// as you get one reload for every two shots fired.
if (this.blueprint && this.blueprint.special && this.blueprint.special.edname === 'special_auto_loader' && modified) {
clipSize += clipSize - 1;
}
let timeToDeplete = clipSize / this.getRoF(modified);
return dps * timeToDeplete / (timeToDeplete + this.getReload(modified));
} else {
return dps;
}
}
/**
@@ -863,7 +876,7 @@ export default class Module {
*/
getHps(modified = true) {
// HPS is a synthetic value
let heat = this.get('thermload', modified);
let heat = this.getThermalLoad(modified);
// We don't use rpshot here as dist draw is per combined shot
let rof = this.getRoF(modified) || 1;
@@ -900,6 +913,24 @@ export default class Module {
return this.get('reload', modified);
}
/**
* Get the burst size for this module
* @param {Boolean} [modified=true] Whether to take modifications into account
* @return {Number} the burst size of this module
*/
getBurst(modified = true) {
return this.get('burst', modified);
}
/**
* Get the burst rate of fire for this module
* @param {Boolean} [modified=true] Whether to take modifications into account
* @return {Number} the burst rate of fire of this module
*/
getBurstRoF(modified = true) {
return this.get('burstrof', modified);
}
/**
* Get the rate of fire for this module.
* The rate of fire is a combination value, and needs to take in to account
@@ -910,8 +941,8 @@ export default class Module {
* @return {Number} the rate of fire for this module
*/
getRoF(modified = true) {
const burst = this.get('burst', modified) || 1;
const burstRoF = this.get('burstrof', modified) || 1;
const burst = this.getBurst(modified) || 1;
const burstRoF = this.getBurstRoF(modified) || 1;
const intRoF = this.get('rof', modified);
return burst / (((burst - 1) / burstRoF) + 1 / intRoF);

View File

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

View File

@@ -10,7 +10,7 @@ import { Ships, Modifications } from 'coriolis-data/dist';
import { chain } from 'lodash';
const zlib = require('zlib');
const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs', 'fh', 'gfsb', 'dc'];
const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs', 'fh', 'gfsb'];
// Constants for modifications struct
const SLOT_ID_DONE = -1;
@@ -1308,7 +1308,7 @@ export default class Ship {
let fsd = this.standard[2].m; // Frame Shift Drive;
let { unladenMass, fuelCapacity } = this;
this.unladenRange = this.calcUnladenRange(); // Includes fuel weight for jump
this.fullTankRange = Calc.jumpRange(unladenMass + fuelCapacity, fsd, fuelCapacity, this); // Full Tank
this.fullTankRange = Calc.jumpRange(unladenMass + fuelCapacity, fsd, this); // Full Tank
this.ladenRange = this.calcLadenRange(); // Includes full tank and caro
this.unladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity, fsd, fuelCapacity, this);
this.ladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity + this.cargoCapacity, fsd, fuelCapacity, this);

View File

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

View File

@@ -26,8 +26,8 @@ export const STATS_FORMATTING = {
'ammo': { 'format': 'int', },
'boot': { 'format': 'int', 'unit': 'secs' },
'brokenregen': { 'format': 'round1', 'unit': 'ps' },
'burst': { 'format': 'int', 'change': 'additive' },
'burstrof': { 'format': 'round1', 'unit': 'ps', 'change': 'additive' },
'burst': { 'format': 'int' },
'burstrof': { 'format': 'round1', 'unit': 'ps' },
'causres': { 'format': 'pct' },
'clip': { 'format': 'int' },
'damage': { 'format': 'round' },
@@ -61,7 +61,7 @@ export const STATS_FORMATTING = {
'ranget': { 'format': 'f1', 'unit': 's' },
'regen': { 'format': 'round1', 'unit': 'ps' },
'reload': { 'format': 'int', 'unit': 's' },
'rof': { 'format': 'round1', 'unit': 'ps', 'synthetic': 'getRoF', 'higherbetter': true },
'rof': { 'format': 'round1', 'unit': 'ps' },
'angle': { 'format': 'round1', 'unit': 'ang' },
'scanrate': { 'format': 'int' },
'scantime': { 'format': 'round1', 'unit': 's' },

View File

@@ -15,6 +15,7 @@ const LS_KEY_SIZE_RATIO = 'sizeRatio';
const LS_KEY_TOOLTIPS = 'tooltips';
const LS_KEY_MODULE_RESISTANCES = 'moduleResistances';
const LS_KEY_ROLLS = 'matsPerGrade';
const LS_KEY_ORBIS = 'orbis';
let LS;
@@ -94,6 +95,7 @@ export class Persist extends EventEmitter {
let buildJson = _get(LS_KEY_BUILDS);
let comparisonJson = _get(LS_KEY_COMPARISONS);
this.orbisCreds = _get(LS_KEY_ORBIS) || { email: '', password: '' };
this.onStorageChange = this.onStorageChange.bind(this);
this.langCode = _getString(LS_KEY_LANG) || 'en';
this.insurance = insurance && Insurance[insurance.toLowerCase()] !== undefined ? insurance : 'standard';
@@ -167,6 +169,10 @@ export class Persist extends EventEmitter {
this.matsPerGrade = JSON.parse(newValue);
this.emit('matsPerGrade', this.matsPerGrade);
break;
case LS_KEY_ORBIS:
this.orbisCreds = JSON.parse(newValue);
this.emit('orbis', this.orbisCreds);
break;
}
} catch (e) {
// On JSON.Parse Error - don't sync or do anything
@@ -192,6 +198,24 @@ export class Persist extends EventEmitter {
this.emit('language', langCode);
}
/**
* Get the current orbis.zone credentials
* @return {String} language code
*/
getOrbisCreds() {
return this.orbisCreds;
};
/**
* Update and save the orbis.zone credentials
* @param {Object} creds object with username and password properties.
*/
setOrbisCreds(creds) {
this.langCode = creds;
_put(LS_KEY_ORBIS, creds);
this.emit('orbis', creds);
}
/**
* Show tooltips setting
* @param {boolean} show Optional - update setting

View File

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

View File

@@ -249,13 +249,6 @@ function _addModifications(module, modifiers, blueprint, grade, specialModificat
if (!modifiers) return;
let special;
if (specialModifications) {
if (specialModifications == 'special_plasma_slug') {
if (module.symbol.match(/PlasmaAccelerator/i)) {
specialModifications = 'special_plasma_slug_pa';
} else {
specialModifications = 'special_plasma_slug_cooled';
}
}
special = Modifications.specials[specialModifications];
}
// Add the blueprint definition, grade and special
@@ -281,9 +274,6 @@ function _addModifications(module, modifiers, blueprint, grade, specialModificat
if (value === Infinity) {
value = modifiers[i].Value * 100;
}
if (modifiers[i].Label.search('DamageFalloffRange') >= 0) {
value = (modifiers[i].Value / module.range - 1) * 100;
}
if (modifiers[i].Label.search('Resistance') >= 0) {
value = (modifiers[i].Value * 100) - (modifiers[i].OriginalValue * 100);
}

View File

@@ -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
* @param {object} ship The URL to shorten

View File

@@ -1,7 +1,6 @@
import React from 'react';
import Persist from '../stores/Persist';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import Module from '../shipyard/Module';
/**
* Determine if a slot on a ship can mount a module of a particular class and group
@@ -140,21 +139,20 @@ function diff(format, mVal, mmVal) {
export function diffDetails(language, m, mm) {
let { formats, translate, units } = language;
let propDiffs = [];
m = new Module(m);
// Module-specific items
if (m.grp === 'pp') {
let mPowerGeneration = m.getPowerGeneration() || 0;
let mPowerGeneration = m.pgen || 0;
let mmPowerGeneration = mm ? mm.getPowerGeneration() : 0;
if (mPowerGeneration != mmPowerGeneration) propDiffs.push(<div key='pgen'>{translate('pgen')}: <span className={diffClass(mPowerGeneration, mmPowerGeneration)}>{diff(formats.round, mPowerGeneration, mmPowerGeneration)}{units.MW}</span></div>);
if (mPowerGeneration != mmPowerGeneration) propDiffs.push(<div key='pgen'>{translate('pgen')}: <span className={diffClass(mPowerGeneration, mmPowerGeneration)}>{diff(formats.round, mPowerGeneration, mmPowerGeneration)}{units.MJ}</span></div>);
} else {
let mPowerUsage = m.getPowerUsage() || 0;
let mPowerUsage = m.power || 0;
let mmPowerUsage = mm ? mm.getPowerUsage() || 0 : 0;
if (mPowerUsage != mmPowerUsage) propDiffs.push(<div key='power'>{translate('power')}: <span className={diffClass(mPowerUsage, mmPowerUsage, true)}>{diff(formats.round, mPowerUsage, mmPowerUsage)}{units.MW}</span></div>);
if (mPowerUsage != mmPowerUsage) propDiffs.push(<div key='power'>{translate('power')}: <span className={diffClass(mPowerUsage, mmPowerUsage, true)}>{diff(formats.round, mPowerUsage, mmPowerUsage)}{units.MJ}</span></div>);
}
let mDps = m.getDps() || 0;
let mDps = m.damage * (m.rpshot || 1) * (m.rof || 1);
let mmDps = mm ? mm.getDps() || 0 : 0;
if (mDps && mDps != mmDps) propDiffs.push(<div key='dps'>{translate('dps')}: <span className={diffClass(mmDps, mDps, true)}>{diff(formats.round, mDps, mmDps)}</span></div>);
@@ -166,7 +164,7 @@ export function diffDetails(language, m, mm) {
if (mAffectsShield) {
if (m.grp == 'sb') { // Both m and mm must be utility modules if this is true
newShield = this.calcShieldStrengthWith(null, m.getShieldBoost() - (mm ? mm.getShieldBoost() || 0 : 0));
newShield = this.calcShieldStrengthWith(null, m.shieldboost - (mm ? mm.getShieldBoost() || 0 : 0));
} else {
newShield = this.calcShieldStrengthWith(m);
}
@@ -181,7 +179,7 @@ export function diffDetails(language, m, mm) {
}
if (m.grp === 'mrp') {
let mProtection = m.getProtection();
let mProtection = m.protection;
let mmProtection = mm ? mm.getProtection() || 0 : 0;
if (mProtection != mmProtection) {
propDiffs.push(<div key='protection'>{translate('protection')}: <span className={diffClass(mmProtection, mProtection, true)}>{diff(formats.pct, mProtection, mmProtection)}</span></div>);
@@ -189,7 +187,7 @@ export function diffDetails(language, m, mm) {
}
if (m.grp === 'hr') {
let mHullReinforcement = m.getHullReinforcement();
let mHullReinforcement = m.hullreinforcement;
let mmHullReinforcement = mm ? mm.getHullReinforcement() || 0 : 0;
if (mHullReinforcement && mHullReinforcement != mmHullReinforcement) propDiffs.push(<div key='hullreinforcement'>{translate('hullreinforcement')}: <span className={diffClass(mmHullReinforcement, mHullReinforcement, true)}>{diff(formats.round, mHullReinforcement, mmHullReinforcement)}</span></div>);
}
@@ -221,7 +219,7 @@ export function diffDetails(language, m, mm) {
let mmCost = mm ? mm.cost : 0;
if (mCost != mmCost) propDiffs.push(<div key='cost'>{translate('cost')}: <span className={diffClass(mCost, mmCost, true) }>{formats.int(mCost ? Math.round(mCost * (1 - Persist.getModuleDiscount())) : 0)}{units.CR}</span></div>);
let mMass = m.getMass() || 0;
let mMass = m.mass || 0;
let mmMass = mm ? mm.getMass() : 0;
if (mMass != mmMass) propDiffs.push(<div key='mass'>{translate('mass')}: <span className={diffClass(mMass, mmMass, true)}>{diff(formats.round, mMass, mmMass)}{units.T}</span></div>);
@@ -242,7 +240,7 @@ export function diffDetails(language, m, mm) {
}
}
let mIntegrity = m.getIntegrity() || 0;
let mIntegrity = m.integrity || 0;
let mmIntegrity = mm ? mm.getIntegrity() || 0 : 0;
if (mIntegrity != mmIntegrity) {
propDiffs.push(<div key='integrity'>{translate('integrity')}: <span className={diffClass(mmIntegrity, mIntegrity, true)}>{diff(formats.round, mIntegrity, mmIntegrity)}</span></div>);

View File

@@ -8,8 +8,6 @@
<meta name="description" content="A ship builder, outfitting and comparison
tool for Elite Dangerous">
<meta name="mobile-web-app-capable" content="yes">
<meta name="viewport" content="width=device-width, initial-scale=1.0,
maximum-scale=1.0, user-scalable=0">
<link rel="manifest" href="/manifest.json">
@@ -27,26 +25,40 @@
<meta name="msapplication-TileImage" content="/mstile-144x144.png">
<meta name="msapplication-config" content="/browserconfig.xml">
<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>
window.CORIOLIS_GAPI_KEY = '<%- htmlWebpackPlugin.options.gapiKey %>';
window.CORIOLIS_VERSION = '<%- htmlWebpackPlugin.options.version %>';
window.CORIOLIS_DATE = '<%- htmlWebpackPlugin.options.date.toISOString().slice(0, 10) %>';
window.BUGSNAG_VERSION = '<%- htmlWebpackPlugin.options.version + '-' + htmlWebpackPlugin.options.date.toISOString() %>';
</script>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-55840909-18"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'UA-55840909-18');
<% if (htmlWebpackPlugin.options.uaTracking) { %>
<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', '<%- htmlWebpackPlugin.options.uaTracking %>', 'auto');
ga('send', 'pageview');
</script>
<script async src='https://www.google-analytics.com/analytics.js'></script>
<% } %>
<!-- Piwik -->
<!-- <script type="text/javascript">
var _paq = _paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(["setCookieDomain", "*.coriolis.edcd.io"]);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="//stats.isadankme.me/";
_paq.push(['setTrackerUrl', u+'piwik.php']);
_paq.push(['setSiteId', '4']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
})();
</script>-->
<!-- End Piwik Code -->
<!-- Bugsnag -->
<!-- Bugsnag -->
<script src="https://d2wy8f7a9ursnm.cloudfront.net/v5.0.0/bugsnag.min.js"></script>
<script>
window.bugsnagClient = bugsnag('ba9fae819372850fb660755341fa6ef5', {appVersion: window.BUGSNAG_VERSION || undefined})
@@ -54,8 +66,7 @@
</script>
</head>
<body style="background-color:#000;">
<section id="coriolis">
<section id="coriolis"></section>
</section>
</body>
</html>

View File

@@ -2,8 +2,6 @@
@bgDarken: 40%;
@disabledDarken: 15%;
@bgTransparency: 10%;
@bgHighlight: 5%;
@fgHighlight: 10%;
// Foreground colors
@fg: #CCC;
@@ -23,14 +21,9 @@
@bgBlack: #000;
@primary-bg: fadeout(darken(@primary, 47%), 15%);
@alt-primary-bg: fadeout(darken(@primary, 42%), 15%); // Lighter brown background
@secondary-bg: fadeout(darken(@secondary, @bgDarken), @bgTransparency); // Blue background
@secondary-bg: fadeout(darken(@secondary, @bgDarken), @bgTransparency); // Brown background
@warning-bg: fadeout(darken(@warning, @bgDarken), @bgTransparency); // Dark Red
@alt-primary-bg-highlighted: lighten(@alt-primary-bg, @bgHighlight);
@fg-highlighted: lighten(@fg, @fgHighlight);
@primary-darker: darken(@primary, 30%);
.fg {
color: @fg;

View File

@@ -54,7 +54,7 @@ textarea {
width:100%;
min-height: 10em;
resize: vertical;
user-select: text;
user-select: auto;
margin:2em 0;
}
}

View File

@@ -49,45 +49,4 @@ a.ship {
font-size: 0.7em;
float: right;
}
}
.shipyard-table-wrapper {
white-space: nowrap;
margin: 0 auto;
font-size: 0.8em;
position: relative;
display: inline-block;
max-width: 100%;
}
table.shipyard-table{
tbody tr.comparehighlight{
background-color: @secondary-bg;
color: @fg-highlighted;
}
}
.shipyard-table-wrapper {
border-bottom: 1px solid @primary-darker;
}
.shipyard-table-wrapper div .shipyard-table td:last-child {
border-right: 1px solid @primary-darker;
}
.shipyard-table-wrapper > .shipyard-table td:first-child {
border-left: 1px solid @primary-darker;
}
.content-wrapper{
display: inline-block;
margin: 0 auto;
max-width: 100%;
}
.table-tools{
text-align: left;
color: @primary;
label{
cursor: pointer;
}
}
}

View File

@@ -52,7 +52,7 @@ tbody tr {
}
.no-touch &.highlight:hover, .no-touch &.highlighted {
background-color: @alt-primary-bg-highlighted; //@warning-bg;
background-color: @warning-bg;
}
&:nth-child(odd){
@@ -84,5 +84,3 @@ td {
text-align: center;
}
}

View File

@@ -1,104 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Coriolis EDCD Edition</title>
<meta charset="UTF-8" />
<link rel="stylesheet" href="<%= htmlWebpackPlugin.files.css[0] %>" />
<!-- Standard headers -->
<meta
name="description"
content="A ship builder, outfitting and comparison
tool for Elite Dangerous"
/>
<meta name="mobile-web-app-capable" content="yes" />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0,
maximum-scale=1.0, user-scalable=0"
/>
<link rel="manifest" href="/manifest.json" />
<link rel="shortcut icon" href=/favicon2.ico>
<link
rel="icon"
sizes="152x152 192x192"
type="image/png"
href="/192x192.png"
/>
<!-- 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>
<!-- Apple/iOS headers -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="Coriolis" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<!-- Microsoft Windows Phone/Tablet headers -->
<meta name="msapplication-TileColor" content="#000000" />
<meta name="msapplication-TileImage" content="/mstile-144x144.png" />
<meta name="msapplication-config" content="/browserconfig.xml" />
<meta name="theme-color" content="#000000" />
</head>
<body>
<body style="background-color:#000;">
<section id="coriolis">
<div class="modal">
<h2>
Please migrate to <a href="https://coriolis.io">coriolis.io</a>
</h2>
You are currently on coriolis.<strong>.edcd</strong>.io This domain is
considered deprecated. To migrate your builds, copy the below text and
go to
<a
target="_blank"
rel="noopener noreferrer"
href="https://coriolis.io"
>this link</a
>, press ctrl + i and then paste in the data. (If you are on mobile,
you can go to the settings and hit import)
<div>
<textarea id="data" class="cb json"></textarea>
</div>
</div>
</section>
</body>
<script>
const LS = localStorage;
/**
* Safe localstorage get string
* @param {String} key key
* @return {String} The stored string
*/
function _getString(key) {
return LS ? LS.getItem(key) : null;
}
/**
* Safe localstorage get
* @param {String} key key
* @return {object | number} The stored data
*/
function _get(key) {
let str = _getString(key);
try {
return str ? JSON.parse(str) : null;
} catch (e) {
return null;
}
}
const textarea = document.querySelector("#data");
const data = {
builds: _get("builds") || {},
comparisons: _get("comparisons") || {},
insurance: _get("insurance") || "standard",
shipDiscount: _get("shipDiscount") || 0,
moduleDiscount: _get("moduleDiscount") || 0
};
textarea.textContent = JSON.stringify(data, null, 2);
</script>
</body>
</html>

View File

@@ -7,8 +7,8 @@ if (workbox) {
workbox.routing.registerNavigationRoute('/index.html');
workbox.routing.registerRoute(
/\.(?:png|jpg|jpeg|svg|gif)$/,
new workbox.strategies.CacheFirst({
new RegExp('/(.*?)'),
workbox.strategies.staleWhileRevalidate({
plugins: [
new workbox.cacheableResponse.Plugin({
statuses: [0, 200]
@@ -17,16 +17,9 @@ if (workbox) {
})
);
workbox.routing.registerRoute(
/\.(?:js|css)$/,
new workbox.strategies.StaleWhileRevalidate({
cacheName: 'static-resources',
})
);
workbox.routing.registerRoute(
new RegExp('https://fonts.(?:googleapis|gstatic).com/(.*)'),
new workbox.strategies.CacheFirst({
workbox.strategies.cacheFirst({
cacheName: 'google-fonts',
plugins: [
new workbox.expiration.Plugin({
@@ -38,6 +31,12 @@ if (workbox) {
]
})
);
try {
workbox.googleAnalytics.initialize();
} catch (e) {
console.log('Probably an ad-blocker');
}
} else {
console.log('Boo! Workbox didn\'t load 😬');
}