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
*.log
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
@@ -9,13 +8,11 @@ Coriolis was created using assets and imagery from Elite: Dangerous, with the pe
## 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
Chat to us on [HipChat](https://www.hipchat.com/gfYQiZcmy)!
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).
Chat to us on [Discord](https://discord.gg/0uwCh6R62aPRjk9w)!
## Development
@@ -29,7 +26,7 @@ See the [Data wiki](https://github.com/cmmcleod/coriolis-data/wiki) for details
## 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/).
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",
"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"
}
],

View File

@@ -2,31 +2,31 @@
{
"shipId": "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"
},
{
"shipId": "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"
},
{
"shipId": "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"
},
{
"shipId": "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"
},
{
"shipId": "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)"
}
]

View File

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

View File

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

View File

@@ -10,7 +10,7 @@ import { getLanguage } from '../src/app/i18n/Language';
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 ModalImport = require('../src/app/components/ModalImport').default;
const mockContext = {
@@ -25,8 +25,6 @@ describe('Import Modal', function() {
onWindowResize: jest.genMockFunction()
};
MockRouter.go = jest.genMockFunction();
let modal, render, ContextProvider = Utils.createContextProvider(mockContext);
/**
@@ -110,7 +108,8 @@ describe('Import Modal', function() {
it('catches an invalid backup', function() {
const importData = require('./fixtures/valid-backup');
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"');
expect(modal.state.importValid).toBeFalsy();
@@ -140,9 +139,10 @@ describe('Import Modal', function() {
expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null);
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/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() {

View File

@@ -2,7 +2,7 @@ import Ship from '../src/app/shipyard/Ship';
import { Ships } from 'coriolis-data/dist';
import * as ModuleUtils from '../src/app/shipyard/ModuleUtils';
describe("Ship Factory", function() {
describe("Ship", function() {
it("can build all ships", function() {
for (let s in Ships) {
@@ -16,7 +16,7 @@ describe("Ship Factory", function() {
ship.buildWith(shipData.defaults);
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.powerAvailable).toBeGreaterThan(0, s + ' powerAvailable');
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
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 id is null');
expect(anaconda.internal[2].m).toEqual(null, 'Anaconda default shield generator slot is empty');
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');
@@ -133,8 +132,7 @@ describe("Ship Factory", function() {
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 id is null');
expect(anaconda.internal[4].m).toEqual(null, 'Anaconda original fuel scoop slot is empty');
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');
});
@@ -150,8 +148,7 @@ describe("Ship Factory", function() {
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 id is null');
expect(anaconda.internal[4].m).toEqual(null, 'Anaconda original refinery slot is empty');
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');
});

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...
{ 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) {
if (err) {

View File

@@ -1,12 +1,12 @@
{
"name": "coriolis_shipyard",
"version": "2.0.0-Beta-1",
"version": "2.2.1",
"repository": {
"type": "git",
"url": "https://github.com/cmmcleod/coriolis"
"url": "https://github.com/EDCD/coriolis"
},
"homepage": "https://coriolis.io",
"bugs": "https://github.com/cmmcleod/coriolis/issues",
"homepage": "https://coriolis.edcd.io",
"bugs": "https://github.com/EDCD/coriolis/issues",
"private": true,
"engine": "node >= 4.0.0",
"license": "MIT",
@@ -20,19 +20,19 @@
"prod-stop": "kill -QUIT $(cat nginx.pid)",
"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",
"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": {
"scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
"testFileExtensions": [
"js"
],
"testRegex": "(/__tests__/test-.*|\\.(test|spec))\\.js$",
"moduleFileExtensions": [
"js",
"json",
"jsx"
],
"automock": true,
"unmockedModulePathPatterns": [
"<rootDir>/node_modules/lodash",
"<rootDir>/node_modules/react",
"<rootDir>/node_modules/react-dom",
"<rootDir>/node_modules/react-addons-test-utils",
@@ -43,7 +43,7 @@
"<rootDir>/node_modules/d3",
"<rootDir>/node_modules/lz-string",
"<rootDir>/node_modules/jsen",
"<rootDir>/node_modules/coriolis-data",
"coriolis-data",
"<rootDir>/src/app/shipyard",
"<rootDir>/src/app/i18n",
"<rootDir>/src/app/utils",
@@ -68,13 +68,13 @@
"extract-text-webpack-plugin": "^0.9.1",
"file-loader": "^0.8.4",
"html-webpack-plugin": "^1.7.0",
"jest-cli": "*",
"jest-cli": "^16.0.1",
"jsen": "^0.6.0",
"json-loader": "^0.5.3",
"less": "^2.5.3",
"less-loader": "^2.2.1",
"react-addons-test-utils": "^0.14.7",
"react-testutils-additions": "^0.16.0",
"react-addons-test-utils": "^15.0.1",
"react-testutils-additions": "^15.1.0",
"rimraf": "^2.4.3",
"style-loader": "^0.13.0",
"url-loader": "^0.5.6",
@@ -84,12 +84,14 @@
"dependencies": {
"babel-polyfill": "*",
"classnames": "^2.2.0",
"coriolis-data": "cmmcleod/coriolis-data",
"d3": "^3.5.9",
"coriolis-data": "EDCD/coriolis-data",
"d3": "3.5.16",
"fbemitter": "^2.0.0",
"lodash": "^4.15.0",
"lz-string": "^1.4.4",
"react": "^0.14.7",
"react-dom": "^0.14.7",
"react-number-editor": "^4.0.2",
"react": "^15.0.1",
"react-dom": "^15.0.1",
"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 {
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,
showModal: React.PropTypes.func.isRequired,
hideModal: React.PropTypes.func.isRequired,
tooltip: React.PropTypes.func.isRequired,
termtip: React.PropTypes.func.isRequired,
language: React.PropTypes.object.isRequired,
noTouch: React.PropTypes.bool.isRequired,
onCommand: 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.state = {
noTouch: !('ontouchstart' in window || navigator.msMaxTouchPoints || navigator.maxTouchPoints),
page: null,
language: getLanguage(Persist.getLangCode()),
route: null,
route: {},
sizeRatio: Persist.getSizeRatio()
};
@@ -237,17 +239,18 @@ export default class Coriolis extends React.Component {
*/
getChildContext() {
return {
language: this.state.language,
route: this.state.route,
sizeRatio: this.state.sizeRatio,
openMenu: this._openMenu,
closeMenu: this._closeMenu,
showModal: this._showModal,
hideModal: this._hideModal,
tooltip: this._tooltip,
termtip: this._termtip,
language: this.state.language,
noTouch: this.state.noTouch,
onCommand: this._onCommand,
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.addEventListener('resize', () => this.emitter.emit('windowResize'));
document.body.addEventListener('scroll', () => this._tooltip());
document.getElementById('coriolis').addEventListener('scroll', () => this._tooltip());
document.addEventListener('keydown', this._keyDown);
Persist.addListener('language', this._onLanguageChange);
Persist.addListener('sizeRatio', this._onSizeRatioChange);
@@ -281,11 +284,16 @@ export default class Coriolis extends React.Component {
render() {
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} />
{ this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) : <NotFoundPage/> }
{ this.state.modal }
{ this.state.tooltip }
<footer>
<div className="right cap">
<a href="https://github.com/EDCD/coriolis" target="_blank" title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>
</div>
</footer>
</div>;
}
}

View File

@@ -1,15 +1,20 @@
import Persist from './stores/Persist';
let standalone = undefined;
/**
* Determine if the app is running in mobile/tablet 'standalone' mode
* @return {Boolean} True if the app is in standalone mode
*/
function isStandAlone() {
try {
return window.navigator.standalone || (window.external && window.external.msIsSiteMode && window.external.msIsSiteMode());
} catch (ex) {
return false;
if (standalone === undefined) {
try {
standalone = window.navigator.standalone || (window.external && window.external.msIsSiteMode && window.external.msIsSiteMode());
} catch (ex) {
standalone = false;
}
}
return standalone;
}
/**
@@ -44,14 +49,14 @@ Router.start = function() {
if (isStandAlone()) {
let state = Persist.getState();
// If a previous state has been stored, load that state
if (state && state.name && state.params) {
Router(this.props.initialPath || '/');
if (state && state.path) {
Router.replace(state.path, null, true);
} else {
Router('/');
Router.replace('/', null, true);
}
} else {
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);
Router.dispatch(ctx);
if (!ctx.unhandled) {
if (isStandAlone()) {
Persist.setState(ctx);
}
history.pushState(ctx.state, ctx.title, ctx.canonicalPath);
}
return ctx;
@@ -85,7 +93,12 @@ Router.go = function(path, state) {
Router.replace = function(path, state, dispatch) {
gaTrack(path);
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);
return ctx;
};
@@ -227,7 +240,7 @@ Route.prototype.match = function(path, params) {
*/
function gaTrack(path) {
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 { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
const PRESS_THRESHOLD = 5000; // mouse/touch down threshold
const PRESS_THRESHOLD = 500; // mouse/touch down threshold
/**
* Available modules menu
@@ -97,7 +97,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
let m = modules[i];
let mount = null;
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', {
warning: !disabled && warningFunc && warningFunc(m),
active,
@@ -173,6 +173,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
* @param {SyntheticEvent} event Event
*/
_touchStart(showDiff, event) {
event.preventDefault();
let rect = event.currentTarget.getBoundingClientRect();
this.touchTimeout = setTimeout(showDiff.bind(this, rect), PRESS_THRESHOLD);
}
@@ -183,10 +184,10 @@ export default class AvailableModulesMenu extends TranslatedComponent {
* @param {SyntheticEvent} event Event
*/
_touchEnd(select, event) {
event.preventDefault();
if (this.touchTimeout !== null) { // If timeout has not fired (been nulled out) yet
select();
}
event.preventDefault();
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})`}>
<text className='cap' y='30' dy='.1em' x={innerWidth / 2} style={{ textAnchor: 'middle' }}>
<tspan>{title}</tspan>
{ unit ? <tspan className='metric'>{` (${unit})`}</tspan> : null }
{ unit ? <tspan className='metric'> ({unit})</tspan> : null }
</text>
</g>
<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'>
<th colSpan='2' className='sortable le' onClick={this._sortCostBy.bind(this,'m')}>
{translate('module')}
{shipDiscount && <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} -${formats.pct(shipDiscount)}]`}</u>}
{moduleDiscount && <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</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> : null}
</th>
<th className='sortable le' onClick={this._sortCostBy.bind(this, 'cr')} >{translate('credits')}</th>
</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, 'cr')}>
{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>
</tr>
</thead>
@@ -400,7 +400,7 @@ export default class CostSection extends TranslatedComponent {
let retrofitCosts = [];
let retrofitTotal = 0, i, l, item;
if (ship.bulkheads.index != retrofitShip.bulkheads.index) {
if (ship.bulkheads.m.index != retrofitShip.bulkheads.m.index) {
item = {
buyClassRating: ship.bulkheads.m.class + ship.bulkheads.m.rating,
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) {
nextProps.ship.applyDiscounts(Persist.getShipDiscount(), Persist.getModuleDiscount());
this._updateAmmoCosts(nextProps.ship);
this._updateRetrofit(nextProps.ship, retrofitShip);
this._sortCost(nextProps.ship);

View File

@@ -1,5 +1,9 @@
import React from 'react';
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
@@ -33,24 +37,33 @@ export default class HardpointSlot extends Slot {
*/
_getSlotDetails(m, translate, formats, u) {
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 { termtip, tooltip } = this.context;
let validMods = Modifications.validity[m.grp] || [];
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
<div className={'cb'}>
<div className={'l'}>{classRating + ' ' + translate(m.name || m.grp)}</div>
<div className={'r'}>{m.mass}{u.T}</div>
<div className={'l'}>
{m.mount && m.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed /></span> : ''}
{m.mount && m.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : ''}
{m.mount && m.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : ''}
{m.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 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.dps ? <div className={'l'}>{translate('DPS')}: {m.dps} { m.mjdps ? <span>({formats.int(m.mjdps)} {u.MJ})</span> : null }</div> : null }
{ m.thermload ? <div className={'l'}>{translate('T-Load')}: {m.thermload}</div> : null }
{ m.type ? <div className={'l'}>{translate('type')}: {m.type}</div> : null }
{ m.rof ? <div className={'l'}>{translate('ROF')}: {m.rof}{u.ps}</div> : null }
{ m.armourpen ? <div className={'l'}>{translate('pen')}: {m.armourpen}</div> : null }
{ m.shieldmul ? <div className={'l'}>+{formats.rPct(m.shieldmul)}</div> : null }
{ m.range ? <div className={'l'}>{m.range} <u>km</u></div> : null }
{ m.ammo >= 0 ? <div className={'l'}>{translate('ammo')}: {formats.int(m.clip)}+{formats.int(m.ammo)}</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.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'epsseps' : 'eps')} onMouseOut={tooltip.bind(null, null)}>{translate('EPS')}: {formats.round1(m.getEps())}{u.MW} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) }{u.MW})</span> : null }</div> : null }
{ m.getHps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'hpsshps' : 'hps')} onMouseOut={tooltip.bind(null, null)}>{translate('HPS')}: {formats.round1(m.getHps())} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getHps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) })</span> : null }</div> : null }
{ m.getDps() && m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, 'dpe')} onMouseOut={tooltip.bind(null, null)}>{translate('DPE')}: {formats.f1(m.getDps() / m.getEps())}</div> : null }
{ m.getRoF() ? <div className={'l'} onMouseOver={termtip.bind(null, 'rof')} onMouseOut={tooltip.bind(null, null)}>{translate('ROF')}: {formats.f1(m.getRoF())}{u.ps}</div> : null }
{ m.getRange() && !m.getDps() ? <div className={'l'}>{translate('Range')} : {formats.round(m.getRange() / 1000)}{u.km}</div> : null }
{ m.getShieldBoost() ? <div className={'l'}>+{formats.pct1(m.getShieldBoost())}</div> : null }
{ m.getAmmo() ? <div className={'l'}>{translate('ammunition')}: {formats.int(m.getClip())}/{formats.int(m.getAmmo())}</div> : null }
{ m && 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>;
} else {

View File

@@ -69,6 +69,7 @@ export default class HardpointsSlotSection extends SlotSection {
availableModules={() => availableModules.getHps(h.maxClass)}
onOpen={this._openMenu.bind(this, h)}
onSelect={this._selectModule.bind(this, h)}
onChange={this.props.onChange}
selected={currentMenu == h}
drag={this._drag.bind(this, h)}
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}>
<ul>
<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>
<div className='select-group cap'>{translate('pl')}</div>
<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', 'T')}><MountTurret className='lg'/></li>
</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>;
}

View File

@@ -27,7 +27,7 @@ function normalizePercent(val) {
if (val === '' || isNaN(val)) {
return 0;
}
val = Math.round(val * 100) / 100;
val = Math.round(val * 1000) / 1000;
return val >= 100 ? 100 : val;
}
@@ -362,37 +362,51 @@ export default class Header extends TranslatedComponent {
return (
<div className='menu-list no-wrap cap' onClick={ (e) => e.stopPropagation() }>
<div style={{ lineHeight: '2em' }}>
{translate('language')}
<select className='cap' value={Persist.getLangCode()} onChange={this._setLanguage}>
{this.languageOptions}
</select>
<br/>
<span className='cap ptr' onClick={this._toggleTooltips} >
{translate('tooltips')}
<div className={cn({ disabled: !tips, 'primary-disabled': tips })} style={{ marginLeft: '0.5em', display: 'inline-block' }}>{(tips ? '✓' : '✗')}</div>
</span>
<br/>
{translate('insurance')}
<select className='cap' value={Persist.getInsurance()} onChange={this._setInsurance}>
{this.insuranceOptions}
</select>
<br/>
{translate('ship')} {translate('discount')}
<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>
<br/>
{translate('module')} {translate('discount')}
<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>
</div>
<table style={{ width: '100%', background: 'none', borderSpacing: '0 0.5em' }}>
<tbody>
<tr>
<td>{translate('language')}</td>
<td className='ri'>
<select className='cap' dir='rtl' value={Persist.getLangCode()} onChange={this._setLanguage}>
{this.languageOptions}
</select>
</td>
</tr>
<tr className='cap ptr' onClick={this._toggleTooltips} >
<td>{translate('tooltips')}</td>
<td className={cn('ri', { disabled: !tips, 'primary-disabled': tips })}>{(tips ? '✓' : '✗')}</td>
</tr>
<tr>
<td>{translate('insurance')}</td>
<td className='ri'>
<select className='cap' dir='rtl' value={Persist.getInsurance()} onChange={this._setInsurance}>
{this.insuranceOptions}
</select>
</td>
</tr>
<tr>
<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 />
<ul>
<ul style={{ width: '100%' }}>
{translate('builds')} & {translate('comparisons')}
<li><Link href="#" className='block' onClick={this._showBackup.bind(this)}>{translate('backup')}</Link></li>
<li><Link href="#" className='block' onClick={this._showDetailedExport.bind(this)}>{translate('detailed export')}</Link></li>
<li><Link href="#" className='block' onClick={this._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>
<hr />
<table style={{ width: 300, backgroundColor: 'transparent' }}>
@@ -462,32 +476,28 @@ export default class Header extends TranslatedComponent {
let translate = this.context.language.translate;
let openedMenu = this.props.currentMenu;
let hasBuilds = Persist.hasBuilds();
if (this.props.appCacheUpdate) {
return <div id="app-update" onClick={() => window.location.reload() }>{translate('PHRASE_UPDATE_RDY')}</div>;
}
return (
<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>
<div className='l menu'>
<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>
{openedMenu == 's' ? this._getShipsMenu() : null}
</div>
<div className='l menu'>
<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>
{openedMenu == 'b' ? this._getBuildsMenu() : null}
</div>
<div className='l menu'>
<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>
{openedMenu == 'comp' ? this._getComparisonsMenu() : null}
</div>
@@ -502,4 +512,4 @@ export default class Header extends TranslatedComponent {
);
}
}
}

View File

@@ -1,6 +1,8 @@
import React from 'react';
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
@@ -18,30 +20,39 @@ export default class InternalSlot extends Slot {
_getSlotDetails(m, translate, formats, u) {
if (m) {
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}>
<div className={'cb'}>
<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={'l'}>{classRating} {translate(m.name || m.grp)}</div>
<div className={'r'}>{formats.round(mass)}{u.T}</div>
</div>
<div className={'cb'}>
{ m.optmass ? <div className={'l'}>{translate('optimal mass') + ': '}{m.optmass}{u.T}</div> : null }
{ m.maxmass ? <div className={'l'}>{translate('max mass') + ': '}{m.maxmass}{u.T}</div> : null }
{ m.bins ? <div className={'l'}>{m.bins + ' '}<u>{translate('bins')}</u></div> : null }
{ m.bays ? <div className={'l'}>{translate('bays') + ': ' + m.bays}</div> : null }
{ m.getOptMass() ? <div className={'l'}>{translate('optimal mass')}: {formats.int(m.getOptMass())}{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.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.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.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.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.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
{ m.rangeLS ? <div className={'l'}>{m.rangeLS}{u.Ls}</div> : null }
{ m.rangeLS === null ? <div className={'l'}><Infinite/>{u.Ls}</div> : null }
{ m.rangeLS ? <div className={'l'}>{translate('range')}: {m.rangeLS}{u.Ls}</div> : null }
{ m.rangeLS === null ? <div className={'l'}>{u.Ls}</div> : null }
{ m.rangeRating ? <div className={'l'}>{translate('range')}: {m.rangeRating}</div> : null }
{ m.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>;
} else {

View File

@@ -16,12 +16,17 @@ export default class InternalSlotSection extends SlotSection {
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props, context, 'internal', 'internal compartments');
super(props, context, 'internal', 'optional internal');
this._empty = this._empty.bind(this);
this._fillWithCargo = this._fillWithCargo.bind(this);
this._fillWithCells = this._fillWithCells.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();
}
/**
* 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
* @param {SyntheticEvent} event Event
@@ -58,7 +143,7 @@ export default class InternalSlotSection extends SlotSection {
let ship = this.props.ship;
let chargeCap = 0; // Capacity of single activation
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.setSlotEnabled(slot, chargeCap <= ship.shieldStrength); // Don't waste cell capacity on overcharge
chargeCap += slot.m.recharge;
@@ -76,7 +161,7 @@ export default class InternalSlotSection extends SlotSection {
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
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
}
});
@@ -99,7 +184,7 @@ export default class InternalSlotSection extends SlotSection {
let slots = [];
let { currentMenu, ship } = this.props;
let { originSlot, targetSlot } = this.state;
let { internal, fuelCapacity, ladenMass } = ship;
let { internal, fuelCapacity } = ship;
let availableModules = ship.getAvailableModules();
for (let i = 0, l = internal.length; i < l; i++) {
@@ -108,11 +193,13 @@ export default class InternalSlotSection extends SlotSection {
slots.push(<InternalSlot
key={i}
maxClass={s.maxClass}
availableModules={() => availableModules.getInts(s.maxClass, s.eligible)}
availableModules={() => availableModules.getInts(ship, s.maxClass, s.eligible)}
onOpen={this._openMenu.bind(this,s)}
onChange={this.props.onChange}
onSelect={this._selectModule.bind(this, s)}
selected={currentMenu == s}
enabled={s.enabled}
eligible={s.eligible}
m={s.m}
drag={this._drag.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
* @param {Function} translate Translate function
* @param {Function} ship The ship
* @return {React.Component} Section menu
*/
_getSectionMenu(translate) {
_getSectionMenu(translate, ship) {
return <div className='select' onClick={e => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li>
<li className='lc' onClick={this._fillWithCargo}>{translate('cargo')}</li>
<li className='lc' onClick={this._fillWithCells}>{translate('scb')}</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>
</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})`}>
<text className='cap' y='30' dy='.1em' x={innerWidth / 2} style={{ textAnchor: 'middle' }}>
<tspan>{xLabel}</tspan>
<tspan className='metric'>{` (${xUnit})`}</tspan>
<tspan className='metric'> ({xUnit})</tspan>
</text>
</g>
<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' }}>
<tspan>{yLabel}</tspan>
<tspan className='metric'>{` (${yUnit})`}</tspan>
<tspan className='metric'> ({yUnit})</tspan>
</text>
</g>
<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
*/
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;
}
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} />
<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={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.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>
</g>
</svg>
);

View File

@@ -71,7 +71,7 @@ export default class PowerManagement extends TranslatedComponent {
case 'n': comp = comp(null, 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 '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 '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++) {
let slot = ship.powerList[i];
if (slot.m && slot.m.power) {
if (slot.m && slot.m.getPowerUsage() > 0) {
let m = slot.m;
let toggleEnabled = this._toggleEnabled.bind(this, slot);
let retractedElem = null, deployedElem = null;
@@ -134,8 +134,8 @@ export default class PowerManagement extends TranslatedComponent {
{' ' + (slot.priority + 1) + ' '}
<span className='ptr btn' onClick={this._priority.bind(this, slot, 1)}>&#9658;</span>
</td>
<td className='ri ptr' style={{ width: '3.25em' }} onClick={toggleEnabled}>{pwr(m.power)}</td>
<td className='ri ptr' style={{ width: '3em' }} onClick={toggleEnabled}><u>{pct(m.power / ship.powerAvailable)}</u></td>
<td className='ri ptr' style={{ width: '3.25em' }} onClick={toggleEnabled}>{pwr(m.getPowerUsage())}</td>
<td className='ri ptr' style={{ width: '3em' }} onClick={toggleEnabled}><u>{pct(m.getPowerUsage() / ship.powerAvailable)}</u></td>
{retractedElem}
{deployedElem}
</tr>);
@@ -214,7 +214,7 @@ export default class PowerManagement extends TranslatedComponent {
<td className='le shorten cap' >{translate('pp')}</td>
<td><u >{translate('SYS')}</u></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></td>
<td></td>
@@ -223,7 +223,7 @@ export default class PowerManagement extends TranslatedComponent {
{this._renderPowerRows(ship, translate, pwr, formats.pct1)}
</tbody>
</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>
);
}

View File

@@ -23,35 +23,40 @@ export default class ShipSummaryTable extends TranslatedComponent {
let translate = language.translate;
let u = language.units;
let formats = language.formats;
let round = formats.round;
let { time, int } = formats;
let armourDetails = null;
let sgClassNames = cn({ warning: ship.sgSlot && !ship.shieldStrength, muted: !ship.sgSlot });
let { time, int, round, f1, f2, pct } = formats;
let sgClassNames = cn({ warning: ship.findInternalByGroup('sg') && !ship.shield, muted: !ship.findInternalByGroup('sg') });
let sgRecover = '-';
let sgRecharge = '-';
let hide = tooltip.bind(null, null);
if (ship.armourMultiplier > 1 || ship.armourAdded) {
armourDetails = <u>({
(ship.armourMultiplier > 1 ? formats.rPct(ship.armourMultiplier) : '') +
(ship.armourAdded ? ' + ' + ship.armourAdded : '')
})</u>;
}
if (ship.shieldStrength) {
if (ship.shield) {
sgRecover = time(ship.calcShieldRecovery());
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'>
<table id='summaryTable'>
<thead>
<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.canBoost() }) }>{translate('boost')}</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 colSpan={3}>{translate('shields')}</th>
<th colSpan={3}>{translate('mass')}</th>
@@ -78,26 +83,26 @@ export default class ShipSummaryTable extends TranslatedComponent {
</thead>
<tbody>
<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.canBoost() ? <span>{int(ship.topBoost)} {u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td>{round(ship.totalDps)}</td>
<td>{int(ship.armour)} {armourDetails}</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.totalDps)}</td>
<td>{f1(ship.totalEps)}</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}>{sgRecharge}</td>
<td>{ship.hullMass} {u.T}</td>
<td>{round(ship.unladenMass)} {u.T}</td>
<td>{round(ship.ladenMass)} {u.T}</td>
<td>{int(ship.unladenMass)} {u.T}</td>
<td>{int(ship.ladenMass)} {u.T}</td>
<td>{round(ship.cargoCapacity)} {u.T}</td>
<td>{round(ship.fuelCapacity)} {u.T}</td>
<td>{round(ship.unladenRange)} {u.LY}</td>
<td>{round(ship.fullTankRange)} {u.LY}</td>
<td>{round(ship.ladenRange)} {u.LY}</td>
<td>{round(ship.maxJumpCount)}</td>
<td>{round(ship.unladenFastestRange)} {u.LY}</td>
<td>{round(ship.ladenFastestRange)} {u.LY}</td>
<td>{f2(ship.unladenRange)} {u.LY}</td>
<td>{f2(ship.fullTankRange)} {u.LY}</td>
<td>{f2(ship.ladenRange)} {u.LY}</td>
<td>{int(ship.maxJumpCount)}</td>
<td>{f2(ship.unladenFastestRange)} {u.LY}</td>
<td>{f2(ship.ladenFastestRange)} {u.LY}</td>
<td>{ship.masslock}</td>
</tr>
</tbody>

View File

@@ -2,8 +2,12 @@ import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import cn from 'classnames';
import AvailableModulesMenu from './AvailableModulesMenu';
import ModificationsMenu from './ModificationsMenu';
import { Modifications } from 'coriolis-data/dist';
import { ListModifications } from './SvgIcons';
import { diffDetails } from '../utils/SlotFunctions';
import { wrapCtxMenu } from '../utils/UtilityFunctions';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
/**
* Abstract Slot
@@ -18,6 +22,7 @@ export default class Slot extends TranslatedComponent {
selected: React.PropTypes.bool,
m: React.PropTypes.object,
ship: React.PropTypes.object.isRequired,
eligible: React.PropTypes.object,
warning: React.PropTypes.func,
drag: React.PropTypes.func,
drop: React.PropTypes.func,
@@ -31,6 +36,8 @@ export default class Slot extends TranslatedComponent {
constructor(props) {
super(props);
this._modificationsSelected = false;
this._contextMenu = wrapCtxMenu(this._contextMenu.bind(this));
this._getMaxClassLabel = this._getMaxClassLabel.bind(this);
}
@@ -73,25 +80,39 @@ export default class Slot extends TranslatedComponent {
render() {
let language = this.context.language;
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;
if (!selected) {
// If not selected then sure that modifications flag is unset
this._modificationsSelected = false;
}
if (m) {
slotDetails = this._getSlotDetails(m, translate, language.formats, language.units); // Must be implemented by sub classes
} else {
slotDetails = <div className={'empty'}>{translate('empty')}</div>;
slotDetails = <div className={'empty'}>{translate(eligible ? 'emptyrestricted' : 'empty')}</div>;
}
if (this.props.selected) {
menu = <AvailableModulesMenu
className={this._getClassNames()}
modules={availableModules()}
shipMass={ship.hullMass}
m={m}
onSelect={onSelect}
warning={warning}
diffDetails={diffDetails.bind(ship, this.context.language)}
/>;
if (selected) {
if (this._modificationsSelected) {
menu = <ModificationsMenu
className={this._getClassNames()}
onChange={onChange}
ship={ship}
m={m}
/>;
} 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
@@ -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='details-container'>
<div className='sz'>{this._getMaxClassLabel(translate)}</div>
{slotDetails}
</div>
{slotDetails}
</div>
{menu}
</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 TranslatedComponent from './TranslatedComponent';
import { wrapCtxMenu } from '../utils/UtilityFunctions';
import { canMount } from '../utils/SlotFunctions';
import { Equalizer } from '../components/SvgIcons';
import cn from 'classnames';
@@ -90,7 +91,7 @@ export default class SlotSection extends TranslatedComponent {
e.stopPropagation();
let os = this.state.originSlot;
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 });
} else {
e.dataTransfer.dropEffect = 'none';
@@ -116,9 +117,9 @@ export default class SlotSection extends TranslatedComponent {
let { originSlot, targetSlot } = this.state;
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
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);
} else { // Otherwise empty the origin slot
this.props.ship.use(originSlot, null, true); // Empty but prevent summary update
@@ -141,12 +142,12 @@ export default class SlotSection extends TranslatedComponent {
return null;
}
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 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) {
return 'drop'; // Can drop
}
@@ -179,7 +180,7 @@ export default class SlotSection extends TranslatedComponent {
<div id={this.sectionId} className={'group'} onDragLeave={this._dragOverNone}>
<div className={cn('section-menu', { selected: sectionMenuOpened })} onClick={open} onContextMenu={ctx}>
<h1>{translate(this.sectionName)} <Equalizer/></h1>
{sectionMenuOpened ? this._getSectionMenu(translate) : null }
{sectionMenuOpened ? this._getSectionMenu(translate, this.props.ship) : null }
</div>
{this._getSlots()}
</div>

View File

@@ -4,6 +4,10 @@ import TranslatedComponent from './TranslatedComponent';
import { jumpRange } from '../shipyard/Calculations';
import { diffDetails } from '../utils/SlotFunctions';
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
@@ -15,51 +19,83 @@ export default class StandardSlot extends TranslatedComponent {
modules: React.PropTypes.array.isRequired,
onSelect: React.PropTypes.func.isRequired,
onOpen: React.PropTypes.func.isRequired,
onChange: React.PropTypes.func.isRequired,
ship: React.PropTypes.object.isRequired,
selected: React.PropTypes.bool,
warning: React.PropTypes.func,
};
/**
* Construct the slot
* @param {object} props Object properties
*/
constructor(props) {
super(props);
this._modificationsSelected = false;
}
/**
* Render the slot
* @return {React.Component} Slot component
*/
render() {
let { termtip, tooltip } = this.context;
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 classRating = m.class + m.rating;
let menu;
let validMods = m == null ? [] : (Modifications.validity[m.grp] || []);
let mass = m.getMass() || m.cargo || m.fuel || 0;
if (this.props.selected) {
menu = <AvailableModulesMenu
modules={modules}
shipMass={ship.ladenMass}
m={m}
onSelect={onSelect}
warning={warning}
diffDetails={diffDetails.bind(ship, this.context.language)}
/>;
if (!selected) {
// If not selected then sure that modifications flag is unset
this._modificationsSelected = false;
}
if (selected) {
if (this._modificationsSelected) {
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 (
<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={'sz'}>{slot.maxClass}</div>
<div>
<div className='l'>{classRating + ' ' + translate(m.grp)}</div>
<div className={'r'}>{m.mass || m.fuel}{units.T}</div>
<div className='l'>{classRating} {translate(m.grp == 'bh' ? m.grp : m.name || m.grp)}</div>
<div className={'r'}>{formats.round(mass)}{units.T}</div>
<div/>
<div className={'cb'}>
{ m.optmass ? <div className='l'>{translate('optimal mass') + ': '}{m.optmass}{units.T}</div> : null }
{ m.maxmass ? <div className='l'>{translate('max mass') + ': '}{m.maxmass}{units.T}</div> : null }
{ m.range ? <div className='l'>{translate('range')}: {m.range}{units.km}</div> : null }
{ m.grp == 'bh' && m.name ? <div className='l'>{translate(m.name)}</div> : null }
{ m.getOptimalMass() ? <div className='l'>{translate('optimal mass')}: {formats.int(m.getOptimalMass())}{units.T}</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.eff ? <div className='l'>{translate('efficiency')}: {m.eff}</div> : null }
{ m.pGen ? <div className='l'>{translate('power')}: {m.pGen}{units.MW}</div> : null }
{ m.maxfuel ? <div className='l'>{translate('max') + ' ' + translate('fuel') + ': '}{m.maxfuel}{units.T}</div> : null }
{ m.weaponcapacity ? <div className='l'>{translate('WEP')}: {m.weaponcapacity}{units.MJ} / {m.weaponrecharge}{units.MW}</div> : null }
{ m.systemcapacity ? <div className='l'>{translate('SYS')}: {m.systemcapacity}{units.MJ} / {m.systemrecharge}{units.MW}</div> : null }
{ m.enginecapacity ? <div className='l'>{translate('ENG')}: {m.enginecapacity}{units.MJ} / {m.enginerecharge}{units.MW}</div> : null }
{ m.getThermalEfficiency() ? <div className='l'>{translate('efficiency')}: {formats.f2(m.getThermalEfficiency())}</div> : null }
{ m.getPowerGeneration() > 0 ? <div className='l'>{translate('pgen')}: {formats.f1(m.getPowerGeneration())}{units.MW}</div> : null }
{ m.getMaxFuelPerJump() ? <div className='l'>{translate('max')} {translate('fuel')}: {formats.f1(m.getMaxFuelPerJump())}{units.T}</div> : null }
{ m.getWeaponsCapacity() ? <div className='l'>{translate('WEP')}: {formats.f1(m.getWeaponsCapacity())}{units.MJ} / {formats.f1(m.getWeaponsRechargeRate())}{units.MW}</div> : null }
{ m.getSystemsCapacity() ? <div className='l'>{translate('SYS')}: {formats.f1(m.getSystemsCapacity())}{units.MJ} / {formats.f1(m.getSystemsRechargeRate())}{units.MW}</div> : null }
{ m.getEnginesCapacity() ? <div className='l'>{translate('ENG')}: {formats.f1(m.getEnginesCapacity())}{units.MJ} / {formats.f1(m.getEnginesRechargeRate())}{units.MW}</div> : null }
{ 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>
@@ -67,4 +103,11 @@ export default class StandardSlot extends TranslatedComponent {
</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 { diffDetails } from '../utils/SlotFunctions';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import * as ShipRoles from '../shipyard/ShipRoles';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
/**
@@ -17,22 +18,9 @@ export default class StandardSlotSection extends SlotSection {
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props, context, 'standard', 'standard');
super(props, context, 'standard', 'core internal');
this._optimizeStandard = this._optimizeStandard.bind(this);
this._optimizeCargo = this._optimizeCargo.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();
this._selectBulkhead = this._selectBulkhead.bind(this);
}
/**
@@ -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() {
let ship = this.props.ship;
ship.internal.forEach((slot) => ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E')));
ship.useLightestStandard();
_multiPurpose(shielded, bulkheadIndex) {
ShipRoles.multiPurpose(this.props.ship, shielded, bulkheadIndex);
this.props.onChange();
this._close();
}
/**
* Explorer build
* Trader Build
* @param {Boolean} shielded True if shield generator should be included
*/
_optimizeExplorer() {
let ship = this.props.ship,
intLength = ship.internal.length,
heatSinkCount = 2, // Fit 2 heat sinks if possible
afmUnitCount = 2, // Fit 2 AFM Units if possible
sgSlot,
fuelScoopSlot,
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
_optimizeCargo(shielded) {
ShipRoles.trader(this.props.ship, shielded);
this.props.onChange();
this._close();
}
ship.setSlotEnabled(ship.cargoHatch, false)
.use(ship.internal[--intLength], ModuleUtils.internal('2f')) // Advanced Discovery Scanner
.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;
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' });
/**
* Explorer role
* @param {Boolean} planetary True if Planetary Vehicle Hangar (PVH) should be included
*/
_optimizeExplorer(planetary) {
ShipRoles.explorer(this.props.ship, planetary);
this.props.onChange();
this._close();
}
/**
* Use the specified bulkhead
* @param {number} bulkheadIndex 0 - 4
* @param {Object} bulkhead Bulkhead module details
*/
_selectBulkhead(bulkheadIndex) {
this.props.ship.useBulkhead(bulkheadIndex);
_selectBulkhead(bulkhead) {
this.props.ship.useBulkhead(bulkhead.index);
this.context.tooltip();
this.props.onChange();
this._close();
@@ -136,76 +81,29 @@ export default class StandardSlotSection extends SlotSection {
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
* @return {Array} Array of Slots
*/
_getSlots() {
let { translate, units } = this.context.language;
let { ship, currentMenu } = this.props;
let slots = new Array(8);
let open = this._openMenu;
let select = this._selectModule;
let selBulkhead = this._selectBulkhead;
let st = ship.standard;
let avail = ship.getAvailableModules().standard;
let bh = ship.bulkheads;
slots[0] = (
<div key='bh' className={cn('slot', { selected: currentMenu === bh })} onClick={open.bind(this, bh)}>
<div className={'details-container'}>
<div className={'details'}>
<div className={'sz'}>8</div>
<div>
<div className={'l'}>{translate('bh')}</div>
<div className={'r'}>{bh.m.mass}{units.T}</div>
<div className={'cl l'}>{translate(bh.m.name)}</div>
</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[0] = <StandardSlot
key='bh'
slot={bh}
modules={ship.getAvailableModules().bulkheads}
onOpen={open.bind(this, bh)}
onSelect={this._selectBulkhead}
selected={currentMenu == bh}
onChange={this.props.onChange}
ship={ship}
/>;
slots[1] = <StandardSlot
key='pp'
@@ -214,8 +112,9 @@ export default class StandardSlotSection extends SlotSection {
onOpen={open.bind(this, st[0])}
onSelect={select.bind(this, st[0])}
selected={currentMenu == st[0]}
onChange={this.props.onChange}
ship={ship}
warning={m => m.pGen < ship.powerRetracted}
warning={m => m.pgen < ship.powerRetracted}
/>;
slots[2] = <StandardSlot
@@ -225,8 +124,9 @@ export default class StandardSlotSection extends SlotSection {
onOpen={open.bind(this, st[1])}
onSelect={select.bind(this, st[1])}
selected={currentMenu == st[1]}
onChange={this.props.onChange}
ship={ship}
warning={m => m.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]}
onOpen={open.bind(this, st[2])}
onSelect={select.bind(this, st[2])}
onChange={this.props.onChange}
ship={ship}
selected={currentMenu == st[2]}
/>;
@@ -246,6 +147,7 @@ export default class StandardSlotSection extends SlotSection {
modules={avail[3]}
onOpen={open.bind(this, st[3])}
onSelect={select.bind(this, st[3])}
onChange={this.props.onChange}
ship={ship}
selected={currentMenu == st[3]}
/>;
@@ -257,8 +159,9 @@ export default class StandardSlotSection extends SlotSection {
onOpen={open.bind(this, st[4])}
onSelect={select.bind(this, st[4])}
selected={currentMenu == st[4]}
onChange={this.props.onChange}
ship={ship}
warning= {m => m.enginecapacity < ship.boostEnergy}
warning= {m => m.engcap < ship.boostEnergy}
/>;
slots[6] = <StandardSlot
@@ -268,8 +171,8 @@ export default class StandardSlotSection extends SlotSection {
onOpen={open.bind(this, st[5])}
onSelect={select.bind(this, st[5])}
selected={currentMenu == st[5]}
onChange={this.props.onChange}
ship={ship}
warning= {m => m.enginecapacity < ship.boostEnergy}
/>;
slots[7] = <StandardSlot
@@ -279,6 +182,7 @@ export default class StandardSlotSection extends SlotSection {
onOpen={open.bind(this, st[6])}
onSelect={select.bind(this, st[6])}
selected={currentMenu == st[6]}
onChange={this.props.onChange}
ship={ship}
warning= {m => m.fuel < st[2].m.maxfuel} // Show warning when fuel tank is smaller than FSD Max Fuel
/>;
@@ -292,21 +196,19 @@ export default class StandardSlotSection extends SlotSection {
* @return {React.Component} Section menu
*/
_getSectionMenu(translate) {
let _fill = this._fill;
let planetaryDisabled = this.props.ship.internal.length < 4;
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul>
<li className='lc' onClick={this._optimizeStandard}>{translate('optimize')}</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>
<li className='lc' onClick={this._optimizeStandard}>{translate('Maximize Jump Range')}</li>
</ul>
<div className='select-group cap'>{translate('roles')}</div>
<ul>
<li className='lc' onClick={this._optimizeCargo}>{translate('Trader')}</li>
<li className='lc' onClick={this._optimizeExplorer}>{translate('Explorer')}</li>
<li className='lc' onClick={this._multiPurpose.bind(this, false, 0)}>{translate('Multi-purpose')}</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>
</div>;
}

View File

@@ -64,8 +64,8 @@ export class CoriolisLogo extends SvgIcon {
*/
svg() {
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'/>
<rect height='3' width='10' y='13.5' x='10' strokeWidth='1' stroke='#ff3b00'/>
<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='#0a8bd6'/>
</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
*/
@@ -334,11 +418,11 @@ export class MountFixed extends SvgIcon {
*/
svg() {
return <g>
<circle fillOpacity='0' r='70' cy='100' cx='100' strokeWidth='5' />
<line y2='60' x2='101' y1='0' x1='101' strokeWidth='5' />
<line y2='101' x2='200' y1='101' x1='140' strokeWidth='5' />
<line y2='101' x2='60' y1='101' x1='0' strokeWidth='5' />
<line y2='200' x2='101' y1='140' x1='101' strokeWidth='5' />
<circle cx='100' cy='100' r='76' fillOpacity='0' />
<path d='M0 100h48' />
<path d='M152 100h48' />
<path d='M100 0v48' />
<path d='M100 152v48' />
</g>;
}
}
@@ -358,8 +442,8 @@ export class MountGimballed extends SvgIcon {
*/
svg() {
return <g>
<ellipse ry='25' rx='95' cy='100' cx='100' fillOpacity='0' strokeWidth='5' />
<ellipse ry='95' rx='25' cy='100' cx='100' fillOpacity='0' strokeWidth='5' />
<ellipse cx='100' cy='100' rx='90' ry='25' fillOpacity='0' />
<ellipse cx='100' cy='100' rx='20' ry='95' fillOpacity='0' />
</g>;
}
}
@@ -379,9 +463,14 @@ export class MountTurret extends SvgIcon {
*/
svg() {
return <g>
<line y2='170' x2='162' y1='170' x1='8' strokeWidth='6' />
<path d='m13,138l144,0l0,-50l-27,-40l-90,0l-27,40l0,50z' id='svg_12' fillOpacity='0' strokeWidth='6' />
<line y2='91' x2='200' y1='91' x1='159' strokeWidth='6' />
<path d='M40 50 A 40 40 0 0 0 0 90' />
<path d='M40 50h40' />
<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>;
}
}
@@ -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
*/

View File

@@ -68,6 +68,7 @@ export default class UtilitySlotSection extends SlotSection {
availableModules={() => availableModules.getHps(h.maxClass)}
onOpen={this._openMenu.bind(this,h)}
onSelect={this._selectModule.bind(this, h)}
onChange={this.props.onChange}
selected={currentMenu == h}
drag={this._drag.bind(this, h)}
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}>
<ul>
<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>
<div className='select-group cap'>{translate('sb')}</div>
<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', 'A', null)}>A</li>
</ul>
<div className='select-group cap'>{translate('cm')}</div>
<div className='select-group cap'>{translate('hs')}</div>
<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>
</div>;
}

View File

@@ -41,29 +41,33 @@ export function getLanguage(langCode) {
formats: {
gen, // General number format (.e.g 1,001,001.1234)
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)
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%)
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)
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)
time: (d) => (d < 0 ? '-' : '') + Math.floor(Math.abs(d) / 60) + ':' + ('00' + Math.floor(Math.abs(d) % 60)).substr(-2, 2)
},
translate,
units: {
CR: <u>{' ' + translate('CR')}</u>, // Credits
kg: <u>{' ' + translate('kg')}</u>, // Kilograms
kgs: <u>{' ' + translate('kg/s')}</u>, // Kilograms per second
km: <u>{' ' + translate('km')}</u>, // Kilometers
Ls: <u>{' ' + translate('Ls')}</u>, // Light Seconds
LY: <u>{' ' + translate('LY')}</u>, // Light Years
MJ: <u>{' ' + translate('MJ')}</u>, // Mega Joules
'm/s': <u>{' ' + translate('m/s')}</u>, // Meters per second
MW: <u>{' ' + translate('MW')}</u>, // Mega Watts (same as Mega Joules per second)
CR: <u> {translate('CR')}</u>, // Credits
kg: <u> {translate('kg')}</u>, // Kilograms
kgs: <u> {translate('kg/s')}</u>, // Kilograms per second
km: <u> {translate('km')}</u>, // Kilometers
Ls: <u> {translate('Ls')}</u>, // Light Seconds
LY: <u> {translate('LY')}</u>, // Light Years
MJ: <u> {translate('MJ')}</u>, // Mega Joules
'm/s': <u> {translate('m/s')}</u>, // Meters per second
MW: <u> {translate('MW')}</u>, // Mega Watts (same as Mega Joules per second)
ps: <u>{translate('/s')}</u>, // per second
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 = {
// 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_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_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_RETROCH: 'Keine Umrüständerungen', // No Retrofitting changes
PHRASE_SELECT_BUILDS: 'Ausstattung zum Vergleich auswählen', // Select Builds to Compare
PHRASE_SG_RECHARGE: null, // Time from 50% to 100% Charge
PHRASE_SG_RECOVER: null, // Recovery (to 50%) after collapse
PHRASE_UNLADEN: null, // Ship Mass excluding Fuel and Cargo
PHRASE_SG_RECHARGE: 'Zeit von 50% bis 100% der Ladung', // Time from 50% to 100% Charge
PHRASE_SG_RECOVER: 'Erneuerung (zu 50%) nach Zusammenbruch', // Recovery (to 50%) after collapse
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
// 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
LY: 'Lj', // Light Years
T: 't', // Tons (Metric Ton - 1000kg)
@@ -94,109 +38,108 @@ export const terms = {
L: 'G', // Large Hardpoint size (single character)
H: 'R', // Huge Hardpoint size (single character)
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
'build name': 'Ausstattungsname', // Ship build/configuration/design name
'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
about: 'über', // Link to about page / about Coriolis.io
action: 'Aktion',
added: 'hinzugefügt',
ammo: 'Munition', // Ammunition
armour: 'Panzerung',
available: 'verfügbar',
available: 'verfügbar', // Available options
backup: 'Sicherungsdatei',
base: null,
bays: null,
base: 'Basis', // Base speed, boost, etc - Base ship stats
bays: 'Lagerraum',
bins: 'Behälter', // Number of Mining Refinery bins
build: 'Ausstattung', // Shorthand for the build/configuration/design name
'build name': 'Ausstattungsname', // Ship build/configuration/design name
builds: 'Ausstattungen', // Ship build/configuration/design names
buy: 'kaufen',
cancel: 'Abbrechen',
cancel: 'abbrechen',
cargo: 'Fracht',
cells: 'Zellen', // Number of cells in a shield cell bank
close: 'Schließen',
close: 'schließen',
compare: 'vergleichen',
'compare all': 'alles vergleichen',
comparison: 'Vergleich',
comparisons: 'Vergleiche',
cost: 'Preis', // Cost / price of a module or price of a ship
costs: 'Kosten', // Costs / prices of a modules or prices of ships
create: 'erstellen',
'create new': 'neu erstellen',
credits: 'Credits',
damage: 'Schaden',
delete: 'Löschen',
dep: 'Ausg', // Weapons/Hardpoints Deployed abbreviation
deployed: 'Ausgefahren', // Weapons/Hardpoints Deployed
disabled: 'Deaktiviert',
'damage per second': 'Schaden pro Sekunde',
delete: 'löschen',
'delete all': 'alles löschen',
dep: 'ausg', // Weapons/Hardpoints Deployed abbreviation
deployed: 'ausgefahren', // Weapons/Hardpoints Deployed
'detailed export': 'detailierter Export',
disabled: 'deaktiviert',
discount: 'Rabatt',
'edit data': 'bearbeiten',
efficiency: 'Effizienz', // Power Plant efficiency
empty: 'leer',
'empty all': 'alles entfernen',
ENG: 'ANT', // Abbreviation - Engine recharge rate for power distributor
'Enter Name': 'Namen eingeben',
Explorer: 'Forscher',
export: 'Export',
'fastest range': 'maximale Reichweite', // Fastet totaljump range - sum of succesive jumps
forum: 'Forum',
fuel: 'Treibstoff',
'fuel level': 'Tankfüllstand', // Percent of fuel (T) in the tank
'full tank': 'Tank voll',
hardpoints: 'Waffenaufhängungen',
hull: 'Hülle', // Ships hull
hull: 'Rumpf', // Ships hull
import: 'importieren',
insurance: 'Versicherung',
jump: null, // Single jump range
'internal compartments': 'Innenbereich',
jump: 'Sprung', // Single jump range
'jump range': 'Sprungreichweite',
jumps: 'Sprünge',
laden: 'beladen',
language: 'Sprache',
maneuverability: 'Manövrierbarkeit',
manufacturer: null,
manufacturer: 'Hersteller',
mass: 'Masse',
MLF: null, // Mass Lock Factor Abbreviation
MNV: null, // Maneuverability abbreviation
module: 'modul',
modules: 'module',
'mass lock factor': 'Massensperrefaktor',
'max mass': 'maximale Masse',
MLF: 'MSF', // Mass Lock Factor Abbreviation
module: 'Modul',
modules: 'Module',
'net cost': 'Nettokosten',
no: 'Nein',
'none created': 'nicht erstellt',
ok: 'OK',
optimize: null,
'optimal mass': 'optimale Masse', // Lowest weight / best weight for jump distance, etc
optimize: 'optimieren',
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
pri: 'Prio', // Priority abbreviation for power management
proceed: 'Fortfahren',
PWR: 'En', // Power Abbreviation. See Power
qty: null, // Quantity abbreviation
proceed: 'fortfahren',
qty: 'Menge', // Quantity abbreviation
range: 'Reichweite',
rate: 'Rate',
recharge: null, // Shield Recharge time from 50% -> 100%
recovery: null, // Shield recovery time (after losing shields/turning on -> 50%)
recharge: 'aufladen', // Shield Recharge time from 50% -> 100%
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 costs': 'Nachladekosten',
rename: 'umbenennen',
repair: 'reparieren',
reset: 'zurücksetzen',
ret: 'Eing', // Retracted abbreviation
retracted: 'Eingefahren', // Weapons/Hardpoints retracted
ret: 'eing', // Retracted abbreviation
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
roles: null, // Commander/Ship build roles - e.g. Trader, Bounty-Hunter, Explorer, etc
save: 'Speichern',
sell: 'Verkaufen',
roles: 'Rollen', // Commander/Ship build roles - e.g. Trader, Bounty-Hunter, Explorer, etc
save: 'speichern',
sell: 'verkaufen',
settings: 'Einstellungen', // Coriolis application settings
shields: 'Schilde',
ship: 'Schiff',
@@ -205,16 +148,19 @@ export const terms = {
size: 'Größe',
skip: 'überspringen', // Skip past something / ignore it
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
strength: null, // Strength in reference to Shield Strength
subtotal: null,
strength: 'Stärke', // Strength in reference to Shield Strength
subtotal: 'Zwischensumme',
time: 'Dauer', // time it takes to complete something
tooltips: null, // Tooltips setting - show/hide
total: 'Gesamt',
Trader: null, // Trader role
tooltips: 'Tooltips', // Tooltips setting - show/hide
total: 'gesamt',
'total range': 'Gesamtbereich',
Trader: 'Händler', // Trader role
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
yes: 'Ja'
yes: 'ja'
};

View File

@@ -14,20 +14,22 @@ export const formats = {
};
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_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_FASTEST_RANGE: 'Consecutive max range jumps',
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_RETROCH: 'No Retrofitting changes',
PHRASE_SELECT_BUILDS: 'Select Builds to Compare',
PHRASE_SG_RECHARGE: 'Time from 50% to 100% Charge',
PHRASE_SELECT_BUILDS: 'Select builds to compare',
PHRASE_SG_RECHARGE: 'Time from 50% to 100% charge',
PHRASE_SG_RECOVER: 'Recovery (to 50%) after collapse',
PHRASE_UNLADEN: 'Ship Mass excluding Fuel and Cargo',
PHRASE_UPDATE_RDY: 'Update Available! Click to Refresh',
PHRASE_UNLADEN: 'Ship mass excluding fuel and cargo',
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
am: 'Auto Field-Maintenance Unit',
bh: 'Bulkheads',
@@ -35,11 +37,13 @@ export const terms = {
bsg: 'Bi-Weave Shield Generator',
c: 'Cannon',
cc: 'Collector Limpet Controller',
cm: 'Countermeasure',
ch: 'Chaff Launcher',
cr: 'Cargo Rack',
cs: 'Cargo Scanner',
cs: 'Manifest Scanner',
dc: 'Docking Computer',
ec: 'Electronic Countermeasure',
fc: 'Fragment Cannon',
fh: 'Fighter Hangar',
fi: 'FSD Interdictor',
fs: 'Fuel Scoop',
fsd: 'Frame Shift Drive',
@@ -47,6 +51,7 @@ export const terms = {
fx: 'Fuel Transfer Limpet Controller',
hb: 'Hatch Breaker Limpet Controller',
hr: 'Hull Reinforcement Package',
hs: 'Heat Sink Launcher',
kw: 'Kill Warrant Scanner',
ls: 'Life Support',
mc: 'Multi-cannon',
@@ -56,11 +61,16 @@ export const terms = {
pa: 'Plasma Accelerator',
pas: 'Planetary Approach Suite',
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',
pl: 'Pulse Laser',
po: 'Point Defence',
pp: 'Power Plant',
psg: 'Prismatic Shield Generator',
pv: 'Planetary Vehicle Hanger',
pv: 'Planetary Vehicle Hangar',
rf: 'Refinery',
rg: 'Rail Gun',
s: 'Sensors',
@@ -71,5 +81,64 @@ export const terms = {
t: 'thrusters',
tp: 'Torpedo Pylon',
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 = {
// 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_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_NO_BUILDS: 'Défaut de configuration pour comparaison', // No builds added to comparison!
PHRASE_NO_RETROCH: 'configuration non modifiée', // No Retrofitting changes
PHRASE_SELECT_BUILDS: 'Sélectionner configurations à comparer', // Select Builds to Compare
PHRASE_LADEN: 'Masse du Vaisseau + Carburant + Cargo', // Ship Mass + Fuel + Cargo
PHRASE_NO_BUILDS: 'Aucune configuration ajoutée pour la comparaison', // No builds added to comparison!
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
// 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
Ls: 'SL', // Light seconds
LY: 'AL', // Light Years
// Sizes
S: 'P', // Small Hardpoint (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
'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
added: 'ajouté',
ammo: 'munition', // Ammunition
armour: 'Armure',
available: 'Disponibilité',
available: 'Disponibilité', // Available options
backup: 'sauvegarde',
base: null,
bays: null,
bays: 'baies',
bins: 'bacs', // Number of Mining Refinery bins
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
buy: 'Acheter',
cancel: 'Annuler',
@@ -135,58 +58,77 @@ export const terms = {
cells: 'Cellule', // Number of cells in a shield cell bank
close: 'fermer',
compare: 'comparer',
'compare all': 'tout comparer',
comparison: 'comparaison',
comparisons: 'comparaisons',
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
create: 'Créer',
'create new': 'Créer nouveau',
credits: 'crédits',
damage: 'Dégâts',
damage: 'dégât',
'damage per second': 'dégât par seconde',
delete: 'supprimer',
'delete all': 'tout supprimer',
dep: 'depl', // Weapons/Hardpoints Deployed abbreviation
deployed: 'déployé', // Weapons/Hardpoints Deployed
'detailed export': 'export détaillé',
disabled: 'désactivé',
discount: 'ristourne',
'edit data': 'Editer donnée',
efficiency: 'rendement', // Power Plant efficiency
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 level': 'niveau de carburant', // Percent of fuel (T) in the tank
'full tank': 'Réservoir plein',
hardpoints: 'Points d\'emport',
hull: 'Coque', // Ships hull
import: 'Importer',
insurance: 'Assurance',
jump: null, // Single jump range
'internal compartments': 'compartiments internes',
jump: 'saut', // Single jump range
'jump range': 'Distance de saut',
jumps: 'Sauts',
laden: 'chargé',
language: 'Langage',
maneuverability: null,
manufacturer: null,
maneuverability: 'maniabilité',
manufacturer: 'fabricant',
mass: 'Masse',
MLF: null, // Mass Lock Factor Abbreviation
MNV: null, // Maneuverability abbreviation
module: null,
modules: null,
'mass lock factor': 'facteur inhibition de masse',
'max mass': 'masse max',
MLF: 'FIM', // Mass Lock Factor Abbreviation
'net cost': 'coûts nets',
no: 'non',
'none created': 'Rien de créé',
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
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
proceed: 'continuer',
PWR: 'P', // Power Abbreviation. See Power
qty: null, // Quantity abbreviation
qty: 'quantité', // Quantity abbreviation
range: 'portée',
rate: 'cadence',
recharge: null, // Shield Recharge time from 50% -> 100%
recovery: null, // Shield recovery time (after losing shields/turning on -> 50%)
recharge: 'recharger', // Shield Recharge time from 50% -> 100%
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 costs': 'recharger coûts',
rename: 'renommer',
repair: 'réparer',
reset: 'Réinitialisation',
ret: 'esc', // Retracted abbreviation
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
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',
sell: 'vendre',
settings: 'paramètres', // Coriolis application settings
@@ -198,12 +140,16 @@ export const terms = {
skip: 'Suivant', // Skip past something / ignore it
speed: 'vitesse',
Stock: 'de base', // Thermal-load abbreviation
strength: null, // Strength in reference to Shield Strength
subtotal: null,
strength: 'force', // Strength in reference to Shield Strength
subtotal: 'Sous-Total',
'T-Load': 'degrés', // Thermal load abbreviation
time: 'temps', // time it takes to complete something
tooltips: null, // Tooltips setting - show/hide
Trader: null, // Trader role
tooltips: 'infobulles', // Tooltips setting - show/hide
'total range': 'plage totale',
Trader: 'commerçant', // Trader role
'unit cost': 'coût unitaire',
unladen: 'Non chargé', // No cargo or fuel
'utility mounts': 'Support utilitaire',
WEP: 'ARM', // Abbreviation - Weapon recharge rate for power distributor
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_CONFIRMATION: 'Вы уверены?', // Are You Sure?
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_LADEN: null, // Ship Mass + Fuel + Cargo
PHRASE_LADEN: 'Масса корабля с учётом топлива и грузов', // Ship Mass + Fuel + Cargo
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_SG_RECHARGE: null, // Time from 50% to 100% Charge
PHRASE_SG_RECOVER: null, // Recovery (to 50%) after collapse
PHRASE_UNLADEN: null, // Ship Mass excluding Fuel and Cargo
PHRASE_SG_RECHARGE: 'восстановление с 60% до 100% объема щита', // Time from 50% to 100% Charge
PHRASE_SG_RECOVER: 'восстановление [до 60%] после снятия щита', // Recovery (to 50%) after collapse
PHRASE_UNLADEN: 'Масса корабля без учета топлива и грузов', // Ship Mass excluding Fuel and Cargo
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
'/min': null, // Per minute
'/s': null, // Per second
kg: null, // Kilogram
'kg/s': null, // Kilograms per second
km: null, // Kilometer
'/s': '/с', // Per second
'm/s': 'м/с', // Meters / Second
Ls: 'Св.сек', // Light seconds
LY: 'Св.лет', // Light Years
MJ: null, // Mega Joules
MW: null, // Mega Watts
CR: 'кр.', // Credits abbreviation
// Sizes
@@ -110,24 +42,25 @@ export const terms = {
L: 'б', // Large Hardpoint size (single character)
H: 'O', // Huge Hardpoint size (single character)
U: 'B', // Utility Hardpoint size (single character) - Kill warrant scanner, etc
small: 'Малый', // Small ship size
medium: 'Средний', // Medium ship size
large: 'большой', // Large Ship Size
// Insurance
alpha: 'Альфа', // Alpha backer insurance level
beta: 'Бета', // Beta back insurance level
standard: 'Стандартный', // Standard insurance level
// Terms
'build name': 'название сборки', // Ship build/configuration/design name
'compare all': 'сравнить все',
'create new': 'Создать новый',
'damage per second': null,
'damage per second': 'урон в секунду',
'delete all': 'Удалить все',
'detailed export': 'Подробный экспорт',
'edit data': 'Редактирование',
'empty all': null,
'empty all': 'пусто все',
'Enter Name': 'Введите имя',
'fastest range': null, // Fastet totaljump range - sum of succesive jumps
'fuel level': null, // Percent of fuel (T) in the tank
'fastest range': 'быстрый диапазон', // Fastet totaljump range - sum of succesive jumps
'fuel level': 'уровень топлива', // Percent of fuel (T) in the tank
'full tank': 'Полный бак',
'internal compartments': 'внутренние отсеки',
'jump range': 'Дальность прыжка',
@@ -135,24 +68,18 @@ export const terms = {
'max mass': 'Максимальная масса',
'net cost': 'разница в цене',
'none created': 'не создано',
'optimal mass': null, // Lowest weight / best weight for jump distance, etc
'refuel time': 'Время дозаправки', // Time to refuel the tank when scooping
'reload costs': null,
'retrofit costs': 'цена модификации', // The cost difference when upgrading / downgrading a component
'retrofit from': 'модификация от', // Retrofit from Build A against build B
'T-Load': 'Тепл.', // Thermal load abbreviation
'total range': null,
'unit cost': null,
'utility mounts': 'Вспомогательное оборудование',
about: 'О ...', // Link to about page / about Coriolis.io
action: 'Действие',
added: 'Добавлено',
ammo: 'Боекомплект', // Ammunition
armour: 'Броня',
available: 'доступно',
available: 'доступно', // Available options
backup: 'Резервная копия',
base: null,
bays: null,
bins: 'контейнеры', // Number of Mining Refinery bins
boost: 'форсаж',
build: 'cборка', // Shorthand for the build/configuration/design name
@@ -179,7 +106,6 @@ export const terms = {
efficiency: 'Эффективность', // Power Plant efficiency
empty: 'пусто',
ENG: 'ДВГ', // Abbreviation - Engine recharge rate for power distributor
Explorer: null,
export: 'Экспорт',
forum: 'Форум',
fuel: 'Топливо',
@@ -187,32 +113,26 @@ export const terms = {
hull: 'Корпус', // Ships hull
import: 'импортировать ',
insurance: 'Страховка',
jump: null, // Single jump range
jumps: 'Прыжков',
laden: 'Груженый',
language: 'Язык',
maneuverability: 'Моневренность',
manufacturer: null,
maneuverability: 'Маневренность',
mass: 'Масса',
max: 'Макс',
MLF: null, // Mass Lock Factor Abbreviation
MNV: null, // Maneuverability abbreviation
module: null,
modules: null,
no: 'Нет',
ok: null,
optimize: null,
pen: 'ПБ', // Armour peneration abbreviation
permalink: 'Постоянная ссылка',
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
proceed: 'продолжить',
PWR: 'Эн', // Power Abbreviation. See Power
qty: null, // Quantity abbreviation
range: 'Дальность',
rate: 'скорость',
recharge: null, // Shield Recharge time from 50% -> 100%
recovery: null, // Shield recovery time (after losing shields/turning on -> 50%)
recharge: 'перезарядка', // Shield Recharge time from 50% -> 100%
recovery: 'включение', // Shield recovery time (after losing shields/turning on -> 50%)
reload: 'Перезагрузить', // Reload weapon/hardpoint
rename: 'Переименовать',
repair: 'Починка',
@@ -220,7 +140,7 @@ export const terms = {
ret: 'Убр.', // Retracted abbreviation
retracted: 'Убрано', // Weapons/Hardpoints retracted
ROF: 'В/сек', // Rate of Fire abbreviation
roles: null, // Commander/Ship build roles - e.g. Trader, Bounty-Hunter, Explorer, etc
save: 'Сохранить',
sell: 'Продать',
settings: 'Настройки', // Coriolis application settings
@@ -233,13 +153,9 @@ export const terms = {
speed: 'скорость',
standard: 'Стандартный', // Standard / Common modules (FSD, power plant, life support, etc)
Stock: 'Стандартная комплектация', // Thermal-load abbreviation
strength: null, // Strength in reference to Shield Strength
subtotal: null,
SYS: 'СИСТЕМЫ', // Abbreviation - System recharge rate for power distributor
time: 'Время', // time it takes to complete something
tooltips: null, // Tooltips setting - show/hide
total: 'Всего',
Trader: null, // Trader role
type: 'Тип',
unladen: 'Пустой', // No cargo or fuel
URL: 'Ссылка', // Link, Uniform Resource Locator

View File

@@ -24,15 +24,17 @@ export default class AboutPage extends Page {
*/
renderPage() {
return <div className={'page'} style={{ textAlign: 'left', maxWidth: 800, margin: '0 auto' }}>
<h1><CoriolisLogo style={{ marginRight: '0.4em' }} className='xl'/><span className='warning'>Coriolis</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>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/cmmcleod/coriolis' target='_blank' title='Coriolis Github Project'>
<a style={{ display: 'block', textDecoration: 'none' }} href='https://github.com/EDCD/coriolis' target='_blank' title='Coriolis Github Project'>
<GitHub style={{ margin: '0.4em' }} className='l fg xl'/>
<h2 style={{ margin: 0, textDecoration: 'none' }}>Github</h2>
github.com/cmmcleod/coriolis
github.com/EDCD/coriolis
</a>
<p>Coriolis is an open source project. Checkout the list of upcoming features and to-do list on github. Any and all contributions and feedback are welcome. If you encounter any bugs please report them and provide as much detail as possible.</p>
@@ -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>
<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>;
}
}

View File

@@ -53,6 +53,7 @@ export default class ComparisonPage extends Page {
super(props, context);
this._sortShips = this._sortShips.bind(this);
this._buildsSelected = this._buildsSelected.bind(this);
this._updateDiscounts = this._updateDiscounts.bind(this);
this.state = this._initState(context);
}
@@ -62,7 +63,7 @@ export default class ComparisonPage extends Page {
* @return {Object} New state object
*/
_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 code = params.code;
let name = params.name ? decodeURIComponent(params.name) : null;
@@ -130,7 +131,7 @@ export default class ComparisonPage extends Page {
builds.sort(sortBy(predicate));
return {
title: 'Coriolis - Compare',
title: 'Coriolis EDCD Edition - Compare',
predicate,
desc,
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
b.buildFrom(code); // Populate components from code
b.buildName = name;
b.applyDiscounts(Persist.getShipDiscount(), Persist.getModuleDiscount());
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
* @param {Object} nextProps Incoming/Next properties
@@ -399,6 +416,7 @@ export default class ComparisonPage extends Page {
*/
componentWillMount() {
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() {
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_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
*/
@@ -65,10 +75,11 @@ export default class OutfittingPage extends Page {
}
let fuelCapacity = ship.fuelCapacity;
this._getTitle = getTitle.bind(this, data.properties.name);
return {
error: null,
title: 'Outfitting - ' + data.properties.name,
title: this._getTitle(buildName),
costTab: Persist.getCostTab() || 'costs',
buildName,
newBuildName: buildName,
@@ -117,7 +128,7 @@ export default class OutfittingPage extends Page {
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,
canRename = buildName && newBuildName && buildName != newBuildName,
canReload = savedCode && canSave,
hStr = ship.getHardpointsString(),
sStr = ship.getStandardString(),
iStr = ship.getInternalString();
hStr = ship.getHardpointsString() + '.' + ship.getModificationsString(),
sStr = ship.getStandardString() + '.' + ship.getModificationsString(),
iStr = ship.getInternalString() + '.' + ship.getModificationsString();
return (
<div id='outfit' className={'page'} style={{ fontSize: (sizeRatio * 0.9) + 'em' }}>
<div id='overview'>
<h1>{ship.name}</h1>
<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}>
<FloppyDisk className='lg' />
</button>

View File

@@ -8,17 +8,18 @@ import { shallowEqual } from '../utils/UtilityFunctions';
export default class Page extends React.Component {
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,
showModal: React.PropTypes.func.isRequired,
hideModal: React.PropTypes.func.isRequired,
tooltip: React.PropTypes.func.isRequired,
termtip: React.PropTypes.func.isRequired,
language: React.PropTypes.object.isRequired,
noTouch: React.PropTypes.bool.isRequired,
onCommand: 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 = {

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;
}
/**
* 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
*/
@@ -41,20 +73,30 @@ export default class ShipyardPage extends Page {
*/
constructor(props, context) {
super(props, context);
this.state = {
title: 'Coriolis - Shipyard',
shipPredicate: 'name',
shipDesc: true
};
if (!ShipyardPage.cachedShipSummaries) {
ShipyardPage.cachedShipSummaries = [];
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 });
};
/**
* 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
* @param {Object} s Ship summary
@@ -118,8 +128,14 @@ export default class ShipyardPage extends Page {
* @return {React.Component} Table Row
*/
_shipRowElement(s, translate, u, fInt, fRound) {
return <tr key={s.id} className='highlight'>
<td className='le'><Link href={'/outfit/' + s.id}>{s.name}</Link></td>
let noTouch = this.context.noTouch;
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='cap'>{translate(SizeMap[s.class])}</td>
<td>{s.agility}</td>
@@ -155,17 +171,27 @@ export default class ShipyardPage extends Page {
* @return {React.Component} The page contents
*/
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 fRound = formats.round;
let shipSummaries = this.shipSummaries;
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 { shipSummaries, shipPredicate, shipPredicateIndex } = this.state;
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
shipSummaries.sort((a, b) => {
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) {
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 (
<div className='page' style={{ fontSize: this.context.sizeRatio + 'em' }}>
<div className='scroll-x'>
<table style={{ fontSize:'0.85em', whiteSpace:'nowrap', margin: '0 auto' }} align='center'>
<div className='page' style={{ fontSize: sizeRatio + 'em' }}>
<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>
<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>
<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('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('max')}</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 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>
</tr>
<tr>
@@ -243,10 +295,11 @@ export default class ShipyardPage extends Page {
<th className='sortable' onClick={sortShips('int', 7)} >8</th>
</tr>
</thead>
<tbody>
{shipRows}
<tbody onMouseLeave={this._highlightShip.bind(this, null)}>
{detailRows}
</tbody>
</table>
</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
@@ -8,7 +9,9 @@
* @return {number} Distance in Light Years
*/
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
*/
export function fastestRange(mass, fsd, fuel) {
let fuelRemaining = fuel % fsd.maxfuel; // Fuel left after making N max jumps
let jumps = Math.floor(fuel / fsd.maxfuel);
let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : 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;
// 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 (let j = 0; j < jumps; j++) {
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;
};
@@ -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.
*
* @param {number} mass Current mass of the ship
* @param {number} shields Base Shield strength MJ for ship
* @param {object} sg The shield generator used
* @param {number} multiplier Shield multiplier for ship (1 + shield boosters if any)
* @return {number} Approximate shield strengh in MJ
* @param {number} mass Current mass of the ship
* @param {number} baseShield Base Shield strength MJ for ship
* @param {object} sg The shield generator used
* @param {number} multiplier Shield multiplier for ship (1 + shield boosters if any)
* @return {number} Approximate shield strengh in MJ
*/
export function shieldStrength(mass, shields, sg, multiplier) {
let opt;
if (mass < sg.minmass) {
return shields * multiplier * sg.minmul;
}
if (mass > sg.maxmass) {
return shields * multiplier * sg.maxmul;
}
if (mass < sg.optmass) {
opt = (sg.optmass - mass) / (sg.optmass - sg.minmass);
opt = 1 - Math.pow(1 - opt, 0.87);
return shields * multiplier * ((opt * sg.minmul) + ((1 - opt) * sg.optmul));
} else {
opt = (sg.optmass - mass) / (sg.maxmass - sg.optmass);
opt = -1 + Math.pow(1 + opt, 2.425);
return shields * multiplier * ((-1 * opt * sg.maxmul) + ((1 + opt) * sg.optmul));
}
export function shieldStrength(mass, baseShield, sg, multiplier) {
// sg might be a module or a template; handle either here
let minMass = sg instanceof Module ? sg.getMinMass() : sg.minmass;
let optMass = sg instanceof Module ? sg.getOptMass() : sg.optmass;
let maxMass = sg instanceof Module ? sg.getMaxMass() : sg.maxmass;
let minMul = sg instanceof Module ? sg.getMinMul() : sg.minmul;
let optMul = sg instanceof Module ? sg.getOptMul() : sg.optmul;
let maxMul = sg instanceof Module ? sg.getMaxMul() : sg.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);
return baseShield * mul * multiplier;
}
/**
@@ -72,13 +75,24 @@ export function shieldStrength(mass, shields, sg, multiplier) {
* @return {object} Approximate speed by pips
*/
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)));
let speed = baseSpeed * multiplier;
// thrusters might be a module or a template; handle either here
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 {
'0 Pips': speed * (1 - (pipSpeed * 4)),
'2 Pips': speed * (1 - (pipSpeed * 2)),
'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 StandardArray = [
@@ -37,17 +29,22 @@ export const ModuleGroupToName = {
am: 'Auto Field-Maintenance Unit',
bsg: 'Bi-Weave Shield Generator',
cr: 'Cargo Rack',
fh: 'Fighter Hangar',
fi: 'Frame Shift Drive Interdictor',
hb: 'Hatch Breaker Limpet Controller',
hr: 'Hull Reinforcement Package',
rf: 'Refinery',
scb: 'Shield Cell Bank',
sg: 'Shield Generator',
pv: 'Planetary Vehicle Hanger',
pv: 'Planetary Vehicle Hangar',
psg: 'Prismatic Shield Generator',
dc: 'Docking Computer',
fx: 'Fuel Transfer 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',
// Hard Points
@@ -188,6 +185,20 @@ export const ShipFacets = [
lbls: ['DPS'],
fmt: 'round',
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
@@ -19,12 +21,13 @@ export default class ModuleSet {
/**
* Instantiate the module set
* @param {Object} modules All Modules
* @param {number} mass Ship mass
* @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
* @param {Object} shipData Ship Specifications Data (see coriolis-data/Ships)
*/
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;
this.mass = mass;
this.standard = {};
@@ -33,6 +36,10 @@ export default class ModuleSet {
this.hpClass = {};
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[2] = filter(stnd.fsd, maxStandardArr[2], 0, mass); // FSD
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
* @param {Object} ship The ship
* @param {integer} c The max class module that can be mounted in the slot
* @param {Object} eligible) The map of eligible internal groups
* @return {object} A map of all eligible modules by group
*/
getInts(c, eligible) {
getInts(ship, c, eligible) {
let o = {};
for (let key in this.internal) {
if (eligible && !eligible[key]) {
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);
if (data.length) { // If group is not empty
o[key] = data;
@@ -103,11 +126,11 @@ export default class ModuleSet {
let pd = this.standard[4][0];
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;
}
}
return pd;
return new Module({ template: pd });
};
/**
@@ -123,7 +146,7 @@ export default class ModuleSet {
th = t;
}
}
return th;
return new Module({ template: th });
};
/**
@@ -135,11 +158,11 @@ export default class ModuleSet {
let sg = this.internal.sg[0];
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;
}
}
return sg;
return new Module({ template: sg });
};
/**
@@ -152,10 +175,10 @@ export default class ModuleSet {
for (let p of this.standard[0]) {
// 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;
}
}
return pp;
return new Module({ template: pp });
}
}

View File

@@ -1,7 +1,12 @@
import { ModuleNameToGroup, BulkheadNames, StandardArray } from './Constants';
import ModuleSet from './ModuleSet';
import Module from './Module';
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
*/
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
* @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)));
if (s) {
s = new Module({ template: s });
}
return s || null;
};
/**
* Finds the hardpoint with the specified ID
* @param {string} id Hardpoint ID
* @param {String} id Hardpoint ID
* @return {Object} Hardpoint module or null
*/
export function hardpoints(id) {
@@ -37,7 +81,7 @@ export function hardpoints(id) {
let group = Modules.hardpoints[n];
for (let i = 0; i < group.length; i++) {
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
* @param {string} id Internal module ID
* @param {String} id Internal module ID
* @return {Object} Internal module or null
*/
export function internal(id) {
@@ -54,7 +98,7 @@ export function internal(id) {
let group = Modules.internal[n];
for (let i = 0; i < group.length; i++) {
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.
* 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 {string} rating module Rating
* @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
* @param {String} rating module Rating
* @param {String} name [Optional] Long/unique name for module -e.g. 'Advanced Discover Scanner'
* @return {Object} The module if found, null if not found
*/
export function findInternal(groupName, clss, rating, name) {
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.
* 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 {string} rating Module Rating
* @param {string} name [Optional] Long/unique name for module -e.g. 'Advanced Discover Scanner'
* @param {String} rating Module Rating
* @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
*/
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.
* 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 {string} rating [Optional] module Rating
* @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} missile [Optional] Missile type - [D]umbfire, [S]eeker
* @param {String} rating [Optional] module Rating
* @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} missile [Optional] Missile type - [D]umbfire, [S]eeker
* @return {String} The id of the module if found, null if not found
*/
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.
* 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 {string} rating module Rating
* @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} missile [Optional] Missile type - [D]umbfire, [S]eeker
* @param {String} rating module Rating
* @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} missile [Optional] Missile type - [D]umbfire, [S]eeker
* @return {String} The id of the module if found, null if not found
*/
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;
}
/**
* 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
* @param {string} bulkheadName Bulkhead name in english
* @param {String} bulkheadName Bulkhead name in english
* @return {number} Bulkhead index
*/
export function bulkheadIndex(bulkheadName) {
@@ -198,7 +227,7 @@ export function bulkheadIndex(bulkheadName) {
/**
* 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
*/
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
*
* @param {string} shipId Unique ship Id/Key
* @param {String} shipId Unique ship Id/Key
* @return {ModuleSet} The set of modules the ship can install
*/
export function forShip(shipId) {
let ship = 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]);
return new ModuleSet(Modules, Ships[shipId]);
}

View File

@@ -6,6 +6,33 @@ import LZString from 'lz-string';
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
* @param {Object} slot Slot model
@@ -30,6 +57,9 @@ function slotToSchema(slot) {
if (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 null;
@@ -48,26 +78,26 @@ export function toDetailedBuild(buildName, ship) {
code = ship.toString();
let data = {
$schema: 'http://cdn.coriolis.io/schemas/ship-loadout/3.json#',
$schema: 'http://cdn.coriolis.io/schemas/ship-loadout/4.json#',
name: buildName,
ship: ship.name,
references: [{
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,
shipId: ship.id
}],
components: {
standard: {
bulkheads: BulkheadNames[ship.bulkheads.index],
bulkheads: BulkheadNames[ship.bulkheads.m.index],
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 },
thrusters: { class: standard[1].m.class, rating: standard[1].m.rating, enabled: Boolean(standard[1].enabled), priority: standard[1].priority + 1 },
frameShiftDrive: { class: standard[2].m.class, rating: standard[2].m.rating, enabled: Boolean(standard[2].enabled), priority: standard[2].priority + 1 },
lifeSupport: { class: standard[3].m.class, rating: standard[3].m.rating, enabled: Boolean(standard[3].enabled), priority: standard[3].priority + 1 },
powerDistributor: { class: standard[4].m.class, rating: standard[4].m.rating, enabled: Boolean(standard[4].enabled), priority: standard[4].priority + 1 },
sensors: { class: standard[5].m.class, rating: standard[5].m.rating, enabled: Boolean(standard[5].enabled), priority: standard[5].priority + 1 },
fuelTank: { class: standard[6].m.class, rating: standard[6].m.rating, enabled: Boolean(standard[6].enabled), priority: standard[6].priority + 1 }
powerPlant: standardToSchema(standard[0]),
thrusters: standardToSchema(standard[1]),
frameShiftDrive: standardToSchema(standard[2]),
lifeSupport: standardToSchema(standard[3]),
powerDistributor: standardToSchema(standard[4]),
sensors: standardToSchema(standard[5]),
fuelTank: standardToSchema(standard[6])
},
hardpoints: 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
* @return {Ship} Ship instance
*/
@@ -97,6 +127,26 @@ export function fromDetailedBuild(detailedBuild) {
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 stn = comps.standard;
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 ModuleUtils from './ModuleUtils';
import Module from './Module';
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} modul The modul in the slot
* @param {Object} modul The module in the slot
* @return {String} The key for the power usage type
*/
function powerUsageType(slot, modul) {
@@ -114,7 +117,7 @@ export default class Ship {
*/
canThrust() {
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
*/
canBoost() {
return this.canThrust() && // Thrusters operational
this.getSlotStatus(this.standard[4]) == 3 && // Power distributor operational
this.boostEnergy <= this.standard[4].m.enginecapacity; // PD capacitor is sufficient for boost
return this.canThrust() && // Thrusters operational
this.getSlotStatus(this.standard[4]) == 3 && // Power distributor operational
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) {
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
*/
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
return ((this.shieldStrength / 2) / this.sgSlot.m.recover) + 15;
return ((this.shield / 2) / (sgSlot.m.recover * brokenRegenRate)) + 15;
}
return 0;
}
@@ -203,9 +209,11 @@ export default class Ship {
* @return {Number} 50 - 100% Recharge time in seconds
*/
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
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;
}
@@ -214,16 +222,20 @@ export default class Ship {
* Calculate the hypothetical shield strength for the ship using the specified parameters
* @param {Object} sg [optional] Shield Generator to use
* @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) {
if (!sg) {
if (!this.sgSlot) {
let sgSlot = this.findInternalByGroup('sg');
if (!sgSlot) {
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.getPowerPrioritesString()
this.getPowerPrioritiesString(),
'.',
this.getModificationsString()
].join('');
}
@@ -294,8 +308,8 @@ export default class Ship {
*/
getStandardString() {
if(!this.serialized.standard) {
this.serialized.standard = this.bulkheads.index + this.standard.reduce((arr, slot, i) => {
arr[i] = slot.m ? slot.m.class + slot.m.rating : '-';
this.serialized.standard = this.bulkheads.m.index + this.standard.reduce((arr, slot, i) => {
arr[i] = slot.m ? slot.m.id : '-';
return arr;
}, new Array(this.standard.length)).join('');
}
@@ -324,6 +338,17 @@ export default class Ship {
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
* @return {String} Serialized active/inactive settings
@@ -336,7 +361,7 @@ export default class Ship {
* Get the serialized module priority settings
* @return {String} Serialized priority settings
*/
getPowerPrioritesString() {
getPowerPrioritiesString() {
return this.serialized.priorities;
}
@@ -368,18 +393,68 @@ export default class Ship {
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.
* @param {Object} comps Collection of ModuleUtils used to build the ship
* @param {array} priorities Slot priorities
* @param {Array} enabled Slot active/inactive
* @param {Array} mods Modifications
* @return {this} The current ship instance for chaining
*/
buildWith(comps, priorities, enabled) {
buildWith(comps, priorities, enabled, mods) {
let internal = this.internal,
standard = this.standard,
hps = this.hardpoints,
bands = this.priorityBands,
cl = standard.length,
i, l;
@@ -387,26 +462,26 @@ export default class Ship {
this.fuelCapacity = 0;
this.cargoCapacity = 0;
this.ladenMass = 0;
this.armourAdded = 0;
this.armourMultiplier = 1;
this.shieldMultiplier = 1;
this.armour = this.baseArmour;
this.shield = this.baseShieldStrength;
this.totalCost = this.m.incCost ? this.m.discountedCost : 0;
this.unladenMass = this.hullMass;
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.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.enabled = enabled ? enabled[0] * 1 : true;
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;
}
this.cargoHatch.mods = mods ? mods[0] : {};
for (i = 0; i < cl; i++) {
standard[i].cat = 0;
@@ -415,9 +490,10 @@ export default class Ship {
standard[i].type = 'SYS';
standard[i].m = null; // Resetting 'old' modul if there was one
standard[i].discountedCost = 0;
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;
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;
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
if (comps) {
this.updatePower()
this.updatePowerGenerated()
.updatePowerUsed()
.updateJumpStats()
.updateShieldStrength()
.updateShield()
.updateArmour()
.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),
hardpoints = new Array(this.hardpoints.length),
internal = new Array(this.internal.length),
mods = new Array(1 + this.standard.length + this.hardpoints.length + this.internal.length),
parts = serializedString.split('.'),
priorities = null,
enabled = null,
@@ -488,6 +571,19 @@ export default class Ship {
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)));
return this.buildWith(
@@ -498,7 +594,8 @@ export default class Ship {
internal
},
priorities,
enabled
enabled,
mods
);
};
@@ -584,18 +681,20 @@ export default class Ship {
if (slot.enabled != enabled) { // Enabled state is changing
slot.enabled = enabled;
if (slot.m) {
this.priorityBands[slot.priority][powerUsageType(slot, slot.m)] += enabled ? slot.m.power : -slot.m.power;
if (ModuleUtils.isShieldGenerator(slot.m.grp)) {
this.updateShieldStrength();
} else if (slot.m.grp == 'sb') {
this.shieldMultiplier += slot.m.shieldmul * (enabled ? 1 : -1);
this.updateShieldStrength();
} else if (slot.m.dps) {
this.totalDps += slot.m.dps * (enabled ? 1 : -1);
if (ModuleUtils.isShieldGenerator(slot.m.grp) || slot.m.grp == 'sb') {
this.updateShield();
}
if (slot.m.getDps()) {
this.totalDps += slot.m.getDps() * (enabled ? 1 : -1);
}
if (slot.m.getEps()) {
this.totalEps += slot.m.getEps() * (enabled ? 1 : -1);
}
if (slot.m.getHps()) {
this.totalHps += slot.m.getHps() * (enabled ? 1 : -1);
}
this.updatePower();
this.updatePowerUsed();
this.updatePowerEnabledString();
}
}
@@ -615,10 +714,7 @@ export default class Ship {
this.updatePowerPrioritesString();
if (slot.enabled) { // Only update power if the slot is enabled
let usage = powerUsageType(slot, slot.m);
this.priorityBands[oldPriority][usage] -= slot.m.power;
this.priorityBands[newPriority][usage] += slot.m.power;
this.updatePower();
this.updatePowerUsed();
}
return true;
}
@@ -634,7 +730,12 @@ export default class Ship {
* @return {this} The ship instance (for chaining operations)
*/
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
switch (old.grp) {
@@ -644,27 +745,27 @@ export default class Ship {
case 'cr':
this.cargoCapacity -= old.cargo;
break;
case 'hr':
this.armourAdded -= old.armouradd;
break;
case 'sb':
this.shieldMultiplier -= slot.enabled ? old.shieldmul : 0;
break;
}
if (slot.incCost && old.cost) {
this.totalCost -= old.cost * this.moduleCostMultiplier;
}
if (old.power && slot.enabled) {
this.priorityBands[slot.priority][powerUsageType(slot, old)] -= old.power;
powerChange = true;
if (old.dps) {
this.totalDps -= old.dps;
}
if (old.getPowerUsage() > 0 && slot.enabled) {
powerUsedChange = true;
}
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) {
@@ -675,12 +776,6 @@ export default class Ship {
case 'cr':
this.cargoCapacity += n.cargo;
break;
case 'hr':
this.armourAdded += n.armouradd;
break;
case 'sb':
this.shieldMultiplier += slot.enabled ? n.shieldmul : 0;
break;
}
if (slot.incCost && n.cost) {
@@ -688,49 +783,105 @@ export default class Ship {
}
if (n.power && slot.enabled) {
this.priorityBands[slot.priority][powerUsageType(slot, n)] += n.power;
powerChange = true;
if (n.dps) {
this.totalDps += n.dps;
}
powerUsedChange = true;
}
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.armour = this.armourAdded + Math.round(this.baseArmour * this.armourMultiplier);
if (!preventUpdate) {
if (powerChange) {
this.updatePower();
if (powerGeneratedChange) {
this.updatePowerGenerated();
}
if (powerUsedChange) {
this.updatePowerUsed();
}
if (armourChange) {
this.updateArmour();
}
if (shieldChange) {
this.updateShield();
}
this.updateTopSpeed();
this.updateJumpStats();
this.updateShieldStrength();
}
return this;
}
/**
* Update all power calculations
* Update power calculations when amount generated changes
* @return {this} The ship instance (for chaining operations)
*/
updatePower() {
let bands = this.priorityBands;
let prevRetracted = 0, prevDeployed = 0;
updatePowerGenerated() {
this.powerAvailable = this.standard[0].m.getPowerGeneration();
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++) {
let band = bands[i];
prevRetracted = band.retractedSum = prevRetracted + band.retracted;
prevDeployed = band.deployedSum = prevDeployed + band.deployed + band.retracted;
}
this.powerAvailable = this.standard[0].m.pGen;
// Update global stats
this.powerRetracted = prevRetracted;
this.powerDeployed = prevDeployed;
this.priorityBands = bands;
return this;
};
}
/**
* Update top speed and boost
@@ -739,17 +890,52 @@ export default class Ship {
updateTopSpeed() {
let speeds = Calc.speed(this.unladenMass + this.fuelCapacity, this.speed, this.boost, this.standard[1].m, this.pipSpeed);
this.topSpeed = speeds['4 Pips'];
this.topBoost = speeds.boost;
this.topBoost = this.canBoost() ? speeds.boost : 0;
return this;
}
/**
* Update Shield strength
* Update shield
* @return {this} The ship instance (for chaining operations)
*/
updateShieldStrength() {
this.sgSlot = this.findInternalByGroup('sg'); // Find Shield Generator slot Index if any
this.shieldStrength = this.sgSlot && this.sgSlot.enabled ? Calc.shieldStrength(this.hullMass, this.baseShieldStrength, this.sgSlot.m, this.shieldMultiplier) : 0;
updateShield() {
// Base shield from generator
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;
}
@@ -759,7 +945,7 @@ export default class Ship {
*/
updateJumpStats() {
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.fullTankRange = Calc.jumpRange(unladenMass + fuelCapacity, fsd); // Full Tank
this.ladenRange = this.calcLadenRange(); // Includes full tank and caro
@@ -811,16 +997,206 @@ export default class Ship {
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.
* Has logic handling ModuleUtils that you may only have 1 of (Shield Generator or Refinery).
*
* @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
* @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
// 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) {
@@ -843,6 +1219,7 @@ export default class Ship {
case 1: this.serialized.hardpoints = null; break;
case 2: this.serialized.internal = null;
}
this.serialized.modifications = null;
}
return this;
}
@@ -855,10 +1232,8 @@ export default class Ship {
*/
useBulkhead(index, preventUpdate) {
let oldBulkhead = this.bulkheads.m;
this.bulkheads.index = index;
this.bulkheads.m = ModuleUtils.bulkheads(this.id, index);
this.bulkheads.m = this.availCS.getBulkhead(index);
this.bulkheads.discountedCost = this.bulkheads.m.cost * this.moduleCostMultiplier;
this.armourMultiplier = ArmourMultiplier[index];
this.updateStats(this.bulkheads, this.bulkheads.m, oldBulkhead, preventUpdate);
this.serialized.standard = null;
return this;
@@ -880,7 +1255,7 @@ export default class Ship {
/**
* 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)
*/
useLightestStandard(m) {
@@ -889,16 +1264,18 @@ export default class Ship {
let standard = this.standard,
// Find lightest Power Distributor that can still boost;
pd = m.pd ? ModuleUtils.standard(4, m.pd) : this.availCS.lightestPowerDist(this.boostEnergy),
fsd = m.fsd || standard[2].maxClass + 'A',
ls = m.ls || standard[3].maxClass + 'D',
s = m.s || standard[5].maxClass + 'D',
fsd = ModuleUtils.standard(2, m.fsd || standard[2].maxClass + 'A'),
ls = ModuleUtils.standard(3, m.ls || standard[3].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;
this.useBulkhead(0)
.use(standard[2], ModuleUtils.standard(2, fsd)) // FSD
.use(standard[3], ModuleUtils.standard(3, ls)) // Life Support
.use(standard[5], ModuleUtils.standard(5, s)) // Sensors
.use(standard[4], pd); // Power Distributor
.use(standard[2], fsd) // FSD
.use(standard[3], ls) // Life Support
.use(standard[5], s) // Sensors
.use(standard[4], pd) // Power Distributor
.use(standard[6], ft); // Fuel Tank
// Thrusters and Powerplant must be determined after all other ModuleUtils are mounted
// Loop at least once to determine absolute lightest PD and TH
@@ -906,14 +1283,14 @@ export default class Ship {
updated = false;
// 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);
if (th !== standard[1].m) {
if (!isEqual.isEqual(th, standard[1].m)) {
this.use(standard[1], th);
updated = true;
}
// 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);
if (pp !== standard[0].m) {
if (!isEqual.isEqual(pp, standard[0].m)) {
this.use(standard[0], pp);
updated = true;
}
@@ -964,5 +1341,4 @@ export default class Ship {
}
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 cn from 'classnames';
import { isShieldGenerator } from '../shipyard/ModuleUtils';
import Module from '../shipyard/Module';
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
@@ -76,7 +97,9 @@ const PROP_BLACKLIST = {
eddbID: 1,
edID: 1,
id: 1,
index: 1,
'class': 1,
rating: 1,
maxfuel: 1,
fuelmul: 1,
fuelpower: 1,
@@ -95,30 +118,30 @@ const PROP_BLACKLIST = {
ssdam: 1,
mjdps: 1,
mjeps: 1,
M: 1,
P: 1,
mass: 1,
cost: 1,
recover: 1,
weaponcapacity: 1,
weaponrecharge: 1,
enginecapacity: 1,
enginerecharge: 1,
systemcapacity: 1,
systemrecharge: 1
wepcap: 1,
weprate: 1,
engcap: 1,
engrate: 1,
syscap: 1,
sysrate: 1,
breachdps: 1,
breachmin: 1,
breachmax: 1,
integrity: 1
};
const TERM_LOOKUP = {
pGen: 'power',
pgen: 'power',
armouradd: 'armour',
shieldmul: 'multiplier',
rof: 'ROF',
dps: 'DPS'
};
const FORMAT_LOOKUP = {
time: 'time',
shieldmul: 'rPct'
time: 'time'
};
const UNIT_LOOKUP = {
@@ -129,7 +152,7 @@ const UNIT_LOOKUP = {
recharge: 'MJ',
rangeLS: 'Ls',
power: 'MJ',
pGen: 'MJ',
pgen: 'MJ',
rof: 'ps'
};
@@ -158,10 +181,10 @@ function diffClass(a, b, negative) {
*/
function diff(format, mVal, mmVal) {
if (mVal == Infinity) {
return <Infinite/>;
return '∞';
} else {
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)} (${diff > 0 ? '+' : ''}${format(diff)})`;
@@ -180,80 +203,96 @@ function diff(format, mVal, mmVal) {
* @return {React.Component} Component to be rendered
*/
export function diffDetails(language, m, mm) {
mm = mm || {};
let { formats, translate, units } = language;
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 mmMass = mm.mass || 0;
let massDiff = mMass - mmMass;
let capDiff = (m.fuel || m.cargo || 0) - (mm.fuel || mm.cargo || 0);
let mmMass = mm ? mm.getMass() : 0;
if (mMass != mmMass) propDiffs.push(<div key='mass'>{translate('mass')}: <span className={diffClass(mMass, mmMass, true)}>{diff(formats.round, mMass, mmMass)}{units.T}</span></div>);
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 mmAffectsShield = isShieldGenerator(mm.grp) || 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>);
}
}
let mmAffectsShield = isShieldGenerator(mm ? mm.grp : null) || mm && mm.grp == 'sb';
if (mAffectsShield || mmAffectsShield) {
let shield = this.calcShieldStrengthWith(); // Get shield strength regardless of slot active / inactive
let newShield = 0;
if (mAffectsShield) {
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 {
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');
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') {
propDiffs.push(<div key='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>);
propDiffs.push(<div key='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>);
propDiffs.push(<div key='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>);
}
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) {
let fsd = m.grp == 'fsd' ? m : null;
let maxRange = this.calcUnladenRange(massDiff, m.fuel, fsd);
let ladenRange = this.calcLadenRange(massDiff + capDiff, m.fuel, fsd);
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) {
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>
<html {%= o.htmlWebpackPlugin.options.appCache ? 'manifest=/' + o.htmlWebpackPlugin.options.appCache : '' %} >
<head>
<title>Coriolis</title>
<title>Coriolis EDCD Edition</title>
<meta charset="UTF-8">
<link rel="stylesheet" href="{%= o.htmlWebpackPlugin.files.css[0] %}">
<!-- Standard headers -->
@@ -25,12 +25,11 @@
</head>
<body style="background-color:#000;">
<section id="coriolis"></section>
<footer>
<div class="right cap">
<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>
</div>
</footer>
<script>
window.CORIOLIS_GAPI_KEY = '{%= o.htmlWebpackPlugin.options.gapiKey %}';
window.CORIOLIS_VERSION = '{%= o.htmlWebpackPlugin.options.version %}';
window.CORIOLIS_DATE = '{%= new Date().toISOString().slice(0, 10) %}';
</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>
@@ -41,7 +40,6 @@
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', '{%= o.htmlWebpackPlugin.options.uaTracking %}', 'auto');
{% } %}
window.CORIOLIS_GAPI_KEY = '{%= o.htmlWebpackPlugin.options.gapiKey %}';
</script>
</body>
</html>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -33,6 +33,10 @@ thead {
&.lft {
border-left: 1px solid @primary-bg;
}
&.rgt {
border-right: 1px solid @primary-bg;
}
}
}
@@ -43,23 +47,26 @@ tbody tr {
text-align: right;
}
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;
}
.no-touch &.highlight:hover, .no-touch &.highlighted {
background-color: @warning-bg;
}
}
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 {
text-align: left;
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 },
"rating": { "$ref": "#/definitions/standardRatings" },
"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": {

View File

@@ -1,16 +1,28 @@
var path = require('path');
var exec = require('child_process').exec;
var webpack = require('webpack');
var pkgJson = require('./package');
var HtmlWebpackPlugin = require("html-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 = {
devtool: 'eval',
devServer: {
headers: { "Access-Control-Allow-Origin": "*" }
},
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']
},
resolve: {
@@ -23,6 +35,7 @@ module.exports = {
publicPath: '/'
},
plugins: [
new CopyDirPlugin(path.join(__dirname, 'src/.htaccess'), ''),
new webpack.optimize.CommonsChunkPlugin('lib', 'lib.js'),
new HtmlWebpackPlugin({
inject: false,

View File

@@ -74,6 +74,8 @@ module.exports = {
}),
new CopyDirPlugin(path.join(__dirname, 'src/schemas'), 'schemas'),
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({
network: ['*'],
settings: ['prefer-online'],