Compare commits

..

115 Commits

Author SHA1 Message Date
Cmdr McDonald
cae7edbe01 Merge branch 'release/2.2.1' 2016-11-05 14:39:21 +00:00
Cmdr McDonald
2fa96fc1a5 Bump version 2016-11-05 14:37:37 +00:00
Cmdr McDonald
6b8a4d1f85 Merge branch 'feature/mods' into develop 2016-11-05 14:03:24 +00:00
Cmdr McDonald
07df44a907 Allow 3dp for ship and module discounts 2016-11-05 13:59:23 +00:00
Cmdr McDonald
be45637435 Use GZIP to decrease length of modifications URL string 2016-11-05 13:43:57 +00:00
Cmdr McDonald
87ab684ba3 Use degree symbol for jitter 2016-11-04 23:58:03 +00:00
Cmdr McDonald
916a6d5e48 Show shield boost decimal place 2016-11-04 23:00:19 +00:00
Cmdr McDonald
b99fbf07f5 Fix up name of shield boost 2016-11-04 22:34:08 +00:00
Cmdr McDonald
6d6ef2a93e Provide weights to 2dp 2016-11-04 20:53:05 +00:00
Cmdr McDonald
d08e5e2858 Fixes for serialization 2016-11-04 17:13:28 +00:00
Cmdr McDonald
c1f4a8d416 Add tooltips for icons; fix import of old-style modification strings 2016-11-04 13:12:44 +00:00
Cmdr McDonald
c17c7125e3 Tidy-ups 2016-11-04 11:34:39 +00:00
Cmdr McDonald
97fc4ce45d Fixes and tidy-ups 2016-11-04 11:31:48 +00:00
Cmdr McDonald
abfc338240 Tidy-ups 2016-11-03 16:19:13 +00:00
Cmdr McDonald
3d129946ce Updates module diff details 2016-11-03 12:23:25 +00:00
Cmdr McDonald
2aab31d317 Fix typo 2016-11-03 11:33:00 +00:00
Cmdr McDonald
f8ff9a6a87 Add more fillouts 2016-11-03 11:29:41 +00:00
Cmdr McDonald
6727c2fc13 Move to pure damage/distdraw/thermload numbers 2016-11-03 10:52:54 +00:00
Cmdr McDonald
ba1f11a88b Updates for shield strength 2016-11-02 14:14:15 +00:00
Cmdr McDonald
c88e4369c8 Update to handle armour 2016-11-02 13:32:49 +00:00
Cmdr McDonald
c490f97c22 Use modifiers when calculating regen rates 2016-11-02 11:11:45 +00:00
Cmdr McDonald
7c71555384 Added facing limit and range for fsdi 2016-11-02 11:03:29 +00:00
Cmdr McDonald
35538f971a Tidy-ups 2016-11-02 10:47:10 +00:00
Cmdr McDonald
b3c82ac2de Added (non-functional) resistances 2016-11-02 10:35:55 +00:00
Cmdr McDonald
ff0d8dccea Handle bulkheads 2016-11-02 09:54:52 +00:00
Cmdr McDonald
06841b1485 Change arc to facinglimit 2016-11-01 12:22:03 +00:00
Cmdr McDonald
f55448c0a8 Ensure that fuel tank mass is displayed 2016-11-01 07:24:38 +00:00
Cmdr McDonald
258701c377 Separate modification slider in to its own component 2016-10-31 23:19:22 +00:00
Cmdr McDonald
1a0f05511b Various bug fixes 2016-10-31 21:08:15 +00:00
Cmdr McDonald
3ec9679893 Tidy up diff 2016-10-31 12:13:03 +00:00
Cmdr McDonald
e5cc3e269e Updates for modifications 2016-10-31 11:22:36 +00:00
Cmdr McDonald
4b14f617ec Changes for mods 2016-10-30 20:54:36 +00:00
Cmdr McDonald
0df051e40f Move more values to functions over direct variable access 2016-10-29 23:32:52 +01:00
Cmdr McDonald
2b464f34e5 Use react-number-editor for modifications 2016-10-29 14:14:57 +01:00
Cmdr McDonald
7a33bd64f8 Updates to modifications menu 2016-10-28 01:46:21 +01:00
Cmdr McDonald
3656c7a18f Update naming to match Elite names 2016-10-27 15:11:23 +01:00
Cmdr McDonald
3114852c63 Updates for mods UI 2016-10-27 13:26:09 +01:00
Cmdr McDonald
45337913ba Make modifications icon clickable 2016-10-25 13:36:46 +01:00
Cmdr McDonald
adc5d1c039 Only show modifications icon if the module has valid modifications. Use no more than two decimal places for modification values. 2016-10-24 18:40:12 +01:00
Cmdr McDonald
183f22c223 Updates to internal representation of modification information. Temporary power usage slider with standard modules for testing 2016-10-24 00:38:12 +01:00
Cmdr McDonald
43eb4935e6 Merge branch 'master' into develop 2016-10-22 21:03:35 +01:00
Cmdr McDonald
2c45011664 Rework modules to be individual objects rather than references to templates 2016-10-22 09:47:43 +01:00
Cmdr McDonald
f29f3f4f8f Merge branch 'release/2.2.0' into develop 2016-10-22 09:31:08 +01:00
Cmdr McDonald
c347336055 Merge branch 'release/2.2.0' 2016-10-22 09:31:05 +01:00
Cmdr McDonald
cd87184169 Apply discounts when ship updates 2016-10-17 19:30:58 +01:00
Cmdr McDonald
09895852c3 Fixes for tests 2016-10-17 15:12:05 +01:00
Cmdr McDonald
32759d3e0e Fix lint issues 2016-10-17 14:33:24 +01:00
Cmdr McDonald
965134fc02 Fix serializer for named standard modules - #2 2016-10-17 14:33:12 +01:00
Cmdr McDonald
7da5360349 Bumped release number 2016-10-16 13:50:45 +01:00
Cmdr McDonald
bf0701340c Use UTF-8 glyph for infinity rather than custom SVG 2016-10-15 09:10:40 +01:00
Cmdr McDonald
a3a490f442 Merge branch 'feature/cabins' into develop 2016-10-14 17:12:57 +01:00
Cmdr McDonald
33b22b8280 Use correct i18n formatting methods. Update note to 2.2 2016-10-14 14:53:20 +01:00
Cmdr McDonald
e6278858b9 Added drag and drop logic for special compartments 2016-10-14 12:09:54 +01:00
Cmdr McDonald
ed7a9ef037 Display new-style hardpoint information 2016-10-14 01:04:05 +01:00
Cmdr McDonald
9c42179617 Handle fighter bays and luxury passenger cabins 2016-10-12 11:48:42 +01:00
Paul Heisig
5f75c9d32b final changes before coriolis.edcd.io launch 2016-10-09 20:49:29 +02:00
Jonathan Harris
fe1d16415e Update links 2016-10-09 11:35:59 +01:00
Jonathan Harris
ed8cbb403e Point badges to this fork 2016-10-09 11:24:17 +01:00
Jonathan Harris
58182bddce Pass lint 2016-10-09 11:21:06 +01:00
Jonathan Harris
771ba7e692 Remove debugging statement 2016-10-09 11:18:36 +01:00
Paul Heisig
ed0d4ddc48 making coriolis enhanced deploy ready... 2016-10-07 21:26:27 +02:00
Paul Heisig
266c63bc0c test commit 2016-10-07 19:21:01 +02:00
Colin McLeod
ebcc92f7d4 Allow external webpack hotloading 2016-06-18 19:59:29 -07:00
Colin McLeod
a1f280cf62 Allow middle click, Fixes #173 2016-06-18 19:59:09 -07:00
Colin McLeod
0306167e6e Cleanup wepback devserver output 2016-06-18 19:47:20 -07:00
Colin McLeod
162136194a Add build name to title, Fixes #171 2016-06-18 19:46:20 -07:00
Colin McLeod
3600a4862f Merge pull request #172 from w0rm49/translation-fixes
fix some typos and errors in russian translation
2016-06-15 20:19:50 -07:00
w0rm49
65fdad64b2 fix some typos and errors in russian translation 2016-06-08 16:23:10 +03:00
Colin McLeod
b49eb06101 Use IDs for standard components instead of [class][rating]. Update tests accordingly 2016-06-06 00:07:21 -07:00
Colin McLeod
eeb4a86a13 Allow 'named' standard module (special case for bulkheads) 2016-06-05 00:56:09 -07:00
Colin McLeod
3a265b2804 Update version to 2.0.0. Update react to v15 2016-06-05 00:54:35 -07:00
Colin McLeod
5ebabd6e6e Tweak string interpolation based on React v15 changes 2016-06-05 00:54:02 -07:00
Colin McLeod
6e6c337bbb Use table to align settings. Fixes #163 2016-06-05 00:52:23 -07:00
Colin McLeod
31b56354e0 Update French translations 2016-06-05 00:51:41 -07:00
Colin McLeod
fd1adbe55c Update German translations 2016-06-05 00:51:19 -07:00
Colin McLeod
3d0259c304 Fix lint issues 2016-04-04 13:02:26 -07:00
Colin McLeod
6f1b86118e Bump beta version 2016-04-04 12:52:31 -07:00
Colin McLeod
b3fcfa7808 Improve scrolling overview of shipyard page 2016-04-04 12:52:05 -07:00
Colin McLeod
4ead80a37c Improve touch detection 2016-04-04 12:51:46 -07:00
Colin McLeod
72dc73e090 Include discounts on comparison page 2016-04-04 12:31:54 -07:00
Colin McLeod
bb19dc25c0 Autofill sensor bug fix 2016-04-04 12:31:27 -07:00
Colin McLeod
1729636657 Make boost speed 0 when unable to boost 2016-04-04 12:30:18 -07:00
Colin McLeod
92809d1d24 Fix scrolling and hover issues on tablet 2016-04-04 12:29:59 -07:00
Colin McLeod
4e81c828df Fix JEST mock issue 2016-03-20 00:36:21 -07:00
Colin McLeod
f5bd7d2ecb Lint fix 2016-03-20 00:35:40 -07:00
Colin McLeod
a989bd5b5d Include migrate.html with prod build 2016-03-19 23:28:02 -07:00
Colin McLeod
1bedbb1909 Take discount into consideration for module tool tip 2016-03-19 23:27:43 -07:00
Colin McLeod
9775e1c742 Refactor footer, UI tweaks 2016-03-19 23:27:26 -07:00
Colin McLeod
19228a9c56 Change hold alt messaging 2016-03-19 23:25:42 -07:00
Colin McLeod
363bf7fd2a Shipyard page changes - in progress 2016-03-10 12:29:19 -08:00
Colin McLeod
15748066c9 Bulkhead refactoring 2016-03-10 12:29:02 -08:00
Colin McLeod
c3fe0a0cef UI Tweaks 2016-03-10 12:28:35 -08:00
Colin McLeod
1544deb350 Update tests for eagle changes 2016-03-07 09:14:48 -08:00
Colin McLeod
f060eb1b62 Fix standalone router bugs 2016-03-06 22:39:33 -08:00
Colin McLeod
32795ea678 Hide ALT text for mobile 2016-03-06 22:39:17 -08:00
Colin McLeod
d744d15132 Tweak touch module select tooltip 2016-03-06 21:33:25 -08:00
Colin McLeod
bfcef18c02 Fix Cost section discount label 2016-03-06 20:24:48 -08:00
Colin McLeod
b04c2ed2f5 Fix thruster warning bug 2016-03-06 20:24:33 -08:00
Colin McLeod
f51998b1c2 Update German, French, and Russian translations 2016-03-06 17:22:42 -08:00
Colin McLeod
d7000bfebf Update test to work with Jest 0.9.0 2016-03-06 17:22:26 -08:00
Colin McLeod
3e2b3e33fb Add localstorage migration for iframe 2016-03-04 11:15:51 -08:00
Colin McLeod
87dd52c043 Tweak and refactor build/role preset functions 2016-03-03 19:58:47 -08:00
Colin McLeod
05bc8ebb93 Fix find lightest shield generator min mass bug 2016-03-03 19:58:08 -08:00
Colin McLeod
09b945d29e tweak comments 2016-03-03 19:57:41 -08:00
Colin McLeod
67b5d749df Add can mount 2016-03-03 19:55:52 -08:00
Colin McLeod
a19fd69d0b Tweak internal slot labels 2016-03-03 19:55:04 -08:00
Colin McLeod
cfb65396ab fix max speed/boost, use lightest FSD and fuel tank 2016-03-02 10:08:16 -08:00
Colin McLeod
a4a0f96502 Add fuel tank to optimize 2016-03-02 10:07:49 -08:00
Colin McLeod
1390339024 Hull reinforcement will always clobber, fix 2016-03-02 10:07:05 -08:00
Colin McLeod
43d19f1dbb Fix phrase case 2016-03-02 10:06:41 -08:00
Colin McLeod
c20439264a Tweak GA tracking 2016-03-02 10:06:28 -08:00
Colin McLeod
61c3941618 Tweak reload on update message positioning 2016-02-29 09:35:55 -08:00
Colin McLeod
da277e4eaa Bump Beta version 2 2016-02-29 09:28:50 -08:00
Colin McLeod
51d24a1105 Fix Planetary Vehicle Hangar typo. Fixes #153 2016-02-26 09:04:47 -08:00
Colin McLeod
138931c0cf fix deploy script 2016-02-24 13:23:30 -08:00
65 changed files with 3726 additions and 1161 deletions

2
.gitignore vendored
View File

@@ -3,3 +3,5 @@ build
.DS_Store .DS_Store
*.log *.log
nginx.pid nginx.pid
.idea
/bin

View File

@@ -1,5 +1,4 @@
![Latest Release](https://img.shields.io/github/release/cmmcleod/coriolis.svg) [![Build Status](https://travis-ci.org/cmmcleod/coriolis.svg?branch=master)](https://travis-ci.org/cmmcleod/coriolis) [![Tasks in Ready](https://badge.waffle.io/cmmcleod/coriolis.png?label=ready&title=Ready)](https://waffle.io/cmmcleod/coriolis) [![Tasks in Progress](https://badge.waffle.io/cmmcleod/coriolis.svg?label=in%20progress&title=In%20Progress)](http://waffle.io/cmmcleod/coriolis) [![Chat to us on HipChat](https://img.shields.io/badge/HipChat-Coriolis-white.svg?style=social)](https://www.hipchat.com/gfYQiZcmy) ![Latest Release](https://img.shields.io/github/release/EDCD/coriolis.svg) [![Build Status](https://travis-ci.org/EDCD/coriolis.svg?branch=master)](https://travis-ci.org/EDCD/coriolis) [![Chat to us on Discord](https://img.shields.io/badge/Discord-EDCD%20%23coriolis-blue.svg?style=social)](https://discord.gg/0uwCh6R62aPRjk9w)
## About ## About
@@ -9,13 +8,11 @@ Coriolis was created using assets and imagery from Elite: Dangerous, with the pe
## Contributing ## Contributing
Please [submit issues](https://github.com/cmmcleod/coriolis/issues), or better yet [pull requests](https://github.com/cmmcleod/coriolis/pulls) for any corrections or additions to the database or the code. 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.
### Feature Requests, Suggestions & Bugs ### Feature Requests, Suggestions & Bugs
Chat to us on [HipChat](https://www.hipchat.com/gfYQiZcmy)! Chat to us on [Discord](https://discord.gg/0uwCh6R62aPRjk9w)!
All such requests are managed and tracked through [issues](https://github.com/cmmcleod/coriolis/issues). An overview of these can be found [here](https://waffle.io/cmmcleod/coriolis).
## Development ## Development
@@ -29,7 +26,7 @@ See the [Data wiki](https://github.com/cmmcleod/coriolis-data/wiki) for details
## License ## License
All Data and [associated JSON](https://github.com/cmmcleod/coriolis-data) files are intellectual property and copyright of Frontier Developments plc ('Frontier', 'Frontier Developments') and are subject to their 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/). [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. The code (Javascript, CSS, HTML, and SVG files only) specificially for Coriolis.io is released under the MIT License.

View File

@@ -6,7 +6,8 @@
{ {
"name": "Coriolis.io", "name": "Coriolis.io",
"url": "http://localhost:3300/outfit/anaconda/48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA?bn=Test%20My%20Ship", "url": "http://localhost:3300/outfit/anaconda/48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA?bn=Test%20My%20Ship",
"code": "48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA", "old-code": "48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA",
"code": "4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA",
"shipId": "anaconda" "shipId": "anaconda"
} }
], ],

View File

@@ -2,31 +2,31 @@
{ {
"shipId": "anaconda", "shipId": "anaconda",
"buildName": "Imported Anaconda", "buildName": "Imported Anaconda",
"buildCode": "08E7E6E5E8E8E5C------1717--------05044j-03--2h--00.Iw18ZlA=.Aw18ZlA=", "buildCode": "0pyttFolodDsyf5------1717--------05044j-03--2h--00.Iw18ZlA=.Aw18ZlA=",
"buildText": "[Anaconda]\nS: 1F/F Pulse Laser\nS: 1F/F Pulse Laser\n\nBH: 1I Lightweight Alloy\nRB: 8E Power Plant\nTM: 7E Thrusters\nFH: 6E Frame Shift Drive\nEC: 5E Life Support\nPC: 8E Power Distributor\nSS: 8E Sensors\nFS: 5C Fuel Tank (Capacity: 32)\n\n7: 6E Cargo Rack (Capacity: 64)\n6: 5E Cargo Rack (Capacity: 32)\n6: 6E Shield Generator\n5: 4E Cargo Rack (Capacity: 16)\n4: 1E Basic Discovery Scanner\n2: 1E Cargo Rack (Capacity: 2)\n" "buildText": "[Anaconda]\nS: 1F/F Pulse Laser\nS: 1F/F Pulse Laser\n\nBH: 1I Lightweight Alloy\nRB: 8E Power Plant\nTM: 7E Thrusters\nFH: 6E Frame Shift Drive\nEC: 5E Life Support\nPC: 8E Power Distributor\nSS: 8E Sensors\nFS: 5C Fuel Tank (Capacity: 32)\n\n7: 6E Cargo Rack (Capacity: 64)\n6: 5E Cargo Rack (Capacity: 32)\n6: 6E Shield Generator\n5: 4E Cargo Rack (Capacity: 16)\n4: 1E Basic Discovery Scanner\n2: 1E Cargo Rack (Capacity: 2)\n"
}, },
{ {
"shipId": "anaconda", "shipId": "anaconda",
"buildName": "Imported Anaconda", "buildName": "Imported Anaconda",
"buildCode": "08E7E6E5E8E8E5C------1717--------05044j-03--2h--00.Iw18ZlA=.Aw18ZlA=", "buildCode": "0pyttFolodDsyf5------1717--------05044j-03--2h--00.Iw18ZlA=.Aw18ZlA=",
"buildText": "\n\n \t[Anaconda]\nS: 1F/F Pulse Laser\nS: 1F/F Pulse Laser\n\nBH: 1I Lightweight Alloy\nRB: 8E Power Plant\nTM: 7E Thrusters\nFH: 6E Frame Shift Drive\nEC: 5E Life Support\nPC: 8E Power Distributor\nSS: 8E Sensors\nFS: 5C Fuel Tank (Capacity: 32)\n\n7: 6E Cargo Rack (Capacity: 64)\n6: 5E Cargo Rack (Capacity: 32)\n6: 6E Shield Generator\n5: 4E Cargo Rack (Capacity: 16)\n4: 1E Basic Discovery Scanner\n2: 1E Cargo Rack (Capacity: 2)\n" "buildText": "\n\n \t[Anaconda]\nS: 1F/F Pulse Laser\nS: 1F/F Pulse Laser\n\nBH: 1I Lightweight Alloy\nRB: 8E Power Plant\nTM: 7E Thrusters\nFH: 6E Frame Shift Drive\nEC: 5E Life Support\nPC: 8E Power Distributor\nSS: 8E Sensors\nFS: 5C Fuel Tank (Capacity: 32)\n\n7: 6E Cargo Rack (Capacity: 64)\n6: 5E Cargo Rack (Capacity: 32)\n6: 6E Shield Generator\n5: 4E Cargo Rack (Capacity: 16)\n4: 1E Basic Discovery Scanner\n2: 1E Cargo Rack (Capacity: 2)\n"
}, },
{ {
"shipId": "cobra_mk_iii", "shipId": "cobra_mk_iii",
"buildName": "Imported Cobra Mk III", "buildName": "Imported Cobra Mk III",
"buildCode": "04A4C4E3D2A3D4C1712222503040202490f242h.Iw1-kA==.Aw1-kA==", "buildCode": "0patcFeldd5sdf41712222503040202490f242h.Iw1-kA==.Aw1-kA==",
"buildText": "[Cobra Mk III]\nM: 1F/F Pulse Laser\nM: 1G/G Burst Laser\nS: 1E/T Fragment Cannon\nS: 1G/T Multi-cannon\nU: 0I Point Defence\nU: 0A Shield Booster\n\nBH: 1I Lightweight Alloy\nRB: 4A Power Plant\nTM: 4C Thrusters\nFH: 4E Frame Shift Drive\nEC: 3D Life Support\nPC: 2A Power Distributor\nSS: 3D Sensors\nFS: 4C Fuel Tank (Capacity: 16)\n\n4: 3E Cargo Rack (Capacity: 8)\n4: 3E Cargo Rack (Capacity: 8)\n4: 4E Shield Generator\n2: 2C Auto Field-Maintenance Unit\n2: 1E Standard Docking Computer\n2: 1E Basic Discovery Scanner\n---\nShield: 112.29 MJ\nPower : 10.45 MW retracted (67%)\n 12.16 MW deployed (78%)\n 15.60 MW available\nCargo : 16 T\nFuel : 16 T\nMass : 235.5 T empty\n 267.5 T full\nRange : 10.69 LY unladen\n 10.05 LY laden\nPrice : 2,929,040 CR\nRe-Buy: 146,452 CR @ 95% insurance\n" "buildText": "[Cobra Mk III]\nM: 1F/F Pulse Laser\nM: 1G/G Burst Laser\nS: 1E/T Fragment Cannon\nS: 1G/T Multi-cannon\nU: 0I Point Defence\nU: 0A Shield Booster\n\nBH: 1I Lightweight Alloy\nRB: 4A Power Plant\nTM: 4C Thrusters\nFH: 4E Frame Shift Drive\nEC: 3D Life Support\nPC: 2A Power Distributor\nSS: 3D Sensors\nFS: 4C Fuel Tank (Capacity: 16)\n\n4: 3E Cargo Rack (Capacity: 8)\n4: 3E Cargo Rack (Capacity: 8)\n4: 4E Shield Generator\n2: 2C Auto Field-Maintenance Unit\n2: 1E Standard Docking Computer\n2: 1E Basic Discovery Scanner\n---\nShield: 112.29 MJ\nPower : 10.45 MW retracted (67%)\n 12.16 MW deployed (78%)\n 15.60 MW available\nCargo : 16 T\nFuel : 16 T\nMass : 235.5 T empty\n 267.5 T full\nRange : 10.69 LY unladen\n 10.05 LY laden\nPrice : 2,929,040 CR\nRe-Buy: 146,452 CR @ 95% insurance\n"
}, },
{ {
"shipId": "type_9_heavy", "shipId": "type_9_heavy",
"buildName": "Imported Type-9 Heavy", "buildName": "Imported Type-9 Heavy",
"buildCode": "35A7D6A5A4D4D5C7e2k2f2h110001020306054j03022f01242i.Iw18eQ==.Aw18eQ==", "buildCode": "3pftsFklkdisif57e2k2f2h110001020306054j03022f01242i.Iw18eQ==.Aw18eQ==",
"buildText": "[Type-9 Heavy]\nM: 2D/G Fragment Cannon\nM: 2I/F Mine Launcher\nM: 2B/FD Missile Rack\nS: 1I/FS Torpedo Pylon\nS: 1F/F Burst Laser\nU: 0I Chaff Launcher\nU: 0F Electronic Countermeasure\nU: 0I Heat Sink Launcher\nU: 0I Point Defence\n\nBH: 1I Mirrored Surface Composite\nRB: 5A Power Plant\nTM: 7D Thrusters\nFH: 6A Frame Shift Drive\nEC: 5A Life Support\nPC: 4D Power Distributor\nSS: 4D Sensors\nFS: 5C Fuel Tank (Capacity: 32)\n\n8: 7E Cargo Rack (Capacity: 128)\n7: 6E Cargo Rack (Capacity: 64)\n6: 6E Shield Generator\n5: 4E Cargo Rack (Capacity: 16)\n4: 3E Cargo Rack (Capacity: 8)\n4: 1C Advanced Discovery Scanner\n3: 2E Cargo Rack (Capacity: 4)\n3: 1E Standard Docking Computer\n2: 1C Detailed Surface Scanner\n" "buildText": "[Type-9 Heavy]\nM: 2D/G Fragment Cannon\nM: 2I/F Mine Launcher\nM: 2B/FD Missile Rack\nS: 1I/FS Torpedo Pylon\nS: 1F/F Burst Laser\nU: 0I Chaff Launcher\nU: 0F Electronic Countermeasure\nU: 0I Heat Sink Launcher\nU: 0I Point Defence\n\nBH: 1I Mirrored Surface Composite\nRB: 5A Power Plant\nTM: 7D Thrusters\nFH: 6A Frame Shift Drive\nEC: 5A Life Support\nPC: 4D Power Distributor\nSS: 4D Sensors\nFS: 5C Fuel Tank (Capacity: 32)\n\n8: 7E Cargo Rack (Capacity: 128)\n7: 6E Cargo Rack (Capacity: 64)\n6: 6E Shield Generator\n5: 4E Cargo Rack (Capacity: 16)\n4: 3E Cargo Rack (Capacity: 8)\n4: 1C Advanced Discovery Scanner\n3: 2E Cargo Rack (Capacity: 4)\n3: 1E Standard Docking Computer\n2: 1C Detailed Surface Scanner\n"
}, },
{ {
"shipId": "vulture", "shipId": "vulture",
"buildName": "Imported Vulture", "buildName": "Imported Vulture",
"buildCode": "44A5A4A3D5A4D3C1e1e0e0j04044a0n532jf1.Iw19kA==.Aw19kA==", "buildCode": "4patfFalddksif31e1e0e0j04044a0n532jf1.Iw19kA==.Aw19kA==",
"buildText": "[Vulture]\nL: 3E/G Pulse Laser\nL: 3E/G Pulse Laser\nU: 0A Frame Shift Wake Scanner\nU: 0A Kill Warrant Scanner\nU: 0A Shield Booster\nU: 0A Shield Booster\n\nBH: 1I Reactive Surface Composite\nRB: 4A Power Plant\nTM: 5A Thrusters\nFH: 4A Frame Shift Drive\nEC: 3D Life Support\nPC: 5A Power Distributor\nSS: 4D Sensors\nFS: 3C Fuel Tank (Capacity: 8)\n\n5: 5A Shield Generator\n4: 4A Auto Field-Maintenance Unit\n2: 2A Shield Cell Bank\n1: 1A Fuel Scoop\n1: 1C Fuel Tank (Capacity: 2)" "buildText": "[Vulture]\nL: 3E/G Pulse Laser\nL: 3E/G Pulse Laser\nU: 0A Frame Shift Wake Scanner\nU: 0A Kill Warrant Scanner\nU: 0A Shield Booster\nU: 0A Shield Booster\n\nBH: 1I Reactive Surface Composite\nRB: 4A Power Plant\nTM: 5A Thrusters\nFH: 4A Frame Shift Drive\nEC: 3D Life Support\nPC: 5A Power Distributor\nSS: 4D Sensors\nFS: 3C Fuel Tank (Capacity: 8)\n\n5: 5A Shield Generator\n4: 4A Auto Field-Maintenance Unit\n2: 2A Shield Cell Bank\n1: 1A Fuel Scoop\n1: 1C Fuel Tank (Capacity: 2)"
} }
] ]

View File

@@ -1,50 +1,50 @@
{ {
"type_6_transporter": { "type_6_transporter": {
"Cargo": "02A4D4A2D2D2D4C-----04040303430101.Iw1-kA==.Aw1-kA==", "Cargo": "0p0tdFal8d8s8f4-----04040303430101.Iw1-kA==.Aw1-kA==",
"Miner": "03A4D4A2D2D2D4C2l2l---040403451q0101.Iw1-kA==.Aw1-kA==", "Miner": "0p5tdFal8d8s8f42l2l---040403451q0101.Iw1-kA==.Aw1-kA==",
"Hopper": "02A4D4A2D1A2D4C1717---030302024300-.Iw1-kA==.Aw1-kA==" "Hopper": "0p0tdFal8d0s8f41717---030302024300-.Iw1-kA==.Aw1-kA=="
}, },
"type_7_transport": { "type_7_transport": {
"Cargo": "02A5D5A4D3D3D5C--------0505040403480101.Iw18aQ==.Aw18aQ==", "Cargo": "0p0tiFfliddsdf5--------0505040403480101.Iw18aQ==.Aw18aQ==",
"Miner": "04D5D5A4D2D3D5C--2l2l----0505041v03450000.Iw18aQ==.Aw18aQ==" "Miner": "0pdtiFflid8sdf5--2l2l----0505041v03450000.Iw18aQ==.Aw18aQ=="
}, },
"federal_dropship": { "federal_dropship": {
"Cargo": "04D5D5A5D3D4D4C-1717------05040448020201.Iw18aQ==.Aw18aQ==" "Cargo": "0pdtiFflnddsif4-1717------05040448020201.Iw18aQ==.Aw18aQ=="
}, },
"asp": { "asp": {
"Miner": "25A5A5A4D4A5A5C0s0s24242l2l---04054a1q02022o27.Iw18WQ==.Aw18WQ==" "Miner": "2pftfFflidfskf50s0s24242l2l---04054a1q02022o27.Iw18WQ==.Aw18WQ=="
}, },
"imperial_clipper": { "imperial_clipper": {
"Cargo": "03A5D5A5D4D5D4C--0s0s----0605450302020101.Iw18aQ==.Aw18aQ==", "Cargo": "0p5tiFflndisnf4--0s0s----0605450302020101.Iw18aQ==.Aw18aQ==",
"Dream": "26A6A5A5D6A5A4C0v0v0s0s0404040n4k5n5d2b29292o-.Iw18aQ==.Aw18aQ==", "Dream": "2pktkFflndpskf40v0v0s0s0404040n4k5n5d2b29292o-.Iw18aQ==.Aw18aQ==",
"Current": "04A6A5A5D4A5A4C----------------.Iw18aQ==.Aw18aQ==" "Current": "0patkFflndfskf4----------------.Iw18aQ==.Aw18aQ=="
}, },
"type_9_heavy": { "type_9_heavy": {
"Current": "04A7D6A5D5D4D6C---------0706054a0303020224.Iw18eQ==.Aw18eQ==" "Current": "0patsFklndnsif6---------0706054a0303020224.Iw18eQ==.Aw18eQ=="
}, },
"python": { "python": {
"Cargo": "04A6D5A4D6D6D5C---------050505040448020201.Iw18eQ==.Aw18eQ==", "Cargo": "0patnFflidsssf5---------050505040448020201.Iw18eQ==.Aw18eQ==",
"Miner": "06A6A5A4D6A6A5C0v0v0v2m2m0404--050505Ce4a1v02022o.Iw18eQ==.Aw18eQ==", "Miner": "0pktkFflidpspf50v0v0v2m2m0404--050505Ce4a1v02022o.Iw18eQ==.Aw18eQ==",
"Dream": "27A6A5A4D7A6A5C0v0v0v27270404040m5n5n4f2d2d032t0201.Iw18eQ==.Aw18eQ==", "Dream": "2pptkFfliduspf50v0v0v27270404040m5n5n4f2d2d032t0201.Iw18eQ==.Aw18eQ==",
"Missile": "07E6E5E4E7E6E5C2f2g2d2ePh----04044j03---002h.Iw18eQ==.Aw18eQ==" "Missile": "0pttoFjljdystf52f2g2d2ePh----04044j03---002h.Iw18eQ==.Aw18eQ=="
}, },
"anaconda": { "anaconda": {
"Dream": "48A7A6A5D8A8A5C2c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d040303326b.Iw18ZlA=.Aw18ZlA=", "Dream": "4putpFklndzsuf52c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d040303326b.Iw18ZlA=.Aw18ZlA=",
"Cargo": "04A6D6A5D5D8D5C----------------0605050504040445030301.Iw18ZlA=.Aw18ZlA=", "Cargo": "0patnFklndnsxf5----------------0605050504040445030301.Iw18ZlA=.Aw18ZlA=",
"Current": "04A6D6A5D5A8D5C----------------0605050504040403034524.Iw18ZlA=.Aw18ZlA=", "Current": "0patnFklndksxf5----------------0605050504040403034524.Iw18ZlA=.Aw18ZlA=",
"Explorer": "04A6D6A5D5A8D5C--------0202------f7050505040s372f2i4524.Iw18ZlA=.Aw18ZlA=", "Explorer": "0patnFklndksxf5--------0202------f7050505040s372f2i4524.Iw18ZlA=.Aw18ZlA=",
"Test": "48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.Iw18ZlA=.Aw18ZlA=" "Test": "4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.Iw18ZlA=.Aw18ZlA="
}, },
"diamondback_explorer": { "diamondback_explorer": {
"Explorer": "02A4D5A3D3D3D5C---0202--320p432i2f.Iw1-kA==.Aw1-kA==" "Explorer": "0p0tdFfldddsdf5---0202--320p432i2f.Iw1-kA==.Aw1-kA=="
}, },
"vulture": { "vulture": {
"Bounty Hunter": "34A4C4A3D5A4A3C1e1e0404-0l4a5d27662j.Iw19kA==.Aw19kA==" "Bounty Hunter": "3patcFalddksff31e1e0404-0l4a5d27662j.Iw19kA==.Aw19kA=="
}, },
"fer_de_lance": { "fer_de_lance": {
"Attack": "25A5C4A4D6A4A3C1r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.Aw18aQ==" "Attack": "2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.Aw18aQ=="
}, },
"eagle": { "eagle": {
"Figther": "42A3A3A1D2A2A2C0p0p24-40532j.Iw19A===.Aw19A===" "Figther": "4p0t5F5l3d5s5f20p0p24-40532j-.Iw1-EA==.Aw1-EA=="
} }
} }

View File

@@ -3348,8 +3348,8 @@
"references": [ "references": [
{ {
"name": "Coriolis.io", "name": "Coriolis.io",
"url": "http://localhost:3300/outfit/eagle/42A3A3A1D2A2A2C0p0p24-40532j.Iw19A===.Aw19A===?bn=Figther", "url": "http://localhost:3300/outfit/eagle/42A3A3A1D2A2A2C0p0p24-40532j-.Iw1-EA==.Aw1-EA==?bn=Figther",
"code": "42A3A3A1D2A2A2C0p0p24-40532j.AwRj49iA.AwgsIkEZigmIA===", "code": "42A3A3A1D2A2A2C0p0p24-40532j-.Iw1-EA==.Aw1-EA==",
"shipId": "eagle" "shipId": "eagle"
} }
], ],

View File

@@ -10,7 +10,7 @@ import { getLanguage } from '../src/app/i18n/Language';
describe('Import Modal', function() { describe('Import Modal', function() {
let MockRouter = require('../src/app/Router'); let MockRouter = require('../src/app/Router').default;
const Persist = require('../src/app/stores/Persist').default; const Persist = require('../src/app/stores/Persist').default;
const ModalImport = require('../src/app/components/ModalImport').default; const ModalImport = require('../src/app/components/ModalImport').default;
const mockContext = { const mockContext = {
@@ -25,8 +25,6 @@ describe('Import Modal', function() {
onWindowResize: jest.genMockFunction() onWindowResize: jest.genMockFunction()
}; };
MockRouter.go = jest.genMockFunction();
let modal, render, ContextProvider = Utils.createContextProvider(mockContext); let modal, render, ContextProvider = Utils.createContextProvider(mockContext);
/** /**
@@ -110,7 +108,8 @@ describe('Import Modal', function() {
it('catches an invalid backup', function() { it('catches an invalid backup', function() {
const importData = require('./fixtures/valid-backup'); const importData = require('./fixtures/valid-backup');
let invalidImportData = Object.assign({}, importData); let invalidImportData = Object.assign({}, importData);
invalidImportData.builds.asp = null; // 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"'); pasteText('"this is not valid"');
expect(modal.state.importValid).toBeFalsy(); expect(modal.state.importValid).toBeFalsy();
@@ -140,9 +139,10 @@ describe('Import Modal', function() {
expect(modal.state.importValid).toBeTruthy(); expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null); expect(modal.state.errorMsg).toEqual(null);
expect(modal.state.singleBuild).toBe(true);
clickProceed(); clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1); expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda/48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA?bn=Test%20My%20Ship'); expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda/4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA?bn=Test%20My%20Ship');
}); });
it('catches an invalid build', function() { it('catches an invalid build', function() {

View File

@@ -2,7 +2,7 @@ import Ship from '../src/app/shipyard/Ship';
import { Ships } from 'coriolis-data/dist'; import { Ships } from 'coriolis-data/dist';
import * as ModuleUtils from '../src/app/shipyard/ModuleUtils'; import * as ModuleUtils from '../src/app/shipyard/ModuleUtils';
describe("Ship Factory", function() { describe("Ship", function() {
it("can build all ships", function() { it("can build all ships", function() {
for (let s in Ships) { for (let s in Ships) {
@@ -16,7 +16,7 @@ describe("Ship Factory", function() {
ship.buildWith(shipData.defaults); ship.buildWith(shipData.defaults);
expect(ship.totalCost).toEqual(shipData.retailCost, s + ' retail cost does not match default build cost'); expect(ship.totalCost).toEqual(shipData.retailCost, s + ' retail cost does not match default build cost');
expect(ship.cargoCapacity).toBeDefined(s + ' cargo'); expect(ship.cargoCapacity).toBeDefined();
expect(ship.priorityBands[0].retracted).toBeGreaterThan(0, s + ' priorityBands'); expect(ship.priorityBands[0].retracted).toBeGreaterThan(0, s + ' priorityBands');
expect(ship.powerAvailable).toBeGreaterThan(0, s + ' powerAvailable'); expect(ship.powerAvailable).toBeGreaterThan(0, s + ' powerAvailable');
expect(ship.unladenRange).toBeGreaterThan(0, s + ' unladenRange'); expect(ship.unladenRange).toBeGreaterThan(0, s + ' unladenRange');
@@ -115,8 +115,7 @@ describe("Ship Factory", function() {
anaconda.use(anaconda.internal[1], ModuleUtils.internal('4j')); // 6E Shield Generator anaconda.use(anaconda.internal[1], ModuleUtils.internal('4j')); // 6E Shield Generator
expect(anaconda.internal[2].c).toEqual(null, 'Anaconda default shield generator slot is empty'); expect(anaconda.internal[2].m).toEqual(null, 'Anaconda default shield generator slot is empty');
expect(anaconda.internal[2].m).toEqual(null, 'Anaconda default shield generator slot id is null');
expect(anaconda.internal[1].m.id).toEqual('4j', 'Slot 1 should have SG 4j in it'); expect(anaconda.internal[1].m.id).toEqual('4j', 'Slot 1 should have SG 4j in it');
expect(anaconda.internal[1].m.grp).toEqual('sg','Slot 1 should have SG 4j in it'); expect(anaconda.internal[1].m.grp).toEqual('sg','Slot 1 should have SG 4j in it');
@@ -133,8 +132,7 @@ describe("Ship Factory", function() {
anaconda.use(anaconda.internal[3], ModuleUtils.internal('32')); anaconda.use(anaconda.internal[3], ModuleUtils.internal('32'));
expect(anaconda.internal[4].c).toEqual(null, 'Anaconda original fuel scoop slot is empty'); expect(anaconda.internal[4].m).toEqual(null, 'Anaconda original fuel scoop slot is empty');
expect(anaconda.internal[4].m).toEqual(null, 'Anaconda original fuel scoop slot id is null');
expect(anaconda.internal[3].m.id).toEqual('32', 'Slot 1 should have FS 32 in it'); expect(anaconda.internal[3].m.id).toEqual('32', 'Slot 1 should have FS 32 in it');
expect(anaconda.internal[3].m.grp).toEqual('fs','Slot 1 should have FS 32 in it'); expect(anaconda.internal[3].m.grp).toEqual('fs','Slot 1 should have FS 32 in it');
}); });
@@ -150,8 +148,7 @@ describe("Ship Factory", function() {
anaconda.use(anaconda.internal[3], ModuleUtils.internal('23')); anaconda.use(anaconda.internal[3], ModuleUtils.internal('23'));
expect(anaconda.internal[4].c).toEqual(null, 'Anaconda original refinery slot is empty'); expect(anaconda.internal[4].m).toEqual(null, 'Anaconda original refinery slot is empty');
expect(anaconda.internal[4].m).toEqual(null, 'Anaconda original refinery slot id is null');
expect(anaconda.internal[3].m.id).toEqual('23', 'Slot 1 should have RF 23 in it'); expect(anaconda.internal[3].m.id).toEqual('23', 'Slot 1 should have RF 23 in it');
expect(anaconda.internal[3].m.grp).toEqual('rf','Slot 1 should have RF 23 in it'); expect(anaconda.internal[3].m.grp).toEqual('rf','Slot 1 should have RF 23 in it');
}); });

View File

@@ -11,6 +11,15 @@ new WebpackDevServer(webpack(config), {
// For some reason connect-history-api-fallback does not allow '.' in the URL for history fallback... // For some reason connect-history-api-fallback does not allow '.' in the URL for history fallback...
{ from: /\/outfit\//, to: '/index.html' } { from: /\/outfit\//, to: '/index.html' }
] ]
},
stats: {
assets: true,
colors: true,
version: false,
hash: false,
timings: true,
chunks: false,
chunkModules: false
} }
}).listen(3300, "0.0.0.0", function (err, result) { }).listen(3300, "0.0.0.0", function (err, result) {
if (err) { if (err) {

View File

@@ -1,12 +1,12 @@
{ {
"name": "coriolis_shipyard", "name": "coriolis_shipyard",
"version": "2.0.0-Beta-1", "version": "2.2.1",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/cmmcleod/coriolis" "url": "https://github.com/EDCD/coriolis"
}, },
"homepage": "https://coriolis.io", "homepage": "https://coriolis.edcd.io",
"bugs": "https://github.com/cmmcleod/coriolis/issues", "bugs": "https://github.com/EDCD/coriolis/issues",
"private": true, "private": true,
"engine": "node >= 4.0.0", "engine": "node >= 4.0.0",
"license": "MIT", "license": "MIT",
@@ -20,19 +20,19 @@
"prod-stop": "kill -QUIT $(cat nginx.pid)", "prod-stop": "kill -QUIT $(cat nginx.pid)",
"build": "npm run clean && NODE_ENV=production webpack -d -p --config webpack.config.prod.js", "build": "npm run clean && NODE_ENV=production webpack -d -p --config webpack.config.prod.js",
"rsync": "rsync -ae \"ssh -i $CORIOLIS_PEM\" --delete build/ $CORIOLIS_USER@$CORIOLIS_HOST:~/wwws", "rsync": "rsync -ae \"ssh -i $CORIOLIS_PEM\" --delete build/ $CORIOLIS_USER@$CORIOLIS_HOST:~/wwws",
"deploy": "npm run lint && npm test && npm run build:prod && npm run rsync" "deploy": "npm run lint && npm test && npm run build && npm run rsync"
}, },
"jest": { "jest": {
"scriptPreprocessor": "<rootDir>/node_modules/babel-jest", "scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
"testFileExtensions": [ "testRegex": "(/__tests__/test-.*|\\.(test|spec))\\.js$",
"js"
],
"moduleFileExtensions": [ "moduleFileExtensions": [
"js", "js",
"json", "json",
"jsx" "jsx"
], ],
"automock": true,
"unmockedModulePathPatterns": [ "unmockedModulePathPatterns": [
"<rootDir>/node_modules/lodash",
"<rootDir>/node_modules/react", "<rootDir>/node_modules/react",
"<rootDir>/node_modules/react-dom", "<rootDir>/node_modules/react-dom",
"<rootDir>/node_modules/react-addons-test-utils", "<rootDir>/node_modules/react-addons-test-utils",
@@ -43,7 +43,7 @@
"<rootDir>/node_modules/d3", "<rootDir>/node_modules/d3",
"<rootDir>/node_modules/lz-string", "<rootDir>/node_modules/lz-string",
"<rootDir>/node_modules/jsen", "<rootDir>/node_modules/jsen",
"<rootDir>/node_modules/coriolis-data", "coriolis-data",
"<rootDir>/src/app/shipyard", "<rootDir>/src/app/shipyard",
"<rootDir>/src/app/i18n", "<rootDir>/src/app/i18n",
"<rootDir>/src/app/utils", "<rootDir>/src/app/utils",
@@ -68,13 +68,13 @@
"extract-text-webpack-plugin": "^0.9.1", "extract-text-webpack-plugin": "^0.9.1",
"file-loader": "^0.8.4", "file-loader": "^0.8.4",
"html-webpack-plugin": "^1.7.0", "html-webpack-plugin": "^1.7.0",
"jest-cli": "*", "jest-cli": "^16.0.1",
"jsen": "^0.6.0", "jsen": "^0.6.0",
"json-loader": "^0.5.3", "json-loader": "^0.5.3",
"less": "^2.5.3", "less": "^2.5.3",
"less-loader": "^2.2.1", "less-loader": "^2.2.1",
"react-addons-test-utils": "^0.14.7", "react-addons-test-utils": "^15.0.1",
"react-testutils-additions": "^0.16.0", "react-testutils-additions": "^15.1.0",
"rimraf": "^2.4.3", "rimraf": "^2.4.3",
"style-loader": "^0.13.0", "style-loader": "^0.13.0",
"url-loader": "^0.5.6", "url-loader": "^0.5.6",
@@ -84,12 +84,14 @@
"dependencies": { "dependencies": {
"babel-polyfill": "*", "babel-polyfill": "*",
"classnames": "^2.2.0", "classnames": "^2.2.0",
"coriolis-data": "cmmcleod/coriolis-data", "coriolis-data": "EDCD/coriolis-data",
"d3": "^3.5.9", "d3": "3.5.16",
"fbemitter": "^2.0.0", "fbemitter": "^2.0.0",
"lodash": "^4.15.0",
"lz-string": "^1.4.4", "lz-string": "^1.4.4",
"react": "^0.14.7", "react-number-editor": "^4.0.2",
"react-dom": "^0.14.7", "react": "^15.0.1",
"react-dom": "^15.0.1",
"superagent": "^1.4.0" "superagent": "^1.4.0"
} }
} }

906
src/.htaccess Normal file
View File

@@ -0,0 +1,906 @@
RewriteEngine on
# If a directory or a file exists, use the request directly
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
# Otherwise forward the request to index.php
RewriteRule . index.html
# Apache Server Configs v2.10.0 | MIT License
# https://github.com/h5bp/server-configs-apache
# (!) Using `.htaccess` files slows down Apache, therefore, if you have
# access to the main server configuration file (which is usually called
# `httpd.conf`), you should add this logic there.
#
# https://httpd.apache.org/docs/current/howto/htaccess.html.
# ######################################################################
# # CROSS-ORIGIN #
# ######################################################################
# ----------------------------------------------------------------------
# | Cross-origin requests |
# ----------------------------------------------------------------------
# Allow cross-origin requests.
#
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
# http://enable-cors.org/
# http://www.w3.org/TR/cors/
# <IfModule mod_headers.c>
# Header set Access-Control-Allow-Origin "*"
# </IfModule>
# ----------------------------------------------------------------------
# | Cross-origin images |
# ----------------------------------------------------------------------
# Send the CORS header for images when browsers request it.
#
# https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image
# https://blog.chromium.org/2011/07/using-cross-domain-images-in-webgl-and.html
<IfModule mod_setenvif.c>
<IfModule mod_headers.c>
<FilesMatch "\.(bmp|cur|gif|ico|jpe?g|png|svgz?|webp)$">
SetEnvIf Origin ":" IS_CORS
Header set Access-Control-Allow-Origin "*" env=IS_CORS
</FilesMatch>
</IfModule>
</IfModule>
# ----------------------------------------------------------------------
# | Cross-origin web fonts |
# ----------------------------------------------------------------------
# Allow cross-origin access to web fonts.
<IfModule mod_headers.c>
<FilesMatch "\.(eot|otf|tt[cf]|woff2?)$">
Header set Access-Control-Allow-Origin "*"
</FilesMatch>
</IfModule>
# ----------------------------------------------------------------------
# | Cross-origin resource timing |
# ----------------------------------------------------------------------
# Allow cross-origin access to the timing information for all resources.
#
# If a resource isn't served with a `Timing-Allow-Origin` header that
# would allow its timing information to be shared with the document,
# some of the attributes of the `PerformanceResourceTiming` object will
# be set to zero.
#
# http://www.w3.org/TR/resource-timing/
# http://www.stevesouders.com/blog/2014/08/21/resource-timing-practical-tips/
# <IfModule mod_headers.c>
# Header set Timing-Allow-Origin: "*"
# </IfModule>
# ######################################################################
# # ERRORS #
# ######################################################################
# ----------------------------------------------------------------------
# | Custom error messages/pages |
# ----------------------------------------------------------------------
# Customize what Apache returns to the client in case of an error.
# https://httpd.apache.org/docs/current/mod/core.html#errordocument
ErrorDocument 404 /404.html
# ----------------------------------------------------------------------
# | Error prevention |
# ----------------------------------------------------------------------
# Disable the pattern matching based on filenames.
#
# This setting prevents Apache from returning a 404 error as the result
# of a rewrite when the directory with the same name does not exist.
#
# https://httpd.apache.org/docs/current/content-negotiation.html#multiviews
Options -MultiViews
# ######################################################################
# # INTERNET EXPLORER #
# ######################################################################
# ----------------------------------------------------------------------
# | Document modes |
# ----------------------------------------------------------------------
# Force Internet Explorer 8/9/10 to render pages in the highest mode
# available in the various cases when it may not.
#
# https://hsivonen.fi/doctype/#ie8
#
# (!) Starting with Internet Explorer 11, document modes are deprecated.
# If your business still relies on older web apps and services that were
# designed for older versions of Internet Explorer, you might want to
# consider enabling `Enterprise Mode` throughout your company.
#
# http://msdn.microsoft.com/en-us/library/ie/bg182625.aspx#docmode
# http://blogs.msdn.com/b/ie/archive/2014/04/02/stay-up-to-date-with-enterprise-mode-for-internet-explorer-11.aspx
<IfModule mod_headers.c>
Header set X-UA-Compatible "IE=edge"
# `mod_headers` cannot match based on the content-type, however,
# the `X-UA-Compatible` response header should be send only for
# HTML documents and not for the other resources.
<FilesMatch "\.(appcache|atom|bbaw|bmp|crx|css|cur|eot|f4[abpv]|flv|geojson|gif|htc|ico|jpe?g|js|json(ld)?|m4[av]|manifest|map|mp4|oex|og[agv]|opus|otf|pdf|png|rdf|rss|safariextz|svgz?|swf|topojson|tt[cf]|txt|vcard|vcf|vtt|webapp|web[mp]|woff2?|xloc|xml|xpi)$">
Header unset X-UA-Compatible
</FilesMatch>
</IfModule>
# ----------------------------------------------------------------------
# | Iframes cookies |
# ----------------------------------------------------------------------
# Allow cookies to be set from iframes in Internet Explorer.
#
# http://msdn.microsoft.com/en-us/library/ms537343.aspx
# http://www.w3.org/TR/2000/CR-P3P-20001215/
# <IfModule mod_headers.c>
# Header set P3P "policyref=\"/w3c/p3p.xml\", CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\""
# </IfModule>
# ######################################################################
# # MEDIA TYPES AND CHARACTER ENCODINGS #
# ######################################################################
# ----------------------------------------------------------------------
# | Media types |
# ----------------------------------------------------------------------
# Serve resources with the proper media types (f.k.a. MIME types).
#
# https://www.iana.org/assignments/media-types/media-types.xhtml
# https://httpd.apache.org/docs/current/mod/mod_mime.html#addtype
<IfModule mod_mime.c>
# Data interchange
AddType application/json json map topojson
AddType application/ld+json jsonld
AddType application/vnd.geo+json geojson
AddType application/xml atom rdf rss xml
# JavaScript
# Normalize to standard type.
# https://tools.ietf.org/html/rfc4329#section-7.2
AddType application/javascript js
# Manifest files
# If you are providing a web application manifest file (see
# the specification: https://w3c.github.io/manifest/), it is
# recommended that you serve it with the `application/manifest+json`
# media type.
#
# Because the web application manifest file doesn't have its
# own unique file extension, you can set its media type either
# by matching:
#
# 1) the exact location of the file (this can be done using a
# directive such as `<Location>`, but it will NOT work in
# the `.htaccess` file, so you will have to do it in the main
# server configuration file or inside of a `<VirtualHost>`
# container)
#
# e.g.:
#
# <Location "/.well-known/manifest.json">
# AddType application/manifest+json json
# </Location>
#
# 2) the filename (this can be problematic as you will need to
# ensure that you don't have any other file with the same name
# as the one you gave to your web application manifest file)
#
# e.g.:
#
# <Files "manifest.json">
# AddType application/manifest+json json
# </Files>
AddType application/x-web-app-manifest+json webapp
AddType text/cache-manifest appcache manifest
# Media files
AddType audio/mp4 f4a f4b m4a
AddType audio/ogg oga ogg opus
AddType image/bmp bmp
AddType image/webp webp
AddType image/x-icon cur ico
AddType video/mp4 f4v f4p m4v mp4
AddType video/ogg ogv
AddType video/webm webm
AddType video/x-flv flv
AddType image/svg+xml svg svgz
# Web fonts
AddType application/font-woff woff
AddType application/font-woff2 woff2
AddType application/vnd.ms-fontobject eot
# Browsers usually ignore the font media types and simply sniff
# the bytes to figure out the font type.
# https://mimesniff.spec.whatwg.org/#matching-a-font-type-pattern
#
# However, Blink and WebKit based browsers will show a warning
# in the console if the following font types are served with any
# other media types.
AddType application/x-font-ttf ttc ttf
AddType font/opentype otf
# Other
AddType application/octet-stream safariextz
AddType application/x-bb-appworld bbaw
AddType application/x-chrome-extension crx
AddType application/x-opera-extension oex
AddType application/x-xpinstall xpi
AddType text/vcard vcard vcf
AddType text/vnd.rim.location.xloc xloc
AddType text/vtt vtt
AddType text/x-component htc
</IfModule>
# ----------------------------------------------------------------------
# | Character encodings |
# ----------------------------------------------------------------------
# Serve all resources labeled as `text/html` or `text/plain`
# with the media type `charset` parameter set to `UTF-8`.
#
# https://httpd.apache.org/docs/current/mod/core.html#adddefaultcharset
AddDefaultCharset utf-8
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Serve the following file types with the media type `charset`
# parameter set to `UTF-8`.
#
# https://httpd.apache.org/docs/current/mod/mod_mime.html#addcharset
<IfModule mod_mime.c>
AddCharset utf-8 .atom \
.bbaw \
.css \
.geojson \
.js \
.json \
.jsonld \
.rdf \
.rss \
.topojson \
.vtt \
.webapp \
.xloc \
.xml
</IfModule>
# ######################################################################
# # REWRITES #
# ######################################################################
# ----------------------------------------------------------------------
# | Rewrite engine |
# ----------------------------------------------------------------------
# (1) Turn on the rewrite engine (this is necessary in order for
# the `RewriteRule` directives to work).
#
# https://httpd.apache.org/docs/current/mod/mod_rewrite.html#RewriteEngine
#
# (2) Enable the `FollowSymLinks` option if it isn't already.
#
# https://httpd.apache.org/docs/current/mod/core.html#options
#
# (3) If your web host doesn't allow the `FollowSymlinks` option,
# you need to comment it out or remove it, and then uncomment
# the `Options +SymLinksIfOwnerMatch` line (4), but be aware
# of the performance impact.
#
# https://httpd.apache.org/docs/current/misc/perf-tuning.html#symlinks
#
# (4) Some cloud hosting services will require you set `RewriteBase`.
#
# http://www.rackspace.com/knowledge_center/frequently-asked-question/why-is-modrewrite-not-working-on-my-site
# https://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewritebase
#
# (5) Depending on how your server is set up, you may also need to
# use the `RewriteOptions` directive to enable some options for
# the rewrite engine.
#
# https://httpd.apache.org/docs/current/mod/mod_rewrite.html#rewriteoptions
<IfModule mod_rewrite.c>
# (1)
RewriteEngine On
# (2)
Options +FollowSymlinks
# (3)
# Options +SymLinksIfOwnerMatch
# (4)
# RewriteBase /
# (5)
# RewriteOptions <options>
</IfModule>
# ----------------------------------------------------------------------
# | Forcing `https://` |
# ----------------------------------------------------------------------
# Redirect from the `http://` to the `https://` version of the URL.
# https://wiki.apache.org/httpd/RewriteHTTPToHTTPS
# <IfModule mod_rewrite.c>
# RewriteEngine On
# RewriteCond %{HTTPS} !=on
# RewriteRule ^(.*)$ https://%{HTTP_HOST}/$1 [R=301,L]
# </IfModule>
# ----------------------------------------------------------------------
# | Suppressing / Forcing the `www.` at the beginning of URLs |
# ----------------------------------------------------------------------
# The same content should never be available under two different
# URLs, especially not with and without `www.` at the beginning.
# This can cause SEO problems (duplicate content), and therefore,
# you should choose one of the alternatives and redirect the other
# one.
#
# By default `Option 1` (no `www.`) is activated.
# http://no-www.org/faq.php?q=class_b
#
# If you would prefer to use `Option 2`, just comment out all the
# lines from `Option 1` and uncomment the ones from `Option 2`.
#
# (!) NEVER USE BOTH RULES AT THE SAME TIME!
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Option 1: rewrite www.example.com → example.com
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{HTTPS} !=on
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
RewriteRule ^ http://%1%{REQUEST_URI} [R=301,L]
</IfModule>
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Option 2: rewrite example.com → www.example.com
#
# Be aware that the following might not be a good idea if you use "real"
# subdomains for certain parts of your website.
# <IfModule mod_rewrite.c>
# RewriteEngine On
# RewriteCond %{HTTPS} !=on
# RewriteCond %{HTTP_HOST} !^www\. [NC]
# RewriteCond %{SERVER_ADDR} !=127.0.0.1
# RewriteCond %{SERVER_ADDR} !=::1
# RewriteRule ^ http://www.%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
# </IfModule>
# ######################################################################
# # SECURITY #
# ######################################################################
# ----------------------------------------------------------------------
# | Clickjacking |
# ----------------------------------------------------------------------
# Protect website against clickjacking.
#
# The example below sends the `X-Frame-Options` response header with
# the value `DENY`, informing browsers not to display the content of
# the web page in any frame.
#
# This might not be the best setting for everyone. You should read
# about the other two possible values the `X-Frame-Options` header
# field can have: `SAMEORIGIN` and `ALLOW-FROM`.
# https://tools.ietf.org/html/rfc7034#section-2.1.
#
# Keep in mind that while you could send the `X-Frame-Options` header
# for all of your websites pages, this has the potential downside that
# it forbids even non-malicious framing of your content (e.g.: when
# users visit your website using a Google Image Search results page).
#
# Nonetheless, you should ensure that you send the `X-Frame-Options`
# header for all pages that allow a user to make a state changing
# operation (e.g: pages that contain one-click purchase links, checkout
# or bank-transfer confirmation pages, pages that make permanent
# configuration changes, etc.).
#
# Sending the `X-Frame-Options` header can also protect your website
# against more than just clickjacking attacks:
# https://cure53.de/xfo-clickjacking.pdf.
#
# https://tools.ietf.org/html/rfc7034
# http://blogs.msdn.com/b/ieinternals/archive/2010/03/30/combating-clickjacking-with-x-frame-options.aspx
# https://www.owasp.org/index.php/Clickjacking
# <IfModule mod_headers.c>
# Header set X-Frame-Options "DENY"
# # `mod_headers` cannot match based on the content-type, however,
# # the `X-Frame-Options` response header should be send only for
# # HTML documents and not for the other resources.
# <FilesMatch "\.(appcache|atom|bbaw|bmp|crx|css|cur|eot|f4[abpv]|flv|geojson|gif|htc|ico|jpe?g|js|json(ld)?|m4[av]|manifest|map|mp4|oex|og[agv]|opus|otf|pdf|png|rdf|rss|safariextz|svgz?|swf|topojson|tt[cf]|txt|vcard|vcf|vtt|webapp|web[mp]|woff2?|xloc|xml|xpi)$">
# Header unset X-Frame-Options
# </FilesMatch>
# </IfModule>
# ----------------------------------------------------------------------
# | Content Security Policy (CSP) |
# ----------------------------------------------------------------------
# Mitigate the risk of cross-site scripting and other content-injection
# attacks.
#
# This can be done by setting a `Content Security Policy` which
# whitelists trusted sources of content for your website.
#
# The example header below allows ONLY scripts that are loaded from the
# current website's origin (no inline scripts, no CDN, etc). That almost
# certainly won't work as-is for your website!
#
# For more details on how to craft a reasonable policy for your website,
# read: http://www.html5rocks.com/en/tutorials/security/content-security-policy/
# (or the specification: http://www.w3.org/TR/CSP11/). Also, to make
# things easier, you can use an online CSP header generator such as:
# http://cspisawesome.com/.
# <IfModule mod_headers.c>
# Header set Content-Security-Policy "script-src 'self'; object-src 'self'"
# # `mod_headers` cannot match based on the content-type, however,
# # the `Content-Security-Policy` response header should be send
# # only for HTML documents and not for the other resources.
# <FilesMatch "\.(appcache|atom|bbaw|bmp|crx|css|cur|eot|f4[abpv]|flv|geojson|gif|htc|ico|jpe?g|js|json(ld)?|m4[av]|manifest|map|mp4|oex|og[agv]|opus|otf|pdf|png|rdf|rss|safariextz|svgz?|swf|topojson|tt[cf]|txt|vcard|vcf|vtt|webapp|web[mp]|woff2?|xloc|xml|xpi)$">
# Header unset Content-Security-Policy
# </FilesMatch>
# </IfModule>
# ----------------------------------------------------------------------
# | File access |
# ----------------------------------------------------------------------
# Block access to directories without a default document.
#
# You should leave the following uncommented, as you shouldn't allow
# anyone to surf through every directory on your server (which may
# includes rather private places such as the CMS's directories).
<IfModule mod_autoindex.c>
Options -Indexes
</IfModule>
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Block access to all hidden files and directories with the exception of
# the visible content from within the `/.well-known/` hidden directory.
#
# These types of files usually contain user preferences or the preserved
# state of an utility, and can include rather private places like, for
# example, the `.git` or `.svn` directories.
#
# The `/.well-known/` directory represents the standard (RFC 5785) path
# prefix for "well-known locations" (e.g.: `/.well-known/manifest.json`,
# `/.well-known/keybase.txt`), and therefore, access to its visible
# content should not be blocked.
#
# https://www.mnot.net/blog/2010/04/07/well-known
# https://tools.ietf.org/html/rfc5785
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_URI} "!(^|/)\.well-known/([^./]+./?)+$" [NC]
RewriteCond %{SCRIPT_FILENAME} -d [OR]
RewriteCond %{SCRIPT_FILENAME} -f
RewriteRule "(^|/)\." - [F]
</IfModule>
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Block access to files that can expose sensitive information.
#
# By default, block access to backup and source files that may be
# left by some text editors and can pose a security risk when anyone
# has access to them.
#
# http://feross.org/cmsploit/
#
# (!) Update the `<FilesMatch>` regular expression from below to
# include any files that might end up on your production server and
# can expose sensitive information about your website. These files may
# include: configuration files, files that contain metadata about the
# project (e.g.: project dependencies), build scripts, etc..
<FilesMatch "(^#.*#|\.(bak|conf|dist|fla|in[ci]|log|psd|sh|sql|sw[op])|~)$">
# Apache < 2.3
<IfModule !mod_authz_core.c>
Order allow,deny
Deny from all
Satisfy All
</IfModule>
# Apache ≥ 2.3
<IfModule mod_authz_core.c>
Require all denied
</IfModule>
</FilesMatch>
# ----------------------------------------------------------------------
# | HTTP Strict Transport Security (HSTS) |
# ----------------------------------------------------------------------
# Force client-side SSL redirection.
#
# If a user types `example.com` in their browser, even if the server
# redirects them to the secure version of the website, that still leaves
# a window of opportunity (the initial HTTP connection) for an attacker
# to downgrade or redirect the request.
#
# The following header ensures that browser will ONLY connect to your
# server via HTTPS, regardless of what the users type in the browser's
# address bar.
#
# (!) Remove the `includeSubDomains` optional directive if the website's
# subdomains are not using HTTPS.
#
# http://www.html5rocks.com/en/tutorials/security/transport-layer-security/
# https://tools.ietf.org/html/draft-ietf-websec-strict-transport-sec-14#section-6.1
# http://blogs.msdn.com/b/ieinternals/archive/2014/08/18/hsts-strict-transport-security-attacks-mitigations-deployment-https.aspx
# <IfModule mod_headers.c>
# Header set Strict-Transport-Security "max-age=16070400; includeSubDomains"
# </IfModule>
# ----------------------------------------------------------------------
# | Reducing MIME type security risks |
# ----------------------------------------------------------------------
# Prevent some browsers from MIME-sniffing the response.
#
# This reduces exposure to drive-by download attacks and cross-origin
# data leaks, and should be left uncommented, especially if the server
# is serving user-uploaded content or content that could potentially be
# treated as executable by the browser.
#
# http://www.slideshare.net/hasegawayosuke/owasp-hasegawa
# http://blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-v-comprehensive-protection.aspx
# http://msdn.microsoft.com/en-us/library/ie/gg622941.aspx
# https://mimesniff.spec.whatwg.org/
<IfModule mod_headers.c>
Header set X-Content-Type-Options "nosniff"
</IfModule>
# ----------------------------------------------------------------------
# | Reflected Cross-Site Scripting (XSS) attacks |
# ----------------------------------------------------------------------
# (1) Try to re-enable the cross-site scripting (XSS) filter built
# into most web browsers.
#
# The filter is usually enabled by default, but in some cases it
# may be disabled by the user. However, in Internet Explorer for
# example, it can be re-enabled just by sending the
# `X-XSS-Protection` header with the value of `1`.
#
# (2) Prevent web browsers from rendering the web page if a potential
# reflected (a.k.a non-persistent) XSS attack is detected by the
# filter.
#
# By default, if the filter is enabled and browsers detect a
# reflected XSS attack, they will attempt to block the attack
# by making the smallest possible modifications to the returned
# web page.
#
# Unfortunately, in some browsers (e.g.: Internet Explorer),
# this default behavior may allow the XSS filter to be exploited,
# thereby, it's better to inform browsers to prevent the rendering
# of the page altogether, instead of attempting to modify it.
#
# http://hackademix.net/2009/11/21/ies-xss-filter-creates-xss-vulnerabilities
#
# (!) Do not rely on the XSS filter to prevent XSS attacks! Ensure that
# you are taking all possible measures to prevent XSS attacks, the
# most obvious being: validating and sanitizing your website's inputs.
#
# http://blogs.msdn.com/b/ie/archive/2008/07/02/ie8-security-part-iv-the-xss-filter.aspx
# http://blogs.msdn.com/b/ieinternals/archive/2011/01/31/controlling-the-internet-explorer-xss-filter-with-the-x-xss-protection-http-header.aspx
# https://www.owasp.org/index.php/Cross-site_Scripting_%28XSS%29
# <IfModule mod_headers.c>
# # (1) (2)
# Header set X-XSS-Protection "1; mode=block"
# # `mod_headers` cannot match based on the content-type, however,
# # the `X-XSS-Protection` response header should be send only for
# # HTML documents and not for the other resources.
# <FilesMatch "\.(appcache|atom|bbaw|bmp|crx|css|cur|eot|f4[abpv]|flv|geojson|gif|htc|ico|jpe?g|js|json(ld)?|m4[av]|manifest|map|mp4|oex|og[agv]|opus|otf|pdf|png|rdf|rss|safariextz|svgz?|swf|topojson|tt[cf]|txt|vcard|vcf|vtt|webapp|web[mp]|woff2?|xloc|xml|xpi)$">
# Header unset X-XSS-Protection
# </FilesMatch>
# </IfModule>
# ----------------------------------------------------------------------
# | Server software information |
# ----------------------------------------------------------------------
# Prevent Apache from sending in the `Server` response header its
# exact version number, the description of the generic OS-type or
# information about its compiled-in modules.
#
# (!) The `ServerTokens` directive will only work in the main server
# configuration file, so don't try to enable it in the `.htaccess` file!
#
# https://httpd.apache.org/docs/current/mod/core.html#servertokens
# ServerTokens Prod
# ######################################################################
# # WEB PERFORMANCE #
# ######################################################################
# ----------------------------------------------------------------------
# | Compression |
# ----------------------------------------------------------------------
<IfModule mod_deflate.c>
# Force compression for mangled `Accept-Encoding` request headers
# https://developer.yahoo.com/blogs/ydn/pushing-beyond-gzipping-25601.html
<IfModule mod_setenvif.c>
<IfModule mod_headers.c>
SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s*,?\s*)+|[X~-]{4,13}$ HAVE_Accept-Encoding
RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
</IfModule>
</IfModule>
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Compress all output labeled with one of the following media types.
#
# (!) For Apache versions below version 2.3.7 you don't need to
# enable `mod_filter` and can remove the `<IfModule mod_filter.c>`
# and `</IfModule>` lines as `AddOutputFilterByType` is still in
# the core directives.
#
# https://httpd.apache.org/docs/current/mod/mod_filter.html#addoutputfilterbytype
<IfModule mod_filter.c>
AddOutputFilterByType DEFLATE "application/atom+xml" \
"application/javascript" \
"application/json" \
"application/ld+json" \
"application/manifest+json" \
"application/rdf+xml" \
"application/rss+xml" \
"application/schema+json" \
"application/vnd.geo+json" \
"application/vnd.ms-fontobject" \
"application/x-font-ttf" \
"application/x-javascript" \
"application/x-web-app-manifest+json" \
"application/xhtml+xml" \
"application/xml" \
"font/opentype" \
"image/bmp" \
"image/svg+xml" \
"image/x-icon" \
"text/cache-manifest" \
"text/css" \
"text/html" \
"text/javascript" \
"text/plain" \
"text/vcard" \
"text/vnd.rim.location.xloc" \
"text/vtt" \
"text/x-component" \
"text/xml"
</IfModule>
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Map the following filename extensions to the specified
# encoding type in order to make Apache serve the file types
# with the appropriate `Content-Encoding` response header
# (do note that this will NOT make Apache compress them!).
#
# If these files types would be served without an appropriate
# `Content-Enable` response header, client applications (e.g.:
# browsers) wouldn't know that they first need to uncompress
# the response, and thus, wouldn't be able to understand the
# content.
#
# https://httpd.apache.org/docs/current/mod/mod_mime.html#addencoding
<IfModule mod_mime.c>
AddEncoding gzip svgz
</IfModule>
</IfModule>
# ----------------------------------------------------------------------
# | Content transformation |
# ----------------------------------------------------------------------
# Prevent intermediate caches or proxies (e.g.: such as the ones
# used by mobile network providers) from modifying the website's
# content.
#
# https://tools.ietf.org/html/rfc2616#section-14.9.5
#
# (!) If you are using `mod_pagespeed`, please note that setting
# the `Cache-Control: no-transform` response header will prevent
# `PageSpeed` from rewriting `HTML` files, and, if the
# `ModPagespeedDisableRewriteOnNoTransform` directive isn't set
# to `off`, also from rewriting other resources.
#
# https://developers.google.com/speed/pagespeed/module/configuration#notransform
# <IfModule mod_headers.c>
# Header merge Cache-Control "no-transform"
# </IfModule>
# ----------------------------------------------------------------------
# | ETags |
# ----------------------------------------------------------------------
# Remove `ETags` as resources are sent with far-future expires headers.
#
# https://developer.yahoo.com/performance/rules.html#etags
# https://tools.ietf.org/html/rfc7232#section-2.3
# `FileETag None` doesn't work in all cases.
<IfModule mod_headers.c>
Header unset ETag
</IfModule>
FileETag None
# ----------------------------------------------------------------------
# | Expires headers |
# ----------------------------------------------------------------------
# Serve resources with far-future expires headers.
#
# (!) If you don't control versioning with filename-based
# cache busting, you should consider lowering the cache times
# to something like one week.
#
# https://httpd.apache.org/docs/current/mod/mod_expires.html
<IfModule mod_expires.c>
ExpiresActive on
ExpiresDefault "access plus 1 month"
# CSS
ExpiresByType text/css "access plus 1 year"
# Data interchange
ExpiresByType application/atom+xml "access plus 1 hour"
ExpiresByType application/rdf+xml "access plus 1 hour"
ExpiresByType application/rss+xml "access plus 1 hour"
ExpiresByType application/json "access plus 0 seconds"
ExpiresByType application/ld+json "access plus 0 seconds"
ExpiresByType application/schema+json "access plus 0 seconds"
ExpiresByType application/vnd.geo+json "access plus 0 seconds"
ExpiresByType application/xml "access plus 0 seconds"
ExpiresByType text/xml "access plus 0 seconds"
# Favicon (cannot be renamed!) and cursor images
ExpiresByType image/x-icon "access plus 1 week"
# HTML
ExpiresByType text/html "access plus 0 seconds"
# JavaScript
ExpiresByType application/javascript "access plus 1 year"
ExpiresByType application/x-javascript "access plus 1 year"
ExpiresByType text/javascript "access plus 1 year"
# Manifest files
ExpiresByType application/manifest+json "access plus 1 year"
ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds"
ExpiresByType text/cache-manifest "access plus 0 seconds"
# Media files
ExpiresByType audio/ogg "access plus 1 month"
ExpiresByType image/bmp "access plus 1 month"
ExpiresByType image/gif "access plus 1 month"
ExpiresByType image/jpeg "access plus 1 month"
ExpiresByType image/png "access plus 1 month"
ExpiresByType video/mp4 "access plus 1 month"
ExpiresByType video/ogg "access plus 1 month"
ExpiresByType video/webm "access plus 1 month"
# Web fonts
ExpiresByType application/font-woff "access plus 1 month"
ExpiresByType application/font-woff2 "access plus 1 month"
ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
ExpiresByType application/x-font-ttf "access plus 1 month"
ExpiresByType font/opentype "access plus 1 month"
ExpiresByType image/svg+xml "access plus 1 month"
</IfModule>
# ----------------------------------------------------------------------
# | File concatenation |
# ----------------------------------------------------------------------
# Allow concatenation from within specific files.
#
# e.g.:
#
# If you have the following lines in a file called, for
# example, `main.combined.js`:
#
# <!--#include file="js/jquery.js" -->
# <!--#include file="js/jquery.timer.js" -->
#
# Apache will replace those lines with the content of the
# specified files.
# <IfModule mod_include.c>
# <FilesMatch "\.combined\.js$">
# Options +Includes
# AddOutputFilterByType INCLUDES application/javascript \
# application/x-javascript \
# text/javascript
# SetOutputFilter INCLUDES
# </FilesMatch>
# <FilesMatch "\.combined\.css$">
# Options +Includes
# AddOutputFilterByType INCLUDES text/css
# SetOutputFilter INCLUDES
# </FilesMatch>
# </IfModule>
# ----------------------------------------------------------------------
# | Filename-based cache busting |
# ----------------------------------------------------------------------
# If you're not using a build process to manage your filename version
# revving, you might want to consider enabling the following directives
# to route all requests such as `/style.12345.css` to `/style.css`.
#
# To understand why this is important and even a better solution than
# using something like `*.css?v231`, please see:
# http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/
# <IfModule mod_rewrite.c>
# RewriteEngine On
# RewriteCond %{REQUEST_FILENAME} !-f
# RewriteRule ^(.+)\.(\d+)\.(bmp|css|cur|gif|ico|jpe?g|js|png|svgz?|webp)$ $1.$3 [L]
# </IfModule>

View File

@@ -21,17 +21,18 @@ import ErrorDetails from './pages/ErrorDetails';
export default class Coriolis extends React.Component { export default class Coriolis extends React.Component {
static childContextTypes = { static childContextTypes = {
language: React.PropTypes.object.isRequired,
sizeRatio: React.PropTypes.number.isRequired,
route: React.PropTypes.object.isRequired,
openMenu: React.PropTypes.func.isRequired,
closeMenu: React.PropTypes.func.isRequired, closeMenu: React.PropTypes.func.isRequired,
showModal: React.PropTypes.func.isRequired,
hideModal: React.PropTypes.func.isRequired, hideModal: React.PropTypes.func.isRequired,
tooltip: React.PropTypes.func.isRequired, language: React.PropTypes.object.isRequired,
termtip: React.PropTypes.func.isRequired, noTouch: React.PropTypes.bool.isRequired,
onCommand: React.PropTypes.func.isRequired,
onWindowResize: React.PropTypes.func.isRequired, onWindowResize: React.PropTypes.func.isRequired,
onCommand: React.PropTypes.func.isRequired openMenu: React.PropTypes.func.isRequired,
route: React.PropTypes.object.isRequired,
showModal: React.PropTypes.func.isRequired,
sizeRatio: React.PropTypes.number.isRequired,
termtip: React.PropTypes.func.isRequired,
tooltip: React.PropTypes.func.isRequired
}; };
/** /**
@@ -54,9 +55,10 @@ export default class Coriolis extends React.Component {
this.emitter = new EventEmitter(); this.emitter = new EventEmitter();
this.state = { this.state = {
noTouch: !('ontouchstart' in window || navigator.msMaxTouchPoints || navigator.maxTouchPoints),
page: null, page: null,
language: getLanguage(Persist.getLangCode()), language: getLanguage(Persist.getLangCode()),
route: null, route: {},
sizeRatio: Persist.getSizeRatio() sizeRatio: Persist.getSizeRatio()
}; };
@@ -237,17 +239,18 @@ export default class Coriolis extends React.Component {
*/ */
getChildContext() { getChildContext() {
return { return {
language: this.state.language,
route: this.state.route,
sizeRatio: this.state.sizeRatio,
openMenu: this._openMenu,
closeMenu: this._closeMenu, closeMenu: this._closeMenu,
showModal: this._showModal,
hideModal: this._hideModal, hideModal: this._hideModal,
tooltip: this._tooltip, language: this.state.language,
termtip: this._termtip, noTouch: this.state.noTouch,
onCommand: this._onCommand,
onWindowResize: this._onWindowResize, onWindowResize: this._onWindowResize,
onCommand: this._onCommand openMenu: this._openMenu,
route: this.state.route,
showModal: this._showModal,
sizeRatio: this.state.sizeRatio,
termtip: this._termtip,
tooltip: this._tooltip
}; };
} }
@@ -266,7 +269,7 @@ export default class Coriolis extends React.Component {
window.onerror = this._onError.bind(this); window.onerror = this._onError.bind(this);
window.addEventListener('resize', () => this.emitter.emit('windowResize')); window.addEventListener('resize', () => this.emitter.emit('windowResize'));
document.body.addEventListener('scroll', () => this._tooltip()); document.getElementById('coriolis').addEventListener('scroll', () => this._tooltip());
document.addEventListener('keydown', this._keyDown); document.addEventListener('keydown', this._keyDown);
Persist.addListener('language', this._onLanguageChange); Persist.addListener('language', this._onLanguageChange);
Persist.addListener('sizeRatio', this._onSizeRatioChange); Persist.addListener('sizeRatio', this._onSizeRatioChange);
@@ -281,11 +284,16 @@ export default class Coriolis extends React.Component {
render() { render() {
let currentMenu = this.state.currentMenu; let currentMenu = this.state.currentMenu;
return <div onClick={this._closeMenu}> return <div style={{ minHeight: '100%' }} onClick={this._closeMenu} className={ this.state.noTouch ? 'no-touch' : null }>
<Header appCacheUpdate={this.state.appCacheUpdate} currentMenu={currentMenu} /> <Header appCacheUpdate={this.state.appCacheUpdate} currentMenu={currentMenu} />
{ this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) : <NotFoundPage/> } { this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) : <NotFoundPage/> }
{ this.state.modal } { this.state.modal }
{ this.state.tooltip } { this.state.tooltip }
<footer>
<div className="right cap">
<a href="https://github.com/EDCD/coriolis" target="_blank" title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>
</div>
</footer>
</div>; </div>;
} }
} }

View File

@@ -1,15 +1,20 @@
import Persist from './stores/Persist'; import Persist from './stores/Persist';
let standalone = undefined;
/** /**
* Determine if the app is running in mobile/tablet 'standalone' mode * Determine if the app is running in mobile/tablet 'standalone' mode
* @return {Boolean} True if the app is in standalone mode * @return {Boolean} True if the app is in standalone mode
*/ */
function isStandAlone() { function isStandAlone() {
try { if (standalone === undefined) {
return window.navigator.standalone || (window.external && window.external.msIsSiteMode && window.external.msIsSiteMode()); try {
} catch (ex) { standalone = window.navigator.standalone || (window.external && window.external.msIsSiteMode && window.external.msIsSiteMode());
return false; } catch (ex) {
standalone = false;
}
} }
return standalone;
} }
/** /**
@@ -44,14 +49,14 @@ Router.start = function() {
if (isStandAlone()) { if (isStandAlone()) {
let state = Persist.getState(); let state = Persist.getState();
// If a previous state has been stored, load that state // If a previous state has been stored, load that state
if (state && state.name && state.params) { if (state && state.path) {
Router(this.props.initialPath || '/'); Router.replace(state.path, null, true);
} else { } else {
Router('/'); Router.replace('/', null, true);
} }
} else { } else {
let url = location.pathname + location.search; let url = location.pathname + location.search;
Router.replace(url, null, true, true); Router.replace(url, null, true);
} }
}; };
@@ -68,6 +73,9 @@ Router.go = function(path, state) {
let ctx = new Context(path, state); let ctx = new Context(path, state);
Router.dispatch(ctx); Router.dispatch(ctx);
if (!ctx.unhandled) { if (!ctx.unhandled) {
if (isStandAlone()) {
Persist.setState(ctx);
}
history.pushState(ctx.state, ctx.title, ctx.canonicalPath); history.pushState(ctx.state, ctx.title, ctx.canonicalPath);
} }
return ctx; return ctx;
@@ -85,7 +93,12 @@ Router.go = function(path, state) {
Router.replace = function(path, state, dispatch) { Router.replace = function(path, state, dispatch) {
gaTrack(path); gaTrack(path);
let ctx = new Context(path, state); let ctx = new Context(path, state);
if (dispatch) Router.dispatch(ctx); if (dispatch) {
Router.dispatch(ctx);
}
if (isStandAlone()) {
Persist.setState(ctx);
}
history.replaceState(ctx.state, ctx.title, ctx.canonicalPath); history.replaceState(ctx.state, ctx.title, ctx.canonicalPath);
return ctx; return ctx;
}; };
@@ -227,7 +240,7 @@ Route.prototype.match = function(path, params) {
*/ */
function gaTrack(path) { function gaTrack(path) {
if (window.ga) { if (window.ga) {
window.ga('send', 'pageview', { page: path }); window.ga('send', 'pageview', path);
} }
} }

View File

@@ -5,7 +5,7 @@ import { stopCtxPropagation } from '../utils/UtilityFunctions';
import cn from 'classnames'; import cn from 'classnames';
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons'; import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
const PRESS_THRESHOLD = 5000; // mouse/touch down threshold const PRESS_THRESHOLD = 500; // mouse/touch down threshold
/** /**
* Available modules menu * Available modules menu
@@ -97,7 +97,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
let m = modules[i]; let m = modules[i];
let mount = null; let mount = null;
let disabled = m.maxmass && (mass + (m.mass ? m.mass : 0)) > m.maxmass; let disabled = m.maxmass && (mass + (m.mass ? m.mass : 0)) > m.maxmass;
let active = mountedModule && mountedModule === m; let active = mountedModule && mountedModule.id === m.id;
let classes = cn(m.name ? 'lc' : 'c', { let classes = cn(m.name ? 'lc' : 'c', {
warning: !disabled && warningFunc && warningFunc(m), warning: !disabled && warningFunc && warningFunc(m),
active, active,
@@ -173,6 +173,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_touchStart(showDiff, event) { _touchStart(showDiff, event) {
event.preventDefault();
let rect = event.currentTarget.getBoundingClientRect(); let rect = event.currentTarget.getBoundingClientRect();
this.touchTimeout = setTimeout(showDiff.bind(this, rect), PRESS_THRESHOLD); this.touchTimeout = setTimeout(showDiff.bind(this, rect), PRESS_THRESHOLD);
} }
@@ -183,10 +184,10 @@ export default class AvailableModulesMenu extends TranslatedComponent {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_touchEnd(select, event) { _touchEnd(select, event) {
event.preventDefault();
if (this.touchTimeout !== null) { // If timeout has not fired (been nulled out) yet if (this.touchTimeout !== null) { // If timeout has not fired (been nulled out) yet
select(); select();
} }
event.preventDefault();
this._hideDiff(); this._hideDiff();
} }

View File

@@ -208,7 +208,7 @@ export default class BarChart extends TranslatedComponent {
<g className='x axis' ref={(elem) => d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}> <g className='x axis' ref={(elem) => d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}>
<text className='cap' y='30' dy='.1em' x={innerWidth / 2} style={{ textAnchor: 'middle' }}> <text className='cap' y='30' dy='.1em' x={innerWidth / 2} style={{ textAnchor: 'middle' }}>
<tspan>{title}</tspan> <tspan>{title}</tspan>
{ unit ? <tspan className='metric'>{` (${unit})`}</tspan> : null } { unit ? <tspan className='metric'> ({unit})</tspan> : null }
</text> </text>
</g> </g>
<g className='y axis' ref={(elem) => { let e = d3.select(elem); e.call(this.yAxis); e.selectAll('text').each(insertLinebreaks); }} /> <g className='y axis' ref={(elem) => { let e = d3.select(elem); e.call(this.yAxis); e.selectAll('text').each(insertLinebreaks); }} />

View File

@@ -304,8 +304,8 @@ export default class CostSection extends TranslatedComponent {
<tr className='main'> <tr className='main'>
<th colSpan='2' className='sortable le' onClick={this._sortCostBy.bind(this,'m')}> <th colSpan='2' className='sortable le' onClick={this._sortCostBy.bind(this,'m')}>
{translate('module')} {translate('module')}
{shipDiscount && <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} -${formats.pct(shipDiscount)}]`}</u>} {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>} {moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
</th> </th>
<th className='sortable le' onClick={this._sortCostBy.bind(this, 'cr')} >{translate('credits')}</th> <th className='sortable le' onClick={this._sortCostBy.bind(this, 'cr')} >{translate('credits')}</th>
</tr> </tr>
@@ -363,7 +363,7 @@ export default class CostSection extends TranslatedComponent {
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'buyName')}>{translate('buy')}</th> <th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'buyName')}>{translate('buy')}</th>
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'cr')}> <th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'cr')}>
{translate('net cost')} {translate('net cost')}
{moduleDiscount < 1 && <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u>} {moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
</th> </th>
</tr> </tr>
</thead> </thead>
@@ -400,7 +400,7 @@ export default class CostSection extends TranslatedComponent {
let retrofitCosts = []; let retrofitCosts = [];
let retrofitTotal = 0, i, l, item; let retrofitTotal = 0, i, l, item;
if (ship.bulkheads.index != retrofitShip.bulkheads.index) { if (ship.bulkheads.m.index != retrofitShip.bulkheads.m.index) {
item = { item = {
buyClassRating: ship.bulkheads.m.class + ship.bulkheads.m.rating, buyClassRating: ship.bulkheads.m.class + ship.bulkheads.m.rating,
buyName: ship.bulkheads.m.name, buyName: ship.bulkheads.m.name,
@@ -606,6 +606,7 @@ export default class CostSection extends TranslatedComponent {
} }
if (nextProps.ship != this.props.ship || nextProps.code != this.props.code) { if (nextProps.ship != this.props.ship || nextProps.code != this.props.code) {
nextProps.ship.applyDiscounts(Persist.getShipDiscount(), Persist.getModuleDiscount());
this._updateAmmoCosts(nextProps.ship); this._updateAmmoCosts(nextProps.ship);
this._updateRetrofit(nextProps.ship, retrofitShip); this._updateRetrofit(nextProps.ship, retrofitShip);
this._sortCost(nextProps.ship); this._sortCost(nextProps.ship);

View File

@@ -1,5 +1,9 @@
import React from 'react'; import React from 'react';
import Slot from './Slot'; import Slot from './Slot';
import { DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret, ListModifications } from './SvgIcons';
import { Modifications } from 'coriolis-data/dist';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
/** /**
* Hardpoint / Utility Slot * Hardpoint / Utility Slot
@@ -33,24 +37,33 @@ export default class HardpointSlot extends Slot {
*/ */
_getSlotDetails(m, translate, formats, u) { _getSlotDetails(m, translate, formats, u) {
if (m) { if (m) {
let classRating = `${m.class}${m.rating}${m.mount ? '/' + m.mount : ''}${m.missile ? m.missile : ''}`; let classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
let { drag, drop } = this.props; let { drag, drop } = this.props;
let { termtip, tooltip } = this.context;
let validMods = Modifications.validity[m.grp] || [];
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}> return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
<div className={'cb'}> <div className={'cb'}>
<div className={'l'}>{classRating + ' ' + translate(m.name || m.grp)}</div> <div className={'l'}>
<div className={'r'}>{m.mass}{u.T}</div> {m.mount && m.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed /></span> : ''}
{m.mount && m.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : ''}
{m.mount && m.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : ''}
{m.type && m.type.match('K') ? <span onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /></span> : ''}
{m.type && m.type.match('T') ? <span onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /></span> : ''}
{m.type && m.type.match('E') ? <span onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /></span> : ''}
{classRating} {translate(m.name || m.grp)}</div>
<div className={'r'}>{formats.round(m.getMass())}{u.T}</div>
</div> </div>
<div className={'cb'}> <div className={'cb'}>
{ m.damage ? <div className={'l'}>{translate('damage')}: {m.damage} { m.ssdam ? <span>({formats.int(m.ssdam)} {u.MJ})</span> : null }</div> : null } { m.getDps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'dpssdps' : 'dps')} onMouseOut={tooltip.bind(null, null)}>{translate('DPS')}: {formats.round1(m.getDps())} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) })</span> : null }</div> : null }
{ m.dps ? <div className={'l'}>{translate('DPS')}: {m.dps} { m.mjdps ? <span>({formats.int(m.mjdps)} {u.MJ})</span> : null }</div> : null } { m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'epsseps' : 'eps')} onMouseOut={tooltip.bind(null, null)}>{translate('EPS')}: {formats.round1(m.getEps())}{u.MW} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) }{u.MW})</span> : null }</div> : null }
{ m.thermload ? <div className={'l'}>{translate('T-Load')}: {m.thermload}</div> : null } { m.getHps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'hpsshps' : 'hps')} onMouseOut={tooltip.bind(null, null)}>{translate('HPS')}: {formats.round1(m.getHps())} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getHps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) })</span> : null }</div> : null }
{ m.type ? <div className={'l'}>{translate('type')}: {m.type}</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.rof ? <div className={'l'}>{translate('ROF')}: {m.rof}{u.ps}</div> : null } { m.getRoF() ? <div className={'l'} onMouseOver={termtip.bind(null, 'rof')} onMouseOut={tooltip.bind(null, null)}>{translate('ROF')}: {formats.f1(m.getRoF())}{u.ps}</div> : null }
{ m.armourpen ? <div className={'l'}>{translate('pen')}: {m.armourpen}</div> : null } { m.getRange() && !m.getDps() ? <div className={'l'}>{translate('Range')} : {formats.round(m.getRange() / 1000)}{u.km}</div> : null }
{ m.shieldmul ? <div className={'l'}>+{formats.rPct(m.shieldmul)}</div> : null } { m.getShieldBoost() ? <div className={'l'}>+{formats.pct1(m.getShieldBoost())}</div> : null }
{ m.range ? <div className={'l'}>{m.range} <u>km</u></div> : null } { m.getAmmo() ? <div className={'l'}>{translate('ammunition')}: {formats.int(m.getClip())}/{formats.int(m.getAmmo())}</div> : null }
{ m.ammo >= 0 ? <div className={'l'}>{translate('ammo')}: {formats.int(m.clip)}+{formats.int(m.ammo)}</div> : null } { m && validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
</div> </div>
</div>; </div>;
} else { } else {

View File

@@ -69,6 +69,7 @@ export default class HardpointsSlotSection extends SlotSection {
availableModules={() => availableModules.getHps(h.maxClass)} availableModules={() => availableModules.getHps(h.maxClass)}
onOpen={this._openMenu.bind(this, h)} onOpen={this._openMenu.bind(this, h)}
onSelect={this._selectModule.bind(this, h)} onSelect={this._selectModule.bind(this, h)}
onChange={this.props.onChange}
selected={currentMenu == h} selected={currentMenu == h}
drag={this._drag.bind(this, h)} drag={this._drag.bind(this, h)}
dragOver={this._dragOverSlot.bind(this, h)} dragOver={this._dragOverSlot.bind(this, h)}
@@ -94,6 +95,7 @@ export default class HardpointsSlotSection extends SlotSection {
return <div className='select hardpoint' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}> return <div className='select hardpoint' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul> <ul>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li> <li className='lc' onClick={this._empty}>{translate('empty all')}</li>
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
</ul> </ul>
<div className='select-group cap'>{translate('pl')}</div> <div className='select-group cap'>{translate('pl')}</div>
<ul> <ul>
@@ -125,6 +127,16 @@ export default class HardpointsSlotSection extends SlotSection {
<li className='c' onClick={_fill.bind(this, 'c', 'G')}><MountGimballed className='lg'/></li> <li className='c' onClick={_fill.bind(this, 'c', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'c', 'T')}><MountTurret className='lg'/></li> <li className='c' onClick={_fill.bind(this, 'c', 'T')}><MountTurret className='lg'/></li>
</ul> </ul>
<div className='select-group cap'>{translate('fc')}</div>
<ul>
<li className='c' onClick={_fill.bind(this, 'fc', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'fc', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'fc', 'T')}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('nl')}</div>
<ul>
<li className='lc' onClick={_fill.bind(this, 'nl', 'F')}>{translate('nl')}</li>
</ul>
</div>; </div>;
} }

View File

@@ -27,7 +27,7 @@ function normalizePercent(val) {
if (val === '' || isNaN(val)) { if (val === '' || isNaN(val)) {
return 0; return 0;
} }
val = Math.round(val * 100) / 100; val = Math.round(val * 1000) / 1000;
return val >= 100 ? 100 : val; return val >= 100 ? 100 : val;
} }
@@ -362,37 +362,51 @@ export default class Header extends TranslatedComponent {
return ( return (
<div className='menu-list no-wrap cap' onClick={ (e) => e.stopPropagation() }> <div className='menu-list no-wrap cap' onClick={ (e) => e.stopPropagation() }>
<div style={{ lineHeight: '2em' }}> <table style={{ width: '100%', background: 'none', borderSpacing: '0 0.5em' }}>
{translate('language')} <tbody>
<select className='cap' value={Persist.getLangCode()} onChange={this._setLanguage}> <tr>
{this.languageOptions} <td>{translate('language')}</td>
</select> <td className='ri'>
<br/> <select className='cap' dir='rtl' value={Persist.getLangCode()} onChange={this._setLanguage}>
<span className='cap ptr' onClick={this._toggleTooltips} > {this.languageOptions}
{translate('tooltips')} </select>
<div className={cn({ disabled: !tips, 'primary-disabled': tips })} style={{ marginLeft: '0.5em', display: 'inline-block' }}>{(tips ? '✓' : '✗')}</div> </td>
</span> </tr>
<br/> <tr className='cap ptr' onClick={this._toggleTooltips} >
{translate('insurance')} <td>{translate('tooltips')}</td>
<select className='cap' value={Persist.getInsurance()} onChange={this._setInsurance}> <td className={cn('ri', { disabled: !tips, 'primary-disabled': tips })}>{(tips ? '✓' : '✗')}</td>
{this.insuranceOptions} </tr>
</select> <tr>
<br/> <td>{translate('insurance')}</td>
{translate('ship')} {translate('discount')} <td className='ri'>
<input type='text' size='10' value={this.state.shipDiscount} onChange={this._changeShipDiscount} onFocus={selectAll} onBlur={this._setShipDiscount} onKeyDown={this._kpShipDiscount}/> <select className='cap' dir='rtl' value={Persist.getInsurance()} onChange={this._setInsurance}>
<u className='primary-disabled'>%</u> {this.insuranceOptions}
<br/> </select>
{translate('module')} {translate('discount')} </td>
<input type='text' size='10' value={this.state.moduleDiscount} onChange={this._changeModuleDiscount} onFocus={selectAll} onBlur={this._setModuleDiscount} onKeyDown={this._kpModuleDiscount}/> </tr>
<u className='primary-disabled'>%</u> <tr>
</div> <td>{translate('ship')} {translate('discount')}</td>
<td className='ri'>
<input type='text' size='10' value={this.state.shipDiscount} onChange={this._changeShipDiscount} onFocus={selectAll} onBlur={this._setShipDiscount} onKeyDown={this._kpShipDiscount}/>
<u className='primary-disabled'>%</u>
</td>
</tr>
<tr>
<td>{translate('module')} {translate('discount')}</td>
<td className='ri'>
<input type='text' size='10' value={this.state.moduleDiscount} onChange={this._changeModuleDiscount} onFocus={selectAll} onBlur={this._setModuleDiscount} onKeyDown={this._kpModuleDiscount}/>
<u className='primary-disabled'>%</u>
</td>
</tr>
</tbody>
</table>
<hr /> <hr />
<ul> <ul style={{ width: '100%' }}>
{translate('builds')} & {translate('comparisons')} {translate('builds')} & {translate('comparisons')}
<li><Link href="#" className='block' onClick={this._showBackup.bind(this)}>{translate('backup')}</Link></li> <li><Link href="#" className='block' onClick={this._showBackup.bind(this)}>{translate('backup')}</Link></li>
<li><Link href="#" className='block' onClick={this._showDetailedExport.bind(this)}>{translate('detailed export')}</Link></li> <li><Link href="#" className='block' onClick={this._showDetailedExport.bind(this)}>{translate('detailed export')}</Link></li>
<li><Link href="#" className='block' onClick={this._showImport.bind(this)}>{translate('import')}</Link></li> <li><Link href="#" className='block' onClick={this._showImport.bind(this)}>{translate('import')}</Link></li>
<li><Link href="#" onClick={this._showDeleteAll.bind(this)}>{translate('delete all')}</Link></li> <li><Link href="#" className='block' onClick={this._showDeleteAll.bind(this)}>{translate('delete all')}</Link></li>
</ul> </ul>
<hr /> <hr />
<table style={{ width: 300, backgroundColor: 'transparent' }}> <table style={{ width: 300, backgroundColor: 'transparent' }}>
@@ -462,32 +476,28 @@ export default class Header extends TranslatedComponent {
let translate = this.context.language.translate; let translate = this.context.language.translate;
let openedMenu = this.props.currentMenu; let openedMenu = this.props.currentMenu;
let hasBuilds = Persist.hasBuilds(); let hasBuilds = Persist.hasBuilds();
if (this.props.appCacheUpdate) {
return <div id="app-update" onClick={() => window.location.reload() }>{translate('PHRASE_UPDATE_RDY')}</div>;
}
return ( return (
<header> <header>
{this.props.appCacheUpdate && <div id="app-update" onClick={() => window.location.reload() }>{translate('PHRASE_UPDATE_RDY')}</div>}
<Link className='l' href='/' style={{ marginRight: '1em' }} title='Home'><CoriolisLogo className='icon xl' /></Link> <Link className='l' href='/' style={{ marginRight: '1em' }} title='Home'><CoriolisLogo className='icon xl' /></Link>
<div className='l menu'> <div className='l menu'>
<div className={cn('menu-header', { selected: openedMenu == 's' })} onClick={this._openShips}> <div className={cn('menu-header', { selected: openedMenu == 's' })} onClick={this._openShips}>
<Rocket className='warning' /><span className='menu-item-label'>{' ' + translate('ships')}</span> <Rocket className='warning' /><span className='menu-item-label'>{translate('ships')}</span>
</div> </div>
{openedMenu == 's' ? this._getShipsMenu() : null} {openedMenu == 's' ? this._getShipsMenu() : null}
</div> </div>
<div className='l menu'> <div className='l menu'>
<div className={cn('menu-header', { selected: openedMenu == 'b', disabled: !hasBuilds })} onClick={hasBuilds && this._openBuilds}> <div className={cn('menu-header', { selected: openedMenu == 'b', disabled: !hasBuilds })} onClick={hasBuilds && this._openBuilds}>
<Hammer className={cn('warning', { 'warning-disabled': !hasBuilds })} /><span className='menu-item-label'>{' ' + translate('builds')}</span> <Hammer className={cn('warning', { 'warning-disabled': !hasBuilds })} /><span className='menu-item-label'>{translate('builds')}</span>
</div> </div>
{openedMenu == 'b' ? this._getBuildsMenu() : null} {openedMenu == 'b' ? this._getBuildsMenu() : null}
</div> </div>
<div className='l menu'> <div className='l menu'>
<div className={cn('menu-header', { selected: openedMenu == 'comp', disabled: !hasBuilds })} onClick={hasBuilds && this._openComp}> <div className={cn('menu-header', { selected: openedMenu == 'comp', disabled: !hasBuilds })} onClick={hasBuilds && this._openComp}>
<StatsBars className={cn('warning', { 'warning-disabled': !hasBuilds })} /><span className='menu-item-label'>{' ' + translate('compare')}</span> <StatsBars className={cn('warning', { 'warning-disabled': !hasBuilds })} /><span className='menu-item-label'>{translate('compare')}</span>
</div> </div>
{openedMenu == 'comp' ? this._getComparisonsMenu() : null} {openedMenu == 'comp' ? this._getComparisonsMenu() : null}
</div> </div>
@@ -502,4 +512,4 @@ export default class Header extends TranslatedComponent {
); );
} }
} }

View File

@@ -1,6 +1,8 @@
import React from 'react'; import React from 'react';
import Slot from './Slot'; import Slot from './Slot';
import { Infinite } from './SvgIcons'; import { ListModifications } from './SvgIcons';
import { Modifications } from 'coriolis-data/dist';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
/** /**
* Internal Slot * Internal Slot
@@ -18,30 +20,39 @@ export default class InternalSlot extends Slot {
_getSlotDetails(m, translate, formats, u) { _getSlotDetails(m, translate, formats, u) {
if (m) { if (m) {
let classRating = m.class + m.rating; let classRating = m.class + m.rating;
let { drag, drop } = this.props; let { drag, drop, ship } = this.props;
let { termtip, tooltip } = this.context;
let validMods = Modifications.validity[m.grp] || [];
let mass = m.getMass() || m.cargo || m.fuel || 0;
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}> return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
<div className={'cb'}> <div className={'cb'}>
<div className={'l'}>{classRating + ' ' + translate(m.name || m.grp)}</div> <div className={'l'}>{classRating} {translate(m.name || m.grp)}</div>
<div className={'r'}>{m.mass || m.cargo || m.fuel || 0}{u.T}</div> <div className={'r'}>{formats.round(mass)}{u.T}</div>
</div> </div>
<div className={'cb'}> <div className={'cb'}>
{ m.optmass ? <div className={'l'}>{translate('optimal mass') + ': '}{m.optmass}{u.T}</div> : null } { m.getOptMass() ? <div className={'l'}>{translate('optimal mass')}: {formats.int(m.getOptMass())}{u.T}</div> : null }
{ m.maxmass ? <div className={'l'}>{translate('max mass') + ': '}{m.maxmass}{u.T}</div> : null } { m.getMaxMass() ? <div className={'l'}>{translate('max mass')}: {formats.int(m.getMaxMass())}{u.T}</div> : null }
{ m.bins ? <div className={'l'}>{m.bins + ' '}<u>{translate('bins')}</u></div> : null } { m.bins ? <div className={'l'}>{m.bins} <u>{translate('bins')}</u></div> : null }
{ m.bays ? <div className={'l'}>{translate('bays') + ': ' + m.bays}</div> : null } { m.bays ? <div className={'l'}>{translate('bays')}: {m.bays}</div> : null }
{ m.rate ? <div className={'l'}>{translate('rate')}: {m.rate}{u.kgs}&nbsp;&nbsp;&nbsp;{translate('refuel time')}: {formats.time(this.props.fuel * 1000 / m.rate)}</div> : null } { m.rate ? <div className={'l'}>{translate('rate')}: {m.rate}{u.kgs}&nbsp;&nbsp;&nbsp;{translate('refuel time')}: {formats.time(this.props.fuel * 1000 / m.rate)}</div> : null }
{ m.ammo ? <div className={'l'}>{translate('ammo')}: {formats.gen(m.ammo)}</div> : null } { m.getAmmo() ? <div className={'l'}>{translate('ammunition')}: {formats.gen(m.getAmmo())}</div> : null }
{ m.cells ? <div className={'l'}>{translate('cells')}: {m.cells}</div> : null } { m.cells ? <div className={'l'}>{translate('cells')}: {m.cells}</div> : null }
{ m.recharge ? <div className={'l'}>{translate('recharge')}: {m.recharge} <u>MJ</u>&nbsp;&nbsp;&nbsp;{translate('total')}: {m.cells * m.recharge}{u.MJ}</div> : null } { m.recharge ? <div className={'l'}>{translate('recharge')}: {m.recharge} <u>MJ</u>&nbsp;&nbsp;&nbsp;{translate('total')}: {m.cells * m.recharge}{u.MJ}</div> : null }
{ m.repair ? <div className={'l'}>{translate('repair')}: {m.repair}</div> : null } { m.repair ? <div className={'l'}>{translate('repair')}: {m.repair}</div> : null }
{ m.range ? <div className={'l'}>{translate('range')} {m.range}{u.km}</div> : null } { m.getFacingLimit() ? <div className={'l'}>{translate('facinglimit')} {formats.f1(m.getFacingLimit())}°</div> : null }
{ m.getRange() ? <div className={'l'}>{translate('range')} {formats.f2(m.getRange())}{u.km}</div> : null }
{ m.getRangeT() ? <div className={'l'}>{translate('ranget')} {formats.f1(m.getRangeT())}{u.s}</div> : null }
{ m.spinup ? <div className={'l'}>{translate('spinup')}: {formats.f1(m.spinup)}{u.s}</div> : null }
{ m.time ? <div className={'l'}>{translate('time')}: {formats.time(m.time)}</div> : null } { m.time ? <div className={'l'}>{translate('time')}: {formats.time(m.time)}</div> : null }
{ m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null } { m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
{ m.rangeLS ? <div className={'l'}>{m.rangeLS}{u.Ls}</div> : null } { m.rangeLS ? <div className={'l'}>{translate('range')}: {m.rangeLS}{u.Ls}</div> : null }
{ m.rangeLS === null ? <div className={'l'}><Infinite/>{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.rangeRating ? <div className={'l'}>{translate('range')}: {m.rangeRating}</div> : null }
{ m.armouradd ? <div className={'l'}>+{m.armouradd} <u>{translate('armour')}</u></div> : null } { m.getHullReinforcement() ? <div className={'l'}>+{formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost'))} <u className='cap'>{translate('armour')}</u></div> : null }
{ m.passengers ? <div className={'l'}>{translate('passengers')}: {m.passengers}</div> : null }
{ m && validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
</div> </div>
</div>; </div>;
} else { } else {

View File

@@ -16,12 +16,17 @@ export default class InternalSlotSection extends SlotSection {
* @param {Object} context React Component context * @param {Object} context React Component context
*/ */
constructor(props, context) { constructor(props, context) {
super(props, context, 'internal', 'internal compartments'); super(props, context, 'internal', 'optional internal');
this._empty = this._empty.bind(this); this._empty = this._empty.bind(this);
this._fillWithCargo = this._fillWithCargo.bind(this); this._fillWithCargo = this._fillWithCargo.bind(this);
this._fillWithCells = this._fillWithCells.bind(this); this._fillWithCells = this._fillWithCells.bind(this);
this._fillWithArmor = this._fillWithArmor.bind(this); this._fillWithArmor = this._fillWithArmor.bind(this);
this._fillWithFuelTanks = this._fillWithFuelTanks.bind(this);
this._fillWithLuxuryCabins = this._fillWithLuxuryCabins.bind(this);
this._fillWithFirstClassCabins = this._fillWithFirstClassCabins.bind(this);
this._fillWithBusinessClassCabins = this._fillWithBusinessClassCabins.bind(this);
this._fillWithEconomyClassCabins = this._fillWithEconomyClassCabins.bind(this);
} }
/** /**
@@ -49,6 +54,86 @@ export default class InternalSlotSection extends SlotSection {
this._close(); this._close();
} }
/**
* Fill all slots with fuel tanks
* @param {SyntheticEvent} event Event
*/
_fillWithFuelTanks(event) {
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
if (clobber || !slot.m) {
ship.use(slot, ModuleUtils.findInternal('ft', slot.maxClass, 'C'));
}
});
this.props.onChange();
this._close();
}
/**
* Fill all slots with luxury passenger cabins
* @param {SyntheticEvent} event Event
*/
_fillWithLuxuryCabins(event) {
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
if (clobber || !slot.m) {
ship.use(slot, ModuleUtils.findInternal('pcq', Math.min(slot.maxClass, 6), 'B')); // Passenger cabins top out at 6
}
});
this.props.onChange();
this._close();
}
/**
* Fill all slots with first class passenger cabins
* @param {SyntheticEvent} event Event
*/
_fillWithFirstClassCabins(event) {
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
if (clobber || !slot.m) {
ship.use(slot, ModuleUtils.findInternal('pcm', Math.min(slot.maxClass, 6), 'C')); // Passenger cabins top out at 6
}
});
this.props.onChange();
this._close();
}
/**
* Fill all slots with business class passenger cabins
* @param {SyntheticEvent} event Event
*/
_fillWithBusinessClassCabins(event) {
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
if (clobber || !slot.m) {
ship.use(slot, ModuleUtils.findInternal('pci', Math.min(slot.maxClass, 6), 'D')); // Passenger cabins top out at 6
}
});
this.props.onChange();
this._close();
}
/**
* Fill all slots with economy class passenger cabins
* @param {SyntheticEvent} event Event
*/
_fillWithEconomyClassCabins(event) {
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
if (clobber || !slot.m) {
ship.use(slot, ModuleUtils.findInternal('pce', Math.min(slot.maxClass, 6), 'E')); // Passenger cabins top out at 6
}
});
this.props.onChange();
this._close();
}
/** /**
* Fill all slots with Shield Cell Banks * Fill all slots with Shield Cell Banks
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
@@ -58,7 +143,7 @@ export default class InternalSlotSection extends SlotSection {
let ship = this.props.ship; let ship = this.props.ship;
let chargeCap = 0; // Capacity of single activation let chargeCap = 0; // Capacity of single activation
ship.internal.forEach(function(slot) { ship.internal.forEach(function(slot) {
if ((!slot.m || (clobber && !ModuleUtils.isShieldGenerator(slot.m.grp))) && (!slot.eligible || slot.eligible.scb)) { // Check eligibility due to Orca special case if ((!slot.m || (clobber && !ModuleUtils.isShieldGenerator(slot.m.grp))) && (!slot.eligible || slot.eligible.scb)) { // Check eligibility due to passenger ships special case
ship.use(slot, ModuleUtils.findInternal('scb', slot.maxClass, 'A')); ship.use(slot, ModuleUtils.findInternal('scb', slot.maxClass, 'A'));
ship.setSlotEnabled(slot, chargeCap <= ship.shieldStrength); // Don't waste cell capacity on overcharge ship.setSlotEnabled(slot, chargeCap <= ship.shieldStrength); // Don't waste cell capacity on overcharge
chargeCap += slot.m.recharge; chargeCap += slot.m.recharge;
@@ -76,7 +161,7 @@ export default class InternalSlotSection extends SlotSection {
let clobber = event.getModifierState('Alt'); let clobber = event.getModifierState('Alt');
let ship = this.props.ship; let ship = this.props.ship;
ship.internal.forEach((slot) => { ship.internal.forEach((slot) => {
if (clobber || !slot.c) { if (clobber || !slot.m) {
ship.use(slot, ModuleUtils.findInternal('hr', Math.min(slot.maxClass, 5), 'D')); // Hull reinforcements top out at 5D ship.use(slot, ModuleUtils.findInternal('hr', Math.min(slot.maxClass, 5), 'D')); // Hull reinforcements top out at 5D
} }
}); });
@@ -99,7 +184,7 @@ export default class InternalSlotSection extends SlotSection {
let slots = []; let slots = [];
let { currentMenu, ship } = this.props; let { currentMenu, ship } = this.props;
let { originSlot, targetSlot } = this.state; let { originSlot, targetSlot } = this.state;
let { internal, fuelCapacity, ladenMass } = ship; let { internal, fuelCapacity } = ship;
let availableModules = ship.getAvailableModules(); let availableModules = ship.getAvailableModules();
for (let i = 0, l = internal.length; i < l; i++) { for (let i = 0, l = internal.length; i < l; i++) {
@@ -108,11 +193,13 @@ export default class InternalSlotSection extends SlotSection {
slots.push(<InternalSlot slots.push(<InternalSlot
key={i} key={i}
maxClass={s.maxClass} maxClass={s.maxClass}
availableModules={() => availableModules.getInts(s.maxClass, s.eligible)} availableModules={() => availableModules.getInts(ship, s.maxClass, s.eligible)}
onOpen={this._openMenu.bind(this,s)} onOpen={this._openMenu.bind(this,s)}
onChange={this.props.onChange}
onSelect={this._selectModule.bind(this, s)} onSelect={this._selectModule.bind(this, s)}
selected={currentMenu == s} selected={currentMenu == s}
enabled={s.enabled} enabled={s.enabled}
eligible={s.eligible}
m={s.m} m={s.m}
drag={this._drag.bind(this, s)} drag={this._drag.bind(this, s)}
dragOver={this._dragOverSlot.bind(this, s)} dragOver={this._dragOverSlot.bind(this, s)}
@@ -129,15 +216,22 @@ export default class InternalSlotSection extends SlotSection {
/** /**
* Generate the section drop-down menu * Generate the section drop-down menu
* @param {Function} translate Translate function * @param {Function} translate Translate function
* @param {Function} ship The ship
* @return {React.Component} Section menu * @return {React.Component} Section menu
*/ */
_getSectionMenu(translate) { _getSectionMenu(translate, ship) {
return <div className='select' onClick={e => e.stopPropagation()} onContextMenu={stopCtxPropagation}> return <div className='select' onClick={e => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul> <ul>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li> <li className='lc' onClick={this._empty}>{translate('empty all')}</li>
<li className='lc' onClick={this._fillWithCargo}>{translate('cargo')}</li> <li className='lc' onClick={this._fillWithCargo}>{translate('cargo')}</li>
<li className='lc' onClick={this._fillWithCells}>{translate('scb')}</li> <li className='lc' onClick={this._fillWithCells}>{translate('scb')}</li>
<li className='lc' onClick={this._fillWithArmor}>{translate('hr')}</li> <li className='lc' onClick={this._fillWithArmor}>{translate('hr')}</li>
<li className='lc' onClick={this._fillWithFuelTanks}>{translate('ft')}</li>
<li className='lc' onClick={this._fillWithEconomyClassCabins}>{translate('pce')}</li>
<li className='lc' onClick={this._fillWithBusinessClassCabins}>{translate('pci')}</li>
<li className='lc' onClick={this._fillWithFirstClassCabins}>{translate('pcm')}</li>
{ ship.luxuryCabins ? <li className='lc' onClick={this._fillWithLuxuryCabins}>{translate('pcq')}</li> : ''}
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
</ul> </ul>
</div>; </div>;
} }

View File

@@ -235,13 +235,13 @@ export default class LineChart extends TranslatedComponent {
<g className='x axis' ref={(elem) => d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}> <g className='x axis' ref={(elem) => d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}>
<text className='cap' y='30' dy='.1em' x={innerWidth / 2} style={{ textAnchor: 'middle' }}> <text className='cap' y='30' dy='.1em' x={innerWidth / 2} style={{ textAnchor: 'middle' }}>
<tspan>{xLabel}</tspan> <tspan>{xLabel}</tspan>
<tspan className='metric'>{` (${xUnit})`}</tspan> <tspan className='metric'> ({xUnit})</tspan>
</text> </text>
</g> </g>
<g className='y axis' ref={(elem) => d3.select(elem).call(this.yAxis)}> <g className='y axis' ref={(elem) => d3.select(elem).call(this.yAxis)}>
<text className='cap' transform='rotate(-90)' y='-50' dy='.1em' x={innerHeight / -2} style={{ textAnchor: 'middle' }}> <text className='cap' transform='rotate(-90)' y='-50' dy='.1em' x={innerHeight / -2} style={{ textAnchor: 'middle' }}>
<tspan>{yLabel}</tspan> <tspan>{yLabel}</tspan>
<tspan className='metric'>{` (${yUnit})`}</tspan> <tspan className='metric'> ({yUnit})</tspan>
</text> </text>
</g> </g>
<g ref={(g) => this.tipContainer = d3.select(g)} style={{ display: 'none' }}> <g ref={(g) => this.tipContainer = d3.select(g)} style={{ display: 'none' }}>

View File

@@ -36,7 +36,7 @@ export default class Link extends React.Component {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
handler(event) { handler(event) {
if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey || event.button > 1) { if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey || event.button > 0) {
return; return;
} }
event.preventDefault(); event.preventDefault();

View File

@@ -0,0 +1,69 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
import TranslatedComponent from './TranslatedComponent';
import cn from 'classnames';
import NumberEditor from 'react-number-editor';
/**
* Modification
*/
export default class ModificationsMenu extends TranslatedComponent {
static propTypes = {
ship: React.PropTypes.object.isRequired,
m: React.PropTypes.object.isRequired,
name: React.PropTypes.string.isRequired,
onChange: React.PropTypes.func.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
this.state = {};
this.state.value = this.props.m.getModValue(this.props.name) * 100 || 0;
}
/**
* Update modification given a value.
* @param {Number} value The value to set
*/
_updateValue(value) {
let scaledValue = Math.floor(Number(value) * 100) / 10000;
// Limit to +1000% / -100%
if (scaledValue > 10) {
scaledValue = 10;
value = 1000;
}
if (scaledValue < -1) {
scaledValue = -1;
value = -100;
}
let m = this.props.m;
let name = this.props.name;
let ship = this.props.ship;
ship.setModification(m, name, scaledValue);
this.setState({ value });
this.props.onChange();
}
/**
* Render the modification
* @return {React.Component} modification
*/
render() {
let translate = this.context.language.translate;
let name = this.props.name;
return (
<div className={'cb'} key={name}>
<div className={'cb'}>{translate(name)}{name === 'jitter' ? ' (°)' : ' (%)'}</div>
<NumberEditor className={'cb'} style={{ width: '100%', textAlign: 'center' }} step={0.01} stepModifier={1} decimals={2} value={this.state.value} onValueChange={this._updateValue.bind(this)} />
</div>
);
}
}

View File

@@ -0,0 +1,63 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
import TranslatedComponent from './TranslatedComponent';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
import cn from 'classnames';
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
import { Modifications } from 'coriolis-data/dist';
import Modification from './Modification';
/**
* Modifications menu
*/
export default class ModificationsMenu extends TranslatedComponent {
static propTypes = {
ship: React.PropTypes.object.isRequired,
m: React.PropTypes.object.isRequired,
onChange: React.PropTypes.func.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
this.state = this._initState(props, context);
}
/**
* Initiate the list of modifications
* @param {Object} props React Component properties
* @param {Object} context React Component context
* @return {Object} list: Array of React Components
*/
_initState(props, context) {
let { m, onChange, ship } = props;
let list = [];
for (let modName of Modifications.validity[m.grp]) {
list.push(<Modification key={ modName } ship={ ship } m={ m } name={ modName } onChange={ onChange }/>);
}
return { list };
}
/**
* Render the list
* @return {React.Component} List
*/
render() {
return (
<div
className={cn('select', this.props.className)}
onClick={(e) => e.stopPropagation() }
onContextMenu={stopCtxPropagation}
>
{this.state.list}
</div>
);
}
}

View File

@@ -271,8 +271,8 @@ export default class PowerBands extends TranslatedComponent {
<line x1={pctScale(0.5)} x2={pctScale(0.5)} y1='0' y2={state.innerHeight} className={pwrWarningClass} /> <line x1={pctScale(0.5)} x2={pctScale(0.5)} y1='0' y2={state.innerHeight} className={pwrWarningClass} />
<text dy='0.5em' x='-3' y={state.retY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'retracted')} onMouseLeave={this._hidetip}>{translate('ret')}</text> <text dy='0.5em' x='-3' y={state.retY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'retracted')} onMouseLeave={this._hidetip}>{translate('ret')}</text>
<text dy='0.5em' x='-3' y={state.depY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'deployed', { orientation: 's', cap: 1 })} onMouseLeave={this._hidetip}>{translate('dep')}</text> <text dy='0.5em' x='-3' y={state.depY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'deployed', { orientation: 's', cap: 1 })} onMouseLeave={this._hidetip}>{translate('dep')}</text>
<text dy='0.5em' x={innerWidth + 5} y={state.retY} className={getClass(retSelected, retSum, available)}>{f2(Math.max(0, retSum)) + ' (' + pct1(Math.max(0, retSum / available)) + ')'}</text> <text dy='0.5em' x={innerWidth + 5} y={state.retY} className={getClass(retSelected, retSum, available)}>{f2(Math.max(0, retSum))} ({pct1(Math.max(0, retSum / available))})</text>
<text dy='0.5em' x={innerWidth + 5} y={state.depY} className={getClass(depSelected, depSum, available)}>{f2(Math.max(0, depSum)) + ' (' + pct1(Math.max(0, depSum / available)) + ')'}</text> <text dy='0.5em' x={innerWidth + 5} y={state.depY} className={getClass(depSelected, depSum, available)}>{f2(Math.max(0, depSum))} ({pct1(Math.max(0, depSum / available))})</text>
</g> </g>
</svg> </svg>
); );

View File

@@ -71,7 +71,7 @@ export default class PowerManagement extends TranslatedComponent {
case 'n': comp = comp(null, desc); break; case 'n': comp = comp(null, desc); break;
case 't': comp = comp((a, b) => a.type.localeCompare(b.type), desc); break; case 't': comp = comp((a, b) => a.type.localeCompare(b.type), desc); break;
case 'pri': comp = comp((a, b) => a.priority - b.priority, desc); break; case 'pri': comp = comp((a, b) => a.priority - b.priority, desc); break;
case 'pwr': comp = comp((a, b) => a.m.power - b.m.power, desc); break; case 'pwr': comp = comp((a, b) => a.m.getPowerUsage() - b.m.getPowerUsage(), desc); break;
case 'r': comp = comp((a, b) => ship.getSlotStatus(a) - ship.getSlotStatus(b), desc); break; case 'r': comp = comp((a, b) => ship.getSlotStatus(a) - ship.getSlotStatus(b), desc); break;
case 'd': comp = comp((a, b) => ship.getSlotStatus(a, true) - ship.getSlotStatus(b, true), desc); break; case 'd': comp = comp((a, b) => ship.getSlotStatus(a, true) - ship.getSlotStatus(b, true), desc); break;
} }
@@ -113,7 +113,7 @@ export default class PowerManagement extends TranslatedComponent {
for (let i = 0, l = ship.powerList.length; i < l; i++) { for (let i = 0, l = ship.powerList.length; i < l; i++) {
let slot = ship.powerList[i]; let slot = ship.powerList[i];
if (slot.m && slot.m.power) { if (slot.m && slot.m.getPowerUsage() > 0) {
let m = slot.m; let m = slot.m;
let toggleEnabled = this._toggleEnabled.bind(this, slot); let toggleEnabled = this._toggleEnabled.bind(this, slot);
let retractedElem = null, deployedElem = null; let retractedElem = null, deployedElem = null;
@@ -134,8 +134,8 @@ export default class PowerManagement extends TranslatedComponent {
{' ' + (slot.priority + 1) + ' '} {' ' + (slot.priority + 1) + ' '}
<span className='ptr btn' onClick={this._priority.bind(this, slot, 1)}>&#9658;</span> <span className='ptr btn' onClick={this._priority.bind(this, slot, 1)}>&#9658;</span>
</td> </td>
<td className='ri ptr' style={{ width: '3.25em' }} onClick={toggleEnabled}>{pwr(m.power)}</td> <td className='ri ptr' style={{ width: '3.25em' }} onClick={toggleEnabled}>{pwr(m.getPowerUsage())}</td>
<td className='ri ptr' style={{ width: '3em' }} onClick={toggleEnabled}><u>{pct(m.power / ship.powerAvailable)}</u></td> <td className='ri ptr' style={{ width: '3em' }} onClick={toggleEnabled}><u>{pct(m.getPowerUsage() / ship.powerAvailable)}</u></td>
{retractedElem} {retractedElem}
{deployedElem} {deployedElem}
</tr>); </tr>);
@@ -214,7 +214,7 @@ export default class PowerManagement extends TranslatedComponent {
<td className='le shorten cap' >{translate('pp')}</td> <td className='le shorten cap' >{translate('pp')}</td>
<td><u >{translate('SYS')}</u></td> <td><u >{translate('SYS')}</u></td>
<td>1</td> <td>1</td>
<td className='ri'>{pwr(pp.pGen)}</td> <td className='ri'>{pwr(pp.getPowerGeneration())}</td>
<td className='ri'><u>100%</u></td> <td className='ri'><u>100%</u></td>
<td></td> <td></td>
<td></td> <td></td>
@@ -223,7 +223,7 @@ export default class PowerManagement extends TranslatedComponent {
{this._renderPowerRows(ship, translate, pwr, formats.pct1)} {this._renderPowerRows(ship, translate, pwr, formats.pct1)}
</tbody> </tbody>
</table> </table>
<PowerBands width={this.state.width} code={code} available={ship.standard[0].m.pGen} bands={ship.priorityBands} /> <PowerBands width={this.state.width} code={code} available={pp.getPowerGeneration()} bands={ship.priorityBands} />
</div> </div>
); );
} }

View File

@@ -23,35 +23,40 @@ export default class ShipSummaryTable extends TranslatedComponent {
let translate = language.translate; let translate = language.translate;
let u = language.units; let u = language.units;
let formats = language.formats; let formats = language.formats;
let round = formats.round; let { time, int, round, f1, f2, pct } = formats;
let { time, int } = formats; let sgClassNames = cn({ warning: ship.findInternalByGroup('sg') && !ship.shield, muted: !ship.findInternalByGroup('sg') });
let armourDetails = null;
let sgClassNames = cn({ warning: ship.sgSlot && !ship.shieldStrength, muted: !ship.sgSlot });
let sgRecover = '-'; let sgRecover = '-';
let sgRecharge = '-'; let sgRecharge = '-';
let hide = tooltip.bind(null, null); let hide = tooltip.bind(null, null);
if (ship.armourMultiplier > 1 || ship.armourAdded) { if (ship.shield) {
armourDetails = <u>({
(ship.armourMultiplier > 1 ? formats.rPct(ship.armourMultiplier) : '') +
(ship.armourAdded ? ' + ' + ship.armourAdded : '')
})</u>;
}
if (ship.shieldStrength) {
sgRecover = time(ship.calcShieldRecovery()); sgRecover = time(ship.calcShieldRecovery());
sgRecharge = time(ship.calcShieldRecharge()); sgRecharge = time(ship.calcShieldRecharge());
} }
// <th colSpan={3}>{translate('shield resistance')}</th>
// <th colSpan={3}>{translate('hull resistance')}</th>
// <th className='lft'>{translate('explosive')}</th>
// <th className='lft'>{translate('kinetic')}</th>
// <th className='lft'>{translate('thermal')}</th>
// <th className='lft'>{translate('explosive')}</th>
// <th className='lft'>{translate('kinetic')}</th>
// <th className='lft'>{translate('thermal')}</th>
// <td>{pct(ship.shieldExplRes)}</td>
// <td>{pct(ship.shieldKinRes)}</td>
// <td>{pct(ship.shieldThermRes)}</td>
// <td>{pct(ship.hullExplRes)}</td>
// <td>{pct(ship.hullKinRes)}</td>
// <td>{pct(ship.hullThermRes)}</td>
return <div id='summary'> return <div id='summary'>
<table id='summaryTable'> <table id='summaryTable'>
<thead> <thead>
<tr className='main'> <tr className='main'>
<th rowSpan={2}>{translate('size')}</th>
<th onMouseEnter={termtip.bind(null, 'maneuverability')} onMouseLeave={hide} rowSpan={2}>{translate('MNV')}</th>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !ship.canThrust() }) }>{translate('speed')}</th> <th rowSpan={2} className={ cn({ 'bg-warning-disabled': !ship.canThrust() }) }>{translate('speed')}</th>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !ship.canBoost() }) }>{translate('boost')}</th> <th rowSpan={2} className={ cn({ 'bg-warning-disabled': !ship.canBoost() }) }>{translate('boost')}</th>
<th onMouseEnter={termtip.bind(null, 'damage per second')} onMouseLeave={hide} rowSpan={2}>{translate('DPS')}</th> <th onMouseEnter={termtip.bind(null, 'damage per second')} onMouseLeave={hide} rowSpan={2}>{translate('DPS')}</th>
<th onMouseEnter={termtip.bind(null, 'energy per second')} onMouseLeave={hide} rowSpan={2}>{translate('EPS')}</th>
<th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th>
<th rowSpan={2}>{translate('armour')}</th> <th rowSpan={2}>{translate('armour')}</th>
<th colSpan={3}>{translate('shields')}</th> <th colSpan={3}>{translate('shields')}</th>
<th colSpan={3}>{translate('mass')}</th> <th colSpan={3}>{translate('mass')}</th>
@@ -78,26 +83,26 @@ export default class ShipSummaryTable extends TranslatedComponent {
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td className='cap'>{translate(SizeMap[ship.class])}</td>
<td>{ship.agility}/10</td>
<td>{ ship.canThrust() ? <span>{int(ship.topSpeed)} {u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td> <td>{ ship.canThrust() ? <span>{int(ship.topSpeed)} {u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td>{ ship.canBoost() ? <span>{int(ship.topBoost)} {u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td> <td>{ ship.canBoost() ? <span>{int(ship.topBoost)} {u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td>{round(ship.totalDps)}</td> <td>{f1(ship.totalDps)}</td>
<td>{int(ship.armour)} {armourDetails}</td> <td>{f1(ship.totalEps)}</td>
<td className={sgClassNames}>{int(ship.shieldStrength)} {u.MJ} { ship.shieldMultiplier > 1 && ship.shieldStrength > 0 ? <u>({formats.rPct(ship.shieldMultiplier)})</u> : null }</td> <td>{f1(ship.totalHps)}</td>
<td>{int(ship.armour)}</td>
<td className={sgClassNames}>{int(ship.shield)} {u.MJ}</td>
<td className={sgClassNames}>{sgRecover}</td> <td className={sgClassNames}>{sgRecover}</td>
<td className={sgClassNames}>{sgRecharge}</td> <td className={sgClassNames}>{sgRecharge}</td>
<td>{ship.hullMass} {u.T}</td> <td>{ship.hullMass} {u.T}</td>
<td>{round(ship.unladenMass)} {u.T}</td> <td>{int(ship.unladenMass)} {u.T}</td>
<td>{round(ship.ladenMass)} {u.T}</td> <td>{int(ship.ladenMass)} {u.T}</td>
<td>{round(ship.cargoCapacity)} {u.T}</td> <td>{round(ship.cargoCapacity)} {u.T}</td>
<td>{round(ship.fuelCapacity)} {u.T}</td> <td>{round(ship.fuelCapacity)} {u.T}</td>
<td>{round(ship.unladenRange)} {u.LY}</td> <td>{f2(ship.unladenRange)} {u.LY}</td>
<td>{round(ship.fullTankRange)} {u.LY}</td> <td>{f2(ship.fullTankRange)} {u.LY}</td>
<td>{round(ship.ladenRange)} {u.LY}</td> <td>{f2(ship.ladenRange)} {u.LY}</td>
<td>{round(ship.maxJumpCount)}</td> <td>{int(ship.maxJumpCount)}</td>
<td>{round(ship.unladenFastestRange)} {u.LY}</td> <td>{f2(ship.unladenFastestRange)} {u.LY}</td>
<td>{round(ship.ladenFastestRange)} {u.LY}</td> <td>{f2(ship.ladenFastestRange)} {u.LY}</td>
<td>{ship.masslock}</td> <td>{ship.masslock}</td>
</tr> </tr>
</tbody> </tbody>

View File

@@ -2,8 +2,12 @@ import React from 'react';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import cn from 'classnames'; import cn from 'classnames';
import AvailableModulesMenu from './AvailableModulesMenu'; import AvailableModulesMenu from './AvailableModulesMenu';
import ModificationsMenu from './ModificationsMenu';
import { Modifications } from 'coriolis-data/dist';
import { ListModifications } from './SvgIcons';
import { diffDetails } from '../utils/SlotFunctions'; import { diffDetails } from '../utils/SlotFunctions';
import { wrapCtxMenu } from '../utils/UtilityFunctions'; import { wrapCtxMenu } from '../utils/UtilityFunctions';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
/** /**
* Abstract Slot * Abstract Slot
@@ -18,6 +22,7 @@ export default class Slot extends TranslatedComponent {
selected: React.PropTypes.bool, selected: React.PropTypes.bool,
m: React.PropTypes.object, m: React.PropTypes.object,
ship: React.PropTypes.object.isRequired, ship: React.PropTypes.object.isRequired,
eligible: React.PropTypes.object,
warning: React.PropTypes.func, warning: React.PropTypes.func,
drag: React.PropTypes.func, drag: React.PropTypes.func,
drop: React.PropTypes.func, drop: React.PropTypes.func,
@@ -31,6 +36,8 @@ export default class Slot extends TranslatedComponent {
constructor(props) { constructor(props) {
super(props); super(props);
this._modificationsSelected = false;
this._contextMenu = wrapCtxMenu(this._contextMenu.bind(this)); this._contextMenu = wrapCtxMenu(this._contextMenu.bind(this));
this._getMaxClassLabel = this._getMaxClassLabel.bind(this); this._getMaxClassLabel = this._getMaxClassLabel.bind(this);
} }
@@ -73,25 +80,39 @@ export default class Slot extends TranslatedComponent {
render() { render() {
let language = this.context.language; let language = this.context.language;
let translate = language.translate; let translate = language.translate;
let { ship, m, dropClass, dragOver, onOpen, selected, onSelect, warning, shipMass, availableModules } = this.props; let { ship, m, dropClass, dragOver, onOpen, onChange, selected, eligible, onSelect, warning, availableModules } = this.props;
let slotDetails, menu; let slotDetails, menu;
if (!selected) {
// If not selected then sure that modifications flag is unset
this._modificationsSelected = false;
}
if (m) { if (m) {
slotDetails = this._getSlotDetails(m, translate, language.formats, language.units); // Must be implemented by sub classes slotDetails = this._getSlotDetails(m, translate, language.formats, language.units); // Must be implemented by sub classes
} else { } else {
slotDetails = <div className={'empty'}>{translate('empty')}</div>; slotDetails = <div className={'empty'}>{translate(eligible ? 'emptyrestricted' : 'empty')}</div>;
} }
if (this.props.selected) { if (selected) {
menu = <AvailableModulesMenu if (this._modificationsSelected) {
className={this._getClassNames()} menu = <ModificationsMenu
modules={availableModules()} className={this._getClassNames()}
shipMass={ship.hullMass} onChange={onChange}
m={m} ship={ship}
onSelect={onSelect} m={m}
warning={warning} />;
diffDetails={diffDetails.bind(ship, this.context.language)} } else {
/>; menu = <AvailableModulesMenu
className={this._getClassNames()}
modules={availableModules()}
shipMass={ship.hullMass}
m={m}
onSelect={onSelect}
warning={warning}
diffDetails={diffDetails.bind(ship, this.context.language)}
/>;
}
} }
// TODO: implement touch dragging // TODO: implement touch dragging
@@ -100,10 +121,17 @@ export default class Slot extends TranslatedComponent {
<div className={cn('slot', dropClass, { selected })} onClick={onOpen} onContextMenu={this._contextMenu} onDragOver={dragOver}> <div className={cn('slot', dropClass, { selected })} onClick={onOpen} onContextMenu={this._contextMenu} onDragOver={dragOver}>
<div className='details-container'> <div className='details-container'>
<div className='sz'>{this._getMaxClassLabel(translate)}</div> <div className='sz'>{this._getMaxClassLabel(translate)}</div>
{slotDetails} {slotDetails}
</div> </div>
{menu} {menu}
</div> </div>
); );
} }
/**
* Toggle the modifications flag when selecting the modifications icon
*/
_toggleModifications() {
this._modificationsSelected = !this._modificationsSelected;
}
} }

View File

@@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import { wrapCtxMenu } from '../utils/UtilityFunctions'; import { wrapCtxMenu } from '../utils/UtilityFunctions';
import { canMount } from '../utils/SlotFunctions';
import { Equalizer } from '../components/SvgIcons'; import { Equalizer } from '../components/SvgIcons';
import cn from 'classnames'; import cn from 'classnames';
@@ -90,7 +91,7 @@ export default class SlotSection extends TranslatedComponent {
e.stopPropagation(); e.stopPropagation();
let os = this.state.originSlot; let os = this.state.originSlot;
if (os) { if (os) {
e.dataTransfer.dropEffect = os != targetSlot && targetSlot.maxClass >= os.m.class ? 'copyMove' : 'none'; e.dataTransfer.dropEffect = os != targetSlot && canMount(this.props.ship, targetSlot, os.m.grp, os.m.class) ? 'copyMove' : 'none';
this.setState({ targetSlot }); this.setState({ targetSlot });
} else { } else {
e.dataTransfer.dropEffect = 'none'; e.dataTransfer.dropEffect = 'none';
@@ -116,9 +117,9 @@ export default class SlotSection extends TranslatedComponent {
let { originSlot, targetSlot } = this.state; let { originSlot, targetSlot } = this.state;
let m = originSlot.m; let m = originSlot.m;
if (targetSlot && m && targetSlot.maxClass >= m.class) { if (targetSlot && m && canMount(this.props.ship, targetSlot, m.grp, m.class)) {
// Swap modules if possible // Swap modules if possible
if (targetSlot.m && originSlot.maxClass >= targetSlot.m.class) { if (targetSlot.m && canMount(this.props.ship, originSlot, targetSlot.m.grp, targetSlot.m.class)) {
this.props.ship.use(originSlot, targetSlot.m, true); this.props.ship.use(originSlot, targetSlot.m, true);
} else { // Otherwise empty the origin slot } else { // Otherwise empty the origin slot
this.props.ship.use(originSlot, null, true); // Empty but prevent summary update this.props.ship.use(originSlot, null, true); // Empty but prevent summary update
@@ -141,12 +142,12 @@ export default class SlotSection extends TranslatedComponent {
return null; return null;
} }
if (slot === originSlot) { if (slot === originSlot) {
if (targetSlot && targetSlot.m && originSlot.maxClass < targetSlot.m.class) { if (targetSlot && targetSlot.m && !canMount(this.props.ship, originSlot, targetSlot.m.grp, targetSlot.m.class)) {
return 'dropEmpty'; // Origin slot will be emptied return 'dropEmpty'; // Origin slot will be emptied
} }
return null; return null;
} }
if (originSlot.m && slot.maxClass >= originSlot.m.class) { // Eligble drop slot if (originSlot.m && canMount(this.props.ship, slot, originSlot.m.grp, originSlot.m.class)) { // Eligble drop slot
if (slot === targetSlot) { if (slot === targetSlot) {
return 'drop'; // Can drop return 'drop'; // Can drop
} }
@@ -179,7 +180,7 @@ export default class SlotSection extends TranslatedComponent {
<div id={this.sectionId} className={'group'} onDragLeave={this._dragOverNone}> <div id={this.sectionId} className={'group'} onDragLeave={this._dragOverNone}>
<div className={cn('section-menu', { selected: sectionMenuOpened })} onClick={open} onContextMenu={ctx}> <div className={cn('section-menu', { selected: sectionMenuOpened })} onClick={open} onContextMenu={ctx}>
<h1>{translate(this.sectionName)} <Equalizer/></h1> <h1>{translate(this.sectionName)} <Equalizer/></h1>
{sectionMenuOpened ? this._getSectionMenu(translate) : null } {sectionMenuOpened ? this._getSectionMenu(translate, this.props.ship) : null }
</div> </div>
{this._getSlots()} {this._getSlots()}
</div> </div>

View File

@@ -4,6 +4,10 @@ import TranslatedComponent from './TranslatedComponent';
import { jumpRange } from '../shipyard/Calculations'; import { jumpRange } from '../shipyard/Calculations';
import { diffDetails } from '../utils/SlotFunctions'; import { diffDetails } from '../utils/SlotFunctions';
import AvailableModulesMenu from './AvailableModulesMenu'; import AvailableModulesMenu from './AvailableModulesMenu';
import ModificationsMenu from './ModificationsMenu';
import { ListModifications } from './SvgIcons';
import { Modifications } from 'coriolis-data/dist';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
/** /**
* Standard Slot * Standard Slot
@@ -15,51 +19,83 @@ export default class StandardSlot extends TranslatedComponent {
modules: React.PropTypes.array.isRequired, modules: React.PropTypes.array.isRequired,
onSelect: React.PropTypes.func.isRequired, onSelect: React.PropTypes.func.isRequired,
onOpen: React.PropTypes.func.isRequired, onOpen: React.PropTypes.func.isRequired,
onChange: React.PropTypes.func.isRequired,
ship: React.PropTypes.object.isRequired, ship: React.PropTypes.object.isRequired,
selected: React.PropTypes.bool, selected: React.PropTypes.bool,
warning: React.PropTypes.func, warning: React.PropTypes.func,
}; };
/**
* Construct the slot
* @param {object} props Object properties
*/
constructor(props) {
super(props);
this._modificationsSelected = false;
}
/** /**
* Render the slot * Render the slot
* @return {React.Component} Slot component * @return {React.Component} Slot component
*/ */
render() { render() {
let { termtip, tooltip } = this.context;
let { translate, formats, units } = this.context.language; let { translate, formats, units } = this.context.language;
let { modules, slot, warning, onSelect, ladenMass, ship } = this.props; let { modules, slot, selected, warning, onSelect, onChange, ship } = this.props;
let m = slot.m; let m = slot.m;
let classRating = m.class + m.rating; let classRating = m.class + m.rating;
let menu; let menu;
let validMods = m == null ? [] : (Modifications.validity[m.grp] || []);
let mass = m.getMass() || m.cargo || m.fuel || 0;
if (this.props.selected) { if (!selected) {
menu = <AvailableModulesMenu // If not selected then sure that modifications flag is unset
modules={modules} this._modificationsSelected = false;
shipMass={ship.ladenMass} }
m={m}
onSelect={onSelect} if (selected) {
warning={warning} if (this._modificationsSelected) {
diffDetails={diffDetails.bind(ship, this.context.language)} menu = <ModificationsMenu
/>; className='standard'
onChange={onChange}
ship={ship}
m={m}
/>;
} else {
menu = <AvailableModulesMenu
className='standard'
modules={modules}
shipMass={ship.ladenMass}
m={m}
onSelect={onSelect}
warning={warning}
diffDetails={diffDetails.bind(ship, this.context.language)}
/>;
}
} }
return ( return (
<div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen}> <div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen} onContextMenu={stopCtxPropagation}>
<div className={cn('details-container', { warning: warning && warning(slot.m) })}> <div className={cn('details-container', { warning: warning && warning(slot.m) })}>
<div className={'sz'}>{slot.maxClass}</div> <div className={'sz'}>{slot.maxClass}</div>
<div> <div>
<div className='l'>{classRating + ' ' + translate(m.grp)}</div> <div className='l'>{classRating} {translate(m.grp == 'bh' ? m.grp : m.name || m.grp)}</div>
<div className={'r'}>{m.mass || m.fuel}{units.T}</div> <div className={'r'}>{formats.round(mass)}{units.T}</div>
<div/>
<div className={'cb'}> <div className={'cb'}>
{ m.optmass ? <div className='l'>{translate('optimal mass') + ': '}{m.optmass}{units.T}</div> : null } { m.grp == 'bh' && m.name ? <div className='l'>{translate(m.name)}</div> : null }
{ m.maxmass ? <div className='l'>{translate('max mass') + ': '}{m.maxmass}{units.T}</div> : null } { m.getOptimalMass() ? <div className='l'>{translate('optimal mass')}: {formats.int(m.getOptimalMass())}{units.T}</div> : null }
{ m.range ? <div className='l'>{translate('range')}: {m.range}{units.km}</div> : null } { m.getMaxMass() ? <div className='l'>{translate('max mass')}: {formats.int(m.getMaxMass())}{units.T}</div> : null }
{ m.getRange() ? <div className='l'>{translate('range')}: {formats.f2(m.getRange())}{units.km}</div> : null }
{ m.time ? <div className='l'>{translate('time')}: {formats.time(m.time)}</div> : null } { m.time ? <div className='l'>{translate('time')}: {formats.time(m.time)}</div> : null }
{ m.eff ? <div className='l'>{translate('efficiency')}: {m.eff}</div> : null } { m.getThermalEfficiency() ? <div className='l'>{translate('efficiency')}: {formats.f2(m.getThermalEfficiency())}</div> : null }
{ m.pGen ? <div className='l'>{translate('power')}: {m.pGen}{units.MW}</div> : null } { m.getPowerGeneration() > 0 ? <div className='l'>{translate('pgen')}: {formats.f1(m.getPowerGeneration())}{units.MW}</div> : null }
{ m.maxfuel ? <div className='l'>{translate('max') + ' ' + translate('fuel') + ': '}{m.maxfuel}{units.T}</div> : null } { m.getMaxFuelPerJump() ? <div className='l'>{translate('max')} {translate('fuel')}: {formats.f1(m.getMaxFuelPerJump())}{units.T}</div> : null }
{ m.weaponcapacity ? <div className='l'>{translate('WEP')}: {m.weaponcapacity}{units.MJ} / {m.weaponrecharge}{units.MW}</div> : null } { m.getWeaponsCapacity() ? <div className='l'>{translate('WEP')}: {formats.f1(m.getWeaponsCapacity())}{units.MJ} / {formats.f1(m.getWeaponsRechargeRate())}{units.MW}</div> : null }
{ m.systemcapacity ? <div className='l'>{translate('SYS')}: {m.systemcapacity}{units.MJ} / {m.systemrecharge}{units.MW}</div> : null } { m.getSystemsCapacity() ? <div className='l'>{translate('SYS')}: {formats.f1(m.getSystemsCapacity())}{units.MJ} / {formats.f1(m.getSystemsRechargeRate())}{units.MW}</div> : null }
{ m.enginecapacity ? <div className='l'>{translate('ENG')}: {m.enginecapacity}{units.MJ} / {m.enginerecharge}{units.MW}</div> : null } { m.getEnginesCapacity() ? <div className='l'>{translate('ENG')}: {formats.f1(m.getEnginesCapacity())}{units.MJ} / {formats.f1(m.getEnginesRechargeRate())}{units.MW}</div> : null }
{ validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
</div> </div>
</div> </div>
</div> </div>
@@ -67,4 +103,11 @@ export default class StandardSlot extends TranslatedComponent {
</div> </div>
); );
} }
/**
* Toggle the modifications flag when selecting the modifications icon
*/
_toggleModifications() {
this._modificationsSelected = !this._modificationsSelected;
}
} }

View File

@@ -4,6 +4,7 @@ import SlotSection from './SlotSection';
import StandardSlot from './StandardSlot'; import StandardSlot from './StandardSlot';
import { diffDetails } from '../utils/SlotFunctions'; import { diffDetails } from '../utils/SlotFunctions';
import * as ModuleUtils from '../shipyard/ModuleUtils'; import * as ModuleUtils from '../shipyard/ModuleUtils';
import * as ShipRoles from '../shipyard/ShipRoles';
import { stopCtxPropagation } from '../utils/UtilityFunctions'; import { stopCtxPropagation } from '../utils/UtilityFunctions';
/** /**
@@ -17,22 +18,9 @@ export default class StandardSlotSection extends SlotSection {
* @param {Object} context React Component context * @param {Object} context React Component context
*/ */
constructor(props, context) { constructor(props, context) {
super(props, context, 'standard', 'standard'); super(props, context, 'standard', 'core internal');
this._optimizeStandard = this._optimizeStandard.bind(this); this._optimizeStandard = this._optimizeStandard.bind(this);
this._optimizeCargo = this._optimizeCargo.bind(this); this._selectBulkhead = this._selectBulkhead.bind(this);
this._optimizeExplorer = this._optimizeExplorer.bind(this);
this._hideDiff = this._hideDiff.bind(this);
}
/**
* Fill all standard slots with the specificed rating (using max class)
* @param {String} rating [A-E]
*/
_fill(rating) {
this.props.ship.useStandard(rating);
this.props.onChange();
this._close();
} }
/** /**
@@ -45,85 +33,42 @@ export default class StandardSlotSection extends SlotSection {
} }
/** /**
* Trader build * Fill all standard slots with the specificed rating (using max class)
* @param {Boolean} shielded True if shield generator should be included
* @param {integer} bulkheadIndex Bulkhead to use see Constants.BulkheadNames
*/ */
_optimizeCargo() { _multiPurpose(shielded, bulkheadIndex) {
let ship = this.props.ship; ShipRoles.multiPurpose(this.props.ship, shielded, bulkheadIndex);
ship.internal.forEach((slot) => ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E')));
ship.useLightestStandard();
this.props.onChange(); this.props.onChange();
this._close(); this._close();
} }
/** /**
* Explorer build * Trader Build
* @param {Boolean} shielded True if shield generator should be included
*/ */
_optimizeExplorer() { _optimizeCargo(shielded) {
let ship = this.props.ship, ShipRoles.trader(this.props.ship, shielded);
intLength = ship.internal.length, this.props.onChange();
heatSinkCount = 2, // Fit 2 heat sinks if possible this._close();
afmUnitCount = 2, // Fit 2 AFM Units if possible }
sgSlot,
fuelScoopSlot,
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
ship.setSlotEnabled(ship.cargoHatch, false) /**
.use(ship.internal[--intLength], ModuleUtils.internal('2f')) // Advanced Discovery Scanner * Explorer role
.use(ship.internal[--intLength], ModuleUtils.internal('2i')); // Detailed Surface Scanner * @param {Boolean} planetary True if Planetary Vehicle Hangar (PVH) should be included
*/
for (let i = 0; i < intLength; i++) { _optimizeExplorer(planetary) {
let slot = ship.internal[i]; ShipRoles.explorer(this.props.ship, planetary);
let nextSlot = (i + 1) < intLength ? ship.internal[i + 1] : null;
if (!fuelScoopSlot && (!slot.eligible || slot.eligible.fs)) { // Fit best possible Fuel Scoop
fuelScoopSlot = slot;
ship.use(fuelScoopSlot, ModuleUtils.findInternal('fs', slot.maxClass, 'A'));
ship.setSlotEnabled(fuelScoopSlot, true);
// Mount a Shield generator if possible AND an AFM Unit has been mounted already (Guarantees at least 1 AFM Unit)
} else if (!sgSlot && afmUnitCount < 2 && sg.class <= slot.maxClass && (!slot.eligible || slot.eligible.sg) && (!nextSlot || nextSlot.maxClass < sg.class)) {
sgSlot = slot;
ship.use(sgSlot, sg);
ship.setSlotEnabled(sgSlot, true);
} else if (afmUnitCount > 0 && (!slot.eligible || slot.eligible.am)) {
afmUnitCount--;
let am = ModuleUtils.findInternal('am', slot.maxClass, afmUnitCount ? 'B' : 'A');
ship.use(slot, am);
ship.setSlotEnabled(slot, false); // Disabled power for AFM Unit
} else {
ship.use(slot, null);
}
}
ship.hardpoints.forEach((s) => {
if (s.maxClass == 0 && heatSinkCount) { // Mount up to 2 heatsinks
ship.use(s, ModuleUtils.hardpoints('02'));
ship.setSlotEnabled(s, heatSinkCount == 2); // Only enable a single Heatsink
heatSinkCount--;
} else {
ship.use(s, null);
}
});
if (sgSlot) {
// The SG and Fuel scoop to not need to be powered at the same time
if (sgSlot.m.power > fuelScoopSlot.m.power) { // The Shield generator uses the most power
ship.setSlotEnabled(fuelScoopSlot, false);
} else { // The Fuel scoop uses the most power
ship.setSlotEnabled(sgSlot, false);
}
}
ship.useLightestStandard({ pd: '1D', ppRating: 'A' });
this.props.onChange(); this.props.onChange();
this._close(); this._close();
} }
/** /**
* Use the specified bulkhead * Use the specified bulkhead
* @param {number} bulkheadIndex 0 - 4 * @param {Object} bulkhead Bulkhead module details
*/ */
_selectBulkhead(bulkheadIndex) { _selectBulkhead(bulkhead) {
this.props.ship.useBulkhead(bulkheadIndex); this.props.ship.useBulkhead(bulkhead.index);
this.context.tooltip(); this.context.tooltip();
this.props.onChange(); this.props.onChange();
this._close(); this._close();
@@ -136,76 +81,29 @@ export default class StandardSlotSection extends SlotSection {
this._optimizeStandard(); this._optimizeStandard();
} }
/**
* Show the bulkhead diff tooltip
* @param {number} bhIndex Potential Bulkhead alternative
* @param {SyntheticEvent} event Event
*/
_bhDiff(bhIndex, event) {
let ship = this.props.ship;
this.context.tooltip(
diffDetails.call(ship, this.context.language, ModuleUtils.bulkheads(ship.id, bhIndex), ship.bulkheads.m),
event.currentTarget.getBoundingClientRect()
);
}
/**
* Hide the diff tooltip
*/
_hideDiff() {
this.context.tooltip();
}
/** /**
* Generate the slot React Components * Generate the slot React Components
* @return {Array} Array of Slots * @return {Array} Array of Slots
*/ */
_getSlots() { _getSlots() {
let { translate, units } = this.context.language;
let { ship, currentMenu } = this.props; let { ship, currentMenu } = this.props;
let slots = new Array(8); let slots = new Array(8);
let open = this._openMenu; let open = this._openMenu;
let select = this._selectModule; let select = this._selectModule;
let selBulkhead = this._selectBulkhead;
let st = ship.standard; let st = ship.standard;
let avail = ship.getAvailableModules().standard; let avail = ship.getAvailableModules().standard;
let bh = ship.bulkheads; let bh = ship.bulkheads;
slots[0] = ( slots[0] = <StandardSlot
<div key='bh' className={cn('slot', { selected: currentMenu === bh })} onClick={open.bind(this, bh)}> key='bh'
<div className={'details-container'}> slot={bh}
<div className={'details'}> modules={ship.getAvailableModules().bulkheads}
<div className={'sz'}>8</div> onOpen={open.bind(this, bh)}
<div> onSelect={this._selectBulkhead}
<div className={'l'}>{translate('bh')}</div> selected={currentMenu == bh}
<div className={'r'}>{bh.m.mass}{units.T}</div> onChange={this.props.onChange}
<div className={'cl l'}>{translate(bh.m.name)}</div> ship={ship}
</div> />;
</div>
</div>
{currentMenu === bh &&
<div className='select' onClick={ e => e.stopPropagation() }>
<ul>
<li onClick={selBulkhead.bind(this, 0)} onMouseOver={this._bhDiff.bind(this, 0)} onMouseLeave={this._hideDiff} className={cn('lc', { active: bh.index == 0 })}>
{translate('Lightweight Alloy')}
</li>
<li onClick={selBulkhead.bind(this, 1)} onMouseOver={this._bhDiff.bind(this, 1)} onMouseLeave={this._hideDiff} className={cn('lc', { active: bh.index == 1 })}>
{translate('Reinforced Alloy')}
</li>
<li onClick={selBulkhead.bind(this, 2)} onMouseOver={this._bhDiff.bind(this, 2)} onMouseLeave={this._hideDiff} className={cn('lc', { active: bh.index == 2 })}>
{translate('Military Grade Composite')}
</li>
<li onClick={selBulkhead.bind(this, 3)} onMouseOver={this._bhDiff.bind(this, 3)} onMouseLeave={this._hideDiff} className={cn('lc', { active: bh.index == 3 })}>
{translate('Mirrored Surface Composite')}
</li>
<li onClick={selBulkhead.bind(this, 4)} onMouseOver={this._bhDiff.bind(this, 4)} onMouseLeave={this._hideDiff} className={cn('lc', { active: bh.index == 4 })}>
{translate('Reactive Surface Composite')}
</li>
</ul>
</div>
}
</div>
);
slots[1] = <StandardSlot slots[1] = <StandardSlot
key='pp' key='pp'
@@ -214,8 +112,9 @@ export default class StandardSlotSection extends SlotSection {
onOpen={open.bind(this, st[0])} onOpen={open.bind(this, st[0])}
onSelect={select.bind(this, st[0])} onSelect={select.bind(this, st[0])}
selected={currentMenu == st[0]} selected={currentMenu == st[0]}
onChange={this.props.onChange}
ship={ship} ship={ship}
warning={m => m.pGen < ship.powerRetracted} warning={m => m.pgen < ship.powerRetracted}
/>; />;
slots[2] = <StandardSlot slots[2] = <StandardSlot
@@ -225,8 +124,9 @@ export default class StandardSlotSection extends SlotSection {
onOpen={open.bind(this, st[1])} onOpen={open.bind(this, st[1])}
onSelect={select.bind(this, st[1])} onSelect={select.bind(this, st[1])}
selected={currentMenu == st[1]} selected={currentMenu == st[1]}
onChange={this.props.onChange}
ship={ship} ship={ship}
warning={m => m.maxmass < ship.ladenMass} warning={m => m.maxmass < (ship.ladenMass - st[1].mass + m.mass)}
/>; />;
@@ -236,6 +136,7 @@ export default class StandardSlotSection extends SlotSection {
modules={avail[2]} modules={avail[2]}
onOpen={open.bind(this, st[2])} onOpen={open.bind(this, st[2])}
onSelect={select.bind(this, st[2])} onSelect={select.bind(this, st[2])}
onChange={this.props.onChange}
ship={ship} ship={ship}
selected={currentMenu == st[2]} selected={currentMenu == st[2]}
/>; />;
@@ -246,6 +147,7 @@ export default class StandardSlotSection extends SlotSection {
modules={avail[3]} modules={avail[3]}
onOpen={open.bind(this, st[3])} onOpen={open.bind(this, st[3])}
onSelect={select.bind(this, st[3])} onSelect={select.bind(this, st[3])}
onChange={this.props.onChange}
ship={ship} ship={ship}
selected={currentMenu == st[3]} selected={currentMenu == st[3]}
/>; />;
@@ -257,8 +159,9 @@ export default class StandardSlotSection extends SlotSection {
onOpen={open.bind(this, st[4])} onOpen={open.bind(this, st[4])}
onSelect={select.bind(this, st[4])} onSelect={select.bind(this, st[4])}
selected={currentMenu == st[4]} selected={currentMenu == st[4]}
onChange={this.props.onChange}
ship={ship} ship={ship}
warning= {m => m.enginecapacity < ship.boostEnergy} warning= {m => m.engcap < ship.boostEnergy}
/>; />;
slots[6] = <StandardSlot slots[6] = <StandardSlot
@@ -268,8 +171,8 @@ export default class StandardSlotSection extends SlotSection {
onOpen={open.bind(this, st[5])} onOpen={open.bind(this, st[5])}
onSelect={select.bind(this, st[5])} onSelect={select.bind(this, st[5])}
selected={currentMenu == st[5]} selected={currentMenu == st[5]}
onChange={this.props.onChange}
ship={ship} ship={ship}
warning= {m => m.enginecapacity < ship.boostEnergy}
/>; />;
slots[7] = <StandardSlot slots[7] = <StandardSlot
@@ -279,6 +182,7 @@ export default class StandardSlotSection extends SlotSection {
onOpen={open.bind(this, st[6])} onOpen={open.bind(this, st[6])}
onSelect={select.bind(this, st[6])} onSelect={select.bind(this, st[6])}
selected={currentMenu == st[6]} selected={currentMenu == st[6]}
onChange={this.props.onChange}
ship={ship} 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
/>; />;
@@ -292,21 +196,19 @@ export default class StandardSlotSection extends SlotSection {
* @return {React.Component} Section menu * @return {React.Component} Section menu
*/ */
_getSectionMenu(translate) { _getSectionMenu(translate) {
let _fill = this._fill; let planetaryDisabled = this.props.ship.internal.length < 4;
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}> return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul> <ul>
<li className='lc' onClick={this._optimizeStandard}>{translate('optimize')}</li> <li className='lc' onClick={this._optimizeStandard}>{translate('Maximize Jump Range')}</li>
<li className='c' onClick={_fill.bind(this, 'E')}>E</li>
<li className='c' onClick={_fill.bind(this, 'D')}>D</li>
<li className='c' onClick={_fill.bind(this, 'C')}>C</li>
<li className='c' onClick={_fill.bind(this, 'B')}>B</li>
<li className='c' onClick={_fill.bind(this, 'A')}>A</li>
</ul> </ul>
<div className='select-group cap'>{translate('roles')}</div> <div className='select-group cap'>{translate('roles')}</div>
<ul> <ul>
<li className='lc' onClick={this._optimizeCargo}>{translate('Trader')}</li> <li className='lc' onClick={this._multiPurpose.bind(this, false, 0)}>{translate('Multi-purpose')}</li>
<li className='lc' onClick={this._optimizeExplorer}>{translate('Explorer')}</li> <li className='lc' onClick={this._multiPurpose.bind(this, true, 2)}>{translate('Combat')}</li>
<li className='lc' onClick={this._optimizeCargo.bind(this, false)}>{translate('Trader')}</li>
<li className='lc' onClick={this._optimizeCargo.bind(this, true)}>{translate('Shielded Trader')}</li>
<li className='lc' onClick={this._optimizeExplorer.bind(this, false)}>{translate('Explorer')}</li>
<li className={cn('lc', { disabled: planetaryDisabled })} onClick={!planetaryDisabled && this._optimizeExplorer.bind(this, true)}>{translate('Planetary Explorer')}</li>
</ul> </ul>
</div>; </div>;
} }

View File

@@ -64,8 +64,8 @@ export class CoriolisLogo extends SvgIcon {
*/ */
svg() { svg() {
return <g transform='translate(1,1)'> return <g transform='translate(1,1)'>
<path stroke='#ff3b00' transform='rotate(45 15 15)' d='m4,4 l 11,-4 l 11,4 l 4,11 l -4,11 l -11,4 l -11,-4 l -4,-11 l 4,-11 l 22,0 l 0,22 l -22,0 z' strokeWidth='1' fill='#000000'/> <path stroke='#0a8bd6' transform='rotate(45 15 15)' d='m4,4 l 11,-4 l 11,4 l 4,11 l -4,11 l -11,4 l -11,-4 l -4,-11 l 4,-11 l 22,0 l 0,22 l -22,0 z' strokeWidth='1' fill='#000000'/>
<rect height='3' width='10' y='13.5' x='10' strokeWidth='1' stroke='#ff3b00'/> <rect height='3' width='10' y='13.5' x='10' strokeWidth='1' stroke='#0a8bd6'/>
</g>; </g>;
} }
} }
@@ -319,6 +319,90 @@ export class Warning extends SvgIcon {
} }
} }
/**
* Thermal damage
*/
export class DamageThermal extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <g>
<ellipse cx='100' cy='100' rx='90' ry='90' fillOpacity='0' />
<ellipse cx='100' cy='100' rx='30' ry='30' fillOpacity='1' />
<path d='M100 20v80' />
</g>;
}
}
/**
* Kinetic damage
*/
export class DamageKinetic extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <g>
<ellipse cx='100' cy='100' rx='90' ry='90' fillOpacity='0' />
<ellipse cx='62' cy='67' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='62' cy='101' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='62' cy='135' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='50' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='84' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='118' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='152' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='138' cy='67' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='138' cy='101' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='138' cy='135' rx='5' ry='5' fillOpacity='1' />
</g>;
}
}
/**
* Explosive damage
*/
export class DamageExplosive extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <g>
<ellipse cx='100' cy='100' rx='50' ry='50' fillOpacity='0' />
<ellipse cx='100' cy='20' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='156.57' cy='36.57' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='180' cy='100' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='156.57' cy='163.43' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='180' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='43.43' cy='163.43' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='20' cy='100' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='43.43' cy='36.57' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='75' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='125' cy='100' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='125' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='75' cy='100' rx='5' ry='5' fillOpacity='1' />
</g>;
}
}
/** /**
* Fixed mount hardpoint * Fixed mount hardpoint
*/ */
@@ -334,11 +418,11 @@ export class MountFixed extends SvgIcon {
*/ */
svg() { svg() {
return <g> return <g>
<circle fillOpacity='0' r='70' cy='100' cx='100' strokeWidth='5' /> <circle cx='100' cy='100' r='76' fillOpacity='0' />
<line y2='60' x2='101' y1='0' x1='101' strokeWidth='5' /> <path d='M0 100h48' />
<line y2='101' x2='200' y1='101' x1='140' strokeWidth='5' /> <path d='M152 100h48' />
<line y2='101' x2='60' y1='101' x1='0' strokeWidth='5' /> <path d='M100 0v48' />
<line y2='200' x2='101' y1='140' x1='101' strokeWidth='5' /> <path d='M100 152v48' />
</g>; </g>;
} }
} }
@@ -358,8 +442,8 @@ export class MountGimballed extends SvgIcon {
*/ */
svg() { svg() {
return <g> return <g>
<ellipse ry='25' rx='95' cy='100' cx='100' fillOpacity='0' strokeWidth='5' /> <ellipse cx='100' cy='100' rx='90' ry='25' fillOpacity='0' />
<ellipse ry='95' rx='25' cy='100' cx='100' fillOpacity='0' strokeWidth='5' /> <ellipse cx='100' cy='100' rx='20' ry='95' fillOpacity='0' />
</g>; </g>;
} }
} }
@@ -379,9 +463,14 @@ export class MountTurret extends SvgIcon {
*/ */
svg() { svg() {
return <g> return <g>
<line y2='170' x2='162' y1='170' x1='8' strokeWidth='6' /> <path d='M40 50 A 40 40 0 0 0 0 90' />
<path d='m13,138l144,0l0,-50l-27,-40l-90,0l-27,40l0,50z' id='svg_12' fillOpacity='0' strokeWidth='6' /> <path d='M40 50h40' />
<line y2='91' x2='200' y1='91' x1='159' strokeWidth='6' /> <path d='M120 90 A 40 40 0 0 0 80 50' />
<path d='M0 90v40' />
<path d='M120 90v40' />
<path d='M0 120h120' />
<path d='M120 90h80' />
<path d='M0 160h120' />
</g>; </g>;
} }
} }
@@ -399,6 +488,25 @@ export class Rocket extends SvgIcon {
} }
} }
/**
* ListModifications (engineers)
*/
export class ListModifications extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <path d='M20 180l90-100-90 100zM176 40a40 40 0 1 1-27-28'/>;
}
}
/** /**
* Hammer * Hammer
*/ */

View File

@@ -68,6 +68,7 @@ export default class UtilitySlotSection extends SlotSection {
availableModules={() => availableModules.getHps(h.maxClass)} availableModules={() => availableModules.getHps(h.maxClass)}
onOpen={this._openMenu.bind(this,h)} onOpen={this._openMenu.bind(this,h)}
onSelect={this._selectModule.bind(this, h)} onSelect={this._selectModule.bind(this, h)}
onChange={this.props.onChange}
selected={currentMenu == h} selected={currentMenu == h}
drag={this._drag.bind(this, h)} drag={this._drag.bind(this, h)}
dragOver={this._dragOverSlot.bind(this, h)} dragOver={this._dragOverSlot.bind(this, h)}
@@ -94,6 +95,7 @@ export default class UtilitySlotSection extends SlotSection {
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}> return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul> <ul>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li> <li className='lc' onClick={this._empty}>{translate('empty all')}</li>
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
</ul> </ul>
<div className='select-group cap'>{translate('sb')}</div> <div className='select-group cap'>{translate('sb')}</div>
<ul> <ul>
@@ -103,9 +105,17 @@ export default class UtilitySlotSection extends SlotSection {
<li className='c' onClick={_use.bind(this, 'sb', 'B', null)}>B</li> <li className='c' onClick={_use.bind(this, 'sb', 'B', null)}>B</li>
<li className='c' onClick={_use.bind(this, 'sb', 'A', null)}>A</li> <li className='c' onClick={_use.bind(this, 'sb', 'A', null)}>A</li>
</ul> </ul>
<div className='select-group cap'>{translate('cm')}</div> <div className='select-group cap'>{translate('hs')}</div>
<ul> <ul>
<li className='lc' onClick={_use.bind(this, 'cm', null, 'Heat Sink Launcher')}>{translate('Heat Sink Launcher')}</li> <li className='lc' onClick={_use.bind(this, 'hs', null, 'Heat Sink Launcher')}>{translate('Heat Sink Launcher')}</li>
</ul>
<div className='select-group cap'>{translate('ch')}</div>
<ul>
<li className='lc' onClick={_use.bind(this, 'ch', null, 'Chaff Launcher')}>{translate('Chaff Launcher')}</li>
</ul>
<div className='select-group cap'>{translate('po')}</div>
<ul>
<li className='lc' onClick={_use.bind(this, 'po', null, 'Point Defence')}>{translate('Point Defence')}</li>
</ul> </ul>
</div>; </div>;
} }

View File

@@ -41,29 +41,33 @@ export function getLanguage(langCode) {
formats: { formats: {
gen, // General number format (.e.g 1,001,001.1234) gen, // General number format (.e.g 1,001,001.1234)
int: d3Locale.numberFormat(',.0f'), // Fixed to 0 decimal places (.e.g 1,001) int: d3Locale.numberFormat(',.0f'), // Fixed to 0 decimal places (.e.g 1,001)
f1: d3Locale.numberFormat(',.1f'), // Fixed to 1 decimal place (.e.g 1,001.1)
f2: d3Locale.numberFormat(',.2f'), // Fixed to 2 decimal places (.e.g 1,001.10) f2: d3Locale.numberFormat(',.2f'), // Fixed to 2 decimal places (.e.g 1,001.10)
s2: d3Locale.numberFormat('.2s'), // SI Format to 2 decimal places (.e.g 1.1k) s2: d3Locale.numberFormat('.2s'), // SI Format to 2 decimal places (.e.g 1.1k)
pct: d3Locale.numberFormat('.2%'), // % to 2 decimal places (.e.g 5.40%) pct: d3Locale.numberFormat('.2%'), // % to 2 decimal places (.e.g 5.40%)
pct1: d3Locale.numberFormat('.1%'), // % to 1 decimal places (.e.g 5.4%) pct1: d3Locale.numberFormat('.1%'), // % to 1 decimal places (.e.g 5.4%)
r1: d3Locale.numberFormat('.1r'), // Rounded to 1 significant number (.e.g 512 => 500, 4.122 => 4)
r2: d3Locale.numberFormat('.2r'), // Rounded to 2 significant numbers (.e.g 512 => 510, 4.122 => 4.1) r2: d3Locale.numberFormat('.2r'), // Rounded to 2 significant numbers (.e.g 512 => 510, 4.122 => 4.1)
rPct: d3.format('%'), // % to 0 decimal places (.e.g 5%) rPct: d3.format('%'), // % to 0 decimal places (.e.g 5%)
round1: (d) => gen(d3.round(d, 1)), // Rounded to 0-1 decimal places (.e.g 5.1, 4)
round: (d) => gen(d3.round(d, 2)), // Rounded to 0-2 decimal places (.e.g 5.12, 4.1) round: (d) => gen(d3.round(d, 2)), // Rounded to 0-2 decimal places (.e.g 5.12, 4.1)
time: (d) => (d < 0 ? '-' : '') + Math.floor(Math.abs(d) / 60) + ':' + ('00' + Math.floor(Math.abs(d) % 60)).substr(-2, 2) time: (d) => (d < 0 ? '-' : '') + Math.floor(Math.abs(d) / 60) + ':' + ('00' + Math.floor(Math.abs(d) % 60)).substr(-2, 2)
}, },
translate, translate,
units: { units: {
CR: <u>{' ' + translate('CR')}</u>, // Credits CR: <u> {translate('CR')}</u>, // Credits
kg: <u>{' ' + translate('kg')}</u>, // Kilograms kg: <u> {translate('kg')}</u>, // Kilograms
kgs: <u>{' ' + translate('kg/s')}</u>, // Kilograms per second kgs: <u> {translate('kg/s')}</u>, // Kilograms per second
km: <u>{' ' + translate('km')}</u>, // Kilometers km: <u> {translate('km')}</u>, // Kilometers
Ls: <u>{' ' + translate('Ls')}</u>, // Light Seconds Ls: <u> {translate('Ls')}</u>, // Light Seconds
LY: <u>{' ' + translate('LY')}</u>, // Light Years LY: <u> {translate('LY')}</u>, // Light Years
MJ: <u>{' ' + translate('MJ')}</u>, // Mega Joules MJ: <u> {translate('MJ')}</u>, // Mega Joules
'm/s': <u>{' ' + translate('m/s')}</u>, // Meters per second 'm/s': <u> {translate('m/s')}</u>, // Meters per second
MW: <u>{' ' + translate('MW')}</u>, // Mega Watts (same as Mega Joules per second) MW: <u> {translate('MW')}</u>, // Mega Watts (same as Mega Joules per second)
ps: <u>{translate('/s')}</u>, // per second ps: <u>{translate('/s')}</u>, // per second
pm: <u>{translate('/min')}</u>, // per minute pm: <u>{translate('/min')}</u>, // per minute
T: <u>{' ' + translate('T')}</u>, // Metric Tons s: <u>{translate('secs')}</u>, // Seconds
T: <u> {translate('T')}</u>, // Metric Tons
} }
}; };
} }

View File

@@ -15,76 +15,20 @@ export const formats = {
export const terms = { export const terms = {
// Phrases // Phrases
PHRASE_BACKUP_DESC: 'Export aller Coriolis-Daten, um sie zu sichern oder oder um sie zu einem anderen Browser/Gerät zu übertragen.', // Backup of all Coriolis data to save or transfer to another browser/device PHRASE_BACKUP_DESC: 'Export aller Coriolis-Daten, um sie zu sichern oder um sie zu einem anderen Browser/Gerät zu übertragen.', // Backup of all Coriolis data to save or transfer to another browser/device
PHRASE_CONFIRMATION: 'Sind Sie sicher?', // Are You Sure? PHRASE_CONFIRMATION: 'Sind Sie sicher?', // Are You Sure?
PHRASE_EXPORT_DESC: 'Ein detaillierter JSON-Export Ihrer Konfiguration für die Verwendung in anderen Websites und Tools', // A detailed JSON export of your build for use in other sites and tools PHRASE_EXPORT_DESC: 'Ein detaillierter JSON-Export Ihrer Konfiguration für die Verwendung in anderen Websites und Tools', // A detailed JSON export of your build for use in other sites and tools
PHRASE_FASTEST_RANGE: null, // Consecutive max range jumps PHRASE_FASTEST_RANGE: 'aufeinanderfolgende maximale Reichweite/Sprünge', // Consecutive max range jumps
PHRASE_IMPORT: 'JSON hier einfügen oder importieren', // Paste JSON or import here PHRASE_IMPORT: 'JSON hier einfügen oder importieren', // Paste JSON or import here
PHRASE_LADEN: null, // Ship Mass + Fuel + Cargo PHRASE_LADEN: 'Schiffsmasse + Treibstoff + Fracht', // Ship Mass + Fuel + Cargo
PHRASE_NO_BUILDS: 'Keine Konfigurationen zum Vergleich ausgewählt!', // No builds added to comparison! PHRASE_NO_BUILDS: 'Keine Konfigurationen zum Vergleich ausgewählt!', // No builds added to comparison!
PHRASE_NO_RETROCH: 'Keine Umrüständerungen', // No Retrofitting changes PHRASE_NO_RETROCH: 'Keine Umrüständerungen', // No Retrofitting changes
PHRASE_SELECT_BUILDS: 'Ausstattung zum Vergleich auswählen', // Select Builds to Compare PHRASE_SELECT_BUILDS: 'Ausstattung zum Vergleich auswählen', // Select Builds to Compare
PHRASE_SG_RECHARGE: null, // Time from 50% to 100% Charge PHRASE_SG_RECHARGE: 'Zeit von 50% bis 100% der Ladung', // Time from 50% to 100% Charge
PHRASE_SG_RECOVER: null, // Recovery (to 50%) after collapse PHRASE_SG_RECOVER: 'Erneuerung (zu 50%) nach Zusammenbruch', // Recovery (to 50%) after collapse
PHRASE_UNLADEN: null, // Ship Mass excluding Fuel and Cargo PHRASE_UNLADEN: 'Schiffsmasse ohne Treibstoff und Fracht', // Ship Mass excluding Fuel and Cargo
PHRASE_UPDATE_RDY: 'Update verfügbar! Klicken zum Aktualisieren', // Update Available! Click to Refresh PHRASE_UPDATE_RDY: 'Update verfügbar! Klicken zum Aktualisieren', // Update Available! Click to Refresh
// Modules / Module Types - These should match the in-game translation (if any)
'Basic Discovery Scanner': 'einfacher Aufklärungsscanner', // Basic Discovery Scanner
'Cargo Hatch': 'Frachtluke', // Cargo Hatch
'Chaff Launcher': 'Düppel-Werfer', // Chaff Launcher
'Detailed Surface Scanner': 'Detailoberflächenscanner', // Detailed Surface Scanner
'Electronic Countermeasure': 'elektronische Gegenmaßnahme', // Electronic Countermeasure
'Heat Sink Launcher': 'Kühlkörperwerfer', // Heat Sink Launcher
'Intermediate Discovery Scanner': 'mittlerer Aufklärungsscanner', // Intermediate Discovery Scanner
'Point Defence': 'Punktverteidigung',
'Standard Docking Computer': 'Standard-Landecomputer', // Standard Docking Computer
am: 'automatische Feldwartungs-Einheit', // Auto Field-Maintenance Unit
bh: 'Rumpfhüllenverstärkung', // Bulkheads
bl: 'Strahlenlaser', // Beam Laser
c: 'Kanone', // Cannon
cc: 'Krallensteuerung: Sammler', // Collector Limpet Controller
cm: 'Gegenmaßnahme', // Countermeasure
cr: 'Frachtgestell', // Cargo Rack
cs: 'Frachtscanner', // Cargo Scanner
dc: 'Standard-Landecomputer', // Docking Computer
fc: 'Splitterkanone', // Fragment Cannon
fi: 'FSA-Unterbrecher', // FSD Interdictor
fs: 'Treibstoffsammler', // Fuel Scoop
fsd: 'Frameshiftantrieb', // Frame Shift Drive
ft: 'Treibstofftank', // Fuel Tank
fx: 'Krallensteuerung Treibstoffstransfer', // Fuel Transfer Limpet Controller
hb: 'Krallen-Steuereinheit (Ladelukenöffner)', // Hatch Breaker Limpet Controller
hr: 'Rumpfhüllenverstärkung (Paket)', // Hull Reinforcement Package
kw: 'Tötungsbefehl-Scanner', // Kill Warrant Scanner
ls: 'Lebenserhaltung', // life support
mc: 'Mehrfachgeschütz', // Multi-cannon
ml: 'Abbaulaser', // Mining Laser
mr: 'Raketenbatterie', // Missile Rack
nl: 'Minenwerfer', // Mine Launcher
pa: 'Plasmabeschleuniger', // Plasma Accelerator
pc: 'Krallensteuerung: Erzsucher', // Prospector Limpet Controller
pd: 'Energieverteiler', // power distributor
pl: 'Impulslaser', // Pulse Laser
pp: 'Kraftwerk', // Power Plant
psg: 'Prismaschildgenerator', // Prismatic Shield Generator
rf: 'Raffinerie', // Refinery
rg: 'Schienenkanone', // Rail Gun
s: 'Sensoren', // Sensors
sb: 'Schild-Booster', // Shield Booster
sc: 'Scanner', // Scanner
scb: 'Schildzellenbank', // Shield Cell Bank
sg: 'Schildgenerator', // Shield Generator
t: 'Schubdüsen', // Thrusters
tp: 'Torpedoaufhängung', // Torpedo Pylon
ul: 'Salvenlaser', // Burst Laser
ws: 'Frameshift-Sogwolkenscanner', // Frame Shift Wake Scanner
// Bulkheads - These should match the in-game translation (if any)
'Lightweight Alloy': 'leichte Legierung',
'Reinforced Alloy': 'verstärkte Legierung',
'Military Grade Composite': 'Militär-Komposit',
'Mirrored Surface Composite': 'Gespiegelte-Oberfläche-Komposit',
'Reactive Surface Composite': 'Reaktive-Oberfläche-Komposit',
// Units / Metrics // Units / Metrics
LY: 'Lj', // Light Years LY: 'Lj', // Light Years
T: 't', // Tons (Metric Ton - 1000kg) T: 't', // Tons (Metric Ton - 1000kg)
@@ -94,109 +38,108 @@ export const terms = {
L: 'G', // Large Hardpoint size (single character) L: 'G', // Large Hardpoint size (single character)
H: 'R', // Huge Hardpoint size (single character) H: 'R', // Huge Hardpoint size (single character)
U: 'W', // Utility Hardpoint size (single character) - Kill warrant scanner, etc U: 'W', // Utility Hardpoint size (single character) - Kill warrant scanner, etc
small: 'klein', // Small ship size
medium: 'mittel', // Medium ship size
large: 'groß', // Large Ship Size
// Terms // Terms
'build name': 'Ausstattungsname', // Ship build/configuration/design name about: 'über', // Link to about page / about Coriolis.io
'compare all': 'Alles vergleichen',
'create new': 'Neu erstellen',
'damage per second': null,
'delete all': 'Alles Löschen',
'detailed export': 'detailierter Export',
'edit data': 'bearbeiten',
'empty all': 'leer alles',
'Enter Name': 'Namen eingeben',
'fastest range': null, // Fastet totaljump range - sum of succesive jumps
'fuel level': null, // Percent of fuel (T) in the tank
'full tank': 'Tank voll',
'internal compartments': 'Innenbereich',
'jump range': 'Sprungreichweite',
'mass lock factor': 'Massensperrefaktor',
'max mass': 'maximale Masse',
'net cost': 'Nettokosten',
'none created': 'Leer',
'optimal mass': null, // Lowest weight / best weight for jump distance, etc
'refuel time': 'Auftankzeit', // Time to refuel the tank when scooping
'reload costs': null,
'retrofit costs': 'Änderungskosten', // The cost difference when upgrading / downgrading a component
'retrofit from': 'Nachrüsten von', // Retrofit from Build A against build B
'T-Load': 'T-Lad', // Thermal load abbreviation
'total range': null,
'unit cost': null,
'utility mounts': 'Werkzeug-Steckplätze',
about: 'Über', // Link to about page / about Coriolis.io
action: 'Aktion', action: 'Aktion',
added: 'hinzugefügt', added: 'hinzugefügt',
ammo: 'Munition', // Ammunition ammo: 'Munition', // Ammunition
armour: 'Panzerung', armour: 'Panzerung',
available: 'verfügbar', available: 'verfügbar', // Available options
backup: 'Sicherungsdatei', backup: 'Sicherungsdatei',
base: null, base: 'Basis', // Base speed, boost, etc - Base ship stats
bays: null, bays: 'Lagerraum',
bins: 'Behälter', // Number of Mining Refinery bins bins: 'Behälter', // Number of Mining Refinery bins
build: 'Ausstattung', // Shorthand for the build/configuration/design name build: 'Ausstattung', // Shorthand for the build/configuration/design name
'build name': 'Ausstattungsname', // Ship build/configuration/design name
builds: 'Ausstattungen', // Ship build/configuration/design names builds: 'Ausstattungen', // Ship build/configuration/design names
buy: 'kaufen', buy: 'kaufen',
cancel: 'Abbrechen', cancel: 'abbrechen',
cargo: 'Fracht', cargo: 'Fracht',
cells: 'Zellen', // Number of cells in a shield cell bank cells: 'Zellen', // Number of cells in a shield cell bank
close: 'Schließen', close: 'schließen',
compare: 'vergleichen', compare: 'vergleichen',
'compare all': 'alles vergleichen',
comparison: 'Vergleich', comparison: 'Vergleich',
comparisons: 'Vergleiche', comparisons: 'Vergleiche',
cost: 'Preis', // Cost / price of a module or price of a ship cost: 'Preis', // Cost / price of a module or price of a ship
costs: 'Kosten', // Costs / prices of a modules or prices of ships costs: 'Kosten', // Costs / prices of a modules or prices of ships
create: 'erstellen', create: 'erstellen',
'create new': 'neu erstellen',
credits: 'Credits', credits: 'Credits',
damage: 'Schaden', damage: 'Schaden',
delete: 'Löschen', 'damage per second': 'Schaden pro Sekunde',
dep: 'Ausg', // Weapons/Hardpoints Deployed abbreviation delete: 'löschen',
deployed: 'Ausgefahren', // Weapons/Hardpoints Deployed 'delete all': 'alles löschen',
disabled: 'Deaktiviert', dep: 'ausg', // Weapons/Hardpoints Deployed abbreviation
deployed: 'ausgefahren', // Weapons/Hardpoints Deployed
'detailed export': 'detailierter Export',
disabled: 'deaktiviert',
discount: 'Rabatt', discount: 'Rabatt',
'edit data': 'bearbeiten',
efficiency: 'Effizienz', // Power Plant efficiency efficiency: 'Effizienz', // Power Plant efficiency
empty: 'leer', empty: 'leer',
'empty all': 'alles entfernen',
ENG: 'ANT', // Abbreviation - Engine recharge rate for power distributor ENG: 'ANT', // Abbreviation - Engine recharge rate for power distributor
'Enter Name': 'Namen eingeben',
Explorer: 'Forscher', Explorer: 'Forscher',
export: 'Export',
'fastest range': 'maximale Reichweite', // Fastet totaljump range - sum of succesive jumps
forum: 'Forum', forum: 'Forum',
fuel: 'Treibstoff', fuel: 'Treibstoff',
'fuel level': 'Tankfüllstand', // Percent of fuel (T) in the tank
'full tank': 'Tank voll',
hardpoints: 'Waffenaufhängungen', hardpoints: 'Waffenaufhängungen',
hull: 'Hülle', // Ships hull hull: 'Rumpf', // Ships hull
import: 'importieren', import: 'importieren',
insurance: 'Versicherung', insurance: 'Versicherung',
jump: null, // Single jump range 'internal compartments': 'Innenbereich',
jump: 'Sprung', // Single jump range
'jump range': 'Sprungreichweite',
jumps: 'Sprünge', jumps: 'Sprünge',
laden: 'beladen', laden: 'beladen',
language: 'Sprache', language: 'Sprache',
maneuverability: 'Manövrierbarkeit', maneuverability: 'Manövrierbarkeit',
manufacturer: null, manufacturer: 'Hersteller',
mass: 'Masse', mass: 'Masse',
MLF: null, // Mass Lock Factor Abbreviation 'mass lock factor': 'Massensperrefaktor',
MNV: null, // Maneuverability abbreviation 'max mass': 'maximale Masse',
module: 'modul', MLF: 'MSF', // Mass Lock Factor Abbreviation
modules: 'module', module: 'Modul',
modules: 'Module',
'net cost': 'Nettokosten',
no: 'Nein', no: 'Nein',
'none created': 'nicht erstellt',
ok: 'OK', ok: 'OK',
optimize: null, 'optimal mass': 'optimale Masse', // Lowest weight / best weight for jump distance, etc
optimize: 'optimieren',
pen: 'Durchdr.', // Armour peneration abbreviation pen: 'Durchdr.', // Armour peneration abbreviation
permalink: 'Permanentlink',
power: 'Energie', // Power = Energy / second. Power generated by the power plant, or power consumed (MW / Mega Watts). Used in the power plant section power: 'Energie', // Power = Energy / second. Power generated by the power plant, or power consumed (MW / Mega Watts). Used in the power plant section
pri: 'Prio', // Priority abbreviation for power management pri: 'Prio', // Priority abbreviation for power management
proceed: 'Fortfahren', proceed: 'fortfahren',
PWR: 'En', // Power Abbreviation. See Power qty: 'Menge', // Quantity abbreviation
qty: null, // Quantity abbreviation
range: 'Reichweite', range: 'Reichweite',
rate: 'Rate', rate: 'Rate',
recharge: null, // Shield Recharge time from 50% -> 100% recharge: 'aufladen', // Shield Recharge time from 50% -> 100%
recovery: null, // Shield recovery time (after losing shields/turning on -> 50%) recovery: 'Erneuerung', // Shield recovery time (after losing shields/turning on -> 50%)
'refuel time': 'Auftankzeit', // Time to refuel the tank when scooping
reload: 'aktualisieren', // Reload weapon/hardpoint reload: 'aktualisieren', // Reload weapon/hardpoint
'reload costs': 'Nachladekosten',
rename: 'umbenennen', rename: 'umbenennen',
repair: 'reparieren', repair: 'reparieren',
reset: 'zurücksetzen', reset: 'zurücksetzen',
ret: 'Eing', // Retracted abbreviation ret: 'eing', // Retracted abbreviation
retracted: 'Eingefahren', // Weapons/Hardpoints retracted retracted: 'eingefahren', // Weapons/Hardpoints retracted
'retrofit costs': 'Änderungskosten', // The cost difference when upgrading / downgrading a component
'retrofit from': 'nachrüsten von', // Retrofit from Build A against build B
ROF: 'Kad', // Rate of Fire abbreviation ROF: 'Kad', // Rate of Fire abbreviation
roles: null, // Commander/Ship build roles - e.g. Trader, Bounty-Hunter, Explorer, etc roles: 'Rollen', // Commander/Ship build roles - e.g. Trader, Bounty-Hunter, Explorer, etc
save: 'Speichern', save: 'speichern',
sell: 'Verkaufen', sell: 'verkaufen',
settings: 'Einstellungen', // Coriolis application settings settings: 'Einstellungen', // Coriolis application settings
shields: 'Schilde', shields: 'Schilde',
ship: 'Schiff', ship: 'Schiff',
@@ -205,16 +148,19 @@ export const terms = {
size: 'Größe', size: 'Größe',
skip: 'überspringen', // Skip past something / ignore it skip: 'überspringen', // Skip past something / ignore it
speed: 'Geschwindigkeit', speed: 'Geschwindigkeit',
standard: 'Standard', // Standard / Common modules (FSD, power plant, life support, etc) standard: 'Grundausstattung', // Standard / Common modules (FSD, power plant, life support, etc)
Stock: 'Standard', // Thermal-load abbreviation Stock: 'Standard', // Thermal-load abbreviation
strength: null, // Strength in reference to Shield Strength strength: 'Stärke', // Strength in reference to Shield Strength
subtotal: null, subtotal: 'Zwischensumme',
time: 'Dauer', // time it takes to complete something time: 'Dauer', // time it takes to complete something
tooltips: null, // Tooltips setting - show/hide tooltips: 'Tooltips', // Tooltips setting - show/hide
total: 'Gesamt', total: 'gesamt',
Trader: null, // Trader role 'total range': 'Gesamtbereich',
Trader: 'Händler', // Trader role
type: 'Typ', type: 'Typ',
unladen: 'Unbeladen', // No cargo or fuel 'unit cost': 'Kosten pro Einheit',
unladen: 'unbeladen', // No cargo or fuel
'utility mounts': 'Werkzeug-Steckplätze',
WEP: 'WAF', // Abbreviation - Weapon recharge rate for power distributor WEP: 'WAF', // Abbreviation - Weapon recharge rate for power distributor
yes: 'Ja' yes: 'ja'
}; };

View File

@@ -14,20 +14,22 @@ export const formats = {
}; };
export const terms = { export const terms = {
PHRASE_ALT_ALL: 'Alt + Click to fill all slots',
PHRASE_BACKUP_DESC: 'Backup of all Coriolis data to save or transfer to another browser/device', PHRASE_BACKUP_DESC: 'Backup of all Coriolis data to save or transfer to another browser/device',
PHRASE_CONFIRMATION: 'Are You Sure?', PHRASE_CONFIRMATION: 'Are you sure?',
PHRASE_EXPORT_DESC: 'A detailed JSON export of your build for use in other sites and tools', PHRASE_EXPORT_DESC: 'A detailed JSON export of your build for use in other sites and tools',
PHRASE_FASTEST_RANGE: 'Consecutive max range jumps', PHRASE_FASTEST_RANGE: 'Consecutive max range jumps',
PHRASE_IMPORT: 'Paste JSON or import here', PHRASE_IMPORT: 'Paste JSON or import here',
PHRASE_LADEN: 'Ship Mass + Fuel + Cargo', PHRASE_LADEN: 'Ship mass + fuel + cargo',
PHRASE_NO_BUILDS: 'No builds added to comparison!', PHRASE_NO_BUILDS: 'No builds added to comparison!',
PHRASE_NO_RETROCH: 'No Retrofitting changes', PHRASE_NO_RETROCH: 'No Retrofitting changes',
PHRASE_SELECT_BUILDS: 'Select Builds to Compare', PHRASE_SELECT_BUILDS: 'Select builds to compare',
PHRASE_SG_RECHARGE: 'Time from 50% to 100% Charge', PHRASE_SG_RECHARGE: 'Time from 50% to 100% charge',
PHRASE_SG_RECOVER: 'Recovery (to 50%) after collapse', PHRASE_SG_RECOVER: 'Recovery (to 50%) after collapse',
PHRASE_UNLADEN: 'Ship Mass excluding Fuel and Cargo', PHRASE_UNLADEN: 'Ship mass excluding fuel and cargo',
PHRASE_UPDATE_RDY: 'Update Available! Click to Refresh', PHRASE_UPDATE_RDY: 'Update Available! Click to refresh',
// Other languages fallback to these values
// Only Translate to other languages if the name is different in-game // Only Translate to other languages if the name is different in-game
am: 'Auto Field-Maintenance Unit', am: 'Auto Field-Maintenance Unit',
bh: 'Bulkheads', bh: 'Bulkheads',
@@ -35,11 +37,13 @@ export const terms = {
bsg: 'Bi-Weave Shield Generator', bsg: 'Bi-Weave Shield Generator',
c: 'Cannon', c: 'Cannon',
cc: 'Collector Limpet Controller', cc: 'Collector Limpet Controller',
cm: 'Countermeasure', ch: 'Chaff Launcher',
cr: 'Cargo Rack', cr: 'Cargo Rack',
cs: 'Cargo Scanner', cs: 'Manifest Scanner',
dc: 'Docking Computer', dc: 'Docking Computer',
ec: 'Electronic Countermeasure',
fc: 'Fragment Cannon', fc: 'Fragment Cannon',
fh: 'Fighter Hangar',
fi: 'FSD Interdictor', fi: 'FSD Interdictor',
fs: 'Fuel Scoop', fs: 'Fuel Scoop',
fsd: 'Frame Shift Drive', fsd: 'Frame Shift Drive',
@@ -47,6 +51,7 @@ export const terms = {
fx: 'Fuel Transfer Limpet Controller', fx: 'Fuel Transfer Limpet Controller',
hb: 'Hatch Breaker Limpet Controller', hb: 'Hatch Breaker Limpet Controller',
hr: 'Hull Reinforcement Package', hr: 'Hull Reinforcement Package',
hs: 'Heat Sink Launcher',
kw: 'Kill Warrant Scanner', kw: 'Kill Warrant Scanner',
ls: 'Life Support', ls: 'Life Support',
mc: 'Multi-cannon', mc: 'Multi-cannon',
@@ -56,11 +61,16 @@ export const terms = {
pa: 'Plasma Accelerator', pa: 'Plasma Accelerator',
pas: 'Planetary Approach Suite', pas: 'Planetary Approach Suite',
pc: 'Prospector Limpet Controller', pc: 'Prospector Limpet Controller',
pce: 'Economy Class Passenger Cabin',
pci: 'Business Class Passenger Cabin',
pcm: 'First Class Passenger Cabin',
pcq: 'Luxury Passenger Cabin',
pd: 'power distributor', pd: 'power distributor',
pl: 'Pulse Laser', pl: 'Pulse Laser',
po: 'Point Defence',
pp: 'Power Plant', pp: 'Power Plant',
psg: 'Prismatic Shield Generator', psg: 'Prismatic Shield Generator',
pv: 'Planetary Vehicle Hanger', pv: 'Planetary Vehicle Hangar',
rf: 'Refinery', rf: 'Refinery',
rg: 'Rail Gun', rg: 'Rail Gun',
s: 'Sensors', s: 'Sensors',
@@ -71,5 +81,64 @@ export const terms = {
t: 'thrusters', t: 'thrusters',
tp: 'Torpedo Pylon', tp: 'Torpedo Pylon',
ul: 'Burst Laser', ul: 'Burst Laser',
ws: 'Frame Shift Wake Scanner' ws: 'Frame Shift Wake Scanner',
// Items on the outfitting page
// Notification of restricted slot for Orca/Beluga
emptyrestricted: 'empty (restricted)',
// 'ammo' was overloaded for outfitting page and modul info, so changed to ammunition for outfitting page
ammunition: 'Ammo',
// Unit for seconds
secs: 's',
// Hardpoint abbreviations
dpe: 'Damage per MJ of energy',
dps: 'Damage per second',
dpssdps: 'Damage per second (sustained damage per second)',
eps: 'Energy per second',
epsseps: 'Energy per second (sustained energy per second)',
hps: 'Heat per second',
hpsshps: 'Heat per second (sustained heat per second)',
// Modifications
ammo: 'Ammunition maximum',
boot: 'Boot time',
brokenregen: 'Broken regeneration rate',
burst: 'Burst',
clip: 'Ammunition clip',
damage: 'Damage',
distdraw: 'Distributor draw',
duration: 'Duration',
eff: 'Efficiency',
engcap: 'Engines capacity',
engrate: 'Engines recharge rate',
explres: 'Explosive resistance',
facinglimit: 'Facing limit',
hullboost: 'Hull boost',
hullreinforcement: 'Hull reinforcement',
integrity: 'Integrity',
jitter: 'Jitter',
kinres: 'Kinetic resistance',
maxfuel: 'Maximum fuel per jump',
mass: 'Mass',
optmass: 'Optimal mass',
optmul: 'Optimal multiplier',
pgen: 'Power generation',
piercing: 'Piercing',
power: 'Power draw',
range: 'Range',
ranget: 'Range', // Range in time (for FSD interdictor)
regen: 'Regeneration rate',
reload: 'Reload time',
rof: 'Rate of fire',
shield: 'Shield',
shieldboost: 'Shield boost',
spinup: 'Spin up time',
syscap: 'Systems capacity',
sysrate: 'Systems recharge rate',
thermload: 'Thermal load',
thermres: 'Thermal resistance',
wepcap: 'Weapons capacity',
weprate: 'Weapons recharge rate',
}; };

View File

@@ -15,119 +15,42 @@ export const formats = {
export const terms = { export const terms = {
// Phrases // Phrases
PHRASE_BACKUP_DESC: 'Exportation détaillée des données Coriolis pour l\'utilisation dans d\'autres sites et outils', // Backup of all Coriolis data to save or transfer to another browser/device PHRASE_BACKUP_DESC: 'Copie des données Coriolis pour l\'utilisation dans d\'autres sites et outils', // Backup of all Coriolis data to save or transfer to another browser/device
PHRASE_CONFIRMATION: 'Êtes-vous sûr?', // Are You Sure? PHRASE_CONFIRMATION: 'Êtes-vous sûr?', // Are You Sure?
PHRASE_EXPORT_DESC: 'Un export détaillé en JSON de votre configuration pour l\'utilisation dans d\'autres sites et outils', // A detailed JSON export of your build for use in other sites and tools PHRASE_EXPORT_DESC: 'Un export détaillé en JSON de votre configuration pour l\'utilisation dans d\'autres sites et outils', // A detailed JSON export of your build for use in other sites and tools
PHRASE_FASTEST_RANGE: 'Portée maximale en cas de sauts successifs', // Consecutive max range jumps
PHRASE_IMPORT: 'Coller JSON ou importer ici', // Paste JSON or import here PHRASE_IMPORT: 'Coller JSON ou importer ici', // Paste JSON or import here
PHRASE_NO_BUILDS: 'Défaut de configuration pour comparaison', // No builds added to comparison! PHRASE_LADEN: 'Masse du Vaisseau + Carburant + Cargo', // Ship Mass + Fuel + Cargo
PHRASE_NO_RETROCH: 'configuration non modifiée', // No Retrofitting changes PHRASE_NO_BUILDS: 'Aucune configuration ajoutée pour la comparaison', // No builds added to comparison!
PHRASE_SELECT_BUILDS: 'Sélectionner configurations à comparer', // Select Builds to Compare PHRASE_NO_RETROCH: 'Aucun changement de configuration', // No Retrofitting changes
PHRASE_SELECT_BUILDS: 'Sélectionner les configurations à comparer', // Select Builds to Compare
PHRASE_SG_RECHARGE: 'Temps de charge de 50% à 100 %', // Time from 50% to 100% Charge
PHRASE_SG_RECOVER: 'Temps de redémarrage après perte du bouclier', // Recovery (to 50%) after collapse
PHRASE_UNLADEN: 'Masse du Vaisseau hors Carburant et Cargo', // Ship Mass excluding Fuel and Cargo
PHRASE_UPDATE_RDY: 'Mise à jour disponible ! Cliquez pour rafraichir', // Update Available! Click to Refresh PHRASE_UPDATE_RDY: 'Mise à jour disponible ! Cliquez pour rafraichir', // Update Available! Click to Refresh
// Modules / Module Types - These should match the in-game translation (if any)
am: 'Unité de maintenance de terrain auto', // Auto Field-Maintenance Unit
'Basic Discovery Scanner': 'Détecteur de découverte simple', // Basic Discovery Scanner
bh: 'Coque', // Bulkheads
bl: 'Rayon Laser', // Beam Laser
'Cargo Hatch': 'Ecoutille de soute', // Cargo Hatch
c: 'Canon', // Cannon
cc: 'Contrôleur de collecteurs', // Collector Limpet Controller
cm: 'Contre-mesure', // Countermeasure
'Chaff Launcher': 'Lanceur de paillettes', // Chaff Launcher
cr: 'Compartiment de soute', // Cargo Rack
cs: 'Scanner de soute', // Cargo Scanner
dc: 'Ordinateur d\'appontage', // Docking Computer
'Detailed Surface Scanner': 'Détecteur de surface détaillé', // Detailed Surface Scanner
'Electronic Countermeasure': 'Contre mesure électronique', // Electronic Countermeasure
fc: 'Canon à fragmentation', // Fragment Cannon
fi: 'Intercepteur de réacteur FSD', // FSD Interdictor
fs: 'Récupérateur de carburant', // Fuel Scoop
fsd: 'Réacteur FSD', // Frame Shift Drive
ft: 'Réservoir de carburant', // Fuel Tank
fx: 'Drone de ravitaillement', // Fuel Transfer Limpet Controller
'Heat Sink Launcher': 'Ejecteur de dissipateur thermique', // Heat Sink Launcher
hb: 'Contrôle de patelle perce-soute', // Hatch Breaker Limpet Controller
hr: 'Renfort de soute', // Hull Reinforcement Package
'Intermediate Discovery Scanner': 'Détecteur de découverte intermédiaire', // Intermediate Discovery Scanner
kw: 'Détecteur d\'avis de recherche', // Kill Warrant Scanner
ls: 'Support vital', // life support
mc: 'Canon multiple', // Multi-cannon
ml: 'Laser minier', // Mining Laser
mr: 'Lance missiles', // Missile Rack
nl: 'Lance-mines', // Mine Launcher
pa: 'accélérateur plasma', // Plasma Accelerator
pc: 'Drône de minage', // Prospector Limpet Controller
pd: 'distributeur d\'énérgie', // power distributor
pl: 'Laser à impulsion', // Pulse Laser
'Point Defence': 'Défense ponctuelle',
pp: 'centrale d\'énergie', // Power Plant
psg: 'générateur de bouclier prisme', // Prismatic Shield Generator
rf: 'Raffinerie', // Refinery
rg: 'Canon électromagnétique', // Rail Gun
s: 'détecteurs', // Sensors
sb: 'Survolteur de bouclier', // Shield Booster
sc: 'scanner', // Scanner
scb: 'Réserve de cellules d\'énergie', // Shield Cell Bank
sg: 'Générateur de bouclier', // Shield Generator
'Standard Docking Computer': 'ordinateur d\'appontage standard', // Standard Docking Computer
t: 'propulseurs', // Thrusters
tp: 'Tube lance-torpille', // Torpedo Pylon
ul: 'Laser à rafale', // Burst Laser
ws: 'Détecteur de sillage FSD', // Frame Shift Wake Scanner
// Bulkheads - These should match the in-game translation (if any)
'Lightweight Alloy': 'alliage léger',
'Reinforced Alloy': 'alliage renforcé',
'Military Grade Composite': 'Composite militaire',
'Mirrored Surface Composite': 'Composite à surface mirroir',
'Reactive Surface Composite': 'Composite à surface réactive',
// Units / Metrics // Units / Metrics
Ls: 'SL', // Light seconds Ls: 'SL', // Light seconds
LY: 'AL', // Light Years LY: 'AL', // Light Years
// Sizes // Sizes
S: 'P', // Small Hardpoint (single Character)
L: 'G', // Large Hardpoint size (single character) L: 'G', // Large Hardpoint size (single character)
large: 'grand', // Large Ship Size
medium: 'moyen', // Medium ship size
S: 'P', // Small Hardpoint (single Character)
small: 'petit', // Small ship size
// Terms // Terms
'build name': 'Nom de la configuration', // Ship build/configuration/design name
'compare all': 'tout comparer',
'create new': 'Créer nouveau',
'damage per second': null,
'delete all': 'tout supprimer',
'detailed export': 'export détaillé',
'edit data': 'Editer donnée',
'empty all': null,
'Enter Name': 'Entrer nom',
'fastest range': null, // Fastet totaljump range - sum of succesive jumps
'fuel level': null, // Percent of fuel (T) in the tank
'full tank': 'Réservoir plein',
'internal compartments': 'compartiments internes',
'jump range': 'Distance de saut',
'mass lock factor': 'facteur inhibition de masse',
'max mass': 'masse max',
'net cost': 'coûts nets',
'none created': 'Rien de créé',
'optimal mass': null, // Lowest weight / best weight for jump distance, etc
'refuel time': 'Temps de remplissage', // Time to refuel the tank when scooping
'reload costs': null,
'retrofit costs': 'Valeur de rachat', // The cost difference when upgrading / downgrading a component
'retrofit from': 'Racheter de', // Retrofit from Build A against build B
'T-Load': 'degrés', // Thermal load abbreviation
'total range': null,
'unit cost': null,
'utility mounts': 'Support utilitaire',
about: 'à propos', // Link to about page / about Coriolis.io about: 'à propos', // Link to about page / about Coriolis.io
added: 'ajouté', added: 'ajouté',
ammo: 'munition', // Ammunition ammo: 'munition', // Ammunition
armour: 'Armure', armour: 'Armure',
available: 'Disponibilité', available: 'Disponibilité', // Available options
backup: 'sauvegarde', backup: 'sauvegarde',
base: null, bays: 'baies',
bays: null,
bins: 'bacs', // Number of Mining Refinery bins bins: 'bacs', // Number of Mining Refinery bins
build: 'Configuration', // Shorthand for the build/configuration/design name build: 'Configuration', // Shorthand for the build/configuration/design name
'build name': 'Nom de la configuration', // Ship build/configuration/design name
builds: 'Configurations', // Ship build/configuration/design names builds: 'Configurations', // Ship build/configuration/design names
buy: 'Acheter', buy: 'Acheter',
cancel: 'Annuler', cancel: 'Annuler',
@@ -135,58 +58,77 @@ export const terms = {
cells: 'Cellule', // Number of cells in a shield cell bank cells: 'Cellule', // Number of cells in a shield cell bank
close: 'fermer', close: 'fermer',
compare: 'comparer', compare: 'comparer',
'compare all': 'tout comparer',
comparison: 'comparaison', comparison: 'comparaison',
comparisons: 'comparaisons', comparisons: 'comparaisons',
cost: 'coût', // Cost / price of a module or price of a ship cost: 'coût', // Cost / price of a module or price of a ship
costs: 'coûts', // Costs / prices of a modules or prices of ships costs: 'coûts', // Costs / prices of a modules or prices of ships
create: 'Créer', create: 'Créer',
'create new': 'Créer nouveau',
credits: 'crédits', credits: 'crédits',
damage: 'Dégâts', damage: 'dégât',
'damage per second': 'dégât par seconde',
delete: 'supprimer', delete: 'supprimer',
'delete all': 'tout supprimer',
dep: 'depl', // Weapons/Hardpoints Deployed abbreviation dep: 'depl', // Weapons/Hardpoints Deployed abbreviation
deployed: 'déployé', // Weapons/Hardpoints Deployed deployed: 'déployé', // Weapons/Hardpoints Deployed
'detailed export': 'export détaillé',
disabled: 'désactivé', disabled: 'désactivé',
discount: 'ristourne', discount: 'ristourne',
'edit data': 'Editer donnée',
efficiency: 'rendement', // Power Plant efficiency efficiency: 'rendement', // Power Plant efficiency
empty: 'Vide', empty: 'Vide',
Explorer: null, 'empty all': 'vide tout',
'Enter Name': 'Entrer nom',
Explorer: 'explorateur',
'fastest range': 'gamme la plus rapide', // Fastet totaljump range - sum of succesive jumps
fuel: 'carburant', fuel: 'carburant',
'fuel level': 'niveau de carburant', // Percent of fuel (T) in the tank
'full tank': 'Réservoir plein',
hardpoints: 'Points d\'emport', hardpoints: 'Points d\'emport',
hull: 'Coque', // Ships hull hull: 'Coque', // Ships hull
import: 'Importer', import: 'Importer',
insurance: 'Assurance', insurance: 'Assurance',
jump: null, // Single jump range 'internal compartments': 'compartiments internes',
jump: 'saut', // Single jump range
'jump range': 'Distance de saut',
jumps: 'Sauts', jumps: 'Sauts',
laden: 'chargé', laden: 'chargé',
language: 'Langage', language: 'Langage',
maneuverability: null, maneuverability: 'maniabilité',
manufacturer: null, manufacturer: 'fabricant',
mass: 'Masse', mass: 'Masse',
MLF: null, // Mass Lock Factor Abbreviation 'mass lock factor': 'facteur inhibition de masse',
MNV: null, // Maneuverability abbreviation 'max mass': 'masse max',
module: null, MLF: 'FIM', // Mass Lock Factor Abbreviation
modules: null, 'net cost': 'coûts nets',
no: 'non', no: 'non',
'none created': 'Rien de créé',
ok: 'D\'accord', ok: 'D\'accord',
optimize: null, 'optimal mass': 'masse optimale', // Lowest weight / best weight for jump distance, etc
optimize: 'optimiser',
pen: 'pén.', // Armour peneration abbreviation pen: 'pén.', // Armour peneration abbreviation
permalink: 'lien durable', permalink: 'lien durable',
power: 'énergie', // Power = Energy / second. Power generated by the power plant, or power consumed (MW / Mega Watts). Used in the power plant section power: 'énergie', // Power = Energy / second. Power generated by the power plant, or power consumed (MW / Mega Watts). Used in the power plant section
proceed: 'continuer', proceed: 'continuer',
PWR: 'P', // Power Abbreviation. See Power PWR: 'P', // Power Abbreviation. See Power
qty: null, // Quantity abbreviation qty: 'quantité', // Quantity abbreviation
range: 'portée', range: 'portée',
rate: 'cadence', rate: 'cadence',
recharge: null, // Shield Recharge time from 50% -> 100% recharge: 'recharger', // Shield Recharge time from 50% -> 100%
recovery: null, // Shield recovery time (after losing shields/turning on -> 50%) recovery: 'récupération', // Shield recovery time (after losing shields/turning on -> 50%)
'refuel time': 'Temps de remplissage', // Time to refuel the tank when scooping
reload: 'recharger', // Reload weapon/hardpoint reload: 'recharger', // Reload weapon/hardpoint
'reload costs': 'recharger coûts',
rename: 'renommer', rename: 'renommer',
repair: 'réparer', repair: 'réparer',
reset: 'Réinitialisation', reset: 'Réinitialisation',
ret: 'esc', // Retracted abbreviation ret: 'esc', // Retracted abbreviation
retracted: 'escamoté', // Weapons/Hardpoints retracted retracted: 'escamoté', // Weapons/Hardpoints retracted
'retrofit costs': 'Valeur de rachat', // The cost difference when upgrading / downgrading a component
'retrofit from': 'Racheter de', // Retrofit from Build A against build B
ROF: 'cadence', // Rate of Fire abbreviation ROF: 'cadence', // Rate of Fire abbreviation
roles: null, // Commander/Ship build roles - e.g. Trader, Bounty-Hunter, Explorer, etc roles: 'rôles', // Commander/Ship build roles - e.g. Trader, Bounty-Hunter, Explorer, etc
save: 'sauvegarder', save: 'sauvegarder',
sell: 'vendre', sell: 'vendre',
settings: 'paramètres', // Coriolis application settings settings: 'paramètres', // Coriolis application settings
@@ -198,12 +140,16 @@ export const terms = {
skip: 'Suivant', // Skip past something / ignore it skip: 'Suivant', // Skip past something / ignore it
speed: 'vitesse', speed: 'vitesse',
Stock: 'de base', // Thermal-load abbreviation Stock: 'de base', // Thermal-load abbreviation
strength: null, // Strength in reference to Shield Strength strength: 'force', // Strength in reference to Shield Strength
subtotal: null, subtotal: 'Sous-Total',
'T-Load': 'degrés', // Thermal load abbreviation
time: 'temps', // time it takes to complete something time: 'temps', // time it takes to complete something
tooltips: null, // Tooltips setting - show/hide tooltips: 'infobulles', // Tooltips setting - show/hide
Trader: null, // Trader role 'total range': 'plage totale',
Trader: 'commerçant', // Trader role
'unit cost': 'coût unitaire',
unladen: 'Non chargé', // No cargo or fuel unladen: 'Non chargé', // No cargo or fuel
'utility mounts': 'Support utilitaire',
WEP: 'ARM', // Abbreviation - Weapon recharge rate for power distributor WEP: 'ARM', // Abbreviation - Weapon recharge rate for power distributor
yes: 'oui' yes: 'oui'
}; };

View File

@@ -18,90 +18,22 @@ export const terms = {
PHRASE_BACKUP_DESC: 'Сохраните все данные перед переносом в другой браузер или устройство', // Backup of all Coriolis data to save or transfer to another browser/device PHRASE_BACKUP_DESC: 'Сохраните все данные перед переносом в другой браузер или устройство', // Backup of all Coriolis data to save or transfer to another browser/device
PHRASE_CONFIRMATION: 'Вы уверены?', // Are You Sure? PHRASE_CONFIRMATION: 'Вы уверены?', // Are You Sure?
PHRASE_EXPORT_DESC: 'Детальный JSON-экспорт вашей сборки для использования в других местах и инструментах', // A detailed JSON export of your build for use in other sites and tools PHRASE_EXPORT_DESC: 'Детальный JSON-экспорт вашей сборки для использования в других местах и инструментах', // A detailed JSON export of your build for use in other sites and tools
PHRASE_FASTEST_RANGE: null, // Consecutive max range jumps PHRASE_FASTEST_RANGE: 'Последовательные прыжки максимальной дальности', // Consecutive max range jumps
PHRASE_IMPORT: 'Для импорта вставьте код в эту форму', // Paste JSON or import here PHRASE_IMPORT: 'Для импорта вставьте код в эту форму', // Paste JSON or import here
PHRASE_LADEN: null, // Ship Mass + Fuel + Cargo PHRASE_LADEN: 'Масса корабля с учётом топлива и грузов', // Ship Mass + Fuel + Cargo
PHRASE_NO_BUILDS: 'Нечего сравнивать', // No builds added to comparison! PHRASE_NO_BUILDS: 'Нечего сравнивать', // No builds added to comparison!
PHRASE_NO_RETROCH: 'нет ранних версий сборкиконфигурации', // No Retrofitting changes PHRASE_NO_RETROCH: 'нет ранних версий сборки\\конфигурации', // No Retrofitting changes
PHRASE_SELECT_BUILDS: 'Выберите конфигурацию для сравнения', // Select Builds to Compare PHRASE_SELECT_BUILDS: 'Выберите конфигурацию для сравнения', // Select Builds to Compare
PHRASE_SG_RECHARGE: null, // Time from 50% to 100% Charge PHRASE_SG_RECHARGE: 'восстановление с 60% до 100% объема щита', // Time from 50% to 100% Charge
PHRASE_SG_RECOVER: null, // Recovery (to 50%) after collapse PHRASE_SG_RECOVER: 'восстановление [до 60%] после снятия щита', // Recovery (to 50%) after collapse
PHRASE_UNLADEN: null, // Ship Mass excluding Fuel and Cargo PHRASE_UNLADEN: 'Масса корабля без учета топлива и грузов', // Ship Mass excluding Fuel and Cargo
PHRASE_UPDATE_RDY: 'Доступно обновление. Нажмите для обновления.', // Update Available! Click to Refresh PHRASE_UPDATE_RDY: 'Доступно обновление. Нажмите для обновления.', // Update Available! Click to Refresh
// Modules / Module Types - These should match the in-game translation (if any)
'Advanced Discovery Scanner': null, // Advanced Discovery Scanner
'Basic Discovery Scanner': 'Стандартный исследовательский сканер', // Basic Discovery Scanner
'Cargo Hatch': 'Грузовой люк', // Cargo Hatch
'Chaff Launcher': 'Постановщик помех', // Chaff Launcher
'Detailed Surface Scanner': 'Подробный сканер поверхности', // Detailed Surface Scanner
'Electronic Countermeasure': 'Электронная противомера', // Electronic Countermeasure
'Heat Sink Launcher': 'Теплоотводная ПУ', // Heat Sink Launcher
'Intermediate Discovery Scanner': 'Средний исследовательский сканер', // Intermediate Discovery Scanner
'Point Defence': 'Противоракетная защита',
'Standard Docking Computer': 'Стандартный стыковочный компьютер', // Standard Docking Computer
am: 'Ремонтный модуль', // Auto Field-Maintenance Unit
bh: 'Корпус', // Bulkheads
bl: 'Лучевой лазер', // Beam Laser
bsg: null, // Bi-Weave Shield Generator
c: 'Пушка', // Cannon
cc: 'Контроллер "дрон-сборщик"', // Collector Limpet Controller
cm: 'Контрмеры', // Countermeasure
cr: 'Грузовой отсек', // Cargo Rack
cs: 'Сканер груза', // Cargo Scanner
dc: 'Стыковочный компьютер', // Docking Computer
fc: 'Осколочное Орудие', // Fragment Cannon
fi: 'Перехватчик FSD', // FSD Interdictor
fs: 'Топливосборщик', // Fuel Scoop
fsd: 'Двигатель FSD', // Frame Shift Drive
ft: 'Топливный бак', // Fuel Tank
fx: 'Контроллер "Дрон-заправщик"', // Fuel Transfer Limpet Controller
hb: 'Контроллер "дрон-взломщик"', // Hatch Breaker Limpet Controller
hr: 'Набор усиления корпуса', // Hull Reinforcement Package
kw: 'Полицейский сканер', // Kill Warrant Scanner
ls: 'Система жизнеобеспечения', // life support
mc: 'Многоствольное орудие', // Multi-cannon
ml: 'Бурильный лазер', // Mining Laser
mr: 'Ракетная установка', // Missile Rack
nl: 'Минноукладчик', // Mine Launcher
pa: 'Ускоритель плазмы', // Plasma Accelerator
pas: null, // Planetary Approach Suite
pc: 'Контроллер "Дрон-исследователь"', // Prospector Limpet Controller
pd: 'Распределитель энергии', // power distributor
pl: 'Импульсный лазер', // Pulse Laser
pp: 'Реактор', // Power Plant
psg: 'Генератор призматического щита', // Prismatic Shield Generator
pv: null, // Planetary Vehicle Hanger
rf: 'Переработка', // Refinery
rg: 'Рельсотрон', // Rail Gun
s: 'Сенсоры', // Sensors
sb: 'Усилитель щита', // Shield Booster
sc: 'Сканер', // Scanner
scb: 'Батареи перезарядки щита', // Shield Cell Bank
sg: 'Генератор щита', // Shield Generator
t: 'Двигатели', // Thrusters
tp: 'Торпедный аппарат', // Torpedo Pylon
ul: 'Мультиимпульсный лазер', // Burst Laser
ws: 'FSD Сканнер', // Frame Shift Wake Scanner
// Bulkheads - These should match the in-game translation (if any)
'Lightweight Alloy': 'Легкий сплав',
'Reinforced Alloy': 'Усиленный сплав',
'Military Grade Composite': 'Военный композит',
'Mirrored Surface Composite': 'Зеркальный композит',
'Reactive Surface Composite': 'Динамическая защита',
// Units / Metrics // Units / Metrics
'/min': null, // Per minute '/s': '/с', // Per second
'/s': null, // Per second
kg: null, // Kilogram
'kg/s': null, // Kilograms per second
km: null, // Kilometer
'm/s': 'м/с', // Meters / Second 'm/s': 'м/с', // Meters / Second
Ls: 'Св.сек', // Light seconds Ls: 'Св.сек', // Light seconds
LY: 'Св.лет', // Light Years LY: 'Св.лет', // Light Years
MJ: null, // Mega Joules
MW: null, // Mega Watts
CR: 'кр.', // Credits abbreviation CR: 'кр.', // Credits abbreviation
// Sizes // Sizes
@@ -110,24 +42,25 @@ export const terms = {
L: 'б', // Large Hardpoint size (single character) L: 'б', // Large Hardpoint size (single character)
H: 'O', // Huge Hardpoint size (single character) H: 'O', // Huge Hardpoint size (single character)
U: 'B', // Utility Hardpoint size (single character) - Kill warrant scanner, etc U: 'B', // Utility Hardpoint size (single character) - Kill warrant scanner, etc
small: 'Малый', // Small ship size
medium: 'Средний', // Medium ship size
large: 'большой', // Large Ship Size
// Insurance // Insurance
alpha: 'Альфа', // Alpha backer insurance level alpha: 'Альфа', // Alpha backer insurance level
beta: 'Бета', // Beta back insurance level beta: 'Бета', // Beta back insurance level
standard: 'Стандартный', // Standard insurance level standard: 'Стандартный', // Standard insurance level
// Terms // Terms
'build name': 'название сборки', // Ship build/configuration/design name 'build name': 'название сборки', // Ship build/configuration/design name
'compare all': 'сравнить все', 'compare all': 'сравнить все',
'create new': 'Создать новый', 'create new': 'Создать новый',
'damage per second': null, 'damage per second': 'урон в секунду',
'delete all': 'Удалить все', 'delete all': 'Удалить все',
'detailed export': 'Подробный экспорт', 'detailed export': 'Подробный экспорт',
'edit data': 'Редактирование', 'edit data': 'Редактирование',
'empty all': null, 'empty all': 'пусто все',
'Enter Name': 'Введите имя', 'Enter Name': 'Введите имя',
'fastest range': null, // Fastet totaljump range - sum of succesive jumps 'fastest range': 'быстрый диапазон', // Fastet totaljump range - sum of succesive jumps
'fuel level': null, // Percent of fuel (T) in the tank 'fuel level': 'уровень топлива', // Percent of fuel (T) in the tank
'full tank': 'Полный бак', 'full tank': 'Полный бак',
'internal compartments': 'внутренние отсеки', 'internal compartments': 'внутренние отсеки',
'jump range': 'Дальность прыжка', 'jump range': 'Дальность прыжка',
@@ -135,24 +68,18 @@ export const terms = {
'max mass': 'Максимальная масса', 'max mass': 'Максимальная масса',
'net cost': 'разница в цене', 'net cost': 'разница в цене',
'none created': 'не создано', 'none created': 'не создано',
'optimal mass': null, // Lowest weight / best weight for jump distance, etc
'refuel time': 'Время дозаправки', // Time to refuel the tank when scooping 'refuel time': 'Время дозаправки', // Time to refuel the tank when scooping
'reload costs': null,
'retrofit costs': 'цена модификации', // The cost difference when upgrading / downgrading a component 'retrofit costs': 'цена модификации', // The cost difference when upgrading / downgrading a component
'retrofit from': 'модификация от', // Retrofit from Build A against build B 'retrofit from': 'модификация от', // Retrofit from Build A against build B
'T-Load': 'Тепл.', // Thermal load abbreviation 'T-Load': 'Тепл.', // Thermal load abbreviation
'total range': null,
'unit cost': null,
'utility mounts': 'Вспомогательное оборудование', 'utility mounts': 'Вспомогательное оборудование',
about: 'О ...', // Link to about page / about Coriolis.io about: 'О ...', // Link to about page / about Coriolis.io
action: 'Действие', action: 'Действие',
added: 'Добавлено', added: 'Добавлено',
ammo: 'Боекомплект', // Ammunition ammo: 'Боекомплект', // Ammunition
armour: 'Броня', armour: 'Броня',
available: 'доступно', available: 'доступно', // Available options
backup: 'Резервная копия', backup: 'Резервная копия',
base: null,
bays: null,
bins: 'контейнеры', // Number of Mining Refinery bins bins: 'контейнеры', // Number of Mining Refinery bins
boost: 'форсаж', boost: 'форсаж',
build: 'cборка', // Shorthand for the build/configuration/design name build: 'cборка', // Shorthand for the build/configuration/design name
@@ -179,7 +106,6 @@ export const terms = {
efficiency: 'Эффективность', // Power Plant efficiency efficiency: 'Эффективность', // Power Plant efficiency
empty: 'пусто', empty: 'пусто',
ENG: 'ДВГ', // Abbreviation - Engine recharge rate for power distributor ENG: 'ДВГ', // Abbreviation - Engine recharge rate for power distributor
Explorer: null,
export: 'Экспорт', export: 'Экспорт',
forum: 'Форум', forum: 'Форум',
fuel: 'Топливо', fuel: 'Топливо',
@@ -187,32 +113,26 @@ export const terms = {
hull: 'Корпус', // Ships hull hull: 'Корпус', // Ships hull
import: 'импортировать ', import: 'импортировать ',
insurance: 'Страховка', insurance: 'Страховка',
jump: null, // Single jump range
jumps: 'Прыжков', jumps: 'Прыжков',
laden: 'Груженый', laden: 'Груженый',
language: 'Язык', language: 'Язык',
maneuverability: 'Моневренность', maneuverability: 'Маневренность',
manufacturer: null,
mass: 'Масса', mass: 'Масса',
max: 'Макс', max: 'Макс',
MLF: null, // Mass Lock Factor Abbreviation
MNV: null, // Maneuverability abbreviation
module: null,
modules: null,
no: 'Нет', no: 'Нет',
ok: null,
optimize: null,
pen: 'ПБ', // Armour peneration abbreviation pen: 'ПБ', // Armour peneration abbreviation
permalink: 'Постоянная ссылка', permalink: 'Постоянная ссылка',
power: 'Мощность', // Power = Energy / second. Power generated by the power plant, or power consumed (MW / Mega Watts). Used in the power plant section power: 'Мощность', // Power = Energy / second. Power generated by the power plant, or power consumed (MW / Mega Watts). Used in the power plant section
pri: 'Осн', // Priority abbreviation for power management pri: 'Осн', // Priority abbreviation for power management
proceed: 'продолжить', proceed: 'продолжить',
PWR: 'Эн', // Power Abbreviation. See Power PWR: 'Эн', // Power Abbreviation. See Power
qty: null, // Quantity abbreviation
range: 'Дальность', range: 'Дальность',
rate: 'скорость', rate: 'скорость',
recharge: null, // Shield Recharge time from 50% -> 100% recharge: 'перезарядка', // Shield Recharge time from 50% -> 100%
recovery: null, // Shield recovery time (after losing shields/turning on -> 50%) recovery: 'включение', // Shield recovery time (after losing shields/turning on -> 50%)
reload: 'Перезагрузить', // Reload weapon/hardpoint reload: 'Перезагрузить', // Reload weapon/hardpoint
rename: 'Переименовать', rename: 'Переименовать',
repair: 'Починка', repair: 'Починка',
@@ -220,7 +140,7 @@ export const terms = {
ret: 'Убр.', // Retracted abbreviation ret: 'Убр.', // Retracted abbreviation
retracted: 'Убрано', // Weapons/Hardpoints retracted retracted: 'Убрано', // Weapons/Hardpoints retracted
ROF: 'В/сек', // Rate of Fire abbreviation ROF: 'В/сек', // Rate of Fire abbreviation
roles: null, // Commander/Ship build roles - e.g. Trader, Bounty-Hunter, Explorer, etc
save: 'Сохранить', save: 'Сохранить',
sell: 'Продать', sell: 'Продать',
settings: 'Настройки', // Coriolis application settings settings: 'Настройки', // Coriolis application settings
@@ -233,13 +153,9 @@ export const terms = {
speed: 'скорость', speed: 'скорость',
standard: 'Стандартный', // Standard / Common modules (FSD, power plant, life support, etc) standard: 'Стандартный', // Standard / Common modules (FSD, power plant, life support, etc)
Stock: 'Стандартная комплектация', // Thermal-load abbreviation Stock: 'Стандартная комплектация', // Thermal-load abbreviation
strength: null, // Strength in reference to Shield Strength
subtotal: null,
SYS: 'СИСТЕМЫ', // Abbreviation - System recharge rate for power distributor SYS: 'СИСТЕМЫ', // Abbreviation - System recharge rate for power distributor
time: 'Время', // time it takes to complete something time: 'Время', // time it takes to complete something
tooltips: null, // Tooltips setting - show/hide
total: 'Всего', total: 'Всего',
Trader: null, // Trader role
type: 'Тип', type: 'Тип',
unladen: 'Пустой', // No cargo or fuel unladen: 'Пустой', // No cargo or fuel
URL: 'Ссылка', // Link, Uniform Resource Locator URL: 'Ссылка', // Link, Uniform Resource Locator

View File

@@ -24,15 +24,17 @@ export default class AboutPage extends Page {
*/ */
renderPage() { renderPage() {
return <div className={'page'} style={{ textAlign: 'left', maxWidth: 800, margin: '0 auto' }}> return <div className={'page'} style={{ textAlign: 'left', maxWidth: 800, margin: '0 auto' }}>
<h1><CoriolisLogo style={{ marginRight: '0.4em' }} className='xl'/><span className='warning'>Coriolis</span></h1> <h1><CoriolisLogo style={{ marginRight: '0.4em' }} className='xl'/><span className='warning'>Coriolis EDCD Edition</span></h1>
<p>This is a clone of the Coriolis project, whose original author is currently unable to maintain it. This clone is maintained by the <a href="http://edcd.github.io/">EDCD community</a>.</p>
<p>To recover your builds, go to <a href='https://coriolis.io/' target='_blank'>https://coriolis.io/</a>, backup your builds (Settings / Backup), copy the text, return here and import (Settings / Import).</p>
<p>The Coriolis project was inspired by <a href='http://www.edshipyard.com/' target='_blank'>E:D Shipyard</a> and, of course, <a href='http://www.elitedangerous.com' target='_blank'>Elite Dangerous</a>. The ultimate goal of Coriolis is to provide rich features to support in-game play and planning while engaging the E:D community to support its development.</p> <p>The Coriolis project was inspired by <a href='http://www.edshipyard.com/' target='_blank'>E:D Shipyard</a> and, of course, <a href='http://www.elitedangerous.com' target='_blank'>Elite Dangerous</a>. The ultimate goal of Coriolis is to provide rich features to support in-game play and planning while engaging the E:D community to support its development.</p>
<p>Coriolis was created using assets and imagery from Elite: Dangerous, with the permission of Frontier Developments plc, for non-commercial purposes. It is not endorsed by nor reflects the views or opinions of Frontier Developments. A number of assets were sourced from <a href='http://edassets.org' target='_blank'>ED Assets</a></p>
<p>Coriolis was created using assets and imagery from Elite: Dangerous, with the permission of Frontier Developments plc, for non-commercial purposes. It is not endorsed by nor reflects the views or opinions of Frontier Developments.</p> <a style={{ display: 'block', textDecoration: 'none' }} href='https://github.com/EDCD/coriolis' target='_blank' title='Coriolis Github Project'>
<a style={{ display: 'block', textDecoration: 'none' }} href='https://github.com/cmmcleod/coriolis' target='_blank' title='Coriolis Github Project'>
<GitHub style={{ margin: '0.4em' }} className='l fg xl'/> <GitHub style={{ margin: '0.4em' }} className='l fg xl'/>
<h2 style={{ margin: 0, textDecoration: 'none' }}>Github</h2> <h2 style={{ margin: 0, textDecoration: 'none' }}>Github</h2>
github.com/cmmcleod/coriolis github.com/EDCD/coriolis
</a> </a>
<p>Coriolis is an open source project. Checkout the list of upcoming features and to-do list on github. Any and all contributions and feedback are welcome. If you encounter any bugs please report them and provide as much detail as possible.</p> <p>Coriolis is an open source project. Checkout the list of upcoming features and to-do list on github. Any and all contributions and feedback are welcome. If you encounter any bugs please report them and provide as much detail as possible.</p>
@@ -47,7 +49,7 @@ export default class AboutPage extends Page {
<p>Help keep the lights on! Donations will be used to cover costs of running and maintaining Coriolis. Thanks for helping!</p> <p>Help keep the lights on! Donations will be used to cover costs of running and maintaining Coriolis. Thanks for helping!</p>
<h3>Chat</h3> <h3>Chat</h3>
<p>You can chat to us on <a href='https://www.hipchat.com/gfYQiZcmy' target='_blank'>HipChat</a> and sign up to chat on all E:D HipChat groups <a href='https://elite-dangerous.hipchat.com/invite/74670/a3af7ea57008362d83d05fada09bdf84' target='_blank'>here</a>.</p> <p>You can chat to us on our <a href='https://discord.gg/0uwCh6R62aPRjk9w' target='_blank'>EDCD Discord server</a>.</p>
</div>; </div>;
} }
} }

View File

@@ -53,6 +53,7 @@ export default class ComparisonPage extends Page {
super(props, context); super(props, context);
this._sortShips = this._sortShips.bind(this); this._sortShips = this._sortShips.bind(this);
this._buildsSelected = this._buildsSelected.bind(this); this._buildsSelected = this._buildsSelected.bind(this);
this._updateDiscounts = this._updateDiscounts.bind(this);
this.state = this._initState(context); this.state = this._initState(context);
} }
@@ -62,7 +63,7 @@ export default class ComparisonPage extends Page {
* @return {Object} New state object * @return {Object} New state object
*/ */
_initState(context) { _initState(context) {
let defaultFacets = [9, 6, 4, 1, 3, 2]; // Reverse order of Armour, Shields, Speed, Jump Range, Cargo Capacity, Cost let defaultFacets = [13, 12, 11, 9, 6, 4, 1, 3, 2]; // Reverse order of Armour, Shields, Speed, Jump Range, Cargo Capacity, Cost, DPS, EPS, HPS
let params = context.route.params; let params = context.route.params;
let code = params.code; let code = params.code;
let name = params.name ? decodeURIComponent(params.name) : null; let name = params.name ? decodeURIComponent(params.name) : null;
@@ -130,7 +131,7 @@ export default class ComparisonPage extends Page {
builds.sort(sortBy(predicate)); builds.sort(sortBy(predicate));
return { return {
title: 'Coriolis - Compare', title: 'Coriolis EDCD Edition - Compare',
predicate, predicate,
desc, desc,
facets, facets,
@@ -161,6 +162,7 @@ export default class ComparisonPage extends Page {
let b = new Ship(id, data.properties, data.slots); // Create a new Ship instance let b = new Ship(id, data.properties, data.slots); // Create a new Ship instance
b.buildFrom(code); // Populate components from code b.buildFrom(code); // Populate components from code
b.buildName = name; b.buildName = name;
b.applyDiscounts(Persist.getShipDiscount(), Persist.getModuleDiscount());
return b; return b;
}; };
@@ -383,6 +385,21 @@ export default class ComparisonPage extends Page {
}); });
} }
/**
* Update all ship costs on disount change
*/
_updateDiscounts() {
let shipDiscount = Persist.getShipDiscount();
let moduleDiscount = Persist.getModuleDiscount();
let builds = [];
for (let b of this.state.builds) {
builds.push(b.applyDiscounts(shipDiscount, moduleDiscount));
}
this.setState({ builds });
}
/** /**
* Update state based on context changes * Update state based on context changes
* @param {Object} nextProps Incoming/Next properties * @param {Object} nextProps Incoming/Next properties
@@ -399,6 +416,7 @@ export default class ComparisonPage extends Page {
*/ */
componentWillMount() { componentWillMount() {
this.resizeListener = this.context.onWindowResize(this._updateDimensions); this.resizeListener = this.context.onWindowResize(this._updateDimensions);
this.persistListener = Persist.addListener('discounts', this._updateDiscounts);
} }
/** /**
@@ -413,6 +431,7 @@ export default class ComparisonPage extends Page {
*/ */
componentWillUnmount() { componentWillUnmount() {
this.resizeListener.remove(); this.resizeListener.remove();
this.persistListener.remove();
} }
/** /**

View File

@@ -24,6 +24,16 @@ import Slider from '../components/Slider';
const SPEED_SERIES = ['boost', '4 Pips', '2 Pips', '0 Pips']; const SPEED_SERIES = ['boost', '4 Pips', '2 Pips', '0 Pips'];
const SPEED_COLORS = ['#0088d2', '#ff8c0d', '#D26D00', '#c06400']; const SPEED_COLORS = ['#0088d2', '#ff8c0d', '#D26D00', '#c06400'];
/**
* Document Title Generator
* @param {String} shipName Ship Name
* @param {String} buildName Build Name
* @return {String} Document title
*/
function getTitle(shipName, buildName) {
return `${shipName}${buildName ? ` - ${buildName}` : ''}`;
}
/** /**
* The Outfitting Page * The Outfitting Page
*/ */
@@ -65,10 +75,11 @@ export default class OutfittingPage extends Page {
} }
let fuelCapacity = ship.fuelCapacity; let fuelCapacity = ship.fuelCapacity;
this._getTitle = getTitle.bind(this, data.properties.name);
return { return {
error: null, error: null,
title: 'Outfitting - ' + data.properties.name, title: this._getTitle(buildName),
costTab: Persist.getCostTab() || 'costs', costTab: Persist.getCostTab() || 'costs',
buildName, buildName,
newBuildName: buildName, newBuildName: buildName,
@@ -117,7 +128,7 @@ export default class OutfittingPage extends Page {
this._updateRoute(shipId, newBuildName, code); this._updateRoute(shipId, newBuildName, code);
} }
this.setState({ buildName: newBuildName, code, savedCode: code }); this.setState({ buildName: newBuildName, code, savedCode: code, title: this._getTitle(newBuildName) });
} }
/** /**
@@ -273,16 +284,16 @@ export default class OutfittingPage extends Page {
canSave = (newBuildName || buildName) && code !== savedCode, canSave = (newBuildName || buildName) && code !== savedCode,
canRename = buildName && newBuildName && buildName != newBuildName, canRename = buildName && newBuildName && buildName != newBuildName,
canReload = savedCode && canSave, canReload = savedCode && canSave,
hStr = ship.getHardpointsString(), hStr = ship.getHardpointsString() + '.' + ship.getModificationsString(),
sStr = ship.getStandardString(), sStr = ship.getStandardString() + '.' + ship.getModificationsString(),
iStr = ship.getInternalString(); iStr = ship.getInternalString() + '.' + ship.getModificationsString();
return ( return (
<div id='outfit' className={'page'} style={{ fontSize: (sizeRatio * 0.9) + 'em' }}> <div id='outfit' className={'page'} style={{ fontSize: (sizeRatio * 0.9) + 'em' }}>
<div id='overview'> <div id='overview'>
<h1>{ship.name}</h1> <h1>{ship.name}</h1>
<div id='build'> <div id='build'>
<input value={newBuildName} onChange={this._buildNameChange} placeholder={translate('Enter Name')} maxsize={50} /> <input value={newBuildName} onChange={this._buildNameChange} placeholder={translate('Enter Name')} maxLength={50} />
<button onClick={canSave && this._saveBuild} disabled={!canSave} onMouseOver={termtip.bind(null, 'save')} onMouseOut={hide}> <button onClick={canSave && this._saveBuild} disabled={!canSave} onMouseOver={termtip.bind(null, 'save')} onMouseOut={hide}>
<FloppyDisk className='lg' /> <FloppyDisk className='lg' />
</button> </button>

View File

@@ -8,17 +8,18 @@ import { shallowEqual } from '../utils/UtilityFunctions';
export default class Page extends React.Component { export default class Page extends React.Component {
static contextTypes = { static contextTypes = {
route: React.PropTypes.object.isRequired,
language: React.PropTypes.object.isRequired,
sizeRatio: React.PropTypes.number.isRequired,
openMenu: React.PropTypes.func.isRequired,
closeMenu: React.PropTypes.func.isRequired, closeMenu: React.PropTypes.func.isRequired,
showModal: React.PropTypes.func.isRequired,
hideModal: React.PropTypes.func.isRequired, hideModal: React.PropTypes.func.isRequired,
tooltip: React.PropTypes.func.isRequired, language: React.PropTypes.object.isRequired,
termtip: React.PropTypes.func.isRequired, noTouch: React.PropTypes.bool.isRequired,
onCommand: React.PropTypes.func.isRequired,
onWindowResize: React.PropTypes.func.isRequired, onWindowResize: React.PropTypes.func.isRequired,
onCommand: React.PropTypes.func.isRequired openMenu: React.PropTypes.func.isRequired,
route: React.PropTypes.object.isRequired,
showModal: React.PropTypes.func.isRequired,
sizeRatio: React.PropTypes.number.isRequired,
termtip: React.PropTypes.func.isRequired,
tooltip: React.PropTypes.func.isRequired
}; };
static propTypes = { static propTypes = {

167
src/app/pages/ShipyardPage.jsx Executable file → Normal file
View File

@@ -27,6 +27,38 @@ function countInt(slot) {
this.maxCargo += crEligible ? ModuleUtils.findInternal('cr', slot.maxClass, 'E').cargo : 0; this.maxCargo += crEligible ? ModuleUtils.findInternal('cr', slot.maxClass, 'E').cargo : 0;
} }
/**
* Generate Ship summary and aggregated properties
* @param {String} shipId Ship Id
* @param {Object} shipData Ship Default Data
* @return {Object} Ship summary and aggregated properties
*/
function shipSummary(shipId, shipData) {
let summary = {
id: shipId,
hpCount: 0,
intCount: 0,
maxCargo: 0,
hp: [0, 0, 0, 0, 0], // Utility, Small, Medium, Large, Huge
int: [0, 0, 0, 0, 0, 0, 0, 0] // Sizes 1 - 8
};
Object.assign(summary, shipData.properties);
let ship = new Ship(shipId, shipData.properties, shipData.slots);
// Build Ship
ship.buildWith(shipData.defaults); // Populate with stock/default components
ship.hardpoints.forEach(countHp.bind(summary)); // Count Hardpoints by class
ship.internal.forEach(countInt.bind(summary)); // Count Internal Compartments by class
summary.retailCost = ship.totalCost; // Record Stock/Default/retail cost
ship.optimizeMass({ pd: '1D' }); // Optimize Mass with 1D PD for maximum possible jump range
summary.maxJumpRange = ship.unladenRange; // Record Jump Range
ship.optimizeMass({ th: ship.standard[1].maxClass + 'A', fsd: '2D', ft: '1C' }); // Optmize mass with Max Thrusters
summary.topSpeed = ship.topSpeed;
summary.topBoost = ship.topBoost;
return summary;
}
/** /**
* The Shipyard summary page * The Shipyard summary page
*/ */
@@ -41,20 +73,30 @@ export default class ShipyardPage extends Page {
*/ */
constructor(props, context) { constructor(props, context) {
super(props, context); super(props, context);
this.state = {
title: 'Coriolis - Shipyard',
shipPredicate: 'name',
shipDesc: true
};
if (!ShipyardPage.cachedShipSummaries) { if (!ShipyardPage.cachedShipSummaries) {
ShipyardPage.cachedShipSummaries = []; ShipyardPage.cachedShipSummaries = [];
for (let s in Ships) { for (let s in Ships) {
ShipyardPage.cachedShipSummaries.push(this._shipSummary(s, Ships[s])); ShipyardPage.cachedShipSummaries.push(shipSummary(s, Ships[s]));
} }
} }
this.shipSummaries = ShipyardPage.cachedShipSummaries; this.state = {
title: 'Coriolis EDCD Edition - Shipyard',
shipPredicate: 'name',
shipDesc: true,
shipSummaries: ShipyardPage.cachedShipSummaries
};
}
/**
* Higlight the current ship in the table
* @param {String} shipId Ship Id
* @param {SyntheticEvent} event Event
*/
_highlightShip(shipId, event) {
event.stopPropagation();
this.setState({ shipId });
} }
/** /**
@@ -76,38 +118,6 @@ export default class ShipyardPage extends Page {
this.setState({ shipPredicate, shipDesc, shipPredicateIndex }); this.setState({ shipPredicate, shipDesc, shipPredicateIndex });
}; };
/**
* Generate Ship summary and aggregated properties
* @param {String} shipId Ship Id
* @param {Object} shipData Ship Default Data
* @return {Object} Ship summary and aggregated properties
*/
_shipSummary(shipId, shipData) {
let summary = {
id: shipId,
hpCount: 0,
intCount: 0,
maxCargo: 0,
hp: [0, 0, 0, 0, 0], // Utility, Small, Medium, Large, Huge
int: [0, 0, 0, 0, 0, 0, 0, 0] // Sizes 1 - 8
};
Object.assign(summary, shipData.properties);
let ship = new Ship(shipId, shipData.properties, shipData.slots);
// Build Ship
ship.buildWith(shipData.defaults); // Populate with stock/default components
ship.hardpoints.forEach(countHp.bind(summary)); // Count Hardpoints by class
ship.internal.forEach(countInt.bind(summary)); // Count Internal Compartments by class
summary.retailCost = ship.totalCost; // Record Stock/Default/retail cost
ship.optimizeMass({ pd: '1D' }); // Optimize Mass with 1D PD for maximum possible jump range
summary.maxJumpRange = ship.unladenRange; // Record Jump Range
ship.optimizeMass({ th: ship.standard[1].maxClass + 'A' }); // Optmize mass with Max Thrusters
summary.topSpeed = ship.topSpeed;
summary.topBoost = ship.topBoost;
return summary;
}
/** /**
* Generate the table row summary for the ship * Generate the table row summary for the ship
* @param {Object} s Ship summary * @param {Object} s Ship summary
@@ -118,8 +128,14 @@ export default class ShipyardPage extends Page {
* @return {React.Component} Table Row * @return {React.Component} Table Row
*/ */
_shipRowElement(s, translate, u, fInt, fRound) { _shipRowElement(s, translate, u, fInt, fRound) {
return <tr key={s.id} className='highlight'> let noTouch = this.context.noTouch;
<td className='le'><Link href={'/outfit/' + s.id}>{s.name}</Link></td>
return <tr
key={s.id}
style={{ height: '1.5em' }}
className={cn({ highlighted: noTouch && this.state.shipId === s.id })}
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
>
<td className='le'>{s.manufacturer}</td> <td className='le'>{s.manufacturer}</td>
<td className='cap'>{translate(SizeMap[s.class])}</td> <td className='cap'>{translate(SizeMap[s.class])}</td>
<td>{s.agility}</td> <td>{s.agility}</td>
@@ -155,17 +171,27 @@ export default class ShipyardPage extends Page {
* @return {React.Component} The page contents * @return {React.Component} The page contents
*/ */
renderPage() { renderPage() {
let { translate, formats, units } = this.context.language; let { sizeRatio, language, termtip, noTouch } = this.context;
let { translate, formats, units } = language;
let hide = this.context.tooltip.bind(null, null);
let fInt = formats.int; let fInt = formats.int;
let fRound = formats.round; let fRound = formats.round;
let shipSummaries = this.shipSummaries; let { shipSummaries, shipPredicate, shipPredicateIndex } = this.state;
let shipPredicate = this.state.shipPredicate;
let shipPredicateIndex = this.state.shipPredicateIndex;
let shipRows = [];
let hide = this.context.tooltip.bind(null, null);
let tip = this.context.termtip;
let sortShips = (predicate, index) => this._sortShips.bind(this, predicate, index); let sortShips = (predicate, index) => this._sortShips.bind(this, predicate, index);
let filters = {
// 'class': { 1: 1, 2: 1}
};
shipSummaries = shipSummaries.filter(s => {
for (let prop in filters) {
if (!(s[prop] in filters[prop])) {
return false;
}
}
return true;
});
// Sort shipsOverview // Sort shipsOverview
shipSummaries.sort((a, b) => { shipSummaries.sort((a, b) => {
let valA = a[shipPredicate], valB = b[shipPredicate]; let valA = a[shipPredicate], valB = b[shipPredicate];
@@ -194,26 +220,52 @@ export default class ShipyardPage extends Page {
} }
}); });
let i = 0;
let shipRows = new Array(shipSummaries.length);
let detailRows = new Array(shipSummaries.length);
for (let s of shipSummaries) { for (let s of shipSummaries) {
shipRows.push(this._shipRowElement(s, translate, units, fInt, fRound)); detailRows[i] = this._shipRowElement(s, translate, units, fInt, fRound);
shipRows[i] = (
<tr
key={i}
style={{ height: '1.5em' }}
className={cn({ highlighted: noTouch && this.state.shipId === s.id })}
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
>
<td className='le'><Link href={'/outfit/' + s.id}>{s.name}</Link></td>
</tr>
);
i++;
} }
return ( return (
<div className='page' style={{ fontSize: this.context.sizeRatio + 'em' }}> <div className='page' style={{ fontSize: sizeRatio + 'em' }}>
<div className='scroll-x'> <p style={{ textAlign: 'center' }}>This is <strong>Coriolis EDCD Edition</strong> - a temporary clone of <a href='https://coriolis.io/' target='_blank'>https://coriolis.io/</a> with added support for E:D 2.2. For more info see Settings / <Link href="/about" className='block'>About</Link></p>
<table style={{ fontSize:'0.85em', whiteSpace:'nowrap', margin: '0 auto' }} align='center'> <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 className='main'>
<th style={{ height: '2.6em', padding: '2px 0.4em 1px' }} className='sortable le rgt' onClick={sortShips('name')}>{translate('ship')}</th>
</tr>
</thead>
<tbody onMouseLeave={this._highlightShip.bind(this, null)}>
{shipRows}
</tbody>
</table>
<div style={{ overflowX: 'scroll', maxWidth: '100%' }}>
<table style={{ marginLeft: 'calc(12em - 1px)', zIndex: 0 }}>
<thead> <thead>
<tr className='main'> <tr className='main'>
<th rowSpan={2} className='sortable le' onClick={sortShips('name')}>{translate('ship')}</th>
<th rowSpan={2} className='sortable' onClick={sortShips('manufacturer')}>{translate('manufacturer')}</th> <th rowSpan={2} className='sortable' onClick={sortShips('manufacturer')}>{translate('manufacturer')}</th>
<th rowSpan={2} className='sortable' onClick={sortShips('class')}>{translate('size')}</th> <th rowSpan={2} className='sortable' onClick={sortShips('class')}>{translate('size')}</th>
<th rowSpan={2} className='sortable' onMouseEnter={tip.bind(null, 'maneuverability')} onMouseLeave={hide} onClick={sortShips('agility')}>{translate('mnv')}</th> <th rowSpan={2} className='sortable' onMouseEnter={termtip.bind(null, 'maneuverability')} onMouseLeave={hide} onClick={sortShips('agility')}>{translate('mnv')}</th>
<th colSpan={4}>{translate('base')}</th> <th colSpan={4}>{translate('base')}</th>
<th colSpan={4}>{translate('max')}</th> <th colSpan={4}>{translate('max')}</th>
<th colSpan={5} className='sortable' onClick={sortShips('hpCount')}>{translate('hardpoints')}</th> <th colSpan={5} className='sortable' onClick={sortShips('hpCount')}>{translate('hardpoints')}</th>
<th colSpan={8} className='sortable' onClick={sortShips('intCount')}>{translate('internal compartments')}</th> <th colSpan={8} className='sortable' onClick={sortShips('intCount')}>{translate('internal compartments')}</th>
<th rowSpan={2} className='sortable' onClick={sortShips('hullMass')}>{translate('hull')}</th> <th rowSpan={2} className='sortable' onClick={sortShips('hullMass')}>{translate('hull')}</th>
<th rowSpan={2} className='sortable' onMouseEnter={tip.bind(null, 'mass lock factor')} onMouseLeave={hide} onClick={sortShips('masslock')} >{translate('MLF')}</th> <th rowSpan={2} className='sortable' onMouseEnter={termtip.bind(null, 'mass lock factor')} onMouseLeave={hide} onClick={sortShips('masslock')} >{translate('MLF')}</th>
<th rowSpan={2} className='sortable' onClick={sortShips('retailCost')}>{translate('cost')}</th> <th rowSpan={2} className='sortable' onClick={sortShips('retailCost')}>{translate('cost')}</th>
</tr> </tr>
<tr> <tr>
@@ -243,10 +295,11 @@ export default class ShipyardPage extends Page {
<th className='sortable' onClick={sortShips('int', 7)} >8</th> <th className='sortable' onClick={sortShips('int', 7)} >8</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody onMouseLeave={this._highlightShip.bind(this, null)}>
{shipRows} {detailRows}
</tbody> </tbody>
</table> </table>
</div>
</div> </div>
</div> </div>
); );

View File

@@ -1,3 +1,4 @@
import Module from './Module';
/** /**
* Calculate the maximum single jump range based on mass and a specific FSD * Calculate the maximum single jump range based on mass and a specific FSD
@@ -8,7 +9,9 @@
* @return {number} Distance in Light Years * @return {number} Distance in Light Years
*/ */
export function jumpRange(mass, fsd, fuel) { export function jumpRange(mass, fsd, fuel) {
return Math.pow(Math.min(fuel === undefined ? fsd.maxfuel : fuel, fsd.maxfuel) / fsd.fuelmul, 1 / fsd.fuelpower) * fsd.optmass / mass; let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
let fsdOptimalMass = fsd instanceof Module ? fsd.getOptimalMass() : fsd.optmass;
return Math.pow(Math.min(fuel === undefined ? fsdMaxFuelPerJump : fuel, fsdMaxFuelPerJump) / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass;
} }
/** /**
@@ -20,15 +23,17 @@ export function jumpRange(mass, fsd, fuel) {
* @return {number} Distance in Light Years * @return {number} Distance in Light Years
*/ */
export function fastestRange(mass, fsd, fuel) { export function fastestRange(mass, fsd, fuel) {
let fuelRemaining = fuel % fsd.maxfuel; // Fuel left after making N max jumps let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
let jumps = Math.floor(fuel / fsd.maxfuel); let fsdOptimalMass = fsd instanceof Module ? fsd.getOptimalMass() : fsd.optmass;
let fuelRemaining = fuel % fsdMaxFuelPerJump; // Fuel left after making N max jumps
let jumps = Math.floor(fuel / fsdMaxFuelPerJump);
mass += fuelRemaining; mass += fuelRemaining;
// Going backwards, start with the last jump using the remaining fuel // Going backwards, start with the last jump using the remaining fuel
let fastestRange = fuelRemaining > 0 ? Math.pow(fuelRemaining / fsd.fuelmul, 1 / fsd.fuelpower) * fsd.optmass / mass : 0; let fastestRange = fuelRemaining > 0 ? Math.pow(fuelRemaining / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass : 0;
// For each max fuel jump, calculate the max jump range based on fuel mass left in the tank // For each max fuel jump, calculate the max jump range based on fuel mass left in the tank
for (let j = 0; j < jumps; j++) { for (let j = 0; j < jumps; j++) {
mass += fsd.maxfuel; mass += fsd.maxfuel;
fastestRange += Math.pow(fsd.maxfuel / fsd.fuelmul, 1 / fsd.fuelpower) * fsd.optmass / mass; fastestRange += Math.pow(fsdMaxFuelPerJump / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass;
} }
return fastestRange; return fastestRange;
}; };
@@ -36,29 +41,27 @@ export function fastestRange(mass, fsd, fuel) {
/** /**
* Calculate the a ships shield strength based on mass, shield generator and shield boosters used. * Calculate the a ships shield strength based on mass, shield generator and shield boosters used.
* *
* @param {number} mass Current mass of the ship * @param {number} mass Current mass of the ship
* @param {number} shields Base Shield strength MJ for ship * @param {number} baseShield Base Shield strength MJ for ship
* @param {object} sg The shield generator used * @param {object} sg The shield generator used
* @param {number} multiplier Shield multiplier for ship (1 + shield boosters if any) * @param {number} multiplier Shield multiplier for ship (1 + shield boosters if any)
* @return {number} Approximate shield strengh in MJ * @return {number} Approximate shield strengh in MJ
*/ */
export function shieldStrength(mass, shields, sg, multiplier) { export function shieldStrength(mass, baseShield, sg, multiplier) {
let opt; // sg might be a module or a template; handle either here
if (mass < sg.minmass) { let minMass = sg instanceof Module ? sg.getMinMass() : sg.minmass;
return shields * multiplier * sg.minmul; let optMass = sg instanceof Module ? sg.getOptMass() : sg.optmass;
} let maxMass = sg instanceof Module ? sg.getMaxMass() : sg.maxmass;
if (mass > sg.maxmass) { let minMul = sg instanceof Module ? sg.getMinMul() : sg.minmul;
return shields * multiplier * sg.maxmul; let optMul = sg instanceof Module ? sg.getOptMul() : sg.optmul;
} let maxMul = sg instanceof Module ? sg.getMaxMul() : sg.maxmul;
if (mass < sg.optmass) {
opt = (sg.optmass - mass) / (sg.optmass - sg.minmass); let xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass));
opt = 1 - Math.pow(1 - opt, 0.87); let exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass)));
return shields * multiplier * ((opt * sg.minmul) + ((1 - opt) * sg.optmul)); let ynorm = Math.pow(xnorm, exponent);
} else { let mul = minMul + ynorm * (maxMul - minMul);
opt = (sg.optmass - mass) / (sg.maxmass - sg.optmass);
opt = -1 + Math.pow(1 + opt, 2.425); return baseShield * mul * multiplier;
return shields * multiplier * ((-1 * opt * sg.maxmul) + ((1 + opt) * sg.optmul));
}
} }
/** /**
@@ -72,13 +75,24 @@ export function shieldStrength(mass, shields, sg, multiplier) {
* @return {object} Approximate speed by pips * @return {object} Approximate speed by pips
*/ */
export function speed(mass, baseSpeed, baseBoost, thrusters, pipSpeed) { export function speed(mass, baseSpeed, baseBoost, thrusters, pipSpeed) {
let multiplier = mass > thrusters.maxmass ? 0 : ((1 - thrusters.M) + (thrusters.M * Math.pow(3 - (2 * Math.max(0.5, mass / thrusters.optmass)), thrusters.P))); // thrusters might be a module or a template; handle either here
let speed = baseSpeed * multiplier; let minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass;
let optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass;
let maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass;
let minMul = thrusters instanceof Module ? thrusters.getMinMul() : thrusters.minmul;
let optMul = thrusters instanceof Module ? thrusters.getOptMul() : thrusters.optmul;
let maxMul = thrusters instanceof Module ? thrusters.getMaxMul() : thrusters.maxmul;
let xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass));
let exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass)));
let ynorm = Math.pow(xnorm, exponent);
let mul = minMul + ynorm * (maxMul - minMul);
let speed = baseSpeed * mul;
return { return {
'0 Pips': speed * (1 - (pipSpeed * 4)), '0 Pips': speed * (1 - (pipSpeed * 4)),
'2 Pips': speed * (1 - (pipSpeed * 2)), '2 Pips': speed * (1 - (pipSpeed * 2)),
'4 Pips': speed, '4 Pips': speed,
'boost': baseBoost * multiplier 'boost': baseBoost * mul
}; };
} }

View File

@@ -1,12 +1,4 @@
export const ArmourMultiplier = [
1, // Lightweight
1.4, // Reinforced
1.945, // Military
1.945, // Mirrored
1.945 // Reactive
];
export const SizeMap = ['', 'small', 'medium', 'large', 'capital']; export const SizeMap = ['', 'small', 'medium', 'large', 'capital'];
export const StandardArray = [ export const StandardArray = [
@@ -37,17 +29,22 @@ export const ModuleGroupToName = {
am: 'Auto Field-Maintenance Unit', am: 'Auto Field-Maintenance Unit',
bsg: 'Bi-Weave Shield Generator', bsg: 'Bi-Weave Shield Generator',
cr: 'Cargo Rack', cr: 'Cargo Rack',
fh: 'Fighter Hangar',
fi: 'Frame Shift Drive Interdictor', fi: 'Frame Shift Drive Interdictor',
hb: 'Hatch Breaker Limpet Controller', hb: 'Hatch Breaker Limpet Controller',
hr: 'Hull Reinforcement Package', hr: 'Hull Reinforcement Package',
rf: 'Refinery', rf: 'Refinery',
scb: 'Shield Cell Bank', scb: 'Shield Cell Bank',
sg: 'Shield Generator', sg: 'Shield Generator',
pv: 'Planetary Vehicle Hanger', pv: 'Planetary Vehicle Hangar',
psg: 'Prismatic Shield Generator', psg: 'Prismatic Shield Generator',
dc: 'Docking Computer', dc: 'Docking Computer',
fx: 'Fuel Transfer Limpet Controller', fx: 'Fuel Transfer Limpet Controller',
pc: 'Prospector Limpet Controller', pc: 'Prospector Limpet Controller',
pce: 'Economy Class Passenger Cabin',
pci: 'Business Class Passenger Cabin',
pcm: 'First Class Passenger Cabin',
pcq: 'Luxury Passenger Cabin',
cc: 'Collector Limpet Controller', cc: 'Collector Limpet Controller',
// Hard Points // Hard Points
@@ -188,6 +185,20 @@ export const ShipFacets = [
lbls: ['DPS'], lbls: ['DPS'],
fmt: 'round', fmt: 'round',
i: 11 i: 11
},
{ // 12
title: 'EPS',
props: ['totalEps'],
lbls: ['EPS'],
fmt: 'round',
i: 12
},
{ // 13
title: 'HPS',
props: ['totalHps'],
lbls: ['HPS'],
fmt: 'round',
i: 13
} }
]; ];

View File

@@ -0,0 +1,15 @@
/**
* Modification - a modification and its value
*/
export default class Modification {
/**
* @param {String} id Unique modification ID
* @param {Number} value Value of the modification
*/
constructor(id, value) {
this.id = id;
this.value = value;
}
}

487
src/app/shipyard/Module.js Executable file
View File

@@ -0,0 +1,487 @@
import * as ModuleUtils from './ModuleUtils';
import * as _ from 'lodash';
/**
* Module - active module in a ship's buildout
*/
export default class Module {
/**
* Construct a new module
* @param {Object} params Module parameters. Either grp/id or template
*/
constructor(params) {
let properties = Object.assign({ grp: null, id: null, template: null }, params);
let template;
if (properties.template == undefined) {
return ModuleUtils.findModule(properties.grp, properties.id);
} else {
template = properties.template;
if (template) {
// Copy all properties from coriolis-data template
for (let p in template) { this[p] = template[p]; }
}
}
this.mods = {};
}
/**
* Get a value for a given modification
* @param {Number} name The name of the modification
* @return {Number} The value of the modification, as a decimal value where 1 is 100%
*/
getModValue(name) {
return this.mods && this.mods[name] ? this.mods[name] / 10000 : null;
}
/**
* Set a value for a given modification ID
* @param {Number} name The name of the modification
* @param {Number} value The value of the modification, as a decimal value where 1 is 100%
*/
setModValue(name, value) {
if (value == null || value == 0) {
delete this.mods[name];
} else {
// Store value with 2dp
this.mods[name] = Math.round(value * 10000);
}
}
/**
* Helper to obtain a modified value using standard multipliers
* @param {String} name the name of the modifier to obtain
* @return {Number} the mass of this module
*/
_getModifiedValue(name) {
let result = 0;
if (this[name]) {
result = this[name];
if (result) {
let mult = this.getModValue(name);
if (mult) { result = result * (1 + mult); }
}
}
return result;
}
/**
* Get the power generation of this module, taking in to account modifications
* @return {Number} the power generation of this module
*/
getPowerGeneration() {
return this._getModifiedValue('pgen');
}
/**
* Get the power usage of this module, taking in to account modifications
* @return {Number} the power usage of this module
*/
getPowerUsage() {
return this._getModifiedValue('power');
}
/**
* Get the integrity of this module, taking in to account modifications
* @return {Number} the integrity of this module
*/
getIntegrity() {
return this._getModifiedValue('integrity');
}
/**
* Get the mass of this module, taking in to account modifications
* @return {Number} the mass of this module
*/
getMass() {
return this._getModifiedValue('mass');
}
/**
* Get the thermal efficiency of this module, taking in to account modifications
* @return {Number} the thermal efficiency of this module
*/
getThermalEfficiency() {
return this._getModifiedValue('eff');
}
/**
* Get the maximum mass of this module, taking in to account modifications
* @return {Number} the maximum mass of this module
*/
getMaxMass() {
return this._getModifiedValue('maxmass');
}
/**
* Get the optimal mass of this module, taking in to account modifications
* @return {Number} the optimal mass of this module
*/
getOptimalMass() {
return this._getModifiedValue('optmass');
}
/**
* Get the optimal multiplier of this module, taking in to account modifications
* @return {Number} the optimal multiplier of this module
*/
getOptimalMultiplier() {
return this._getModifiedValue('optmult');
}
/**
* Get the maximum fuel per jump for this module, taking in to account modifications
* @return {Number} the maximum fuel per jump of this module
*/
getMaxFuelPerJump() {
return this._getModifiedValue('maxfuel');
}
/**
* Get the systems capacity for this module, taking in to account modifications
* @return {Number} the systems capacity of this module
*/
getSystemsCapacity() {
return this._getModifiedValue('syscap');
}
/**
* Get the engines capacity for this module, taking in to account modifications
* @return {Number} the engines capacity of this module
*/
getEnginesCapacity() {
return this._getModifiedValue('engcap');
}
/**
* Get the weapons capacity for this module, taking in to account modifications
* @return {Number} the weapons capacity of this module
*/
getWeaponsCapacity() {
return this._getModifiedValue('wepcap');
}
/**
* Get the systems recharge rate for this module, taking in to account modifications
* @return {Number} the systems recharge rate of this module
*/
getSystemsRechargeRate() {
return this._getModifiedValue('sysrate');
}
/**
* Get the engines recharge rate for this module, taking in to account modifications
* @return {Number} the engines recharge rate of this module
*/
getEnginesRechargeRate() {
return this._getModifiedValue('engrate');
}
/**
* Get the weapons recharge rate for this module, taking in to account modifications
* @return {Number} the weapons recharge rate of this module
*/
getWeaponsRechargeRate() {
return this._getModifiedValue('weprate');
}
/**
* Get the kinetic resistance for this module, taking in to account modifications
* @return {Number} the kinetic resistance of this module
*/
getKineticResistance() {
return this._getModifiedValue('kinres');
}
/**
* Get the thermal resistance for this module, taking in to account modifications
* @return {Number} the thermal resistance of this module
*/
getThermalResistance() {
return this._getModifiedValue('thermres');
}
/**
* Get the explosive resistance for this module, taking in to account modifications
* @return {Number} the explosive resistance of this module
*/
getExplosiveResistance() {
return this._getModifiedValue('explres');
}
/**
* Get the regeneration rate for this module, taking in to account modifications
* @return {Number} the regeneration rate of this module
*/
getRegenerationRate() {
return this._getModifiedValue('regen');
}
/**
* Get the broken regeneration rate for this module, taking in to account modifications
* @return {Number} the broken regeneration rate of this module
*/
getBrokenRegenerationRate() {
return this._getModifiedValue('brokenregen');
}
/**
* Get the range for this module, taking in to account modifications
* @return {Number} the range rate of this module
*/
getRange() {
return this._getModifiedValue('range');
}
/**
* Get the range (in terms of seconds, for FSDI) for this module, taking in to account modifications
* @return {Number} the range of this module
*/
getRangeT() {
return this._getModifiedValue('ranget');
}
/**
* Get the capture arc for this module, taking in to account modifications
* @return {Number} the capture arc of this module
*/
getCaptureArc() {
return this._getModifiedValue('arc');
}
/**
* Get the hull reinforcement for this module, taking in to account modifications
* @return {Number} the hull reinforcement of this module
*/
getHullReinforcement() {
return this._getModifiedValue('hullreinforcement');
}
/**
* Get the delay for this module, taking in to account modifications
* @return {Number} the delay of this module
*/
getDelay() {
return this._getModifiedValue('delay');
}
/**
* Get the duration for this module, taking in to account modifications
* @return {Number} the duration of this module
*/
getDuration() {
return this._getModifiedValue('duration');
}
/**
* Get the shield boost for this module, taking in to account modifications
* @return {Number} the shield boost of this module
*/
getShieldBoost() {
return this._getModifiedValue('shieldboost');
}
/**
* Get the minimum mass for this module, taking in to account modifications
* @return {Number} the minimum mass of this module
*/
getMinMass() {
// Modifier is optmass
let result = 0;
if (this['minmass']) {
result = this['minmass'];
if (result) {
let mult = this.getModValue('optmass');
if (mult) { result = result * (1 + mult); }
}
}
return result;
}
/**
* Get the optimum mass for this module, taking in to account modifications
* @return {Number} the optimum mass of this module
*/
getOptMass() {
return this._getModifiedValue('optmass');
}
/**
* Get the maximum mass for this module, taking in to account modifications
* @return {Number} the maximum mass of this module
*/
getMaxMass() {
// Modifier is optmass
let result = 0;
if (this['maxmass']) {
result = this['maxmass'];
if (result) {
let mult = this.getModValue('optmass');
if (mult) { result = result * (1 + mult); }
}
}
return result;
}
/**
* Get the minimum multiplier for this module, taking in to account modifications
* @return {Number} the minimum multiplier of this module
*/
getMinMul() {
// Modifier is optmul
let result = 0;
if (this['minmul']) {
result = this['minmul'];
if (result) {
let mult = this.getModValue('optmul');
if (mult) { result = result * (1 + mult); }
}
}
return result;
}
/**
* Get the optimum multiplier for this module, taking in to account modifications
* @return {Number} the optimum multiplier of this module
*/
getOptMul() {
return this._getModifiedValue('optmul');
}
/**
* Get the maximum multiplier for this module, taking in to account modifications
* @return {Number} the maximum multiplier of this module
*/
getMaxMul() {
// Modifier is optmul
let result = 0;
if (this['maxmul']) {
result = this['maxmul'];
if (result) {
let mult = this.getModValue('optmul');
if (mult) { result = result * (1 + mult); }
}
}
return result;
}
/**
* Get the damage for this module, taking in to account modifications
* @return {Number} the damage of this module
*/
getDamage() {
return this._getModifiedValue('damage');
}
/**
* Get the distributor draw for this module, taking in to account modifications
* @return {Number} the distributor draw of this module
*/
getDistDraw() {
return this._getModifiedValue('distdraw');
}
/**
* Get the thermal load for this module, taking in to account modifications
* @return {Number} the thermal load of this module
*/
getThermalLoad() {
return this._getModifiedValue('thermload');
}
/**
* Get the rounds per shot for this module, taking in to account modifications
* @return {Number} the rounds per shot of this module
*/
getRoundsPerShot() {
return this._getModifiedValue('roundspershot');
}
/**
* Get the DPS for this module, taking in to account modifications
* @return {Number} the DPS of this module
*/
getDps() {
// DPS is a synthetic value
let damage = this.getDamage();
let rpshot = this.getRoundsPerShot() || 1;
let rof = this.getRoF() || 1;
return damage * rpshot * rof;
}
/**
* Get the EPS for this module, taking in to account modifications
* @return {Number} the EPS of this module
*/
getEps() {
// EPS is a synthetic value
let distdraw = this.getDistDraw();
let rpshot = this.getRoundsPerShot() || 1;
let rof = this.getRoF() || 1;
return distdraw * rpshot * rof;
}
/**
* Get the HPS for this module, taking in to account modifications
* @return {Number} the HPS of this module
*/
getHps() {
// HPS is a synthetic value
let heat = this.getThermalLoad();
let rpshot = this.getRoundsPerShot() || 1;
let rof = this.getRoF() || 1;
return heat * rpshot * rof;
}
/**
* Get the clip size for this module, taking in to account modifications
* @return {Number} the clip size of this module
*/
getClip() {
return this._getModifiedValue('clip');
}
/**
* Get the ammo size for this module, taking in to account modifications
* @return {Number} the ammo size of this module
*/
getAmmo() {
return this._getModifiedValue('ammo');
}
/**
* Get the reload time for this module, taking in to account modifications
* @return {Number} the reload time of this module
*/
getReload() {
return this._getModifiedValue('reload');
}
/**
* Get the rate of fire for this module, taking in to account modifications
* @return {Number} the rate of fire for this module
*/
getRoF() {
return this._getModifiedValue('rof');
}
/**
* Get the facing limit for this module, taking in to account modifications
* @return {Number} the facing limit for this module
*/
getFacingLimit() {
return this._getModifiedValue('facinglimit');
}
/**
* Get the hull boost for this module, taking in to account modifications
* @return {Number} the hull boost for this module
*/
getHullBoost() {
return this._getModifiedValue('hullboost');
}
}

View File

@@ -1,3 +1,5 @@
import Module from './Module';
import { BulkheadNames } from './Constants';
/** /**
* Filter eligble modules based on parameters * Filter eligble modules based on parameters
@@ -19,12 +21,13 @@ export default class ModuleSet {
/** /**
* Instantiate the module set * Instantiate the module set
* @param {Object} modules All Modules * @param {Object} modules All Modules
* @param {number} mass Ship mass * @param {Object} shipData Ship Specifications Data (see coriolis-data/Ships)
* @param {Array} maxStandardArr Array of standard slots classes/sizes
* @param {Array} maxInternal Array of internal slots classes/sizes
* @param {Array} maxHardPoint Array of hardpoint slots classes/sizes
*/ */
constructor(modules, mass, maxStandardArr, maxInternal, maxHardPoint) { constructor(modules, shipData) {
let maxInternal = isNaN(shipData.slots.internal[0]) ? shipData.slots.internal[0].class : shipData.slots.internal[0];
let mass = shipData.properties.hullMass + 6.5;
let maxStandardArr = shipData.slots.standard;
let maxHardPoint = shipData.slots.hardpoints[0];
let stnd = modules.standard; let stnd = modules.standard;
this.mass = mass; this.mass = mass;
this.standard = {}; this.standard = {};
@@ -33,6 +36,10 @@ export default class ModuleSet {
this.hpClass = {}; this.hpClass = {};
this.intClass = {}; this.intClass = {};
this.bulkheads = shipData.bulkheads.map((b, i) => {
return Object.assign(new Module(), { grp: 'bh', id: i, name: BulkheadNames[i], index: i, class: '', rating: '' }, b);
});
this.standard[0] = filter(stnd.pp, maxStandardArr[0], 0, mass); // Power Plant this.standard[0] = filter(stnd.pp, maxStandardArr[0], 0, mass); // Power Plant
this.standard[2] = filter(stnd.fsd, maxStandardArr[2], 0, mass); // FSD this.standard[2] = filter(stnd.fsd, maxStandardArr[2], 0, mass); // FSD
this.standard[4] = filter(stnd.pd, maxStandardArr[4], 0, mass); // Power Distributor this.standard[4] = filter(stnd.pd, maxStandardArr[4], 0, mass); // Power Distributor
@@ -53,18 +60,34 @@ export default class ModuleSet {
} }
} }
/**
* Get the specified bulkhead
* @param {integer} index Bulkhead index
* @return {Object} Bulkhead module details
*/
getBulkhead(index) {
return this.bulkheads[index] ? new Module({ template: this.bulkheads[index] }) : null;
}
/** /**
* Determine the modules that areeligible for an internal slot * Determine the modules that areeligible for an internal slot
* @param {Object} ship The ship
* @param {integer} c The max class module that can be mounted in the slot * @param {integer} c The max class module that can be mounted in the slot
* @param {Object} eligible) The map of eligible internal groups * @param {Object} eligible) The map of eligible internal groups
* @return {object} A map of all eligible modules by group * @return {object} A map of all eligible modules by group
*/ */
getInts(c, eligible) { getInts(ship, c, eligible) {
let o = {}; let o = {};
for (let key in this.internal) { for (let key in this.internal) {
if (eligible && !eligible[key]) { if (eligible && !eligible[key]) {
continue; continue;
} }
if (key == 'pcq' && !(ship.luxuryCabins && ship.luxuryCabins === true)) {
continue;
}
if (key == 'fh' && !(ship.fighterHangars && ship.fighterHangars === true)) {
continue;
}
let data = filter(this.internal[key], c, 0, this.mass); let data = filter(this.internal[key], c, 0, this.mass);
if (data.length) { // If group is not empty if (data.length) { // If group is not empty
o[key] = data; o[key] = data;
@@ -103,11 +126,11 @@ export default class ModuleSet {
let pd = this.standard[4][0]; let pd = this.standard[4][0];
for (let p of this.standard[4]) { for (let p of this.standard[4]) {
if (p.mass < pd.mass && p.enginecapacity >= boostEnergy) { if (p.mass < pd.mass && p.engcap >= boostEnergy) {
pd = p; pd = p;
} }
} }
return pd; return new Module({ template: pd });
}; };
/** /**
@@ -123,7 +146,7 @@ export default class ModuleSet {
th = t; th = t;
} }
} }
return th; return new Module({ template: th });
}; };
/** /**
@@ -135,11 +158,11 @@ export default class ModuleSet {
let sg = this.internal.sg[0]; let sg = this.internal.sg[0];
for (let s of this.internal.sg) { for (let s of this.internal.sg) {
if (s.mass < sg.mass && s.minmass <= hullMass && s.maxmass > hullMass) { if (s.mass < sg.mass && s.maxmass > hullMass) {
sg = s; sg = s;
} }
} }
return sg; return new Module({ template: sg });
}; };
/** /**
@@ -152,10 +175,10 @@ export default class ModuleSet {
for (let p of this.standard[0]) { for (let p of this.standard[0]) {
// Provides enough power, is lighter or the same mass as current power plant but better output/efficiency // Provides enough power, is lighter or the same mass as current power plant but better output/efficiency
if (p.pGen >= powerNeeded && (p.mass < pp.mass || (p.mass == pp.mass && p.pGen > pp.pGen))) { if (p.pgen >= powerNeeded && (p.mass < pp.mass || (p.mass == pp.mass && p.pgen > pp.pgen))) {
pp = p; pp = p;
} }
} }
return pp; return new Module({ template: pp });
} }
} }

View File

@@ -1,7 +1,12 @@
import { ModuleNameToGroup, BulkheadNames, StandardArray } from './Constants'; import { ModuleNameToGroup, BulkheadNames, StandardArray } from './Constants';
import ModuleSet from './ModuleSet'; import ModuleSet from './ModuleSet';
import Module from './Module';
import { Ships, Modules } from 'coriolis-data/dist'; import { Ships, Modules } from 'coriolis-data/dist';
/*
* All functions below must return a fresh Module rather than a definition or existing module, as
* the resultant object can be altered with modifications.
*/
/** /**
@@ -9,9 +14,45 @@ import { Ships, Modules } from 'coriolis-data/dist';
* @return {Object} Cargo hatch model * @return {Object} Cargo hatch model
*/ */
export function cargoHatch() { export function cargoHatch() {
return { name: 'Cargo Hatch', class: 1, rating: 'H', power: 0.6 }; let hatch = new Module();
Object.assign(hatch, { name: 'Cargo Hatch', class: 1, rating: 'H', power: 0.6 });
return hatch;
}; };
/**
* Finds the module with the specific group and ID
* @param {String} grp Module group (pp - power plant, pl - pulse laser etc)
* @param {String} id The module ID
* @return {Object} The module or null
*/
export function findModule(grp, id) {
// See if it's a standard module
if (Modules.standard[grp]) {
let standardmod = Modules.standard[grp].find(e => e.id == id);
if (standardmod != null) {
return new Module({ template: standardmod });
}
}
// See if it's an internal module
if (Modules.internal[grp]) {
let internalmod = Modules.internal[grp].find(e => e.id == id);
if (internalmod != null) {
return new Module({ template: internalmod });
}
}
// See if it's a hardpoint module
if (Modules.hardpoints[grp]) {
let hardpointmod = Modules.hardpoints[grp].find(e => e.id == id);
if (hardpointmod != null) {
return new Module({ template: hardpointmod });
}
}
return null;
}
/** /**
* Finds the standard module type with the specified ID * Finds the standard module type with the specified ID
* @param {String|Number} type Standard Module Type (0/pp - Power Plant, 1/t - Thrusters, etc) * @param {String|Number} type Standard Module Type (0/pp - Power Plant, 1/t - Thrusters, etc)
@@ -24,12 +65,15 @@ export function standard(type, id) {
} }
let s = Modules.standard[type].find(e => e.id == id || (e.class == id.charAt(0) && e.rating == id.charAt(1))); let s = Modules.standard[type].find(e => e.id == id || (e.class == id.charAt(0) && e.rating == id.charAt(1)));
if (s) {
s = new Module({ template: s });
}
return s || null; return s || null;
}; };
/** /**
* Finds the hardpoint with the specified ID * Finds the hardpoint with the specified ID
* @param {string} id Hardpoint ID * @param {String} id Hardpoint ID
* @return {Object} Hardpoint module or null * @return {Object} Hardpoint module or null
*/ */
export function hardpoints(id) { export function hardpoints(id) {
@@ -37,7 +81,7 @@ export function hardpoints(id) {
let group = Modules.hardpoints[n]; let group = Modules.hardpoints[n];
for (let i = 0; i < group.length; i++) { for (let i = 0; i < group.length; i++) {
if (group[i].id == id) { if (group[i].id == id) {
return group[i]; return new Module({ template: group[i] });
} }
} }
} }
@@ -46,7 +90,7 @@ export function hardpoints(id) {
/** /**
* Finds the internal module with the specified ID * Finds the internal module with the specified ID
* @param {string} id Internal module ID * @param {String} id Internal module ID
* @return {Object} Internal module or null * @return {Object} Internal module or null
*/ */
export function internal(id) { export function internal(id) {
@@ -54,7 +98,7 @@ export function internal(id) {
let group = Modules.internal[n]; let group = Modules.internal[n];
for (let i = 0; i < group.length; i++) { for (let i = 0; i < group.length; i++) {
if (group[i].id == id) { if (group[i].id == id) {
return group[i]; return new Module({ template: group[i] });
} }
} }
} }
@@ -65,11 +109,11 @@ export function internal(id) {
* Finds an internal module based on Class, Rating, Group and/or name. * Finds an internal module based on Class, Rating, Group and/or name.
* At least one ofGroup name or unique module name must be provided * At least one ofGroup name or unique module name must be provided
* *
* @param {string} groupName [Optional] Full name or abbreviated name for module group * @param {String} groupName [Optional] Full name or abbreviated name for module group
* @param {integer} clss module Class * @param {integer} clss module Class
* @param {string} rating module Rating * @param {String} rating module Rating
* @param {string} name [Optional] Long/unique name for module -e.g. 'Advanced Discover Scanner' * @param {String} name [Optional] Long/unique name for module -e.g. 'Advanced Discover Scanner'
* @return {String} The id of the module if found, null if not found * @return {Object} The module if found, null if not found
*/ */
export function findInternal(groupName, clss, rating, name) { export function findInternal(groupName, clss, rating, name) {
let groups = {}; let groups = {};
@@ -103,10 +147,10 @@ export function findInternal(groupName, clss, rating, name) {
* Finds an internal Module ID based on Class, Rating, Group and/or name. * Finds an internal Module ID based on Class, Rating, Group and/or name.
* At least one ofGroup name or unique module name must be provided * At least one ofGroup name or unique module name must be provided
* *
* @param {string} groupName [Optional] Full name or abbreviated name for module group * @param {String} groupName [Optional] Full name or abbreviated name for module group
* @param {integer} clss module Class * @param {integer} clss module Class
* @param {string} rating Module Rating * @param {String} rating Module Rating
* @param {string} name [Optional] Long/unique name for module -e.g. 'Advanced Discover Scanner' * @param {String} name [Optional] Long/unique name for module -e.g. 'Advanced Discover Scanner'
* @return {String} The id of the module if found, null if not found * @return {String} The id of the module if found, null if not found
*/ */
export function findInternalId(groupName, clss, rating, name) { export function findInternalId(groupName, clss, rating, name) {
@@ -118,12 +162,12 @@ export function findInternalId(groupName, clss, rating, name) {
* Finds a hardpoint Module based on Class, Rating, Group and/or name. * Finds a hardpoint Module based on Class, Rating, Group and/or name.
* At least one ofGroup name or unique module name must be provided * At least one ofGroup name or unique module name must be provided
* *
* @param {string} groupName [Optional] Full name or abbreviated name for module group * @param {String} groupName [Optional] Full name or abbreviated name for module group
* @param {integer} clss Module Class * @param {integer} clss Module Class
* @param {string} rating [Optional] module Rating * @param {String} rating [Optional] module Rating
* @param {string} name [Optional] Long/unique name for module -e.g. 'Heat Sink Launcher' * @param {String} name [Optional] Long/unique name for module -e.g. 'Heat Sink Launcher'
* @param {string} mount Mount type - [F]ixed, [G]imballed, [T]urret * @param {String} mount Mount type - [F]ixed, [G]imballed, [T]urret
* @param {string} missile [Optional] Missile type - [D]umbfire, [S]eeker * @param {String} missile [Optional] Missile type - [D]umbfire, [S]eeker
* @return {String} The id of the module if found, null if not found * @return {String} The id of the module if found, null if not found
*/ */
export function findHardpoint(groupName, clss, rating, name, mount, missile) { export function findHardpoint(groupName, clss, rating, name, mount, missile) {
@@ -158,12 +202,12 @@ export function findHardpoint(groupName, clss, rating, name, mount, missile) {
* Finds a hardpoint module ID based on Class, Rating, Group and/or name. * Finds a hardpoint module ID based on Class, Rating, Group and/or name.
* At least one of Group name or unique module name must be provided * At least one of Group name or unique module name must be provided
* *
* @param {string} groupName [Optional] Full name or abbreviated name for module group * @param {String} groupName [Optional] Full name or abbreviated name for module group
* @param {integer} clss module Class * @param {integer} clss module Class
* @param {string} rating module Rating * @param {String} rating module Rating
* @param {string} name [Optional] Long/unique name for module -e.g. 'Heat Sink Launcher' * @param {String} name [Optional] Long/unique name for module -e.g. 'Heat Sink Launcher'
* @param {string} mount Mount type - [F]ixed, [G]imballed, [T]urret * @param {String} mount Mount type - [F]ixed, [G]imballed, [T]urret
* @param {string} missile [Optional] Missile type - [D]umbfire, [S]eeker * @param {String} missile [Optional] Missile type - [D]umbfire, [S]eeker
* @return {String} The id of the module if found, null if not found * @return {String} The id of the module if found, null if not found
*/ */
export function findHardpointId(groupName, clss, rating, name, mount, missile) { export function findHardpointId(groupName, clss, rating, name, mount, missile) {
@@ -171,24 +215,9 @@ export function findHardpointId(groupName, clss, rating, name, mount, missile) {
return h ? h.id : 0; return h ? h.id : 0;
} }
/**
* Looks up the bulkhead module for a specific ship and bulkhead
* @param {string} shipId Unique ship Id/Key
* @param {string|number} index Index for the specified bulkhead
* @return {Object} The bulkhead module object
*/
export function bulkheads(shipId, index) {
let bulkhead = Ships[shipId].bulkheads[index];
bulkhead.class = 1;
bulkhead.rating = 'I';
bulkhead.name = BulkheadNames[index];
return bulkhead;
}
/** /**
* Get the bulkhead index for the given bulkhead name * Get the bulkhead index for the given bulkhead name
* @param {string} bulkheadName Bulkhead name in english * @param {String} bulkheadName Bulkhead name in english
* @return {number} Bulkhead index * @return {number} Bulkhead index
*/ */
export function bulkheadIndex(bulkheadName) { export function bulkheadIndex(bulkheadName) {
@@ -198,7 +227,7 @@ export function bulkheadIndex(bulkheadName) {
/** /**
* Determine if a module group is a shield generator * Determine if a module group is a shield generator
* @param {string} g Module Group name * @param {String} g Module Group name
* @return {Boolean} True if the group is a shield generator * @return {Boolean} True if the group is a shield generator
*/ */
export function isShieldGenerator(g) { export function isShieldGenerator(g) {
@@ -211,11 +240,9 @@ export function isShieldGenerator(g) {
* *
* 6.5 T is the lightest possible mass of standard components that any ship can use * 6.5 T is the lightest possible mass of standard components that any ship can use
* *
* @param {string} shipId Unique ship Id/Key * @param {String} shipId Unique ship Id/Key
* @return {ModuleSet} The set of modules the ship can install * @return {ModuleSet} The set of modules the ship can install
*/ */
export function forShip(shipId) { export function forShip(shipId) {
let ship = Ships[shipId]; return new ModuleSet(Modules, Ships[shipId]);
let maxInternal = isNaN(ship.slots.internal[0]) ? ship.slots.internal[0].class : ship.slots.internal[0];
return new ModuleSet(Modules, ship.properties.hullMass + 6.5, ship.slots.standard, maxInternal, ship.slots.hardpoints[0]);
} }

View File

@@ -6,6 +6,33 @@ import LZString from 'lz-string';
const STANDARD = ['powerPlant', 'thrusters', 'frameShiftDrive', 'lifeSupport', 'powerDistributor', 'sensors', 'fuelTank']; const STANDARD = ['powerPlant', 'thrusters', 'frameShiftDrive', 'lifeSupport', 'powerDistributor', 'sensors', 'fuelTank'];
/**
* Generates ship-loadout JSON Schema standard object
* @param {Object} standard model
* @return {Object} JSON Schema
*/
function standardToSchema(standard) {
if (standard.m) {
let o = {
class: standard.m.class,
rating: standard.m.rating,
enabled: Boolean(standard.enabled),
priority: standard.priority + 1
};
if (standard.m.name) {
o.name = standard.m.name;
}
if (standard.m.mods && Object.keys(standard.m.mods).length > 0) {
o.modifications = standard.m.mods;
}
return o;
}
return null;
}
/** /**
* Generates ship-loadout JSON Schema slot object * Generates ship-loadout JSON Schema slot object
* @param {Object} slot Slot model * @param {Object} slot Slot model
@@ -30,6 +57,9 @@ function slotToSchema(slot) {
if (slot.m.missile) { if (slot.m.missile) {
o.missile = slot.m.missile; o.missile = slot.m.missile;
} }
if (slot.m.mods && Object.keys(slot.m.mods).length > 0) {
o.modifications = slot.m.mods;
}
return o; return o;
} }
return null; return null;
@@ -48,26 +78,26 @@ export function toDetailedBuild(buildName, ship) {
code = ship.toString(); code = ship.toString();
let data = { let data = {
$schema: 'http://cdn.coriolis.io/schemas/ship-loadout/3.json#', $schema: 'http://cdn.coriolis.io/schemas/ship-loadout/4.json#',
name: buildName, name: buildName,
ship: ship.name, ship: ship.name,
references: [{ references: [{
name: 'Coriolis.io', name: 'Coriolis.io',
url: `https://coriolis.io/outfit/${ship.id}/${code}?bn=${encodeURIComponent(buildName)}`, url: `https://coriolis.edcd.io/outfit/${ship.id}/${code}?bn=${encodeURIComponent(buildName)}`,
code, code,
shipId: ship.id shipId: ship.id
}], }],
components: { components: {
standard: { standard: {
bulkheads: BulkheadNames[ship.bulkheads.index], bulkheads: BulkheadNames[ship.bulkheads.m.index],
cargoHatch: { enabled: Boolean(ship.cargoHatch.enabled), priority: ship.cargoHatch.priority + 1 }, cargoHatch: { enabled: Boolean(ship.cargoHatch.enabled), priority: ship.cargoHatch.priority + 1 },
powerPlant: { class: standard[0].m.class, rating: standard[0].m.rating, enabled: Boolean(standard[0].enabled), priority: standard[0].priority + 1 }, powerPlant: standardToSchema(standard[0]),
thrusters: { class: standard[1].m.class, rating: standard[1].m.rating, enabled: Boolean(standard[1].enabled), priority: standard[1].priority + 1 }, thrusters: standardToSchema(standard[1]),
frameShiftDrive: { class: standard[2].m.class, rating: standard[2].m.rating, enabled: Boolean(standard[2].enabled), priority: standard[2].priority + 1 }, frameShiftDrive: standardToSchema(standard[2]),
lifeSupport: { class: standard[3].m.class, rating: standard[3].m.rating, enabled: Boolean(standard[3].enabled), priority: standard[3].priority + 1 }, lifeSupport: standardToSchema(standard[3]),
powerDistributor: { class: standard[4].m.class, rating: standard[4].m.rating, enabled: Boolean(standard[4].enabled), priority: standard[4].priority + 1 }, powerDistributor: standardToSchema(standard[4]),
sensors: { class: standard[5].m.class, rating: standard[5].m.rating, enabled: Boolean(standard[5].enabled), priority: standard[5].priority + 1 }, sensors: standardToSchema(standard[5]),
fuelTank: { class: standard[6].m.class, rating: standard[6].m.rating, enabled: Boolean(standard[6].enabled), priority: standard[6].priority + 1 } fuelTank: standardToSchema(standard[6])
}, },
hardpoints: hardpoints.filter(slot => slot.maxClass > 0).map(slotToSchema), hardpoints: hardpoints.filter(slot => slot.maxClass > 0).map(slotToSchema),
utility: hardpoints.filter(slot => slot.maxClass === 0).map(slotToSchema), utility: hardpoints.filter(slot => slot.maxClass === 0).map(slotToSchema),
@@ -86,7 +116,7 @@ export function toDetailedBuild(buildName, ship) {
}; };
/** /**
* Instantiates a ship from a ship-loadout object * Instantiates a ship from a ship-loadout object, using the code
* @param {Object} detailedBuild ship-loadout object * @param {Object} detailedBuild ship-loadout object
* @return {Ship} Ship instance * @return {Ship} Ship instance
*/ */
@@ -97,6 +127,26 @@ export function fromDetailedBuild(detailedBuild) {
throw 'No such ship: ' + detailedBuild.ship; throw 'No such ship: ' + detailedBuild.ship;
} }
let comps = detailedBuild.components;
let stn = comps.standard;
let shipData = Ships[shipId];
let ship = new Ship(shipId, shipData.properties, shipData.slots);
return ship.buildFrom(detailedBuild.references[0].code);
};
/**
* Instantiates a ship from a ship-loadout object
* @param {Object} detailedBuild ship-loadout object
* @return {Ship} Ship instance
*/
export function oldfromDetailedBuild(detailedBuild) {
let shipId = Object.keys(Ships).find((shipId) => Ships[shipId].properties.name.toLowerCase() == detailedBuild.ship.toLowerCase());
if (!shipId) {
throw 'No such ship: ' + detailedBuild.ship;
}
let comps = detailedBuild.components; let comps = detailedBuild.components;
let stn = comps.standard; let stn = comps.standard;
let priorities = [stn.cargoHatch && stn.cargoHatch.priority !== undefined ? stn.cargoHatch.priority - 1 : 0]; let priorities = [stn.cargoHatch && stn.cargoHatch.priority !== undefined ? stn.cargoHatch.priority - 1 : 0];

View File

@@ -1,14 +1,17 @@
import { ArmourMultiplier } from './Constants';
import * as Calc from './Calculations'; import * as Calc from './Calculations';
import * as ModuleUtils from './ModuleUtils'; import * as ModuleUtils from './ModuleUtils';
import Module from './Module';
import LZString from 'lz-string'; import LZString from 'lz-string';
import isEqual from 'lodash/lang';
import { Modifications } from 'coriolis-data/dist';
const zlib = require('zlib');
const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs']; const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs', 'fh'];
/** /**
* Returns the power usage type of a slot and it's particular modul * Returns the power usage type of a slot and it's particular module
* @param {Object} slot The Slot * @param {Object} slot The Slot
* @param {Object} modul The modul in the slot * @param {Object} modul The module in the slot
* @return {String} The key for the power usage type * @return {String} The key for the power usage type
*/ */
function powerUsageType(slot, modul) { function powerUsageType(slot, modul) {
@@ -114,7 +117,7 @@ export default class Ship {
*/ */
canThrust() { canThrust() {
return this.getSlotStatus(this.standard[1]) == 3 && // Thrusters are powered return this.getSlotStatus(this.standard[1]) == 3 && // Thrusters are powered
this.ladenMass < this.standard[1].m.maxmass; // Max mass not exceeded this.ladenMass < this.standard[1].m.getMaxMass(); // Max mass not exceeded
} }
/** /**
@@ -122,9 +125,9 @@ export default class Ship {
* @return {[type]} True if boost capable * @return {[type]} True if boost capable
*/ */
canBoost() { canBoost() {
return this.canThrust() && // Thrusters operational return this.canThrust() && // Thrusters operational
this.getSlotStatus(this.standard[4]) == 3 && // Power distributor operational this.getSlotStatus(this.standard[4]) == 3 && // Power distributor operational
this.boostEnergy <= this.standard[4].m.enginecapacity; // PD capacitor is sufficient for boost this.boostEnergy <= this.standard[4].m.getEnginesCapacity(); // PD capacitor is sufficient for boost
} }
/** /**
@@ -158,7 +161,8 @@ export default class Ship {
*/ */
calcUnladenRange(massDelta, fuel, fsd) { calcUnladenRange(massDelta, fuel, fsd) {
fsd = fsd || this.standard[2].m; fsd = fsd || this.standard[2].m;
return Calc.jumpRange(this.unladenMass + (massDelta || 0) + Math.min(fsd.maxfuel, fuel || this.fuelCapacity), fsd || this.standard[2].m, fuel); let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
return Calc.jumpRange(this.unladenMass + (massDelta || 0) + Math.min(fsdMaxFuelPerJump, fuel || this.fuelCapacity), fsd || this.standard[2].m, fuel);
} }
/** /**
@@ -189,9 +193,11 @@ export default class Ship {
* @return {Number} Recovery time in seconds * @return {Number} Recovery time in seconds
*/ */
calcShieldRecovery() { calcShieldRecovery() {
if (this.shieldStrength && this.sgSlot) { if (this.shield > 0) {
let sgSlot = this.findInternalByGroup('sg');
let brokenRegenRate = 1 + sgSlot.m.getModValue('brokenregen');
// 50% of shield strength / recovery recharge rate + 15 second delay before recharge starts // 50% of shield strength / recovery recharge rate + 15 second delay before recharge starts
return ((this.shieldStrength / 2) / this.sgSlot.m.recover) + 15; return ((this.shield / 2) / (sgSlot.m.recover * brokenRegenRate)) + 15;
} }
return 0; return 0;
} }
@@ -203,9 +209,11 @@ export default class Ship {
* @return {Number} 50 - 100% Recharge time in seconds * @return {Number} 50 - 100% Recharge time in seconds
*/ */
calcShieldRecharge() { calcShieldRecharge() {
if (this.shieldStrength && this.sgSlot) { if (this.shield > 0) {
let sgSlot = this.findInternalByGroup('sg');
let regenRate = 1 + sgSlot.m.getModValue('regen');
// 50% -> 100% recharge time, Bi-Weave shields charge at 1.8 MJ/s // 50% -> 100% recharge time, Bi-Weave shields charge at 1.8 MJ/s
return (this.shieldStrength / 2) / (this.sgSlot.m.grp == 'bsg' ? 1.8 : 1); return (this.shield / 2) / ((sgSlot.m.grp == 'bsg' ? 1.8 : 1) * regenRate);
} }
return 0; return 0;
} }
@@ -214,16 +222,20 @@ export default class Ship {
* Calculate the hypothetical shield strength for the ship using the specified parameters * Calculate the hypothetical shield strength for the ship using the specified parameters
* @param {Object} sg [optional] Shield Generator to use * @param {Object} sg [optional] Shield Generator to use
* @param {Number} multiplierDelta [optional] Change to shield multiplier (+0.2, - 0.12, etc) * @param {Number} multiplierDelta [optional] Change to shield multiplier (+0.2, - 0.12, etc)
* @return {Number} Shield strength in MH * @return {Number} Shield strength in MJ
*/ */
calcShieldStrengthWith(sg, multiplierDelta) { calcShieldStrengthWith(sg, multiplierDelta) {
if (!sg) { if (!sg) {
if (!this.sgSlot) { let sgSlot = this.findInternalByGroup('sg');
if (!sgSlot) {
return 0; return 0;
} }
sg = this.sgSlot.m; sg = sgSlot.m;
} }
return Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sg, this.shieldMultiplier + (multiplierDelta || 0));
// TODO obtain shield boost
// return Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sg, this.shieldMultiplier + (multiplierDelta || 0));
return Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sg, 0 + (multiplierDelta || 0));
} }
/** /**
@@ -284,7 +296,9 @@ export default class Ship {
'.', '.',
this.getPowerEnabledString(), this.getPowerEnabledString(),
'.', '.',
this.getPowerPrioritesString() this.getPowerPrioritiesString(),
'.',
this.getModificationsString()
].join(''); ].join('');
} }
@@ -294,8 +308,8 @@ export default class Ship {
*/ */
getStandardString() { getStandardString() {
if(!this.serialized.standard) { if(!this.serialized.standard) {
this.serialized.standard = this.bulkheads.index + this.standard.reduce((arr, slot, i) => { this.serialized.standard = this.bulkheads.m.index + this.standard.reduce((arr, slot, i) => {
arr[i] = slot.m ? slot.m.class + slot.m.rating : '-'; arr[i] = slot.m ? slot.m.id : '-';
return arr; return arr;
}, new Array(this.standard.length)).join(''); }, new Array(this.standard.length)).join('');
} }
@@ -324,6 +338,17 @@ export default class Ship {
return this.serialized.hardpoints; return this.serialized.hardpoints;
} }
/**
* Serializes the modifications to a string
* @return {String} Serialized modifications 'code'
*/
getModificationsString() {
// Modifications can be updated outside of the ship's direct knowledge, for example when sliders change the value,
// so always recreate it from scratch
this.updateModificationsString();
return this.serialized.modifications;
}
/** /**
* Get the serialized module active/inactive settings * Get the serialized module active/inactive settings
* @return {String} Serialized active/inactive settings * @return {String} Serialized active/inactive settings
@@ -336,7 +361,7 @@ export default class Ship {
* Get the serialized module priority settings * Get the serialized module priority settings
* @return {String} Serialized priority settings * @return {String} Serialized priority settings
*/ */
getPowerPrioritesString() { getPowerPrioritiesString() {
return this.serialized.priorities; return this.serialized.priorities;
} }
@@ -368,18 +393,68 @@ export default class Ship {
return this; return this;
} }
/**
* Set a modification value
* @param {Object} m The module to change
* @param {Object} name The name of the modification to change
* @param {Number} value The new value of the modification
*/
setModification(m, name, value) {
// Handle special cases
if (name == 'pgen') {
// Power generation
m.setModValue(name, value);
this.updatePowerGenerated();
} else if (name == 'power') {
// Power usage
m.setModValue(name, value);
this.updatePowerUsed();
} else if (name == 'mass') {
// Mass
let oldMass = m.getMass();
m.setModValue(name, value);
let newMass = m.getMass();
this.unladenMass = this.unladenMass - oldMass + newMass;
this.ladenMass = this.ladenMass - oldMass + newMass;
this.updateTopSpeed();
this.updateJumpStats();
} else if (name == 'maxfuel') {
m.setModValue(name, value);
this.updateJumpStats();
} else if (name == 'optmass') {
m.setModValue(name, value);
// Could be for either thrusters or FSD
this.updateTopSpeed();
this.updateJumpStats();
} else if (name == 'optmul') {
m.setModValue(name, value);
// Could be for either thrusters or FSD
this.updateTopSpeed();
this.updateJumpStats();
} else if (name == 'shieldboost') {
m.setModValue(name, value);
this.updateShield();
} else if (name == 'hullboost') {
m.setModValue(name, value);
this.updateArmour();
} else {
// Generic
m.setModValue(name, value);
}
}
/** /**
* Builds/Updates the ship instance with the ModuleUtils[comps] passed in. * Builds/Updates the ship instance with the ModuleUtils[comps] passed in.
* @param {Object} comps Collection of ModuleUtils used to build the ship * @param {Object} comps Collection of ModuleUtils used to build the ship
* @param {array} priorities Slot priorities * @param {array} priorities Slot priorities
* @param {Array} enabled Slot active/inactive * @param {Array} enabled Slot active/inactive
* @param {Array} mods Modifications
* @return {this} The current ship instance for chaining * @return {this} The current ship instance for chaining
*/ */
buildWith(comps, priorities, enabled) { buildWith(comps, priorities, enabled, mods) {
let internal = this.internal, let internal = this.internal,
standard = this.standard, standard = this.standard,
hps = this.hardpoints, hps = this.hardpoints,
bands = this.priorityBands,
cl = standard.length, cl = standard.length,
i, l; i, l;
@@ -387,26 +462,26 @@ export default class Ship {
this.fuelCapacity = 0; this.fuelCapacity = 0;
this.cargoCapacity = 0; this.cargoCapacity = 0;
this.ladenMass = 0; this.ladenMass = 0;
this.armourAdded = 0; this.armour = this.baseArmour;
this.armourMultiplier = 1; this.shield = this.baseShieldStrength;
this.shieldMultiplier = 1;
this.totalCost = this.m.incCost ? this.m.discountedCost : 0; this.totalCost = this.m.incCost ? this.m.discountedCost : 0;
this.unladenMass = this.hullMass; this.unladenMass = this.hullMass;
this.totalDps = 0; this.totalDps = 0;
this.totalEps = 0;
this.totalHps = 0;
this.shieldExplRes = 0;
this.shieldKinRes = 0;
this.shieldThermRes = 0;
this.hullExplRes = 0;
this.hullKinRes = 0;
this.hullThermRes = 0;
this.bulkheads.m = null; this.bulkheads.m = null;
this.useBulkhead(comps && comps.bulkheads ? comps.bulkheads : 0, true); this.useBulkhead(comps && comps.bulkheads ? comps.bulkheads : 0, true);
this.bulkheads.m.mods = mods && mods[0] ? mods[0] : {};
this.cargoHatch.priority = priorities ? priorities[0] * 1 : 0; this.cargoHatch.priority = priorities ? priorities[0] * 1 : 0;
this.cargoHatch.enabled = enabled ? enabled[0] * 1 : true; this.cargoHatch.enabled = enabled ? enabled[0] * 1 : true;
this.cargoHatch.mods = mods ? mods[0] : {};
for (i = 0, l = this.priorityBands.length; i < l; i++) {
this.priorityBands[i].deployed = 0;
this.priorityBands[i].retracted = 0;
}
if (this.cargoHatch.enabled) {
bands[this.cargoHatch.priority].retracted += this.cargoHatch.m.power;
}
for (i = 0; i < cl; i++) { for (i = 0; i < cl; i++) {
standard[i].cat = 0; standard[i].cat = 0;
@@ -415,9 +490,10 @@ export default class Ship {
standard[i].type = 'SYS'; standard[i].type = 'SYS';
standard[i].m = null; // Resetting 'old' modul if there was one standard[i].m = null; // Resetting 'old' modul if there was one
standard[i].discountedCost = 0; standard[i].discountedCost = 0;
if (comps) { if (comps) {
this.use(standard[i], ModuleUtils.standard(i, comps.standard[i]), true); let module = ModuleUtils.standard(i, comps.standard[i]);
if (module != null) { module.mods = mods && mods[i + 1] ? mods[i + 1] : {}; }
this.use(standard[i], module, true);
} }
} }
@@ -434,7 +510,9 @@ export default class Ship {
hps[i].discountedCost = 0; hps[i].discountedCost = 0;
if (comps && comps.hardpoints[i] !== 0) { if (comps && comps.hardpoints[i] !== 0) {
this.use(hps[i], ModuleUtils.hardpoints(comps.hardpoints[i]), true); let module = ModuleUtils.hardpoints(comps.hardpoints[i]);
if (module != null) { module.mods = mods && mods[cl + i] ? mods[cl + i] : {}; }
this.use(hps[i], module, true);
} }
} }
@@ -449,19 +527,23 @@ export default class Ship {
internal[i].discountedCost = 0; internal[i].discountedCost = 0;
if (comps && comps.internal[i] !== 0) { if (comps && comps.internal[i] !== 0) {
this.use(internal[i], ModuleUtils.internal(comps.internal[i]), true); let module = ModuleUtils.internal(comps.internal[i]);
if (module != null) { module.mods = mods && mods[cl + i] ? mods[cl + i] : {}; }
this.use(internal[i], module, true);
} }
} }
// Update aggragated stats // Update aggragated stats
if (comps) { if (comps) {
this.updatePower() this.updatePowerGenerated()
.updatePowerUsed()
.updateJumpStats() .updateJumpStats()
.updateShieldStrength() .updateShield()
.updateArmour()
.updateTopSpeed(); .updateTopSpeed();
} }
return this.updatePowerPrioritesString().updatePowerEnabledString(); return this.updatePowerPrioritesString().updatePowerEnabledString().updateModificationsString();
} }
/** /**
@@ -475,6 +557,7 @@ export default class Ship {
let standard = new Array(this.standard.length), let standard = new Array(this.standard.length),
hardpoints = new Array(this.hardpoints.length), hardpoints = new Array(this.hardpoints.length),
internal = new Array(this.internal.length), internal = new Array(this.internal.length),
mods = new Array(1 + this.standard.length + this.hardpoints.length + this.internal.length),
parts = serializedString.split('.'), parts = serializedString.split('.'),
priorities = null, priorities = null,
enabled = null, enabled = null,
@@ -488,6 +571,19 @@ export default class Ship {
priorities = LZString.decompressFromBase64(parts[2].replace(/-/g, '/')).split(''); priorities = LZString.decompressFromBase64(parts[2].replace(/-/g, '/')).split('');
} }
if (parts[3]) {
const modstr = parts[3].replace(/-/g, '/');
if (modstr.match(':')) {
this.decodeModificationsString(modstr, mods);
} else {
try {
this.decodeModificationsStruct(zlib.gunzipSync(new Buffer(modstr, 'base64')), mods);
} catch (err) {
// Could be out-of-date URL; ignore
}
}
}
decodeToArray(code, internal, decodeToArray(code, hardpoints, decodeToArray(code, standard, 1))); decodeToArray(code, internal, decodeToArray(code, hardpoints, decodeToArray(code, standard, 1)));
return this.buildWith( return this.buildWith(
@@ -498,7 +594,8 @@ export default class Ship {
internal internal
}, },
priorities, priorities,
enabled enabled,
mods
); );
}; };
@@ -584,18 +681,20 @@ export default class Ship {
if (slot.enabled != enabled) { // Enabled state is changing if (slot.enabled != enabled) { // Enabled state is changing
slot.enabled = enabled; slot.enabled = enabled;
if (slot.m) { if (slot.m) {
this.priorityBands[slot.priority][powerUsageType(slot, slot.m)] += enabled ? slot.m.power : -slot.m.power; if (ModuleUtils.isShieldGenerator(slot.m.grp) || slot.m.grp == 'sb') {
this.updateShield();
if (ModuleUtils.isShieldGenerator(slot.m.grp)) { }
this.updateShieldStrength(); if (slot.m.getDps()) {
} else if (slot.m.grp == 'sb') { this.totalDps += slot.m.getDps() * (enabled ? 1 : -1);
this.shieldMultiplier += slot.m.shieldmul * (enabled ? 1 : -1); }
this.updateShieldStrength(); if (slot.m.getEps()) {
} else if (slot.m.dps) { this.totalEps += slot.m.getEps() * (enabled ? 1 : -1);
this.totalDps += slot.m.dps * (enabled ? 1 : -1); }
if (slot.m.getHps()) {
this.totalHps += slot.m.getHps() * (enabled ? 1 : -1);
} }
this.updatePower(); this.updatePowerUsed();
this.updatePowerEnabledString(); this.updatePowerEnabledString();
} }
} }
@@ -615,10 +714,7 @@ export default class Ship {
this.updatePowerPrioritesString(); this.updatePowerPrioritesString();
if (slot.enabled) { // Only update power if the slot is enabled if (slot.enabled) { // Only update power if the slot is enabled
let usage = powerUsageType(slot, slot.m); this.updatePowerUsed();
this.priorityBands[oldPriority][usage] -= slot.m.power;
this.priorityBands[newPriority][usage] += slot.m.power;
this.updatePower();
} }
return true; return true;
} }
@@ -634,7 +730,12 @@ export default class Ship {
* @return {this} The ship instance (for chaining operations) * @return {this} The ship instance (for chaining operations)
*/ */
updateStats(slot, n, old, preventUpdate) { updateStats(slot, n, old, preventUpdate) {
let powerChange = slot == this.standard[0]; let powerGeneratedChange = slot == this.standard[0];
let powerUsedChange = false;
let armourChange = (slot == this.bulkheads) || (n && n.grp == 'hr') || (old && old.grp == 'hr');
let shieldChange = (n && n.grp == 'bsg') || (old && old.grp == 'bsg') || (n && n.grp == 'psg') || (old && old.grp == 'psg') || (n && n.grp == 'sg') || (old && old.grp == 'sg') || (n && n.grp == 'sb') || (old && old.grp == 'sb');
if (old) { // Old modul now being removed if (old) { // Old modul now being removed
switch (old.grp) { switch (old.grp) {
@@ -644,27 +745,27 @@ export default class Ship {
case 'cr': case 'cr':
this.cargoCapacity -= old.cargo; this.cargoCapacity -= old.cargo;
break; break;
case 'hr':
this.armourAdded -= old.armouradd;
break;
case 'sb':
this.shieldMultiplier -= slot.enabled ? old.shieldmul : 0;
break;
} }
if (slot.incCost && old.cost) { if (slot.incCost && old.cost) {
this.totalCost -= old.cost * this.moduleCostMultiplier; this.totalCost -= old.cost * this.moduleCostMultiplier;
} }
if (old.power && slot.enabled) { if (old.getPowerUsage() > 0 && slot.enabled) {
this.priorityBands[slot.priority][powerUsageType(slot, old)] -= old.power; powerUsedChange = true;
powerChange = true;
if (old.dps) {
this.totalDps -= old.dps;
}
} }
this.unladenMass -= old.mass || 0;
if (old.getDps()) {
this.totalDps -= old.getDps();
}
if (old.getEps()) {
this.totalEps -= old.getEps();
}
if (old.getHps()) {
this.totalHps -= old.getHps();
}
this.unladenMass -= old.getMass() || 0;
} }
if (n) { if (n) {
@@ -675,12 +776,6 @@ export default class Ship {
case 'cr': case 'cr':
this.cargoCapacity += n.cargo; this.cargoCapacity += n.cargo;
break; break;
case 'hr':
this.armourAdded += n.armouradd;
break;
case 'sb':
this.shieldMultiplier += slot.enabled ? n.shieldmul : 0;
break;
} }
if (slot.incCost && n.cost) { if (slot.incCost && n.cost) {
@@ -688,49 +783,105 @@ export default class Ship {
} }
if (n.power && slot.enabled) { if (n.power && slot.enabled) {
this.priorityBands[slot.priority][powerUsageType(slot, n)] += n.power; powerUsedChange = true;
powerChange = true;
if (n.dps) {
this.totalDps += n.dps;
}
} }
this.unladenMass += n.mass || 0;
if (n.getDps()) {
this.totalDps += n.getDps();
}
if (n.getEps()) {
this.totalEps += n.getEps();
}
if (n.getHps()) {
this.totalHps += n.getHps();
}
this.unladenMass += n.getMass() || 0;
} }
this.ladenMass = this.unladenMass + this.cargoCapacity + this.fuelCapacity; this.ladenMass = this.unladenMass + this.cargoCapacity + this.fuelCapacity;
this.armour = this.armourAdded + Math.round(this.baseArmour * this.armourMultiplier);
if (!preventUpdate) { if (!preventUpdate) {
if (powerChange) { if (powerGeneratedChange) {
this.updatePower(); this.updatePowerGenerated();
}
if (powerUsedChange) {
this.updatePowerUsed();
}
if (armourChange) {
this.updateArmour();
}
if (shieldChange) {
this.updateShield();
} }
this.updateTopSpeed(); this.updateTopSpeed();
this.updateJumpStats(); this.updateJumpStats();
this.updateShieldStrength();
} }
return this; return this;
} }
/** /**
* Update all power calculations * Update power calculations when amount generated changes
* @return {this} The ship instance (for chaining operations) * @return {this} The ship instance (for chaining operations)
*/ */
updatePower() { updatePowerGenerated() {
let bands = this.priorityBands; this.powerAvailable = this.standard[0].m.getPowerGeneration();
let prevRetracted = 0, prevDeployed = 0; return this;
};
/**
* Update power calculations when amount consumed changes
* @return {this} The ship instance (for chaining operations)
*/
updatePowerUsed() {
let bands = [
{ deployed: 0, retracted: 0, },
{ deployed: 0, retracted: 0, },
{ deployed: 0, retracted: 0, },
{ deployed: 0, retracted: 0, },
{ deployed: 0, retracted: 0, }
];
if (this.cargoHatch.enabled) {
bands[this.cargoHatch.priority].retracted += this.cargoHatch.m.getPowerUsage();
}
for (let slotNum in this.standard) {
const slot = this.standard[slotNum];
if (slot.m && slot.enabled) {
bands[slot.priority][powerUsageType(slot, slot.m)] += slot.m.getPowerUsage();
}
}
for (let slotNum in this.internal) {
const slot = this.internal[slotNum];
if (slot.m && slot.enabled) {
bands[slot.priority][powerUsageType(slot, slot.m)] += slot.m.getPowerUsage();
}
}
for (let slotNum in this.hardpoints) {
const slot = this.hardpoints[slotNum];
if (slot.m && slot.enabled) {
bands[slot.priority][powerUsageType(slot, slot.m)] += slot.m.getPowerUsage();
}
}
// Work out the running totals
let prevRetracted = 0, prevDeployed = 0;
for (let i = 0, l = bands.length; i < l; i++) { for (let i = 0, l = bands.length; i < l; i++) {
let band = bands[i]; let band = bands[i];
prevRetracted = band.retractedSum = prevRetracted + band.retracted; prevRetracted = band.retractedSum = prevRetracted + band.retracted;
prevDeployed = band.deployedSum = prevDeployed + band.deployed + band.retracted; prevDeployed = band.deployedSum = prevDeployed + band.deployed + band.retracted;
} }
this.powerAvailable = this.standard[0].m.pGen; // Update global stats
this.powerRetracted = prevRetracted; this.powerRetracted = prevRetracted;
this.powerDeployed = prevDeployed; this.powerDeployed = prevDeployed;
this.priorityBands = bands;
return this; return this;
}; }
/** /**
* Update top speed and boost * Update top speed and boost
@@ -739,17 +890,52 @@ export default class Ship {
updateTopSpeed() { updateTopSpeed() {
let speeds = Calc.speed(this.unladenMass + this.fuelCapacity, this.speed, this.boost, this.standard[1].m, this.pipSpeed); let speeds = Calc.speed(this.unladenMass + this.fuelCapacity, this.speed, this.boost, this.standard[1].m, this.pipSpeed);
this.topSpeed = speeds['4 Pips']; this.topSpeed = speeds['4 Pips'];
this.topBoost = speeds.boost; this.topBoost = this.canBoost() ? speeds.boost : 0;
return this; return this;
} }
/** /**
* Update Shield strength * Update shield
* @return {this} The ship instance (for chaining operations) * @return {this} The ship instance (for chaining operations)
*/ */
updateShieldStrength() { updateShield() {
this.sgSlot = this.findInternalByGroup('sg'); // Find Shield Generator slot Index if any // Base shield from generator
this.shieldStrength = this.sgSlot && this.sgSlot.enabled ? Calc.shieldStrength(this.hullMass, this.baseShieldStrength, this.sgSlot.m, this.shieldMultiplier) : 0; let baseShield = 0;
let sgSlot = this.findInternalByGroup('sg');
if (sgSlot && sgSlot.enabled) {
baseShield = Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sgSlot.m, 1);
}
let shield = baseShield;
// Shield from boosters
for (let slot of this.hardpoints) {
if (slot.m && slot.m.grp == 'sb') {
shield += baseShield * slot.m.getShieldBoost();
}
}
this.shield = shield;
return this;
}
/**
* Update armour
* @return {this} The ship instance (for chaining operations)
*/
updateArmour() {
// Armour from bulkheads
let bulkhead = this.bulkheads.m;
let armour = this.baseArmour + (this.baseArmour * bulkhead.getHullBoost());
// Armour from HRPs
for (let slot of this.internal) {
if (slot.m && slot.m.grp == 'hr') {
armour += slot.m.getHullReinforcement();
// Hull boost for HRPs is applied against the ship's base armour
armour += this.baseArmour * slot.m.getModValue('hullboost');
}
}
this.armour = armour;
return this; return this;
} }
@@ -759,7 +945,7 @@ export default class Ship {
*/ */
updateJumpStats() { updateJumpStats() {
let fsd = this.standard[2].m; // Frame Shift Drive; let fsd = this.standard[2].m; // Frame Shift Drive;
let { unladenMass, ladenMass, fuelCapacity } = this; let { unladenMass, fuelCapacity } = this;
this.unladenRange = this.calcUnladenRange(); // Includes fuel weight for jump this.unladenRange = this.calcUnladenRange(); // Includes fuel weight for jump
this.fullTankRange = Calc.jumpRange(unladenMass + fuelCapacity, fsd); // Full Tank this.fullTankRange = Calc.jumpRange(unladenMass + fuelCapacity, fsd); // Full Tank
this.ladenRange = this.calcLadenRange(); // Includes full tank and caro this.ladenRange = this.calcLadenRange(); // Includes full tank and caro
@@ -811,16 +997,206 @@ export default class Ship {
return this; return this;
} }
/**
* Update the modifications string
* @return {this} The ship instance (for chaining operations)
*/
oldupdateModificationsString() {
let allMods = new Array();
let bulkheadMods = new Array();
if (this.bulkheads.m && this.bulkheads.m.mods) {
for (let modKey in this.bulkheads.m.mods) {
bulkheadMods.push(Modifications.modifiers.indexOf(modKey) + ':' + Math.round(this.bulkheads.m.getModValue(modKey) * 10000));
}
}
allMods.push(bulkheadMods.join(';'));
for (let slot of this.standard) {
let slotMods = new Array();
if (slot.m && slot.m.mods) {
for (let modKey in slot.m.mods) {
slotMods.push(Modifications.modifiers.indexOf(modKey) + ':' + Math.round(slot.m.getModValue(modKey) * 10000));
}
}
allMods.push(slotMods.join(';'));
}
for (let slot of this.hardpoints) {
let slotMods = new Array();
if (slot.m && slot.m.mods) {
for (let modKey in slot.m.mods) {
slotMods.push(Modifications.modifiers.indexOf(modKey) + ':' + Math.round(slot.m.getModValue(modKey) * 10000));
}
}
allMods.push(slotMods.join(';'));
}
for (let slot of this.internal) {
let slotMods = new Array();
if (slot.m && slot.m.mods) {
for (let modKey in slot.m.mods) {
slotMods.push(Modifications.modifiers.indexOf(modKey) + ':' + Math.round(slot.m.getModValue(modKey) * 10000));
}
}
allMods.push(slotMods.join(';'));
}
this.serialized.modifications = LZString.compressToBase64(allMods.join(',').replace(/,+$/, '')).replace(/\//g, '-');
return this;
}
/**
* Populate the modifications array with modification values from the code
* @param {String} code Serialized modification code
* @param {Array} arr Modification array
*/
decodeModificationsString(code, arr) {
let moduleMods = code.split(',');
for (let i = 0; i < arr.length; i++) {
arr[i] = {};
if (moduleMods.length > i && moduleMods[i] != '') {
let mods = moduleMods[i].split(';');
for (let j = 0; j < mods.length; j++) {
let modElements = mods[j].split(':');
if (modElements[0].match('[0-9]+')) {
arr[i][Modifications.modifiers[modElements[0]]] = Number(modElements[1]);
} else {
arr[i][modElements[0]] = Number(modElements[1]);
}
}
}
}
}
/**
* Update the modifications string.
* This is a binary structure. It starts with a byte that identifies a slot, with bulkheads being ID 0 and moving through
* standard modules, hardpoints, and finally internal modules. It then contains one or more modifications, with each
* modification being a one-byte modification ID and at two-byte modification value. Modification IDs are based on the array
* in Modifications.modifiers. The list of modifications is terminated by a modification ID of -1. The structure then repeats
* for the next module, and the next, and is terminated by a slot ID of -1.
* @return {this} The ship instance (for chaining operations)
*/
updateModificationsString() {
// Start off by gathering the information that we need
let slots = new Array();
let bulkheadMods = new Array();
if (this.bulkheads.m && this.bulkheads.m.mods) {
for (let modKey in this.bulkheads.m.mods) {
bulkheadMods.push({ id: Modifications.modifiers.indexOf(modKey), value: Math.round(this.bulkheads.m.getModValue(modKey) * 10000) });
}
}
slots.push(bulkheadMods);
for (let slot of this.standard) {
let slotMods = new Array();
if (slot.m && slot.m.mods) {
for (let modKey in slot.m.mods) {
slotMods.push({ id: Modifications.modifiers.indexOf(modKey), value: Math.round(slot.m.getModValue(modKey) * 10000) });
}
}
slots.push(slotMods);
}
for (let slot of this.hardpoints) {
let slotMods = new Array();
if (slot.m && slot.m.mods) {
for (let modKey in slot.m.mods) {
slotMods.push({ id: Modifications.modifiers.indexOf(modKey), value: Math.round(slot.m.getModValue(modKey) * 10000) });
}
}
slots.push(slotMods);
}
for (let slot of this.internal) {
let slotMods = new Array();
if (slot.m && slot.m.mods) {
for (let modKey in slot.m.mods) {
slotMods.push({ id: Modifications.modifiers.indexOf(modKey), value: Math.round(slot.m.getModValue(modKey) * 10000) });
}
}
slots.push(slotMods);
}
// Now work out the size of the binary buffer from our modifications array
let bufsize = 0;
for (let slot of slots) {
if (slot.length > 0) {
bufsize = bufsize + 1 + (5 * slot.length) + 1;
}
}
if (bufsize > 0) {
bufsize = bufsize + 1; // For end marker
// Now create and populate the buffer
let buffer = Buffer.alloc(bufsize);
let curpos = 0;
let i = 0;
for (let slot of slots) {
if (slot.length > 0) {
buffer.writeInt8(i, curpos++);
for (let slotMod of slot) {
buffer.writeInt8(slotMod.id, curpos++);
buffer.writeInt32LE(slotMod.value, curpos);
curpos += 4;
}
buffer.writeInt8(-1, curpos++);
}
i++;
}
if (curpos > 0) {
buffer.writeInt8(-1, curpos++);
}
this.serialized.modifications = zlib.gzipSync(buffer).toString('base64').replace(/\//g, '-');
} else {
this.serialized.modifications = null;
}
return this;
}
/**
* Populate the modifications array with modification values from the code.
* See updateModificationsString() for details of the structure.
* @param {String} buffer Buffer holding modification info
* @param {Array} arr Modification array
*/
decodeModificationsStruct(buffer, arr) {
let curpos = 0;
let slot = buffer.readInt8(curpos++);
while (slot != -1) {
let modifications = {};
let modificationId = buffer.readInt8(curpos++);
while (modificationId != -1) {
let modificationValue = buffer.readInt32LE(curpos);
curpos += 4;
modifications[Modifications.modifiers[modificationId]] = modificationValue;
modificationId = buffer.readInt8(curpos++);
}
arr[slot] = modifications;
slot = buffer.readInt8(curpos++);
}
}
/** /**
* Update a slot with a the modul if the id is different from the current id for this slot. * Update a slot with a the modul if the id is different from the current id for this slot.
* Has logic handling ModuleUtils that you may only have 1 of (Shield Generator or Refinery). * Has logic handling ModuleUtils that you may only have 1 of (Shield Generator or Refinery).
* *
* @param {Object} slot The modul slot * @param {Object} slot The modul slot
* @param {Object} m Properties for the selected module * @param {Object} mdef Properties for the selected modul
* @param {boolean} preventUpdate If true, do not update aggregated stats * @param {boolean} preventUpdate If true, do not update aggregated stats
* @return {this} The ship instance (for chaining operations) * @return {this} The ship instance (for chaining operations)
*/ */
use(slot, m, preventUpdate) { use(slot, mdef, preventUpdate) {
// See if the module passed in is really a module or just a definition, and fix it accordingly so that we have a module instance
let m;
if (mdef == null) {
m = null;
} else if (mdef instanceof Module) {
m = mdef;
} else {
m = new Module({ grp: mdef.grp, id: mdef.id });
}
if (slot.m != m) { // Selecting a different modul if (slot.m != m) { // Selecting a different modul
// Slot is an internal slot, is not being emptied, and the selected modul group/type must be of unique // Slot is an internal slot, is not being emptied, and the selected modul group/type must be of unique
if (slot.cat == 2 && m && UNIQUE_MODULES.indexOf(m.grp) != -1) { if (slot.cat == 2 && m && UNIQUE_MODULES.indexOf(m.grp) != -1) {
@@ -843,6 +1219,7 @@ export default class Ship {
case 1: this.serialized.hardpoints = null; break; case 1: this.serialized.hardpoints = null; break;
case 2: this.serialized.internal = null; case 2: this.serialized.internal = null;
} }
this.serialized.modifications = null;
} }
return this; return this;
} }
@@ -855,10 +1232,8 @@ export default class Ship {
*/ */
useBulkhead(index, preventUpdate) { useBulkhead(index, preventUpdate) {
let oldBulkhead = this.bulkheads.m; let oldBulkhead = this.bulkheads.m;
this.bulkheads.index = index; this.bulkheads.m = this.availCS.getBulkhead(index);
this.bulkheads.m = ModuleUtils.bulkheads(this.id, index);
this.bulkheads.discountedCost = this.bulkheads.m.cost * this.moduleCostMultiplier; this.bulkheads.discountedCost = this.bulkheads.m.cost * this.moduleCostMultiplier;
this.armourMultiplier = ArmourMultiplier[index];
this.updateStats(this.bulkheads, this.bulkheads.m, oldBulkhead, preventUpdate); this.updateStats(this.bulkheads, this.bulkheads.m, oldBulkhead, preventUpdate);
this.serialized.standard = null; this.serialized.standard = null;
return this; return this;
@@ -880,7 +1255,7 @@ export default class Ship {
/** /**
* Use the lightest standard ModuleUtils unless otherwise specified * Use the lightest standard ModuleUtils unless otherwise specified
* @param {Object} m Module overrides * @param {Object} m Module override set (standard type => module ID)
* @return {this} The ship instance (for chaining operations) * @return {this} The ship instance (for chaining operations)
*/ */
useLightestStandard(m) { useLightestStandard(m) {
@@ -889,16 +1264,18 @@ export default class Ship {
let standard = this.standard, let standard = this.standard,
// Find lightest Power Distributor that can still boost; // Find lightest Power Distributor that can still boost;
pd = m.pd ? ModuleUtils.standard(4, m.pd) : this.availCS.lightestPowerDist(this.boostEnergy), pd = m.pd ? ModuleUtils.standard(4, m.pd) : this.availCS.lightestPowerDist(this.boostEnergy),
fsd = m.fsd || standard[2].maxClass + 'A', fsd = ModuleUtils.standard(2, m.fsd || standard[2].maxClass + 'A'),
ls = m.ls || standard[3].maxClass + 'D', ls = ModuleUtils.standard(3, m.ls || standard[3].maxClass + 'D'),
s = m.s || standard[5].maxClass + 'D', s = ModuleUtils.standard(5, m.s || standard[5].maxClass + 'D'),
ft = m.ft ? ModuleUtils.standard(6, m.ft) : standard[6].m, // Use existing fuel tank unless specified
updated; updated;
this.useBulkhead(0) this.useBulkhead(0)
.use(standard[2], ModuleUtils.standard(2, fsd)) // FSD .use(standard[2], fsd) // FSD
.use(standard[3], ModuleUtils.standard(3, ls)) // Life Support .use(standard[3], ls) // Life Support
.use(standard[5], ModuleUtils.standard(5, s)) // Sensors .use(standard[5], s) // Sensors
.use(standard[4], pd); // Power Distributor .use(standard[4], pd) // Power Distributor
.use(standard[6], ft); // Fuel Tank
// Thrusters and Powerplant must be determined after all other ModuleUtils are mounted // Thrusters and Powerplant must be determined after all other ModuleUtils are mounted
// Loop at least once to determine absolute lightest PD and TH // Loop at least once to determine absolute lightest PD and TH
@@ -906,14 +1283,14 @@ export default class Ship {
updated = false; updated = false;
// Find lightest Thruster that still works for the ship at max mass // Find lightest Thruster that still works for the ship at max mass
let th = m.th ? ModuleUtils.standard(1, m.th) : this.availCS.lightestThruster(this.ladenMass); let th = m.th ? ModuleUtils.standard(1, m.th) : this.availCS.lightestThruster(this.ladenMass);
if (th !== standard[1].m) { if (!isEqual.isEqual(th, standard[1].m)) {
this.use(standard[1], th); this.use(standard[1], th);
updated = true; updated = true;
} }
// Find lightest Power plant that can power the ship // Find lightest Power plant that can power the ship
let pp = m.pp ? ModuleUtils.standard(0, m.pp) : this.availCS.lightestPowerPlant(Math.max(this.powerRetracted, this.powerDeployed), m.ppRating); let pp = m.pp ? ModuleUtils.standard(0, m.pp) : this.availCS.lightestPowerPlant(Math.max(this.powerRetracted, this.powerDeployed), m.ppRating);
if (pp !== standard[0].m) { if (!isEqual.isEqual(pp, standard[0].m)) {
this.use(standard[0], pp); this.use(standard[0], pp);
updated = true; updated = true;
} }
@@ -964,5 +1341,4 @@ export default class Ship {
} }
return this; return this;
} }
} }

View File

@@ -0,0 +1,126 @@
import * as ModuleUtils from './ModuleUtils';
import { canMount } from '../utils/SlotFunctions';
/**
* Standard / typical role for multi-purpose or combat (if shielded with better bulkheads)
* @param {Ship} ship Ship instance
* @param {Boolean} shielded True if shield generator should be included
* @param {integer} bulkheadIndex Bulkhead to use see Constants.BulkheadNames
*/
export function multiPurpose(ship, shielded, bulkheadIndex) {
ship.useStandard('A')
.use(ship.standard[3], ModuleUtils.standard(3, ship.standard[3].maxClass + 'D')) // D Life Support
.use(ship.standard[5], ModuleUtils.standard(5, ship.standard[5].maxClass + 'D')) // D Sensors
.useBulkhead(bulkheadIndex);
if (shielded) {
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);
return true;
}
});
}
}
/**
* Trader Role
* @param {Ship} ship Ship instance
* @param {Boolean} shielded True if shield generator should be included
* @param {Object} standardOpts [Optional] Standard module optional overrides
*/
export function trader(ship, shielded, standardOpts) {
let sg = shielded ? ship.getAvailableModules().lightestShieldGenerator(ship.hullMass) : null;
for (let i = ship.internal.length; i--;) {
let slot = ship.internal[i];
if (sg && canMount(ship, slot, 'sg', sg.class)) {
ship.use(slot, sg);
sg = null;
} else {
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
}
}
ship.useLightestStandard(standardOpts);
}
/**
* Explorer Role
* @param {Ship} ship Ship instance
* @param {Boolean} planetary True if Planetary Vehicle Hangar (PVH) should be included
*/
export function explorer(ship, planetary) {
let standardOpts = { ppRating: 'A' },
intLength = ship.internal.length,
heatSinkCount = 2, // Fit 2 heat sinks if possible
afmUnitCount = 2, // Fit 2 AFM Units if possible
shieldNext = planetary,
sgSlot,
fuelScoopSlot,
pvhSlot,
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
if (!planetary) { // Non-planetary explorers don't really need to boost
standardOpts.pd = '1D';
}
ship.setSlotEnabled(ship.cargoHatch, false)
.use(ship.internal[--intLength], ModuleUtils.internal('2f')); // Advanced Discovery Scanner
if (!planetary || intLength > 3) { // Don't mount a DDS on planetary explorer ships too small for both a PVH and DDS
ship.use(ship.internal[--intLength], ModuleUtils.internal('2i')); // Detailed Surface Scanner
}
for (let i = 0; i < intLength; i++) {
let slot = ship.internal[i];
let nextSlot = (i + 1) < intLength ? ship.internal[i + 1] : null;
// Fit best possible Fuel Scoop
if (!fuelScoopSlot && canMount(ship, slot, 'fs')) {
fuelScoopSlot = slot;
ship.use(slot, ModuleUtils.findInternal('fs', slot.maxClass, 'A'));
ship.setSlotEnabled(slot, true);
// Mount a Shield generator if possible AND an AFM Unit has been mounted already (Guarantees at least 1 AFM Unit)
} else if (!sgSlot && shieldNext && canMount(ship, slot, 'sg', sg.class) && !canMount(ship, nextSlot, 'sg', sg.class)) {
sgSlot = slot;
shieldNext = false;
ship.use(slot, sg);
ship.setSlotEnabled(slot, true);
// if planetary explorer and the next slot cannot mount a PVH or the next modul to mount is a SG
} else if (planetary && !pvhSlot && canMount(ship, slot, 'pv') && (shieldNext || !canMount(ship, nextSlot, 'pv', 2))) {
pvhSlot = slot;
ship.use(slot, ModuleUtils.findInternal('pv', Math.min(Math.floor(pvhSlot.maxClass / 2) * 2, 6), 'G'));
ship.setSlotEnabled(slot, false); // Disabled power for PVH
shieldNext = !sgSlot;
} else if (afmUnitCount > 0 && canMount(ship, slot, 'am')) {
afmUnitCount--;
ship.use(slot, ModuleUtils.findInternal('am', slot.maxClass, 'A'));
ship.setSlotEnabled(slot, false); // Disabled power for AFM Unit
shieldNext = !sgSlot;
} else {
ship.use(slot, null);
}
}
for (let s of ship.hardpoints) {
if (s.maxClass == 0 && heatSinkCount) { // Mount up to 2 heatsinks
ship.use(s, ModuleUtils.hardpoints('02'));
ship.setSlotEnabled(s, heatSinkCount == 2); // Only enable a single Heatsink
heatSinkCount--;
} else {
ship.use(s, null);
}
}
if (sgSlot) {
// The SG and Fuel scoop to not need to be powered at the same time
if (sgSlot.m.getPowerUsage() > fuelScoopSlot.m.getPowerUsage()) { // The Shield generator uses the most power
ship.setSlotEnabled(fuelScoopSlot, false);
} else { // The Fuel scoop uses the most power
ship.setSlotEnabled(sgSlot, false);
}
}
ship.useLightestStandard(standardOpts);
}

View File

@@ -1,7 +1,28 @@
import React from 'react'; import React from 'react';
import cn from 'classnames'; import cn from 'classnames';
import { isShieldGenerator } from '../shipyard/ModuleUtils'; import { isShieldGenerator } from '../shipyard/ModuleUtils';
import Module from '../shipyard/Module';
import { Infinite } from '../components/SvgIcons'; import { Infinite } from '../components/SvgIcons';
import Persist from '../stores/Persist';
/**
* Determine if a slot on a ship can mount a module of a particular class and group
* @param {Object} ship Ship object
* @param {Object} slot Slot object
* @param {String} group Module group/type abbrivation/code
* @param {Integer} clazz [Optional] Module Class/Size
* @return {Boolean} True if the slot can mount the module
*/
export function canMount(ship, slot, group, clazz) {
if (slot &&
(!slot.eligible || slot.eligible[group]) &&
(group != 'pcq' || (ship.luxuryCabins && ship.luxuryCabins === true)) &&
(group != 'fh' || (ship.fighterHangars && ship.fighterHangars === true)) &&
(clazz === undefined || slot.maxClass >= clazz)) {
return true;
}
return false;
}
/** /**
* Returns the translate name for the module mounted in the specified * Returns the translate name for the module mounted in the specified
@@ -76,7 +97,9 @@ const PROP_BLACKLIST = {
eddbID: 1, eddbID: 1,
edID: 1, edID: 1,
id: 1, id: 1,
index: 1,
'class': 1, 'class': 1,
rating: 1,
maxfuel: 1, maxfuel: 1,
fuelmul: 1, fuelmul: 1,
fuelpower: 1, fuelpower: 1,
@@ -95,30 +118,30 @@ const PROP_BLACKLIST = {
ssdam: 1, ssdam: 1,
mjdps: 1, mjdps: 1,
mjeps: 1, mjeps: 1,
M: 1,
P: 1,
mass: 1, mass: 1,
cost: 1, cost: 1,
recover: 1, recover: 1,
weaponcapacity: 1, wepcap: 1,
weaponrecharge: 1, weprate: 1,
enginecapacity: 1, engcap: 1,
enginerecharge: 1, engrate: 1,
systemcapacity: 1, syscap: 1,
systemrecharge: 1 sysrate: 1,
breachdps: 1,
breachmin: 1,
breachmax: 1,
integrity: 1
}; };
const TERM_LOOKUP = { const TERM_LOOKUP = {
pGen: 'power', pgen: 'power',
armouradd: 'armour', armouradd: 'armour',
shieldmul: 'multiplier',
rof: 'ROF', rof: 'ROF',
dps: 'DPS' dps: 'DPS'
}; };
const FORMAT_LOOKUP = { const FORMAT_LOOKUP = {
time: 'time', time: 'time'
shieldmul: 'rPct'
}; };
const UNIT_LOOKUP = { const UNIT_LOOKUP = {
@@ -129,7 +152,7 @@ const UNIT_LOOKUP = {
recharge: 'MJ', recharge: 'MJ',
rangeLS: 'Ls', rangeLS: 'Ls',
power: 'MJ', power: 'MJ',
pGen: 'MJ', pgen: 'MJ',
rof: 'ps' rof: 'ps'
}; };
@@ -158,10 +181,10 @@ function diffClass(a, b, negative) {
*/ */
function diff(format, mVal, mmVal) { function diff(format, mVal, mmVal) {
if (mVal == Infinity) { if (mVal == Infinity) {
return <Infinite/>; return '∞';
} else { } else {
let diff = mVal - mmVal; let diff = mVal - mmVal;
if (!diff || !mVal || diff == mVal || Math.abs(diff) == Infinity) { if (!diff || mVal === undefined || diff == mVal || Math.abs(diff) == Infinity) {
return format(mVal); return format(mVal);
} }
return `${format(mVal)} (${diff > 0 ? '+' : ''}${format(diff)})`; return `${format(mVal)} (${diff > 0 ? '+' : ''}${format(diff)})`;
@@ -180,80 +203,96 @@ function diff(format, mVal, mmVal) {
* @return {React.Component} Component to be rendered * @return {React.Component} Component to be rendered
*/ */
export function diffDetails(language, m, mm) { export function diffDetails(language, m, mm) {
mm = mm || {};
let { formats, translate, units } = language; let { formats, translate, units } = language;
let propDiffs = []; let propDiffs = [];
let mCost = m.cost || 0;
let mmCost = mm ? mm.cost : 0;
if (mCost != mmCost) propDiffs.push(<div key='cost'>{translate('cost')}: <span className={diffClass(mCost, mmCost, true) }>{mCost ? Math.round(mCost * (1 - Persist.getModuleDiscount())) : 0}{units.CR}</span></div>);
let mMass = m.mass || 0; let mMass = m.mass || 0;
let mmMass = mm.mass || 0; let mmMass = mm ? mm.getMass() : 0;
let massDiff = mMass - mmMass; 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>);
let capDiff = (m.fuel || m.cargo || 0) - (mm.fuel || mm.cargo || 0);
let mPowerUsage = m.power || 0;
let mmPowerUsage = mm ? mm.getPowerUsage() : 0;
if (mPowerUsage != mmPowerUsage) propDiffs.push(<div key='power'>{translate('power')}: <span className={diffClass(mPowerUsage, mmPowerUsage, true)}>{diff(formats.round, mPowerUsage, mmPowerUsage)}{units.MJ}</span></div>);
// for (let p in m) {
// if (!PROP_BLACKLIST[p] && !isNaN(m[p])) {
// let mVal = m[p] === null ? Infinity : m[p];
// let mmVal = mm[p] === null ? Infinity : mm[p];
// let format = formats[FORMAT_LOOKUP[p]] || formats.round;
// propDiffs.push(<div key={p}>
// {`${translate(TERM_LOOKUP[p] || p)}: `}
// <span className={diffClass(mVal, mmVal, p == 'power')}>{diff(format, mVal, mmVal)}{units[UNIT_LOOKUP[p]]}</span>
// </div>);
// }
// }
let mDps = m.damage * (m.rpshot || 1) * m.rof || 0;
let mmDps = mm ? mm.getDps() || 0 : 0;
if (mDps != mmDps) propDiffs.push(<div key='dps'>{translate('dps')}: <span className={diffClass(mmDps, mDps, true)}>{diff(formats.round, mDps, mmDps)}</span></div>);
let mAffectsShield = isShieldGenerator(m.grp) || m.grp == 'sb'; let mAffectsShield = isShieldGenerator(m.grp) || m.grp == 'sb';
let mmAffectsShield = isShieldGenerator(mm.grp) || mm.grp == 'sb'; let mmAffectsShield = isShieldGenerator(mm ? mm.grp : null) || mm && mm.grp == 'sb';
propDiffs.push(<div key='cost'>{translate('cost')}: <span className={diffClass(m.cost, mm.cost, true) }>{formats.int(m.cost || 0)}{units.CR}</span></div>);
propDiffs.push(<div key='mass'>{translate('mass')}: <span className={diffClass(mMass, mm.mass, true)}>{diff(formats.round, mMass, mmMass)}{units.T}</span></div>);
for (let p in m) {
if (!PROP_BLACKLIST[p] && !isNaN(m[p])) {
let mVal = m[p] === null ? Infinity : m[p];
let mmVal = mm[p] === null ? Infinity : mm[p];
let format = formats[FORMAT_LOOKUP[p]] || formats.round;
propDiffs.push(<div key={p}>
{`${translate(TERM_LOOKUP[p] || p)}: `}
<span className={diffClass(mVal, mmVal, p == 'power')}>{diff(format, mVal, mmVal)}{units[UNIT_LOOKUP[p]]}</span>
</div>);
}
}
if (mAffectsShield || mmAffectsShield) { if (mAffectsShield || mmAffectsShield) {
let shield = this.calcShieldStrengthWith(); // Get shield strength regardless of slot active / inactive let shield = this.calcShieldStrengthWith(); // Get shield strength regardless of slot active / inactive
let newShield = 0; let newShield = 0;
if (mAffectsShield) { if (mAffectsShield) {
if (m.grp == 'sb') { // Both m and mm must be utility modules if this is true if (m.grp == 'sb') { // Both m and mm must be utility modules if this is true
newShield = this.calcShieldStrengthWith(null, m.shieldmul - (mm.shieldmul || 0)); newShield = this.calcShieldStrengthWith(null, m.shieldboost - (mm ? mm.getShieldBoost() || 0 : 0));
} else { } else {
newShield = this.calcShieldStrengthWith(m); newShield = this.calcShieldStrengthWith(m);
} }
} else {
// Old module must be a shield booster
newShield = this.calcShieldStrengthWith(null, -mm.getShieldBoost());
} }
let sgDiffClass = Math.round((newShield - shield) * 100) / 100 == 0 ? 'muted' : (newShield > shield ? 'secondary' : 'warning'); let sgDiffClass = Math.round((newShield - shield) * 100) / 100 == 0 ? 'muted' : (newShield > shield ? 'secondary' : 'warning');
propDiffs.push(<div key='shields'>{`${translate('shields')}: `}<span className={sgDiffClass}>{diff(formats.int, newShield, shield)}{units.MJ}</span></div>); propDiffs.push(<div key='shields'>{translate('shields')}: <span className={sgDiffClass}>{diff(formats.int, newShield, shield)}{units.MJ}</span></div>);
} }
if (m.grp == 'pd') { if (m.grp == 'pd') {
propDiffs.push(<div key='wep'> propDiffs.push(<div key='wep'>
{`${translate('WEP')}: `} {`${translate('WEP')}: `}
<span className={diffClass(m.weaponcapacity, mm.weaponcapacity)}>{m.weaponcapacity}{units.MJ}</span> <span className={diffClass(m.wepcap, mm.getWeaponsCapacity())}>{m.wepcap}{units.MJ}</span>
{' / '} {' / '}
<span className={diffClass(m.weaponrecharge, mm.weaponrecharge)}>{m.weaponrecharge}{units.MW}</span> <span className={diffClass(m.weprate, mm.getWeaponsRechargeRate())}>{m.weprate}{units.MW}</span>
</div>); </div>);
propDiffs.push(<div key='sys'> propDiffs.push(<div key='sys'>
{`${translate('SYS')}: `} {`${translate('SYS')}: `}
<span className={diffClass(m.systemcapacity, mm.systemcapacity)}>{m.systemcapacity}{units.MJ}</span> <span className={diffClass(m.syscap, mm.getSystemsCapacity())}>{m.syscap}{units.MJ}</span>
{' / '} {' / '}
<span className={diffClass(m.systemrecharge, mm.systemrecharge)}>{m.systemrecharge}{units.MW}</span> <span className={diffClass(m.sysrate, mm.getSystemsRechargeRate())}>{m.sysrate}{units.MW}</span>
</div>); </div>);
propDiffs.push(<div key='eng'> propDiffs.push(<div key='eng'>
{`${translate('ENG')}: `} {`${translate('ENG')}: `}
<span className={diffClass(m.enginecapacity, mm.enginecapacity)}>{m.enginecapacity}{units.MJ}</span> <span className={diffClass(m.engcap, mm.getEnginesCapacity())}>{m.engcap}{units.MJ}</span>
{' / '} {' / '}
<span className={diffClass(m.enginerecharge, mm.enginerecharge)}>{m.enginerecharge}{units.MW}</span> <span className={diffClass(m.engrate, mm.getEnginesRechargeRate())}>{m.engrate}{units.MW}</span>
</div>); </div>);
} }
let massDiff = mMass - mmMass;
let mCap = m.fuel || m.cargo || 0;
let mmCap = mm ? mm.fuel || mm.cargo || 0 : 0;
let capDiff = mCap - mmCap;
if (m.grp == 'fsd' || massDiff || capDiff) { if (m.grp == 'fsd' || massDiff || capDiff) {
let fsd = m.grp == 'fsd' ? m : null; let fsd = m.grp == 'fsd' ? m : null;
let maxRange = this.calcUnladenRange(massDiff, m.fuel, fsd); let maxRange = this.calcUnladenRange(massDiff, m.fuel, fsd);
let ladenRange = this.calcLadenRange(massDiff + capDiff, m.fuel, fsd); let ladenRange = this.calcLadenRange(massDiff + capDiff, m.fuel, fsd);
if (maxRange != this.unladenRange) { if (maxRange != this.unladenRange) {
propDiffs.push(<div key='maxRange'>{`${translate('max')} ${translate('jump range')}: `}<span className={maxRange > this.unladenRange ? 'secondary' : 'warning'}>{formats.round(maxRange)}{units.LY}</span></div>); propDiffs.push(<div key='maxRange'>{translate('max')} {translate('jump range')}: <span className={maxRange > this.unladenRange ? 'secondary' : 'warning'}>{formats.round(maxRange)}{units.LY}</span></div>);
} }
if (ladenRange != this.ladenRange) { if (ladenRange != this.ladenRange) {
propDiffs.push(<div key='unladenRange'>{`${translate('laden')} ${translate('jump range')}: `}<span className={ladenRange > this.ladenRange ? 'secondary' : 'warning'}>{formats.round(ladenRange)}{units.LY}</span></div>); propDiffs.push(<div key='unladenRange'>{translate('laden')} {translate('jump range')}: <span className={ladenRange > this.ladenRange ? 'secondary' : 'warning'}>{formats.round(ladenRange)}{units.LY}</span></div>);
} }
} }
return <div className='cap' style={{ whiteSpace: 'nowrap' }}>{propDiffs}</div>; return propDiffs ? <div className='cap' style={{ whiteSpace: 'nowrap' }}>{propDiffs}</div> : null;
} }

14
src/index.html Executable file → Normal file
View File

@@ -1,7 +1,7 @@
<!DOCTYPE html> <!DOCTYPE html>
<html {%= o.htmlWebpackPlugin.options.appCache ? 'manifest=/' + o.htmlWebpackPlugin.options.appCache : '' %} > <html {%= o.htmlWebpackPlugin.options.appCache ? 'manifest=/' + o.htmlWebpackPlugin.options.appCache : '' %} >
<head> <head>
<title>Coriolis</title> <title>Coriolis EDCD Edition</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="stylesheet" href="{%= o.htmlWebpackPlugin.files.css[0] %}"> <link rel="stylesheet" href="{%= o.htmlWebpackPlugin.files.css[0] %}">
<!-- Standard headers --> <!-- Standard headers -->
@@ -25,12 +25,11 @@
</head> </head>
<body style="background-color:#000;"> <body style="background-color:#000;">
<section id="coriolis"></section> <section id="coriolis"></section>
<footer> <script>
<div class="right cap"> window.CORIOLIS_GAPI_KEY = '{%= o.htmlWebpackPlugin.options.gapiKey %}';
<a href="https://github.com/cmmcleod/coriolis/releases/" target="_blank" title="Coriolis Github Project">{%= o.htmlWebpackPlugin.options.version %} - {%= new Date().toISOString().slice(0, 10) %}</a> window.CORIOLIS_VERSION = '{%= o.htmlWebpackPlugin.options.version %}';
</div> window.CORIOLIS_DATE = '{%= new Date().toISOString().slice(0, 10) %}';
</footer> </script>
<script src="{%= o.htmlWebpackPlugin.files.chunks.lib.entry %}" charset="utf-8" crossorigin="anonymous"></script> <script src="{%= o.htmlWebpackPlugin.files.chunks.lib.entry %}" charset="utf-8" crossorigin="anonymous"></script>
<script src="{%= o.htmlWebpackPlugin.files.chunks.app.entry %}" charset="utf-8" crossorigin="anonymous"></script> <script src="{%= o.htmlWebpackPlugin.files.chunks.app.entry %}" charset="utf-8" crossorigin="anonymous"></script>
<script> <script>
@@ -41,7 +40,6 @@
})(window,document,'script','//www.google-analytics.com/analytics.js','ga'); })(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', '{%= o.htmlWebpackPlugin.options.uaTracking %}', 'auto'); ga('create', '{%= o.htmlWebpackPlugin.options.uaTracking %}', 'auto');
{% } %} {% } %}
window.CORIOLIS_GAPI_KEY = '{%= o.htmlWebpackPlugin.options.gapiKey %}';
</script> </script>
</body> </body>
</html> </html>

View File

@@ -23,9 +23,7 @@ html, body {
height: 100%; height: 100%;
width: 100%; width: 100%;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
overflow-x: hidden; overflow: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
} }
body { body {
@@ -42,17 +40,23 @@ div, a, li {
#coriolis { #coriolis {
width: 100%; width: 100%;
min-height: 95%; height: 100%;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
} }
.page { .page {
margin: 0; margin: 0;
padding: 0.5em 0; padding: 0.5em;
width: 100%; width: 100%;
min-height: 100%; min-height: 100%;
clear: both; clear: both;
text-align: center; text-align: center;
box-sizing: border-box; box-sizing: border-box;
.largePhone({
padding: 0.5em 0.25em;
});
} }
.l { .l {
@@ -159,4 +163,4 @@ footer {
float: right; float: right;
text-align: right; text-align: right;
} }
} }

View File

@@ -97,6 +97,7 @@ header {
input { input {
border: none; border: none;
background-color: transparent;
text-align: right; text-align: right;
font-size: 1em; font-size: 1em;
font-family: @fStandard; font-family: @fStandard;
@@ -171,7 +172,7 @@ header {
&:visited { &:visited {
color: @warning; color: @warning;
} }
&:hover { .no-touch &:hover {
color: teal; color: teal;
} }
&.active { &.active {

View File

@@ -78,7 +78,7 @@ select {
stroke-width: 0.5em; stroke-width: 0.5em;
stroke: @primary-disabled; stroke: @primary-disabled;
&:hover { .no-touch &:hover {
border-color: @primary; border-color: @primary;
color: @primary; color: @primary;
stroke: @primary; stroke: @primary;
@@ -95,14 +95,14 @@ select {
color: @warning-disabled; color: @warning-disabled;
stroke: @warning-disabled; stroke: @warning-disabled;
&:hover { .no-touch &:hover {
border-color: @warning; border-color: @warning;
color: @warning; color: @warning;
stroke: @warning; stroke: @warning;
} }
} }
&.disabled { &.disabled, &.disabled:hover {
cursor: not-allowed; cursor: not-allowed;
border-color: @disabled; border-color: @disabled;
color: @disabled; color: @disabled;
@@ -150,4 +150,10 @@ select {
} }
} }
&.standard {
ul {
width: 16.25em;
}
}
} }

View File

@@ -12,6 +12,8 @@
background-color: @primary-bg; background-color: @primary-bg;
border: 1px solid @primary-disabled; border: 1px solid @primary-disabled;
color: @fg; color: @fg;
stroke: @fg;
stroke-width: 20;
fill: @fg; fill: @fg;
.details-container { .details-container {
@@ -54,6 +56,8 @@
font-size: 1.2em; font-size: 1.2em;
width: 1.2em; width: 1.2em;
color: @primary-disabled; color: @primary-disabled;
stroke: @primary-disabled;
stroke-width: 20;
border-right: 1px solid @primary-disabled; border-right: 1px solid @primary-disabled;
box-sizing: border-box; box-sizing: border-box;
padding-top: 0.2em; padding-top: 0.2em;
@@ -64,6 +68,8 @@
text-transform: uppercase; text-transform: uppercase;
font-size: 1.3em; font-size: 1.3em;
color: lighten(@primary-bg, 12%); color: lighten(@primary-bg, 12%);
stroke: lighten(@primary-bg, 12%);
stroke-width: 20;
text-align: center; text-align: center;
letter-spacing: 0.1em; letter-spacing: 0.1em;
line-height: 1.7em; line-height: 1.7em;
@@ -71,12 +77,16 @@
&.selected { &.selected {
color: @primary-bg; color: @primary-bg;
stroke: @primary-bg;
stroke-width: 20;
fill: @primary-bg; fill: @primary-bg;
background-color: @primary; background-color: @primary;
border: 1px solid @primary; border: 1px solid @primary;
z-index: 1; z-index: 1;
.sz { .sz {
color: @primary; color: @primary;
stroke: @primary;
stroke-width: 20;
background-color: @primary-bg; background-color: @primary-bg;
border-right: 1px solid @primary; border-right: 1px solid @primary;
} }

View File

@@ -2,7 +2,7 @@
.user-select-none(); .user-select-none();
cursor: pointer; cursor: pointer;
&:hover { .no-touch &:hover {
color: @primary; color: @primary;
} }
} }

View File

@@ -33,6 +33,10 @@ thead {
&.lft { &.lft {
border-left: 1px solid @primary-bg; border-left: 1px solid @primary-bg;
} }
&.rgt {
border-right: 1px solid @primary-bg;
}
} }
} }
@@ -43,23 +47,26 @@ tbody tr {
text-align: right; text-align: right;
} }
td { .no-touch &.highlight:hover, .no-touch &.highlighted {
line-height: 1.4em; background-color: @warning-bg;
padding: 0 0.3em;
&.val {
border: 1px solid @primary-disabled;
}
&.lbl {
border: 1px solid @primary-disabled;
text-transform: uppercase;
color: @primary-bg;
background-color: @primary-disabled;
}
} }
} }
td { td {
line-height: 1.4em;
padding: 0 0.3em;
&.val {
border: 1px solid @primary-disabled;
}
&.lbl {
border: 1px solid @primary-disabled;
text-transform: uppercase;
color: @primary-bg;
background-color: @primary-disabled;
}
&.tl { &.tl {
text-align: left; text-align: left;
padding-left: 0.7em; padding-left: 0.7em;

79
src/migrate.html Normal file
View File

@@ -0,0 +1,79 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Coriolis: Migrate to HTTPS</title>
</head>
<body>
<script>
function fromJsonToObject(str) {
try {
var o = JSON.parse(str);
if (o instanceof Object && !(o instanceof Array)) {
return o;
}
} catch (e) { }
return {};
}
function listener(event) {
var origin = event.origin || event.originalEvent.origin;
var source = event.source;
var data = event.message;
if (window.location.href.indexOf('coriolis') != -1 && origin !== "http://coriolis.io") {
return;
}
try {
var builds = fromJsonToObject(localStorage.getItem('builds'));
var comparisons = fromJsonToObject(localStorage.getItem('comparisons'));
// merge builds
if (typeof data.builds == 'object') {
for (var bName in data.builds) {
// Build existing in http and does not existing in HTTPS
if (data.builds.hasOwnProperty(bName) && !builds[bName]) {
build[bName] = data.builds[bName];
}
}
localStorage.setItem('builds', JSON.stringify(builds));
}
// merge comparisons
if (typeof data.comparisons == 'object') {
for (var comp in data.comparisons) {
// Comparison existing in http and does not existing in HTTPS
if (data.comparisons.hasOwnProperty(comp) && !data.comparisons[comp]) {
comparisons[comp] = data.comparisons[comp];
}
}
localStorage.setItem('comparisons', JSON.stringify(comparisons));
}
source.postMessage({
success: true,
buildCount: Object.keys(builds).length,
comparisonCount: Object.keys(comparisons).length
}, origin);
} catch (e) {
source.postMessage({ success: false, error: e }, origin);
}
}
if (window.addEventListener){
window.addEventListener("message", listener, false);
} else {
window.attachEvent("onmessage", listener);
}
</script>
</body>
</html>

View File

@@ -76,7 +76,11 @@
"class": { "type": "integer", "minimum": 2, "maximum": 8 }, "class": { "type": "integer", "minimum": 2, "maximum": 8 },
"rating": { "$ref": "#/definitions/standardRatings" }, "rating": { "$ref": "#/definitions/standardRatings" },
"enabled": { "type": "boolean" }, "enabled": { "type": "boolean" },
"priority": { "type": "integer", "minimum": 1, "maximum": 5 } "priority": { "type": "integer", "minimum": 1, "maximum": 5 },
"name": {
"description": "The name identifing the thrusters (if applicable), e.g. 'Enhanced Performance'",
"type": "string"
}
} }
}, },
"frameShiftDrive": { "frameShiftDrive": {

View File

@@ -1,16 +1,28 @@
var path = require('path'); var path = require('path');
var exec = require('child_process').exec;
var webpack = require('webpack'); var webpack = require('webpack');
var pkgJson = require('./package'); var pkgJson = require('./package');
var HtmlWebpackPlugin = require("html-webpack-plugin"); var HtmlWebpackPlugin = require("html-webpack-plugin");
var ExtractTextPlugin = require("extract-text-webpack-plugin"); var ExtractTextPlugin = require("extract-text-webpack-plugin");
function CopyDirPlugin(source, destination) {
this.source = source;
this.destination = destination;
}
CopyDirPlugin.prototype.apply = function(compiler) {
compiler.plugin('done', function() {
console.log(compiler.outputPath, this.destination);
exec('cp -r ' + this.source + ' ' + path.join(compiler.outputPath, this.destination));
}.bind(this));
};
module.exports = { module.exports = {
devtool: 'eval', devtool: 'eval',
devServer: { devServer: {
headers: { "Access-Control-Allow-Origin": "*" } headers: { "Access-Control-Allow-Origin": "*" }
}, },
entry: { entry: {
app: [ 'webpack-dev-server/client?http://localhost:3300', 'webpack/hot/only-dev-server', path.join(__dirname, "src/app/index.js") ], app: [ 'webpack-dev-server/client?http://0.0.0.0:3300', 'webpack/hot/only-dev-server', path.join(__dirname, "src/app/index.js") ],
lib: ['d3', 'react', 'react-dom', 'classnames', 'fbemitter', 'lz-string'] lib: ['d3', 'react', 'react-dom', 'classnames', 'fbemitter', 'lz-string']
}, },
resolve: { resolve: {
@@ -23,6 +35,7 @@ module.exports = {
publicPath: '/' publicPath: '/'
}, },
plugins: [ plugins: [
new CopyDirPlugin(path.join(__dirname, 'src/.htaccess'), ''),
new webpack.optimize.CommonsChunkPlugin('lib', 'lib.js'), new webpack.optimize.CommonsChunkPlugin('lib', 'lib.js'),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
inject: false, inject: false,

View File

@@ -74,6 +74,8 @@ module.exports = {
}), }),
new CopyDirPlugin(path.join(__dirname, 'src/schemas'), 'schemas'), new CopyDirPlugin(path.join(__dirname, 'src/schemas'), 'schemas'),
new CopyDirPlugin(path.join(__dirname, 'src/images/logo/*'), ''), new CopyDirPlugin(path.join(__dirname, 'src/images/logo/*'), ''),
new CopyDirPlugin(path.join(__dirname, 'src/migrate.html'), ''),
new CopyDirPlugin(path.join(__dirname, 'src/.htaccess'), ''),
new AppCachePlugin({ new AppCachePlugin({
network: ['*'], network: ['*'],
settings: ['prefer-online'], settings: ['prefer-online'],