Compare commits

..

251 Commits

Author SHA1 Message Date
Cmdr McDonald
05e06f30f5 Merge branch 'release/2.2.7' 2017-01-11 21:59:01 +00:00
Cmdr McDonald
fb5ba6a0b2 Update damage dealt to use actual resistances 2017-01-11 21:57:49 +00:00
Cmdr McDonald
80656a7a78 Fix resistance diminishing return calculations 2017-01-11 21:33:31 +00:00
Cmdr McDonald
ce980cf091 Merge branch 'release/2.2.6' into develop 2017-01-10 19:34:18 +00:00
Cmdr McDonald
a4656e223a Merge branch 'release/2.2.6' 2017-01-10 19:34:10 +00:00
Cmdr McDonald
66d4b5ac4c Fix tests due to new module values 2017-01-10 19:30:43 +00:00
Cmdr McDonald
6961469ae5 Merge branch 'feature/changes' into develop 2017-01-10 19:15:56 +00:00
Cmdr McDonald
06f4abdf8b Import builds with military slots 2017-01-10 19:12:19 +00:00
Cmdr McDonald
7855d0e171 Reinstate jump range graph 2017-01-02 12:06:44 +00:00
Cmdr McDonald
40f213c883 Do not lose ship selector selection on narrow screens 2017-01-02 12:05:55 +00:00
Cmdr McDonald
be1bfeb6f3 Ensure that information is not lost on narrow screens. Fix for #48 2017-01-02 12:05:22 +00:00
Cmdr McDonald
b40a2e96e0 Fix for change to military slots 2016-12-24 23:49:33 +00:00
Cmdr McDonald
5f036c586c Use own URL shortener 2016-12-24 21:57:11 +00:00
Cmdr McDonald
091789c819 Revert overloading of 'reload' translation key 2016-12-24 20:57:51 +00:00
Cmdr McDonald
0ce8bfac79 Fixes for #46 2016-12-24 20:56:37 +00:00
Cmdr McDonald
e53ffd0273 Update shield recovery/regeneration calculations 2016-12-20 13:58:00 +00:00
Cmdr McDonald
bb7db144d6 Allow collapse/expand of damage sections 2016-12-19 21:50:53 +00:00
Cmdr McDonald
2e42a328e0 Add base resistances to defence summary tooltip 2016-12-19 17:56:54 +00:00
Cmdr McDonald
f82122f29f Add module copy functionality - drag module whilst holding 'alt' to copy 2016-12-18 21:37:21 +00:00
Cmdr McDonald
5bf907809d Make weapons real modules to benefits from standard DPS etc. calculations 2016-12-18 21:26:30 +00:00
Cmdr McDonald
51d7b6c9aa Add 'Piercing' information to hardpoints; add 'Hardness' information to ship summary 2016-12-18 09:36:33 +00:00
Cmdr McDonald
b8cff0c2fc Add 'Damage received' section 2016-12-17 10:46:52 +00:00
Cmdr McDonald
6ac69a6388 Add translation 2016-12-16 21:16:17 +00:00
Cmdr McDonald
32282141cf Use ship rather than ship ID 2016-12-16 21:09:11 +00:00
Cmdr McDonald
059c2badf4 Lint 2016-12-16 20:38:58 +00:00
Cmdr McDonald
fb090618da Add 'Damage dealt' section 2016-12-16 20:37:06 +00:00
Cmdr McDonald
9ed0e30538 Add hardness to shipyard 2016-12-16 16:25:57 +00:00
Cmdr McDonald
af82b8ca1e Fix up tests 2016-12-15 16:46:38 +00:00
Cmdr McDonald
6e18793d82 Lint fix 2016-12-14 22:41:10 +00:00
Cmdr McDonald
22e74164c5 Alternate (embedded) code versioning scheme; start to fix up tests 2016-12-14 22:38:21 +00:00
Cmdr McDonald
93ba1bf67a Do not include disabled shield boosters in calculations 2016-12-14 17:39:01 +00:00
Cmdr McDonald
46ed9003dd Tidy-ups 2016-12-14 13:31:20 +00:00
Cmdr McDonald
5603315bf0 Version URLs to handle changes to ship specifications over time 2016-12-14 13:23:54 +00:00
Cmdr McDonald
5bbc6be3d8 Obey restricted slot rules when adding all for internal slots 2016-12-14 13:23:19 +00:00
Cmdr McDonald
203e9c7b46 Fix for importing definitions with missing slots 2016-12-14 08:49:23 +00:00
Cmdr McDonald
2a6850ded0 Set initial shield boost correctly 2016-12-13 22:08:49 +00:00
Cmdr McDonald
041f873f97 Updates 2016-12-13 18:56:59 +00:00
Cmdr McDonald
b944035541 Use separate speed/rotation/acceleration multipliers for thrusters if available 2016-12-12 13:37:36 +00:00
Cmdr McDonald
7c6a4fc5f8 Do not rely on coriolis-data' internal ordering of modules for display purposes 2016-12-12 10:38:50 +00:00
Cmdr McDonald
5426b55637 Ensure module ordering is consistent 2016-12-12 10:28:54 +00:00
Cmdr McDonald
a6a10df39c Add movement summary; add standard internal class sizes to shipyard page; fix issue when importing Viper Mk IV 2016-12-11 21:52:49 +00:00
Cmdr McDonald
0dc58bad7e Beta version 2016-12-03 15:52:17 +00:00
Cmdr McDonald
794faacbd4 Merge branch 'release/2.2.5' into develop 2016-12-03 15:48:50 +00:00
Cmdr McDonald
0a37b36ec2 Merge branch 'release/2.2.5' 2016-12-03 15:48:45 +00:00
Cmdr McDonald
85e6796e88 Bump version 2016-12-03 15:48:39 +00:00
Cmdr McDonald
fa1ef47b71 Add note to disable ghostery in error situations 2016-12-01 13:26:33 +00:00
Cmdr McDonald
f31e3c09f4 Merge branch 'feature/rebalance' into develop 2016-12-01 10:28:21 +00:00
Cmdr McDonald
e6ab536601 Merge branch 'feature/rebalance' of https://github.com/EDCD/coriolis into feature/rebalance 2016-12-01 10:28:08 +00:00
Cmdr McDonald
5bced9fe56 Bump version number 2016-11-30 15:09:25 +00:00
Cmdr McDonald
67742060d3 Merge branch 'feature/burst' into develop 2016-11-30 15:09:04 +00:00
Cmdr McDonald
ca2136544c Handle unmodifiable values 2016-11-30 15:08:32 +00:00
Cmdr McDonald
ee19e9af50 Lint 2016-11-30 14:56:33 +00:00
Cmdr McDonald
f457fd0bff Fixes for burst calculations 2016-11-30 14:51:19 +00:00
Cmdr McDonald
c1ce07e039 Calculate rate of fire for multi-burst weapons 2016-11-30 12:19:40 +00:00
Cmdr McDonald
c8d1536f77 Bump version number 2016-11-29 13:53:23 +00:00
Cmdr McDonald
231ad4af59 Merge branch 'release/2.2.4' into develop 2016-11-28 16:54:35 +00:00
Cmdr McDonald
d5f61d7ae8 Merge branch 'release/2.2.4' 2016-11-28 16:54:32 +00:00
Cmdr McDonald
8a5d4a36bf Bumped revision 2016-11-28 16:51:24 +00:00
Cmdr McDonald
2c9237626d Show specials; handle import of specials 2016-11-27 19:43:43 +00:00
Cmdr McDonald
37f889e317 Merge branch 'feature/incendiary' into develop 2016-11-26 23:24:37 +00:00
Cmdr McDonald
f86ba48295 Fix tests 2016-11-26 18:58:29 +00:00
Cmdr McDonald
aac35633a3 Merge branch 'feature/incendiary' of https://github.com/EDCD/coriolis into feature/incendiary 2016-11-26 18:56:57 +00:00
Cmdr McDonald
e73e0a305d Use new-style modification data 2016-11-26 18:54:22 +00:00
Cmdr McDonald
4b2b0efe37 Allow non-numeric modifiers 2016-11-26 18:54:22 +00:00
Cmdr McDonald
8fe20f6f65 Checkpoint - handle non-numeric modifiers 2016-11-26 18:54:22 +00:00
Cmdr McDonald
11af7f567a Move to method for damage type to allow for modifications 2016-11-26 18:54:22 +00:00
Cmdr McDonald
3b8444482f Add URL shortlink for outfitting page 2016-11-26 18:53:47 +00:00
Cmdr McDonald
c09e1b1b3e Merge branch 'feature/incendiary' of https://github.com/EDCD/coriolis into feature/incendiary 2016-11-26 13:16:38 +00:00
Cmdr McDonald
5770cf8d39 Use new-style modification data 2016-11-26 13:15:43 +00:00
Cmdr McDonald
6da09f2e5d Allow non-numeric modifiers 2016-11-26 13:15:43 +00:00
Cmdr McDonald
294fadf7cd Checkpoint - handle non-numeric modifiers 2016-11-26 13:15:43 +00:00
Cmdr McDonald
2a97678574 Move to method for damage type to allow for modifications 2016-11-26 13:15:43 +00:00
Cmdr McDonald
76b3bd34f5 Use new-style modification data 2016-11-26 12:54:22 +00:00
Cmdr McDonald
02bfecb92d Allow non-numeric modifiers 2016-11-24 22:23:14 +00:00
Cmdr McDonald
719759ad56 Merge branch 'release/2.2.3' into develop 2016-11-24 14:37:43 +00:00
Cmdr McDonald
fd446b29ba Merge branch 'release/2.2.3' 2016-11-24 14:37:39 +00:00
Cmdr McDonald
e5552d3e10 Updates ready for release 2016-11-24 13:49:37 +00:00
Cmdr McDonald
50946eeeb8 Fix misnamed diamondbacks - issue #36 2016-11-24 13:19:14 +00:00
Cmdr McDonald
faab41117c Checkpoint - handle non-numeric modifiers 2016-11-24 12:50:33 +00:00
Cmdr McDonald
0ab59c1f9a Handle import of restricted slots - fix for #35 2016-11-24 11:10:41 +00:00
Cmdr McDonald
1067dceaa3 Move to method for damage type to allow for modifications 2016-11-23 13:02:23 +00:00
Cmdr McDonald
9042de422a patch 2016-11-23 01:05:13 +00:00
Cmdr McDonald
f0547feb93 Merge branch 'feature/blueprints' into develop 2016-11-23 00:56:19 +00:00
Cmdr McDonald
f863daa347 Fix hull boost calculation. Partial fix for #29 2016-11-23 00:54:42 +00:00
Cmdr McDonald
fdb202e7d6 Add blueprints 2016-11-22 15:52:31 +00:00
Cmdr McDonald
c6bde19052 Merge branch 'release/2.2.2' into develop 2016-11-21 16:44:19 +00:00
Cmdr McDonald
f6aff3d3bb Merge branch 'release/2.2.2' 2016-11-21 16:44:15 +00:00
Cmdr McDonald
2f4a2ebe03 Bumped release 2016-11-21 16:42:17 +00:00
Cmdr McDonald
ca20e94b93 Merge branch 'feature/res' into develop 2016-11-21 16:39:25 +00:00
Cmdr McDonald
40a87dceeb Update tooltip to match reality 2016-11-21 11:34:45 +00:00
Cmdr McDonald
95b7d60be4 Fix URL strings for query parameter method 2016-11-21 11:33:34 +00:00
Cmdr McDonald
24abd6583f Remove requirement for double encoding 2016-11-21 10:14:16 +00:00
Cmdr McDonald
8857aba53f Use query parameters rather than long path 2016-11-21 10:06:14 +00:00
Cmdr McDonald
e4830811b0 Fix up jitter 2016-11-17 14:23:40 +00:00
Cmdr McDonald
143380ac58 Tidy-ups 2016-11-16 20:58:35 +00:00
Cmdr McDonald
a2f6fb6ac0 Added help tooltip for modifications 2016-11-16 20:49:22 +00:00
Cmdr McDonald
0d3c128059 Lints and tests 2016-11-15 13:38:02 +00:00
Cmdr McDonald
930a555425 Update costs for reload 2016-11-15 13:33:59 +00:00
Cmdr McDonald
d6f213fbe7 Remove logging 2016-11-14 23:02:56 +00:00
Cmdr McDonald
0571e8e099 Added jitter for hardpoints 2016-11-14 22:58:09 +00:00
Cmdr McDonald
54c61ecb7d Fix base armour 2016-11-14 22:18:16 +00:00
Cmdr McDonald
030867c4f8 Update test results 2016-11-14 16:47:28 +00:00
Cmdr McDonald
33a7c71fec Tweaks for restoring data from previous builds 2016-11-14 16:39:58 +00:00
Cmdr McDonald
4e0f682ad6 Use forrked browserify-zlib as it has bug fixes 2016-11-14 12:17:30 +00:00
Cmdr McDonald
4486aa2e2b Handle saved builds and old URLs 2016-11-14 11:57:39 +00:00
Cmdr McDonald
0c94c81746 Remove explicit bulkheads name from slot 2016-11-13 21:02:39 +00:00
Cmdr McDonald
5b037e3a00 Additional info if import fails 2016-11-13 17:04:57 +00:00
Cmdr McDonald
42a2b907ce Linting 2016-11-13 16:51:07 +00:00
Cmdr McDonald
a65dae1631 Various fixes; allow direct import from URL 2016-11-13 16:42:59 +00:00
Cmdr McDonald
7d4c534956 Re-enable shields for comparison 2016-11-13 13:44:00 +00:00
Cmdr McDonald
8397d3505b Rework per-module resistance calculations 2016-11-13 13:13:57 +00:00
Cmdr McDonald
9556f28ba4 Add ability to import directly from companion API output 2016-11-12 12:02:52 +00:00
Cmdr McDonald
f489257f86 Update shield cell numbers when appropriate 2016-11-11 12:30:32 +00:00
Cmdr McDonald
c96693c439 Tidy up descriptions; allow total cost to be non-integer 2016-11-11 12:19:08 +00:00
Cmdr McDonald
3d4f6d7861 Slightly friendlier modifications 2016-11-11 12:04:30 +00:00
Cmdr McDonald
606eabfec7 Fix issues with losing precision due to using decimal modification values. Validate modification information 2016-11-11 11:15:56 +00:00
Cmdr McDonald
782603727a Re-add recovery/recharge times and tweak styling 2016-11-11 00:39:14 +00:00
Cmdr McDonald
ad570534a0 Update test outputs 2016-11-11 00:21:53 +00:00
Cmdr McDonald
87e903e473 Add 'Offence summary' and 'Defence summary' components 2016-11-11 00:15:49 +00:00
Cmdr McDonald
cf6d32ea04 Enable boost display even if power distributor is disabled 2016-11-10 22:15:42 +00:00
Cmdr McDonald
5b81a0b25f Fix modification value for additive modifications 2016-11-10 15:18:05 +00:00
Cmdr McDonald
0688faac93 Added offence and defence summary 2016-11-10 00:13:56 +00:00
Cmdr McDonald
3719bb9696 Merge branch 'feature/fixes' into develop 2016-11-09 19:05:43 +00:00
Cmdr McDonald
21582d1598 Handle potentially null modifications object 2016-11-09 18:57:45 +00:00
Cmdr McDonald
9a9607fcfb Merge branch 'gienkov-pl-language' into feature/fixes 2016-11-09 16:41:19 +00:00
Cmdr McDonald
8602c5667b Merge branch 'pl-language' of https://github.com/gienkov/coriolis into gienkov-pl-language 2016-11-09 16:40:43 +00:00
Cmdr McDonald
f13a987388 Lint tidy-ups 2016-11-09 16:40:22 +00:00
Cmdr McDonald
38eaebefc0 Fix import and export of ships with modifications, bump schema version to 4 2016-11-09 16:32:11 +00:00
Cmdr McDonald
c1bc514e6b Remove swapfile 2016-11-08 09:29:32 +00:00
Cmdr McDonald
42e98fd015 Fix tooltip DPS 2016-11-08 09:25:01 +00:00
Cmdr McDonald
5f0b851de7 Take modifications in to account when deciding whether to issue a
warning on a standard module.
Fix for #16.
2016-11-08 09:05:54 +00:00
Cmdr McDonald
616ed0bf10 Show modification icon for modified modules
Fix for #14
2016-11-07 17:11:39 +00:00
Cmdr McDonald
108ab3b1ee Update DPS/HPS/EPS in real-time as modifiers change 2016-11-07 10:15:20 +00:00
Cmdr McDonald
04caef9613 Merge branch 'release/2.2.1' into develop 2016-11-05 14:39:28 +00:00
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
Grzegorz
5634fbd568 pl language 2016-10-10 17:47:37 +02:00
Grzegorz
7d99d59790 pl option in menu 2016-10-10 17:39:17 +02: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
93 changed files with 10035 additions and 1397 deletions

3
.gitignore vendored
View File

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

96
ChangeLog.md Normal file
View File

@@ -0,0 +1,96 @@
#2.2.7
* Fix resistance diminishing return calculations
* Do not allow -100% to be entered as a modification value
#2.2.6
* Add pitch/roll/yaw information
* Use combination of pitch, roll and yaw to provide a more useful agility metric
* Add movement summary to outfitting page
* Add standard internal class sizes to shipyard page
* Fix issue when importing Viper Mk IV
* Ensure ordering of all types of modules (standard, internal, utilities) is consistent
* Add rebuilds per bay information for fighter hangars
* Add ability to show military compartments
* Show module reinforcement package results in defence summary
* Use separate speed/rotation/acceleration multipliers for thrusters if available
* Obey restricted slot rules when adding all for internal slots
* Version URLs to handle changes to ship specifications over time
* Do not include disabled shield boosters in calculations
* Add 'Damage dealt' section
* Add 'Damage received' section
* Add 'Piercing' information to hardpoints
* Add 'Hardness' information to ship summary
* Add module copy functionality - drag module whilst holding 'alt' to copy
* Add base resistances to defence summary tooltip
* Update shield recovery/regeneration calculations
* Pin menu to top of page
* Switch to custom shortlink method to avoid google length limitations
* Ensure that information is not lost on narrow screens
* Do not lose ship selector selection on narrow screens
* Reinstate jump range graph
* Use coriolis-data 2.2.6:
* Update weapons with changed values for 2.2.03
* Add individual pitch/roll/yaw statistics for each ship
* Remove old and meaningless agility stat
* Use sane order for multi-module JSON - coriolis can re-order as it sees fit when displaying modules
* Fix cost of fighter hangars
* Update Powerplay weapons with current statistics
* Add separate min/opt/max multipliers for enhanced thrusters for speed, acceleration and rotation
* Add module reinforcement packages
* Add military compartments
* Fix missing damage value for 2B dumbfires
* Update shield recharge rates
* Reduce hull mass of Viper to 50T
* Fix incorrect optimal mass value for 8A thrusters
* Add power draw for detailed surface scanner
#2.2.5
* Calculate rate of fire for multi-burst weapons
* Add note to disable ghostery in error situation
* Use coriolis-data 2.2.5:
* Fix incorrect ID for emissive munitions special
* Fix rate of fire for burst lasers
* Add fragment cannon modifications
* Fix internal name of dazzle shell
#2.2.4
* Add shortlink for outfitting page
* Use coriolis-data 2.2.4:
* Fix incorrect ID for class 5 luxury passenger cabin
* Add damage type modifier
* Change modifications from simple strings to objects, to allow more data-driven behaviour
* Add special effects
* Modification tooltip now shows special effect
#2.2.3
* Fix hull boost calculation - now shows correct % modifier and total armour
* Fix import of DiamondBack - can now be imported
* Fix import of Beluga - can now be imported
* Use coriolis-data 2.2.3:
* Fix mismatch between class 5 and class 7 fighter hangars - now shows correct module
* Add details for concordant sequence special effect - now shows correct damage
* Fix details for thermal shock special effect - now shows correct damage
* Add engineer blueprints
* Modification tooltip now shows name and grade of modifications for imported builds
* Retain import URL unless user changes the build - allows future updates of Coriolis to take advantage of additional build information
#2.2.2
* Update DPS/HPS/EPS in real-time as modifiers change
* Use coriolis-data 2.2.2:
* Add distributor draw modifier to shield generators
* Remove modifiers for sensors
* Add initial loadout passenger cabins for Beluga
* Add initial loadout passenger cabins for Orca
* Update costs and initial loadouts for Keelback and Type-7
* Add resistances for hull reinforcement packages
* Added modifier actions to create modifications from raw data
* Show modification icon for modified modules
* Take modifications in to account when deciding whether to issue a warning on a standard module
* Fix hardpoint comparison DPS number when selecting an alternate module
* Ensure that retrofit tab only shows changed modules
* Fix import and export of ships with modifications, bump schema version to 4
* Enable boost display even if power distributor is disabled
* Calculate breakdown of ship offensive and defensive stats
* Add 'Offence summary' and 'Defence summary' components
* Add ability to import from companion API output through import feature
* Add ability to import from companion API output through URL

View File

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

View File

@@ -0,0 +1,30 @@
{
"adder": {
"t3": {"speed": 205, "boost": 298, "pitch": 35.37, "roll": 93.09, "yaw": 13.03},
"t2": {"speed": 209, "boost": 304, "pitch": 36.06, "roll": 94.90, "yaw": 13.29},
"t1": {"speed": 213, "boost": 310, "pitch": 36.80, "roll": 96.84, "yaw": 13.56},
"t0": {"speed": 218, "boost": 317, "pitch": 37.70, "roll": 99.20, "yaw": 13.89},
"t9": {"speed": 220, "boost": 321, "pitch": 38.08, "roll": 100.21, "yaw": 14.03},
"t8": {"speed": 225, "boost": 327, "pitch": 38.86, "roll": 102.26, "yaw": 14.32},
"t7": {"speed": 230, "boost": 334, "pitch": 39.69, "roll": 104.44, "yaw": 14.62},
"t6": {"speed": 234, "boost": 340, "pitch": 40.41, "roll": 106.34, "yaw": 14.89},
"t5": {"speed": 242, "boost": 351, "pitch": 41.71, "roll": 109.78, "yaw": 15.37}
},
"eagle": {
"t2": {"speed": 223, "boost": 325, "pitch": 46.45, "roll": 111.48, "yaw": 16.72},
"t1": {"speed": 229, "boost": 334, "pitch": 47.69, "roll": 114.46, "yaw": 17.17},
"t0": {"speed": 235, "boost": 343, "pitch": 49.00, "roll": 117.60, "yaw": 17.64},
"t9": {"speed": 239, "boost": 349, "pitch": 49.80, "roll": 119.53, "yaw": 17.93},
"t8": {"speed": 243, "boost": 355, "pitch": 50.70, "roll": 121.69, "yaw": 18.25},
"t7": {"speed": 248, "boost": 361, "pitch": 51.62, "roll": 123.89, "yaw": 18.58},
"t6": {"speed": 252, "boost": 367, "pitch": 52.46, "roll": 125.91, "yaw": 18.89},
"t5": {"speed": 259, "boost": 378, "pitch": 53.99, "roll": 129.56, "yaw": 19.43}
},
"hauler": {
"t4": {"speed": 203, "boost": 305, "pitch": 36.61, "roll": 101.71, "yaw": 14.24},
"t3": {"speed": 209, "boost": 314, "pitch": 37.63, "roll": 104.54, "yaw": 14.64},
"t2": {"speed": 216, "boost": 324, "pitch": 38.89, "roll": 108.03, "yaw": 15.12},
"t1": {"speed": 222, "boost": 333, "pitch": 39.97, "roll": 111.02, "yaw": 15.54},
"t0": {"speed": 232, "boost": 348, "pitch": 41.76, "roll": 116.00, "yaw": 16.24}
}
}

View File

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

View File

@@ -0,0 +1,322 @@
{
"$schema": "http://cdn.coriolis.io/schemas/ship-loadout/4.json#",
"name": "Test My Ship",
"ship": "Anaconda",
"references": [
{
"name": "Coriolis.io",
"url": "http://localhost:3300/outfit/anaconda/48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA.H4sIAAAAAAAAA2MUe8HMwPD-PwDDhxeuCAAAAA==?bn=Test%20My%20Ship",
"old-code": "48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA.H4sIAAAAAAAAA2MUe8HMwPD-PwDDhxeuCAAAAA==",
"code": "4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA.H4sIAAAAAAAAA2MUe8HMwPD-PwDDhxeuCAAAAA==",
"shipId": "anaconda"
}
],
"components": {
"standard": {
"bulkheads": "Reactive Surface Composite",
"cargoHatch": {
"enabled": false,
"priority": 5
},
"powerPlant": {
"class": 8,
"rating": "A",
"enabled": true,
"priority": 1,
"modifications": {
"pgen": 1000
}
},
"thrusters": {
"class": 6,
"rating": "A",
"enabled": true,
"priority": 1
},
"frameShiftDrive": {
"class": 6,
"rating": "A",
"enabled": true,
"priority": 3
},
"lifeSupport": {
"class": 5,
"rating": "A",
"enabled": true,
"priority": 1
},
"powerDistributor": {
"class": 8,
"rating": "A",
"enabled": true,
"priority": 1
},
"sensors": {
"class": 8,
"rating": "A",
"enabled": true,
"priority": 1
},
"fuelTank": {
"class": 5,
"rating": "C",
"enabled": true,
"priority": 1
}
},
"hardpoints": [
{
"class": 4,
"rating": "A",
"enabled": true,
"priority": 2,
"group": "Plasma Accelerator",
"mount": "Fixed"
},
{
"class": 3,
"rating": "D",
"enabled": true,
"priority": 2,
"group": "Beam Laser",
"mount": "Turret"
},
{
"class": 3,
"rating": "D",
"enabled": true,
"priority": 2,
"group": "Beam Laser",
"mount": "Turret"
},
{
"class": 3,
"rating": "D",
"enabled": true,
"priority": 2,
"group": "Beam Laser",
"mount": "Turret"
},
{
"class": 2,
"rating": "E",
"enabled": true,
"priority": 2,
"group": "Cannon",
"mount": "Turret"
},
{
"class": 2,
"rating": "E",
"enabled": true,
"priority": 2,
"group": "Cannon",
"mount": "Turret"
},
{
"class": 1,
"rating": "F",
"enabled": true,
"priority": 2,
"group": "Beam Laser",
"mount": "Turret"
},
{
"class": 1,
"rating": "F",
"enabled": true,
"priority": 2,
"group": "Beam Laser",
"mount": "Turret"
}
],
"utility": [
{
"class": 0,
"rating": "A",
"enabled": true,
"priority": 1,
"group": "Shield Booster"
},
{
"class": 0,
"rating": "A",
"enabled": true,
"priority": 1,
"group": "Shield Booster"
},
null,
{
"class": 0,
"rating": "C",
"enabled": true,
"priority": 2,
"group": "Kill Warrant Scanner"
},
{
"class": 0,
"rating": "C",
"enabled": true,
"priority": 2,
"group": "Cargo Scanner"
},
{
"class": 0,
"rating": "F",
"enabled": false,
"priority": 1,
"group": "Electronic Countermeasure",
"name": "Electronic Countermeasure"
},
{
"class": 0,
"rating": "I",
"enabled": true,
"priority": 1,
"group": "Chaff Launcher",
"name": "Chaff Launcher"
},
{
"class": 0,
"rating": "I",
"enabled": true,
"priority": 2,
"group": "Point Defence",
"name": "Point Defence"
}
],
"internal": [
{
"class": 7,
"rating": "A",
"enabled": true,
"priority": 1,
"group": "Shield Generator"
},
{
"class": 6,
"rating": "A",
"enabled": true,
"priority": 1,
"group": "Shield Cell Bank"
},
{
"class": 6,
"rating": "E",
"enabled": true,
"priority": 1,
"group": "Cargo Rack"
},
{
"class": 5,
"rating": "D",
"enabled": true,
"priority": 1,
"group": "Hull Reinforcement Package"
},
{
"class": 5,
"rating": "E",
"enabled": true,
"priority": 1,
"group": "Cargo Rack"
},
null,
null,
null,
{
"class": 4,
"rating": "E",
"enabled": true,
"priority": 1,
"group": "Cargo Rack"
},
{
"class": 4,
"rating": "E",
"enabled": true,
"priority": 1,
"group": "Cargo Rack"
},
{
"class": 4,
"rating": "A",
"enabled": true,
"priority": 3,
"group": "Fuel Scoop"
},
{
"class": 2,
"rating": "A",
"enabled": true,
"priority": 3,
"group": "Frame Shift Drive Interdictor"
}
]
},
"stats": {
"class": 3,
"fighterHangars": 1,
"hullCost": 141889930,
"speed": 180,
"topSpeed": 186.5,
"boost": 240,
"boostEnergy": 27,
"topBoost": 249.34,
"topPitch": 25.97,
"topRoll": 62.34,
"topYaw": 10.39,
"topSpeed": 187.01,
"totalCost": 882362058,
"totalDpe": 142.68,
"totalDps": 101.13,
"totalEps": 22.71,
"totalHps": 677.29,
"totalExplDpe": 0,
"totalExplDps": 0,
"totalExplSDps": 0,
"totalHps": 33.62,
"totalKinDpe": 116.29,
"totalKinDps": 16.01,
"totalKinSDps": 12.09,
"totalSDps": 89.99,
"totalThermDpe": 20.44,
"totalThermDps": 53.82,
"totalThermSDps": 53.82,
"baseShieldStrength": 350,
"baseArmour": 945,
"hullExplRes": 0.22,
"hullKinRes": 0.27,
"hullMass": 400,
"hullThermRes": -0.36,
"masslock": 23,
"pipSpeed": 0.14,
"pitch": 25,
"moduleCostMultiplier": 1,
"modulearmour": 0,
"moduleprotection": 0,
"fuelCapacity": 32,
"cargoCapacity": 128,
"ladenMass": 1323.2,
"armour": 2227.5,
"baseArmour": 525,
"unladenMass": 1163.2,
"powerAvailable": 39.6,
"powerRetracted": 23.33,
"powerDeployed": 34.13,
"roll": 60,
"unladenRange": 18.74,
"yaw": 10,
"fullTankRange": 18.36,
"hardness": 65,
"ladenRange": 16.59,
"unladenFastestRange": 74.2,
"ladenFastestRange": 66.96,
"maxJumpCount": 4,
"shield": 833,
"shieldCells": 1840,
"shieldExplRes": 0.5,
"shieldKinRes": 0.4,
"shieldThermRes": -0.2
}
}

View File

@@ -0,0 +1,255 @@
{
"$schema": "http://cdn.coriolis.io/schemas/ship-loadout/4.json#",
"name": "Multi-purpose Asp Explorer",
"ship": "Asp Explorer",
"references": [
{
"name": "Coriolis.io",
"url": "https://coriolis.edcd.io/outfit/asp?code=0pftiFflfddsnf5------020202033c044002v62f2i.AwRj4yvI.CwRgDBldHnJA.H4sIAAAAAAAAA2P858DAwPCXEUhwHPvx%2F78YG5AltB7I%2F8%2F0TwImJboDSPJ%2F%2B%2Ff%2Fv%2FKlX%2F%2F%2Fi3AwMTBIfARK%2FGf%2BJwVSxArStVAYqOjvz%2F%2F%2FJVo5GRhE2IBc4SKQSSz%2FDGEmCa398P8%2F%2F2%2BgTf%2F%2FAwDFxwtofAAAAA%3D%3D&bn=Multi-purpose%20Asp%20Explorer",
"code": "0pftiFflfddsnf5------020202033c044002v62f2i.AwRj4yvI.CwRgDBldHnJA.H4sIAAAAAAAAA2P858DAwPCXEUhwHPvx/78YG5AltB7I/8/0TwImJboDSPJ/+/f/v/KlX///i3AwMTBIfARK/Gf+JwVSxArStVAYqOjvz///JVo5GRhE2IBc4SKQSSz/DGEmCa398P8//2+gTf//AwDFxwtofAAAAA==",
"shipId": "asp"
}
],
"components": {
"standard": {
"bulkheads": "Lightweight Alloy",
"cargoHatch": {
"enabled": false,
"priority": 5
},
"powerPlant": {
"class": 5,
"rating": "A",
"enabled": true,
"priority": 2,
"modifications": {
"eff": -1850,
"pgen": 6,
"mass": 431
},
"blueprint": {
"id": 64,
"name": "Low emissions",
"grade": 1
}
},
"thrusters": {
"class": 5,
"rating": "D",
"enabled": true,
"priority": 1,
"modifications": {
"optmul": 440,
"integrity": -266,
"thermload": -1326,
"optmass": 520,
"power": 241
},
"blueprint": {
"id": 24,
"name": "Clean",
"grade": 1
}
},
"frameShiftDrive": {
"class": 5,
"rating": "A",
"enabled": true,
"priority": 1,
"modifications": {
"mass": 5025,
"integrity": -1539,
"power": 2437,
"optmass": 4870,
"maxfuel": 370
},
"blueprint": {
"id": 26,
"name": "Increased range",
"grade": 5
}
},
"lifeSupport": {
"class": 4,
"rating": "A",
"enabled": true,
"priority": 1,
"modifications": {
"mass": -3923,
"integrity": -1797
},
"blueprint": {
"id": 49,
"name": "Lightweight",
"grade": 1
}
},
"powerDistributor": {
"class": 3,
"rating": "D",
"enabled": true,
"priority": 1
},
"sensors": {
"class": 5,
"rating": "D",
"enabled": true,
"priority": 1
},
"fuelTank": {
"class": 5,
"rating": "C",
"enabled": true,
"priority": 1
}
},
"hardpoints": [
null,
null,
null,
null,
null,
null
],
"utility": [
{
"class": 0,
"rating": "I",
"enabled": true,
"priority": 1,
"group": "Heat Sink Launcher",
"name": "Heat Sink Launcher"
},
{
"class": 0,
"rating": "I",
"enabled": true,
"priority": 1,
"group": "Heat Sink Launcher",
"name": "Heat Sink Launcher"
},
{
"class": 0,
"rating": "I",
"enabled": true,
"priority": 1,
"group": "Heat Sink Launcher",
"name": "Heat Sink Launcher"
},
{
"class": 0,
"rating": "I",
"enabled": true,
"priority": 1,
"group": "Point Defence",
"name": "Point Defence"
}
],
"internal": [
{
"class": 6,
"rating": "A",
"enabled": true,
"priority": 1,
"group": "Fuel Scoop"
},
{
"class": 5,
"rating": "E",
"enabled": true,
"priority": 2,
"group": "Cargo Rack"
},
{
"class": 3,
"rating": "A",
"enabled": true,
"priority": 1,
"group": "Shield Generator"
},
{
"class": 3,
"rating": "E",
"enabled": true,
"priority": 2,
"group": "Cargo Rack"
},
{
"class": 2,
"rating": "G",
"enabled": true,
"priority": 1,
"group": "Planetary Vehicle Hangar"
},
{
"class": 1,
"rating": "C",
"enabled": true,
"priority": 2,
"group": "Scanner",
"name": "Advanced Discovery Scanner"
},
{
"class": 1,
"rating": "C",
"enabled": true,
"priority": 2,
"group": "Scanner",
"name": "Detailed Surface Scanner"
}
]
},
"stats": {
"class": 2,
"hullCost": 6135660,
"speed": 250,
"boost": 340,
"boostEnergy": 13,
"agility": 6,
"baseShieldStrength": 140,
"baseArmour": 210,
"hullMass": 280,
"masslock": 11,
"pipSpeed": 0.13,
"moduleCostMultiplier": 1,
"fuelCapacity": 32,
"cargoCapacity": 40,
"ladenMass": 435.26,
"armour": 378,
"shield": 113.43,
"shieldCells": 0,
"totalCost": 48402550,
"unladenMass": 363.26,
"totalDpe": 0,
"totalExplDpe": 0,
"totalKinDpe": 0,
"totalThermDpe": 0,
"totalDps": 0,
"totalExplDps": 0,
"totalKinDps": 0,
"totalThermDps": 0,
"totalSDps": 0,
"totalExplSDps": 0,
"totalKinSDps": 0,
"totalThermSDps": 0,
"totalEps": 1.2,
"totalHps": 1,
"shieldExplRes": 0.5,
"shieldKinRes": 0.6,
"shieldThermRes": 1.2,
"hullExplRes": 1.4,
"hullKinRes": 1.2,
"hullThermRes": 1,
"powerAvailable": 20.41,
"powerRetracted": 11.91,
"powerDeployed": 11.91,
"unladenRange": 50.45,
"fullTankRange": 47.03,
"ladenRange": 42.71,
"unladenFastestRange": 317.24,
"ladenFastestRange": 287.02,
"maxJumpCount": 7,
"topSpeed": 274.01,
"topBoost": 372.65
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,552 @@
{
"cargo": {
"capacity": 32
},
"free": false,
"fuel": {
"main": {
"capacity": 128
},
"reserve": {
"capacity": 0.81
}
},
"id": 31,
"modules": {
"Armour": {
"module": {
"free": false,
"id": 128049346,
"name": "BelugaLiner_Armour_Grade1",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 0
}
},
"Bobble01": [],
"Bobble02": [],
"Bobble03": [],
"Bobble04": [],
"Bobble05": [],
"Bobble06": [],
"Bobble07": [],
"Bobble08": [],
"Bobble09": [],
"Bobble10": [],
"Decal1": {
"module": {
"free": false,
"id": 128667757,
"name": "Decal_Explorer_Ranger",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 0
}
},
"Decal2": {
"module": {
"free": false,
"id": 128667742,
"name": "Decal_Combat_Deadly",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 0
}
},
"Decal3": {
"module": {
"free": false,
"id": 128667750,
"name": "Decal_Trade_Tycoon",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 0
}
},
"EngineColour": [],
"FrameShiftDrive": {
"module": {
"free": false,
"id": 128064132,
"modifiers": {
"engineerID": 300100,
"id": 175,
"modifiers": [
{
"name": "mod_mass",
"type": 1,
"value": 0.4457540512085
},
{
"name": "mod_health",
"type": 1,
"value": -0.24584779143333
},
{
"name": "mod_passive_power",
"type": 1,
"value": 0.24457727372646
},
{
"name": "mod_fsd_optimised_mass",
"type": 1,
"value": 0.49257898330688
},
{
"name": "mod_fsd_max_fuel_per_jump",
"type": 2,
"value": 0.028505677357316
},
{
"name": "mod_fsd_heat_rate",
"type": 2,
"value": -0.079360365867615
}
],
"moduleTags": [
16
],
"recipeID": 128673694,
"slotIndex": 53
},
"name": "Int_Hyperdrive_Size7_Class5",
"on": true,
"priority": 0,
"recipeLevel": 5,
"recipeName": "FSD_LongRange",
"recipeValue": 0,
"unloaned": 0,
"value": 46160201
}
},
"FuelTank": {
"module": {
"free": false,
"id": 128064352,
"name": "Int_FuelTank_Size7_Class3",
"on": true,
"priority": 1,
"unloaned": 1602822,
"value": 1602822
}
},
"LifeSupport": {
"module": {
"free": false,
"id": 128064174,
"name": "Int_LifeSupport_Size8_Class2",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 1569565
}
},
"MainEngines": {
"module": {
"free": false,
"id": 128064094,
"modifiers": {
"engineerID": 300100,
"id": 253,
"modifiers": [
{
"name": "mod_engine_mass_curve_multiplier",
"type": 1,
"value": 0.098235413432121
},
{
"name": "mod_engine_heat",
"type": 1,
"value": 0.18069696426392
},
{
"name": "mod_passive_power",
"type": 1,
"value": 0.033788848668337
},
{
"name": "mod_health",
"type": 1,
"value": -0.056404989212751
},
{
"name": "mod_engine_mass_curve",
"type": 1,
"value": -0.027384582906961
},
{
"name": "mod_engine_heat",
"type": 2,
"value": -0.072683908045292
}
],
"moduleTags": [
17
],
"recipeID": 128673655,
"slotIndex": 52
},
"name": "Int_Engine_Size7_Class2",
"on": true,
"priority": 0,
"recipeLevel": 1,
"recipeName": "Engine_Dirty",
"recipeValue": 0,
"unloaned": 0,
"value": 1709638
}
},
"MediumHardpoint1": {
"module": {
"free": false,
"id": 128049436,
"name": "Hpt_BeamLaser_Turret_Medium",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 1889910
}
},
"MediumHardpoint2": {
"module": {
"free": false,
"id": 128049436,
"name": "Hpt_BeamLaser_Turret_Medium",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 1889910
}
},
"MediumHardpoint3": {
"module": {
"free": false,
"id": 128049460,
"name": "Hpt_MultiCannon_Gimbal_Medium",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 51300
}
},
"MediumHardpoint4": {
"module": {
"free": false,
"id": 128049460,
"name": "Hpt_MultiCannon_Gimbal_Medium",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 51300
}
},
"MediumHardpoint5": {
"module": {
"free": false,
"id": 128049460,
"name": "Hpt_MultiCannon_Gimbal_Medium",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 51300
}
},
"PaintJob": {
"module": {
"free": false,
"id": 128732290,
"name": "PaintJob_BelugaLiner_Tactical_White",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 0
}
},
"PlanetaryApproachSuite": {
"module": {
"free": false,
"id": 128672317,
"name": "Int_PlanetApproachSuite",
"on": true,
"priority": 1,
"unloaned": 450,
"value": 450
}
},
"PowerDistributor": {
"module": {
"free": false,
"id": 128064207,
"name": "Int_PowerDistributor_Size6_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 3128120
}
},
"PowerPlant": {
"module": {
"free": false,
"id": 128064057,
"modifiers": {
"engineerID": 300100,
"id": 277,
"modifiers": [
{
"name": "mod_powerplant_power",
"type": 1,
"value": 0.054692290723324
},
{
"name": "mod_health",
"type": 1,
"value": -0.033690698444843
},
{
"name": "mod_powerplant_heat",
"type": 1,
"value": 0.027470717206597
},
{
"name": "mod_powerplant_heat",
"type": 2,
"value": -0.056317910552025
}
],
"moduleTags": [
18
],
"recipeID": 128673765,
"slotIndex": 51
},
"name": "Int_Powerplant_Size6_Class5",
"on": true,
"priority": 1,
"recipeLevel": 1,
"recipeName": "PowerPlant_Boosted",
"recipeValue": 0,
"unloaned": 0,
"value": 14561578
}
},
"Radar": {
"module": {
"free": false,
"id": 128064239,
"name": "Int_Sensors_Size5_Class2",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 71500
}
},
"Slot01_Size6": {
"module": {
"free": false,
"id": 128666681,
"name": "Int_FuelScoop_Size6_Class5",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 25887249
}
},
"Slot02_Size6": {
"module": {
"free": false,
"id": 128064287,
"name": "Int_ShieldGenerator_Size6_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 14561578
}
},
"Slot03_Size6": {
"module": {
"free": false,
"id": 128727927,
"name": "Int_PassengerCabin_Size6_Class2",
"on": true,
"priority": 1,
"unloaned": 165808,
"value": 165808
}
},
"Slot04_Size6": {
"module": {
"free": false,
"id": 128727928,
"name": "Int_PassengerCabin_Size6_Class3",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 497429
}
},
"Slot05_Size5": {
"module": {
"free": false,
"id": 128727925,
"name": "Int_PassengerCabin_Size5_Class4",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 1492286
}
},
"Slot06_Size5": {
"module": {
"free": false,
"id": 128064342,
"name": "Int_CargoRack_Size5_Class1",
"on": true,
"priority": 1,
"unloaned": 100409,
"value": 100409
}
},
"Slot07_Size4": {
"module": {
"free": false,
"id": 128727922,
"name": "Int_PassengerCabin_Size4_Class1",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 17059
}
},
"Slot08_Size3": {
"module": {
"free": false,
"id": 128667632,
"name": "Int_Repairer_Size3_Class5",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 2361960
}
},
"Slot09_Size3": {
"module": {
"free": false,
"id": 128672289,
"name": "Int_BuggyBay_Size2_Class2",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 19440
}
},
"Slot10_Size3": {
"module": {
"free": false,
"id": 128666634,
"name": "Int_DetailedSurfaceScanner_Tiny",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 225000
}
},
"Slot11_Size3": {
"module": {
"free": false,
"id": 128663561,
"name": "Int_StellarBodyDiscoveryScanner_Advanced",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 1390500
}
},
"TinyHardpoint1": {
"module": {
"free": false,
"id": 128049513,
"name": "Hpt_ChaffLauncher_Tiny",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 7650
}
},
"TinyHardpoint2": {
"module": {
"free": false,
"id": 128668536,
"name": "Hpt_ShieldBooster_Size0_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 252900
}
},
"TinyHardpoint3": {
"module": {
"free": false,
"id": 128668536,
"name": "Hpt_ShieldBooster_Size0_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 252900
}
},
"TinyHardpoint4": {
"module": {
"free": false,
"id": 128668536,
"name": "Hpt_ShieldBooster_Size0_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 281000
}
},
"TinyHardpoint5": {
"module": {
"free": false,
"id": 128668536,
"name": "Hpt_ShieldBooster_Size0_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 281000
}
},
"TinyHardpoint6": {
"module": {
"free": false,
"id": 128668536,
"name": "Hpt_ShieldBooster_Size0_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 281000
}
},
"WeaponColour": {
"module": {
"free": false,
"id": 128732194,
"name": "WeaponCustomisation_Purple",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 0
}
}
},
"name": "BelugaLiner",
"value": {
"hull": 71688743,
"modules": 120812762,
"unloaned": 1869489
}
}

View File

@@ -0,0 +1,327 @@
{
"$schema": "http://cdn.coriolis.io/schemas/ship-loadout/4.json#",
"name": "Multi-purpose Imperial Courier",
"ship": "Imperial Courier",
"references": [
{
"name": "Coriolis.io",
"url": "https://coriolis.edcd.io/outfit/imperial_courier?code=0patzF5l0das8f31a1a270202000e402t0101-2f.AwRj4zKA.CwRgDBldLiQ%3D.H4sIAAAAAAAAA12OP0tCYRjFj9fuVbvF1du9ekkT8s%2FkIg4NElyIBBd321yaGvwUQTS3N7UFfYygIT9EoyQUJA36ns47XJCWA%2B%2Fz%2Bz3Pe3ImBbDNKaqNPSBoGrL4ngfomKpFGiJ%2BLgHteR1IPjxJT5pF11uSeXNsJVcRfgdC92syWUuK0iMdKZqrjJ%2F0aoA71lJ5oKf38knWcCiptCPdhJIerdS00vlK0qktlqoj983UmqqHjQ33VsW8eazFmaTyULP2hQ4lX8LBme6g%2F6v0TTdbxJ2KhdEIaCw15MF%2FNB0L%2BS2hwEwyFM8KgP%2BqEpWWA3Qu9Z3z9kPWHzakt7Dt%2BAeD7ghSTgEAAA%3D%3D&bn=Multi-purpose%20Imperial%20Courier",
"code": "0patzF5l0das8f31a1a270202000e402t0101-2f.AwRj4zKA.CwRgDBldLiQ=.H4sIAAAAAAAAA12OP0tCYRjFj9fuVbvF1du9ekkT8s/kIg4NElyIBBd321yaGvwUQTS3N7UFfYygIT9EoyQUJA36ns47XJCWA+/z+z3Pe3ImBbDNKaqNPSBoGrL4ngfomKpFGiJ+LgHteR1IPjxJT5pF11uSeXNsJVcRfgdC92syWUuK0iMdKZqrjJ/0aoA71lJ5oKf38knWcCiptCPdhJIerdS00vlK0qktlqoj983UmqqHjQ33VsW8eazFmaTyULP2hQ4lX8LBme6g/6v0TTdbxJ2KhdEIaCw15MF/NB0L+S2hwEwyFM8KgP+qEpWWA3Qu9Z3z9kPWHzakt7Dt+AeD7ghSTgEAAA==",
"shipId": "imperial_courier"
}
],
"components": {
"standard": {
"bulkheads": "Lightweight Alloy",
"cargoHatch": {
"enabled": false,
"priority": 5
},
"powerPlant": {
"class": 4,
"rating": "A",
"enabled": true,
"priority": 2,
"modifications": {
"pgen": 1052,
"integrity": -482,
"eff": 974
},
"blueprint": {
"id": 63,
"name": "Overcharged",
"grade": 1
}
},
"thrusters": {
"class": 3,
"rating": "A",
"enabled": true,
"priority": 1,
"name": "Enhanced Performance",
"modifications": {
"optmul": 2476,
"thermload": 7023,
"power": 1763,
"integrity": 165,
"optmass": -667
},
"blueprint": {
"id": 22,
"name": "Dirty",
"grade": 4
}
},
"frameShiftDrive": {
"class": 3,
"rating": "A",
"enabled": true,
"priority": 1,
"modifications": {
"mass": 4082,
"integrity": -2422,
"power": 1782,
"optmass": 4927
},
"blueprint": {
"id": 26,
"name": "Increased range",
"grade": 5
}
},
"lifeSupport": {
"class": 1,
"rating": "A",
"enabled": true,
"priority": 1
},
"powerDistributor": {
"class": 3,
"rating": "A",
"enabled": true,
"priority": 1
},
"sensors": {
"class": 2,
"rating": "D",
"enabled": true,
"priority": 1
},
"fuelTank": {
"class": 3,
"rating": "C",
"enabled": true,
"priority": 1
}
},
"hardpoints": [
{
"class": 2,
"rating": "F",
"enabled": true,
"priority": 1,
"group": "Pulse Laser",
"mount": "Fixed",
"modifications": {
"rof": 5931,
"damage": -184,
"jitter": 50,
"distdraw": -4689,
"piercing": 3328
},
"blueprint": {
"id": 89,
"name": "Rapid fire",
"grade": 5
}
},
{
"class": 2,
"rating": "F",
"enabled": true,
"priority": 1,
"group": "Pulse Laser",
"mount": "Fixed",
"modifications": {
"rof": 4715,
"damage": -97,
"jitter": 30,
"distdraw": -4548,
"piercing": 1057,
"integrity": 319
},
"blueprint": {
"id": 89,
"name": "Rapid fire",
"grade": 5
}
},
{
"class": 2,
"rating": "F",
"enabled": true,
"priority": 1,
"group": "Multi-cannon",
"mount": "Gimballed",
"modifications": {
"damage": 2437,
"distdraw": 5487,
"rof": 1120,
"jitter": 58,
"thermload": 1346,
"power": 1009,
"integrity": -202,
"ammo": -2000
},
"blueprint": {
"id": 88,
"name": "Overcharged",
"grade": 3,
"special": {
"id": 3,
"name": "Corrosive shell"
}
}
}
],
"utility": [
{
"class": 0,
"rating": "I",
"enabled": true,
"priority": 1,
"group": "Heat Sink Launcher",
"name": "Heat Sink Launcher",
"modifications": {
"ammo": 5000,
"mass": 17684,
"reload": 9707
},
"blueprint": {
"id": 37,
"name": "Ammo capacity",
"grade": 3
}
},
{
"class": 0,
"rating": "I",
"enabled": true,
"priority": 1,
"group": "Heat Sink Launcher",
"name": "Heat Sink Launcher",
"modifications": {
"ammo": 5000,
"mass": 18520,
"reload": 8715
},
"blueprint": {
"id": 37,
"name": "Ammo capacity",
"grade": 3
}
},
{
"class": 0,
"rating": "I",
"enabled": true,
"priority": 1,
"group": "Chaff Launcher",
"name": "Chaff Launcher"
},
{
"class": 0,
"rating": "A",
"enabled": true,
"priority": 1,
"group": "Frame Shift Wake Scanner"
}
],
"internal": [
{
"class": 3,
"rating": "A",
"enabled": true,
"priority": 1,
"group": "Shield Generator",
"modifications": {
"optmul": 1888,
"explres": 455,
"kinres": 546,
"thermres": 1092,
"brokenregen": -2614,
"regen": -876,
"distdraw": 463
},
"blueprint": {
"id": 77,
"name": "Reinforced",
"grade": 3
}
},
{
"class": 3,
"rating": "A",
"enabled": true,
"priority": 1,
"group": "Fuel Scoop"
},
{
"class": 2,
"rating": "E",
"enabled": true,
"priority": 2,
"group": "Cargo Rack"
},
{
"class": 2,
"rating": "E",
"enabled": true,
"priority": 2,
"group": "Cargo Rack"
},
null,
{
"class": 1,
"rating": "C",
"enabled": true,
"priority": 2,
"group": "Scanner",
"name": "Advanced Discovery Scanner"
}
]
},
"stats": {
"class": 1,
"hullCost": 2481550,
"speed": 280,
"boost": 380,
"boostEnergy": 10,
"agility": 6,
"baseShieldStrength": 200,
"baseArmour": 80,
"hullMass": 35,
"masslock": 7,
"pipSpeed": 0.05,
"moduleCostMultiplier": 1,
"fuelCapacity": 8,
"cargoCapacity": 8,
"ladenMass": 104.25,
"armour": 144,
"shield": 404.19,
"shieldCells": 0,
"totalCost": 14059860,
"unladenMass": 88.25,
"totalDpe": 32.25,
"totalExplDpe": 0,
"totalKinDpe": 9.41,
"totalThermDpe": 22.84,
"totalDps": 53.8,
"totalExplDps": 0,
"totalKinDps": 17.44,
"totalThermDps": 36.35,
"totalSDps": 48.99,
"totalExplSDps": 0,
"totalKinSDps": 12.64,
"totalThermSDps": 36.35,
"totalEps": 9.84,
"totalHps": 12.28,
"shieldExplRes": 0.48,
"shieldKinRes": 0.55,
"shieldThermRes": 1.09,
"hullExplRes": 1.4,
"hullKinRes": 1.2,
"hullThermRes": 1,
"powerAvailable": 17.24,
"powerRetracted": 11.3,
"powerDeployed": 16.41,
"unladenRange": 25.57,
"fullTankRange": 23.92,
"ladenRange": 22.09,
"unladenFastestRange": 116.23,
"ladenFastestRange": 107,
"maxJumpCount": 5,
"topSpeed": 386.56,
"topBoost": 524.62
}
}

View File

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

View File

@@ -1,50 +1,50 @@
{ {
"type_6_transporter": { "type_6_transporter": {
"Cargo": "02A4D4A2D2D2D4C-----04040303430101.Iw1-kA==.Aw1-kA==", "Cargo": "A0p0tdFal8d8s8f4-----04040303430101.Iw1/kA==.Aw1/kA==.",
"Miner": "03A4D4A2D2D2D4C2l2l---040403451q0101.Iw1-kA==.Aw1-kA==", "Miner": "A0p5tdFal8d8s8f42l2l---040403451q0101.Iw1/kA==.Aw1/kA==.",
"Hopper": "02A4D4A2D1A2D4C1717---030302024300-.Iw1-kA==.Aw1-kA==" "Hopper": "A0p0tdFal8d0s8f41717---030302024300-.Iw1/kA==.Aw1/kA==."
}, },
"type_7_transport": { "type_7_transport": {
"Cargo": "02A5D5A4D3D3D5C--------0505040403480101.Iw18aQ==.Aw18aQ==", "Cargo": "A0p0tiFfliddsdf5--------0505040403480101.Iw18aQ==.Aw18aQ==.",
"Miner": "04D5D5A4D2D3D5C--2l2l----0505041v03450000.Iw18aQ==.Aw18aQ==" "Miner": "A0pdtiFflid8sdf5--2l2l----0505041v03450000.Iw18aQ==.Aw18aQ==."
}, },
"federal_dropship": { "federal_dropship": {
"Cargo": "04D5D5A5D3D4D4C-1717------05040448020201.Iw18aQ==.Aw18aQ==" "Cargo": "A0pdtiFflnddsif4-1717------05040448--020201.Iw18eQ==.Aw18eQ==."
}, },
"asp": { "asp": {
"Miner": "25A5A5A4D4A5A5C0s0s24242l2l---04054a1q02022o27.Iw18WQ==.Aw18WQ==" "Miner": "A2pftfFflidfskf50s0s24242l2l---04054a1q02022o27.Iw18WQ==.Aw18WQ==."
}, },
"imperial_clipper": { "imperial_clipper": {
"Cargo": "03A5D5A5D4D5D4C--0s0s----0605450302020101.Iw18aQ==.Aw18aQ==", "Cargo": "A0p5tiFflndisnf4--0s0s----0605450302020101.Iw18aQ==.Aw18aQ==.",
"Dream": "26A6A5A5D6A5A4C0v0v0s0s0404040n4k5n5d2b29292o-.Iw18aQ==.Aw18aQ==", "Dream": "A2pktkFflndpskf40v0v0s0s0404040n4k5n5d2b29292o-.AwRj4yWU1I==.CwBhCYy6YRigzLIA.",
"Current": "04A6A5A5D4A5A4C----------------.Iw18aQ==.Aw18aQ==" "Current": "A0patkFflndfskf4----------------.AwRj4yWU1I==.CwBhCYy6YRigzLIA."
}, },
"type_9_heavy": { "type_9_heavy": {
"Current": "04A7D6A5D5D4D6C---------0706054a0303020224.Iw18eQ==.Aw18eQ==" "Current": "A0patsFklndnsif6---------0706054a0303020224.AwRj4yoo.EwBhEYy6dsg=."
}, },
"python": { "python": {
"Cargo": "04A6D5A4D6D6D5C---------050505040448020201.Iw18eQ==.Aw18eQ==", "Cargo": "A0patnFflidsssf5---------050505040448020201.Iw18eQ==.Aw18eQ==.",
"Miner": "06A6A5A4D6A6A5C0v0v0v2m2m0404--050505Ce4a1v02022o.Iw18eQ==.Aw18eQ==", "Miner": "A0pktkFflidpspf50v0v0v2m2m0404--050505Ce4a1v02022o.Iw18eQ==.IwBhBYy6dkCYg===.",
"Dream": "27A6A5A4D7A6A5C0v0v0v27270404040m5n5n4f2d2d032t0201.Iw18eQ==.Aw18eQ==", "Dream": "A2pptkFfliduspf50v0v0v27270404040m5n5n4f2d2d032t0201.Iw1+gDBxA===.EwBhEYy6e0WEA===.",
"Missile": "07E6E5E4E7E6E5C2f2g2d2ePh----04044j03---002h.Iw18eQ==.Aw18eQ==" "Missile": "A0pttoFjljdystf52f2g2d2ePh----04044j03---002h.Iw18eQ==.Aw18eQ==."
}, },
"anaconda": { "anaconda": {
"Dream": "48A7A6A5D8A8A5C2c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d040303326b.Iw18ZlA=.Aw18ZlA=", "Dream": "A4putpFklndzsuf52c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d04-0303326b.AwRj4yo5dyg=.MwBhCYy6duvARiA=.",
"Cargo": "04A6D6A5D5D8D5C----------------0605050504040445030301.Iw18ZlA=.Aw18ZlA=", "Cargo": "A0patnFklndnsxf5----------------06050505040404-45030301.Iw18ZVA=.Aw18ZVA=.",
"Current": "04A6D6A5D5A8D5C----------------0605050504040403034524.Iw18ZlA=.Aw18ZlA=", "Current": "A0patnFklndksxf5----------------06050505040404-03034524.Iw18ZVA=.Aw18ZVA=.",
"Explorer": "04A6D6A5D5A8D5C--------0202------f7050505040s372f2i4524.Iw18ZlA=.Aw18ZlA=", "Explorer": "A0patnFklndksxf5--------0202------f7050505040s37-2f2i4524.AwRj4yVKJ9hA.AwhMIyumQRhEA===.",
"Test": "48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.Iw18ZlA=.Aw18ZlA=" "Test": "A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.Iw18ZVA=.Aw18ZVA=."
}, },
"diamondback_explorer": { "diamondback_explorer": {
"Explorer": "02A4D5A3D3D3D5C---0202--320p432i2f.Iw1-kA==.Aw1-kA==" "Explorer": "A0p0tdFfldddsdf5---0202--320p432i2f.AwRj4zTI.AwiMIypI."
}, },
"vulture": { "vulture": {
"Bounty Hunter": "34A4C4A3D5A4A3C1e1e0404-0l4a5d27662j.Iw19kA==.Aw19kA==" "Bounty Hunter": "A3patcFalddksff31e1e0404-0l4a-5d27662j.AwRj4z2I.MwBhBYy6oJmAjLIA."
}, },
"fer_de_lance": { "fer_de_lance": {
"Attack": "25A5C4A4D6A4A3C1r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.Aw18aQ==" "Attack": "A2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.CwBhrSu8EZyA."
}, },
"eagle": { "eagle": {
"Figther": "42A3A3A1D2A2A2C0p0p24-40532j.Iw19A===.Aw19A===" "Figther": "A4p0t5F5l3d5s5f20p0p24-4053-2j-.Iw18kA==.Aw18kA==."
} }
} }

View File

@@ -33,7 +33,8 @@
}, },
"anaconda": { "anaconda": {
"Dream": "48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404040l0b0100034k5n05050404040303326b.AwRj4yo5dig=.MwBhEYy6duwEziA=", "Dream": "48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404040l0b0100034k5n05050404040303326b.AwRj4yo5dig=.MwBhEYy6duwEziA=",
"Cargo": "03A7D6A5D4D8D5C----------------060505054d040403030301.AwRj4yuqg===.Aw18ZlA=" "Cargo": "03A7D6A5D4D8D5C----------------060505054d040403030301.AwRj4yuqg===.Aw18ZlA=",
"Modified": "0pyttFolodDsyf5------1717--------05044j-03----2h00.Iw18ZlA=.Aw18ZlA=.H4sIAAAAAAAAA2MUe8HMwPD-PwDDhxeuCAAAAA=="
}, },
"diamondback_explorer": { "diamondback_explorer": {
"Explorer": "02A4D5A3D3D3D5C-------320p432i2f.AwRj4zTI.AwiMIypI" "Explorer": "02A4D5A3D3D3D5C-------320p432i2f.AwRj4zTI.AwiMIypI"
@@ -63,4 +64,4 @@
1, 1,
1 1
] ]
} }

View File

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

90
__tests__/test-agility.js Normal file
View File

@@ -0,0 +1,90 @@
import Ship from '../src/app/shipyard/Ship';
import { Ships } from 'coriolis-data/dist';
import * as ModuleUtils from '../src/app/shipyard/ModuleUtils';
describe("Agility", function() {
it("correctly calculates speed", function() {
let agilityData = require('./fixtures/agility-data');
for (let shipId in agilityData) {
for (let thrusterId in agilityData[shipId]) {
const thrusterData = agilityData[shipId][thrusterId];
let shipData = Ships[shipId];
let ship = new Ship(shipId, shipData.properties, shipData.slots);
ship.buildWith(shipData.defaults);
ship.use(ship.standard[1], ModuleUtils.findModule('t', thrusterId));
expect(Math.round(ship.topSpeed)).toBe(thrusterData.speed);
}
}
});
it("correctly calculates boost", function() {
let agilityData = require('./fixtures/agility-data');
for (let shipId in agilityData) {
for (let thrusterId in agilityData[shipId]) {
const thrusterData = agilityData[shipId][thrusterId];
let shipData = Ships[shipId];
let ship = new Ship(shipId, shipData.properties, shipData.slots);
ship.buildWith(shipData.defaults);
// Turn off internals to ensure we have enough power to boost
for (let internal in ship.internal) {
ship.internal[internal].enabled = 0;
}
ship.use(ship.standard[1], ModuleUtils.findModule('t', thrusterId));
expect(Math.round(ship.topBoost)).toBe(thrusterData.boost);
}
}
});
it("correctly calculates pitch", function() {
let agilityData = require('./fixtures/agility-data');
for (let shipId in agilityData) {
for (let thrusterId in agilityData[shipId]) {
const thrusterData = agilityData[shipId][thrusterId];
let shipData = Ships[shipId];
let ship = new Ship(shipId, shipData.properties, shipData.slots);
ship.buildWith(shipData.defaults);
ship.use(ship.standard[1], ModuleUtils.findModule('t', thrusterId));
expect(Math.round(ship.pitches[4] * 100) / 100).toBeCloseTo(thrusterData.pitch, 1);
}
}
});
it("correctly calculates roll", function() {
let agilityData = require('./fixtures/agility-data');
for (let shipId in agilityData) {
for (let thrusterId in agilityData[shipId]) {
const thrusterData = agilityData[shipId][thrusterId];
let shipData = Ships[shipId];
let ship = new Ship(shipId, shipData.properties, shipData.slots);
ship.buildWith(shipData.defaults);
ship.use(ship.standard[1], ModuleUtils.findModule('t', thrusterId));
expect(Math.round(ship.rolls[4] * 100) / 100).toBeCloseTo(thrusterData.roll, 1);
}
}
});
it("correctly calculates yaw", function() {
let agilityData = require('./fixtures/agility-data');
for (let shipId in agilityData) {
for (let thrusterId in agilityData[shipId]) {
const thrusterData = agilityData[shipId][thrusterId];
let shipData = Ships[shipId];
let ship = new Ship(shipId, shipData.properties, shipData.slots);
ship.buildWith(shipData.defaults);
ship.use(ship.standard[1], ModuleUtils.findModule('t', thrusterId));
expect(Math.round(ship.yaws[4] * 100) / 100).toBeCloseTo(thrusterData.yaw, 1);
}
}
});
});

View File

@@ -10,7 +10,7 @@ import { getLanguage } from '../src/app/i18n/Language';
describe('Import Modal', function() { describe('Import Modal', function() {
let MockRouter = require('../src/app/Router'); let MockRouter = require('../src/app/Router').default;
const Persist = require('../src/app/stores/Persist').default; const Persist = require('../src/app/stores/Persist').default;
const ModalImport = require('../src/app/components/ModalImport').default; const ModalImport = require('../src/app/components/ModalImport').default;
const mockContext = { const mockContext = {
@@ -25,8 +25,6 @@ describe('Import Modal', function() {
onWindowResize: jest.genMockFunction() onWindowResize: jest.genMockFunction()
}; };
MockRouter.go = jest.genMockFunction();
let modal, render, ContextProvider = Utils.createContextProvider(mockContext); let modal, render, ContextProvider = Utils.createContextProvider(mockContext);
/** /**
@@ -110,7 +108,8 @@ describe('Import Modal', function() {
it('catches an invalid backup', function() { it('catches an invalid backup', function() {
const importData = require('./fixtures/valid-backup'); const importData = require('./fixtures/valid-backup');
let invalidImportData = Object.assign({}, importData); let invalidImportData = Object.assign({}, importData);
invalidImportData.builds.asp = null; // Remove Asp Miner build used in comparison //invalidImportData.builds.asp = null; // Remove Asp Miner build used in comparison
delete(invalidImportData.builds.asp);
pasteText('"this is not valid"'); pasteText('"this is not valid"');
expect(modal.state.importValid).toBeFalsy(); expect(modal.state.importValid).toBeFalsy();
@@ -130,7 +129,7 @@ describe('Import Modal', function() {
}); });
}); });
describe('Import Detailed Build', function() { describe('Import Detailed V3 Build', function() {
beforeEach(reset); beforeEach(reset);
@@ -140,20 +139,67 @@ describe('Import Modal', function() {
expect(modal.state.importValid).toBeTruthy(); expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null); expect(modal.state.errorMsg).toEqual(null);
expect(modal.state.singleBuild).toBe(true);
clickProceed(); clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1); expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda/48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA?bn=Test%20My%20Ship'); expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.AwRj4zNLaA%3D%3D.CwBhCYzBGW9qCTSqq5xA.&bn=Test%20My%20Ship');
}); });
it('catches an invalid build', function() { it('catches an invalid build', function() {
const importData = require('./fixtures/anaconda-test-detailed-export-v3'); const importData = require('./fixtures/anaconda-test-detailed-export-v3');
pasteText(JSON.stringify(importData).replace('components', 'comps')); pasteText(JSON.stringify(importData).replace('references', 'refs'));
expect(modal.state.importValid).toBeFalsy(); expect(modal.state.importValid).toBeFalsy();
expect(modal.state.errorMsg).toEqual('Anaconda Build "Test My Ship": Invalid data'); expect(modal.state.errorMsg).toEqual('Anaconda Build "Test My Ship": Invalid data');
}); });
}); });
describe('Import Detailed V4 Build', function() {
beforeEach(reset);
it('imports a valid v4 build', function() {
const importData = require('./fixtures/anaconda-test-detailed-export-v4');
pasteText(JSON.stringify(importData));
expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null);
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.AwRj4zNLaA%3D%3D.CwBhCYzBGW9qCTSqq5xA.H4sIAAAAAAAAA2MUe8HMwPD%2FPwMcAABTINwTEgAAAA%3D%3D&bn=Test%20My%20Ship');
});
});
describe('Import Detailed Engineered V4 Build', function() {
beforeEach(reset);
it('imports a valid v4 build', function() {
const importData = require('./fixtures/asp-test-detailed-export-v4');
pasteText(JSON.stringify(importData));
expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null);
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/asp?code=A0pftiFflfddsnf5------020202033c044002v62f2i.AwRj4yvI.CwRgDBldHnJA.H4sIAAAAAAAAA2P858DAwPCXEUhwHPvx%2F78YG5AltB7I%2F8%2F0TwImJboDSPJ%2F%2B%2Ff%2Fv%2FKlX%2F%2F%2Fi3AwMTBIfARK%2FGf%2BJwVSxArStVAYqOjvz%2F%2F%2FJVo5GRhE2IBc4SKQSSz%2FDGEmCa398P8%2F%2F2%2BgTf%2F%2FAwDFxwtofAAAAA%3D%3D&bn=Multi-purpose%20Asp%20Explorer');
});
it('imports a valid v4 build with modifications', function() {
const importData = require('./fixtures/courier-test-detailed-export-v4');
pasteText(JSON.stringify(importData));
expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null);
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/imperial_courier?code=A0patzF5l0das8f31a1a270202000e402t0101-2f.AwRj4zKA.CwRgDBldLiQ%3D.H4sIAAAAAAAAA12OP0tCYRjFj9fuVbvF1du9ekkT8s%2FkIg4NElyIBBd321yaGvwUQTS3N7UFfYygIT9EoyQUJA36ns47XJCWA%2B%2Fz%2Bz3Pe3ImBbDNKaqNPSBoGrL4ngfomKpFGiJ%2BLgHteR1IPjxJT5pF11uSeXNsJVcRfgdC92syWUuK0iMdKZqrjJ%2F0aoA71lJ5oKf38knWcCiptCPdhJIerdS00vlK0qktlqoj983UmqqHjQ33VsW8eazFmaTyULP2hQ4lX8LBme6g%2F6v0TTdbxJ2KhdEIaCw15MF%2FNB0L%2BS2hwEwyFM8KgP%2BqEpWWA3Qu9Z3z9kPWHzakt7Dt%2BAeD7ghSTgEAAA%3D%3D&bn=Multi-purpose%20Imperial%20Courier');
});
});
describe('Import Detaild Builds Array', function() { describe('Import Detaild Builds Array', function() {
beforeEach(reset); beforeEach(reset);
@@ -179,22 +225,51 @@ describe('Import Modal', function() {
}); });
}); });
describe('Import Companion API Build', function() {
beforeEach(reset);
it('imports a valid v4 build', function() {
const importData = require('./fixtures/companion-api-import-1');
pasteText(JSON.stringify(importData));
expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null);
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/federal_corvette?code=A2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifr--v66g2f.AwRj4zNaqA%3D%3D.CwRgDBldUExuBiIlUA%3D%3D.H4sIAAAAAAAAA02SO0sDQRSFbxJ389jgJOsaN%2FGVmPXVKKRQC8FSA9oJWihWWgV%2FgIWFYBNb8RcIWiiIYiF2NqksIqaRoD%2FBQghB41zPFVa3OdzZ890zM3snpBeI6DsEyZgGkbpg5tg2lhzWGbEikN6aSVS0HSL3Ogxo6IvZmdbM9hFsjuickGgn%2B8SGv%2FvJ7DpxIqeCHjb0vJ80GrWIxu5RFmqARnYQEj%2FrMCdesFQzSOeYXvPP1BmGZPeiREa9xWyW0WifwnFX0MMJve4Hd5IQo4I9TcclGrxCUmoO34qVDaK%2BJuiJfD6%2FytZ%2Fj%2FGQAL7fD%2Fyiy8fbcNQdjsXJAFn9DRbyQchZIS9RqZJcGpckt4xjsdKL%2FtndJYjVQJkW8URUVYJTAegLat0IJOKJqCeB0gHoQ6BHgUQ8EdUNZvgghj3tAPmKa1vPQhIqTyp1KHE9AWgKV7Ka8NMinoiywLCrV%2F5Gvolo61ySpMn7xMdwsc3cf4w48w2Is40fwFld9oPzLbyL6CT88QaWoZpMcyDg32Jo0br4EaxDJXk8BT3o%2B7ktGS9B3GU8puS7zJh%2FAHGMT2qjAgAA&bn=Imported%20Federal%20Corvette');
});
it('imports a valid v4 build', function() {
const importData = require('./fixtures/companion-api-import-2');
pasteText(JSON.stringify(importData));
expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null);
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/beluga?code=A0pktsFplCdpsnf70t0t2727270004040404043c4fmimlmm04mc0iv62i2f.AwRj4yukg%3D%3D%3D.CwRgDBldHi8IUA%3D%3D.H4sIAAAAAAAAA2P8Z8%2FAwPCXEUiIKTMxMPCv%2F%2Ff%2FP8cFIPGf6Z8YTEr0GjMDg%2FJWICERBOTzn%2Fn7%2F7%2FIO5Ai5n9SIEWsQEIoSxAolfbt%2F3%2BJPk4GBhE7YQYGYVmgcuVnf4Aq%2FwMAIrEcGGsAAAA%3D&bn=Imported%20Beluga%20Liner');
});
});
describe('Import E:D Shipyard Builds', function() { describe('Import E:D Shipyard Builds', function() {
it('imports a valid builds', function() { // it('imports a valid build', function() {
const imports = require('./fixtures/ed-shipyard-import-valid'); // const imports = require('./fixtures/ed-shipyard-import-valid');
//
for (let i = 0; i < imports.length; i++ ) { // for (let i = 0; i < imports.length; i++ ) {
reset(); // reset();
let fixture = imports[i]; // let fixture = imports[i];
pasteText(fixture.buildText); // pasteText(fixture.buildText);
expect(modal.state.importValid).toBeTruthy(); // expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null); // expect(modal.state.errorMsg).toEqual(null);
clickProceed(); // clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1); // expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/' + fixture.shipId + '/' + fixture.buildCode + '?bn=' + encodeURIComponent(fixture.buildName)); // expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/' + fixture.shipId + '?code=' + encodeURIComponent(fixture.buildCode) + '&bn=' + encodeURIComponent(fixture.buildName));
} // }
}); // });
it('catches invalid builds', function() { it('catches invalid builds', function() {
const imports = require('./fixtures/ed-shipyard-import-invalid'); const imports = require('./fixtures/ed-shipyard-import-invalid');

View File

@@ -4,16 +4,16 @@ import * as Serializer from '../src/app/shipyard/Serializer';
import jsen from 'jsen'; import jsen from 'jsen';
describe("Serializer", function() { describe("Serializer", function() {
const anacondaTestExport = require.requireActual('./fixtures/anaconda-test-detailed-export-v3'); const anacondaTestExport = require.requireActual('./fixtures/anaconda-test-detailed-export-v4');
const code = anacondaTestExport.references[0].code; const code = anacondaTestExport.references[0].code;
const anaconda = Ships.anaconda; const anaconda = Ships.anaconda;
const validate = jsen(require('../src/schemas/ship-loadout/3')); const validate = jsen(require('../src/schemas/ship-loadout/4'));
describe("To Detailed Build", function() { describe("To Detailed Build", function() {
let testBuild = new Ship('anaconda', anaconda.properties, anaconda.slots).buildFrom(code); let testBuild = new Ship('anaconda', anaconda.properties, anaconda.slots).buildFrom(code);
let exportData = Serializer.toDetailedBuild('Test My Ship', testBuild); let exportData = Serializer.toDetailedBuild('Test My Ship', testBuild);
it("conforms to the v3 ship-loadout schema", function() { it("conforms to the v4 ship-loadout schema", function() {
expect(validate(exportData)).toBe(true); expect(validate(exportData)).toBe(true);
}); });
@@ -31,7 +31,7 @@ describe("Serializer", function() {
const builds = require('./fixtures/expected-builds'); const builds = require('./fixtures/expected-builds');
const exportData = Serializer.toDetailedExport(builds); const exportData = Serializer.toDetailedExport(builds);
it("conforms to the v3 ship-loadout schema", function() { it("conforms to the v4 ship-loadout schema", function() {
expect(exportData instanceof Array).toBe(true); expect(exportData instanceof Array).toBe(true);
for (let detailedBuild of exportData) { for (let detailedBuild of exportData) {

View File

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

View File

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

View File

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

906
src/.htaccess Normal file
View File

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

View File

@@ -7,6 +7,8 @@ import Persist from './stores/Persist';
import Header from './components/Header'; import Header from './components/Header';
import Tooltip from './components/Tooltip'; import Tooltip from './components/Tooltip';
import ModalImport from './components/ModalImport'; import ModalImport from './components/ModalImport';
import * as CompanionApiUtils from './utils/CompanionApiUtils';
import { outfitURL } from './utils/UrlGenerators';
import AboutPage from './pages/AboutPage'; import AboutPage from './pages/AboutPage';
import NotFoundPage from './pages/NotFoundPage'; import NotFoundPage from './pages/NotFoundPage';
@@ -15,23 +17,26 @@ import ComparisonPage from './pages/ComparisonPage';
import ShipyardPage from './pages/ShipyardPage'; import ShipyardPage from './pages/ShipyardPage';
import ErrorDetails from './pages/ErrorDetails'; import ErrorDetails from './pages/ErrorDetails';
const zlib = require('zlib');
/** /**
* Coriolis App * Coriolis App
*/ */
export default class Coriolis extends React.Component { export default class Coriolis extends React.Component {
static childContextTypes = { static childContextTypes = {
language: React.PropTypes.object.isRequired,
sizeRatio: React.PropTypes.number.isRequired,
route: React.PropTypes.object.isRequired,
openMenu: React.PropTypes.func.isRequired,
closeMenu: React.PropTypes.func.isRequired, closeMenu: React.PropTypes.func.isRequired,
showModal: React.PropTypes.func.isRequired,
hideModal: React.PropTypes.func.isRequired, hideModal: React.PropTypes.func.isRequired,
tooltip: React.PropTypes.func.isRequired, language: React.PropTypes.object.isRequired,
termtip: React.PropTypes.func.isRequired, noTouch: React.PropTypes.bool.isRequired,
onCommand: React.PropTypes.func.isRequired,
onWindowResize: React.PropTypes.func.isRequired, onWindowResize: React.PropTypes.func.isRequired,
onCommand: React.PropTypes.func.isRequired openMenu: React.PropTypes.func.isRequired,
route: React.PropTypes.object.isRequired,
showModal: React.PropTypes.func.isRequired,
sizeRatio: React.PropTypes.number.isRequired,
termtip: React.PropTypes.func.isRequired,
tooltip: React.PropTypes.func.isRequired
}; };
/** /**
@@ -51,23 +56,48 @@ export default class Coriolis extends React.Component {
this._onLanguageChange = this._onLanguageChange.bind(this); this._onLanguageChange = this._onLanguageChange.bind(this);
this._onSizeRatioChange = this._onSizeRatioChange.bind(this); this._onSizeRatioChange = this._onSizeRatioChange.bind(this);
this._keyDown = this._keyDown.bind(this); this._keyDown = this._keyDown.bind(this);
this._importBuild = this._importBuild.bind(this);
this.emitter = new EventEmitter(); this.emitter = new EventEmitter();
this.state = { this.state = {
noTouch: !('ontouchstart' in window || navigator.msMaxTouchPoints || navigator.maxTouchPoints),
page: null, page: null,
language: getLanguage(Persist.getLangCode()), language: getLanguage(Persist.getLangCode()),
route: null, route: {},
sizeRatio: Persist.getSizeRatio() sizeRatio: Persist.getSizeRatio()
}; };
Router('', (r) => this._setPage(ShipyardPage, r)); Router('', (r) => this._setPage(ShipyardPage, r));
Router('/import?', (r) => this._importBuild(r));
Router('/import/:data', (r) => this._importBuild(r));
Router('/outfit/?', (r) => this._setPage(OutfittingPage, r));
Router('/outfit/:ship/?', (r) => this._setPage(OutfittingPage, r));
Router('/outfit/:ship/:code?', (r) => this._setPage(OutfittingPage, r)); Router('/outfit/:ship/:code?', (r) => this._setPage(OutfittingPage, r));
Router('/compare/:name?', (r) => this._setPage(ComparisonPage, r)); Router('/compare/:name?', (r) => this._setPage(ComparisonPage, r));
Router('/comparison?', (r) => this._setPage(ComparisonPage, r));
Router('/comparison/:code', (r) => this._setPage(ComparisonPage, r)); Router('/comparison/:code', (r) => this._setPage(ComparisonPage, r));
Router('/about', (r) => this._setPage(AboutPage, r)); Router('/about', (r) => this._setPage(AboutPage, r));
Router('*', (r) => this._setPage(null, r)); Router('*', (r) => this._setPage(null, r));
} }
/**
* Import a build directly
* @param {Object} r The current route
*/
_importBuild(r) {
try {
// Need to decode and gunzip the data, then build the ship
const data = zlib.gunzipSync(new Buffer(r.params.data, 'base64'));
const json = JSON.parse(data);
const ship = CompanionApiUtils.shipFromJson(json);
r.params.ship = ship.id;
r.params.code = ship.toString();
this._setPage(OutfittingPage, r);
} catch (err) {
this._onError('Failed to import ship', r.path, 0, 0, err);
}
}
/** /**
* Updates / Sets the page and route context * Updates / Sets the page and route context
* @param {[type]} page The page to be shown * @param {[type]} page The page to be shown
@@ -237,17 +267,18 @@ export default class Coriolis extends React.Component {
*/ */
getChildContext() { getChildContext() {
return { return {
language: this.state.language,
route: this.state.route,
sizeRatio: this.state.sizeRatio,
openMenu: this._openMenu,
closeMenu: this._closeMenu, closeMenu: this._closeMenu,
showModal: this._showModal,
hideModal: this._hideModal, hideModal: this._hideModal,
tooltip: this._tooltip, language: this.state.language,
termtip: this._termtip, noTouch: this.state.noTouch,
onCommand: this._onCommand,
onWindowResize: this._onWindowResize, onWindowResize: this._onWindowResize,
onCommand: this._onCommand openMenu: this._openMenu,
route: this.state.route,
showModal: this._showModal,
sizeRatio: this.state.sizeRatio,
termtip: this._termtip,
tooltip: this._tooltip
}; };
} }
@@ -266,7 +297,7 @@ export default class Coriolis extends React.Component {
window.onerror = this._onError.bind(this); window.onerror = this._onError.bind(this);
window.addEventListener('resize', () => this.emitter.emit('windowResize')); window.addEventListener('resize', () => this.emitter.emit('windowResize'));
document.body.addEventListener('scroll', () => this._tooltip()); document.getElementById('coriolis').addEventListener('scroll', () => this._tooltip());
document.addEventListener('keydown', this._keyDown); document.addEventListener('keydown', this._keyDown);
Persist.addListener('language', this._onLanguageChange); Persist.addListener('language', this._onLanguageChange);
Persist.addListener('sizeRatio', this._onSizeRatioChange); Persist.addListener('sizeRatio', this._onSizeRatioChange);
@@ -281,11 +312,16 @@ export default class Coriolis extends React.Component {
render() { render() {
let currentMenu = this.state.currentMenu; let currentMenu = this.state.currentMenu;
return <div onClick={this._closeMenu}> return <div style={{ minHeight: '100%' }} onClick={this._closeMenu} className={ this.state.noTouch ? 'no-touch' : null }>
<Header appCacheUpdate={this.state.appCacheUpdate} currentMenu={currentMenu} /> <Header appCacheUpdate={this.state.appCacheUpdate} currentMenu={currentMenu} />
{ this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) : <NotFoundPage/> } { this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) : <NotFoundPage/> }
{ this.state.modal } { this.state.modal }
{ this.state.tooltip } { this.state.tooltip }
<footer>
<div className="right cap">
<a href="https://github.com/EDCD/coriolis" target="_blank" title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>
</div>
</footer>
</div>; </div>;
} }
} }

View File

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

View File

@@ -5,7 +5,7 @@ import { stopCtxPropagation } from '../utils/UtilityFunctions';
import cn from 'classnames'; import cn from 'classnames';
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons'; import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
const PRESS_THRESHOLD = 5000; // mouse/touch down threshold const PRESS_THRESHOLD = 500; // mouse/touch down threshold
/** /**
* Available modules menu * Available modules menu
@@ -93,11 +93,13 @@ export default class AvailableModulesMenu extends TranslatedComponent {
let prevClass = null, prevRating = null; let prevClass = null, prevRating = null;
let elems = []; let elems = [];
for (let i = 0; i < modules.length; i++) { const sortedModules = modules.sort(this._moduleOrder);
let m = modules[i];
for (let i = 0; i < sortedModules.length; i++) {
let m = sortedModules[i];
let mount = null; let mount = null;
let disabled = m.maxmass && (mass + (m.mass ? m.mass : 0)) > m.maxmass; let disabled = m.maxmass && (mass + (m.mass ? m.mass : 0)) > m.maxmass;
let active = mountedModule && mountedModule === m; let active = mountedModule && mountedModule.id === m.id;
let classes = cn(m.name ? 'lc' : 'c', { let classes = cn(m.name ? 'lc' : 'c', {
warning: !disabled && warningFunc && warningFunc(m), warning: !disabled && warningFunc && warningFunc(m),
active, active,
@@ -126,7 +128,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
case 'T': mount = <MountTurret className={'lg'}/>; break; case 'T': mount = <MountTurret className={'lg'}/>; break;
} }
if (i > 0 && modules.length > 3 && m.class != prevClass && (m.rating != prevRating || m.mount) && m.grp != 'pa') { if (i > 0 && sortedModules.length > 3 && m.class != prevClass && (m.rating != prevRating || m.mount) && m.grp != 'pa') {
elems.push(<br key={'b' + m.grp + i} />); elems.push(<br key={'b' + m.grp + i} />);
} }
@@ -173,6 +175,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_touchStart(showDiff, event) { _touchStart(showDiff, event) {
event.preventDefault();
let rect = event.currentTarget.getBoundingClientRect(); let rect = event.currentTarget.getBoundingClientRect();
this.touchTimeout = setTimeout(showDiff.bind(this, rect), PRESS_THRESHOLD); this.touchTimeout = setTimeout(showDiff.bind(this, rect), PRESS_THRESHOLD);
} }
@@ -183,10 +186,10 @@ export default class AvailableModulesMenu extends TranslatedComponent {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_touchEnd(select, event) { _touchEnd(select, event) {
event.preventDefault();
if (this.touchTimeout !== null) { // If timeout has not fired (been nulled out) yet if (this.touchTimeout !== null) { // If timeout has not fired (been nulled out) yet
select(); select();
} }
event.preventDefault();
this._hideDiff(); this._hideDiff();
} }
@@ -200,6 +203,46 @@ export default class AvailableModulesMenu extends TranslatedComponent {
this.context.tooltip(); this.context.tooltip();
} }
/**
* Order two modules suitably for display in module selection
* @param {Object} a the first module
* @param {Object} b the second module
* @return {int} -1 if the first module should go first, 1 if the second module should go first
*/
_moduleOrder(a, b) {
// Named modules go last
if (!a.name && b.name) {
return -1;
}
if (a.name && !b.name) {
return 1;
}
// Class ordered from highest (8) to lowest (1)
if (a.class < b.class) {
return 1;
}
if (a.class > b.class) {
return -1;
}
// Mount type, if applicable
if (a.mount && b.mount && a.mount !== b.mount) {
if (a.mount === 'F' || (a.mount === 'G' && b.mount === 'T')) {
return -1;
} else {
return 1;
}
}
// Rating ordered from lowest (E) to highest (A)
if (a.rating < b.rating) {
return 1;
}
if (a.rating > b.rating) {
return -1;
}
// Do not attempt to order by name at this point, as that mucks up the order of armour
return 0;
}
/** /**
* Scroll to mounted (if it exists) module group on mount * Scroll to mounted (if it exists) module group on mount
*/ */

View File

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

View File

@@ -2,6 +2,7 @@ import React from 'react';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import Link from './Link'; import Link from './Link';
import cn from 'classnames'; import cn from 'classnames';
import { outfitURL } from '../utils/UrlGenerators';
import { SizeMap } from '../shipyard/Constants'; import { SizeMap } from '../shipyard/Constants';
@@ -71,7 +72,7 @@ export default class ComparisonTable extends TranslatedComponent {
* @return {React.Component} Table row * @return {React.Component} Table row
*/ */
_buildRow(build, facets, formats, units) { _buildRow(build, facets, formats, units) {
let url = `/outfit/${build.id}/${build.toString()}?bn=${build.buildName}`; let url = outfitURL(build.id, build.toString(), build.buildName);
let cells = [ let cells = [
<td key='s' className='tl'><Link href={url}>{build.name}</Link></td>, <td key='s' className='tl'><Link href={url}>{build.name}</Link></td>,
<td key='bn' className='tl'><Link href={url}>{build.buildName}</Link></td> <td key='bn' className='tl'><Link href={url}>{build.buildName}</Link></td>

View File

@@ -304,8 +304,8 @@ export default class CostSection extends TranslatedComponent {
<tr className='main'> <tr className='main'>
<th colSpan='2' className='sortable le' onClick={this._sortCostBy.bind(this,'m')}> <th colSpan='2' className='sortable le' onClick={this._sortCostBy.bind(this,'m')}>
{translate('module')} {translate('module')}
{shipDiscount && <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} -${formats.pct(shipDiscount)}]`}</u>} {shipDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} -${formats.pct(shipDiscount)}]`}</u> : null}
{moduleDiscount && <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u>} {moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
</th> </th>
<th className='sortable le' onClick={this._sortCostBy.bind(this, 'cr')} >{translate('credits')}</th> <th className='sortable le' onClick={this._sortCostBy.bind(this, 'cr')} >{translate('credits')}</th>
</tr> </tr>
@@ -363,7 +363,7 @@ export default class CostSection extends TranslatedComponent {
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'buyName')}>{translate('buy')}</th> <th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'buyName')}>{translate('buy')}</th>
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'cr')}> <th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'cr')}>
{translate('net cost')} {translate('net cost')}
{moduleDiscount < 1 && <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u>} {moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
</th> </th>
</tr> </tr>
</thead> </thead>
@@ -379,7 +379,7 @@ export default class CostSection extends TranslatedComponent {
<td colSpan='4' className='lbl cap' >{translate('retrofit from')}</td> <td colSpan='4' className='lbl cap' >{translate('retrofit from')}</td>
<td className='val cen' style={{ borderRight: 'none', width: '1em' }}><u className='primary-disabled'>&#9662;</u></td> <td className='val cen' style={{ borderRight: 'none', width: '1em' }}><u className='primary-disabled'>&#9662;</u></td>
<td className='val' style={{ borderLeft:'none', padding: 0 }}> <td className='val' style={{ borderLeft:'none', padding: 0 }}>
<select style={{ width: '100%', padding: 0 }} value={retrofitName} onChange={this._onBaseRetrofitChange}> <select style={{ width: '100%', padding: 0 }} value={retrofitName || translate('Stock')} onChange={this._onBaseRetrofitChange}>
{options} {options}
</select> </select>
</td> </td>
@@ -400,7 +400,7 @@ export default class CostSection extends TranslatedComponent {
let retrofitCosts = []; let retrofitCosts = [];
let retrofitTotal = 0, i, l, item; let retrofitTotal = 0, i, l, item;
if (ship.bulkheads.index != retrofitShip.bulkheads.index) { if (ship.bulkheads.m.index != retrofitShip.bulkheads.m.index) {
item = { item = {
buyClassRating: ship.bulkheads.m.class + ship.bulkheads.m.rating, buyClassRating: ship.bulkheads.m.class + ship.bulkheads.m.rating,
buyName: ship.bulkheads.m.name, buyName: ship.bulkheads.m.name,
@@ -419,7 +419,9 @@ export default class CostSection extends TranslatedComponent {
let retroSlotGroup = retrofitShip[g]; let retroSlotGroup = retrofitShip[g];
let slotGroup = ship[g]; let slotGroup = ship[g];
for (i = 0, l = slotGroup.length; i < l; i++) { for (i = 0, l = slotGroup.length; i < l; i++) {
if (slotGroup[i].m != retroSlotGroup[i].m) { const modId = slotGroup[i].m ? slotGroup[i].m.eddbID : null;
const retroModId = retroSlotGroup[i].m ? retroSlotGroup[i].m.eddbID : null;
if (modId != retroModId) {
item = { netCost: 0, retroItem: retroSlotGroup[i] }; item = { netCost: 0, retroItem: retroSlotGroup[i] };
if (slotGroup[i].m) { if (slotGroup[i].m) {
item.buyName = slotGroup[i].m.name || slotGroup[i].m.grp; item.buyName = slotGroup[i].m.name || slotGroup[i].m.grp;
@@ -505,19 +507,19 @@ export default class CostSection extends TranslatedComponent {
scoop = true; scoop = true;
break; break;
case 'scb': case 'scb':
q = slotGroup[i].m.cells; q = slotGroup[i].m.getCells();
break; break;
case 'am': case 'am':
q = slotGroup[i].m.ammo; q = slotGroup[i].m.getAmmo();
break; break;
case 'pv': case 'pv':
srvs += slotGroup[i].m.vehicles; srvs += slotGroup[i].m.getBays();
break; break;
case 'fx': case 'hb': case 'cc': case 'pc': case 'fx': case 'hb': case 'cc': case 'pc':
limpets = ship.cargoCapacity; limpets = ship.cargoCapacity;
break; break;
default: default:
q = slotGroup[i].m.clip + slotGroup[i].m.ammo; q = slotGroup[i].m.getClip() + slotGroup[i].m.getAmmo();
} }
// Calculate ammo costs only if a cost is specified // Calculate ammo costs only if a cost is specified
if (slotGroup[i].m.ammocost > 0) { if (slotGroup[i].m.ammocost > 0) {
@@ -530,6 +532,17 @@ export default class CostSection extends TranslatedComponent {
ammoCosts.push(item); ammoCosts.push(item);
ammoTotal += item.total; ammoTotal += item.total;
} }
// Add fighters
if (slotGroup[i].m.grp === 'fh') {
item = {
m: slotGroup[i].m,
max: slotGroup[i].m.getRebuildsPerBay() * slotGroup[i].m.getBays(),
cost: slotGroup[i].m.fightercost,
total: slotGroup[i].m.getRebuildsPerBay() * slotGroup[i].m.getBays() * slotGroup[i].m.fightercost
};
ammoCosts.push(item);
ammoTotal += item.total;
}
} }
} }
} }
@@ -550,12 +563,13 @@ export default class CostSection extends TranslatedComponent {
item = { item = {
m: { name: 'SRVs', class: '', rating: '' }, m: { name: 'SRVs', class: '', rating: '' },
max: srvs, max: srvs,
cost: 6005, cost: 1030,
total: srvs * 6005 total: srvs * 1030
}; };
ammoCosts.push(item); ammoCosts.push(item);
ammoTotal += item.total; ammoTotal += item.total;
} }
// Calculate refuel costs if no scoop present // Calculate refuel costs if no scoop present
if (!scoop) { if (!scoop) {
item = { item = {
@@ -606,6 +620,7 @@ export default class CostSection extends TranslatedComponent {
} }
if (nextProps.ship != this.props.ship || nextProps.code != this.props.code) { if (nextProps.ship != this.props.ship || nextProps.code != this.props.code) {
nextProps.ship.applyDiscounts(Persist.getShipDiscount(), Persist.getModuleDiscount());
this._updateAmmoCosts(nextProps.ship); this._updateAmmoCosts(nextProps.ship);
this._updateRetrofit(nextProps.ship, retrofitShip); this._updateRetrofit(nextProps.ship, retrofitShip);
this._sortCost(nextProps.ship); this._sortCost(nextProps.ship);

View File

@@ -0,0 +1,244 @@
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import ShipSelector from './ShipSelector';
import { nameComparator } from '../utils/SlotFunctions';
import { CollapseSection, ExpandSection, MountFixed, MountGimballed, MountTurret } from './SvgIcons';
/**
* Generates an internationalization friendly weapon comparator that will
* sort by specified property (if provided) then by name/group, class, rating
* @param {function} translate Translation function
* @param {function} propComparator Optional property comparator
* @param {boolean} desc Use descending order
* @return {function} Comparator function for names
*/
export function weaponComparator(translate, propComparator, desc) {
return (a, b) => {
if (!desc) { // Flip A and B if ascending order
let t = a;
a = b;
b = t;
}
// If a property comparator is provided use it first
let diff = propComparator ? propComparator(a, b) : nameComparator(translate, a, b);
if (diff) {
return diff;
}
// Property matches so sort by name / group, then class, rating
if (a.name === b.name && a.grp === b.grp) {
if(a.class == b.class) {
return a.rating > b.rating ? 1 : -1;
}
return a.class - b.class;
}
return nameComparator(translate, a, b);
};
}
/**
* Damage against a selected ship
*/
export default class DamageDealt extends TranslatedComponent {
static PropTypes = {
ship: React.PropTypes.object.isRequired,
code: React.PropTypes.string.isRequired
};
static DEFAULT_AGAINST = Ships['anaconda'];
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this._sort = this._sort.bind(this);
this._onShipChange = this._onShipChange.bind(this);
this._onCollapseExpand = this._onCollapseExpand.bind(this);
this.state = {
predicate: 'n',
desc: true,
against: DamageDealt.DEFAULT_AGAINST,
expanded: false
};
}
/**
* Set the initial weapons state
*/
componentWillMount() {
const weapons = this._calcWeapons(this.props.ship, this.state.against);
this.setState({ weapons });
}
/**
* Set the updated weapons state if our ship changes
* @param {Object} nextProps Incoming/Next properties
* @param {Object} nextContext Incoming/Next conext
* @return {boolean} Returns true if the component should be rerendered
*/
componentWillReceiveProps(nextProps, nextContext) {
if (nextProps.code != this.props.code) {
const weapons = this._calcWeapons(this.props.ship, this.state.against);
this.setState({ weapons });
}
return true;
}
/**
* Calculate the damage dealt by a ship
* @param {Object} ship The ship which will deal the damage
* @param {Object} against The ship against which damage will be dealt
* @return {boolean} Returns the per-weapon damage
*/
_calcWeapons(ship, against) {
let weapons = [];
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].m) {
const m = ship.hardpoints[i].m;
const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
const effectiveness = m.getPiercing() >= against.properties.hardness ? 1 : m.getPiercing() / against.properties.hardness;
const effectiveDps = m.getDps() * effectiveness;
const effectiveSDps = m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) * effectiveness : effectiveDps;
weapons.push({ id: i,
mount: m.mount,
name: m.name || m.grp,
classRating,
effectiveDps,
effectiveSDps,
effectiveness });
}
}
return weapons;
}
/**
* Triggered when the collapse or expand icons are clicked
*/
_onCollapseExpand() {
this.setState({ expanded: !this.state.expanded });
}
/**
* Triggered when the ship we compare against changes
* @param {string} s the new ship ID
*/
_onShipChange(s) {
const against = Ships[s];
const weapons = this._calcWeapons(this.props.ship, against);
this.setState({ against, weapons });
}
/**
* Set the sort order and sort
* @param {string} predicate Sort predicate
*/
_sortOrder(predicate) {
let desc = this.state.desc;
if (predicate == this.state.predicate) {
desc = !desc;
} else {
desc = true;
}
this._sort(this.props.ship, predicate, desc);
this.setState({ predicate, desc });
}
/**
* Sorts the weapon list
* @param {Ship} ship Ship instance
* @param {string} predicate Sort predicate
* @param {Boolean} desc Sort order descending
*/
_sort(ship, predicate, desc) {
let comp = weaponComparator.bind(null, this.context.language.translate);
switch (predicate) {
case 'n': comp = comp(null, desc); break;
case 'edps': comp = comp((a, b) => a.effectiveDps - b.effectiveDps, desc); break;
case 'esdps': comp = comp((a, b) => a.effectiveSDps - b.effectiveSDps, desc); break;
case 'e': comp = comp((a, b) => a.effectiveness - b.effectiveness, desc); break;
}
this.state.weapons.sort(comp);
}
/**
* Render individual rows for hardpoints
* @param {Function} translate Translate function
* @param {Object} formats Localised formats map
* @return {array} The individual rows
*
*/
_renderRows(translate, formats) {
const { termtip, tooltip } = this.context;
let rows = [];
if (this.state.weapons) {
for (let i = 0; i < this.state.weapons.length; i++) {
const weapon = this.state.weapons[i];
rows.push(<tr key={weapon.id}>
<td className='ri'>
{weapon.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed className='icon'/></span> : null}
{weapon.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : null}
{weapon.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : null}
{weapon.classRating} {translate(weapon.name)}
</td>
<td className='ri'>{formats.round1(weapon.effectiveDps)}</td>
<td className='ri'>{formats.round1(weapon.effectiveSDps)}</td>
<td className='ri'>{formats.pct(weapon.effectiveness)}</td>
</tr>);
}
}
return rows;
}
/**
* Render damage dealt
* @return {React.Component} contents
*/
render() {
const { language, tooltip, termtip } = this.context;
const { formats, translate } = language;
const { expanded } = this.state;
const sortOrder = this._sortOrder;
const onCollapseExpand = this._onCollapseExpand;
return (
<span>
<h1>{translate('damage dealt against')} {expanded ? <span onClick={onCollapseExpand}><CollapseSection className='summary'/></span> : <span onClick={onCollapseExpand}><ExpandSection className='summary'/></span>}</h1>
{expanded ? <span>
<ShipSelector initial={this.state.against} currentMenu={this.props.currentMenu} onChange={this._onShipChange} />
<table className='summary' style={{ width: '100%' }}>
<thead>
<tr className='main'>
<td className='sortable' onClick={sortOrder.bind(this, 'n')}>{translate('weapon')}</td>
<td className='sortable' onClick={sortOrder.bind(this, 'edps')}>{translate('effective dps')}</td>
<td className='sortable' onClick={sortOrder.bind(this, 'esdps')}>{translate('effective sdps')}</td>
<td className='sortable' onClick={sortOrder.bind(this, 'e')}>{translate('effectiveness')}</td>
</tr>
</thead>
<tbody>
{this._renderRows(translate, formats)}
</tbody>
</table></span> : null }
</span>
);
}
}

View File

@@ -0,0 +1,276 @@
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import { Modules } from 'coriolis-data/dist';
import { nameComparator } from '../utils/SlotFunctions';
import { CollapseSection, ExpandSection, MountFixed, MountGimballed, MountTurret } from './SvgIcons';
import Module from '../shipyard/Module';
/**
* Generates an internationalization friendly weapon comparator that will
* sort by specified property (if provided) then by name/group, class, rating
* @param {function} translate Translation function
* @param {function} propComparator Optional property comparator
* @param {boolean} desc Use descending order
* @return {function} Comparator function for names
*/
export function weaponComparator(translate, propComparator, desc) {
return (a, b) => {
if (!desc) { // Flip A and B if ascending order
let t = a;
a = b;
b = t;
}
// If a property comparator is provided use it first
let diff = propComparator ? propComparator(a, b) : nameComparator(translate, a, b);
if (diff) {
return diff;
}
// Property matches so sort by name / group, then class, rating
if (a.name === b.name && a.grp === b.grp) {
if(a.class == b.class) {
return a.rating > b.rating ? 1 : -1;
}
return a.class - b.class;
}
return nameComparator(translate, a, b);
};
}
/**
* Damage received by a selected ship
*/
export default class DamageReceived extends TranslatedComponent {
static PropTypes = {
ship: React.PropTypes.object.isRequired,
code: React.PropTypes.string.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this._sort = this._sort.bind(this);
this._onCollapseExpand = this._onCollapseExpand.bind(this);
this.state = {
predicate: 'n',
desc: true,
expanded: false
};
}
/**
* Set the initial weapons state
*/
componentWillMount() {
this.setState({ weapons: this._calcWeapons(this.props.ship) });
}
/**
* Set the updated weapons state
* @param {Object} nextProps Incoming/Next properties
* @param {Object} nextContext Incoming/Next conext
* @return {boolean} Returns true if the component should be rerendered
*/
componentWillReceiveProps(nextProps, nextContext) {
this.setState({ weapons: this._calcWeapons(nextProps.ship) });
return true;
}
/**
* Calculate the damage received by a ship
* @param {Object} ship The ship which will receive the damage
* @return {boolean} Returns the per-weapon damage
*/
_calcWeapons(ship) {
let weapons = [];
for (let grp in Modules.hardpoints) {
if (Modules.hardpoints[grp][0].damage && Modules.hardpoints[grp][0].type) {
for (let mId in Modules.hardpoints[grp]) {
const m = new Module(Modules.hardpoints[grp][mId]);
const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
// Base DPS
const baseDps = m.getDps();
const baseSDps = m.getClip() ? (m.getClip() * baseDps / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : baseDps;
// Effective DPS taking in to account shield resistance
let effectivenessShields = 0;
if (m.getDamageType().indexOf('E') != -1) {
effectivenessShields += (1 - ship.shieldExplRes);
}
if (m.getDamageType().indexOf('K') != -1) {
effectivenessShields += (1 - ship.shieldKinRes);
}
if (m.getDamageType().indexOf('T') != -1) {
effectivenessShields += (1 - ship.shieldThermRes);
}
if (m.getDamageType().indexOf('A') != -1) {
effectivenessShields += 1;
}
effectivenessShields /= m.getDamageType().length;
const effectiveDpsShields = baseDps * effectivenessShields;
const effectiveSDpsShields = baseSDps * effectivenessShields;
// Effective DPS taking in to account hull hardness and resistance
let effectivenessHull = 0;
if (m.getDamageType().indexOf('E') != -1) {
effectivenessHull += (1 - ship.hullExplRes);
}
if (m.getDamageType().indexOf('K') != -1) {
effectivenessHull += (1 - ship.hullKinRes);
}
if (m.getDamageType().indexOf('T') != -1) {
effectivenessHull += (1 - ship.hullThermRes);
}
if (m.getDamageType().indexOf('A') != -1) {
effectivenessHull += 1;
}
effectivenessHull /= m.getDamageType().length;
effectivenessHull *= Math.min(m.getPiercing() / ship.hardness, 1);
const effectiveDpsHull = baseDps * effectivenessHull;
const effectiveSDpsHull = baseSDps * effectivenessHull;
weapons.push({ id: m.id,
classRating,
name: m.name || m.grp,
mount: m.mount,
effectiveDpsShields,
effectiveSDpsShields,
effectivenessShields,
effectiveDpsHull,
effectiveSDpsHull,
effectivenessHull });
}
}
}
return weapons;
}
/**
* Triggered when the collapse or expand icons are clicked
*/
_onCollapseExpand() {
this.setState({ expanded: !this.state.expanded });
}
/**
* Set the sort order and sort
* @param {string} predicate Sort predicate
*/
_sortOrder(predicate) {
let desc = this.state.desc;
if (predicate == this.state.predicate) {
desc = !desc;
} else {
desc = true;
}
this._sort(this.props.ship, predicate, desc);
this.setState({ predicate, desc });
}
/**
* Sorts the weapon list
* @param {Ship} ship Ship instance
* @param {string} predicate Sort predicate
* @param {Boolean} desc Sort order descending
*/
_sort(ship, predicate, desc) {
let comp = weaponComparator.bind(null, this.context.language.translate);
switch (predicate) {
case 'n': comp = comp(null, desc); break;
case 'edpss': comp = comp((a, b) => a.effectiveDpsShields - b.effectiveDpsShields, desc); break;
case 'esdpss': comp = comp((a, b) => a.effectiveSDpsShields - b.effectiveSDpsShields, desc); break;
case 'es': comp = comp((a, b) => a.effectivenessShields - b.effectivenessShields, desc); break;
case 'edpsh': comp = comp((a, b) => a.effectiveDpsHull - b.effectiveDpsHull, desc); break;
case 'esdpsh': comp = comp((a, b) => a.effectiveSDpsHull - b.effectiveSDpsHull, desc); break;
case 'eh': comp = comp((a, b) => a.effectivenessHull - b.effectivenessHull, desc); break;
}
this.state.weapons.sort(comp);
}
/**
* Render individual rows for weapons
* @param {Function} translate Translate function
* @param {Object} formats Localised formats map
* @return {array} The individual rows
*
*/
_renderRows(translate, formats) {
const { termtip, tooltip } = this.context;
let rows = [];
for (let i = 0; i < this.state.weapons.length; i++) {
const weapon = this.state.weapons[i];
rows.push(<tr key={weapon.id}>
<td className='ri'>
{weapon.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed className='icon'/></span> : null}
{weapon.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : null}
{weapon.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : null}
{weapon.classRating} {translate(weapon.name)}
</td>
<td>{formats.round1(weapon.effectiveDpsShields)}</td>
<td>{formats.round1(weapon.effectiveSDpsShields)}</td>
<td>{formats.pct(weapon.effectivenessShields)}</td>
<td>{formats.round1(weapon.effectiveDpsHull)}</td>
<td>{formats.round1(weapon.effectiveSDpsHull)}</td>
<td>{formats.pct(weapon.effectivenessHull)}</td>
</tr>);
}
return rows;
}
/**
* Render damage received
* @return {React.Component} contents
*/
render() {
const { language, tooltip, termtip } = this.context;
const { formats, translate } = language;
const { expanded } = this.state;
const sortOrder = this._sortOrder;
const onCollapseExpand = this._onCollapseExpand;
return (
<span>
<h1>{translate('damage received by')} {expanded ? <span onClick={onCollapseExpand}><CollapseSection className='summary'/></span> : <span onClick={onCollapseExpand}><ExpandSection className='summary'/></span>}</h1>
{expanded ? <span>
<table className='summary' style={{ width: '100%' }}>
<thead>
<tr className='main'>
<th rowSpan={2} className='sortable' onClick={sortOrder.bind(this, 'n')} >{translate('weapon')}</th>
<th colSpan={3} >{translate('against shields')}</th>
<th colSpan={3} >{translate('against hull')}</th>
</tr>
<tr>
<th className='sortable lft' onClick={sortOrder.bind(this, 'edpss')} onMouseOver={termtip.bind(null, 'dps')} onMouseOut={tooltip.bind(null, null)}>{translate('DPS')}</th>
<th className='sortable' onClick={sortOrder.bind(this, 'esdpss')} onMouseOver={termtip.bind(null, 'sdps')} onMouseOut={tooltip.bind(null, null)}>{translate('SDPS')}</th>
<th className='sortable' onClick={sortOrder.bind(this, 'es')} >{translate('effectiveness')}</th>
<th className='sortable lft' onClick={sortOrder.bind(this, 'edpsh')} onMouseOver={termtip.bind(null, 'dps')} onMouseOut={tooltip.bind(null, null)}>{translate('DPS')}</th>
<th className='sortable' onClick={sortOrder.bind(this, 'esdpsh')} onMouseOver={termtip.bind(null, 'sdps')} onMouseOut={tooltip.bind(null, null)}>{translate('SDPS')}</th>
<th className='sortable' onClick={sortOrder.bind(this, 'eh')} >{translate('effectiveness')}</th>
</tr>
</thead>
<tbody>
{this._renderRows(translate, formats)}
</tbody>
</table></span> : null }
</span>
);
}
}

View File

@@ -0,0 +1,106 @@
import React from 'react';
import cn from 'classnames';
import TranslatedComponent from './TranslatedComponent';
import { DamageKinetic, DamageThermal, DamageExplosive } from './SvgIcons';
/**
* Defence summary
*/
export default class DefenceSummary extends TranslatedComponent {
static PropTypes = {
ship: React.PropTypes.object.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
}
/**
* Render defence summary
* @return {React.Component} contents
*/
render() {
let ship = this.props.ship;
let { language, tooltip, termtip } = this.context;
let { formats, translate, units } = language;
let hide = tooltip.bind(null, null);
const shieldGenerator = ship.findShieldGenerator();
// Damage values are 1 - resistance values
return (
<span>
<h1>{translate('defence summary')}</h1>
<table className='summary' style={{ marginLeft: 'auto', marginRight: 'auto', lineHeight: '1em', backgroundColor: 'transparent' }}>
<tbody>
{ship.shield ?
<tr>
<td colSpan='4' className='summary'><h2>{translate('shields')}: {formats.int(ship.shield)} {units.MJ}</h2></td>
</tr> : null }
{ship.shield ?
<tr>
<td className='ri' onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECOVER', { cap: 0 })} onMouseLeave={hide}>{translate('recovery')}</td>
<td className='le'>{formats.time(ship.calcShieldRecovery())}</td>
<td className='ri' onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECHARGE', { cap: 0 })} onMouseLeave={hide}>{translate('recharge')}</td>
<td className='le'>{formats.time(ship.calcShieldRecharge())}</td>
</tr> : null }
{ship.shield ?
<tr>
<td className='le'>{translate('damage from')}</td>
<td className='ri'>
<span onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /></span>&nbsp;
<span onMouseOver={termtip.bind(null, translate('base') + ' ' + formats.pct1(1 - shieldGenerator.explres))} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(1 - ship.shieldExplRes)}</span>
</td>
<td className='ri'>
<span onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /></span>&nbsp;
<span onMouseOver={termtip.bind(null, translate('base') + ' ' + formats.pct1(1 - shieldGenerator.kinres))} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(1 - ship.shieldKinRes)}</span>
</td>
<td className='ri'>
<span onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /></span>&nbsp;
<span onMouseOver={termtip.bind(null, translate('base') + ' ' + formats.pct1(1 - shieldGenerator.thermres))} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(1 - ship.shieldThermRes)}</span>
</td>
</tr> : null }
{ ship.shield && ship.shieldCells ?
<tr>
<td colSpan='4'><h2>{translate('shield cells')}: {formats.int(ship.shieldCells)} {units.MJ}</h2></td>
</tr> : null }
<tr>
<td colSpan='4'><h2>{translate('armour')}: {formats.int(ship.armour)}</h2></td>
</tr>
<tr>
<td className='le'>{translate('damage from')}</td>
<td className='ri'>
<span onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /></span>&nbsp;
<span onMouseOver={termtip.bind(null, translate('base') + ' ' + formats.pct1(1 - ship.bulkheads.m.explres))} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(1 - ship.hullExplRes)}</span></td>
<td className='ri'>
<span onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /></span>&nbsp;
<span onMouseOver={termtip.bind(null, translate('base') + ' ' + formats.pct1(1 - ship.bulkheads.m.kinres))} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(1 - ship.hullKinRes)}</span>
</td>
<td className='ri'>
<span onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /></span>&nbsp;
<span onMouseOver={termtip.bind(null, translate('base') + ' ' + formats.pct1(1 - ship.bulkheads.m.thermres))} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(1 - ship.hullThermRes)}</span>
</td>
</tr>
{ship.modulearmour > 0 ?
<tr>
<td colSpan='4'><h2>{translate('module armour')}: {formats.int(ship.modulearmour)}</h2></td>
</tr> : null }
{ship.moduleprotection > 0 ?
<tr>
<td colSpan='2' className='cn'>{translate('internal protection')} {formats.pct1(ship.moduleprotection)}</td>
<td colSpan='2' className='cn'>{translate('external protection')} {formats.pct1(ship.moduleprotection / 2)}</td>
</tr> : null }
</tbody>
</table>
</span>
);
}
}

View File

@@ -1,5 +1,10 @@
import React from 'react'; import React from 'react';
import Slot from './Slot'; import Slot from './Slot';
import Persist from '../stores/Persist';
import { DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret, ListModifications, Modified } from './SvgIcons';
import { Modifications } from 'coriolis-data/dist';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
/** /**
* Hardpoint / Utility Slot * Hardpoint / Utility Slot
@@ -33,24 +38,51 @@ export default class HardpointSlot extends Slot {
*/ */
_getSlotDetails(m, translate, formats, u) { _getSlotDetails(m, translate, formats, u) {
if (m) { if (m) {
let classRating = `${m.class}${m.rating}${m.mount ? '/' + m.mount : ''}${m.missile ? m.missile : ''}`; let classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
let { drag, drop } = this.props; let { drag, drop } = this.props;
let { termtip, tooltip } = this.context;
let validMods = Modifications.validity[m.grp] || [];
let showModuleResistances = Persist.showModuleResistances();
// Modifications tooltip shows blueprint and grade, if available
let modTT = translate('modified');
if (m && m.blueprint && m.blueprint.name) {
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
if (m.blueprint.special && m.blueprint.special.id) {
modTT += ', ' + translate(m.blueprint.special.name);
}
}
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}> return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
<div className={'cb'}> <div className={'cb'}>
<div className={'l'}>{classRating + ' ' + translate(m.name || m.grp)}</div> <div className={'l'}>
<div className={'r'}>{m.mass}{u.T}</div> {m.mount && m.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed /></span> : ''}
{m.mount && m.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : ''}
{m.mount && m.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : ''}
{m.getDamageType() && m.getDamageType().match('K') ? <span onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /></span> : ''}
{m.getDamageType() && m.getDamageType().match('T') ? <span onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /></span> : ''}
{m.getDamageType() && m.getDamageType().match('E') ? <span onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /></span> : ''}
{classRating} {translate(m.name || m.grp)}{ m.mods && Object.keys(m.mods).length > 0 ? <span className='r' onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : null }
</div>
<div className={'r'}>{formats.round(m.getMass())}{u.T}</div>
</div> </div>
<div className={'cb'}> <div className={'cb'}>
{ m.damage ? <div className={'l'}>{translate('damage')}: {m.damage} { m.ssdam ? <span>({formats.int(m.ssdam)} {u.MJ})</span> : null }</div> : null } { m.getDps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'dpssdps' : 'dps')} onMouseOut={tooltip.bind(null, null)}>{translate('DPS')}: {formats.round1(m.getDps())} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) })</span> : null }</div> : null }
{ m.dps ? <div className={'l'}>{translate('DPS')}: {m.dps} { m.mjdps ? <span>({formats.int(m.mjdps)} {u.MJ})</span> : null }</div> : null } { m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'epsseps' : 'eps')} onMouseOut={tooltip.bind(null, null)}>{translate('EPS')}: {formats.round1(m.getEps())}{u.MW} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) }{u.MW})</span> : null }</div> : null }
{ m.thermload ? <div className={'l'}>{translate('T-Load')}: {m.thermload}</div> : null } { m.getHps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'hpsshps' : 'hps')} onMouseOut={tooltip.bind(null, null)}>{translate('HPS')}: {formats.round1(m.getHps())} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getHps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) })</span> : null }</div> : null }
{ m.type ? <div className={'l'}>{translate('type')}: {m.type}</div> : null } { m.getDps() && m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, 'dpe')} onMouseOut={tooltip.bind(null, null)}>{translate('DPE')}: {formats.f1(m.getDps() / m.getEps())}</div> : null }
{ m.rof ? <div className={'l'}>{translate('ROF')}: {m.rof}{u.ps}</div> : null } { m.getRoF() ? <div className={'l'} onMouseOver={termtip.bind(null, 'rof')} onMouseOut={tooltip.bind(null, null)}>{translate('ROF')}: {formats.f1(m.getRoF())}{u.ps}</div> : null }
{ m.armourpen ? <div className={'l'}>{translate('pen')}: {m.armourpen}</div> : null } { m.getRange() ? <div className={'l'}>{translate('range')} {formats.f1(m.getRange() / 1000)}{u.km}</div> : null }
{ m.shieldmul ? <div className={'l'}>+{formats.rPct(m.shieldmul)}</div> : null } { m.getShieldBoost() ? <div className={'l'}>+{formats.pct1(m.getShieldBoost())}</div> : null }
{ m.range ? <div className={'l'}>{m.range} <u>km</u></div> : null } { m.getAmmo() ? <div className={'l'}>{translate('ammunition')}: {formats.int(m.getClip())}/{formats.int(m.getAmmo())}</div> : null }
{ m.ammo >= 0 ? <div className={'l'}>{translate('ammo')}: {formats.int(m.clip)}+{formats.int(m.ammo)}</div> : null } { m.getPiercing() ? <div className={'l'}>{translate('piercing')}: {formats.int(m.getPiercing())}</div> : null }
{ m.getJitter() ? <div className={'l'}>{translate('jitter')}: {formats.f2(m.getJitter())}°</div> : null }
{ showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null }
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
{ m && validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
</div> </div>
</div>; </div>;
} else { } else {

View File

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

View File

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

View File

@@ -1,6 +1,9 @@
import React from 'react'; import React from 'react';
import Slot from './Slot'; import Slot from './Slot';
import { Infinite } from './SvgIcons'; import Persist from '../stores/Persist';
import { ListModifications, Modified } from './SvgIcons';
import { Modifications } from 'coriolis-data/dist';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
/** /**
* Internal Slot * Internal Slot
@@ -18,30 +21,54 @@ export default class InternalSlot extends Slot {
_getSlotDetails(m, translate, formats, u) { _getSlotDetails(m, translate, formats, u) {
if (m) { if (m) {
let classRating = m.class + m.rating; let classRating = m.class + m.rating;
let { drag, drop } = this.props; let { drag, drop, ship } = this.props;
let { termtip, tooltip } = this.context;
let validMods = Modifications.validity[m.grp] || [];
let showModuleResistances = Persist.showModuleResistances();
// Modifications tooltip shows blueprint and grade, if available
let modTT = translate('modified');
if (m && m.blueprint && m.blueprint.name) {
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
}
let mass = m.getMass() || m.cargo || m.fuel || 0;
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}> return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
<div className={'cb'}> <div className={'cb'}>
<div className={'l'}>{classRating + ' ' + translate(m.name || m.grp)}</div> <div className={'l'}>{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : ''}</div>
<div className={'r'}>{m.mass || m.cargo || m.fuel || 0}{u.T}</div> <div className={'r'}>{formats.round(mass)}{u.T}</div>
</div> </div>
<div className={'cb'}> <div className={'cb'}>
{ m.optmass ? <div className={'l'}>{translate('optimal mass') + ': '}{m.optmass}{u.T}</div> : null } { m.getOptMass() ? <div className={'l'}>{translate('optimal mass')}: {formats.int(m.getOptMass())}{u.T}</div> : null }
{ m.maxmass ? <div className={'l'}>{translate('max mass') + ': '}{m.maxmass}{u.T}</div> : null } { m.getMaxMass() ? <div className={'l'}>{translate('max mass')}: {formats.int(m.getMaxMass())}{u.T}</div> : null }
{ m.bins ? <div className={'l'}>{m.bins + ' '}<u>{translate('bins')}</u></div> : null } { m.bins ? <div className={'l'}>{m.bins} <u>{translate('bins')}</u></div> : null }
{ m.bays ? <div className={'l'}>{translate('bays') + ': ' + m.bays}</div> : null } { m.bays ? <div className={'l'}>{translate('bays')}: {m.bays}</div> : null }
{ m.rebuildsperbay ? <div className={'l'}>{translate('rebuildsperbay')}: {m.rebuildsperbay}</div> : null }
{ m.rate ? <div className={'l'}>{translate('rate')}: {m.rate}{u.kgs}&nbsp;&nbsp;&nbsp;{translate('refuel time')}: {formats.time(this.props.fuel * 1000 / m.rate)}</div> : null } { m.rate ? <div className={'l'}>{translate('rate')}: {m.rate}{u.kgs}&nbsp;&nbsp;&nbsp;{translate('refuel time')}: {formats.time(this.props.fuel * 1000 / m.rate)}</div> : null }
{ m.ammo ? <div className={'l'}>{translate('ammo')}: {formats.gen(m.ammo)}</div> : null } { m.getAmmo() ? <div className={'l'}>{translate('ammunition')}: {formats.gen(m.getAmmo())}</div> : null }
{ m.cells ? <div className={'l'}>{translate('cells')}: {m.cells}</div> : null } { m.cells ? <div className={'l'}>{translate('cells')}: {m.cells}</div> : null }
{ m.recharge ? <div className={'l'}>{translate('recharge')}: {m.recharge} <u>MJ</u>&nbsp;&nbsp;&nbsp;{translate('total')}: {m.cells * m.recharge}{u.MJ}</div> : null } { m.shieldreinforcement ? <div className={'l'}>{translate('shieldreinforcement')}: {formats.int(m.getShieldReinforcement())} <u>MJ</u>&nbsp;&nbsp;&nbsp;{translate('total')}: {formats.int(m.cells * m.getShieldReinforcement())}{u.MJ}</div> : null }
{ m.repair ? <div className={'l'}>{translate('repair')}: {m.repair}</div> : null } { m.repair ? <div className={'l'}>{translate('repair')}: {m.repair}</div> : null }
{ m.range ? <div className={'l'}>{translate('range')} {m.range}{u.km}</div> : null } { m.getFacingLimit() ? <div className={'l'}>{translate('facinglimit')} {formats.f1(m.getFacingLimit())}°</div> : null }
{ m.getRange() ? <div className={'l'}>{translate('range')} {formats.f2(m.getRange())}{u.km}</div> : null }
{ m.getRangeT() ? <div className={'l'}>{translate('ranget')} {formats.f1(m.getRangeT())}{u.s}</div> : null }
{ m.spinup ? <div className={'l'}>{translate('spinup')}: {formats.f1(m.spinup)}{u.s}</div> : null }
{ m.time ? <div className={'l'}>{translate('time')}: {formats.time(m.time)}</div> : null } { m.time ? <div className={'l'}>{translate('time')}: {formats.time(m.time)}</div> : null }
{ m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null } { m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
{ m.rangeLS ? <div className={'l'}>{m.rangeLS}{u.Ls}</div> : null } { m.rangeLS ? <div className={'l'}>{translate('range')}: {m.rangeLS}{u.Ls}</div> : null }
{ m.rangeLS === null ? <div className={'l'}><Infinite/>{u.Ls}</div> : null } { m.rangeLS === null ? <div className={'l'}>{u.Ls}</div> : null }
{ m.rangeRating ? <div className={'l'}>{translate('range')}: {m.rangeRating}</div> : null } { m.rangeRating ? <div className={'l'}>{translate('range')}: {m.rangeRating}</div> : null }
{ m.armouradd ? <div className={'l'}>+{m.armouradd} <u>{translate('armour')}</u></div> : null } { m.getHullReinforcement() ? <div className={'l'}>+{formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost') / 10000)} <u className='cap'>{translate('armour')}</u></div> : null }
{ m.getProtection() ? <div className={'l'}>{formats.rPct(m.getProtection())} <u className='cap'>{translate('protection')}</u></div> : null }
{ m.passengers ? <div className={'l'}>{translate('passengers')}: {m.passengers}</div> : null }
{ showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null }
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
{ m.getRegenerationRate() ? <div className='l'>{translate('regen')}: {formats.round1(m.getRegenerationRate())}{u.ps}</div> : null }
{ m.getBrokenRegenerationRate() ? <div className='l'>{translate('brokenregen')}: {formats.round1(m.getBrokenRegenerationRate())}{u.ps}</div> : null }
{ m && validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
</div> </div>
</div>; </div>;
} else { } else {

View File

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

View File

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

View File

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

View File

@@ -11,6 +11,7 @@ import * as ModuleUtils from '../shipyard/ModuleUtils';
import { fromDetailedBuild } from '../shipyard/Serializer'; import { fromDetailedBuild } from '../shipyard/Serializer';
import { Download } from './SvgIcons'; import { Download } from './SvgIcons';
import { outfitURL } from '../utils/UrlGenerators'; import { outfitURL } from '../utils/UrlGenerators';
import * as CompanionApiUtils from '../utils/CompanionApiUtils';
const textBuildRegex = new RegExp('^\\[([\\w \\-]+)\\]\n'); const textBuildRegex = new RegExp('^\\[([\\w \\-]+)\\]\n');
const lineRegex = new RegExp('^([\\dA-Z]{1,2}): (\\d)([A-I])[/]?([FGT])?([SD])? ([\\w\\- ]+)'); const lineRegex = new RegExp('^([\\dA-Z]{1,2}): (\\d)([A-I])[/]?([FGT])?([SD])? ([\\w\\- ]+)');
@@ -112,6 +113,7 @@ export default class ModalImport extends TranslatedComponent {
this._importBackup = this._importBackup.bind(this); this._importBackup = this._importBackup.bind(this);
this._importDetailedArray = this._importDetailedArray.bind(this); this._importDetailedArray = this._importDetailedArray.bind(this);
this._importTextBuild = this._importTextBuild.bind(this); this._importTextBuild = this._importTextBuild.bind(this);
this._importCompanionApiBuild = this._importCompanionApiBuild.bind(this);
this._validateImport = this._validateImport.bind(this); this._validateImport = this._validateImport.bind(this);
} }
@@ -183,6 +185,21 @@ export default class ModalImport extends TranslatedComponent {
this.setState({ builds }); this.setState({ builds });
} }
/**
* Import a build direct from the companion API
* @param {string} build JSON from the companion API information
* @throws {string} if parse/import fails
*/
_importCompanionApiBuild(build) {
const shipModel = CompanionApiUtils.shipModelFromJson(build);
const ship = CompanionApiUtils.shipFromJson(build);
let builds = {};
builds[shipModel] = {};
builds[shipModel]['Imported ' + Ships[shipModel].properties.name] = ship.toString();
this.setState({ builds, singleBuild: true });
}
/** /**
* Import a text build from ED Shipyard * Import a text build from ED Shipyard
* @param {string} buildStr Build string * @param {string} buildStr Build string
@@ -315,7 +332,11 @@ export default class ModalImport extends TranslatedComponent {
throw 'Must be an object or array!'; throw 'Must be an object or array!';
} }
if (importData instanceof Array) { // Must be detailed export json if (importData.modules != null && importData.modules.Armour != null) { // Only the companion API has this information
this._importCompanionApiBuild(importData); // Single sihp definition
} else if (importData.ship != null && importData.ship.modules != null && importData.ship.modules.Armour != null) { // Only the companion API has this information
this._importCompanionApiBuild(importData.ship); // Complete API dump
} else if (importData instanceof Array) { // Must be detailed export json
this._importDetailedArray(importData); this._importDetailedArray(importData);
} else if (importData.ship && typeof importData.name !== undefined) { // Using JSON from a single ship build export } else if (importData.ship && typeof importData.name !== undefined) { // Using JSON from a single ship build export
this._importDetailedArray([importData]); // Convert to array with singleobject this._importDetailedArray([importData]); // Convert to array with singleobject
@@ -325,6 +346,7 @@ export default class ModalImport extends TranslatedComponent {
} }
} }
} catch (e) { } catch (e) {
// console.log(e.stack);
this.setState({ errorMsg: (typeof e == 'string') ? e : 'Cannot Parse the data!' }); this.setState({ errorMsg: (typeof e == 'string') ? e : 'Cannot Parse the data!' });
return; return;
} }

View File

@@ -0,0 +1,88 @@
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 Modification 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. This comes in as a string and must be stored in state as a string,
* because it needs to allow illegal 'numbers' ('-', '1.', etc) when the user is typing
* in a value by hand
*/
_updateValue(value) {
const name = this.props.name;
let scaledValue = Math.round(Number(value) * 100);
// Limit to +1000% / -100%
if (scaledValue > 100000) {
scaledValue = 100000;
value = 1000;
}
if (scaledValue < -9999) {
scaledValue = -9999;
value = -99.99;
}
let m = this.props.m;
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;
if (name === 'type') {
// We don't show type
return null;
}
let symbol;
if (name === 'jitter') {
symbol = '°';
} else if (name !== 'burst') {
symbol = '%';
}
if (symbol) {
symbol = ' (' + symbol + ')';
}
return (
<div className={'cb'} key={name}>
<div className={'cb'}>{translate(name)}{symbol}</div>
<NumberEditor className={'cb'} style={{ width: '90%', textAlign: 'center' }} step={0.01} stepModifier={1} decimals={2} value={this.state.value} onValueChange={this._updateValue.bind(this)} />
</div>
);
}
}

View File

@@ -0,0 +1,65 @@
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() {
let { tooltip, termtip } = this.context;
return (
<div
className={cn('select', this.props.className)}
onClick={(e) => e.stopPropagation() }
onContextMenu={stopCtxPropagation}
onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')} onMouseOut={tooltip.bind(null, null)}
>
{this.state.list}
</div>
);
}
}

View File

@@ -0,0 +1,92 @@
import React from 'react';
import cn from 'classnames';
import TranslatedComponent from './TranslatedComponent';
import { DamageKinetic, DamageThermal, DamageExplosive } from './SvgIcons';
/**
* Movement summary
*/
export default class MovementSummary extends TranslatedComponent {
static PropTypes = {
ship: React.PropTypes.object.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
}
/**
* Render movement summary
* @return {React.Component} contents
*/
render() {
let ship = this.props.ship;
let { language, tooltip, termtip } = this.context;
let { formats, translate, units } = language;
let hide = tooltip.bind(null, null);
let boostMultiplier = ship.topBoost / ship.topSpeed;
return (
<span>
<h1>{translate('movement summary')}</h1>
<table style={{ marginLeft: 'auto', marginRight: 'auto', lineHeight: '1em', backgroundColor: 'transparent' }}>
<tbody>
<tr>
<td >&nbsp;</td>
<td colSpan='6'>{translate('engine pips')}</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td onMouseOver={termtip.bind(null, '4b')} onMouseOut={tooltip.bind(null, null)}>4B</td>
</tr>
<tr>
<td className='ri'>{translate('speed')} ({units['m/s']})</td>
<td className='ri'>{formats.int(ship.speeds[0])}</td>
<td className='ri'>{formats.int(ship.speeds[1])}</td>
<td className='ri'>{formats.int(ship.speeds[2])}</td>
<td className='ri'>{formats.int(ship.speeds[3])}</td>
<td className='ri'>{formats.int(ship.speeds[4])}</td>
<td className='ri'>{formats.int(ship.speeds[4] * boostMultiplier)}</td>
</tr>
<tr>
<td className='ri'>{translate('pitch')} ({units['°/s']})</td>
<td className='ri'>{formats.int(ship.pitches[0])}</td>
<td className='ri'>{formats.int(ship.pitches[1])}</td>
<td className='ri'>{formats.int(ship.pitches[2])}</td>
<td className='ri'>{formats.int(ship.pitches[3])}</td>
<td className='ri'>{formats.int(ship.pitches[4])}</td>
<td className='ri'>{formats.int(ship.pitches[4] * boostMultiplier)}</td>
</tr>
<tr>
<td className='ri'>{translate('roll')} ({units['°/s']})</td>
<td className='ri'>{formats.int(ship.rolls[0])}</td>
<td className='ri'>{formats.int(ship.rolls[1])}</td>
<td className='ri'>{formats.int(ship.rolls[2])}</td>
<td className='ri'>{formats.int(ship.rolls[3])}</td>
<td className='ri'>{formats.int(ship.rolls[4])}</td>
<td className='ri'>{formats.int(ship.rolls[4] * boostMultiplier)}</td>
</tr>
<tr>
<td className='ri'>{translate('yaw')} ({units['°/s']})</td>
<td className='ri'>{formats.int(ship.yaws[0])}</td>
<td className='ri'>{formats.int(ship.yaws[1])}</td>
<td className='ri'>{formats.int(ship.yaws[2])}</td>
<td className='ri'>{formats.int(ship.yaws[3])}</td>
<td className='ri'>{formats.int(ship.yaws[4])}</td>
<td className='ri'>{formats.int(ship.yaws[4] * boostMultiplier)}</td>
</tr>
</tbody>
</table>
</span>
);
}
}

View File

@@ -0,0 +1,70 @@
import React from 'react';
import cn from 'classnames';
import TranslatedComponent from './TranslatedComponent';
import { DamageKinetic, DamageThermal, DamageExplosive } from './SvgIcons';
/**
* Offence summary
*/
export default class OffenceSummary extends TranslatedComponent {
static PropTypes = {
ship: React.PropTypes.object.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
}
/**
* Render offence summary
* @return {React.Component} contents
*/
render() {
let ship = this.props.ship;
let { language, tooltip, termtip } = this.context;
let { formats, translate } = language;
return (
<span>
<h1>{translate('offence summary')}</h1>
<table className='summary' style={{ marginLeft: 'auto', marginRight: 'auto', lineHeight: '1em', backgroundColor: 'transparent' }}>
<tbody>
<tr>
<td colSpan='4' className='summary'><h2>{translate('dps')}: {formats.f1(ship.totalDps)}</h2></td>
</tr>
<tr>
<td className='le'>{translate('damage by')}</td>
<td className='ri' onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /> {formats.f1(ship.totalExplDps)}</td>
<td className='ri' onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /> {formats.f1(ship.totalKinDps)}</td>
<td className='ri' onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /> {formats.f1(ship.totalThermDps)}</td>
</tr>
<tr>
<td colSpan='4' className='summary'><h2>{translate('sdps')}: {formats.f1(ship.totalSDps)}</h2></td>
</tr>
<tr>
<td className='le'>{translate('damage by')}</td>
<td className='ri' onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /> {formats.f1(ship.totalExplSDps)}</td>
<td className='ri' onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /> {formats.f1(ship.totalKinSDps)}</td>
<td className='ri' onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /> {formats.f1(ship.totalThermSDps)}</td>
</tr>
<tr>
<td colSpan='4' className='summary'><h2>{translate('dpe')}: {formats.f1(ship.totalDpe)}</h2></td>
</tr>
<tr>
<td className='le'>{translate('damage by')}</td>
<td className='ri' onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /> {formats.f1(ship.totalExplDpe)}</td>
<td className='ri' onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /> {formats.f1(ship.totalKinDpe)}</td>
<td className='ri' onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /> {formats.f1(ship.totalThermDpe)}</td>
</tr>
</tbody>
</table>
</span>
);
}
}

View File

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

View File

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

View File

@@ -0,0 +1,90 @@
import React from 'react';
import cn from 'classnames';
import { Ships } from 'coriolis-data/dist';
import TranslatedComponent from './TranslatedComponent';
import { Rocket } from './SvgIcons';
/**
* Selector for ships
*/
export default class ShipSelector extends TranslatedComponent {
static PropTypes = {
initial: React.PropTypes.object.isRequired,
onChange: React.PropTypes.func.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this.state = { ship : this.props.initial };
}
/**
* Generate the ships menu
* @return {React.Component} Menu
*/
_getShipsMenu() {
const _selectShip = this._selectShip;
const _openMenu = this._openMenu;
let shipList = [];
for (let s in Ships) {
shipList.push(<div key={s} onClick={_selectShip.bind(this, s)} className='block' >{Ships[s].properties.name}</div>);
}
return shipList;
}
/**
* Handle opening the menu
* @param {string} menu The ID of the opened menu
* @param {SyntheticEvent} event Event
*/
_openMenu(menu, event) {
event.stopPropagation();
if (this.props.currentMenu == menu) {
menu = null;
}
this.context.openMenu(menu);
}
/**
* Handle selection of a ship
* @param {string} s The selected ship ID
*/
_selectShip(s) {
this.setState({ ship: Ships[s] });
this.context.openMenu(null);
this.props.onChange(s);
}
/**
* Render ship selector
* @return {React.Component} contents
*/
render() {
const currentMenu = this.props.currentMenu;
const ship = this.state.ship;
return (
<div className='shipselector'>
<div className='menu'>
<div className={cn('menu-header', { selected: currentMenu == 'wds' })} onClick={this._openMenu.bind(this, 'wds')}>
<Rocket className='warning' /><span className='menu-item-label'>{ship.properties.name}</span>
{currentMenu == 'wds' ?
<div className='menu-list quad no-wrap' onClick={ (e) => e.stopPropagation() }>
{this._getShipsMenu()}
</div> : null }
</div>
</div>
</div>
);
}
}

View File

@@ -23,22 +23,13 @@ export default class ShipSummaryTable extends TranslatedComponent {
let translate = language.translate; let translate = language.translate;
let u = language.units; let u = language.units;
let formats = language.formats; let formats = language.formats;
let round = formats.round; let { time, int, round, f1, f2, pct } = formats;
let { time, int } = formats; let sgClassNames = cn({ warning: ship.findInternalByGroup('sg') && !ship.shield, muted: !ship.findInternalByGroup('sg') });
let armourDetails = null;
let sgClassNames = cn({ warning: ship.sgSlot && !ship.shieldStrength, muted: !ship.sgSlot });
let sgRecover = '-'; let sgRecover = '-';
let sgRecharge = '-'; let sgRecharge = '-';
let hide = tooltip.bind(null, null); let hide = tooltip.bind(null, null);
if (ship.armourMultiplier > 1 || ship.armourAdded) { if (ship.shield) {
armourDetails = <u>({
(ship.armourMultiplier > 1 ? formats.rPct(ship.armourMultiplier) : '') +
(ship.armourAdded ? ' + ' + ship.armourAdded : '')
})</u>;
}
if (ship.shieldStrength) {
sgRecover = time(ship.calcShieldRecovery()); sgRecover = time(ship.calcShieldRecovery());
sgRecharge = time(ship.calcShieldRecharge()); sgRecharge = time(ship.calcShieldRecharge());
} }
@@ -47,13 +38,14 @@ export default class ShipSummaryTable extends TranslatedComponent {
<table id='summaryTable'> <table id='summaryTable'>
<thead> <thead>
<tr className='main'> <tr className='main'>
<th rowSpan={2}>{translate('size')}</th>
<th onMouseEnter={termtip.bind(null, 'maneuverability')} onMouseLeave={hide} rowSpan={2}>{translate('MNV')}</th>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !ship.canThrust() }) }>{translate('speed')}</th> <th rowSpan={2} className={ cn({ 'bg-warning-disabled': !ship.canThrust() }) }>{translate('speed')}</th>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !ship.canBoost() }) }>{translate('boost')}</th> <th rowSpan={2} className={ cn({ 'bg-warning-disabled': !ship.canBoost() }) }>{translate('boost')}</th>
<th onMouseEnter={termtip.bind(null, 'damage per second')} onMouseLeave={hide} rowSpan={2}>{translate('DPS')}</th> <th onMouseEnter={termtip.bind(null, 'damage per second')} onMouseLeave={hide} rowSpan={2}>{translate('DPS')}</th>
<th onMouseEnter={termtip.bind(null, 'energy per second')} onMouseLeave={hide} rowSpan={2}>{translate('EPS')}</th>
<th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th>
<th rowSpan={2}>{translate('hardness')}</th>
<th rowSpan={2}>{translate('armour')}</th> <th rowSpan={2}>{translate('armour')}</th>
<th colSpan={3}>{translate('shields')}</th> <th rowSpan={2}>{translate('shields')}</th>
<th colSpan={3}>{translate('mass')}</th> <th colSpan={3}>{translate('mass')}</th>
<th rowSpan={2}>{translate('cargo')}</th> <th rowSpan={2}>{translate('cargo')}</th>
<th rowSpan={2}>{translate('fuel')}</th> <th rowSpan={2}>{translate('fuel')}</th>
@@ -62,9 +54,6 @@ export default class ShipSummaryTable extends TranslatedComponent {
<th onMouseEnter={termtip.bind(null, 'mass lock factor')} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th> <th onMouseEnter={termtip.bind(null, 'mass lock factor')} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th>
</tr> </tr>
<tr> <tr>
<th className='lft'>{translate('strength')}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECOVER', { cap: 0 })} onMouseLeave={hide}>{translate('recovery')}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECHARGE', { cap: 0 })} onMouseLeave={hide}>{translate('recharge')}</th>
<th className='lft'>{translate('hull')}</th> <th className='lft'>{translate('hull')}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_UNLADEN', { cap: 0 })} onMouseLeave={hide}>{translate('unladen')}</th> <th onMouseEnter={termtip.bind(null, 'PHRASE_UNLADEN', { cap: 0 })} onMouseLeave={hide}>{translate('unladen')}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_LADEN', { cap: 0 })} onMouseLeave={hide}>{translate('laden')}</th> <th onMouseEnter={termtip.bind(null, 'PHRASE_LADEN', { cap: 0 })} onMouseLeave={hide}>{translate('laden')}</th>
@@ -78,26 +67,25 @@ export default class ShipSummaryTable extends TranslatedComponent {
</thead> </thead>
<tbody> <tbody>
<tr> <tr>
<td className='cap'>{translate(SizeMap[ship.class])}</td>
<td>{ship.agility}/10</td>
<td>{ ship.canThrust() ? <span>{int(ship.topSpeed)} {u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td> <td>{ ship.canThrust() ? <span>{int(ship.topSpeed)} {u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td>{ ship.canBoost() ? <span>{int(ship.topBoost)} {u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td> <td>{ ship.canBoost() ? <span>{int(ship.topBoost)} {u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td>{round(ship.totalDps)}</td> <td>{f1(ship.totalDps)}</td>
<td>{int(ship.armour)} {armourDetails}</td> <td>{f1(ship.totalEps)}</td>
<td className={sgClassNames}>{int(ship.shieldStrength)} {u.MJ} { ship.shieldMultiplier > 1 && ship.shieldStrength > 0 ? <u>({formats.rPct(ship.shieldMultiplier)})</u> : null }</td> <td>{f1(ship.totalHps)}</td>
<td className={sgClassNames}>{sgRecover}</td> <td>{int(ship.hardness)}</td>
<td className={sgClassNames}>{sgRecharge}</td> <td>{int(ship.armour)}</td>
<td className={sgClassNames}>{int(ship.shield)} {u.MJ}</td>
<td>{ship.hullMass} {u.T}</td> <td>{ship.hullMass} {u.T}</td>
<td>{round(ship.unladenMass)} {u.T}</td> <td>{int(ship.unladenMass)} {u.T}</td>
<td>{round(ship.ladenMass)} {u.T}</td> <td>{int(ship.ladenMass)} {u.T}</td>
<td>{round(ship.cargoCapacity)} {u.T}</td> <td>{round(ship.cargoCapacity)} {u.T}</td>
<td>{round(ship.fuelCapacity)} {u.T}</td> <td>{round(ship.fuelCapacity)} {u.T}</td>
<td>{round(ship.unladenRange)} {u.LY}</td> <td>{f2(ship.unladenRange)} {u.LY}</td>
<td>{round(ship.fullTankRange)} {u.LY}</td> <td>{f2(ship.fullTankRange)} {u.LY}</td>
<td>{round(ship.ladenRange)} {u.LY}</td> <td>{f2(ship.ladenRange)} {u.LY}</td>
<td>{round(ship.maxJumpCount)}</td> <td>{int(ship.maxJumpCount)}</td>
<td>{round(ship.unladenFastestRange)} {u.LY}</td> <td>{f2(ship.unladenFastestRange)} {u.LY}</td>
<td>{round(ship.ladenFastestRange)} {u.LY}</td> <td>{f2(ship.ladenFastestRange)} {u.LY}</td>
<td>{ship.masslock}</td> <td>{ship.masslock}</td>
</tr> </tr>
</tbody> </tbody>

View File

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

View File

@@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import { wrapCtxMenu } from '../utils/UtilityFunctions'; import { wrapCtxMenu } from '../utils/UtilityFunctions';
import { canMount } from '../utils/SlotFunctions';
import { Equalizer } from '../components/SvgIcons'; import { Equalizer } from '../components/SvgIcons';
import cn from 'classnames'; import cn from 'classnames';
@@ -76,7 +77,7 @@ export default class SlotSection extends TranslatedComponent {
_drag(originSlot, e) { _drag(originSlot, e) {
e.dataTransfer.setData('text/html', e.currentTarget); e.dataTransfer.setData('text/html', e.currentTarget);
e.dataTransfer.effectAllowed = 'all'; e.dataTransfer.effectAllowed = 'all';
this.setState({ originSlot }); this.setState({ originSlot, copy: e.getModifierState('Alt') });
this._close(); this._close();
} }
@@ -90,7 +91,9 @@ export default class SlotSection extends TranslatedComponent {
e.stopPropagation(); e.stopPropagation();
let os = this.state.originSlot; let os = this.state.originSlot;
if (os) { if (os) {
e.dataTransfer.dropEffect = os != targetSlot && targetSlot.maxClass >= os.m.class ? 'copyMove' : 'none'; // Show correct icon
const effect = this.state.copy ? 'copy' : 'move';
e.dataTransfer.dropEffect = os != targetSlot && canMount(this.props.ship, targetSlot, os.m.grp, os.m.class) ? effect : 'none';
this.setState({ targetSlot }); this.setState({ targetSlot });
} else { } else {
e.dataTransfer.dropEffect = 'none'; e.dataTransfer.dropEffect = 'none';
@@ -113,20 +116,30 @@ export default class SlotSection extends TranslatedComponent {
* the origin slot will be empty. * the origin slot will be empty.
*/ */
_drop() { _drop() {
let { originSlot, targetSlot } = this.state; let { originSlot, targetSlot, copy } = this.state;
let m = originSlot.m; let m = originSlot.m;
if (targetSlot && m && targetSlot.maxClass >= m.class) { if (copy) {
// Swap modules if possible // We want to copy the module in to the target slot
if (targetSlot.m && originSlot.maxClass >= targetSlot.m.class) { if (targetSlot && canMount(this.props.ship, targetSlot, m.grp, m.class)) {
this.props.ship.use(originSlot, targetSlot.m, true); const mCopy = m.clone();
} else { // Otherwise empty the origin slot this.props.ship.use(targetSlot, mCopy);
this.props.ship.use(originSlot, null, true); // Empty but prevent summary update this.props.onChange();
}
} else {
// We want to move the module in to the target slot, and swap back any module that was originally in the target slot
if (targetSlot && m && canMount(this.props.ship, targetSlot, m.grp, m.class)) {
// Swap modules if possible
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
}
this.props.ship.use(targetSlot, m); // update target slot
this.props.onChange();
} }
this.props.ship.use(targetSlot, m); // update target slot
this.props.onChange();
} }
this.setState({ originSlot: null, targetSlot: null }); this.setState({ originSlot: null, targetSlot: null, copy: null });
} }
/** /**
@@ -141,12 +154,12 @@ export default class SlotSection extends TranslatedComponent {
return null; return null;
} }
if (slot === originSlot) { if (slot === originSlot) {
if (targetSlot && targetSlot.m && originSlot.maxClass < targetSlot.m.class) { if (targetSlot && targetSlot.m && !canMount(this.props.ship, originSlot, targetSlot.m.grp, targetSlot.m.class)) {
return 'dropEmpty'; // Origin slot will be emptied return 'dropEmpty'; // Origin slot will be emptied
} }
return null; return null;
} }
if (originSlot.m && slot.maxClass >= originSlot.m.class) { // Eligble drop slot if (originSlot.m && canMount(this.props.ship, slot, originSlot.m.grp, originSlot.m.class)) { // Eligble drop slot
if (slot === targetSlot) { if (slot === targetSlot) {
return 'drop'; // Can drop return 'drop'; // Can drop
} }
@@ -179,7 +192,7 @@ export default class SlotSection extends TranslatedComponent {
<div id={this.sectionId} className={'group'} onDragLeave={this._dragOverNone}> <div id={this.sectionId} className={'group'} onDragLeave={this._dragOverNone}>
<div className={cn('section-menu', { selected: sectionMenuOpened })} onClick={open} onContextMenu={ctx}> <div className={cn('section-menu', { selected: sectionMenuOpened })} onClick={open} onContextMenu={ctx}>
<h1>{translate(this.sectionName)} <Equalizer/></h1> <h1>{translate(this.sectionName)} <Equalizer/></h1>
{sectionMenuOpened ? this._getSectionMenu(translate) : null } {sectionMenuOpened ? this._getSectionMenu(translate, this.props.ship) : null }
</div> </div>
{this._getSlots()} {this._getSlots()}
</div> </div>

View File

@@ -1,9 +1,14 @@
import React from 'react'; import React from 'react';
import cn from 'classnames'; import cn from 'classnames';
import Persist from '../stores/Persist';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import { jumpRange } from '../shipyard/Calculations'; import { jumpRange } from '../shipyard/Calculations';
import { diffDetails } from '../utils/SlotFunctions'; import { diffDetails } from '../utils/SlotFunctions';
import AvailableModulesMenu from './AvailableModulesMenu'; import AvailableModulesMenu from './AvailableModulesMenu';
import ModificationsMenu from './ModificationsMenu';
import { ListModifications, Modified } from './SvgIcons';
import { Modifications } from 'coriolis-data/dist';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
/** /**
* Standard Slot * Standard Slot
@@ -15,51 +20,92 @@ export default class StandardSlot extends TranslatedComponent {
modules: React.PropTypes.array.isRequired, modules: React.PropTypes.array.isRequired,
onSelect: React.PropTypes.func.isRequired, onSelect: React.PropTypes.func.isRequired,
onOpen: React.PropTypes.func.isRequired, onOpen: React.PropTypes.func.isRequired,
onChange: React.PropTypes.func.isRequired,
ship: React.PropTypes.object.isRequired, ship: React.PropTypes.object.isRequired,
selected: React.PropTypes.bool, selected: React.PropTypes.bool,
warning: React.PropTypes.func, warning: React.PropTypes.func,
}; };
/**
* Construct the slot
* @param {object} props Object properties
*/
constructor(props) {
super(props);
this._modificationsSelected = false;
}
/** /**
* Render the slot * Render the slot
* @return {React.Component} Slot component * @return {React.Component} Slot component
*/ */
render() { render() {
let { termtip, tooltip } = this.context;
let { translate, formats, units } = this.context.language; let { translate, formats, units } = this.context.language;
let { modules, slot, warning, onSelect, ladenMass, ship } = this.props; let { modules, slot, selected, warning, onSelect, onChange, ship } = this.props;
let m = slot.m; let m = slot.m;
let classRating = m.class + m.rating; let classRating = m.class + m.rating;
let menu; let menu;
let validMods = m == null ? [] : (Modifications.validity[m.grp] || []);
let showModuleResistances = Persist.showModuleResistances();
let mass = m.getMass() || m.cargo || m.fuel || 0;
if (this.props.selected) { // Modifications tooltip shows blueprint and grade, if available
menu = <AvailableModulesMenu let modTT = translate('modified');
modules={modules} if (m && m.blueprint && m.blueprint.name) {
shipMass={ship.ladenMass} modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
m={m} }
onSelect={onSelect}
warning={warning} if (!selected) {
diffDetails={diffDetails.bind(ship, this.context.language)} // 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 ( return (
<div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen}> <div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen} onContextMenu={stopCtxPropagation}>
<div className={cn('details-container', { warning: warning && warning(slot.m) })}> <div className={cn('details-container', { warning: warning && warning(slot.m) })}>
<div className={'sz'}>{slot.maxClass}</div> <div className={'sz'}>{slot.maxClass}</div>
<div> <div>
<div className='l'>{classRating + ' ' + translate(m.grp)}</div> <div className={'l'}>{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span className='r' onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : null }</div>
<div className={'r'}>{m.mass || m.fuel}{units.T}</div> <div className={'r'}>{formats.round(mass)}{units.T}</div>
<div/>
<div className={'cb'}> <div className={'cb'}>
{ m.optmass ? <div className='l'>{translate('optimal mass') + ': '}{m.optmass}{units.T}</div> : null } { m.getOptimalMass() ? <div className='l'>{translate('optimal mass')}: {formats.int(m.getOptimalMass())}{units.T}</div> : null }
{ m.maxmass ? <div className='l'>{translate('max mass') + ': '}{m.maxmass}{units.T}</div> : null } { m.getMaxMass() ? <div className='l'>{translate('max mass')}: {formats.int(m.getMaxMass())}{units.T}</div> : null }
{ m.range ? <div className='l'>{translate('range')}: {m.range}{units.km}</div> : null } { m.getRange() ? <div className='l'>{translate('range')}: {formats.f2(m.getRange())}{units.km}</div> : null }
{ m.time ? <div className='l'>{translate('time')}: {formats.time(m.time)}</div> : null } { m.time ? <div className='l'>{translate('time')}: {formats.time(m.time)}</div> : null }
{ m.eff ? <div className='l'>{translate('efficiency')}: {m.eff}</div> : null } { m.getThermalEfficiency() ? <div className='l'>{translate('efficiency')}: {formats.f2(m.getThermalEfficiency())}</div> : null }
{ m.pGen ? <div className='l'>{translate('power')}: {m.pGen}{units.MW}</div> : null } { m.getPowerGeneration() > 0 ? <div className='l'>{translate('pgen')}: {formats.f1(m.getPowerGeneration())}{units.MW}</div> : null }
{ m.maxfuel ? <div className='l'>{translate('max') + ' ' + translate('fuel') + ': '}{m.maxfuel}{units.T}</div> : null } { m.getMaxFuelPerJump() ? <div className='l'>{translate('max')} {translate('fuel')}: {formats.f1(m.getMaxFuelPerJump())}{units.T}</div> : null }
{ m.weaponcapacity ? <div className='l'>{translate('WEP')}: {m.weaponcapacity}{units.MJ} / {m.weaponrecharge}{units.MW}</div> : null } { m.getWeaponsCapacity() ? <div className='l'>{translate('WEP')}: {formats.f1(m.getWeaponsCapacity())}{units.MJ} / {formats.f1(m.getWeaponsRechargeRate())}{units.MW}</div> : null }
{ m.systemcapacity ? <div className='l'>{translate('SYS')}: {m.systemcapacity}{units.MJ} / {m.systemrecharge}{units.MW}</div> : null } { m.getSystemsCapacity() ? <div className='l'>{translate('SYS')}: {formats.f1(m.getSystemsCapacity())}{units.MJ} / {formats.f1(m.getSystemsRechargeRate())}{units.MW}</div> : null }
{ m.enginecapacity ? <div className='l'>{translate('ENG')}: {m.enginecapacity}{units.MJ} / {m.enginerecharge}{units.MW}</div> : null } { m.getEnginesCapacity() ? <div className='l'>{translate('ENG')}: {formats.f1(m.getEnginesCapacity())}{units.MJ} / {formats.f1(m.getEnginesRechargeRate())}{units.MW}</div> : null }
{ showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null }
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
{ validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
</div> </div>
</div> </div>
</div> </div>
@@ -67,4 +113,11 @@ export default class StandardSlot extends TranslatedComponent {
</div> </div>
); );
} }
/**
* Toggle the modifications flag when selecting the modifications icon
*/
_toggleModifications() {
this._modificationsSelected = !this._modificationsSelected;
}
} }

View File

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

View File

@@ -64,8 +64,8 @@ export class CoriolisLogo extends SvgIcon {
*/ */
svg() { svg() {
return <g transform='translate(1,1)'> return <g transform='translate(1,1)'>
<path stroke='#ff3b00' transform='rotate(45 15 15)' d='m4,4 l 11,-4 l 11,4 l 4,11 l -4,11 l -11,4 l -11,-4 l -4,-11 l 4,-11 l 22,0 l 0,22 l -22,0 z' strokeWidth='1' fill='#000000'/> <path stroke='#0a8bd6' transform='rotate(45 15 15)' d='m4,4 l 11,-4 l 11,4 l 4,11 l -4,11 l -11,4 l -11,-4 l -4,-11 l 4,-11 l 22,0 l 0,22 l -22,0 z' strokeWidth='1' fill='#000000'/>
<rect height='3' width='10' y='13.5' x='10' strokeWidth='1' stroke='#ff3b00'/> <rect height='3' width='10' y='13.5' x='10' strokeWidth='1' stroke='#0a8bd6'/>
</g>; </g>;
} }
} }
@@ -319,6 +319,90 @@ export class Warning extends SvgIcon {
} }
} }
/**
* Thermal damage
*/
export class DamageThermal extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <g>
<ellipse cx='100' cy='100' rx='90' ry='90' fillOpacity='0' />
<ellipse cx='100' cy='100' rx='30' ry='30' fillOpacity='1' />
<path d='M100 20v80' />
</g>;
}
}
/**
* Kinetic damage
*/
export class DamageKinetic extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <g>
<ellipse cx='100' cy='100' rx='90' ry='90' fillOpacity='0' />
<ellipse cx='62' cy='67' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='62' cy='101' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='62' cy='135' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='50' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='84' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='118' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='152' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='138' cy='67' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='138' cy='101' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='138' cy='135' rx='5' ry='5' fillOpacity='1' />
</g>;
}
}
/**
* Explosive damage
*/
export class DamageExplosive extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <g>
<ellipse cx='100' cy='100' rx='50' ry='50' fillOpacity='0' />
<ellipse cx='100' cy='20' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='156.57' cy='36.57' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='180' cy='100' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='156.57' cy='163.43' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='180' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='43.43' cy='163.43' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='20' cy='100' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='43.43' cy='36.57' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='75' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='125' cy='100' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='125' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='75' cy='100' rx='5' ry='5' fillOpacity='1' />
</g>;
}
}
/** /**
* Fixed mount hardpoint * Fixed mount hardpoint
*/ */
@@ -334,11 +418,11 @@ export class MountFixed extends SvgIcon {
*/ */
svg() { svg() {
return <g> return <g>
<circle fillOpacity='0' r='70' cy='100' cx='100' strokeWidth='5' /> <circle cx='100' cy='100' r='76' fillOpacity='0' />
<line y2='60' x2='101' y1='0' x1='101' strokeWidth='5' /> <path d='M0 100h48' />
<line y2='101' x2='200' y1='101' x1='140' strokeWidth='5' /> <path d='M152 100h48' />
<line y2='101' x2='60' y1='101' x1='0' strokeWidth='5' /> <path d='M100 0v48' />
<line y2='200' x2='101' y1='140' x1='101' strokeWidth='5' /> <path d='M100 152v48' />
</g>; </g>;
} }
} }
@@ -358,8 +442,8 @@ export class MountGimballed extends SvgIcon {
*/ */
svg() { svg() {
return <g> return <g>
<ellipse ry='25' rx='95' cy='100' cx='100' fillOpacity='0' strokeWidth='5' /> <ellipse cx='100' cy='100' rx='90' ry='25' fillOpacity='0' />
<ellipse ry='95' rx='25' cy='100' cx='100' fillOpacity='0' strokeWidth='5' /> <ellipse cx='100' cy='100' rx='20' ry='95' fillOpacity='0' />
</g>; </g>;
} }
} }
@@ -379,13 +463,64 @@ export class MountTurret extends SvgIcon {
*/ */
svg() { svg() {
return <g> return <g>
<line y2='170' x2='162' y1='170' x1='8' strokeWidth='6' /> <path d='M40 50 A 40 40 0 0 0 0 90' />
<path d='m13,138l144,0l0,-50l-27,-40l-90,0l-27,40l0,50z' id='svg_12' fillOpacity='0' strokeWidth='6' /> <path d='M40 50h40' />
<line y2='91' x2='200' y1='91' x1='159' strokeWidth='6' /> <path d='M120 90 A 40 40 0 0 0 80 50' />
<path d='M0 90v40' />
<path d='M120 90v40' />
<path d='M0 120h120' />
<path d='M120 90h80' />
<path d='M0 160h120' />
</g>; </g>;
} }
} }
/**
* Collapse section
*/
export class CollapseSection 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>
<path d='m 100,180 0,-140' />
<path d='m 100,40 25,45' />
<path d='m 100,40 -25,45' />
<path d='m 20,20 160,0' />
</g>;
}
}
/**
* Expand section
*/
export class ExpandSection 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>
<path d='m 100,20 0,140' />
<path d='m 100,160 25,-45' />
<path d='m 100,160 -25,-45' />
<path d='m 20,180 160,0' />
</g>;
}
}
/** /**
* Rocket ship * Rocket ship
*/ */
@@ -399,6 +534,71 @@ 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'; }
/**
* Render the Icon
* @return {React.Component} SVG Icon
*/
render() {
return (
<svg className={cn('modicon', this.props.className)} style={this.props.style} viewBox={this.viewBox()}>
{this.svg()}
</svg>
);
}
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <path d='M20 180l90-100-90 100zM176 40a40 40 0 1 1-27-28'/>;
}
}
/**
* Modified (engineers)
*/
export class Modified extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Render the Icon
* @return {React.Component} SVG Icon
*/
render() {
return (
<svg className={cn('modicon', this.props.className)} style={this.props.style} viewBox={this.viewBox()}>
{this.svg()}
</svg>
);
}
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <g>
<path d="M100,5L18,52.5L18,147.5L100,195L182,147.5L182,52.5L100,5Z"/>
<path d="M100,70L74,85L74,115L100,130L126,115L126,85L100,70Z"/>
</g>;
}
}
/** /**
* Hammer * Hammer
*/ */

View File

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

View File

@@ -5,6 +5,7 @@ import * as ES from './es';
import * as FR from './fr'; import * as FR from './fr';
import * as IT from './it'; import * as IT from './it';
import * as RU from './ru'; import * as RU from './ru';
import * as PL from './pl';
import d3 from 'd3'; import d3 from 'd3';
let fallbackTerms = EN.terms; let fallbackTerms = EN.terms;
@@ -23,6 +24,7 @@ export function getLanguage(langCode) {
case 'fr': lang = FR; break; case 'fr': lang = FR; break;
case 'it': lang = IT; break; case 'it': lang = IT; break;
case 'ru': lang = RU; break; case 'ru': lang = RU; break;
case 'pl': lang = PL; break;
default: default:
lang = EN; lang = EN;
} }
@@ -41,29 +43,34 @@ export function getLanguage(langCode) {
formats: { formats: {
gen, // General number format (.e.g 1,001,001.1234) gen, // General number format (.e.g 1,001,001.1234)
int: d3Locale.numberFormat(',.0f'), // Fixed to 0 decimal places (.e.g 1,001) int: d3Locale.numberFormat(',.0f'), // Fixed to 0 decimal places (.e.g 1,001)
f1: d3Locale.numberFormat(',.1f'), // Fixed to 1 decimal place (.e.g 1,001.1)
f2: d3Locale.numberFormat(',.2f'), // Fixed to 2 decimal places (.e.g 1,001.10) f2: d3Locale.numberFormat(',.2f'), // Fixed to 2 decimal places (.e.g 1,001.10)
s2: d3Locale.numberFormat('.2s'), // SI Format to 2 decimal places (.e.g 1.1k) s2: d3Locale.numberFormat('.2s'), // SI Format to 2 decimal places (.e.g 1.1k)
pct: d3Locale.numberFormat('.2%'), // % to 2 decimal places (.e.g 5.40%) pct: d3Locale.numberFormat('.2%'), // % to 2 decimal places (.e.g 5.40%)
pct1: d3Locale.numberFormat('.1%'), // % to 1 decimal places (.e.g 5.4%) pct1: d3Locale.numberFormat('.1%'), // % to 1 decimal places (.e.g 5.4%)
r1: d3Locale.numberFormat('.1r'), // Rounded to 1 significant number (.e.g 512 => 500, 4.122 => 4)
r2: d3Locale.numberFormat('.2r'), // Rounded to 2 significant numbers (.e.g 512 => 510, 4.122 => 4.1) r2: d3Locale.numberFormat('.2r'), // Rounded to 2 significant numbers (.e.g 512 => 510, 4.122 => 4.1)
rPct: d3.format('%'), // % to 0 decimal places (.e.g 5%) rPct: d3.format('%'), // % to 0 decimal places (.e.g 5%)
round1: (d) => gen(d3.round(d, 1)), // Rounded to 0-1 decimal places (.e.g 5.1, 4)
round: (d) => gen(d3.round(d, 2)), // Rounded to 0-2 decimal places (.e.g 5.12, 4.1) round: (d) => gen(d3.round(d, 2)), // Rounded to 0-2 decimal places (.e.g 5.12, 4.1)
time: (d) => (d < 0 ? '-' : '') + Math.floor(Math.abs(d) / 60) + ':' + ('00' + Math.floor(Math.abs(d) % 60)).substr(-2, 2) time: (d) => (d < 0 ? '-' : '') + Math.floor(Math.abs(d) / 60) + ':' + ('00' + Math.floor(Math.abs(d) % 60)).substr(-2, 2)
}, },
translate, translate,
units: { units: {
CR: <u>{' ' + translate('CR')}</u>, // Credits CR: <u> {translate('CR')}</u>, // Credits
kg: <u>{' ' + translate('kg')}</u>, // Kilograms kg: <u> {translate('kg')}</u>, // Kilograms
kgs: <u>{' ' + translate('kg/s')}</u>, // Kilograms per second kgs: <u> {translate('kg/s')}</u>, // Kilograms per second
km: <u>{' ' + translate('km')}</u>, // Kilometers km: <u> {translate('km')}</u>, // Kilometers
Ls: <u>{' ' + translate('Ls')}</u>, // Light Seconds Ls: <u> {translate('Ls')}</u>, // Light Seconds
LY: <u>{' ' + translate('LY')}</u>, // Light Years LY: <u> {translate('LY')}</u>, // Light Years
MJ: <u>{' ' + translate('MJ')}</u>, // Mega Joules MJ: <u> {translate('MJ')}</u>, // Mega Joules
'm/s': <u>{' ' + translate('m/s')}</u>, // Meters per second 'm/s': <u> {translate('m/s')}</u>, // Meters per second
MW: <u>{' ' + translate('MW')}</u>, // Mega Watts (same as Mega Joules per second) '°/s': <u> {translate('°/s')}</u>, // Degrees per second
MW: <u> {translate('MW')}</u>, // Mega Watts (same as Mega Joules per second)
ps: <u>{translate('/s')}</u>, // per second ps: <u>{translate('/s')}</u>, // per second
pm: <u>{translate('/min')}</u>, // per minute pm: <u>{translate('/min')}</u>, // per minute
T: <u>{' ' + translate('T')}</u>, // Metric Tons s: <u>{translate('secs')}</u>, // Seconds
T: <u> {translate('T')}</u>, // Metric Tons
} }
}; };
} }
@@ -78,5 +85,6 @@ export const Languages = {
it: 'Italiano', it: 'Italiano',
es: 'Español', es: 'Español',
fr: 'Français', fr: 'Français',
ru: 'ру́сский' ru: 'ру́сский',
pl: 'polski'
}; };

View File

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

View File

@@ -14,20 +14,24 @@ export const formats = {
}; };
export const terms = { export const terms = {
PHRASE_ALT_ALL: 'Alt + Click to fill all slots',
PHRASE_BACKUP_DESC: 'Backup of all Coriolis data to save or transfer to another browser/device', PHRASE_BACKUP_DESC: 'Backup of all Coriolis data to save or transfer to another browser/device',
PHRASE_CONFIRMATION: 'Are You Sure?', PHRASE_CONFIRMATION: 'Are you sure?',
PHRASE_EXPORT_DESC: 'A detailed JSON export of your build for use in other sites and tools', PHRASE_EXPORT_DESC: 'A detailed JSON export of your build for use in other sites and tools',
PHRASE_FASTEST_RANGE: 'Consecutive max range jumps', PHRASE_FASTEST_RANGE: 'Consecutive max range jumps',
PHRASE_IMPORT: 'Paste JSON or import here', PHRASE_IMPORT: 'Paste JSON or import here',
PHRASE_LADEN: 'Ship Mass + Fuel + Cargo', PHRASE_LADEN: 'Ship mass + fuel + cargo',
PHRASE_NO_BUILDS: 'No builds added to comparison!', PHRASE_NO_BUILDS: 'No builds added to comparison!',
PHRASE_NO_RETROCH: 'No Retrofitting changes', PHRASE_NO_RETROCH: 'No Retrofitting changes',
PHRASE_SELECT_BUILDS: 'Select Builds to Compare', PHRASE_SELECT_BUILDS: 'Select builds to compare',
PHRASE_SG_RECHARGE: 'Time from 50% to 100% Charge', PHRASE_SG_RECHARGE: 'Time from 50% to 100% charge',
PHRASE_SG_RECOVER: 'Recovery (to 50%) after collapse', PHRASE_SG_RECOVER: 'Recovery (to 50%) after collapse',
PHRASE_UNLADEN: 'Ship Mass excluding Fuel and Cargo', PHRASE_UNLADEN: 'Ship mass excluding fuel and cargo',
PHRASE_UPDATE_RDY: 'Update Available! Click to Refresh', PHRASE_UPDATE_RDY: 'Update Available! Click to refresh',
HELP_MODIFICATIONS_MENU: 'Click on a number to enter a new value, or drag along the bar for small changes',
// Other languages fallback to these values
// Only Translate to other languages if the name is different in-game // Only Translate to other languages if the name is different in-game
am: 'Auto Field-Maintenance Unit', am: 'Auto Field-Maintenance Unit',
bh: 'Bulkheads', bh: 'Bulkheads',
@@ -35,11 +39,13 @@ export const terms = {
bsg: 'Bi-Weave Shield Generator', bsg: 'Bi-Weave Shield Generator',
c: 'Cannon', c: 'Cannon',
cc: 'Collector Limpet Controller', cc: 'Collector Limpet Controller',
cm: 'Countermeasure', ch: 'Chaff Launcher',
cr: 'Cargo Rack', cr: 'Cargo Rack',
cs: 'Cargo Scanner', cs: 'Manifest Scanner',
dc: 'Docking Computer', dc: 'Docking Computer',
ec: 'Electronic Countermeasure',
fc: 'Fragment Cannon', fc: 'Fragment Cannon',
fh: 'Fighter Hangar',
fi: 'FSD Interdictor', fi: 'FSD Interdictor',
fs: 'Fuel Scoop', fs: 'Fuel Scoop',
fsd: 'Frame Shift Drive', fsd: 'Frame Shift Drive',
@@ -47,20 +53,27 @@ export const terms = {
fx: 'Fuel Transfer Limpet Controller', fx: 'Fuel Transfer Limpet Controller',
hb: 'Hatch Breaker Limpet Controller', hb: 'Hatch Breaker Limpet Controller',
hr: 'Hull Reinforcement Package', hr: 'Hull Reinforcement Package',
hs: 'Heat Sink Launcher',
kw: 'Kill Warrant Scanner', kw: 'Kill Warrant Scanner',
ls: 'Life Support', ls: 'Life Support',
mc: 'Multi-cannon', mc: 'Multi-cannon',
ml: 'Mining Laser', ml: 'Mining Laser',
mr: 'Missile Rack', mr: 'Missile Rack',
mrp: 'Module Reinforcement Package',
nl: 'Mine Launcher', nl: 'Mine Launcher',
pa: 'Plasma Accelerator', pa: 'Plasma Accelerator',
pas: 'Planetary Approach Suite', pas: 'Planetary Approach Suite',
pc: 'Prospector Limpet Controller', pc: 'Prospector Limpet Controller',
pce: 'Economy Class Passenger Cabin',
pci: 'Business Class Passenger Cabin',
pcm: 'First Class Passenger Cabin',
pcq: 'Luxury Passenger Cabin',
pd: 'power distributor', pd: 'power distributor',
pl: 'Pulse Laser', pl: 'Pulse Laser',
po: 'Point Defence',
pp: 'Power Plant', pp: 'Power Plant',
psg: 'Prismatic Shield Generator', psg: 'Prismatic Shield Generator',
pv: 'Planetary Vehicle Hanger', pv: 'Planetary Vehicle Hangar',
rf: 'Refinery', rf: 'Refinery',
rg: 'Rail Gun', rg: 'Rail Gun',
s: 'Sensors', s: 'Sensors',
@@ -71,5 +84,87 @@ export const terms = {
t: 'thrusters', t: 'thrusters',
tp: 'Torpedo Pylon', tp: 'Torpedo Pylon',
ul: 'Burst Laser', ul: 'Burst Laser',
ws: 'Frame Shift Wake Scanner' ws: 'Frame Shift Wake Scanner',
// Items on the outfitting page
// Notification of restricted slot
emptyrestricted: 'empty (restricted)',
'damage dealt against': 'Damage dealt against',
'damage received by': 'Damage received by',
'against shields': 'Against shields',
'against hull': 'Against hull',
// 'ammo' was overloaded for outfitting page and modul info, so changed to ammunition for outfitting page
ammunition: 'Ammo',
// Unit for seconds
secs: 's',
rebuildsperbay: 'Rebuilds per bay',
// Weapon, offence, defence and movement
dpe: 'Damage per MJ of energy',
dps: 'Damage per second',
sdps: 'Sustained 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)',
'damage by': 'Damage by',
'damage from': 'Damage from',
'shield cells': 'Shield cells',
'recovery': 'Recovery',
'recharge': 'Recharge',
'engine pips': 'Engine Pips',
'4b': '4 pips and boost',
'speed': 'Speed',
'pitch': 'Pitch',
'roll': 'Roll',
'yaw': 'Yaw',
'internal protection': 'Internal protection',
'external protection': 'External protection',
// Modifications
ammo: 'Ammunition maximum',
boot: 'Boot time',
brokenregen: 'Broken regeneration rate',
burst: 'Burst',
burstrof: 'Burst rate of fire',
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',
protection: 'Protection',
range: 'Range',
ranget: 'Range', // Range in time (for FSD interdictor)
regen: 'Regeneration rate',
reload: 'Reload',
rof: 'Rate of fire',
shield: 'Shield',
shieldboost: 'Shield boost',
shieldreinforcement: 'Shield reinforcement',
spinup: 'Spin up time',
syscap: 'Systems capacity',
sysrate: 'Systems recharge rate',
thermload: 'Thermal load',
thermres: 'Thermal resistance',
wepcap: 'Weapons capacity',
weprate: 'Weapons recharge rate',
}; };

View File

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

77
src/app/i18n/pl.js Normal file
View File

@@ -0,0 +1,77 @@
export const formats = {
decimal: '.',
thousands: ',',
grouping: [3],
currency: ['$', ''],
dateTime: '%a %b %e %X %Y',
date: '%m/%d/%Y',
time: '%H:%M:%S',
periods: ['AM', 'PM'],
days: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'],
shortDays: ['Nie', 'Pon', 'Wt', 'Śr', 'Czw', 'Pt', 'Sob'],
months: ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],
shortMonths: ['Sty', 'Lut', 'Mar', 'Kwi', 'Maj', 'Cze', 'Lip', 'Sie', 'Wrz', 'Paź', 'Lis', 'Gru']
};
export const terms = {
PHRASE_ALT_ALL: 'Alt + kliknięcie by wypełnić wszystkie sloty',
PHRASE_BACKUP_DESC: 'Kopia zapasowa wszystkich danych Coriolis w celu zapisu lub przeniesienia na inne urządzenie/przeglądarkę',
PHRASE_CONFIRMATION: 'Czy jesteś pewien?',
PHRASE_EXPORT_DESC: 'Szczegółowy eksport schematu w formacie JSON w celu użycia na innych stronach i narzędziach',
PHRASE_FASTEST_RANGE: 'Maksymalna ilość skoków na najwyższym zasięgu',
PHRASE_IMPORT: 'Wklej tu JSON lub importuj',
PHRASE_LADEN: 'Masa statku + paliwo + ładunek',
PHRASE_NO_BUILDS: 'Nie dodano schematu do porównania!',
PHRASE_NO_RETROCH: 'Brak zmian retrofit',
PHRASE_SELECT_BUILDS: 'Wybierz schematy do porównania',
PHRASE_SG_RECHARGE: 'Czas od 50% do 100% naładowania',
PHRASE_SG_RECOVER: 'Odnowienie (do 50%) po upadku',
PHRASE_UNLADEN: 'Masa statku z wyłączeniem paliwa i ładunku',
PHRASE_UPDATE_RDY: 'Dostępna aktualizacja! Naciśnij by odświeżyć',
// 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',
bl: 'Beam Laser',
bsg: '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 Interdictor',
fs: 'Fuel Scoop',
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: 'Planetary Approach Suite',
pc: 'Prospector Limpet Controller',
pd: 'power distributor',
pl: 'Pulse Laser',
pp: 'Power Plant',
psg: 'Prismatic Shield Generator',
pv: 'Planetary Vehicle Hangar',
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: 'Frame Shift Wake Scanner'
};

View File

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

View File

@@ -24,30 +24,23 @@ export default class AboutPage extends Page {
*/ */
renderPage() { renderPage() {
return <div className={'page'} style={{ textAlign: 'left', maxWidth: 800, margin: '0 auto' }}> return <div className={'page'} style={{ textAlign: 'left', maxWidth: 800, margin: '0 auto' }}>
<h1><CoriolisLogo style={{ marginRight: '0.4em' }} className='xl'/><span className='warning'>Coriolis</span></h1> <h1><CoriolisLogo style={{ marginRight: '0.4em' }} className='xl'/><span className='warning'>Coriolis EDCD Edition</span></h1>
<p>This is a clone of the Coriolis project, whose original author is currently unable to maintain it. This clone is maintained by the <a href="http://edcd.github.io/">EDCD community</a>.</p>
<p>To recover your builds, go to <a href='https://coriolis.io/' target='_blank'>https://coriolis.io/</a>, backup your builds (Settings / Backup), copy the text, return here and import (Settings / Import).</p>
<p>The Coriolis project was inspired by <a href='http://www.edshipyard.com/' target='_blank'>E:D Shipyard</a> and, of course, <a href='http://www.elitedangerous.com' target='_blank'>Elite Dangerous</a>. The ultimate goal of Coriolis is to provide rich features to support in-game play and planning while engaging the E:D community to support its development.</p> <p>The Coriolis project was inspired by <a href='http://www.edshipyard.com/' target='_blank'>E:D Shipyard</a> and, of course, <a href='http://www.elitedangerous.com' target='_blank'>Elite Dangerous</a>. The ultimate goal of Coriolis is to provide rich features to support in-game play and planning while engaging the E:D community to support its development.</p>
<p>Coriolis was created using assets and imagery from Elite: Dangerous, with the permission of Frontier Developments plc, for non-commercial purposes. It is not endorsed by nor reflects the views or opinions of Frontier Developments. A number of assets were sourced from <a href='http://edassets.org' target='_blank'>ED Assets</a></p>
<p>Coriolis was created using assets and imagery from Elite: Dangerous, with the permission of Frontier Developments plc, for non-commercial purposes. It is not endorsed by nor reflects the views or opinions of Frontier Developments.</p> <a style={{ display: 'block', textDecoration: 'none' }} href='https://github.com/EDCD/coriolis' target='_blank' title='Coriolis Github Project'>
<a style={{ display: 'block', textDecoration: 'none' }} href='https://github.com/cmmcleod/coriolis' target='_blank' title='Coriolis Github Project'>
<GitHub style={{ margin: '0.4em' }} className='l fg xl'/> <GitHub style={{ margin: '0.4em' }} className='l fg xl'/>
<h2 style={{ margin: 0, textDecoration: 'none' }}>Github</h2> <h2 style={{ margin: 0, textDecoration: 'none' }}>Github</h2>
github.com/cmmcleod/coriolis github.com/EDCD/coriolis
</a> </a>
<p>Coriolis is an open source project. Checkout the list of upcoming features and to-do list on github. Any and all contributions and feedback are welcome. If you encounter any bugs please report them and provide as much detail as possible.</p> <p>Coriolis is an open source project. Checkout the list of upcoming features and to-do list on github. Any and all contributions and feedback are welcome. If you encounter any bugs please report them and provide as much detail as possible.</p>
<form action='https://www.paypal.com/cgi-bin/webscr' method='post' target='_blank'>
<input type='hidden' name='cmd' value='_s-xclick' />
<input type='hidden' name='encrypted' value='-----BEGIN PKCS7-----MIIHLwYJKoZIhvcNAQcEoIIHIDCCBxwCAQExggEwMIIBLAIBADCBlDCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb20CAQAwDQYJKoZIhvcNAQEBBQAEgYCjl3XoqQ3Q+x/qS7Va1lwvF0IgUs8gBbrwj1/uEv/xFyPSB2G0kgWqiB2c/8vvfcjjyMr4nlzLUlmQ0yl1zZaeTXFciN5a+JsvaBISThIlN9UP7PXP61TVHCECtt/hBNtlOmg8/gG8khJCj8+qi81XsNAz5bEDpdahKW3fwGHD4jELMAkGBSsOAwIaBQAwgawGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQI4EVkn3RE+9qAgYhg2sTmY1Gul2yJyLYJZPRMO/PwgzEogb2NlIcshJSO+KvBea5NjjTXN2EJNqJa24h4lGA1mdrSgzTGDrVbdcnuti9+7ggn5R5s5IwEEQnN4JQx3IAqsp3UmJbti5t776Ns50nQbjA8NzxI+gwUmIvUQaVs6wC4HYXG6q8QtqUIWeVDhvbnt+H8oIIDhzCCA4MwggLsoAMCAQICAQAwDQYJKoZIhvcNAQEFBQAwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tMB4XDTA0MDIxMzEwMTMxNVoXDTM1MDIxMzEwMTMxNVowgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLUGF5UGFsIEluYy4xEzARBgNVBAsUCmxpdmVfY2VydHMxETAPBgNVBAMUCGxpdmVfYXBpMRwwGgYJKoZIhvcNAQkBFg1yZUBwYXlwYWwuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDBR07d/ETMS1ycjtkpkvjXZe9k+6CieLuLsPumsJ7QC1odNz3sJiCbs2wC0nLE0uLGaEtXynIgRqIddYCHx88pb5HTXv4SZeuv0Rqq4+axW9PLAAATU8w04qqjaSXgbGLP3NmohqM6bV9kZZwZLR/klDaQGo1u9uDb9lr4Yn+rBQIDAQABo4HuMIHrMB0GA1UdDgQWBBSWn3y7xm8XvVk/UtcKG+wQ1mSUazCBuwYDVR0jBIGzMIGwgBSWn3y7xm8XvVk/UtcKG+wQ1mSUa6GBlKSBkTCBjjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkNBMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtQYXlQYWwgSW5jLjETMBEGA1UECxQKbGl2ZV9jZXJ0czERMA8GA1UEAxQIbGl2ZV9hcGkxHDAaBgkqhkiG9w0BCQEWDXJlQHBheXBhbC5jb22CAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQCBXzpWmoBa5e9fo6ujionW1hUhPkOBakTr3YCDjbYfvJEiv/2P+IobhOGJr85+XHhN0v4gUkEDI8r2/rNk1m0GA8HKddvTjyGw/XqXa+LSTlDYkqI8OwR8GEYj4efEtcRpRYBxV8KxAW93YDWzFGvruKnnLbDAF6VR5w/cCMn5hzGCAZowggGWAgEBMIGUMIGOMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExFjAUBgNVBAcTDU1vdW50YWluIFZpZXcxFDASBgNVBAoTC1BheVBhbCBJbmMuMRMwEQYDVQQLFApsaXZlX2NlcnRzMREwDwYDVQQDFAhsaXZlX2FwaTEcMBoGCSqGSIb3DQEJARYNcmVAcGF5cGFsLmNvbQIBADAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMTUwNjA1MjEwNDQzWjAjBgkqhkiG9w0BCQQxFgQUx6Bs50H7tbYJln13pP5J7J1KiSUwDQYJKoZIhvcNAQEBBQAEgYBiOr1RX38uvwghuIZxKpjXX4LG/GoyYM6citfsBD5vjUGj0udmsamjlur+dwxJNs9dULnJO6huoTvxqxTui0Mh3n21YKoMqVE/erfNk2XygrJw9bEtW+HXjU3F+OGKR7dfD9STp2ZlvTEvZR9JRV5A/udC9/9U9eD5iLKRRwkIBg==-----END PKCS7-----' />
<input type='image' src='https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif' name='submit' alt='PayPal - Donate to Coriolis.io' style={{ border:'none' }} />
<img alt='' border='0' src='https://www.paypalobjects.com/en_US/i/scr/pixel.gif' width='1' height='1' />
</form>
<p>Help keep the lights on! Donations will be used to cover costs of running and maintaining Coriolis. Thanks for helping!</p>
<h3>Chat</h3> <h3>Chat</h3>
<p>You can chat to us on <a href='https://www.hipchat.com/gfYQiZcmy' target='_blank'>HipChat</a> and sign up to chat on all E:D HipChat groups <a href='https://elite-dangerous.hipchat.com/invite/74670/a3af7ea57008362d83d05fada09bdf84' target='_blank'>here</a>.</p> <p>You can chat to us on our <a href='https://discord.gg/0uwCh6R62aPRjk9w' target='_blank'>EDCD Discord server</a>.</p>
</div>; </div>;
} }
} }

View File

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

View File

@@ -26,7 +26,7 @@ export default class ErrorDetails extends React.Component {
if (ed) { if (ed) {
content = <div style={{ textAlign:'left', fontSize:'0.8em', width: '43em', margin: '0 auto' }}> content = <div style={{ textAlign:'left', fontSize:'0.8em', width: '43em', margin: '0 auto' }}>
<div className='cen'> <div className='cen'>
<a href='https://github.com/cmmcleod/coriolis/issues' target='_blank' title='Coriolis Github Project'>Create an issue on Github</a> <a href='https://github.com/edcd/coriolis/issues' target='_blank' title='Coriolis Github Project'>Create an issue on Github</a>
{' if this keeps happening. Add these details:'} {' if this keeps happening. Add these details:'}
</div> </div>
<div style={{ marginTop: '2em' }}> <div style={{ marginTop: '2em' }}>
@@ -42,6 +42,7 @@ export default class ErrorDetails extends React.Component {
return <div className='error'> return <div className='error'>
<h1>Jameson, we have a problem..</h1> <h1>Jameson, we have a problem..</h1>
<h1><small>{error.message}</small></h1> <h1><small>{error.message}</small></h1>
<div>Please note that this site uses Google Analytics to track performance and usage. If you are blocking cookies, for example using Ghostery, please disable blocking for this site and try again.</div>
{content} {content}
</div>; </div>;
} }

View File

@@ -8,22 +8,37 @@ import Persist from '../stores/Persist';
import Ship from '../shipyard/Ship'; import Ship from '../shipyard/Ship';
import { toDetailedBuild } from '../shipyard/Serializer'; import { toDetailedBuild } from '../shipyard/Serializer';
import { outfitURL } from '../utils/UrlGenerators'; import { outfitURL } from '../utils/UrlGenerators';
import { FloppyDisk, Bin, Switch, Download, Reload, Fuel, LinkIcon } from '../components/SvgIcons';
import { FloppyDisk, Bin, Switch, Download, Reload, Fuel } from '../components/SvgIcons';
import ShipSummaryTable from '../components/ShipSummaryTable'; import ShipSummaryTable from '../components/ShipSummaryTable';
import StandardSlotSection from '../components/StandardSlotSection'; import StandardSlotSection from '../components/StandardSlotSection';
import HardpointsSlotSection from '../components/HardpointsSlotSection'; import HardpointsSlotSection from '../components/HardpointsSlotSection';
import InternalSlotSection from '../components/InternalSlotSection'; import InternalSlotSection from '../components/InternalSlotSection';
import UtilitySlotSection from '../components/UtilitySlotSection'; import UtilitySlotSection from '../components/UtilitySlotSection';
import OffenceSummary from '../components/OffenceSummary';
import DefenceSummary from '../components/DefenceSummary';
import MovementSummary from '../components/MovementSummary';
import DamageDealt from '../components/DamageDealt';
import DamageReceived from '../components/DamageReceived';
import LineChart from '../components/LineChart'; import LineChart from '../components/LineChart';
import PowerManagement from '../components/PowerManagement'; import PowerManagement from '../components/PowerManagement';
import CostSection from '../components/CostSection'; import CostSection from '../components/CostSection';
import ModalExport from '../components/ModalExport'; import ModalExport from '../components/ModalExport';
import ModalPermalink from '../components/ModalPermalink';
import Slider from '../components/Slider'; import Slider from '../components/Slider';
const SPEED_SERIES = ['boost', '4 Pips', '2 Pips', '0 Pips']; const SPEED_SERIES = ['boost', '4 Pips', '2 Pips', '0 Pips'];
const SPEED_COLORS = ['#0088d2', '#ff8c0d', '#D26D00', '#c06400']; const SPEED_COLORS = ['#0088d2', '#ff8c0d', '#D26D00', '#c06400'];
/**
* Document Title Generator
* @param {String} shipName Ship Name
* @param {String} buildName Build Name
* @return {String} Document title
*/
function getTitle(shipName, buildName) {
return `${shipName}${buildName ? ` - ${buildName}` : ''}`;
}
/** /**
* The Outfitting Page * The Outfitting Page
*/ */
@@ -65,10 +80,11 @@ export default class OutfittingPage extends Page {
} }
let fuelCapacity = ship.fuelCapacity; let fuelCapacity = ship.fuelCapacity;
this._getTitle = getTitle.bind(this, data.properties.name);
return { return {
error: null, error: null,
title: 'Outfitting - ' + data.properties.name, title: this._getTitle(buildName),
costTab: Persist.getCostTab() || 'costs', costTab: Persist.getCostTab() || 'costs',
buildName, buildName,
newBuildName: buildName, newBuildName: buildName,
@@ -117,7 +133,7 @@ export default class OutfittingPage extends Page {
this._updateRoute(shipId, newBuildName, code); this._updateRoute(shipId, newBuildName, code);
} }
this.setState({ buildName: newBuildName, code, savedCode: code }); this.setState({ buildName: newBuildName, code, savedCode: code, title: this._getTitle(newBuildName) });
} }
/** /**
@@ -258,6 +274,13 @@ export default class OutfittingPage extends Page {
this.resizeListener.remove(); this.resizeListener.remove();
} }
/**
* Generates the short URL
*/
_genShortlink() {
this.context.showModal(<ModalPermalink url={window.location.href}/>);
}
/** /**
* Render the Page * Render the Page
* @return {React.Component} The page contents * @return {React.Component} The page contents
@@ -273,16 +296,16 @@ export default class OutfittingPage extends Page {
canSave = (newBuildName || buildName) && code !== savedCode, canSave = (newBuildName || buildName) && code !== savedCode,
canRename = buildName && newBuildName && buildName != newBuildName, canRename = buildName && newBuildName && buildName != newBuildName,
canReload = savedCode && canSave, canReload = savedCode && canSave,
hStr = ship.getHardpointsString(), hStr = ship.getHardpointsString() + '.' + ship.getModificationsString(),
sStr = ship.getStandardString(), sStr = ship.getStandardString() + '.' + ship.getModificationsString(),
iStr = ship.getInternalString(); iStr = ship.getInternalString() + '.' + ship.getModificationsString();
return ( return (
<div id='outfit' className={'page'} style={{ fontSize: (sizeRatio * 0.9) + 'em' }}> <div id='outfit' className={'page'} style={{ fontSize: (sizeRatio * 0.9) + 'em' }}>
<div id='overview'> <div id='overview'>
<h1>{ship.name}</h1> <h1>{ship.name}</h1>
<div id='build'> <div id='build'>
<input value={newBuildName} onChange={this._buildNameChange} placeholder={translate('Enter Name')} maxsize={50} /> <input value={newBuildName || ''} onChange={this._buildNameChange} placeholder={translate('Enter Name')} maxLength={50} />
<button onClick={canSave && this._saveBuild} disabled={!canSave} onMouseOver={termtip.bind(null, 'save')} onMouseOut={hide}> <button onClick={canSave && this._saveBuild} disabled={!canSave} onMouseOver={termtip.bind(null, 'save')} onMouseOut={hide}>
<FloppyDisk className='lg' /> <FloppyDisk className='lg' />
</button> </button>
@@ -301,6 +324,9 @@ export default class OutfittingPage extends Page {
<button onClick={buildName && this._exportBuild} disabled={!buildName} onMouseOver={termtip.bind(null, 'export')} onMouseOut={hide}> <button onClick={buildName && this._exportBuild} disabled={!buildName} onMouseOver={termtip.bind(null, 'export')} onMouseOut={hide}>
<Download className='lg'/> <Download className='lg'/>
</button> </button>
<button onClick={this._genShortlink} onMouseOver={termtip.bind(null, 'shortlink')} onMouseOut={hide}>
<LinkIcon className='lg' />
</button>
</div> </div>
</div> </div>
@@ -312,6 +338,18 @@ export default class OutfittingPage extends Page {
<PowerManagement ship={ship} code={code} onChange={shipUpdated} /> <PowerManagement ship={ship} code={code} onChange={shipUpdated} />
<CostSection ship={ship} buildName={buildName} code={sStr + hStr + iStr} /> <CostSection ship={ship} buildName={buildName} code={sStr + hStr + iStr} />
<div className='group third'>
<OffenceSummary ship={ship} code={code}/>
</div>
<div className='group third'>
<DefenceSummary ship={ship} code={code}/>
</div>
<div className='group third'>
<MovementSummary ship={ship} code={code}/>
</div>
<div ref='chartThird' className='group third'> <div ref='chartThird' className='group third'>
<h1>{translate('jump range')}</h1> <h1>{translate('jump range')}</h1>
<LineChart <LineChart
@@ -324,39 +362,6 @@ export default class OutfittingPage extends Page {
xLabel={translate('cargo')} xLabel={translate('cargo')}
func={state.jumpRangeChartFunc} func={state.jumpRangeChartFunc}
/> />
</div>
<div className='group third'>
<h1>{translate('total range')}</h1>
<LineChart
width={chartWidth}
xMax={ship.cargoCapacity}
yMax={ship.unladenFastestRange}
xUnit={translate('T')}
yUnit={translate('LY')}
yLabel={translate('fastest range')}
xLabel={translate('cargo')}
func={state.fastestRangeChartFunc}
/>
</div>
<div className='group third'>
<h1>{translate('speed')}</h1>
<LineChart
width={chartWidth}
xMax={ship.cargoCapacity}
yMax={ship.topBoost + 10}
xUnit={translate('T')}
yUnit={translate('m/s')}
yLabel={translate('speed')}
series={SPEED_SERIES}
colors={SPEED_COLORS}
xLabel={translate('cargo')}
func={state.speedChartFunc}
/>
</div>
<div className='group half'>
<table style={{ width: '100%', lineHeight: '1em', backgroundColor: 'transparent' }}> <table style={{ width: '100%', lineHeight: '1em', backgroundColor: 'transparent' }}>
<tbody > <tbody >
<tr> <tr>
@@ -382,7 +387,68 @@ export default class OutfittingPage extends Page {
</table> </table>
</div> </div>
<div>
<DamageDealt ship={ship} code={code} currentMenu={menu}/>
</div>
<div>
<DamageReceived ship={ship} code={code} currentMenu={menu}/>
</div>
</div> </div>
); );
} }
} }
// <div ref='chartThird' className='group third'>
// <h1>{translate('jump range')}</h1>
// <LineChart
// width={chartWidth}
// xMax={ship.cargoCapacity}
// yMax={ship.unladenRange}
// xUnit={translate('T')}
// yUnit={translate('LY')}
// yLabel={translate('jump range')}
// xLabel={translate('cargo')}
// func={state.jumpRangeChartFunc}
// />
// </div>
// <div className='group third'>
// <h1>{translate('speed')}</h1>
// <LineChart
// width={chartWidth}
// xMax={ship.cargoCapacity}
// yMax={ship.topBoost + 10}
// xUnit={translate('T')}
// yUnit={translate('m/s')}
// yLabel={translate('speed')}
// series={SPEED_SERIES}
// colors={SPEED_COLORS}
// xLabel={translate('cargo')}
// func={state.speedChartFunc}
// />
// </div>
// <div className='group half'>
// <table style={{ width: '100%', lineHeight: '1em', backgroundColor: 'transparent' }}>
// <tbody >
// <tr>
// <td style={{ verticalAlign: 'top', padding: 0, width: '2.5em' }} onMouseEnter={termtip.bind(null, 'fuel level')} onMouseLeave={hide}>
// <Fuel className='xl primary-disabled' />
// </td>
// <td>
// <Slider
// axis={true}
// onChange={this._fuelChange}
// axisUnit={translate('T')}
// percent={fuelLevel}
// max={fuelCapacity}
// scale={sizeRatio}
// onResize={onWindowResize}
// />
// </td>
// <td className='primary' style={{ width: '10em', verticalAlign: 'top', fontSize: '0.9em', textAlign: 'left' }}>
// {formats.f2(fuelLevel * fuelCapacity)}{units.T} {formats.pct1(fuelLevel)}
// </td>
// </tr>
// </tbody>
// </table>
// </div>

View File

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

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

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

View File

@@ -1,3 +1,4 @@
import Module from './Module';
/** /**
* Calculate the maximum single jump range based on mass and a specific FSD * Calculate the maximum single jump range based on mass and a specific FSD
@@ -8,7 +9,9 @@
* @return {number} Distance in Light Years * @return {number} Distance in Light Years
*/ */
export function jumpRange(mass, fsd, fuel) { export function jumpRange(mass, fsd, fuel) {
return Math.pow(Math.min(fuel === undefined ? fsd.maxfuel : fuel, fsd.maxfuel) / fsd.fuelmul, 1 / fsd.fuelpower) * fsd.optmass / mass; let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
let fsdOptimalMass = fsd instanceof Module ? fsd.getOptimalMass() : fsd.optmass;
return Math.pow(Math.min(fuel === undefined ? fsdMaxFuelPerJump : fuel, fsdMaxFuelPerJump) / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass;
} }
/** /**
@@ -20,15 +23,17 @@ export function jumpRange(mass, fsd, fuel) {
* @return {number} Distance in Light Years * @return {number} Distance in Light Years
*/ */
export function fastestRange(mass, fsd, fuel) { export function fastestRange(mass, fsd, fuel) {
let fuelRemaining = fuel % fsd.maxfuel; // Fuel left after making N max jumps let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
let jumps = Math.floor(fuel / fsd.maxfuel); let fsdOptimalMass = fsd instanceof Module ? fsd.getOptimalMass() : fsd.optmass;
let fuelRemaining = fuel % fsdMaxFuelPerJump; // Fuel left after making N max jumps
let jumps = Math.floor(fuel / fsdMaxFuelPerJump);
mass += fuelRemaining; mass += fuelRemaining;
// Going backwards, start with the last jump using the remaining fuel // Going backwards, start with the last jump using the remaining fuel
let fastestRange = fuelRemaining > 0 ? Math.pow(fuelRemaining / fsd.fuelmul, 1 / fsd.fuelpower) * fsd.optmass / mass : 0; let fastestRange = fuelRemaining > 0 ? Math.pow(fuelRemaining / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass : 0;
// For each max fuel jump, calculate the max jump range based on fuel mass left in the tank // For each max fuel jump, calculate the max jump range based on fuel mass left in the tank
for (let j = 0; j < jumps; j++) { for (let j = 0; j < jumps; j++) {
mass += fsd.maxfuel; mass += fsd.maxfuel;
fastestRange += Math.pow(fsd.maxfuel / fsd.fuelmul, 1 / fsd.fuelpower) * fsd.optmass / mass; fastestRange += Math.pow(fsdMaxFuelPerJump / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass;
} }
return fastestRange; return fastestRange;
}; };
@@ -36,49 +41,135 @@ export function fastestRange(mass, fsd, fuel) {
/** /**
* Calculate the a ships shield strength based on mass, shield generator and shield boosters used. * Calculate the a ships shield strength based on mass, shield generator and shield boosters used.
* *
* @param {number} mass Current mass of the ship * @param {number} mass Current mass of the ship
* @param {number} shields Base Shield strength MJ for ship * @param {number} baseShield Base Shield strength MJ for ship
* @param {object} sg The shield generator used * @param {object} sg The shield generator used
* @param {number} multiplier Shield multiplier for ship (1 + shield boosters if any) * @param {number} multiplier Shield multiplier for ship (1 + shield boosters if any)
* @return {number} Approximate shield strengh in MJ * @return {number} Approximate shield strengh in MJ
*/ */
export function shieldStrength(mass, shields, sg, multiplier) { export function shieldStrength(mass, baseShield, sg, multiplier) {
let opt; // sg might be a module or a template; handle either here
if (mass < sg.minmass) { let minMass = sg instanceof Module ? sg.getMinMass() : sg.minmass;
return shields * multiplier * sg.minmul; let optMass = sg instanceof Module ? sg.getOptMass() : sg.optmass;
} let maxMass = sg instanceof Module ? sg.getMaxMass() : sg.maxmass;
if (mass > sg.maxmass) { let minMul = sg instanceof Module ? sg.getMinMul() : sg.minmul;
return shields * multiplier * sg.maxmul; let optMul = sg instanceof Module ? sg.getOptMul() : sg.optmul;
} let maxMul = sg instanceof Module ? sg.getMaxMul() : sg.maxmul;
if (mass < sg.optmass) {
opt = (sg.optmass - mass) / (sg.optmass - sg.minmass); let xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass));
opt = 1 - Math.pow(1 - opt, 0.87); let exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass)));
return shields * multiplier * ((opt * sg.minmul) + ((1 - opt) * sg.optmul)); let ynorm = Math.pow(xnorm, exponent);
} else { let mul = minMul + ynorm * (maxMul - minMul);
opt = (sg.optmass - mass) / (sg.maxmass - sg.optmass);
opt = -1 + Math.pow(1 + opt, 2.425); return baseShield * mul * multiplier;
return shields * multiplier * ((-1 * opt * sg.maxmul) + ((1 + opt) * sg.optmul));
}
} }
/** /**
* Calculate the a ships speed based on mass, and thrusters. * Calculate the a ships speed based on mass, and thrusters.
* *
* @param {number} mass Current mass of the ship * @param {number} mass the mass of the ship
* @param {number} baseSpeed Base speed m/s for ship * @param {number} baseSpeed base speed m/s for ship
* @param {number} baseBoost Base boost speed m/s for ship * @param {object} thrusters The ship's thrusters
* @param {object} thrusters The Thrusters used * @param {number} engpip the multiplier per pip to engines
* @param {number} pipSpeed Speed pip multiplier * @return {array} Speed by pips
* @return {object} Approximate speed by pips
*/ */
export function speed(mass, baseSpeed, baseBoost, thrusters, pipSpeed) { export function speed(mass, baseSpeed, thrusters, engpip) {
let multiplier = mass > thrusters.maxmass ? 0 : ((1 - thrusters.M) + (thrusters.M * Math.pow(3 - (2 * Math.max(0.5, mass / thrusters.optmass)), thrusters.P))); // thrusters might be a module or a template; handle either here
let speed = baseSpeed * multiplier; const minMass = thrusters instanceof Module ? thrusters.getMinMass() : thrusters.minmass;
const optMass = thrusters instanceof Module ? thrusters.getOptMass() : thrusters.optmass;
const maxMass = thrusters instanceof Module ? thrusters.getMaxMass() : thrusters.maxmass;
const minMul = thrusters instanceof Module ? thrusters.getMinMul('speed') : (thrusters.minmulspeed ? thrusters.minmulspeed : thrusters.minmul);
const optMul = thrusters instanceof Module ? thrusters.getOptMul('speed') : (thrusters.optmulspeed ? thrusters.minmulspeed : thrusters.minmul);
const maxMul = thrusters instanceof Module ? thrusters.getMaxMul('speed') : (thrusters.maxmulspeed ? thrusters.minmulspeed : thrusters.minmul);
return { let results = normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseSpeed, engpip);
'0 Pips': speed * (1 - (pipSpeed * 4)),
'2 Pips': speed * (1 - (pipSpeed * 2)), return results;
'4 Pips': speed, }
'boost': baseBoost * multiplier
}; /**
* Calculate pitch of a ship based on mass and thrusters
* @param {number} mass the mass of the ship
* @param {number} basePitch base pitch of the ship
* @param {object} thrusters the ship's thrusters
* @param {number} engpip the multiplier per pip to engines
* @return {array} Pitch by pips
*/
export function pitch(mass, basePitch, thrusters, engpip) {
// 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('rotation') : (thrusters.minmulrotation ? thrusters.minmulrotation : thrusters.minmul);
let optMul = thrusters instanceof Module ? thrusters.getOptMul('rotation') : (thrusters.optmulrotation ? thrusters.optmulrotation : thrusters.optmul);
let maxMul = thrusters instanceof Module ? thrusters.getMaxMul('rotation') : (thrusters.maxmulrotation ? thrusters.maxmulrotation : thrusters.maxmul);
return normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, basePitch, engpip);
}
/**
* Calculate yaw of a ship based on mass and thrusters
* @param {number} mass the mass of the ship
* @param {number} baseYaw base yaw of the ship
* @param {object} thrusters the ship's thrusters
* @param {number} engpip the multiplier per pip to engines
* @return {array} Yaw by pips
*/
export function yaw(mass, baseYaw, thrusters, engpip) {
// 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('rotation') : (thrusters.minmulrotation ? thrusters.minmulrotation : thrusters.minmul);
let optMul = thrusters instanceof Module ? thrusters.getOptMul('rotation') : (thrusters.optmulrotation ? thrusters.optmulrotation : thrusters.optmul);
let maxMul = thrusters instanceof Module ? thrusters.getMaxMul('rotation') : (thrusters.maxmulrotation ? thrusters.maxmulrotation : thrusters.maxmul);
return normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseYaw, engpip);
}
/**
* Calculate roll of a ship based on mass and thrusters
* @param {number} mass the mass of the ship
* @param {number} baseRoll base roll of the ship
* @param {object} thrusters the ship's thrusters
* @param {number} engpip the multiplier per pip to engines
* @return {array} Roll by pips
*/
export function roll(mass, baseRoll, thrusters, engpip) {
// 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('rotation') : (thrusters.minmulrotation ? thrusters.minmulrotation : thrusters.minmul);
let optMul = thrusters instanceof Module ? thrusters.getOptMul('rotation') : (thrusters.optmulrotation ? thrusters.optmulrotation : thrusters.optmul);
let maxMul = thrusters instanceof Module ? thrusters.getMaxMul('rotation') : (thrusters.maxmulrotation ? thrusters.maxmulrotation : thrusters.maxmul);
return normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseRoll, engpip);
}
/**
* Normalise according to FD's calculations and return suitable values
* @param {number} minMass the minimum mass of the thrusters
* @param {number} optMass the optimum mass of the thrusters
* @param {number} maxMass the maximum mass of the thrusters
* @param {number} minMul the minimum multiplier of the thrusters
* @param {number} optMul the optimum multiplier of the thrusters
* @param {number} maxMul the maximum multiplier of the thrusters
* @param {number} mass the mass of the ship
* @param {base} base the base value from which to calculate
* @param {number} engpip the multiplier per pip to engines
* @return {array} values by pips
*/
function normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, base, engpip) {
const xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass));
const exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass)));
const ynorm = Math.pow(xnorm, exponent);
const mul = minMul + ynorm * (maxMul - minMul);
const res = base * mul;
return [res * (1 - (engpip * 4)),
res * (1 - (engpip * 3)),
res * (1 - (engpip * 2)),
res * (1 - (engpip * 1)),
res];
} }

View File

@@ -1,12 +1,4 @@
export const ArmourMultiplier = [
1, // Lightweight
1.4, // Reinforced
1.945, // Military
1.945, // Mirrored
1.945 // Reactive
];
export const SizeMap = ['', 'small', 'medium', 'large', 'capital']; export const SizeMap = ['', 'small', 'medium', 'large', 'capital'];
export const StandardArray = [ export const StandardArray = [
@@ -37,32 +29,41 @@ export const ModuleGroupToName = {
am: 'Auto Field-Maintenance Unit', am: 'Auto Field-Maintenance Unit',
bsg: 'Bi-Weave Shield Generator', bsg: 'Bi-Weave Shield Generator',
cr: 'Cargo Rack', cr: 'Cargo Rack',
fh: 'Fighter Hangar',
fi: 'Frame Shift Drive Interdictor', fi: 'Frame Shift Drive Interdictor',
hb: 'Hatch Breaker Limpet Controller', hb: 'Hatch Breaker Limpet Controller',
hr: 'Hull Reinforcement Package', hr: 'Hull Reinforcement Package',
rf: 'Refinery', rf: 'Refinery',
scb: 'Shield Cell Bank', scb: 'Shield Cell Bank',
sg: 'Shield Generator', sg: 'Shield Generator',
pv: 'Planetary Vehicle Hanger', pv: 'Planetary Vehicle Hangar',
psg: 'Prismatic Shield Generator', psg: 'Prismatic Shield Generator',
dc: 'Docking Computer', dc: 'Docking Computer',
fx: 'Fuel Transfer Limpet Controller', fx: 'Fuel Transfer Limpet Controller',
pc: 'Prospector Limpet Controller', pc: 'Prospector Limpet Controller',
pce: 'Economy Class Passenger Cabin',
pci: 'Business Class Passenger Cabin',
pcm: 'First Class Passenger Cabin',
pcq: 'Luxury Passenger Cabin',
cc: 'Collector Limpet Controller', cc: 'Collector Limpet Controller',
// Hard Points // Hard Points
bl: 'Beam Laser', bl: 'Beam Laser',
ul: 'Burst Laser', ul: 'Burst Laser',
c: 'Cannon', c: 'Cannon',
ch: 'Chaff Launcher',
cs: 'Cargo Scanner', cs: 'Cargo Scanner',
cm: 'Countermeasure', cm: 'Countermeasure',
ec: 'Electronic Countermeasure',
fc: 'Fragment Cannon', fc: 'Fragment Cannon',
hs: 'Heat Sink Launcher',
ws: 'Frame Shift Wake Scanner', ws: 'Frame Shift Wake Scanner',
kw: 'Kill Warrant Scanner', kw: 'Kill Warrant Scanner',
nl: 'Mine Launcher', nl: 'Mine Launcher',
ml: 'Mining Laser', ml: 'Mining Laser',
mr: 'Missile Rack', mr: 'Missile Rack',
pa: 'Plasma Accelerator', pa: 'Plasma Accelerator',
po: 'Point Defence',
mc: 'Multi-cannon', mc: 'Multi-cannon',
pl: 'Pulse Laser', pl: 'Pulse Laser',
rg: 'Rail Gun', rg: 'Rail Gun',
@@ -104,8 +105,9 @@ export const BulkheadNames = [
export const ShipFacets = [ export const ShipFacets = [
{ // 0 { // 0
title: 'agility', title: 'agility',
props: ['agility'], props: ['topPitch', 'topRoll', 'topYaw'],
fmt: 'int', lbls: ['pitch', 'roll', 'yaw'],
fmt: 'f1',
i: 0 i: 0
}, },
{ // 1 { // 1
@@ -124,7 +126,7 @@ export const ShipFacets = [
}, },
{ // 3 { // 3
title: 'shields', title: 'shields',
props: ['shieldStrength'], props: ['shield'],
unit: 'MJ', unit: 'MJ',
fmt: 'int', fmt: 'int',
i: 3 i: 3
@@ -184,10 +186,31 @@ export const ShipFacets = [
}, },
{ // 11 { // 11
title: 'DPS', title: 'DPS',
props: ['totalDps'], props: ['totalDps', 'totalExplDps', 'totalKinDps', 'totalThermDps'],
lbls: ['DPS'], lbls: ['total', 'explosive', 'kinetic', 'thermal'],
fmt: 'round', fmt: 'round',
i: 11 i: 11
},
{ // 14
title: 'Sustained DPS',
props: ['totalSDps', 'totalExplSDps', 'totalKinSDps', 'totalThermSDps'],
lbls: ['total', 'explosive', 'kinetic', 'thermal'],
fmt: 'round',
i: 14
},
{ // 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;
}
}

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

@@ -0,0 +1,645 @@
import * as ModuleUtils from './ModuleUtils';
import * as _ from 'lodash';
import { Modifications } from 'coriolis-data/dist';
/**
* 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);
if (properties.class != undefined) {
// We already have a fully-formed module; copy the data over
for (let p in properties) { this[p] = properties[p]; }
} else if (properties.template != undefined) {
// We have a template from coriolis-data; copy the data over
for (let p in properties.template) { this[p] = properties.template[p]; }
} else {
// We don't have a template; find it given the group and ID
return ModuleUtils.findModule(properties.grp, properties.id);
}
}
/**
* Clone an existing module
* @return {Object} A clone of the existing module
*/
clone() {
return new Module(JSON.parse(JSON.stringify(this)));
}
/**
* Get a value for a given modification
* @param {Number} name The name of the modification
* @return {object} The value of the modification. If it is a numeric value then it is returned as an integer value scaled so that 1.23% == 123
*/
getModValue(name) {
return this.mods && this.mods[name] ? this.mods[name] : null;
}
/**
* Set a value for a given modification ID
* @param {Number} name The name of the modification
* @param {object} value The value of the modification. If it is a numeric value then it should be an integer scaled so that -2.34% == -234
*/
setModValue(name, value) {
if (!this.mods) {
this.mods = {};
}
if (value == null || value == 0) {
delete this.mods[name];
} else {
if (isNaN(value)) {
this.mods[name] = value;
} else {
// Round just to be sure
this.mods[name] = Math.round(value);
}
}
}
/**
* Helper to obtain a modified value using standard multipliers
* @param {String} name the name of the modifier to obtain
* @param {Boolean} additive Optional true if the value is additive rather than multiplicative
* @return {Number} the mass of this module
*/
_getModifiedValue(name, additive) {
let result = this[name] || (additive ? 0 : null); // Additive NULL === 0
if (result != null) {
const modification = Modifications.modifications[name];
if (!modification) {
return result;
}
// We store percentages as decimals, so to get them back we need to divide by 10000. Otherwise
// we divide by 100. Both ways we end up with a value with two decimal places
let modValue;
if (modification.type === 'percentage') {
modValue = this.getModValue(name) / 10000;
} else if (modification.type === 'numeric') {
modValue = this.getModValue(name) / 100;
} else {
modValue = this.getModValue(name);
}
if (modValue) {
if (additive) {
result = result + modValue;
} else {
result = result * (1 + modValue);
}
}
} else {
if (name === 'burst') {
// Burst is special, as if it can not exist but have a modification
const modValue = this.getModValue(name) / 100;
return modValue;
} else if (name === 'burstrof') {
// Burst rate of fire is special, as if it can not exist but have a modification
const modValue = this.getModValue(name) / 100;
return modValue;
}
}
return result;
}
/**
* Return true if this is a shield generator
* @return {Boolean} if this is a shield generator
*/
isShieldGenerator() {
return (this.grp === 'sg' || this.grp === 'psg' || this.grp === 'bsg');
}
/**
* 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', true);
}
/**
* 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', true);
}
/**
* 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', true);
}
/**
* 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 protection for this module, taking in to account modifications
* @return {Number} the protection of this module
*/
getProtection() {
return this._getModifiedValue('protection');
}
/**
* 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') / 10000;
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') / 10000;
if (mult) { result = result * (1 + mult); }
}
}
return result;
}
/**
* Get the minimum multiplier for this module, taking in to account modifications
* @param {string} type the type for which we are obtaining the multiplier. Can be 'speed', 'rotation', 'acceleration', or null
* @return {Number} the minimum multiplier of this module
*/
getMinMul(type = null) {
// Modifier is optmul
let result = 0;
if (this['minmul' + type]) {
result = this['minmul' + type];
} else if (this['minmul']) {
result = this['minmul'];
}
if (result) {
let mult = this.getModValue('optmul') / 10000;
if (mult) { result = result * (1 + mult); }
}
return result;
}
/**
* Get the optimum multiplier for this module, taking in to account modifications
* @param {string} type the type for which we are obtaining the multiplier. Can be 'speed', 'rotation', 'acceleration', or null
* @return {Number} the optimum multiplier of this module
*/
getOptMul(type = null) {
// Modifier is optmul
let result = 0;
if (this['optmul' + type]) {
result = this['optmul' + type];
} else if (this['optmul']) {
result = this['optmul'];
}
if (result) {
let mult = this.getModValue('optmul') / 10000;
if (mult) { result = result * (1 + mult); }
}
return result;
}
/**
* Get the maximum multiplier for this module, taking in to account modifications
* @param {string} type the type for which we are obtaining the multiplier. Can be 'speed', 'rotation', 'acceleration', or null
* @return {Number} the maximum multiplier of this module
*/
getMaxMul(type = null) {
// Modifier is optmul
let result = 0;
if (this['maxmul' + type]) {
result = this['maxmul' + type];
} else if (this['maxmul']) {
result = this['maxmul'];
}
if (result) {
let mult = this.getModValue('optmul') / 10000;
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.roundspershot || 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.roundspershot || 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.roundspershot || 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 burst size for this module, taking in to account modifications
* @return {Number} the burst size of this module
*/
getBurst() {
return this._getModifiedValue('burst');
}
/**
* Get the burst rate of fire for this module, taking in to account modifications
* @return {Number} the burst rate of fire of this module
*/
getBurstRoF() {
return this._getModifiedValue('burstrof');
}
/**
* Get the rate of fire for this module, taking in to account modifications.
* The rate of fire is a combination value, and needs to take in to account
* bursts of fire.
* Firing goes [burst 1] [burst interval] [burst 2] [burst interval] ... [burst n] [interval]
* where 'n' is 'burst', 'burst interval' is '1/burstrof' and 'interval' is '1/rof'
* @return {Number} the rate of fire for this module
*/
getRoF() {
const burst = this.getBurst() || 1;
const burstRoF = this.getBurstRoF() || 1;
const intRoF = this._getModifiedValue('rof');
return burst / (((burst - 1) / burstRoF) + 1 / intRoF);
}
/**
* 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');
}
/**
* Get the shield reinforcement for this module, taking in to account modifications
* @return {Number} the shield reinforcement for this module
*/
getShieldReinforcement() {
return this._getModifiedValue('shieldreinforcement');
}
/**
* Get the piercing for this module, taking in to account modifications
* @return {Number} the piercing for this module
*/
getPiercing() {
return this._getModifiedValue('piercing');
}
/**
* Get the bays for this module, taking in to account modifications
* @return {Number} the bays for this module
*/
getBays() {
return this._getModifiedValue('bays');
}
/**
* Get the rebuilds per bay for this module, taking in to account modifications
* @return {Number} the rebuilds per bay for this module
*/
getRebuildsPerBay() {
return this._getModifiedValue('rebuildsperbay');
}
/**
* Get the cells for this module, taking in to account modifications
* @return {Number} the cells for this module
*/
getCells() {
return this._getModifiedValue('cells');
}
/**
* Get the jitter for this module, taking in to account modifications
* @return {Number} the jitter for this module
*/
getJitter() {
return this._getModifiedValue('jitter', true);
}
/**
* Get the damage type for this module, taking in to account modifications
* @return {string} the damage types for this module; any combination of E T and K
*/
getDamageType() {
return this.getModValue('type') || this.type;
}
}

View File

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

View File

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

View File

@@ -2,10 +2,45 @@ import { ModuleGroupToName, MountMap, BulkheadNames } from './Constants';
import { Ships } from 'coriolis-data/dist'; import { Ships } from 'coriolis-data/dist';
import Ship from './Ship'; import Ship from './Ship';
import * as ModuleUtils from './ModuleUtils'; import * as ModuleUtils from './ModuleUtils';
import * as Utils from '../utils/UtilityFunctions';
import LZString from 'lz-string'; import LZString from 'lz-string';
import { outfitURL } from '../utils/UrlGenerators';
const STANDARD = ['powerPlant', 'thrusters', 'frameShiftDrive', 'lifeSupport', 'powerDistributor', 'sensors', 'fuelTank']; const STANDARD = ['powerPlant', 'thrusters', 'frameShiftDrive', 'lifeSupport', 'powerDistributor', 'sensors', 'fuelTank'];
const STANDARD_GROUPS = { 'powerPlant': 'pp', 'thrusters': 't', 'frameShiftDrive': 'fsd', 'lifeSupport': 'ls', 'powerDistributor': 'pd', 'sensors': 's', 'fuelTank': 'ft' };
/**
* 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;
}
if (standard.m.blueprint && Object.keys(standard.m.blueprint).length > 0) {
o.blueprint = standard.m.blueprint;
}
return o;
}
return null;
}
/** /**
* Generates ship-loadout JSON Schema slot object * Generates ship-loadout JSON Schema slot object
* @param {Object} slot Slot model * @param {Object} slot Slot model
@@ -30,6 +65,13 @@ function slotToSchema(slot) {
if (slot.m.missile) { if (slot.m.missile) {
o.missile = slot.m.missile; o.missile = slot.m.missile;
} }
if (slot.m.mods && Object.keys(slot.m.mods).length > 0) {
o.modifications = slot.m.mods;
}
if (slot.m.blueprint && Object.keys(slot.m.blueprint).length > 0) {
o.blueprint = slot.m.blueprint;
}
return o; return o;
} }
return null; return null;
@@ -48,26 +90,26 @@ export function toDetailedBuild(buildName, ship) {
code = ship.toString(); code = ship.toString();
let data = { let data = {
$schema: 'http://cdn.coriolis.io/schemas/ship-loadout/3.json#', $schema: 'http://cdn.coriolis.io/schemas/ship-loadout/4.json#',
name: buildName, name: buildName,
ship: ship.name, ship: ship.name,
references: [{ references: [{
name: 'Coriolis.io', name: 'Coriolis.io',
url: `https://coriolis.io/outfit/${ship.id}/${code}?bn=${encodeURIComponent(buildName)}`, url: 'https://coriolis.edcd.io' + outfitURL(ship.id, code, buildName),
code, code,
shipId: ship.id shipId: ship.id
}], }],
components: { components: {
standard: { standard: {
bulkheads: BulkheadNames[ship.bulkheads.index], bulkheads: BulkheadNames[ship.bulkheads.m.index],
cargoHatch: { enabled: Boolean(ship.cargoHatch.enabled), priority: ship.cargoHatch.priority + 1 }, cargoHatch: { enabled: Boolean(ship.cargoHatch.enabled), priority: ship.cargoHatch.priority + 1 },
powerPlant: { class: standard[0].m.class, rating: standard[0].m.rating, enabled: Boolean(standard[0].enabled), priority: standard[0].priority + 1 }, powerPlant: standardToSchema(standard[0]),
thrusters: { class: standard[1].m.class, rating: standard[1].m.rating, enabled: Boolean(standard[1].enabled), priority: standard[1].priority + 1 }, thrusters: standardToSchema(standard[1]),
frameShiftDrive: { class: standard[2].m.class, rating: standard[2].m.rating, enabled: Boolean(standard[2].enabled), priority: standard[2].priority + 1 }, frameShiftDrive: standardToSchema(standard[2]),
lifeSupport: { class: standard[3].m.class, rating: standard[3].m.rating, enabled: Boolean(standard[3].enabled), priority: standard[3].priority + 1 }, lifeSupport: standardToSchema(standard[3]),
powerDistributor: { class: standard[4].m.class, rating: standard[4].m.rating, enabled: Boolean(standard[4].enabled), priority: standard[4].priority + 1 }, powerDistributor: standardToSchema(standard[4]),
sensors: { class: standard[5].m.class, rating: standard[5].m.rating, enabled: Boolean(standard[5].enabled), priority: standard[5].priority + 1 }, sensors: standardToSchema(standard[5]),
fuelTank: { class: standard[6].m.class, rating: standard[6].m.rating, enabled: Boolean(standard[6].enabled), priority: standard[6].priority + 1 } fuelTank: standardToSchema(standard[6])
}, },
hardpoints: hardpoints.filter(slot => slot.maxClass > 0).map(slotToSchema), hardpoints: hardpoints.filter(slot => slot.maxClass > 0).map(slotToSchema),
utility: hardpoints.filter(slot => slot.maxClass === 0).map(slotToSchema), utility: hardpoints.filter(slot => slot.maxClass === 0).map(slotToSchema),
@@ -92,54 +134,21 @@ export function toDetailedBuild(buildName, ship) {
*/ */
export function fromDetailedBuild(detailedBuild) { export function fromDetailedBuild(detailedBuild) {
let shipId = Object.keys(Ships).find((shipId) => Ships[shipId].properties.name.toLowerCase() == detailedBuild.ship.toLowerCase()); let shipId = Object.keys(Ships).find((shipId) => Ships[shipId].properties.name.toLowerCase() == detailedBuild.ship.toLowerCase());
if (!shipId) { if (!shipId) {
throw 'No such ship: ' + detailedBuild.ship; 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];
let enabled = [stn.cargoHatch && stn.cargoHatch.enabled !== undefined ? stn.cargoHatch.enabled : true];
let shipData = Ships[shipId]; let shipData = Ships[shipId];
let ship = new Ship(shipId, shipData.properties, shipData.slots); let ship = new Ship(shipId, shipData.properties, shipData.slots);
let bulkheads = ModuleUtils.bulkheadIndex(stn.bulkheads);
if (bulkheads < 0) { if (!detailedBuild.references[0] || !detailedBuild.references[0].code) {
throw 'Invalid bulkheads: ' + stn.bulkheads; throw 'Missing reference code';
} }
let standard = STANDARD.map((c) => { ship.buildFrom(detailedBuild.references[0].code);
if (!stn[c].class || !stn[c].rating) {
throw 'Invalid value for ' + c;
}
priorities.push(stn[c].priority === undefined ? 0 : stn[c].priority - 1);
enabled.push(stn[c].enabled === undefined ? true : stn[c].enabled);
return stn[c].class + stn[c].rating;
});
let internal = comps.internal.map(c => c ? ModuleUtils.findInternalId(c.group, c.class, c.rating, c.name) : 0);
let hardpoints = comps.hardpoints
.map(c => c ? ModuleUtils.findHardpointId(c.group, c.class, c.rating, c.name, MountMap[c.mount], c.missile) : 0)
.concat(comps.utility.map(c => c ? ModuleUtils.findHardpointId(c.group, c.class, c.rating, c.name, MountMap[c.mount]) : 0));
// The ordering of these arrays must match the order in which they are read in Ship.buildWith
priorities = priorities.concat(
comps.hardpoints.map(c => (!c || c.priority === undefined) ? 0 : c.priority - 1),
comps.utility.map(c => (!c || c.priority === undefined) ? 0 : c.priority - 1),
comps.internal.map(c => (!c || c.priority === undefined) ? 0 : c.priority - 1)
);
enabled = enabled.concat(
comps.hardpoints.map(c => (!c || c.enabled === undefined) ? true : c.enabled * 1),
comps.utility.map(c => (!c || c.enabled === undefined) ? true : c.enabled * 1),
comps.internal.map(c => (!c || c.enabled === undefined) ? true : c.enabled * 1)
);
ship.buildWith({ bulkheads, standard, hardpoints, internal }, priorities, enabled);
return ship; return ship;
}; }
/** /**
* Generates an array of ship-loadout JSON Schema object for export * Generates an array of ship-loadout JSON Schema object for export
@@ -178,7 +187,7 @@ export function fromComparison(name, builds, facets, predicate, desc) {
f: facets, f: facets,
p: predicate, p: predicate,
d: desc ? 1 : 0 d: desc ? 1 : 0
})).replace(/\//g, '-'); }));
}; };
/** /**
@@ -187,5 +196,5 @@ export function fromComparison(name, builds, facets, predicate, desc) {
* @return {Object} Comparison data object * @return {Object} Comparison data object
*/ */
export function toComparison(code) { export function toComparison(code) {
return JSON.parse(LZString.decompressFromBase64(code.replace(/-/g, '/'))); return JSON.parse(LZString.decompressFromBase64(Utils.fromUrlSafe(code)));
}; };

File diff suppressed because it is too large Load Diff

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

@@ -11,6 +11,7 @@ const LS_KEY_MOD_DISCOUNT = 'moduleDiscount';
const LS_KEY_STATE = 'state'; const LS_KEY_STATE = 'state';
const LS_KEY_SIZE_RATIO = 'sizeRatio'; const LS_KEY_SIZE_RATIO = 'sizeRatio';
const LS_KEY_TOOLTIPS = 'tooltips'; const LS_KEY_TOOLTIPS = 'tooltips';
const LS_KEY_MODULE_RESISTANCES = 'moduleResistances';
let LS; let LS;
@@ -81,6 +82,7 @@ export class Persist extends EventEmitter {
LS = null; LS = null;
} }
let moduleResistances = _get(LS_KEY_MODULE_RESISTANCES);
let tips = _get(LS_KEY_TOOLTIPS); let tips = _get(LS_KEY_TOOLTIPS);
let insurance = _getString(LS_KEY_INSURANCE); let insurance = _getString(LS_KEY_INSURANCE);
let shipDiscount = _get(LS_KEY_SHIP_DISCOUNT); let shipDiscount = _get(LS_KEY_SHIP_DISCOUNT);
@@ -99,6 +101,7 @@ export class Persist extends EventEmitter {
this.state = _get(LS_KEY_STATE); this.state = _get(LS_KEY_STATE);
this.sizeRatio = _get(LS_KEY_SIZE_RATIO) || 1; this.sizeRatio = _get(LS_KEY_SIZE_RATIO) || 1;
this.tooltipsEnabled = tips === null ? true : tips; this.tooltipsEnabled = tips === null ? true : tips;
this.moduleResistancesEnabled = moduleResistances === null ? true : moduleResistances;
if (LS) { if (LS) {
window.addEventListener('storage', this.onStorageChange); window.addEventListener('storage', this.onStorageChange);
@@ -143,6 +146,10 @@ export class Persist extends EventEmitter {
this.tooltipsEnabled = !!newValue && newValue.toLowerCase() == 'true'; this.tooltipsEnabled = !!newValue && newValue.toLowerCase() == 'true';
this.emit('tooltips', this.tooltipsEnabled); this.emit('tooltips', this.tooltipsEnabled);
break; break;
case LS_KEY_MODULE_RESISTANCES:
this.moduleResistancesEnabled = !!newValue && newValue.toLowerCase() == 'true';
this.emit('moduleresistances', this.moduleResistancesEnabled);
break;
} }
} catch (e) { } catch (e) {
// On JSON.Parse Error - don't sync or do anything // On JSON.Parse Error - don't sync or do anything
@@ -183,6 +190,21 @@ export class Persist extends EventEmitter {
return this.tooltipsEnabled; return this.tooltipsEnabled;
} }
/**
* Show module resistances setting
* @param {boolean} show Optional - update setting
* @return {boolean} True if module resistances should be shown
*/
showModuleResistances(show) {
if (show !== undefined) {
this.moduleResistancesEnabled = !!show;
_put(LS_KEY_MODULE_RESISTANCES, this.moduleResistancesEnabled);
this.emit('moduleresistances', this.moduleResistancesEnabled);
}
return this.moduleResistancesEnabled;
}
/** /**
* Persist a ship build in local storage. * Persist a ship build in local storage.
* *

View File

@@ -0,0 +1,424 @@
import React from 'react';
import { Modifications, Modules, Ships } from 'coriolis-data/dist';
import Module from '../shipyard/Module';
import Ship from '../shipyard/Ship';
// mapping from fd's ship model names to coriolis'
const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
'Adder': 'adder',
'Anaconda': 'anaconda',
'Asp': 'asp',
'Asp_Scout': 'asp_scout',
'BelugaLiner': 'beluga',
'CobraMkIII': 'cobra_mk_iii',
'CobraMkIV': 'cobra_mk_iv',
'Cutter': 'imperial_cutter',
'DiamondBackXL': 'diamondback_explorer',
'DiamondBack': 'diamondback',
'Eagle': 'eagle',
'Empire_Courier': 'imperial_courier',
'Empire_Eagle': 'imperial_eagle',
'Empire_Trader': 'imperial_clipper',
'Federation_Corvette': 'federal_corvette',
'Federation_Dropship': 'federal_dropship',
'Federation_Dropship_MkII': 'federal_assault_ship',
'Federation_Gunship': 'federal_gunship',
'FerDeLance': 'fer_de_lance',
'Hauler': 'hauler',
'Independant_Trader': 'keelback',
'Orca': 'orca',
'Python': 'python',
'SideWinder': 'sidewinder',
'Type6': 'type_6_transporter',
'Type7': 'type_7_transport',
'Type9': 'type_9_heavy',
'Viper': 'viper',
'Viper_MkIV': 'viper_mk_iv',
'Vulture': 'vulture'
};
// Mapping from hardpoint class to name in companion API
const HARDPOINT_NUM_TO_CLASS = {
0: 'Tiny',
1: 'Small',
2: 'Medium',
3: 'Large',
4: 'Huge'
};
/**
* Obtain a module given its ED ID
* @param {Integer} edId the Elite ID of the module
* @return {Module} the module
*/
function _moduleFromEdId(edId) {
if (!edId) return null;
// Check standard modules
for (const grp in Modules.standard) {
if (Modules.standard.hasOwnProperty(grp)) {
for (const i in Modules.standard[grp]) {
if (Modules.standard[grp][i].edID === edId) {
// Found it
return new Module({ template: Modules.standard[grp][i] });
}
}
}
}
// Check hardpoint modules
for (const grp in Modules.hardpoints) {
if (Modules.hardpoints.hasOwnProperty(grp)) {
for (const i in Modules.hardpoints[grp]) {
if (Modules.hardpoints[grp][i].edID === edId) {
// Found it
return new Module({ template: Modules.hardpoints[grp][i] });
}
}
}
}
// Check internal modules
for (const grp in Modules.internal) {
if (Modules.internal.hasOwnProperty(grp)) {
for (const i in Modules.internal[grp]) {
if (Modules.internal[grp][i].edID === edId) {
// Found it
return new Module({ template: Modules.internal[grp][i] });
}
}
}
}
// Not found
return null;
}
/**
* Obtain the model of a ship given its ED name
* @param {string} edName the Elite name of the ship
* @return {string} the Coriolis model of the ship
*/
function _shipModelFromEDName(edName) {
return SHIP_FD_NAME_TO_CORIOLIS_NAME[edName];
}
/**
* Obtain a ship's model from the companion API JSON
* @param {object} json the companion API JSON
* @return {string} the Coriolis model of the ship
*/
export function shipModelFromJson(json) {
return _shipModelFromEDName(json.name);
}
/**
* Build a ship from the companion API JSON
* @param {object} json the companion API JSON
* @return {Ship} the built ship
*/
export function shipFromJson(json) {
// Start off building a basic ship
const shipModel = shipModelFromJson(json);
if (!shipModel) {
throw 'No such ship found: "' + json.name + '"';
}
const shipTemplate = Ships[shipModel];
let ship = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots);
ship.buildWith(null);
// Set the cargo hatch. We don't have any information on it so guess it's priority 5 and disabled
ship.cargoHatch.enabled = false;
ship.cargoHatch.priority = 4;
// Add the bulkheads
const armourJson = json.modules.Armour.module;
if (armourJson.name.endsWith('_Armour_Grade1')) {
ship.useBulkhead(0, true);
} else if (armourJson.name.endsWith('_Armour_Grade2')) {
ship.useBulkhead(1, true);
} else if (armourJson.name.endsWith('_Armour_Grade3')) {
ship.useBulkhead(2, true);
} else if (armourJson.name.endsWith('_Armour_Mirrored')) {
ship.useBulkhead(3, true);
} else if (armourJson.name.endsWith('_Armour_Reactive')) {
ship.useBulkhead(4, true);
} else {
throw 'Unknown bulkheads "' + armourJson.name + '"';
}
ship.bulkheads.enabled = true;
if (armourJson.modifiers) _addModifications(ship.bulkheads.m, armourJson.modifiers, armourJson.recipeName, armourJson.recipeLevel);
// Add the standard modules
// Power plant
const powerplantJson = json.modules.PowerPlant.module;
const powerplant = _moduleFromEdId(powerplantJson.id);
if (powerplantJson.modifiers) _addModifications(powerplant, powerplantJson.modifiers, powerplantJson.recipeName, powerplantJson.recipeLevel);
ship.use(ship.standard[0], powerplant, true);
ship.standard[0].enabled = powerplantJson.on === true;
ship.standard[0].priority = powerplantJson.priority;
// Thrusters
const thrustersJson = json.modules.MainEngines.module;
const thrusters = _moduleFromEdId(thrustersJson.id);
if (thrustersJson.modifiers) _addModifications(thrusters, thrustersJson.modifiers, thrustersJson.recipeName, thrustersJson.recipeLevel);
ship.use(ship.standard[1], thrusters, true);
ship.standard[1].enabled = thrustersJson.on === true;
ship.standard[1].priority = thrustersJson.priority;
// FSD
const frameshiftdriveJson = json.modules.FrameShiftDrive.module;
const frameshiftdrive = _moduleFromEdId(frameshiftdriveJson.id);
if (frameshiftdriveJson.modifiers) _addModifications(frameshiftdrive, frameshiftdriveJson.modifiers, frameshiftdriveJson.recipeName, frameshiftdriveJson.recipeLevel);
ship.use(ship.standard[2], frameshiftdrive, true);
ship.standard[2].enabled = frameshiftdriveJson.on === true;
ship.standard[2].priority = frameshiftdriveJson.priority;
// Life support
const lifesupportJson = json.modules.LifeSupport.module;
const lifesupport = _moduleFromEdId(lifesupportJson.id);
if (lifesupportJson.modifiers)_addModifications(lifesupport, lifesupportJson.modifiers, lifesupportJson.recipeName, lifesupportJson.recipeLevel);
ship.use(ship.standard[3], lifesupport, true);
ship.standard[3].enabled = lifesupportJson.on === true;
ship.standard[3].priority = lifesupportJson.priority;
// Power distributor
const powerdistributorJson = json.modules.PowerDistributor.module;
const powerdistributor = _moduleFromEdId(powerdistributorJson.id);
if (powerdistributorJson.modifiers) _addModifications(powerdistributor, powerdistributorJson.modifiers, powerdistributorJson.recipeName, powerdistributorJson.recipeLevel);
ship.use(ship.standard[4], powerdistributor, true);
ship.standard[4].enabled = powerdistributorJson.on === true;
ship.standard[4].priority = powerdistributorJson.priority;
// Sensors
const sensorsJson = json.modules.Radar.module;
const sensors = _moduleFromEdId(sensorsJson.id);
if (sensorsJson.modifiers) _addModifications(sensors, sensorsJson.modifiers, sensorsJson.recipeName, sensorsJson.recipeLevel);
ship.use(ship.standard[5], sensors, true);
ship.standard[5].enabled = sensorsJson.on === true;
ship.standard[5].priority = sensorsJson.priority;
// Fuel tank
const fueltankJson = json.modules.FuelTank.module;
const fueltank = _moduleFromEdId(fueltankJson.id);
ship.use(ship.standard[6], fueltank, true);
ship.standard[6].enabled = true;
ship.standard[6].priority = 0;
// Add hardpoints
let hardpointClassNum = -1;
let hardpointSlotNum = -1;
let hardpointArrayNum = 0;
for (let i in shipTemplate.slots.hardpoints) {
if (shipTemplate.slots.hardpoints[i] === hardpointClassNum) {
// Another slot of the same class
hardpointSlotNum++;
} else {
// The first slot of a new class
hardpointClassNum = shipTemplate.slots.hardpoints[i];
hardpointSlotNum = 1;
}
// Now that we know what we're looking for, find it
const hardpointName = HARDPOINT_NUM_TO_CLASS[hardpointClassNum] + 'Hardpoint' + hardpointSlotNum;
const hardpointSlot = json.modules[hardpointName];
if (!hardpointSlot) {
// This can happen with old imports that don't contain new hardpoints
} else if (!hardpointSlot.module) {
// No module
} else {
const hardpointJson = hardpointSlot.module;
const hardpoint = _moduleFromEdId(hardpointJson.id);
if (hardpointJson.modifiers) _addModifications(hardpoint, hardpointJson.modifiers, hardpointJson.recipeName, hardpointJson.recipeLevel);
ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true);
ship.hardpoints[hardpointArrayNum].enabled = hardpointJson.on === true;
ship.hardpoints[hardpointArrayNum].priority = hardpointJson.priority;
}
hardpointArrayNum++;
}
// Add internal compartments
let internalSlotNum = 1;
let militarySlotNum = 1;
for (let i in shipTemplate.slots.internal) {
const internalClassNum = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].class : shipTemplate.slots.internal[i];
const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name == 'Military' : false;
// The internal slot might be a standard or a military slot. Military slots have a different naming system
let internalSlot = null;
if (isMilitary) {
const internalName = 'Military0' + militarySlotNum;
internalSlot = json.modules[internalName];
militarySlotNum++;
} else {
while (internalSlot === null && internalSlotNum < 99) {
// Slot numbers are not contiguous so handle skips
const internalName = 'Slot' + (internalSlotNum <= 9 ? '0' : '') + internalSlotNum + '_Size' + internalClassNum;
if (json.modules[internalName]) {
internalSlot = json.modules[internalName];
}
internalSlotNum++;
}
}
if (!internalSlot) {
// This can happen with old imports that don't contain new slots
} else if (!internalSlot.module) {
// No module
} else {
const internalJson = internalSlot.module;
const internal = _moduleFromEdId(internalJson.id);
if (internalJson.modifiers) _addModifications(internal, internalJson.modifiers, internalJson.recipeName, internalJson.recipeLevel);
ship.use(ship.internal[i], internal, true);
ship.internal[i].enabled = internalJson.on === true;
ship.internal[i].priority = internalJson.priority;
}
}
// Now update the ship's codes before returning it
return ship.updatePowerPrioritesString().updatePowerEnabledString().updateModificationsString();
}
/**
* Add the modifications for a module
* @param {Module} module the module
* @param {Object} modifiers the modifiers
* @param {Object} blueprint the blueprint of the modification
* @param {Object} grade the grade of the modification
*/
function _addModifications(module, modifiers, blueprint, grade) {
if (!modifiers || !modifiers.modifiers) return;
let special;
for (const i in modifiers.modifiers) {
// Some special modifications
if (modifiers.modifiers[i].name === 'mod_weapon_clip_size_override') {
// This is a numeric addition to the clip size, but we need to work it out in terms of being a percentage so
// that it works the same as other modifications
const origClip = module.clip || 1;
module.setModValue('clip', ((modifiers.modifiers[i].value - origClip) / origClip) * 10000);
} else if (modifiers.modifiers[i].name === 'mod_weapon_burst_size') {
// This is an absolute number that acts as an override
module.setModValue('burst', modifiers.modifiers[i].value * 100);
} else if (modifiers.modifiers[i].name === 'mod_weapon_burst_rof') {
// For some reason this is a non-normalised percentage (i.e. 12.23% is 12.23 value rather than 0.1223 as everywhere else), so fix that here
module.setModValue('burstrof', modifiers.modifiers[i].value * 100);
} else {
// Look up the modifiers to find what we need to do
const modifierActions = Modifications.modifierActions[modifiers.modifiers[i].name];
const value = modifiers.modifiers[i].value;
// Carry out the required changes
for (const action in modifierActions) {
if (isNaN(modifierActions[action])) {
module.setModValue(action, modifierActions[action]);
} else {
const actionValue = modifierActions[action] * value;
let mod = module.getModValue(action) / 10000;
if (!mod) {
mod = 0;
}
module.setModValue(action, ((1 + mod) * (1 + actionValue) - 1) * 10000);
}
}
}
// Note the special if present
if (modifiers.modifiers[i].name && modifiers.modifiers[i].name.startsWith('special_')) {
special = Modifications.specials[modifiers.modifiers[i].name];
}
}
// Add the blueprint ID, grade and special
if (blueprint) {
module.blueprint = Object.assign({}, Modifications.blueprints[blueprint]);
if (grade) {
module.blueprint.grade = Number(grade);
}
if (special) {
module.blueprint.special = special;
}
}
// Need to fix up a few items
// Shield boosters are treated internally as straight modifiers, so rather than (for example)
// being a 4% boost they are a 104% multiplier. Unfortunately this means that our % modification
// is incorrect so we fix it
if (module.grp === 'sb' && module.getModValue('shieldboost')) {
const alteredBoost = (1 + module.shieldboost) * (module.getModValue('shieldboost') / 10000);
module.setModValue('shieldboost', alteredBoost * 10000 / module.shieldboost);
}
// Shield booster resistance is actually a damage modifier, so needs to be inverted.
if (module.grp === 'sb') {
if (module.getModValue('explres')) {
module.setModValue('explres', ((module.getModValue('explres') / 10000) * -1) * 10000);
}
if (module.getModValue('kinres')) {
module.setModValue('kinres', ((module.getModValue('kinres') / 10000) * -1) * 10000);
}
if (module.getModValue('thermres')) {
module.setModValue('thermres', ((module.getModValue('thermres') / 10000) * -1) * 10000);
}
}
// Shield generator resistance is actually a damage modifier, so needs to be inverted.
// In addition, the modification is based off the inherent resistance of the module
if (module.isShieldGenerator()) {
if (module.getModValue('explres')) {
module.setModValue('explres', ((1 - (1 - module.explres) * (1 + module.getModValue('explres') / 10000)) - module.explres) * 10000);
}
if (module.getModValue('kinres')) {
module.setModValue('kinres', ((1 - (1 - module.kinres) * (1 + module.getModValue('kinres') / 10000)) - module.kinres) * 10000);
}
if (module.getModValue('thermres')) {
module.setModValue('thermres', ((1 - (1 - module.thermres) * (1 + module.getModValue('thermres') / 10000)) - module.thermres) * 10000);
}
}
// Hull reinforcement package resistance is actually a damage modifier, so needs to be inverted.
if (module.grp === 'hr') {
if (module.getModValue('explres')) {
module.setModValue('explres', ((module.getModValue('explres') / 10000) * -1) * 10000);
}
if (module.getModValue('kinres')) {
module.setModValue('kinres', ((module.getModValue('kinres') / 10000) * -1) * 10000);
}
if (module.getModValue('thermres')) {
module.setModValue('thermres', ((module.getModValue('thermres') / 10000) * -1) * 10000);
}
}
// Bulkhead resistance is actually a damage modifier, so needs to be inverted.
// In addition, the modification is based off the inherent resistance of the module
if (module.grp == 'bh') {
if (module.getModValue('explres')) {
module.setModValue('explres', ((1 - (1 - module.explres) * (1 + module.getModValue('explres') / 10000)) - module.explres) * 10000);
}
if (module.getModValue('kinres')) {
module.setModValue('kinres', ((1 - (1 - module.kinres) * (1 + module.getModValue('kinres') / 10000)) - module.kinres) * 10000);
}
if (module.getModValue('thermres')) {
module.setModValue('thermres', ((1 - (1 - module.thermres) * (1 + module.getModValue('thermres') / 10000)) - module.thermres) * 10000);
}
}
// Bulkhead boost is based off the inherent boost of the module
if (module.grp == 'bh') {
const alteredBoost = (1 + module.hullboost) * (1 + module.getModValue('hullboost') / 10000) - 1;
module.setModValue('hullboost', (alteredBoost / module.hullboost - 1) * 10000);
}
// Jitter is an absolute number, so we need to divide it by 100
if (module.getModValue('jitter')) {
module.setModValue('jitter', module.getModValue('jitter') / 100);
}
// FD uses interval between bursts internally, so we need to translate this to a real rate of fire
if (module.getModValue('rof')) {
module.setModValue('rof', ((1 / (1 + module.getModValue('rof') / 10000)) - 1) * 10000);
}
}

View File

@@ -1,17 +1,27 @@
import request from 'superagent'; import request from 'superagent';
const SHORTEN_API = 'https://www.googleapis.com/urlshortener/v1/url?key=';
/**
* Shorten a URL
* @param {string} url The URL to shorten
* @param {function} success Success callback
* @param {function} error Failure/Error callback
*/
export default function shorternUrl(url, success, error) {
shortenUrlEddp(url, success, error);
}
const SHORTEN_API_GOOGLE = 'https://www.googleapis.com/urlshortener/v1/url?key=';
/** /**
* Shorten a URL using Google's URL shortener API * Shorten a URL using Google's URL shortener API
* @param {string} url The URL to shorten * @param {string} url The URL to shorten
* @param {function} success Success callback * @param {function} success Success callback
* @param {function} error Failure/Error callback * @param {function} error Failure/Error callback
*/ */
export default function shortenUrl(url, success, error) { function shortenUrlGoogle(url, success, error) {
if (window.navigator.onLine) { if (window.navigator.onLine) {
try { try {
request.post(SHORTEN_API + window.CORIOLIS_GAPI_KEY) request.post(SHORTEN_API_GOOGLE + window.CORIOLIS_GAPI_KEY)
.send({ longUrl: url }) .send({ longUrl: url })
.end(function(err, response) { .end(function(err, response) {
if (err) { if (err) {
@@ -27,3 +37,30 @@ export default function shortenUrl(url, success, error) {
error('Not Online'); error('Not Online');
} }
} }
const SHORTEN_API_EDDP = 'http://eddp.co/u';
/**
* Shorten a URL using EDDP's URL shortener API
* @param {string} url The URL to shorten
* @param {function} success Success callback
* @param {function} error Failure/Error callback
*/
function shortenUrlEddp(url, success, error) {
if (window.navigator.onLine) {
try {
request.post(SHORTEN_API_EDDP)
.send(url)
.end(function(err, response) {
if (err) {
error('Bad Request');
} else {
success(response.header['location']);
}
});
} catch (e) {
error(e.message ? e.message : e);
}
} else {
error('Not Online');
}
}

View File

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

View File

@@ -1,4 +1,3 @@
/** /**
* Generates a URL for the outiffing page * Generates a URL for the outiffing page
* @param {String} shipId Ship Id * @param {String} shipId Ship Id
@@ -7,15 +6,18 @@
* @return {String} URL * @return {String} URL
*/ */
export function outfitURL(shipId, code, buildName) { export function outfitURL(shipId, code, buildName) {
let parts = ['/outfit/', shipId]; let path = '/outfit/' + shipId;
let sepChar = '?';
if (code) { if (code) {
parts.push('/', code); path = path + sepChar + 'code=' + encodeURIComponent(code);
sepChar = '&';
} }
if (buildName) { if (buildName) {
parts.push('?bn=', encodeURIComponent(buildName)); path = path + sepChar + 'bn=' + encodeURIComponent(buildName);
} }
return parts.join('');
}
return path;
}

View File

@@ -58,3 +58,16 @@ export function shallowEqual(objA, objB) {
return true; return true;
} }
/**
* Turn a URL-safe base-64 encoded string in to a normal version.
* Coriolis used to use a different encoding system, and some old
* data might be bookmarked or on local storage, so we keep this
* around and use it when decoding data from the old-style URLs to
* be safe.
* @param {string} data the string
* @return {string} the converted string
*/
export function fromUrlSafe(data) {
return data ? data.replace(/-/g, '/').replace(/_/g, '+') : null;
}

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

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

View File

@@ -16,6 +16,7 @@
@import 'tooltip'; @import 'tooltip';
@import 'buttons'; @import 'buttons';
@import 'error'; @import 'error';
@import 'shipselector';
@import 'sortable'; @import 'sortable';
@import 'loader'; @import 'loader';
@@ -23,9 +24,7 @@ html, body {
height: 100%; height: 100%;
width: 100%; width: 100%;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
overflow-x: hidden; overflow: hidden;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
} }
body { body {
@@ -42,17 +41,25 @@ div, a, li {
#coriolis { #coriolis {
width: 100%; width: 100%;
min-height: 95%; height: 100%;
padding-top: 48px;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
box-sizing: border-box;
} }
.page { .page {
margin: 0; margin: 0;
padding: 0.5em 0; padding: 0.5em;
width: 100%; width: 100%;
min-height: 100%; min-height: 100%;
clear: both; clear: both;
text-align: center; text-align: center;
box-sizing: border-box; box-sizing: border-box;
.largePhone({
padding: 0.5em 0.25em;
});
} }
.l { .l {
@@ -159,4 +166,4 @@ footer {
float: right; float: right;
text-align: right; text-align: right;
} }
} }

View File

@@ -20,8 +20,12 @@ header {
line-height: 3em; line-height: 3em;
font-family: @fTitle; font-family: @fTitle;
vertical-align: middle; vertical-align: middle;
position: relative; position: absolute;
top: 0px;
left: 0px;
width: 100%;
z-index: 2; z-index: 2;
box-sizing: border-box;
.user-select-none(); .user-select-none();
.menu { .menu {
@@ -97,6 +101,7 @@ header {
input { input {
border: none; border: none;
background-color: transparent;
text-align: right; text-align: right;
font-size: 1em; font-size: 1em;
font-family: @fStandard; font-family: @fStandard;
@@ -171,7 +176,7 @@ header {
&:visited { &:visited {
color: @warning; color: @warning;
} }
&:hover { .no-touch &:hover {
color: teal; color: teal;
} }
&.active { &.active {
@@ -185,6 +190,7 @@ header {
} }
.no-wrap { .no-wrap {
overflow-x: auto;
white-space: nowrap; white-space: nowrap;
} }
@@ -199,4 +205,4 @@ header {
margin:0px; margin:0px;
text-transform: uppercase; text-transform: uppercase;
} }
} }

View File

@@ -1,4 +1,5 @@
// Standard icons
.icon { .icon {
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
@@ -25,3 +26,41 @@
height: 2em; height: 2em;
} }
} }
// Modifiction icons - hard-code stroke/fill
.modicon {
display: inline-block;
vertical-align: middle;
width: 1.1em;
height: 1em;
stoke: @fg;
stroke-width: 20;
fill: transparent;
&.sm {
width: 0.8em;
height: 0.75em;
}
&.tn {
width: 0.6em;
height: 0.5em;
}
&.lg {
width: 1.6em;
height: 1.5em;
}
&.xl {
width: 2.1em;
height: 2em;
}
}
.summary {
stroke: @fg;
stroke-width: 10;
fill: @fg;
}

View File

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

199
src/less/shipselector.less Executable file
View File

@@ -0,0 +1,199 @@
.shipselector {
background-color: @bgBlack;
margin: 0;
padding: 0 0 0 1em;
height: 3em;
line-height: 3em;
font-family: @fTitle;
vertical-align: middle;
position: relative;
.user-select-none();
.menu {
position: relative;
z-index: 1;
cursor: default;
&.r {
.menu-list {
right: 0;
}
}
.smallTablet({
position: static;
position: initial;
});
}
.menu-header {
padding : 0 1em;
cursor: pointer;
color: @warning;
text-transform: uppercase;
}
.menu-list {
font-family: @fStandard;
position: absolute;
padding: 0.5em 1em;
box-sizing: border-box;
min-width: 100%;
overflow-x: hidden;
background-color: @bgBlack;
font-size: 0.9em;
overflow-y: auto;
z-index: 0;
-webkit-overflow-scrolling: touch;
max-height: 500px;
&::-webkit-scrollbar {
width: 0.5em;
}
&::-webkit-scrollbar-track {
background-color: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: @warning-disabled;
}
input {
border: none;
background-color: transparent;
text-align: right;
font-size: 1em;
font-family: @fStandard;
}
.smallTablet({
max-height: 400px;
left: 0;
right: 0;
border-bottom: 1px solid @bg;
});
.tablet({
li, a {
padding: 0.3em 0;
}
});
}
.dbl {
-webkit-column-count: 2; /* Chrome, Safari, Opera */
-moz-column-count: 2; /* Firefox */
column-count: 2;
ul {
min-width: 10em;
}
.smallTablet({
-webkit-column-count: 3; /* Chrome, Safari, Opera */
-moz-column-count: 3; /* Firefox */
column-count: 3;
ul {
min-width: 20em;
}
});
.largePhone({
-webkit-column-count: 2; /* Chrome, Safari, Opera */
-moz-column-count: 2; /* Firefox */
column-count: 2;
});
.smallPhone({
-webkit-column-count: 1; /* Chrome, Safari, Opera */
-moz-column-count: 1; /* Firefox */
column-count: 1;
});
}
.quad {
-webkit-column-count: 4; /* Chrome, Safari, Opera */
-moz-column-count: 4; /* Firefox */
column-count: 4;
ul {
min-width: 10em;
}
.smallTablet({
-webkit-column-count: 3; /* Chrome, Safari, Opera */
-moz-column-count: 3; /* Firefox */
column-count: 3;
ul {
min-width: 20em;
}
});
.largePhone({
-webkit-column-count: 2; /* Chrome, Safari, Opera */
-moz-column-count: 2; /* Firefox */
column-count: 2;
});
.smallPhone({
-webkit-column-count: 1; /* Chrome, Safari, Opera */
-moz-column-count: 1; /* Firefox */
column-count: 1;
});
}
ul {
display: inline-block;
white-space: nowrap;
margin: 0 0 0.5em;
padding: 0;
line-height: 1.3em;
}
li {
white-space: normal;
list-style: none;
margin-left: 1em;
line-height: 1.1em;
}
a {
vertical-align: middle;
color: @warning;
text-decoration: none;
&:visited {
color: @warning;
}
.no-touch &:hover {
color: teal;
}
&.active {
color: @primary;
}
}
hr {
border: none;
border-top: 1px solid @disabled;
}
.no-wrap {
white-space: nowrap;
}
.block {
display: block;
line-height: 1.5em;
}
.title {
font-size: 1.3em;
display: inline-block;
margin:0px;
text-transform: uppercase;
}
}

View File

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

View File

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

View File

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

79
src/migrate.html Normal file
View File

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

View File

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

View File

@@ -0,0 +1,366 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "http://cdn.coriolis.io/schemas/ship-loadout/4.json#",
"title": "Ship Loadout",
"type": "object",
"description": "The details for a specific ship build/loadout",
"required": ["name", "ship", "components"],
"properties": {
"name": {
"description": "The name of the build/loadout",
"type": "string",
"minLength": 1
},
"ship": {
"description": "The full display name of the ship",
"type": "string",
"minimum": 3
},
"manufacturer": {
"description": "The ship manufacturer",
"type": "string"
},
"references" : {
"description": "3rd Party references and/or links to this build/loadout",
"type": "array",
"items": {
"type": "object",
"required": ["name","url"],
"additionalProperties": true,
"properties": {
"name": {
"description": "The name of the 3rd party, .e.g 'Coriolis.io' or 'E:D Shipyard'",
"type": "string"
},
"url": {
"description": "The link/url to the 3rd party referencing this build/loadout",
"type": "string"
}
}
}
},
"components": {
"description": "The components used by this build",
"type": "object",
"additionalProperties": false,
"required": ["standard", "internal", "hardpoints", "utility"],
"properties": {
"standard": {
"description": "The set of standard components across all ships",
"type": "object",
"additionalProperties": false,
"required": ["bulkheads", "powerPlant", "thrusters", "frameShiftDrive", "lifeSupport", "powerDistributor", "sensors", "fuelTank", "cargoHatch"],
"properties": {
"bulkheads": {
"enum": ["Lightweight Alloy", "Reinforced Alloy", "Military Grade Composite", "Mirrored Surface Composite", "Reactive Surface Composite"]
},
"cargoHatch": {
"required": ["enabled", "priority"],
"properties": {
"enabled": { "type": "boolean" },
"priority": { "type": "integer", "minimum": 1, "maximum": 5 }
}
},
"powerPlant": {
"required": ["class", "rating", "enabled", "priority"],
"properties": {
"class": { "type": "integer", "minimum": 2, "maximum": 8 },
"rating": { "$ref": "#/definitions/standardRatings" },
"enabled": { "type": "boolean" },
"priority": { "type": "integer", "minimum": 1, "maximum": 5 },
"blueprint": { "type": "object" },
"modifications": { "type": "object" }
}
},
"thrusters": {
"required": ["class", "rating", "enabled", "priority"],
"properties": {
"class": { "type": "integer", "minimum": 2, "maximum": 8 },
"rating": { "$ref": "#/definitions/standardRatings" },
"enabled": { "type": "boolean" },
"priority": { "type": "integer", "minimum": 1, "maximum": 5 },
"name": {
"description": "The name identifing the thrusters (if applicable), e.g. 'Enhanced Performance'",
"type": "string"
},
"blueprint": { "type": "object" },
"modifications": { "type": "object" }
}
},
"frameShiftDrive": {
"required": ["class", "rating", "enabled", "priority"],
"properties": {
"class": { "type": "integer", "minimum": 2, "maximum": 8 },
"rating": { "$ref": "#/definitions/standardRatings" },
"enabled": { "type": "boolean" },
"priority": { "type": "integer", "minimum": 1, "maximum": 5 },
"blueprint": { "type": "object" },
"modifications": { "type": "object" }
}
},
"lifeSupport": {
"required": ["class", "rating", "enabled", "priority"],
"properties": {
"class": { "type": "integer", "minimum": 1, "maximum": 6 },
"rating": { "$ref": "#/definitions/standardRatings" },
"enabled": { "type": "boolean" },
"priority": { "type": "integer", "minimum": 1, "maximum": 5 },
"blueprint": { "type": "object" },
"modifications": { "type": "object" }
}
},
"powerDistributor": {
"required": ["class", "rating", "enabled", "priority"],
"properties": {
"class": { "type": "integer", "minimum": 1, "maximum": 8 },
"rating": { "$ref": "#/definitions/standardRatings" },
"enabled": { "type": "boolean" },
"priority": { "type": "integer", "minimum": 1, "maximum": 5 },
"blueprint": { "type": "object" },
"modifications": { "type": "object" }
}
},
"sensors": {
"required": ["class", "rating", "enabled", "priority"],
"properties": {
"class": { "type": "integer", "minimum": 1, "maximum": 8 },
"rating": { "$ref": "#/definitions/standardRatings" },
"enabled": { "type": "boolean" },
"priority": { "type": "integer", "minimum": 1, "maximum": 5 },
"blueprint": { "type": "object" },
"modifications": { "type": "object" }
}
},
"fuelTank": {
"required": ["class", "rating", "enabled", "priority"],
"properties": {
"class": { "type": "integer", "minimum": 1, "maximum": 6 },
"rating": { "$ref": "#/definitions/standardRatings" },
"enabled": { "type": "boolean" },
"priority": { "type": "integer", "minimum": 1, "maximum": 5 },
"blueprint": { "type": "object" },
"modifications": { "type": "object" }
}
}
}
},
"internal": {
"type": "array",
"items": {
"type": ["object", "null"],
"required": ["class", "rating", "enabled", "priority", "group"],
"properties" : {
"class": { "type": "integer", "minimum": 1, "maximum": 8 },
"rating": { "$ref": "#/definitions/standardRatings" },
"enabled": { "type": "boolean" },
"priority": { "type": "integer", "minimum": 1, "maximum": 5 },
"group": {
"description": "The group of the component, e.g. 'Shield Generator', or 'Cargo Rack'",
"type": "string"
},
"name": {
"description": "The name identifying the component (if applicable), e.g. 'Advance Discovery Scanner', or 'Detailed Surface Scanner'",
"type": "string"
},
"blueprint": { "type": "object" },
"modifications": { "type": "object" }
}
},
"minItems": 3
},
"hardpoints": {
"type": "array",
"items": {
"type": ["object", "null"],
"required": ["class", "rating", "enabled", "priority", "group", "mount"],
"properties" : {
"class": { "type": "integer", "minimum": 1, "maximum": 4 },
"rating": { "$ref": "#/definitions/allRatings" },
"enabled": { "type": "boolean" },
"priority": { "type": "integer", "minimum": 1, "maximum": 5 },
"mount": { "type": "string", "enum": ["Fixed", "Gimballed", "Turret"] },
"group": {
"description": "The group of the component, e.g. 'Beam Laser', or 'Missile Rack'",
"type": "string"
},
"name": {
"description": "The name identifing the component (if applicable), e.g. 'Retributor', or 'Mining Lance'",
"type": "string"
},
"blueprint": { "type": "object" },
"modifications": { "type": "object" }
}
},
"minItems": 1
},
"utility": {
"type": "array",
"items": {
"type": ["object", "null"],
"required": ["class", "rating", "enabled", "priority", "group"],
"properties" : {
"class": { "type": "integer", "minimum": 0, "maximum": 0 },
"rating": { "$ref": "#/definitions/allRatings" },
"enabled": { "type": "boolean" },
"priority": { "type": "integer", "minimum": 1, "maximum": 5 },
"group": {
"description": "The group of the component, e.g. 'Shield Booster', or 'Kill Warrant Scanner'",
"type": "string"
},
"name": {
"description": "The name identifing the component (if applicable), e.g. 'Point Defence', or 'Electronic Countermeasure'",
"type": "string"
},
"blueprint": { "type": "object" },
"modifications": { "type": "object" }
}
},
"minItems": 1
}
}
},
"stats": {
"description": "Optional statistics from the build",
"type": "object",
"additionalProperties": true,
"properties": {
"agility": {
"type": "integer",
"minimum": 0
},
"armour": {
"description": "Sum of base armour + any hull reinforcements",
"type": "integer",
"minimum": 1
},
"armourAdded":{
"description": "Armour added through Hull reinforcement",
"type": "integer",
"minimum": 0
},
"baseShieldStrength": {
"type": "integer",
"minimum": 1
},
"baseArmour": {
"type": "integer",
"minimum": 1
},
"boost": {
"description": "Maximum boost speed of the ships (4 pips, straight-line)",
"type": "number",
"minimum": 0
},
"cargoCapacity": {
"type": "integer",
"minimum": 0
},
"class": {
"description": "Ship Class/Size [Small, Medium, Large]",
"enum": [1,2,3]
},
"totalDps": {
"description": "Total damage dealt per second of all weapons",
"type": "number",
"minimum": 0
},
"totalEps": {
"description": "Total energy consumed per second of all weapons",
"type": "number",
"minimum": 0
},
"totalHps": {
"description": "Total heat generated per second of all weapons",
"type": "number",
"minimum": 0
},
"hullCost": {
"description": "Cost of the ship's hull",
"type": "integer",
"minimum": 1
},
"hullMass": {
"description": "Mass of the Ship hull only",
"type": "number",
"minimum": 1
},
"hullExplRes": {
"description": "Multiplier for explosive damage to hull",
"type": "number"
},
"hullKinRes": {
"description": "Multiplier for kinetic damage to hull",
"type": "number"
},
"hullThermRes": {
"description": "Multiplier for thermal damage to hull",
"type": "number"
},
"fuelCapacity": {
"type": "integer",
"minimum": 1
},
"fullTankRange": {
"description": "Single Jump range with a full tank (unladenMass + fuel)",
"type": "number",
"minimum": 0
},
"ladenMass": {
"description": "Mass of the Ship + fuel + cargo (hull + all components + fuel tank + cargo capacity)",
"type": "number",
"minimum": 1
},
"ladenRange": {
"description": "Single Jump range with full cargo load, see ladenMass",
"type": "number",
"minimum": 0
},
"masslock": {
"description": "Mass Lock Factor of the Ship",
"type": "integer",
"minimum": 1
},
"shield": {
"description": "Shield strength in Mega Joules (Mj)",
"type": "number",
"minimum": 0
},
"shieldExplRes": {
"description": "Multiplier for explosive damage to shields",
"type": "number"
},
"shieldKinRes": {
"description": "Multiplier for kinetic damage to shields",
"type": "number"
},
"shieldThermRes": {
"description": "Multiplier for thermal damage to shields",
"type": "number"
},
"speed": {
"description": "Maximum speed of the ships (4 pips, straight-line)",
"type": "number",
"minimum": 1
},
"totalCost": {
"description": "Total cost of the loadout, including discounts",
"type": "number"
},
"unladenRange": {
"description": "Single Jump range when unladen, see unladenMass",
"type": "number",
"minimum": 0
},
"unladenMass": {
"description": "Mass of the Ship (hull + all components)",
"type": "number",
"minimum": 1
}
}
}
},
"definitions": {
"standardRatings": { "enum": ["A", "B", "C", "D", "E", "F", "G", "H"] },
"allRatings": { "enum": ["A", "B", "C", "D", "E", "F", "G", "H", "I" ] }
}
}

View File

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

View File

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