Compare commits

...

299 Commits

Author SHA1 Message Date
Cmdr McDonald
39e9a38068 Merge branch 'release/2.2.14' 2017-02-08 09:29:04 +00:00
Cmdr McDonald
73d5915b3a Package bump 2017-02-08 09:27:27 +00:00
Cmdr McDonald
b06d2d1f72 Merge branch 'feature/mods' into develop 2017-02-08 09:26:09 +00:00
Cmdr McDonald
6e71f5e6db Use ship name rather than model if possible 2017-02-08 09:24:13 +00:00
Cmdr McDonald
191e31ff18 Use new-style blueprint data; fix numeric special effect calculations 2017-02-05 15:38:04 +00:00
Cmdr McDonald
c655b65779 Ensure TTD is recalculated when EPS is recalculated 2017-02-02 23:40:24 +00:00
Cmdr McDonald
3e168a3e5f Merge branch 'release/2.2.13' into develop 2017-02-02 23:05:50 +00:00
Cmdr McDonald
ee52a4b716 Merge branch 'release/2.2.13' 2017-02-02 23:05:46 +00:00
Cmdr McDonald
a14974fef4 Bump package 2017-02-02 23:03:35 +00:00
Cmdr McDonald
2c0959d68b Merge branch 'feature/fixed' into develop 2017-02-02 23:01:08 +00:00
Cmdr McDonald
0221d05b04 Tweak recalculation of mass 2017-02-01 08:57:56 +00:00
Cmdr McDonald
975432b45a Lint 2017-02-01 08:33:16 +00:00
Cmdr McDonald
e78551f5d9 Ensure that ship mass is recalculated when appropriate - #67 2017-02-01 08:24:50 +00:00
Cmdr McDonald
b1daf6f799 Fix issue with auto loader not showing - #66 2017-02-01 07:45:44 +00:00
Cmdr McDonald
df03102c35 Added time to drain calculation and display 2017-01-30 21:31:55 +00:00
Cmdr McDonald
e7d1a0df63 Merge branch 'release/2.2.12' into develop 2017-01-29 08:27:28 +00:00
Cmdr McDonald
ca0c9675b3 Merge branch 'release/2.2.12' 2017-01-29 08:27:24 +00:00
Cmdr McDonald
491899fe49 Package bump 2017-01-29 08:27:17 +00:00
Cmdr McDonald
cc22a89ad6 Merge branch 'feature/specials' into develop 2017-01-29 08:26:34 +00:00
Cmdr McDonald
d389114189 Remove extraneous functions 2017-01-28 20:40:58 +00:00
Cmdr McDonald
9d0f7a166b Don't show both power generation and power draw in diffDetails 2017-01-27 22:23:36 +00:00
Cmdr McDonald
c344d56d48 Merge branch 'feature/specials' of https://github.com/EDCD/coriolis into feature/specials 2017-01-27 21:34:33 +00:00
Cmdr McDonald
eb5d0e868c Remove logging 2017-01-27 21:34:27 +00:00
Cmdr McDonald
c28f22a78b Add units for time 2017-01-27 21:34:27 +00:00
Cmdr McDonald
e2b22dd4ca Alter shortlink hotkey - for #58 2017-01-27 21:32:07 +00:00
Cmdr McDonald
f5ecccec48 Add weapon mods to damage dealt 2017-01-27 21:32:07 +00:00
Cmdr McDonald
7c8eee4707 Add package info 2017-01-27 21:32:07 +00:00
Cmdr McDonald
99e251fd4b Fix missing break in case 2017-01-27 21:32:07 +00:00
Cmdr McDonald
c1ddecfd3e Add specials 2017-01-27 21:32:07 +00:00
Cmdr McDonald
0ae59c5e48 Remove old reference to coriolis.io 2017-01-27 21:32:07 +00:00
Cmdr McDonald
eb76040b08 Remove logging 2017-01-27 21:31:05 +00:00
Cmdr McDonald
7698cb75e9 Add units for time 2017-01-27 21:30:15 +00:00
Cmdr McDonald
bd7139c703 Merge branch 'feature/specials' of https://github.com/EDCD/coriolis into feature/specials 2017-01-27 09:54:26 +00:00
Cmdr McDonald
c9b9404ba1 Alter shortlink hotkey - for #58 2017-01-27 09:54:17 +00:00
Cmdr McDonald
84ff013a9e Add weapon mods to damage dealt 2017-01-27 09:54:17 +00:00
Cmdr McDonald
d2bcf6cd71 Add package info 2017-01-27 09:54:17 +00:00
Cmdr McDonald
e52c6730f4 Fix missing break in case 2017-01-27 09:54:17 +00:00
Cmdr McDonald
e448b3cb0a Add specials 2017-01-27 09:54:17 +00:00
Cmdr McDonald
705f3158aa Remove old reference to coriolis.io 2017-01-27 09:54:17 +00:00
Cmdr McDonald
f4789717fe Alter shortlink hotkey - for #58 2017-01-27 09:54:08 +00:00
Cmdr McDonald
93e69f215b Add weapon mods to damage dealt 2017-01-27 09:46:32 +00:00
Cmdr McDonald
d04e662de4 Merge branch 'hotfix/2.2.11b' into develop 2017-01-27 08:14:37 +00:00
Cmdr McDonald
a9f9285278 Merge branch 'hotfix/2.2.11b' 2017-01-27 08:14:26 +00:00
Cmdr McDonald
c5e27d43aa Do not apply dropoff twice when calculating weapon effectiveness 2017-01-27 08:14:22 +00:00
Cmdr McDonald
db376829f5 Add package info 2017-01-27 07:56:50 +00:00
Cmdr McDonald
79a31fd992 Fix missing break in case 2017-01-26 18:01:22 +00:00
Cmdr McDonald
094f3aa3e2 Add specials 2017-01-26 16:06:29 +00:00
Cmdr McDonald
41d51743d3 Remove old reference to coriolis.io 2017-01-26 14:38:23 +00:00
Cmdr McDonald
cf741d18ed Merge branch 'release/2.2.11' into develop 2017-01-25 19:46:52 +00:00
Cmdr McDonald
c02f674530 Merge branch 'release/2.2.11' 2017-01-25 19:46:47 +00:00
Cmdr McDonald
89f2273b5d Tidy-ups 2017-01-25 19:44:36 +00:00
Cmdr McDonald
351b084ce9 Merge branch 'feature/help' into develop 2017-01-25 19:34:06 +00:00
Cmdr McDonald
dd48d2d007 Increase falloff with range for focused mods 2017-01-25 19:08:55 +00:00
Cmdr McDonald
63e850b5aa Fixes for fragment cannons 2017-01-25 14:23:57 +00:00
Cmdr McDonald
46775879f7 Add EDMC import help 2017-01-25 11:07:17 +00:00
Cmdr McDonald
c9ba4e3fcb Remove editor file 2017-01-25 11:00:10 +00:00
Cmdr McDonald
8b855b62a1 Fix tests with new values 2017-01-25 10:59:28 +00:00
Cmdr McDonald
d6a687300c Add engagement range to damage received panel 2017-01-25 10:52:09 +00:00
Cmdr McDonald
ddc968129d Damage dealt only shows enabled weapons 2017-01-25 10:20:28 +00:00
Cmdr McDonald
e364560ca7 Provide damage dealt statistics for both shields and hull 2017-01-25 10:11:24 +00:00
Cmdr McDonald
baf6f8130a Tidy up spacing for movement summary 2017-01-25 09:25:01 +00:00
Cmdr McDonald
f8f5cd2581 Add 'average' roll for blueprints 2017-01-25 09:20:01 +00:00
Cmdr McDonald
8019453d0d Make absolute damage visible 2017-01-25 08:01:20 +00:00
Cmdr McDonald
6ad17595dc Merge branch 'hotfix/2.2.10c' into develop 2017-01-24 11:49:24 +00:00
Cmdr McDonald
50e1f7d4df Lint fixes 2017-01-24 11:41:50 +00:00
Cmdr McDonald
fdb931fe00 Merge branch 'feature/help' of https://github.com/EDCD/coriolis into feature/help 2017-01-23 14:21:17 +00:00
Cmdr McDonald
86efb59237 Add beta flag 2017-01-23 14:21:05 +00:00
Cmdr McDonald
f05aa66e53 Add help system and initial help file 2017-01-23 14:21:05 +00:00
Cmdr McDonald
f4e95cd150 Add framework for help 2017-01-23 14:21:05 +00:00
Cmdr McDonald
5798c0e8a6 Add help system and initial help file 2017-01-23 14:20:06 +00:00
Cmdr McDonald
22f98fa8a7 Add framework for help 2017-01-23 14:19:43 +00:00
Cmdr McDonald
897668c0d4 Merge branch 'hotfix/2.2.10b' into develop 2017-01-23 14:18:50 +00:00
Cmdr McDonald
99c0bfcee1 Merge branch 'hotfix/2.2.10b' 2017-01-23 14:17:50 +00:00
Cmdr McDonald
37bfc700e9 Fix for direct entering of modifications 2017-01-23 14:17:42 +00:00
Cmdr McDonald
8fcebf59f6 Add beta flag 2017-01-23 13:17:53 +00:00
Cmdr McDonald
486c055631 Merge branch 'feature/help' of https://github.com/EDCD/coriolis into feature/help 2017-01-23 13:16:22 +00:00
Cmdr McDonald
92246302b9 Add help system and initial help file 2017-01-23 13:15:45 +00:00
Cmdr McDonald
0d646c6193 Add framework for help 2017-01-23 13:14:59 +00:00
Cmdr McDonald
217ea3c5ac Merge branch 'release/2.10' into develop 2017-01-23 13:05:32 +00:00
Cmdr McDonald
fcf0494df6 Merge branch 'release/2.10' 2017-01-23 13:05:21 +00:00
Cmdr McDonald
20ba6eb822 Tidy-ups prior to release 2017-01-23 13:05:14 +00:00
Cmdr McDonald
0e2c0349e0 Merge branch 'feature/blueprints' into develop 2017-01-22 17:16:40 +00:00
Cmdr McDonald
c49e2cff03 Lint and test fixes 2017-01-22 17:12:48 +00:00
Cmdr McDonald
d79313bfbe Fix shield boost values; add tooltips 2017-01-22 14:15:33 +00:00
Cmdr McDonald
fd404b5155 Handle heavy duty shield booster special modification value 2017-01-22 10:59:36 +00:00
Cmdr McDonald
49e72146b4 Only update relevant modification parameters 2017-01-22 09:02:32 +00:00
Cmdr McDonald
9b534b62c8 Add modification functionality 2017-01-21 10:34:08 +00:00
Cmdr McDonald
0be59af9b0 Allow selection of blueprints type and grade 2017-01-20 17:53:49 +00:00
Cmdr McDonald
5d87a6cd56 Add help system and initial help file 2017-01-20 11:56:32 +00:00
Cmdr McDonald
c61c17d465 Add framework for help 2017-01-19 22:25:51 +00:00
Cmdr McDonald
029ba63aa5 Use new-style modification information 2017-01-19 22:25:13 +00:00
Cmdr McDonald
99e9e0c76f Merge branch 'feature/fixes' into develop 2017-01-15 23:06:13 +00:00
Cmdr McDonald
5bbc6e1cbe Use damage distribution numbers 2017-01-15 17:10:46 +00:00
Cmdr McDonald
1e5f66e528 Fix detailed export of module reinforcement packages 2017-01-15 17:07:12 +00:00
Cmdr McDonald
cdb837a25a Merge branch 'release/2.2.9' into develop 2017-01-14 16:24:18 +00:00
Cmdr McDonald
dd1175abf4 Merge branch 'release/2.2.9' 2017-01-14 16:24:09 +00:00
Cmdr McDonald
619976230d Bump version 2017-01-14 16:24:04 +00:00
Cmdr McDonald
2cb0d5209b Merge branch 'feature/falloff' into develop 2017-01-14 16:23:12 +00:00
Cmdr McDonald
ad06e23afa Add total DPS and effectiveness information to 'Damage Dealt' section 2017-01-14 16:20:48 +00:00
Cmdr McDonald
3def84e435 Use better DPE calculation methodology 2017-01-14 13:15:15 +00:00
Cmdr McDonald
7f377d6345 Add and use range when calculating weapon effectiveness for damage dealt 2017-01-14 13:10:09 +00:00
Cmdr McDonald
53137e0ae1 Add falloff for weapons 2017-01-14 09:52:31 +00:00
Cmdr McDonald
792eda2572 Use SSL-enabled server for shortlinks 2017-01-13 20:05:45 +00:00
Cmdr McDonald
550c94fa94 Merge branch 'release/2.2.8' into develop 2017-01-13 11:42:36 +00:00
Cmdr McDonald
2a841281d4 Merge branch 'release/2.2.8' 2017-01-13 11:42:31 +00:00
Cmdr McDonald
260f29834a Update release notes 2017-01-13 11:35:01 +00:00
Cmdr McDonald
ddb35d321c Fix issue where filling all internals with cargo racks would include restricted slots 2017-01-13 11:34:18 +00:00
Cmdr McDonald
6017c1ecff Merge branch 'release/2.2.7' into develop 2017-01-11 21:59:09 +00:00
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
76 changed files with 9285 additions and 676 deletions

3
.gitignore vendored
View File

@@ -4,4 +4,5 @@ build
*.log
nginx.pid
.idea
/bin
/bin
env

172
ChangeLog.md Normal file
View File

@@ -0,0 +1,172 @@
#2.2.14
* Ensure that jitter is shown correctly when the result of a special effect
* Use restyled blueprint information
* Use the ship name (if available) rather than the ship model for the window title
* Use coriolis-data 2.2.14:
* Alter blueprint structure to combine components and features
* Make hidden value of modifications its own attribute
* Fix incorrect ED ID for class 6 passenger cabins
#2.2.13
* Add 'time to drain' summary value. This is the time to drain the WEP capacitor if firing all enabled weapons
* Do not include utility slot DPS/EPS/HPS in summary information
* Ensure that auto loader special shows in the tooltip
* Ensure that ship mass is recalculated when appropriate
* Use coriolis-data 2.2.13:
* Add plasma slug special effect for plasma accelerator
* Tweak hull costs of ships
#2.2.12
* Tidy up old references to coriolis.io
* Add ability to add and remove special effects to weapon modifications
* Add weapon engineering information to Damage Dealt section
* Change shortcut for link from ctrl-l to ctrl-o to avoid clash with location bar
* Only show one of power generation or draw in tooltips, according to module
* Use coriolis-data 2.2.12:
* Add special effects for each blueprint
* Add IDs for most Powerplay modules
#2.2.11
* Add help system and initial help file
* Make absolute damage visible
* Add 'average' roll for blueprints
* Update spacing for movement summary to make it more readable
* Provide damage dealt statistics for both shields and hull
* Damage dealt panel only shows enabled weapons
* Add engagement range to damage received panel
* Handle burst rate of fire as an absolute number rather than a perentage modification
* Ensure that clip values are always rounded up
* Ensure that focused weapon mod uses range modifier to increase falloff as well
* Use coriolis-data 2.2.11:
* Remove non-existent chaff launcher capacity blueprint grades
* Fix incorrect values for charge enhanced power distributor
* Remove incorrect AFMU blueprints
* Correct fragment cannon Double Shot blueprint information
* Correct Focused weapon blueprint information
#2.2.10
* Fix detailed export of module reinforcement packages
* Use damagedist for exact breakdown of weapons that have more than one type of damage
* Use new-style modification validity data
* Provide ability to select engineering blueprint and roll sample values for them
* Use coriolis-data 2.2.10:
* Fix incorrect base shield values for Cutter and Corvette
* Update weapons to have %-based damage distributions
* Remove power draw for detailed surface scanner - although shown in outfitting it is not part of active power
* Fix incorrect names for lightweight and kinetic armour
* Add engineering blueprints
#2.2.9
* Use SSL-enabled server for shortlinks
* Add falloff for weapons
* Use falloff when calculating weapon effectiveness in damage dealt
* Add engagement range slider to 'Damage Dealt' section to allow user to see change in weapon effectiveness with range
* Use better DPE calculation methodology
* Add total DPS and effectiveness information to 'Damage Dealt' section
* Use coriolis-data 2.2.9:
* Add falloff metric for weapons
* Add falloff from range modification
#2.2.8
* Fix issue where filling all internals with cargo racks would include restricted slots
* Use coriolis-data 2.2.8:
* Set military slot of Viper Mk IV to class 3; was incorrectly set as class 2
* Update base regeneration rate of prismatic shield generators to values in 2.2.03
* Update specials with information in 2.2.03
#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

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

@@ -0,0 +1,325 @@
{
"$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": 18.71,
"totalExplDpe": 0,
"totalExplDps": 0,
"totalExplSDps": 0,
"totalAbsDpe": 3.57,
"totalAbsDps": 18.78,
"totalAbsSDps": 14.45,
"totalHps": 28.28,
"totalKinDpe": 117.48,
"totalKinDps": 22.27,
"totalKinSDps": 16.91,
"totalSDps": 89.99,
"totalThermDpe": 21.63,
"totalThermDps": 60.08,
"totalThermSDps": 58.64,
"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,
"timeToDrain": 7.04
}
}

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

View File

@@ -1,50 +1,50 @@
{
"type_6_transporter": {
"Cargo": "0p0tdFal8d8s8f4-----04040303430101.Iw1-kA==.Aw1-kA==",
"Miner": "0p5tdFal8d8s8f42l2l---040403451q0101.Iw1-kA==.Aw1-kA==",
"Hopper": "0p0tdFal8d0s8f41717---030302024300-.Iw1-kA==.Aw1-kA=="
"Cargo": "A0p0tdFal8d8s8f4-----04040303430101.Iw1/kA==.Aw1/kA==.",
"Miner": "A0p5tdFal8d8s8f42l2l---040403451q0101.Iw1/kA==.Aw1/kA==.",
"Hopper": "A0p0tdFal8d0s8f41717---030302024300-.Iw1/kA==.Aw1/kA==."
},
"type_7_transport": {
"Cargo": "0p0tiFfliddsdf5--------0505040403480101.Iw18aQ==.Aw18aQ==",
"Miner": "0pdtiFflid8sdf5--2l2l----0505041v03450000.Iw18aQ==.Aw18aQ=="
"Cargo": "A0p0tiFfliddsdf5--------0505040403480101.Iw18aQ==.Aw18aQ==.",
"Miner": "A0pdtiFflid8sdf5--2l2l----0505041v03450000.Iw18aQ==.Aw18aQ==."
},
"federal_dropship": {
"Cargo": "0pdtiFflnddsif4-1717------05040448020201.Iw18aQ==.Aw18aQ=="
"Cargo": "A0pdtiFflnddsif4-1717------05040448--020201.Iw18eQ==.Aw18eQ==."
},
"asp": {
"Miner": "2pftfFflidfskf50s0s24242l2l---04054a1q02022o27.Iw18WQ==.Aw18WQ=="
"Miner": "A2pftfFflidfskf50s0s24242l2l---04054a1q02022o27.Iw18WQ==.Aw18WQ==."
},
"imperial_clipper": {
"Cargo": "0p5tiFflndisnf4--0s0s----0605450302020101.Iw18aQ==.Aw18aQ==",
"Dream": "2pktkFflndpskf40v0v0s0s0404040n4k5n5d2b29292o-.Iw18aQ==.Aw18aQ==",
"Current": "0patkFflndfskf4----------------.Iw18aQ==.Aw18aQ=="
"Cargo": "A0p5tiFflndisnf4--0s0s----0605450302020101.Iw18aQ==.Aw18aQ==.",
"Dream": "A2pktkFflndpskf40v0v0s0s0404040n4k5n5d2b29292o-.AwRj4yWU1I==.CwBhCYy6YRigzLIA.",
"Current": "A0patkFflndfskf4----------------.AwRj4yWU1I==.CwBhCYy6YRigzLIA."
},
"type_9_heavy": {
"Current": "0patsFklndnsif6---------0706054a0303020224.Iw18eQ==.Aw18eQ=="
"Current": "A0patsFklndnsif6---------0706054a0303020224.AwRj4yoo.EwBhEYy6dsg=."
},
"python": {
"Cargo": "0patnFflidsssf5---------050505040448020201.Iw18eQ==.Aw18eQ==",
"Miner": "0pktkFflidpspf50v0v0v2m2m0404--050505Ce4a1v02022o.Iw18eQ==.Aw18eQ==",
"Dream": "2pptkFfliduspf50v0v0v27270404040m5n5n4f2d2d032t0201.Iw18eQ==.Aw18eQ==",
"Missile": "0pttoFjljdystf52f2g2d2ePh----04044j03---002h.Iw18eQ==.Aw18eQ=="
"Cargo": "A0patnFflidsssf5---------050505040448020201.Iw18eQ==.Aw18eQ==.",
"Miner": "A0pktkFflidpspf50v0v0v2m2m0404--050505Ce4a1v02022o.Iw18eQ==.IwBhBYy6dkCYg===.",
"Dream": "A2pptkFfliduspf50v0v0v27270404040m5n5n4f2d2d032t0201.Iw1+gDBxA===.EwBhEYy6e0WEA===.",
"Missile": "A0pttoFjljdystf52f2g2d2ePh----04044j03---002h.Iw18eQ==.Aw18eQ==."
},
"anaconda": {
"Dream": "4putpFklndzsuf52c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d040303326b.Iw18ZlA=.Aw18ZlA=",
"Cargo": "0patnFklndnsxf5----------------0605050504040445030301.Iw18ZlA=.Aw18ZlA=",
"Current": "0patnFklndksxf5----------------0605050504040403034524.Iw18ZlA=.Aw18ZlA=",
"Explorer": "0patnFklndksxf5--------0202------f7050505040s372f2i4524.Iw18ZlA=.Aw18ZlA=",
"Test": "4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.Iw18ZlA=.Aw18ZlA="
"Dream": "A4putpFklndzsuf52c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d04-0303326b.AwRj4yo5dyg=.MwBhCYy6duvARiA=.",
"Cargo": "A0patnFklndnsxf5----------------06050505040404-45030301.Iw18ZVA=.Aw18ZVA=.",
"Current": "A0patnFklndksxf5----------------06050505040404-03034524.Iw18ZVA=.Aw18ZVA=.",
"Explorer": "A0patnFklndksxf5--------0202------f7050505040s37-2f2i4524.AwRj4yVKJ9hA.AwhMIyumQRhEA===.",
"Test": "A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.Iw18ZVA=.Aw18ZVA=."
},
"diamondback_explorer": {
"Explorer": "0p0tdFfldddsdf5---0202--320p432i2f.Iw1-kA==.Aw1-kA=="
"Explorer": "A0p0tdFfldddsdf5---0202--320p432i2f.AwRj4zTI.AwiMIypI."
},
"vulture": {
"Bounty Hunter": "3patcFalddksff31e1e0404-0l4a5d27662j.Iw19kA==.Aw19kA=="
"Bounty Hunter": "A3patcFalddksff31e1e0404-0l4a-5d27662j.AwRj4z2I.MwBhBYy6oJmAjLIA."
},
"fer_de_lance": {
"Attack": "2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.Aw18aQ=="
"Attack": "A2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.CwBhrSu8EZyA."
},
"eagle": {
"Figther": "4p0t5F5l3d5s5f20p0p24-40532j-.Iw1-EA==.Aw1-EA=="
"Figther": "A4p0t5F5l3d5s5f20p0p24-4053-2j-.Iw18kA==.Aw18kA==."
}
}
}

View File

@@ -33,7 +33,8 @@
},
"anaconda": {
"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": {
"Explorer": "02A4D5A3D3D3D5C-------320p432i2f.AwRj4zTI.AwiMIypI"
@@ -63,4 +64,4 @@
1,
1
]
}
}

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

@@ -108,7 +108,8 @@ describe('Import Modal', function() {
it('catches an invalid backup', function() {
const importData = require('./fixtures/valid-backup');
let invalidImportData = Object.assign({}, importData);
invalidImportData.builds.asp = null; // Remove Asp Miner build used in comparison
//invalidImportData.builds.asp = null; // Remove Asp Miner build used in comparison
delete(invalidImportData.builds.asp);
pasteText('"this is not valid"');
expect(modal.state.importValid).toBeFalsy();
@@ -128,7 +129,7 @@ describe('Import Modal', function() {
});
});
describe('Import Detailed Build', function() {
describe('Import Detailed V3 Build', function() {
beforeEach(reset);
@@ -141,18 +142,64 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda/4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-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() {
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.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() {
beforeEach(reset);
@@ -178,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.H4sIAAAAAAAAA02SPy9DURjG3%2F65vW1v47TXVbeqqF7EQtIIBomRJswsYmISH8BgkFhqFZ9AwlALMYitkXQyEF2k4SMYJNK0dV7PK7nc5ck55%2Fm9z%2FnznpBeJqLvECQbM4hUjZnjO5hyWGfFikAGGjGiku0QuddhQCNdZmdWM9snsDmih4REOdlnNvz9DrPrJIicPdSwoZf8pAnTIpq8x7DYADS%2Bi5DERY85%2BYqpmkc6x%2FWGf6beKCR3YBIZFZCxCgrtczjuOmo4qTf94F4KYuxhz5jjEhXmUJNexFrpIUo02ALN1j9u1JMgD%2FMga1GfbMNRd9iHUwGy%2BpspZF3IBSGvMFJluS%2FuR24FJ2KlV%2Fxju6sQq4lhRsQTUVUJTgegLtS6EUjEE1HPAmUC0KdAjwKJeCKqD8zoURx72gHyDW9nvQhJGHkyUscS1x%2BAZnAlqwU%2FI%2BKJKEvextXrf93eQrR1KUlS5HWwGC61mfOn0oN3IM4OHoBzuuIHj33hS5jT8KeamIYa0sjhgH%2BLfplP4kcwD5Xl3xR1wfeHtqWzBHHX8I9SH9Je%2FgGvXxeungIAAA%3D%3D&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() {
it('imports a valid builds', function() {
const imports = require('./fixtures/ed-shipyard-import-valid');
for (let i = 0; i < imports.length; i++ ) {
reset();
let fixture = imports[i];
pasteText(fixture.buildText);
expect(modal.state.importValid).toBeTruthy();
expect(modal.state.errorMsg).toEqual(null);
clickProceed();
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));
}
});
// it('imports a valid build', function() {
// const imports = require('./fixtures/ed-shipyard-import-valid');
//
// for (let i = 0; i < imports.length; i++ ) {
// reset();
// let fixture = imports[i];
// pasteText(fixture.buildText);
// expect(modal.state.importValid).toBeTruthy();
// expect(modal.state.errorMsg).toEqual(null);
// clickProceed();
// expect(MockRouter.go.mock.calls.length).toBe(1);
// expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/' + fixture.shipId + '?code=' + encodeURIComponent(fixture.buildCode) + '&bn=' + encodeURIComponent(fixture.buildName));
// }
// });
it('catches invalid builds', function() {
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';
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 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() {
let testBuild = new Ship('anaconda', anaconda.properties, anaconda.slots).buildFrom(code);
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);
});
@@ -31,7 +31,7 @@ describe("Serializer", function() {
const builds = require('./fixtures/expected-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);
for (let detailedBuild of exportData) {

View File

@@ -16,7 +16,7 @@ describe("Ship", function() {
ship.buildWith(shipData.defaults);
expect(ship.totalCost).toEqual(shipData.retailCost, s + ' retail cost does not match default build cost');
expect(ship.cargoCapacity).toBeDefined(s + ' cargo');
expect(ship.cargoCapacity).toBeDefined();
expect(ship.priorityBands[0].retracted).toBeGreaterThan(0, s + ' priorityBands');
expect(ship.powerAvailable).toBeGreaterThan(0, s + ' powerAvailable');
expect(ship.unladenRange).toBeGreaterThan(0, s + ' unladenRange');
@@ -24,7 +24,7 @@ describe("Ship", function() {
expect(ship.fuelCapacity).toBeGreaterThan(0, s + ' fuelCapacity');
expect(ship.unladenFastestRange).toBeGreaterThan(0, s + ' unladenFastestRange');
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.topSpeed).toBeGreaterThan(0, s + ' topSpeed');
}

View File

@@ -1,12 +1,12 @@
{
"name": "coriolis_shipyard",
"version": "2.1.2",
"version": "2.2.14",
"repository": {
"type": "git",
"url": "https://github.com/cmmcleod/coriolis"
"url": "https://github.com/EDCD/coriolis"
},
"homepage": "https://coriolis.io",
"bugs": "https://github.com/cmmcleod/coriolis/issues",
"homepage": "https://coriolis.edcd.io",
"bugs": "https://github.com/EDCD/coriolis/issues",
"private": true,
"engine": "node >= 4.0.0",
"license": "MIT",
@@ -24,15 +24,16 @@
},
"jest": {
"scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
"testFileExtensions": [
"js"
],
"testRegex": "(/__tests__/test-.*|\\.(test|spec))\\.js$",
"moduleFileExtensions": [
"js",
"json",
"jsx"
],
"automock": true,
"bail": false,
"unmockedModulePathPatterns": [
"<rootDir>/node_modules/lodash",
"<rootDir>/node_modules/react",
"<rootDir>/node_modules/react-dom",
"<rootDir>/node_modules/react-addons-test-utils",
@@ -68,7 +69,7 @@
"extract-text-webpack-plugin": "^0.9.1",
"file-loader": "^0.8.4",
"html-webpack-plugin": "^1.7.0",
"jest-cli": "^0.9.2",
"jest-cli": "^16.0.1",
"jsen": "^0.6.0",
"json-loader": "^0.5.3",
"less": "^2.5.3",
@@ -84,10 +85,13 @@
"dependencies": {
"babel-polyfill": "*",
"classnames": "^2.2.0",
"browserify-zlib": "ipfs/browserify-zlib",
"coriolis-data": "EDCD/coriolis-data",
"d3": "3.5.16",
"fbemitter": "^2.0.0",
"lodash": "^4.15.0",
"lz-string": "^1.4.4",
"react-number-editor": "Athanasius/react-number-editor.git#miggy",
"react": "^15.0.1",
"react-dom": "^15.0.1",
"superagent": "^1.4.0"

View File

@@ -6,7 +6,12 @@ import Persist from './stores/Persist';
import Header from './components/Header';
import Tooltip from './components/Tooltip';
import ModalExport from './components/ModalExport';
import ModalHelp from './components/ModalHelp';
import ModalImport from './components/ModalImport';
import ModalPermalink from './components/ModalPermalink';
import * as CompanionApiUtils from './utils/CompanionApiUtils';
import { outfitURL } from './utils/UrlGenerators';
import AboutPage from './pages/AboutPage';
import NotFoundPage from './pages/NotFoundPage';
@@ -15,6 +20,8 @@ import ComparisonPage from './pages/ComparisonPage';
import ShipyardPage from './pages/ShipyardPage';
import ErrorDetails from './pages/ErrorDetails';
const zlib = require('zlib');
/**
* Coriolis App
*/
@@ -52,6 +59,7 @@ export default class Coriolis extends React.Component {
this._onLanguageChange = this._onLanguageChange.bind(this);
this._onSizeRatioChange = this._onSizeRatioChange.bind(this);
this._keyDown = this._keyDown.bind(this);
this._importBuild = this._importBuild.bind(this);
this.emitter = new EventEmitter();
this.state = {
@@ -63,13 +71,36 @@ export default class Coriolis extends React.Component {
};
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('/compare/:name?', (r) => this._setPage(ComparisonPage, r));
Router('/comparison?', (r) => this._setPage(ComparisonPage, r));
Router('/comparison/:code', (r) => this._setPage(ComparisonPage, r));
Router('/about', (r) => this._setPage(AboutPage, 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
* @param {[type]} page The page to be shown
@@ -131,14 +162,26 @@ export default class Coriolis extends React.Component {
this._hideModal();
this._closeMenu();
break;
case 72: // 'h'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + h
e.preventDefault();
this._showModal(<ModalHelp />);
}
break;
case 73: // 'i'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + i
e.preventDefault();
this._showModal(<ModalImport />);
}
break;
case 101010: // 's'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + i
case 79: // 'o'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + o
e.preventDefault();
this._showModal(<ModalPermalink url={window.location.href}/>);
}
break;
case 83: // 's'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + s
e.preventDefault();
this.emitter.emit('command', 'save');
}

View File

@@ -93,11 +93,13 @@ export default class AvailableModulesMenu extends TranslatedComponent {
let prevClass = null, prevRating = null;
let elems = [];
for (let i = 0; i < modules.length; i++) {
let m = modules[i];
const sortedModules = modules.sort(this._moduleOrder);
for (let i = 0; i < sortedModules.length; i++) {
let m = sortedModules[i];
let mount = null;
let disabled = m.maxmass && (mass + (m.mass ? m.mass : 0)) > m.maxmass;
let active = mountedModule && mountedModule === m;
let active = mountedModule && mountedModule.id === m.id;
let classes = cn(m.name ? 'lc' : 'c', {
warning: !disabled && warningFunc && warningFunc(m),
active,
@@ -126,7 +128,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
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} />);
}
@@ -201,6 +203,46 @@ export default class AvailableModulesMenu extends TranslatedComponent {
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
*/

View File

@@ -2,6 +2,7 @@ import React from 'react';
import TranslatedComponent from './TranslatedComponent';
import Link from './Link';
import cn from 'classnames';
import { outfitURL } from '../utils/UrlGenerators';
import { SizeMap } from '../shipyard/Constants';
@@ -71,7 +72,7 @@ export default class ComparisonTable extends TranslatedComponent {
* @return {React.Component} Table row
*/
_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 = [
<td key='s' className='tl'><Link href={url}>{build.name}</Link></td>,
<td key='bn' className='tl'><Link href={url}>{build.buildName}</Link></td>

View File

@@ -379,7 +379,7 @@ export default class CostSection extends TranslatedComponent {
<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' 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}
</select>
</td>
@@ -419,7 +419,9 @@ export default class CostSection extends TranslatedComponent {
let retroSlotGroup = retrofitShip[g];
let slotGroup = ship[g];
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] };
if (slotGroup[i].m) {
item.buyName = slotGroup[i].m.name || slotGroup[i].m.grp;
@@ -505,19 +507,19 @@ export default class CostSection extends TranslatedComponent {
scoop = true;
break;
case 'scb':
q = slotGroup[i].m.cells;
q = slotGroup[i].m.getCells();
break;
case 'am':
q = slotGroup[i].m.ammo;
q = slotGroup[i].m.getAmmo();
break;
case 'pv':
srvs += slotGroup[i].m.vehicles;
srvs += slotGroup[i].m.getBays();
break;
case 'fx': case 'hb': case 'cc': case 'pc':
limpets = ship.cargoCapacity;
break;
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
if (slotGroup[i].m.ammocost > 0) {
@@ -530,6 +532,17 @@ export default class CostSection extends TranslatedComponent {
ammoCosts.push(item);
ammoTotal += item.total;
}
// Add fighters
if (slotGroup[i].m.grp === 'fh') {
item = {
m: slotGroup[i].m,
max: slotGroup[i].m.getRebuildsPerBay() * slotGroup[i].m.getBays(),
cost: slotGroup[i].m.fightercost,
total: slotGroup[i].m.getRebuildsPerBay() * slotGroup[i].m.getBays() * slotGroup[i].m.fightercost
};
ammoCosts.push(item);
ammoTotal += item.total;
}
}
}
}
@@ -550,12 +563,13 @@ export default class CostSection extends TranslatedComponent {
item = {
m: { name: 'SRVs', class: '', rating: '' },
max: srvs,
cost: 6005,
total: srvs * 6005
cost: 1030,
total: srvs * 1030
};
ammoCosts.push(item);
ammoTotal += item.total;
}
// Calculate refuel costs if no scoop present
if (!scoop) {
item = {
@@ -606,6 +620,7 @@ export default class CostSection extends TranslatedComponent {
}
if (nextProps.ship != this.props.ship || nextProps.code != this.props.code) {
nextProps.ship.applyDiscounts(Persist.getShipDiscount(), Persist.getModuleDiscount());
this._updateAmmoCosts(nextProps.ship);
this._updateRetrofit(nextProps.ship, retrofitShip);
this._sortCost(nextProps.ship);

View File

@@ -0,0 +1,356 @@
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';
import Slider from '../components/Slider';
/**
* 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,
range: 0.1667,
maxRange: 6000
};
}
/**
* Set the initial weapons state
*/
componentWillMount() {
const data = this._calcWeapons(this.props.ship, this.state.against, this.state.range * this.state.maxRange);
this.setState({ weapons: data.weapons, totals: data.totals });
}
/**
* 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 data = this._calcWeapons(this.props.ship, this.state.against, this.state.range * this.state.maxRange);
this.setState({ weapons: data.weapons, totals: data.totals });
}
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
* @param {Object} range The engagement range
* @return {boolean} Returns the per-weapon damage
*/
_calcWeapons(ship, against, range) {
const translate = this.context.language.translate;
// Tidy up the range so that it's to 4 decimal places
range = Math.round(10000 * range) / 10000;
// Track totals
let totals = {};
totals.effectivenessShields = 0;
totals.effectiveDpsShields = 0;
totals.effectiveSDpsShields = 0;
totals.effectivenessHull = 0;
totals.effectiveDpsHull = 0;
totals.effectiveSDpsHull = 0;
let totalDps = 0;
let weapons = [];
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
const m = ship.hardpoints[i].m;
if (m.getDamage() && m.grp !== 'po') {
let dropoff = 1;
if (m.getFalloff()) {
// Calculate the dropoff % due to range
if (range > m.getRange()) {
// Weapon is out of range
dropoff = 0;
} else {
const falloff = m.getFalloff();
if (range > falloff) {
const dropoffRange = m.getRange() - falloff;
// Assuming straight-line falloff
dropoff = 1 - (range - falloff) / dropoffRange;
}
}
}
const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
let engineering;
if (m.blueprint && m.blueprint.name) {
engineering = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
if (m.blueprint.special && m.blueprint.special.id >= 0) {
engineering += ', ' + translate(m.blueprint.special.name);
}
}
const effectivenessShields = dropoff;
const effectiveDpsShields = m.getDps() * effectivenessShields;
const effectiveSDpsShields = (m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) * effectivenessShields : effectiveDpsShields);
const effectivenessHull = (m.getPiercing() >= against.properties.hardness ? 1 : m.getPiercing() / against.properties.hardness) * dropoff;
const effectiveDpsHull = m.getDps() * effectivenessHull;
const effectiveSDpsHull = (m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) * effectivenessHull : effectiveDpsHull);
totals.effectiveDpsShields += effectiveDpsShields;
totals.effectiveSDpsShields += effectiveSDpsShields;
totals.effectiveDpsHull += effectiveDpsHull;
totals.effectiveSDpsHull += effectiveSDpsHull;
totalDps += m.getDps();
weapons.push({ id: i,
mount: m.mount,
name: m.name || m.grp,
classRating,
engineering,
effectiveDpsShields,
effectiveSDpsShields,
effectivenessShields,
effectiveDpsHull,
effectiveSDpsHull,
effectivenessHull });
}
}
}
totals.effectivenessShields = totalDps == 0 ? 0 : totals.effectiveDpsShields / totalDps;
totals.effectivenessHull = totalDps == 0 ? 0 : totals.effectiveDpsHull / totalDps;
return { weapons, totals };
}
/**
* 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 data = this._calcWeapons(this.props.ship, against, this.state.range * this.state.maxRange);
this.setState({ against, weapons: data.weapons, totals: data.totals });
}
/**
* 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 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)}
{weapon.engineering ? ' (' + weapon.engineering + ')' : null }
</td>
<td className='ri'>{formats.round1(weapon.effectiveDpsShields)}</td>
<td className='ri'>{formats.round1(weapon.effectiveSDpsShields)}</td>
<td className='ri'>{formats.pct(weapon.effectivenessShields)}</td>
<td className='ri'>{formats.round1(weapon.effectiveDpsHull)}</td>
<td className='ri'>{formats.round1(weapon.effectiveSDpsHull)}</td>
<td className='ri'>{formats.pct(weapon.effectivenessHull)}</td>
</tr>);
}
}
return rows;
}
/**
* Update current range
* @param {number} range Range 0-1
*/
_rangeChange(range) {
const data = this._calcWeapons(this.props.ship, this.state.against, this.state.range * this.state.maxRange);
this.setState({ range, weapons: data.weapons, totals: data.totals });
}
/**
* Render damage dealt
* @return {React.Component} contents
*/
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { expanded, maxRange, range, totals } = 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'>
<th rowSpan='2' className='sortable' onClick={sortOrder.bind(this, 'n')}>{translate('weapon')}</th>
<th colSpan='3'>{translate('shields')}</th>
<th colSpan='3'>{translate('armour')}</th>
</tr>
<tr>
<th className='lft sortable' onClick={sortOrder.bind(this, 'edpss')}>{translate('effective dps')}</th>
<th className='sortable' onClick={sortOrder.bind(this, 'esdpss')}>{translate('effective sdps')}</th>
<th className='sortable' onClick={sortOrder.bind(this, 'es')}>{translate('effectiveness')}</th>
<th className='lft sortable' onClick={sortOrder.bind(this, 'edpsh')}>{translate('effective dps')}</th>
<th className='sortable' onClick={sortOrder.bind(this, 'esdpsh')}>{translate('effective sdps')}</th>
<th className='sortable' onClick={sortOrder.bind(this, 'eh')}>{translate('effectiveness')}</th>
</tr>
</thead>
<tbody>
{this._renderRows(translate, formats)}
</tbody>
<tfoot>
<tr className='main'>
<td className='ri'><i>{translate('total')}</i></td>
<td className='ri'><i>{formats.round1(totals.effectiveDpsShields)}</i></td>
<td className='ri'><i>{formats.round1(totals.effectiveSDpsShields)}</i></td>
<td className='ri'><i>{formats.pct(totals.effectivenessShields)}</i></td>
<td className='ri'><i>{formats.round1(totals.effectiveDpsHull)}</i></td>
<td className='ri'><i>{formats.round1(totals.effectiveSDpsHull)}</i></td>
<td className='ri'><i>{formats.pct(totals.effectivenessHull)}</i></td>
</tr>
</tfoot>
</table>
<table style={{ width: '80%', lineHeight: '1em', backgroundColor: 'transparent', margin: 'auto' }}>
<tbody >
<tr>
<td style={{ verticalAlign: 'top', padding: 0, width: '2.5em' }} onMouseEnter={termtip.bind(null, 'PHRASE_ENGAGEMENT_RANGE')} onMouseLeave={tooltip.bind(null, null)}>{translate('engagement range')}</td>
<td>
<Slider
axis={true}
onChange={this._rangeChange.bind(this)}
axisUnit={translate('m')}
percent={range}
max={maxRange}
scale={sizeRatio}
onResize={onWindowResize}
/>
</td>
<td className='primary' style={{ width: '10em', verticalAlign: 'top', fontSize: '0.9em', textAlign: 'left' }}>
{formats.f2(range * maxRange / 1000)}{units.km}
</td>
</tr>
</tbody>
</table></span> : null }
</span>
);
}
}

View File

@@ -0,0 +1,327 @@
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';
import Slider from '../components/Slider';
/**
* 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,
range: 0.1667,
maxRange: 6000
};
}
/**
* Set the initial weapons state
*/
componentWillMount() {
this.setState({ weapons: this._calcWeapons(this.props.ship, this.state.range * this.state.maxRange) });
}
/**
* 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) {
if (nextProps.code != this.props.code) {
this.setState({ weapons: this._calcWeapons(nextProps.ship, this.state.range * this.state.maxRange) });
}
return true;
}
/**
* Calculate the damage received by a ship
* @param {Object} ship The ship which will receive the damage
* @param {Object} range The engagement range
* @return {boolean} Returns the per-weapon damage
*/
_calcWeapons(ship, range) {
// Tidy up the range so that it's to 4 decimal places
range = Math.round(10000 * range) / 10000;
let weapons = [];
for (let grp in Modules.hardpoints) {
if (Modules.hardpoints[grp][0].damage && Modules.hardpoints[grp][0].damagedist) {
for (let mId in Modules.hardpoints[grp]) {
const m = new Module(Modules.hardpoints[grp][mId]);
let dropoff = 1;
if (m.getFalloff()) {
// Calculate the dropoff % due to range
if (range > m.getRange()) {
// Weapon is out of range
dropoff = 0;
} else {
const falloff = m.getFalloff();
if (range > falloff) {
const dropoffRange = m.getRange() - falloff;
// Assuming straight-line falloff
dropoff = 1 - (range - falloff) / dropoffRange;
}
}
}
const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
// Base DPS
const baseDps = m.getDps() * dropoff;
const baseSDps = m.getClip() ? ((m.getClip() * baseDps / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) * dropoff : baseDps;
// Effective DPS taking in to account shield resistance
let effectivenessShields = 0;
if (m.getDamageDist().E) {
effectivenessShields += m.getDamageDist().E * (1 - ship.shieldExplRes);
}
if (m.getDamageDist().K) {
effectivenessShields += m.getDamageDist().K * (1 - ship.shieldKinRes);
}
if (m.getDamageDist().T) {
effectivenessShields += m.getDamageDist().T * (1 - ship.shieldThermRes);
}
if (m.getDamageDist().A) {
effectivenessShields += m.getDamageDist().A;
}
effectivenessShields *= dropoff;
const effectiveDpsShields = baseDps * effectivenessShields;
const effectiveSDpsShields = baseSDps * effectivenessShields;
// Effective DPS taking in to account hull hardness and resistance
let effectivenessHull = 0;
if (m.getDamageDist().E) {
effectivenessHull += m.getDamageDist().E * (1 - ship.hullExplRes);
}
if (m.getDamageDist().K) {
effectivenessHull += m.getDamageDist().K * (1 - ship.hullKinRes);
}
if (m.getDamageDist().T) {
effectivenessHull += m.getDamageDist().T * (1 - ship.hullThermRes);
}
if (m.getDamageDist().A) {
effectivenessHull += m.getDamageDist().A;
}
effectivenessHull *= Math.min(m.getPiercing() / ship.hardness, 1) * dropoff;
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;
}
/**
* Update current range
* @param {number} range Range 0-1
*/
_rangeChange(range) {
this.setState({ range, weapons: this._calcWeapons(this.props.ship, this.state.range * this.state.maxRange) });
}
/**
* Render damage received
* @return {React.Component} contents
*/
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { expanded, maxRange, range } = 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>
<table style={{ width: '80%', lineHeight: '1em', backgroundColor: 'transparent', margin: 'auto' }}>
<tbody >
<tr>
<td style={{ verticalAlign: 'top', padding: 0, width: '2.5em' }} onMouseEnter={termtip.bind(null, 'PHRASE_ENGAGEMENT_RANGE')} onMouseLeave={tooltip.bind(null, null)}>{translate('engagement range')}</td>
<td>
<Slider
axis={true}
onChange={this._rangeChange.bind(this)}
axisUnit={translate('m')}
percent={range}
max={maxRange}
scale={sizeRatio}
onResize={onWindowResize}
/>
</td>
<td className='primary' style={{ width: '10em', verticalAlign: 'top', fontSize: '0.9em', textAlign: 'left' }}>
{formats.f2(range * maxRange / 1000)}{units.km}
</td>
</tr>
</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 Slot from './Slot';
import Persist from '../stores/Persist';
import { DamageAbsolute, DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret, ListModifications, Modified } from './SvgIcons';
import { Modifications } from 'coriolis-data/dist';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
/**
* Hardpoint / Utility Slot
@@ -33,24 +38,54 @@ export default class HardpointSlot extends Slot {
*/
_getSlotDetails(m, translate, formats, u) {
if (m) {
let classRating = `${m.class}${m.rating}${m.mount ? '/' + m.mount : ''}${m.missile ? m.missile : ''}`;
let classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
let { drag, drop } = this.props;
let { termtip, tooltip } = this.context;
let validMods = Modifications.modules[m.grp].modifications || [];
let showModuleResistances = Persist.showModuleResistances();
// Modifications tooltip shows blueprint and grade, if available
let modTT = translate('modified');
if (m && m.blueprint && m.blueprint.name) {
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
if (m.blueprint.special && m.blueprint.special.id >= 0) {
modTT += ', ' + translate(m.blueprint.special.name);
}
}
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
<div className={'cb'}>
<div className={'l'}>{classRating} {translate(m.name || m.grp)}</div>
<div className={'r'}>{m.mass}{u.T}</div>
<div className={'l'}>
{m.mount && m.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed /></span> : ''}
{m.mount && m.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : ''}
{m.mount && m.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : ''}
{m.getDamageDist() && m.getDamageDist().K ? <span onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /></span> : ''}
{m.getDamageDist() && m.getDamageDist().T ? <span onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /></span> : ''}
{m.getDamageDist() && m.getDamageDist().E ? <span onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /></span> : ''}
{m.getDamageDist() && m.getDamageDist().A ? <span onMouseOver={termtip.bind(null, 'absolute')} onMouseOut={tooltip.bind(null, null)}><DamageAbsolute /></span> : ''}
{classRating} {translate(m.name || m.grp)}{ m.mods && Object.keys(m.mods).length > 0 ? <span className='r' onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : null }
</div>
<div className={'r'}>{formats.round(m.getMass())}{u.T}</div>
</div>
<div className={'cb'}>
{ m.damage ? <div className={'l'}>{translate('damage')}: {m.damage} { m.ssdam ? <span>({formats.int(m.ssdam)} {u.MJ})</span> : null }</div> : null }
{ m.dps ? <div className={'l'}>{translate('DPS')}: {m.dps} { m.mjdps ? <span>({formats.int(m.mjdps)} {u.MJ})</span> : null }</div> : null }
{ m.thermload ? <div className={'l'}>{translate('T-Load')}: {m.thermload}</div> : null }
{ m.type ? <div className={'l'}>{translate('type')}: {m.type}</div> : null }
{ m.rof ? <div className={'l'}>{translate('ROF')}: {m.rof}{u.ps}</div> : null }
{ m.armourpen ? <div className={'l'}>{translate('pen')}: {m.armourpen}</div> : null }
{ m.shieldmul ? <div className={'l'}>+{formats.rPct(m.shieldmul)}</div> : null }
{ m.range ? <div className={'l'}>{m.range} <u>km</u></div> : null }
{ m.ammo >= 0 ? <div className={'l'}>{translate('ammo')}: {formats.int(m.clip)}+{formats.int(m.ammo)}</div> : null }
{ m.getDps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'dpssdps' : 'dps')} onMouseOut={tooltip.bind(null, null)}>{translate('DPS')}: {formats.round1(m.getDps())} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) })</span> : null }</div> : null }
{ m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'epsseps' : 'eps')} onMouseOut={tooltip.bind(null, null)}>{translate('EPS')}: {formats.round1(m.getEps())}{u.MW} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) }{u.MW})</span> : null }</div> : null }
{ m.getHps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'hpsshps' : 'hps')} onMouseOut={tooltip.bind(null, null)}>{translate('HPS')}: {formats.round1(m.getHps())} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getHps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) })</span> : null }</div> : null }
{ m.getDps() && m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, 'dpe')} onMouseOut={tooltip.bind(null, null)}>{translate('DPE')}: {formats.f1(m.getDps() / m.getEps())}</div> : null }
{ m.getRoF() ? <div className={'l'} onMouseOver={termtip.bind(null, 'rof')} onMouseOut={tooltip.bind(null, null)}>{translate('ROF')}: {formats.f1(m.getRoF())}{u.ps}</div> : null }
{ m.getRange() ? <div className={'l'}>{translate('range')} {formats.f1(m.getRange() / 1000)}{u.km}</div> : null }
{ m.getFalloff() ? <div className={'l'}>{translate('falloff')} {formats.round(m.getFalloff() / 1000)}{u.km}</div> : null }
{ m.getShieldBoost() ? <div className={'l'}>+{formats.pct1(m.getShieldBoost())}</div> : null }
{ m.getAmmo() ? <div className={'l'}>{translate('ammunition')}: {formats.int(m.getClip())}/{formats.int(m.getAmmo())}</div> : null }
{ m.getShotSpeed() ? <div className={'l'}>{translate('shotspeed')}: {formats.int(m.getShotSpeed())}{u.mps}</div> : null }
{ m.getPiercing() ? <div className={'l'}>{translate('piercing')}: {formats.int(m.getPiercing())}</div> : null }
{ m.getJitter() ? <div className={'l'}>{translate('jitter')}: {formats.f2(m.getJitter())}°</div> : null }
{ 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>;
} else {

View File

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

View File

@@ -5,12 +5,13 @@ import { Insurance } from '../shipyard/Constants';
import Link from './Link';
import ActiveLink from './ActiveLink';
import cn from 'classnames';
import { Cogs, CoriolisLogo, Hammer, Rocket, StatsBars } from './SvgIcons';
import { Cogs, CoriolisLogo, Hammer, Help, Rocket, StatsBars } from './SvgIcons';
import { Ships } from 'coriolis-data/dist';
import Persist from '../stores/Persist';
import { toDetailedExport } from '../shipyard/Serializer';
import ModalDeleteAll from './ModalDeleteAll';
import ModalExport from './ModalExport';
import ModalHelp from './ModalHelp';
import ModalImport from './ModalImport';
import Slider from './Slider';
import { outfitURL } from '../utils/UrlGenerators';
@@ -27,7 +28,7 @@ function normalizePercent(val) {
if (val === '' || isNaN(val)) {
return 0;
}
val = Math.round(val * 100) / 100;
val = Math.round(val * 1000) / 1000;
return val >= 100 ? 100 : val;
}
@@ -53,11 +54,11 @@ function selectAll(e) {
*/
export default class Header extends TranslatedComponent {
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
this.shipOrder = Object.keys(Ships).sort();
@@ -74,6 +75,7 @@ export default class Header extends TranslatedComponent {
this._openBuilds = this._openMenu.bind(this, 'b');
this._openComp = this._openMenu.bind(this, 'comp');
this._openSettings = this._openMenu.bind(this, 'settings');
this._showHelp = this._showHelp.bind(this);
this.languageOptions = [];
this.insuranceOptions = [];
this.state = {
@@ -203,6 +205,13 @@ export default class Header extends TranslatedComponent {
Persist.showTooltips(!Persist.showTooltips());
}
/**
* Toggle module resistances setting
*/
_toggleModuleResistances() {
Persist.showModuleResistances(!Persist.showModuleResistances());
}
/**
* Show delete all modal
* @param {SyntheticEvent} e Event
@@ -241,6 +250,17 @@ export default class Header extends TranslatedComponent {
/>);
}
/**
* Show help modal
* @param {SyntheticEvent} e Event
*/
_showHelp(e) {
let translate = this.context.language.translate;
e.preventDefault();
this.context.showModal(<ModalHelp title={translate('help')} />);
}
/**
* Show import modal
* @param {SyntheticEvent} e Event
@@ -359,6 +379,7 @@ export default class Header extends TranslatedComponent {
_getSettingsMenu() {
let translate = this.context.language.translate;
let tips = Persist.showTooltips();
let moduleResistances = Persist.showModuleResistances();
return (
<div className='menu-list no-wrap cap' onClick={ (e) => e.stopPropagation() }>
@@ -376,6 +397,10 @@ export default class Header extends TranslatedComponent {
<td>{translate('tooltips')}</td>
<td className={cn('ri', { disabled: !tips, 'primary-disabled': tips })}>{(tips ? '✓' : '✗')}</td>
</tr>
<tr className='cap ptr' onClick={this._toggleModuleResistances} >
<td>{translate('module resistances')}</td>
<td className={cn('ri', { disabled: !moduleResistances, 'primary-disabled': moduleResistances })}>{(moduleResistances ? '✓' : '✗')}</td>
</tr>
<tr>
<td>{translate('insurance')}</td>
<td className='ri'>
@@ -438,6 +463,7 @@ export default class Header extends TranslatedComponent {
Persist.addListener('deletedAll', update);
Persist.addListener('builds', update);
Persist.addListener('tooltips', update);
Persist.addListener('moduleresistances', update);
}
/**
@@ -508,8 +534,14 @@ export default class Header extends TranslatedComponent {
</div>
{openedMenu == 'settings' ? this._getSettingsMenu() : null}
</div>
<div className='r menu'>
<div className={cn('menu-header')} onClick={this._showHelp}>
<Help className='xl warning'/>
</div>
</div>
</header>
);
}
}
}

View File

@@ -1,6 +1,9 @@
import React from 'react';
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
@@ -18,30 +21,54 @@ export default class InternalSlot extends Slot {
_getSlotDetails(m, translate, formats, u) {
if (m) {
let classRating = m.class + m.rating;
let { drag, drop } = this.props;
let { drag, drop, ship } = this.props;
let { termtip, tooltip } = this.context;
let validMods = Modifications.modules[m.grp].modifications || [];
let showModuleResistances = Persist.showModuleResistances();
// Modifications tooltip shows blueprint and grade, if available
let modTT = translate('modified');
if (m && m.blueprint && m.blueprint.name) {
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
}
let mass = m.getMass() || m.cargo || m.fuel || 0;
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
<div className={'cb'}>
<div className={'l'}>{classRating} {translate(m.name || m.grp)}</div>
<div className={'r'}>{m.mass || m.cargo || m.fuel || 0}{u.T}</div>
<div className={'l'}>{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : ''}</div>
<div className={'r'}>{formats.round(mass)}{u.T}</div>
</div>
<div className={'cb'}>
{ m.optmass ? <div className={'l'}>{translate('optimal mass')}: {m.optmass}{u.T}</div> : null }
{ m.maxmass ? <div className={'l'}>{translate('max mass')}: {m.maxmass}{u.T}</div> : null }
{ m.getOptMass() ? <div className={'l'}>{translate('optimal mass')}: {formats.int(m.getOptMass())}{u.T}</div> : null }
{ m.getMaxMass() ? <div className={'l'}>{translate('max mass')}: {formats.int(m.getMaxMass())}{u.T}</div> : null }
{ m.bins ? <div className={'l'}>{m.bins} <u>{translate('bins')}</u></div> : null }
{ m.bays ? <div className={'l'}>{translate('bays')}: {m.bays}</div> : null }
{ m.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.ammo ? <div className={'l'}>{translate('ammo')}: {formats.gen(m.ammo)}</div> : null }
{ m.getAmmo() ? <div className={'l'}>{translate('ammunition')}: {formats.gen(m.getAmmo())}</div> : null }
{ m.cells ? <div className={'l'}>{translate('cells')}: {m.cells}</div> : null }
{ m.recharge ? <div className={'l'}>{translate('recharge')}: {m.recharge} <u>MJ</u>&nbsp;&nbsp;&nbsp;{translate('total')}: {m.cells * m.recharge}{u.MJ}</div> : null }
{ m.getShieldReinforcement() ? <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.range ? <div className={'l'}>{translate('range')} {m.range}{u.km}</div> : null }
{ m.time ? <div className={'l'}>{translate('time')}: {formats.time(m.time)}</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.getSpinup() ? <div className={'l'}>{translate('spinup')}: {formats.f1(m.getSpinup())}{u.s}</div> : null }
{ m.getTime() ? <div className={'l'}>{translate('time')}: {formats.time(m.getTime())}</div> : null }
{ m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
{ m.rangeLS ? <div className={'l'}>{translate('range')}: {m.rangeLS}{u.Ls}</div> : null }
{ m.rangeLS === null ? <div className={'l'}><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.armouradd ? <div className={'l'}>+{m.armouradd} <u className='cap'>{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>;
} else {

View File

@@ -4,6 +4,7 @@ import SlotSection from './SlotSection';
import InternalSlot from './InternalSlot';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
import { canMount } from '../utils/SlotFunctions';
/**
* Internal slot section
@@ -16,12 +17,18 @@ export default class InternalSlotSection extends SlotSection {
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props, context, 'internal', 'internal compartments');
super(props, context, 'internal', 'optional internal');
this._empty = this._empty.bind(this);
this._fillWithCargo = this._fillWithCargo.bind(this);
this._fillWithCells = this._fillWithCells.bind(this);
this._fillWithArmor = this._fillWithArmor.bind(this);
this._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 +48,7 @@ export default class InternalSlotSection extends SlotSection {
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
if (clobber || !slot.m) {
if ((clobber || !slot.m) && canMount(ship, slot, 'cr')) {
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
}
});
@@ -49,6 +56,86 @@ export default class InternalSlotSection extends SlotSection {
this._close();
}
/**
* Fill all slots with fuel tanks
* @param {SyntheticEvent} event Event
*/
_fillWithFuelTanks(event) {
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
if ((clobber || !slot.m) && canMount(ship, slot, '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) && canMount(ship, slot, '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) && canMount(ship, slot, '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) && canMount(ship, slot, '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) && canMount(ship, slot, '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
* @param {SyntheticEvent} event Event
@@ -58,7 +145,7 @@ export default class InternalSlotSection extends SlotSection {
let ship = this.props.ship;
let chargeCap = 0; // Capacity of single activation
ship.internal.forEach(function(slot) {
if ((!slot.m || (clobber && !ModuleUtils.isShieldGenerator(slot.m.grp))) && (!slot.eligible || slot.eligible.scb)) { // Check eligibility due to Orca special case
if ((clobber && !(slot.m && ModuleUtils.isShieldGenerator(slot.m.grp)) || !slot.m) && canMount(ship, slot, 'scb')) {
ship.use(slot, ModuleUtils.findInternal('scb', slot.maxClass, 'A'));
ship.setSlotEnabled(slot, chargeCap <= ship.shieldStrength); // Don't waste cell capacity on overcharge
chargeCap += slot.m.recharge;
@@ -76,7 +163,7 @@ export default class InternalSlotSection extends SlotSection {
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
if (clobber || !slot.m) {
if ((clobber || !slot.m) && canMount(ship, slot, 'hr')) {
ship.use(slot, ModuleUtils.findInternal('hr', Math.min(slot.maxClass, 5), 'D')); // Hull reinforcements top out at 5D
}
});
@@ -84,6 +171,22 @@ export default class InternalSlotSection extends SlotSection {
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) && canMount(ship, slot, '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
*/
@@ -99,7 +202,7 @@ export default class InternalSlotSection extends SlotSection {
let slots = [];
let { currentMenu, ship } = this.props;
let { originSlot, targetSlot } = this.state;
let { internal, fuelCapacity, ladenMass } = ship;
let { internal, fuelCapacity } = ship;
let availableModules = ship.getAvailableModules();
for (let i = 0, l = internal.length; i < l; i++) {
@@ -108,11 +211,13 @@ export default class InternalSlotSection extends SlotSection {
slots.push(<InternalSlot
key={i}
maxClass={s.maxClass}
availableModules={() => availableModules.getInts(s.maxClass, s.eligible)}
availableModules={() => availableModules.getInts(ship, s.maxClass, s.eligible)}
onOpen={this._openMenu.bind(this,s)}
onChange={this.props.onChange}
onSelect={this._selectModule.bind(this, s)}
selected={currentMenu == s}
enabled={s.enabled}
eligible={s.eligible}
m={s.m}
drag={this._drag.bind(this, s)}
dragOver={this._dragOverSlot.bind(this, s)}
@@ -129,15 +234,22 @@ export default class InternalSlotSection extends SlotSection {
/**
* Generate the section drop-down menu
* @param {Function} translate Translate function
* @param {Function} ship The ship
* @return {React.Component} Section menu
*/
_getSectionMenu(translate) {
_getSectionMenu(translate, ship) {
return <div className='select' onClick={e => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li>
<li className='lc' onClick={this._fillWithCargo}>{translate('cargo')}</li>
<li className='lc' onClick={this._fillWithCells}>{translate('scb')}</li>
<li className='lc' onClick={this._fillWithArmor}>{translate('hr')}</li>
<li className='lc' onClick={this._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>
</div>;

View File

@@ -0,0 +1,48 @@
/* eslint react/no-danger: 0 */
import React from 'react';
import { findDOMNode } from 'react-dom';
import TranslatedComponent from './TranslatedComponent';
/**
* Help Modal
*/
export default class ModalHelp extends TranslatedComponent {
static propTypes = {
title: React.PropTypes.string
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
}
/**
* Focus on textarea and select all
*/
componentDidMount() {
const e = findDOMNode(this.refs.exportField);
if (e) {
e.focus();
e.select();
}
}
/**
* Render the modal
* @return {React.Component} Modal Content
*/
render() {
const translate = this.context.language.translate;
const text = translate('HELP_TEXT');
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
<h2>{translate(this.props.title || 'Help')}</h2>
<div dangerouslySetInnerHTML={{ __html: text }} />
<button className='r dismiss cap' onClick={this.context.hideModal}>{translate('close')}</button>
</div>;
}
}

View File

@@ -11,6 +11,7 @@ import * as ModuleUtils from '../shipyard/ModuleUtils';
import { fromDetailedBuild } from '../shipyard/Serializer';
import { Download } from './SvgIcons';
import { outfitURL } from '../utils/UrlGenerators';
import * as CompanionApiUtils from '../utils/CompanionApiUtils';
const textBuildRegex = new RegExp('^\\[([\\w \\-]+)\\]\n');
const lineRegex = new RegExp('^([\\dA-Z]{1,2}): (\\d)([A-I])[/]?([FGT])?([SD])? ([\\w\\- ]+)');
@@ -112,6 +113,7 @@ export default class ModalImport extends TranslatedComponent {
this._importBackup = this._importBackup.bind(this);
this._importDetailedArray = this._importDetailedArray.bind(this);
this._importTextBuild = this._importTextBuild.bind(this);
this._importCompanionApiBuild = this._importCompanionApiBuild.bind(this);
this._validateImport = this._validateImport.bind(this);
}
@@ -183,6 +185,21 @@ export default class ModalImport extends TranslatedComponent {
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
* @param {string} buildStr Build string
@@ -315,7 +332,11 @@ export default class ModalImport extends TranslatedComponent {
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);
} else if (importData.ship && typeof importData.name !== undefined) { // Using JSON from a single ship build export
this._importDetailedArray([importData]); // Convert to array with singleobject
@@ -325,6 +346,7 @@ export default class ModalImport extends TranslatedComponent {
}
}
} catch (e) {
// console.log(e.stack);
this.setState({ errorMsg: (typeof e == 'string') ? e : 'Cannot Parse the data!' });
return;
}

View File

@@ -0,0 +1,89 @@
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,
value: React.PropTypes.number.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 = props.value;
}
/**
* 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, true);
this.setState({ value });
this.props.onChange();
}
/**
* Render the modification
* @return {React.Component} modification
*/
render() {
let translate = this.context.language.translate;
let { m, name } = this.props;
if (name === 'damagedist') {
// We don't show damage distribution
return null;
}
let symbol;
if (name === 'jitter') {
symbol = '°';
} else if (name !== 'burst' && name != 'burstrof') {
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,313 @@
import React from 'react';
import * as _ from 'lodash';
import { findDOMNode } from 'react-dom';
import TranslatedComponent from './TranslatedComponent';
import { isEmpty, 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);
this._toggleBlueprintsMenu = this._toggleBlueprintsMenu.bind(this);
this._toggleSpecialsMenu = this._toggleSpecialsMenu.bind(this);
this._rollWorst = this._rollWorst.bind(this);
this._rollRandom = this._rollRandom.bind(this);
this._rollAverage = this._rollAverage.bind(this);
this._rollBest = this._rollBest.bind(this);
this._reset = this._reset.bind(this);
}
/**
* Initialise state
* @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;
const { language, tooltip, termtip } = context;
const translate = language.translate;
// Set up the blueprints
let blueprints = [];
for (const blueprintName in Modifications.modules[m.grp].blueprints) {
for (const grade of Modifications.modules[m.grp].blueprints[blueprintName]) {
const close = this._blueprintSelected.bind(this, Modifications.blueprints[blueprintName].id, grade);
const key = blueprintName + ':' + grade;
blueprints.push(<div style={{ cursor: 'pointer' }} key={ key } onClick={ close }>{translate(Modifications.blueprints[blueprintName].name + ' grade ' + grade)}</div>);
}
}
// Set up the special effects
let specials = [];
if (Modifications.modules[m.grp].specials && Modifications.modules[m.grp].specials.length > 0) {
const close = this._specialSelected.bind(this, null);
specials.push(<div style={{ cursor: 'pointer' }} key={ 'none' } onClick={ close }>{translate('PHRASE_NO_SPECIAL')}</div>);
for (const specialName of Modifications.modules[m.grp].specials) {
const close = this._specialSelected.bind(this, specialName);
specials.push(<div style={{ cursor: 'pointer' }} key={ specialName } onClick={ close }>{translate(Modifications.specials[specialName].name)}</div>);
}
}
// Set up the modifications
const modifications = this._setModifications(props);
const blueprintMenuOpened = false;
const specialMenuOpened = false;
return { blueprintMenuOpened, blueprints, modifications, specialMenuOpened, specials };
}
/**
* Initialise the modifications
* @param {Object} props React Component properties
* @return {Object} list: Array of React Components
*/
_setModifications(props) {
const { m, onChange, ship } = props;
let modifications = [];
for (const modName of Modifications.modules[m.grp].modifications) {
if (!Modifications.modifications[modName].hidden) {
const key = modName + (m.getModValue(modName) / 100 || 0);
modifications.push(<Modification key={ key } ship={ ship } m={ m } name={ modName } value={ m.getModValue(modName) / 100 || 0 } onChange={ onChange }/>);
}
}
return modifications;
}
/**
* Toggle the blueprints menu
*/
_toggleBlueprintsMenu() {
const blueprintMenuOpened = !this.state.blueprintMenuOpened;
this.setState({ blueprintMenuOpened });
}
/**
* Activated when a blueprint is selected
* @param {int} blueprintId The ID of the selected blueprint
* @param {int} grade The grade of the selected blueprint
*/
_blueprintSelected(blueprintId, grade) {
const { m } = this.props;
const blueprint = Object.assign({}, _.find(Modifications.blueprints, function(o) { return o.id === blueprintId; }));
blueprint.grade = grade;
m.blueprint = blueprint;
const blueprintMenuOpened = false;
this.setState({ blueprintMenuOpened });
this.props.onChange();
}
/**
* Toggle the specials menu
*/
_toggleSpecialsMenu() {
const specialMenuOpened = !this.state.specialMenuOpened;
this.setState({ specialMenuOpened });
}
/**
* Activated when a special is selected
* @param {int} special The name of the selected special
*/
_specialSelected(special) {
const { m } = this.props;
if (m.blueprint) {
if (special === null) {
m.blueprint.special = null;
} else {
m.blueprint.special = Modifications.specials[special];
}
}
const specialMenuOpened = false;
this.setState({ specialMenuOpened, modifications: this._setModifications(this.props) });
this.props.onChange();
}
/**
* Set the result of a roll
* @param {object} ship The ship to which the roll applies
* @param {object} m The module to which the roll applies
* @param {string} featureName The modification feature to which the roll applies
* @param {number} value The value of the roll
*/
_setRollResult(ship, m, featureName, value) {
if (Modifications.modifications[featureName].method !== 'overwrite') {
if (m.grp == 'sb' && featureName == 'shieldboost') {
// Shield boosters are a special case. Their boost is dependent on their base so we need to calculate the value here
value = ((1 + m.shieldboost) * (1 + value) - 1) / m.shieldboost - 1;
}
}
if (Modifications.modifications[featureName].type == 'percentage') {
ship.setModification(m, featureName, value * 10000);
} else if (Modifications.modifications[featureName].type == 'numeric') {
ship.setModification(m, featureName, value * 100);
} else {
ship.setModification(m, featureName, value);
}
}
/**
* Provide a 'worst' roll within the information we have
*/
_rollWorst() {
const { m, ship } = this.props;
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
let value = features[featureName][0];
this._setRollResult(ship, m, featureName, value);
}
this.setState({ modifications: this._setModifications(this.props) });
this.props.onChange();
}
/**
* Provide an 'average' roll within the information we have
*/
_rollAverage() {
const { m, ship } = this.props;
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
let value = (features[featureName][0] + features[featureName][1]) / 2;
this._setRollResult(ship, m, featureName, value);
}
this.setState({ modifications: this._setModifications(this.props) });
this.props.onChange();
}
/**
* Provide a random roll within the information we have
*/
_rollRandom() {
const { m, ship } = this.props;
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
let value = features[featureName][0] + (Math.random() * (features[featureName][1] - features[featureName][0]));
this._setRollResult(ship, m, featureName, value);
}
this.setState({ modifications: this._setModifications(this.props) });
this.props.onChange();
}
/**
* Provide a 'best' roll within the information we have
*/
_rollBest() {
const { m, ship } = this.props;
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
let value = features[featureName][1];
this._setRollResult(ship, m, featureName, value);
}
this.setState({ modifications: this._setModifications(this.props) });
this.props.onChange();
}
/**
* Reset modification information
*/
_reset() {
const { m, ship } = this.props;
ship.clearModifications(m);
ship.clearBlueprint(m);
this.setState({ modifications: this._setModifications(this.props) });
this.props.onChange();
}
/**
* Render the list
* @return {React.Component} List
*/
render() {
const { language, tooltip, termtip } = this.context;
const translate = language.translate;
const { m } = this.props;
const { blueprintMenuOpened, specialMenuOpened } = this.state;
const _toggleBlueprintsMenu = this._toggleBlueprintsMenu;
const _toggleSpecialsMenu = this._toggleSpecialsMenu;
const _rollBest = this._rollBest;
const _rollWorst = this._rollWorst;
const _rollAverage = this._rollAverage;
const _rollRandom = this._rollRandom;
const _reset = this._reset;
let blueprintLabel;
let haveBlueprint = false;
if (m.blueprint && !isEmpty(m.blueprint)) {
blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
haveBlueprint = true;
} else {
blueprintLabel = translate('PHRASE_SELECT_BLUEPRINT');
}
let specialLabel;
let haveSpecial = false;
if (m.blueprint && m.blueprint.special) {
specialLabel = m.blueprint.special.name;
} else {
specialLabel = translate('PHRASE_SELECT_SPECIAL');
}
const showBlueprintsMenu = blueprintMenuOpened;
const showSpecial = haveBlueprint && this.state.specials.length > 0;
const showSpecialsMenu = specialMenuOpened;
const showRolls = haveBlueprint && !blueprintMenuOpened && !specialMenuOpened;
const showMods = !blueprintMenuOpened && !specialMenuOpened;
return (
<div
className={cn('select', this.props.className)}
onClick={(e) => e.stopPropagation() }
onContextMenu={stopCtxPropagation}
>
<div className={ cn('section-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleBlueprintsMenu}>{blueprintLabel}</div>
{ showBlueprintsMenu ? this.state.blueprints : '' }
{ showSpecial ? <div className={ cn('section-menu', { selected: specialMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleSpecialsMenu}>{specialLabel}</div> : '' }
{ showSpecialsMenu ? this.state.specials : '' }
{ showRolls ?
<table style={{ width: '100%', backgroundColor: 'transparent' }}>
<tbody>
<tr>
<td> { translate('roll') }: </td>
<td style={{ cursor: 'pointer' }} onClick={_rollWorst} onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')} onMouseOut={tooltip.bind(null, null)}> { translate('worst') } </td>
<td style={{ cursor: 'pointer' }} onClick={_rollAverage}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_AVERAGE')} onMouseOut={tooltip.bind(null, null)}> { translate('average') } </td>
<td style={{ cursor: 'pointer' }} onClick={_rollBest}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')} onMouseOut={tooltip.bind(null, null)}> { translate('best') } </td>
<td style={{ cursor: 'pointer' }} onClick={_rollRandom} onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')} onMouseOut={tooltip.bind(null, null)}> { translate('random') } </td>
<td style={{ cursor: 'pointer' }} onClick={_reset}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')} onMouseOut={tooltip.bind(null, null)}> { translate('reset') } </td>
</tr>
</tbody>
</table> : '' }
{ showMods ?
<span onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')} onMouseOut={tooltip.bind(null, null)} >
{ this.state.modifications }
</span> : '' }
</div>
);
}
}

View File

@@ -0,0 +1,91 @@
import React from 'react';
import cn from 'classnames';
import TranslatedComponent from './TranslatedComponent';
/**
* 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', borderSpacing: '0.5em' }}>
<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,73 @@
import React from 'react';
import cn from 'classnames';
import TranslatedComponent from './TranslatedComponent';
import { DamageAbsolute, 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='5' 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, 'absolute')} onMouseOut={tooltip.bind(null, null)}><DamageAbsolute /> {formats.f1(ship.totalAbsDps)}</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='5' 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, 'absolute')} onMouseOut={tooltip.bind(null, null)}><DamageAbsolute /> {formats.f1(ship.totalAbsSDps)}</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='5' 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, 'absolute')} onMouseOut={tooltip.bind(null, null)}><DamageAbsolute /> {formats.f1(ship.totalAbsDpe)}</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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,8 +2,8 @@ import React from 'react';
import cn from 'classnames';
import SlotSection from './SlotSection';
import StandardSlot from './StandardSlot';
import Module from '../shipyard/Module';
import { diffDetails } from '../utils/SlotFunctions';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import * as ShipRoles from '../shipyard/ShipRoles';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
@@ -18,7 +18,7 @@ export default class StandardSlotSection extends SlotSection {
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props, context, 'standard', 'standard');
super(props, context, 'standard', 'core internal');
this._optimizeStandard = this._optimizeStandard.bind(this);
this._selectBulkhead = this._selectBulkhead.bind(this);
}
@@ -86,12 +86,10 @@ export default class StandardSlotSection extends SlotSection {
* @return {Array} Array of Slots
*/
_getSlots() {
let { translate, units, formats } = this.context.language;
let { ship, currentMenu } = this.props;
let slots = new Array(8);
let open = this._openMenu;
let select = this._selectModule;
let selBulkhead = this._selectBulkhead;
let st = ship.standard;
let avail = ship.getAvailableModules().standard;
let bh = ship.bulkheads;
@@ -103,6 +101,7 @@ export default class StandardSlotSection extends SlotSection {
onOpen={open.bind(this, bh)}
onSelect={this._selectBulkhead}
selected={currentMenu == bh}
onChange={this.props.onChange}
ship={ship}
/>;
@@ -113,8 +112,9 @@ export default class StandardSlotSection extends SlotSection {
onOpen={open.bind(this, st[0])}
onSelect={select.bind(this, st[0])}
selected={currentMenu == st[0]}
onChange={this.props.onChange}
ship={ship}
warning={m => m.pGen < ship.powerRetracted}
warning={m => m instanceof Module ? m.getPowerGeneration() < ship.powerRetracted : m.pgen < ship.powerRetracted}
/>;
slots[2] = <StandardSlot
@@ -124,8 +124,9 @@ export default class StandardSlotSection extends SlotSection {
onOpen={open.bind(this, st[1])}
onSelect={select.bind(this, st[1])}
selected={currentMenu == st[1]}
onChange={this.props.onChange}
ship={ship}
warning={m => m.maxmass < (ship.ladenMass - st[1].mass + m.mass)}
warning={m => m instanceof Module ? m.getMaxMass() < (ship.ladenMass - st[1].mass + m.mass) : m.maxmass < (ship.ladenMass - st[1].mass + m.mass)}
/>;
@@ -135,6 +136,7 @@ export default class StandardSlotSection extends SlotSection {
modules={avail[2]}
onOpen={open.bind(this, st[2])}
onSelect={select.bind(this, st[2])}
onChange={this.props.onChange}
ship={ship}
selected={currentMenu == st[2]}
/>;
@@ -145,6 +147,7 @@ export default class StandardSlotSection extends SlotSection {
modules={avail[3]}
onOpen={open.bind(this, st[3])}
onSelect={select.bind(this, st[3])}
onChange={this.props.onChange}
ship={ship}
selected={currentMenu == st[3]}
/>;
@@ -156,8 +159,9 @@ export default class StandardSlotSection extends SlotSection {
onOpen={open.bind(this, st[4])}
onSelect={select.bind(this, st[4])}
selected={currentMenu == st[4]}
onChange={this.props.onChange}
ship={ship}
warning= {m => m.enginecapacity < ship.boostEnergy}
warning={m => m instanceof Module ? m.getEnginesCapacity() < ship.boostEnergy : m.engcap < ship.boostEnergy}
/>;
slots[6] = <StandardSlot
@@ -167,8 +171,8 @@ export default class StandardSlotSection extends SlotSection {
onOpen={open.bind(this, st[5])}
onSelect={select.bind(this, st[5])}
selected={currentMenu == st[5]}
onChange={this.props.onChange}
ship={ship}
warning= {m => m.enginecapacity < ship.boostEnergy}
/>;
slots[7] = <StandardSlot
@@ -178,6 +182,7 @@ export default class StandardSlotSection extends SlotSection {
onOpen={open.bind(this, st[6])}
onSelect={select.bind(this, st[6])}
selected={currentMenu == st[6]}
onChange={this.props.onChange}
ship={ship}
warning= {m => m.fuel < st[2].m.maxfuel} // Show warning when fuel tank is smaller than FSD Max Fuel
/>;

View File

@@ -319,6 +319,108 @@ export class Warning extends SvgIcon {
}
}
/**
* Absolute damage
*/
export class DamageAbsolute 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 <ellipse cx='100' cy='100' rx='90' ry='90' fillOpacity='0' />;
}
}
/**
* Thermal damage
*/
export class DamageThermal extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <g>
<ellipse cx='100' cy='100' rx='90' ry='90' fillOpacity='0' />
<ellipse cx='100' cy='100' rx='30' ry='30' fillOpacity='1' />
<path d='M100 20v80' />
</g>;
}
}
/**
* Kinetic damage
*/
export class DamageKinetic extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <g>
<ellipse cx='100' cy='100' rx='90' ry='90' fillOpacity='0' />
<ellipse cx='62' cy='67' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='62' cy='101' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='62' cy='135' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='50' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='84' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='118' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='152' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='138' cy='67' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='138' cy='101' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='138' cy='135' rx='5' ry='5' fillOpacity='1' />
</g>;
}
}
/**
* Explosive damage
*/
export class DamageExplosive extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <g>
<ellipse cx='100' cy='100' rx='50' ry='50' fillOpacity='0' />
<ellipse cx='100' cy='20' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='156.57' cy='36.57' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='180' cy='100' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='156.57' cy='163.43' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='180' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='43.43' cy='163.43' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='20' cy='100' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='43.43' cy='36.57' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='75' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='125' cy='100' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='100' cy='125' rx='5' ry='5' fillOpacity='1' />
<ellipse cx='75' cy='100' rx='5' ry='5' fillOpacity='1' />
</g>;
}
}
/**
* Fixed mount hardpoint
*/
@@ -334,11 +436,11 @@ export class MountFixed extends SvgIcon {
*/
svg() {
return <g>
<circle fillOpacity='0' r='70' cy='100' cx='100' strokeWidth='5' />
<line y2='60' x2='101' y1='0' x1='101' strokeWidth='5' />
<line y2='101' x2='200' y1='101' x1='140' strokeWidth='5' />
<line y2='101' x2='60' y1='101' x1='0' strokeWidth='5' />
<line y2='200' x2='101' y1='140' x1='101' strokeWidth='5' />
<circle cx='100' cy='100' r='76' fillOpacity='0' />
<path d='M0 100h48' />
<path d='M152 100h48' />
<path d='M100 0v48' />
<path d='M100 152v48' />
</g>;
}
}
@@ -358,8 +460,8 @@ export class MountGimballed extends SvgIcon {
*/
svg() {
return <g>
<ellipse ry='25' rx='95' cy='100' cx='100' fillOpacity='0' strokeWidth='5' />
<ellipse ry='95' rx='25' cy='100' cx='100' fillOpacity='0' strokeWidth='5' />
<ellipse cx='100' cy='100' rx='90' ry='25' fillOpacity='0' />
<ellipse cx='100' cy='100' rx='20' ry='95' fillOpacity='0' />
</g>;
}
}
@@ -379,13 +481,64 @@ export class MountTurret extends SvgIcon {
*/
svg() {
return <g>
<line y2='170' x2='162' y1='170' x1='8' strokeWidth='6' />
<path d='m13,138l144,0l0,-50l-27,-40l-90,0l-27,40l0,50z' id='svg_12' fillOpacity='0' strokeWidth='6' />
<line y2='91' x2='200' y1='91' x1='159' strokeWidth='6' />
<path d='M40 50 A 40 40 0 0 0 0 90' />
<path d='M40 50h40' />
<path d='M120 90 A 40 40 0 0 0 80 50' />
<path d='M0 90v40' />
<path d='M120 90v40' />
<path d='M0 120h120' />
<path d='M120 90h80' />
<path d='M0 160h120' />
</g>;
}
}
/**
* 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
*/
@@ -399,6 +552,90 @@ export class Rocket extends SvgIcon {
}
}
/**
* Help
*/
export class Help extends SvgIcon {
/**
* Overriden view box
* @return {String} view box
*/
viewBox() { return '0 0 200 200'; }
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return <path d="M90.381 140.644a225.03 225.03 0 0 1-.106-5.706c0-7.47 1.058-13.92 3.172-19.346 1.55-4.087 4.052-8.21 7.505-12.367 2.536-3.031 7.1-7.453 13.688-13.267 6.59-5.815 10.872-10.448 12.844-13.9 1.974-3.453 2.96-7.224 2.96-11.312 0-7.4-2.89-13.9-8.667-19.503-5.779-5.602-12.862-8.403-21.248-8.403-8.104 0-14.87 2.536-20.296 7.61-5.427 5.074-8.986 13.003-10.677 23.784L50 65.91c1.762-14.448 6.995-25.511 15.698-33.194 8.704-7.68 20.208-11.522 34.514-11.522 15.152 0 27.237 4.124 36.258 12.369C145.49 41.807 150 51.779 150 63.477c0 6.766-1.585 13.003-4.756 18.712-3.17 5.708-9.372 12.65-18.605 20.824-6.201 5.496-10.253 9.55-12.156 12.156-1.904 2.609-3.312 5.603-4.23 8.986-.915 3.383-1.443 8.88-1.584 16.49H90.38zm-1.163 38.162v-21.67h21.67v21.67h-21.67z"/>;
}
}
/**
* 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
*/

View File

@@ -68,6 +68,7 @@ export default class UtilitySlotSection extends SlotSection {
availableModules={() => availableModules.getHps(h.maxClass)}
onOpen={this._openMenu.bind(this,h)}
onSelect={this._selectModule.bind(this, h)}
onChange={this.props.onChange}
selected={currentMenu == h}
drag={this._drag.bind(this, h)}
dragOver={this._dragOverSlot.bind(this, h)}
@@ -104,9 +105,17 @@ export default class UtilitySlotSection extends SlotSection {
<li className='c' onClick={_use.bind(this, 'sb', 'B', null)}>B</li>
<li className='c' onClick={_use.bind(this, 'sb', 'A', null)}>A</li>
</ul>
<div className='select-group cap'>{translate('cm')}</div>
<div className='select-group cap'>{translate('hs')}</div>
<ul>
<li className='lc' onClick={_use.bind(this, 'cm', null, 'Heat Sink Launcher')}>{translate('Heat Sink Launcher')}</li>
<li className='lc' onClick={_use.bind(this, 'hs', null, 'Heat Sink Launcher')}>{translate('Heat Sink Launcher')}</li>
</ul>
<div className='select-group cap'>{translate('ch')}</div>
<ul>
<li className='lc' onClick={_use.bind(this, 'ch', null, 'Chaff Launcher')}>{translate('Chaff Launcher')}</li>
</ul>
<div className='select-group cap'>{translate('po')}</div>
<ul>
<li className='lc' onClick={_use.bind(this, 'po', null, 'Point Defence')}>{translate('Point Defence')}</li>
</ul>
</div>;
}

View File

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

View File

@@ -28,6 +28,17 @@ export const terms = {
PHRASE_SG_RECOVER: 'Recovery (to 50%) after collapse',
PHRASE_UNLADEN: 'Ship mass excluding fuel and cargo',
PHRASE_UPDATE_RDY: 'Update Available! Click to refresh',
PHRASE_ENGAGEMENT_RANGE: 'The distance between your ship and its target',
PHRASE_SELECT_BLUEPRINT: 'Click to select a blueprint',
PHRASE_BLUEPRINT_WORST: 'Worst primary values for this blueprint',
PHRASE_BLUEPRINT_AVERAGE: 'Average primary values for this blueprint',
PHRASE_BLUEPRINT_RANDOM: 'Random selection between worst and best primary values for this blueprint',
PHRASE_BLUEPRINT_BEST: 'Best primary values for this blueprint',
PHRASE_BLUEPRINT_RESET: 'Remove all modifications and blueprint',
PHRASE_SELECT_SPECIAL: 'Click to select an experimental effect',
PHRASE_NO_SPECIAL: 'No experimental effect',
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
@@ -37,11 +48,13 @@ export const terms = {
bsg: 'Bi-Weave Shield Generator',
c: 'Cannon',
cc: 'Collector Limpet Controller',
cm: 'Countermeasure',
ch: 'Chaff Launcher',
cr: 'Cargo Rack',
cs: 'Cargo Scanner',
cs: 'Manifest Scanner',
dc: 'Docking Computer',
ec: 'Electronic Countermeasure',
fc: 'Fragment Cannon',
fh: 'Fighter Hangar',
fi: 'FSD Interdictor',
fs: 'Fuel Scoop',
fsd: 'Frame Shift Drive',
@@ -49,17 +62,24 @@ export const terms = {
fx: 'Fuel Transfer Limpet Controller',
hb: 'Hatch Breaker Limpet Controller',
hr: 'Hull Reinforcement Package',
hs: 'Heat Sink Launcher',
kw: 'Kill Warrant Scanner',
ls: 'Life Support',
mc: 'Multi-cannon',
ml: 'Mining Laser',
mr: 'Missile Rack',
mrp: 'Module Reinforcement Package',
nl: 'Mine Launcher',
pa: 'Plasma Accelerator',
pas: 'Planetary Approach Suite',
pc: 'Prospector Limpet Controller',
pce: 'Economy Class Passenger Cabin',
pci: 'Business Class Passenger Cabin',
pcm: 'First Class Passenger Cabin',
pcq: 'Luxury Passenger Cabin',
pd: 'power distributor',
pl: 'Pulse Laser',
po: 'Point Defence',
pp: 'Power Plant',
psg: 'Prismatic Shield Generator',
pv: 'Planetary Vehicle Hangar',
@@ -73,5 +93,225 @@ export const terms = {
t: 'thrusters',
tp: 'Torpedo Pylon',
ul: 'Burst Laser',
ws: 'Frame Shift Wake Scanner'
ws: 'Frame Shift Wake Scanner',
// Items on the outfitting page
// Notification of restricted slot
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',
// Blueprint rolls
worst: 'Worst',
average: 'Average',
random: 'Random',
best: 'Best',
reset: 'Reset',
// 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',
'engagement range': 'Engagement range',
'total': 'Total',
// 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',
shotspeed: 'Shot speed',
spinup: 'Spin up time',
syscap: 'Systems capacity',
sysrate: 'Systems recharge rate',
thermload: 'Thermal load',
thermres: 'Thermal resistance',
wepcap: 'Weapons capacity',
weprate: 'Weapons recharge rate',
// Help text
HELP_TEXT: `
<h1>Introduction</h1>
Coriolis is a ship builder for Elite: Dangerous. This help file provides you with the information you need to use Coriolis.
<h1>Importing Your Ship Into Coriolis</h1>
Often, you will want to start with your existing ship in Coriolis and see how particular changes might affect it, for example upgrading your FSD. There are a number of tools that can be used to import your ship without you having to create it manually. This has the added benefit of copying over any engineering modifications that have taken place as well. </p>
<h2>Importing Your Ship From EDDI</h2>
To import your ship from EDDI first ensure that your connection to the Frontier servers&apos; companion API is working. To do this check the &apos;Companion App&apos; tab where you should see "Your connection to the companion app is operational". If not then follow the instructions in the companion app tab in EDDI to connect to the Frontier servers.</p>
Once you have a working companion API connection go to the &apos;Shipyard&apos; tab. At the right-hand side of each ship is an &apos;Export to Coriolis&apos; button that will open your default web browser in Coriolis with the ship&apos;s build. </p>
Note that Internet Explorer and Edge might not import correctly, due to their internal restrictions on URL length. If you find that this is the case then please change your default browser to Chrome. </p>
<h2>Importing Your Ship From EDMC</h2>
To import your ship from EDMC once your connection to the Frontier servers&apos; companion API is working go to &apos;Settings -&gt;Configuration&apos; and set the &apos;Preferred Shipyard&apos; to &apos;Coriolis&apos;. Once this is set up clicking on your ship in the main window will open your default web browser in Coriolis with the ship&apos;s build.</p>
Note that Internet Explorer and Edge might not import correctly, due to their internal restrictions on URL length. If you find that this is the case then please change your default browser to Chrome. </p>
<h1>Understanding And Using The Outfitting Panels</h1>
The outfitting page is where you will spend most of your time, and contains the information for your ship. Information on each of the panels is provided below. </p>
<h2>Key Values</h2>
Along the top of the screen are some of the key values for your build. This is a handy reference for the values, but more information is provided for the values in the further panels. </p>
Here, along with most places in Coriolis, acronyms will have tooltips explaining what they mean. Hover over the acronym to obtain more detail, or look in the glossary at the end of this help.</p>
All values are the highest possible, assuming that you have maximum pips in the relevant capacitor (ENG for speed, WEP for time to drain, etc.).</p>
<h2>Modules</h2>
The next set of panels laid out horizontally across the screen contain the modules you have put in your build. From left to right these are the core modules, the internal modules, the hardpoints and the utility mounts. These represent the available slots in your ship and cannot be altered. Each slot has a class, or size, and in general any module up to a given size can fit in a given slot (exceptions being bulkheads, life support and sensors in core modules and restricted internal slots, which can only take a subset of module depending on their restrictions). </p>
To add a module to a slot left-click on the slot and select the required module. Only the modules capable of fitting in the selected slot will be shown. </p>
To remove a module from a slot right-click on the module. </p>
To move a module from one slot to another drag it. If you instead want to copy the module drag it whilst holding down the &apos;Alt&apos; key. </p>
<h2>Power Management</h2>
The power management panel provides information about power usage and priorities. It allows you to enable and disable individual modules, as well as set power priorities for each module.</p>
<h2>Costs</h2>
The costs panel provides information about the costs for each of your modules, and the total cost and insurance for your build. By default Coriolis uses the standard costs, however discounts for your ship, modules and insurance can be altered in the &apos;Settings&apos; at the top-right of the page.</p>
The retrofit costs provides information about the costs of changing the base build for your ship, or your saved build, to the current build.</p>
The reload costs provides information about the costs of reloading your current build.</p>
<h2>Offence Summary</h2>
The offence summary panel provides information about the damage that you deal with your weapons.</p>
The first headline gives an overall damage per second rating: this is the optimum amount of damage the build will do per second according to weapon statistics. After that is a breakdown of the damage per second the build will do for each type of damage: absolute, explosive, kinetic, and thermal.</p>
The next headline gives an overall sustained damage per second rating: this is the optimum amount of damage the build will do per second over a longer period of time, taking in to account ammunition clip capacities and reload times. After that is a breakdown of the sustained damage per second the build will do for each type of damage: absolute, explosive, kinetic, and thermal.</p>
The final headline gives an overall damage per energy rating: this is the amount of damage the build will do per unit of weapon capacitor energy expended. After that is a breakdown of the damage per energy the build will do for each type of damage: absolute, explosive, kinetic, and thermal.</p>
<h2>Defence Summary</h2>
The defence summary panel provides information about the strength of your defences and the damage that you receive from opponents.</p>
The first headline gives your total shield strength (if you have shields), taking in to account your base shield plus boosters. After that are the details of how long it will take for your shields to recover from 0 to 50% (recovery time) and from 50% to 100% (recharge time). The next line provides a breakdown of the shield damage taken from different damage types. For example, if you damage from kinetic is 60% then it means that a weapon usually dealing 10 points of damage will only deal 6, the rest being resisted by the shield. Note that this does not include any resistance alterations due to pips in your SYS capacitor.</p>
The second headline gives your total shield cell strength (if you have shield cell banks). This is the sum of the recharge of all of equipped shield cell banks.</p>
The third headline gives your total armour strength, taking in to account your base armour plus hull reinforcement packages. The next line provides a breakdown of the hull damage taken from different damage types. For example, if you damage from kinetic is 120% then it means that a weapon usually dealing 10 points of damage will deal 12.</p>
The fourth headline gives your total module protection strength from module reinforcement packages. The next line provides a breakdown of the protection for both internal and external modules whilst all module reinforcement packages are functioning. For example, if external module protection is 20% then 10 points of damage will 2 points of damage to the module reinforcement packages and 8 points of damage to the module</p>
<h2>Movement Summary</h2>
The movement summary panel provides information about the build&apos;s speed and agility.</p>
Along the top of this panel are the number of pips you put in to your ENG capacitor, from 0 to 4 and also include 4 pips and boost (4b). Along the side of this panel are the names of the metrics. These are:
<dl>
<dt>Speed</dt><dd>The fastest the ship can move, in metres per second</dd>
<dt>Pitch</dt><dd>The fastest the ship can raise or lower its nose, in degrees per second</dd>
<dt>Roll</dt><dd>The fastest the ship can roll its body, in degrees per second</dd>
<dt>Yaw</dt><dd>The fastest the ship can turn its nose left or right, in degrees per second</dd>
</dl>
<h2>Jump Range</h2>
The jump range panel provides information about the build&apos; jump range. The graph shows how the build&apos;s jump range changes with the amount of cargo on-board. The slider can be altered to change the amount of fuel you have on-board.
<h2>Damage Dealt</h2>
The damage dealt panel provides information about the effectiveness of your build&apos;s weapons against opponents&apos; shields and hull at different engagement distances.</p>
The ship against which you want to check damage dealt can be selected by clicking on the red ship icon or the red ship name at the top of this panel.</p>
The main section of this panel is a table showing your weapons and their effectiveness. Effectiveness against shields takes in to account the weapon and its engagement range, but does not consider the target ship&apos;s resistances. Effectiveness against hull takes in to account the weapon and, its engagement range and the target&apos;s hardness, but does not consider the target ship&apos;s resistances. This is because resistances will alter significantly depending on the target&apos;s build.</p>
Effective DPS and effective SDPS are the equivalent of DPS and SDPS for the weapon. Effectiveness is a percentage value that shows how effective the DPS of the weapon is compared in reality against the given target compared to the weapon&apos;s stated DPS. Effectiveness can never go above 100%.</p>
Total effective DPS, SDPS and effectiveness against both shields and hull are provided at the bottom of the table.</p>
At the bottom of this panel you can change your engagement range. The engagement range is the distance between your ship and your target. Many weapons suffer from what is known as damage falloff, where their effectiveness decreases the further the distance between your ship and your target. This allows you to model the effect of engaging at different ranges.
Note that this panel only shows enabled weapons, so if you want to see your overall effectiveness for a subset of your weapons you can disable the undesired weapons in the power management panel.
<h2>Damage Received</h2>
The damage received panel provides information about the effectiveness of your build&apos;s defences against opponent&apos;s weapons at different engagement range. Features and functions are the same as the damage dealt panel, except that it does take in to account your build&apos;s resistances.</p>
<h1>Keyboard Shortcuts</h1>
<dl>
<dt>Ctrl-e</dt><dd>open export dialogue (outfitting page only)</dd>
<dt>Ctrl-h</dt><dd>open help dialogue</dd>
<dt>Ctrl-i</dt><dd>open import dialogue</dd>
<dt>Ctrl-o</dt><dd>open shortlink dialogue</dd>
<dt>Esc</dt><dd>close any open dialogue</dd>
</dl>
<h1>Glossary</h1>
<dl>
<dt>Absolute damage</dt><dd>A type of damage, without any protection. Absolute damage is always dealt at 100% regardless of if the damage is to shields, hull or modules, and irrespective of resistances</dd>
<dt>DPS</dt><dd>Damage per second; the amount of damage that a weapon or a ship can deal per second to a target under optimum conditions</dd>
<dt>EPS</dt><dd>Energy per second; the amount of energy that a weapon or a ship drains from the weapons capacitor per second when firing</dd>
<dt>HPS</dt><dd>Heat per second; the amount of heat that a weapon or a ship generates per second when firing</dd>
<dt>Effectivness</dt><dd>A comparison of the maximum DPS of a given weapon to the actual DPS of the given weapon in a specific situation. DPS can be reduced by range to the target, the target&apos;s hull and shield resistances, and the target&apos;s hardness</dd>
<dt>Explosive damage</dt><dd>A type of damage, protected against by explosive resistance</dd>
<dt>Hardness</dt><dd>The inherent resistance to damage of a ship&apos;s hull. Hardness is defined on a per-ship basis and there is currently nothing that can be done to change it. Hardness of a ship&apos;s hull is compared to the piercing of weapons: if piercing is higher than hardness the weapon does 100% damage, otherwise it does a fraction of its damage calculated as piercing/hardness</dd>
<dt>Falloff</dt><dd>The distance at which a weapons starts to do less damage than its stated DPS</dd>
<dt>Kinetic damage</dt><dd>A type of damage, protected against by kinetic resistance</dd>
<dt>SDPS</dt><dd>Sustained damage per second; the amount of damage that a weapon or a ship can deal per second to a target, taking in to account ammunition reload</dd>
<dt>SEPS</dt><dd>Sustained energy per second; the amount of energy that a weapon or a ship drains from the weapons capacitor per second when firing, taking in to account ammunition reload</dd>
<dt>SHPS</dt><dd>Sustained heat per second; the amount of heat that a weapon or a ship generates per second when firing, taking in to account ammunition reload</dd>
<dt>Thermal damage</dt><dd>A type of damage, protected against by thermal resistance</dd>
</dl>
`,
};

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

@@ -29,7 +29,7 @@ export default class AboutPage extends Page {
<p>This is a clone of the Coriolis project, whose original author is currently unable to maintain it. This clone is maintained by the <a href="http://edcd.github.io/">EDCD community</a>.</p>
<p>To recover your builds, go to <a href='https://coriolis.io/' target='_blank'>https://coriolis.io/</a>, backup your builds (Settings / Backup), copy the text, return here and import (Settings / Import).</p>
<p>The Coriolis project was inspired by <a href='http://www.edshipyard.com/' target='_blank'>E:D Shipyard</a> and, of course, <a href='http://www.elitedangerous.com' target='_blank'>Elite Dangerous</a>. The ultimate goal of Coriolis is to provide rich features to support in-game play and planning while engaging the E:D community to support its development.</p>
<p>Coriolis was created using assets and imagery from Elite: Dangerous, with the permission of Frontier Developments plc, for non-commercial purposes. It is not endorsed by nor reflects the views or opinions of Frontier Developments.</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>
<a style={{ display: 'block', textDecoration: 'none' }} href='https://github.com/EDCD/coriolis' target='_blank' title='Coriolis Github Project'>
<GitHub style={{ margin: '0.4em' }} className='l fg xl'/>
@@ -39,15 +39,6 @@ export default class AboutPage extends Page {
<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>
<p>You can chat to us on our <a href='https://discord.gg/0uwCh6R62aPRjk9w' target='_blank'>EDCD Discord server</a>.</p>
</div>;

View File

@@ -63,7 +63,7 @@ export default class ComparisonPage extends Page {
* @return {Object} New state object
*/
_initState(context) {
let defaultFacets = [9, 6, 4, 1, 3, 2]; // Reverse order of Armour, Shields, Speed, Jump Range, Cargo Capacity, Cost
let defaultFacets = [13, 12, 11, 9, 6, 4, 1, 3, 2]; // Reverse order of Armour, Shields, Speed, Jump Range, Cargo Capacity, Cost, DPS, EPS, HPS
let params = context.route.params;
let code = params.code;
let name = params.name ? decodeURIComponent(params.name) : null;
@@ -345,7 +345,7 @@ export default class ComparisonPage extends Page {
let code = fromComparison(name, builds, selectedFacets, predicate, desc);
let loc = window.location;
return `${loc.protocol}//${loc.host}/comparison/${code}`;
return loc.protocol + '//' + loc.host + '/comparison?code=' + encodeURIComponent(code);
}
/**

View File

@@ -26,7 +26,7 @@ export default class ErrorDetails extends React.Component {
if (ed) {
content = <div style={{ textAlign:'left', fontSize:'0.8em', width: '43em', margin: '0 auto' }}>
<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:'}
</div>
<div style={{ marginTop: '2em' }}>
@@ -42,6 +42,7 @@ export default class ErrorDetails extends React.Component {
return <div className='error'>
<h1>Jameson, we have a problem..</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}
</div>;
}

View File

@@ -8,17 +8,22 @@ import Persist from '../stores/Persist';
import Ship from '../shipyard/Ship';
import { toDetailedBuild } from '../shipyard/Serializer';
import { outfitURL } from '../utils/UrlGenerators';
import { FloppyDisk, Bin, Switch, Download, Reload, Fuel } from '../components/SvgIcons';
import { FloppyDisk, Bin, Switch, Download, Reload, Fuel, LinkIcon } from '../components/SvgIcons';
import ShipSummaryTable from '../components/ShipSummaryTable';
import StandardSlotSection from '../components/StandardSlotSection';
import HardpointsSlotSection from '../components/HardpointsSlotSection';
import InternalSlotSection from '../components/InternalSlotSection';
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 PowerManagement from '../components/PowerManagement';
import CostSection from '../components/CostSection';
import ModalExport from '../components/ModalExport';
import ModalPermalink from '../components/ModalPermalink';
import Slider from '../components/Slider';
const SPEED_SERIES = ['boost', '4 Pips', '2 Pips', '0 Pips'];
@@ -31,7 +36,7 @@ const SPEED_COLORS = ['#0088d2', '#ff8c0d', '#D26D00', '#c06400'];
* @return {String} Document title
*/
function getTitle(shipName, buildName) {
return `${shipName}${buildName ? ` - ${buildName}` : ''}`;
return buildName ? buildName : shipName;
}
/**
@@ -47,6 +52,8 @@ export default class OutfittingPage extends Page {
constructor(props, context) {
super(props, context);
this.state = this._initState(context);
this._keyDown = this._keyDown.bind(this);
this._exportBuild = this._exportBuild.bind(this);
}
/**
@@ -176,7 +183,7 @@ export default class OutfittingPage extends Page {
let translate = this.context.language.translate;
let { buildName, ship } = this.state;
this.context.showModal(<ModalExport
title={buildName + ' ' + translate('export')}
title={(buildName || ship.name) + ' ' + translate('export')}
description={translate('PHRASE_EXPORT_DESC')}
data={toDetailedBuild(buildName, ship, ship.toString())}
/>);
@@ -253,6 +260,7 @@ export default class OutfittingPage extends Page {
*/
componentWillMount() {
this.resizeListener = this.context.onWindowResize(this._updateDimensions);
document.addEventListener('keydown', this._keyDown);
}
/**
@@ -269,6 +277,29 @@ export default class OutfittingPage extends Page {
this.resizeListener.remove();
}
/**
* Generates the short URL
*/
_genShortlink() {
this.context.showModal(<ModalPermalink url={window.location.href}/>);
}
/**
* Handle Key Down
* @param {Event} e Keyboard Event
*/
_keyDown(e) {
// .keyCode will eventually be replaced with .key
switch (e.keyCode) {
case 69: // 'e'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + e
e.preventDefault();
this._exportBuild();
}
break;
}
}
/**
* Render the Page
* @return {React.Component} The page contents
@@ -284,16 +315,16 @@ export default class OutfittingPage extends Page {
canSave = (newBuildName || buildName) && code !== savedCode,
canRename = buildName && newBuildName && buildName != newBuildName,
canReload = savedCode && canSave,
hStr = ship.getHardpointsString(),
sStr = ship.getStandardString(),
iStr = ship.getInternalString();
hStr = ship.getHardpointsString() + '.' + ship.getModificationsString(),
sStr = ship.getStandardString() + '.' + ship.getModificationsString(),
iStr = ship.getInternalString() + '.' + ship.getModificationsString();
return (
<div id='outfit' className={'page'} style={{ fontSize: (sizeRatio * 0.9) + 'em' }}>
<div id='overview'>
<h1>{ship.name}</h1>
<div id='build'>
<input value={newBuildName} onChange={this._buildNameChange} placeholder={translate('Enter Name')} maxsize={50} />
<input value={newBuildName || ''} onChange={this._buildNameChange} placeholder={translate('Enter Name')} maxLength={50} />
<button onClick={canSave && this._saveBuild} disabled={!canSave} onMouseOver={termtip.bind(null, 'save')} onMouseOut={hide}>
<FloppyDisk className='lg' />
</button>
@@ -312,6 +343,9 @@ export default class OutfittingPage extends Page {
<button onClick={buildName && this._exportBuild} disabled={!buildName} onMouseOver={termtip.bind(null, 'export')} onMouseOut={hide}>
<Download className='lg'/>
</button>
<button onClick={this._genShortlink} onMouseOver={termtip.bind(null, 'shortlink')} onMouseOut={hide}>
<LinkIcon className='lg' />
</button>
</div>
</div>
@@ -323,6 +357,18 @@ export default class OutfittingPage extends Page {
<PowerManagement ship={ship} code={code} onChange={shipUpdated} />
<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'>
<h1>{translate('jump range')}</h1>
<LineChart
@@ -335,39 +381,6 @@ export default class OutfittingPage extends Page {
xLabel={translate('cargo')}
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' }}>
<tbody >
<tr>
@@ -393,7 +406,68 @@ export default class OutfittingPage extends Page {
</table>
</div>
<div>
<DamageDealt ship={ship} code={code} currentMenu={menu}/>
</div>
<div>
<DamageReceived ship={ship} code={code} currentMenu={menu}/>
</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

@@ -40,7 +40,9 @@ function shipSummary(shipId, shipData) {
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
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);
@@ -55,6 +57,7 @@ function shipSummary(shipId, shipData) {
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;
}
@@ -138,15 +141,22 @@ export default class ShipyardPage extends Page {
>
<td className='le'>{s.manufacturer}</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.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.topSpeed)}{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'>{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[2] })}>{s.hp[2]}</td>
<td className={cn({ disabled: !s.hp[3] })}>{s.hp[3]}</td>
@@ -241,7 +251,6 @@ export default class ShipyardPage extends Page {
return (
<div className='page' style={{ fontSize: sizeRatio + 'em' }}>
<p style={{ textAlign: 'center' }}>This is <strong>Coriolis EDCD Edition</strong> - a temporary clone of <a href='https://coriolis.io/' target='_blank'>https://coriolis.io/</a> with added support for E:D 2.1. For more info see Settings / <Link href="/about" className='block'>About</Link></p>
<div style={{ whiteSpace: 'nowrap', margin: '0 auto', fontSize: '0.8em', position: 'relative', display: 'inline-block', maxWidth: '100%' }}>
<table style={{ width: '12em', position: 'absolute', zIndex: 1 }}>
<thead>
@@ -259,9 +268,11 @@ export default class ShipyardPage extends Page {
<tr className='main'>
<th rowSpan={2} className='sortable' onClick={sortShips('manufacturer')}>{translate('manufacturer')}</th>
<th rowSpan={2} className='sortable' onClick={sortShips('class')}>{translate('size')}</th>
<th rowSpan={2} className='sortable' onMouseEnter={termtip.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('max')}</th>
<th colSpan={6}>{translate('core module classes')}</th>
<th colSpan={5} className='sortable' onClick={sortShips('hpCount')}>{translate('hardpoints')}</th>
<th colSpan={8} className='sortable' onClick={sortShips('intCount')}>{translate('internal compartments')}</th>
<th rowSpan={2} className='sortable' onClick={sortShips('hullMass')}>{translate('hull')}</th>
@@ -279,6 +290,13 @@ export default class ShipyardPage extends Page {
<th className='sortable' onClick={sortShips('maxJumpRange')}>{translate('jump')}</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' onClick={sortShips('hp', 2)}>{translate('M')}</th>
<th className='sortable' onClick={sortShips('hp', 3)}>{translate('L')}</th>

View File

@@ -1,3 +1,4 @@
import Module from './Module';
/**
* Calculate the maximum single jump range based on mass and a specific FSD
@@ -8,7 +9,9 @@
* @return {number} Distance in Light Years
*/
export function jumpRange(mass, fsd, fuel) {
return Math.pow(Math.min(fuel === undefined ? fsd.maxfuel : fuel, fsd.maxfuel) / fsd.fuelmul, 1 / fsd.fuelpower) * fsd.optmass / mass;
let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
let fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass;
return Math.pow(Math.min(fuel === undefined ? fsdMaxFuelPerJump : fuel, fsdMaxFuelPerJump) / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass;
}
/**
@@ -20,15 +23,17 @@ export function jumpRange(mass, fsd, fuel) {
* @return {number} Distance in Light Years
*/
export function fastestRange(mass, fsd, fuel) {
let fuelRemaining = fuel % fsd.maxfuel; // Fuel left after making N max jumps
let jumps = Math.floor(fuel / fsd.maxfuel);
let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
let fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass;
let fuelRemaining = fuel % fsdMaxFuelPerJump; // Fuel left after making N max jumps
let jumps = Math.floor(fuel / fsdMaxFuelPerJump);
mass += fuelRemaining;
// Going backwards, start with the last jump using the remaining fuel
let fastestRange = fuelRemaining > 0 ? Math.pow(fuelRemaining / fsd.fuelmul, 1 / fsd.fuelpower) * fsd.optmass / mass : 0;
let fastestRange = fuelRemaining > 0 ? Math.pow(fuelRemaining / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass : 0;
// For each max fuel jump, calculate the max jump range based on fuel mass left in the tank
for (let j = 0; j < jumps; j++) {
mass += fsd.maxfuel;
fastestRange += Math.pow(fsd.maxfuel / fsd.fuelmul, 1 / fsd.fuelpower) * fsd.optmass / mass;
fastestRange += Math.pow(fsdMaxFuelPerJump / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass;
}
return fastestRange;
};
@@ -36,49 +41,135 @@ export function fastestRange(mass, fsd, fuel) {
/**
* Calculate the a ships shield strength based on mass, shield generator and shield boosters used.
*
* @param {number} mass Current mass of the ship
* @param {number} shields Base Shield strength MJ for ship
* @param {object} sg The shield generator used
* @param {number} multiplier Shield multiplier for ship (1 + shield boosters if any)
* @return {number} Approximate shield strengh in MJ
* @param {number} mass Current mass of the ship
* @param {number} baseShield Base Shield strength MJ for ship
* @param {object} sg The shield generator used
* @param {number} multiplier Shield multiplier for ship (1 + shield boosters if any)
* @return {number} Approximate shield strengh in MJ
*/
export function shieldStrength(mass, shields, sg, multiplier) {
let opt;
if (mass < sg.minmass) {
return shields * multiplier * sg.minmul;
}
if (mass > sg.maxmass) {
return shields * multiplier * sg.maxmul;
}
if (mass < sg.optmass) {
opt = (sg.optmass - mass) / (sg.optmass - sg.minmass);
opt = 1 - Math.pow(1 - opt, 0.87);
return shields * multiplier * ((opt * sg.minmul) + ((1 - opt) * sg.optmul));
} else {
opt = (sg.optmass - mass) / (sg.maxmass - sg.optmass);
opt = -1 + Math.pow(1 + opt, 2.425);
return shields * multiplier * ((-1 * opt * sg.maxmul) + ((1 + opt) * sg.optmul));
}
export function shieldStrength(mass, baseShield, sg, multiplier) {
// sg might be a module or a template; handle either here
let minMass = sg instanceof Module ? sg.getMinMass() : sg.minmass;
let optMass = sg instanceof Module ? sg.getOptMass() : sg.optmass;
let maxMass = sg instanceof Module ? sg.getMaxMass() : sg.maxmass;
let minMul = sg instanceof Module ? sg.getMinMul() : sg.minmul;
let optMul = sg instanceof Module ? sg.getOptMul() : sg.optmul;
let maxMul = sg instanceof Module ? sg.getMaxMul() : sg.maxmul;
let xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass));
let exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass)));
let ynorm = Math.pow(xnorm, exponent);
let mul = minMul + ynorm * (maxMul - minMul);
return baseShield * mul * multiplier;
}
/**
* Calculate the a ships speed based on mass, and thrusters.
*
* @param {number} mass Current mass of the ship
* @param {number} baseSpeed Base speed m/s for ship
* @param {number} baseBoost Base boost speed m/s for ship
* @param {object} thrusters The Thrusters used
* @param {number} pipSpeed Speed pip multiplier
* @return {object} Approximate speed by pips
* @param {number} mass the mass of the ship
* @param {number} baseSpeed base speed m/s for ship
* @param {object} thrusters The ship's thrusters
* @param {number} engpip the multiplier per pip to engines
* @return {array} Speed by pips
*/
export function speed(mass, baseSpeed, baseBoost, thrusters, pipSpeed) {
let multiplier = mass > thrusters.maxmass ? 0 : ((1 - thrusters.M) + (thrusters.M * Math.pow(3 - (2 * Math.max(0.5, mass / thrusters.optmass)), thrusters.P)));
let speed = baseSpeed * multiplier;
export function speed(mass, baseSpeed, thrusters, engpip) {
// thrusters might be a module or a template; handle either here
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 {
'0 Pips': speed * (1 - (pipSpeed * 4)),
'2 Pips': speed * (1 - (pipSpeed * 2)),
'4 Pips': speed,
'boost': baseBoost * multiplier
};
let results = normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseSpeed, engpip);
return results;
}
/**
* 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 StandardArray = [
@@ -37,9 +29,11 @@ export const ModuleGroupToName = {
am: 'Auto Field-Maintenance Unit',
bsg: 'Bi-Weave Shield Generator',
cr: 'Cargo Rack',
fh: 'Fighter Hangar',
fi: 'Frame Shift Drive Interdictor',
hb: 'Hatch Breaker Limpet Controller',
hr: 'Hull Reinforcement Package',
mrp: 'Module Reinforcement Package',
rf: 'Refinery',
scb: 'Shield Cell Bank',
sg: 'Shield Generator',
@@ -48,21 +42,29 @@ export const ModuleGroupToName = {
dc: 'Docking Computer',
fx: 'Fuel Transfer Limpet Controller',
pc: 'Prospector Limpet Controller',
pce: 'Economy Class Passenger Cabin',
pci: 'Business Class Passenger Cabin',
pcm: 'First Class Passenger Cabin',
pcq: 'Luxury Passenger Cabin',
cc: 'Collector Limpet Controller',
// Hard Points
bl: 'Beam Laser',
ul: 'Burst Laser',
c: 'Cannon',
ch: 'Chaff Launcher',
cs: 'Cargo Scanner',
cm: 'Countermeasure',
ec: 'Electronic Countermeasure',
fc: 'Fragment Cannon',
hs: 'Heat Sink Launcher',
ws: 'Frame Shift Wake Scanner',
kw: 'Kill Warrant Scanner',
nl: 'Mine Launcher',
ml: 'Mining Laser',
mr: 'Missile Rack',
pa: 'Plasma Accelerator',
po: 'Point Defence',
mc: 'Multi-cannon',
pl: 'Pulse Laser',
rg: 'Rail Gun',
@@ -104,8 +106,9 @@ export const BulkheadNames = [
export const ShipFacets = [
{ // 0
title: 'agility',
props: ['agility'],
fmt: 'int',
props: ['topPitch', 'topRoll', 'topYaw'],
lbls: ['pitch', 'roll', 'yaw'],
fmt: 'f1',
i: 0
},
{ // 1
@@ -124,7 +127,7 @@ export const ShipFacets = [
},
{ // 3
title: 'shields',
props: ['shieldStrength'],
props: ['shield'],
unit: 'MJ',
fmt: 'int',
i: 3
@@ -184,10 +187,31 @@ export const ShipFacets = [
},
{ // 11
title: 'DPS',
props: ['totalDps'],
lbls: ['DPS'],
props: ['totalDps', 'totalExplDps', 'totalKinDps', 'totalThermDps'],
lbls: ['total', 'explosive', 'kinetic', 'thermal'],
fmt: 'round',
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;
}
}

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

@@ -0,0 +1,723 @@
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
* @param {Number} raw True if the value returned should be raw i.e. without the influence of special effects
* @return {object} The value of the modification. If it is a numeric value then it is returned as an integer value scaled so that 1.23% == 123
*/
getModValue(name, raw) {
let result = this.mods && this.mods[name] ? this.mods[name] : null;
if ((!raw) && this.blueprint && this.blueprint.special) {
// This module has a special effect, see if we need to alter our returned value
const modifierActions = Modifications.modifierActions[this.blueprint.special.edname];
if (modifierActions && modifierActions[name]) {
// this special effect modifies our returned value
const modification = Modifications.modifications[name];
if (modification.method === 'additive') {
result = result + modifierActions[name];
} else if (modification.method === 'overwrite') {
result = modifierActions[name];
} else {
// rate of fire is special, as it's really burst interval. Handle that here
let mod = null;
if (name === 'rof') {
mod = 1 / (1 + modifierActions[name]) - 1;
} else {
mod = modifierActions[name];
}
const multiplier = modification.type === 'percentage' ? 10000 : 100;
result = (((1 + result / multiplier) * (1 + mod)) - 1) * multiplier;
}
}
}
// Sanitise the resultant value to 4dp equivalent
return isNaN(result) ? result : Math.round(result);
}
/**
* 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
* @param {bool} valueiswithspecial true if the value includes the special effect (when coming from a UI component)
*/
setModValue(name, value, valueiswithspecial) {
if (!this.mods) {
this.mods = {};
}
if (valueiswithspecial && this.blueprint && this.blueprint.special) {
// This module has a special effect, see if we need to alter the stored value
const modifierActions = Modifications.modifierActions[this.blueprint.special.edname];
if (modifierActions && modifierActions[name]) {
// This special effect modifies the value being set, so we need to revert it prior to storing the value
const modification = Modifications.modifications[name];
if (modification.method === 'additive') {
value = value - modifierActions[name];
} else if (modification.method === 'overwrite') {
value = null;
} else {
// rate of fire is special, as it's really burst interval. Handle that here
let mod = null;
if (name === 'rof') {
mod = 1 / (1 + modifierActions[name]) - 1;
} else {
mod = modifierActions[name];
}
value = ((value / 10000 + 1) / (1 + mod) - 1) * 10000;
}
}
}
if (value == null || value == 0) {
delete this.mods[name];
} else {
this.mods[name] = value;
}
}
/**
* Helper to obtain a modified value using standard multipliers
* @param {String} name the name of the modifier to obtain
* @return {Number} the mass of this module
*/
_getModifiedValue(name) {
const modification = Modifications.modifications[name];
let result = this[name];
if (!result) {
if (modification && modification.method === 'additive') {
// Additive modifications start at 0 rather than NULL
result = 0;
} else {
result = null;
}
}
if (result != null) {
if (modification) {
// 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 (modification.method === 'additive') {
result = result + modValue;
} else if (modification.method === 'overwrite') {
result = modValue;
} else {
result = result * (1 + modValue);
}
}
}
} else {
if (name === 'burst') {
// Burst is special, as if it can not exist but have a modification
result = this.getModValue(name) / 100;
} else if (name === 'burstrof') {
// Burst rate of fire is special, as if it can not exist but have a modification
result = this.getModValue(name) / 100;
}
}
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 fuel per jump for this module, taking in to account modifications
* @return {Number} the maximum fuel per jump of this module
*/
getMaxFuelPerJump() {
return this._getModifiedValue('maxfuel');
}
/**
* Get the systems capacity for this module, taking in to account modifications
* @return {Number} the systems capacity of this module
*/
getSystemsCapacity() {
return this._getModifiedValue('syscap');
}
/**
* Get the engines capacity for this module, taking in to account modifications
* @return {Number} the engines capacity of this module
*/
getEnginesCapacity() {
return this._getModifiedValue('engcap');
}
/**
* Get the weapons capacity for this module, taking in to account modifications
* @return {Number} the weapons capacity of this module
*/
getWeaponsCapacity() {
return this._getModifiedValue('wepcap');
}
/**
* Get the systems recharge rate for this module, taking in to account modifications
* @return {Number} the systems recharge rate of this module
*/
getSystemsRechargeRate() {
return this._getModifiedValue('sysrate');
}
/**
* Get the engines recharge rate for this module, taking in to account modifications
* @return {Number} the engines recharge rate of this module
*/
getEnginesRechargeRate() {
return this._getModifiedValue('engrate');
}
/**
* Get the weapons recharge rate for this module, taking in to account modifications
* @return {Number} the weapons recharge rate of this module
*/
getWeaponsRechargeRate() {
return this._getModifiedValue('weprate');
}
/**
* Get the kinetic resistance for this module, taking in to account modifications
* @return {Number} the kinetic resistance of this module
*/
getKineticResistance() {
return this._getModifiedValue('kinres');
}
/**
* Get the thermal resistance for this module, taking in to account modifications
* @return {Number} the thermal resistance of this module
*/
getThermalResistance() {
return this._getModifiedValue('thermres');
}
/**
* Get the explosive resistance for this module, taking in to account modifications
* @return {Number} the explosive resistance of this module
*/
getExplosiveResistance() {
return this._getModifiedValue('explres');
}
/**
* Get the regeneration rate for this module, taking in to account modifications
* @return {Number} the regeneration rate of this module
*/
getRegenerationRate() {
return this._getModifiedValue('regen');
}
/**
* Get the broken regeneration rate for this module, taking in to account modifications
* @return {Number} the broken regeneration rate of this module
*/
getBrokenRegenerationRate() {
return this._getModifiedValue('brokenregen');
}
/**
* Get the range for this module, taking in to account modifications
* @return {Number} the range rate of this module
*/
getRange() {
return this._getModifiedValue('range');
}
/**
* Get the falloff for this module, taking in to account modifications
* @return {Number} the falloff of this module
*/
getFalloff() {
if (this.getModValue('fallofffromrange')) {
// Falloff from range means what it says, so use range instead of falloff
return this.getRange();
} else {
// Need to find out if we have a focused modification, in which case our falloff is scaled to range
if (this.blueprint && this.blueprint.name === 'Focused') {
const rangeMod = this.getModValue('range') / 10000;
return this.falloff * (1 + rangeMod);
} else {
// Standard falloff calculation
const range = this.getRange();
const falloff = this._getModifiedValue('falloff');
return (falloff > range ? range : falloff);
}
}
}
/**
* 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() {
// Clip size is always rounded up
let result = this._getModifiedValue('clip');
if (result) { result = Math.ceil(result); }
return result;
}
/**
* 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 distribution for this module, taking in to account modifications
* @return {string} the damage distribution for this module
*/
getDamageDist() {
return this.getModValue('damagedist') || this.damagedist;
}
/**
* Get the shot speed for this module, taking in to account modifications
* @return {string} the shot speed for this module
*/
getShotSpeed() {
return this._getModifiedValue('shotspeed');
}
/**
* Get the spinup for this module, taking in to account modifications
* @return {string} the spinup for this module
*/
getSpinup() {
return this._getModifiedValue('spinup');
}
/**
* Get the time for this module, taking in to account modifications
* @return {string} the time for this module
*/
getTime() {
return this._getModifiedValue('time');
}
}

View File

@@ -1,4 +1,4 @@
import Module from './Module';
import { BulkheadNames } from './Constants';
/**
@@ -37,7 +37,7 @@ export default class ModuleSet {
this.intClass = {};
this.bulkheads = shipData.bulkheads.map((b, i) => {
return Object.assign({ grp: 'bh', name: BulkheadNames[i], index: i, class: '', rating: '' }, b);
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
@@ -66,21 +66,28 @@ export default class ModuleSet {
* @return {Object} Bulkhead module details
*/
getBulkhead(index) {
return this.bulkheads[index] || null;
return this.bulkheads[index] ? new Module({ template: this.bulkheads[index] }) : null;
}
/**
* Determine the modules that areeligible for an internal slot
* @param {Object} ship The ship
* @param {integer} c The max class module that can be mounted in the slot
* @param {Object} eligible) The map of eligible internal groups
* @return {object} A map of all eligible modules by group
*/
getInts(c, eligible) {
getInts(ship, c, eligible) {
let o = {};
for (let key in this.internal) {
if (eligible && !eligible[key]) {
continue;
}
if (key == 'pcq' && !(ship.luxuryCabins && ship.luxuryCabins === true)) {
continue;
}
if (key == 'fh' && !(ship.fighterHangars && ship.fighterHangars === true)) {
continue;
}
let data = filter(this.internal[key], c, 0, this.mass);
if (data.length) { // If group is not empty
o[key] = data;
@@ -119,11 +126,11 @@ export default class ModuleSet {
let pd = this.standard[4][0];
for (let p of this.standard[4]) {
if (p.mass < pd.mass && p.enginecapacity >= boostEnergy) {
if (p.mass < pd.mass && p.engcap >= boostEnergy) {
pd = p;
}
}
return pd;
return new Module({ template: pd });
};
/**
@@ -139,7 +146,7 @@ export default class ModuleSet {
th = t;
}
}
return th;
return new Module({ template: th });
};
/**
@@ -155,7 +162,7 @@ export default class ModuleSet {
sg = s;
}
}
return sg;
return new Module({ template: sg });
};
/**
@@ -168,10 +175,10 @@ export default class ModuleSet {
for (let p of this.standard[0]) {
// Provides enough power, is lighter or the same mass as current power plant but better output/efficiency
if (p.pGen >= powerNeeded && (p.mass < pp.mass || (p.mass == pp.mass && p.pGen > pp.pGen))) {
if (p.pgen >= powerNeeded && (p.mass < pp.mass || (p.mass == pp.mass && p.pgen > pp.pgen))) {
pp = p;
}
}
return pp;
return new Module({ template: pp });
}
}

View File

@@ -1,7 +1,12 @@
import { ModuleNameToGroup, BulkheadNames, StandardArray } from './Constants';
import ModuleSet from './ModuleSet';
import Module from './Module';
import { Ships, Modules } from 'coriolis-data/dist';
/*
* All functions below must return a fresh Module rather than a definition or existing module, as
* the resultant object can be altered with modifications.
*/
/**
@@ -9,9 +14,45 @@ import { Ships, Modules } from 'coriolis-data/dist';
* @return {Object} Cargo hatch model
*/
export function cargoHatch() {
return { name: 'Cargo Hatch', class: 1, rating: 'H', power: 0.6 };
let hatch = new Module();
Object.assign(hatch, { name: 'Cargo Hatch', class: 1, rating: 'H', power: 0.6 });
return hatch;
};
/**
* Finds the module with the specific group and ID
* @param {String} grp Module group (pp - power plant, pl - pulse laser etc)
* @param {String} id The module ID
* @return {Object} The module or null
*/
export function findModule(grp, id) {
// See if it's a standard module
if (Modules.standard[grp]) {
let standardmod = Modules.standard[grp].find(e => e.id == id);
if (standardmod != null) {
return new Module({ template: standardmod });
}
}
// See if it's an internal module
if (Modules.internal[grp]) {
let internalmod = Modules.internal[grp].find(e => e.id == id);
if (internalmod != null) {
return new Module({ template: internalmod });
}
}
// See if it's a hardpoint module
if (Modules.hardpoints[grp]) {
let hardpointmod = Modules.hardpoints[grp].find(e => e.id == id);
if (hardpointmod != null) {
return new Module({ template: hardpointmod });
}
}
return null;
}
/**
* Finds the standard module type with the specified ID
* @param {String|Number} type Standard Module Type (0/pp - Power Plant, 1/t - Thrusters, etc)
@@ -24,6 +65,9 @@ export function standard(type, id) {
}
let s = Modules.standard[type].find(e => e.id == id || (e.class == id.charAt(0) && e.rating == id.charAt(1)));
if (s) {
s = new Module({ template: s });
}
return s || null;
};
@@ -37,7 +81,7 @@ export function hardpoints(id) {
let group = Modules.hardpoints[n];
for (let i = 0; i < group.length; i++) {
if (group[i].id == id) {
return group[i];
return new Module({ template: group[i] });
}
}
}
@@ -54,13 +98,66 @@ export function internal(id) {
let group = Modules.internal[n];
for (let i = 0; i < group.length; i++) {
if (group[i].id == id) {
return group[i];
return new Module({ template: group[i] });
}
}
}
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.
* At least one ofGroup name or unique module name must be provided
@@ -168,6 +265,16 @@ export function findHardpoint(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);
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;
}

View File

@@ -2,10 +2,45 @@ import { ModuleGroupToName, MountMap, BulkheadNames } from './Constants';
import { Ships } from 'coriolis-data/dist';
import Ship from './Ship';
import * as ModuleUtils from './ModuleUtils';
import * as Utils from '../utils/UtilityFunctions';
import LZString from 'lz-string';
import { outfitURL } from '../utils/UrlGenerators';
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
* @param {Object} slot Slot model
@@ -30,6 +65,13 @@ function slotToSchema(slot) {
if (slot.m.missile) {
o.missile = slot.m.missile;
}
if (slot.m.mods && Object.keys(slot.m.mods).length > 0) {
o.modifications = slot.m.mods;
}
if (slot.m.blueprint && Object.keys(slot.m.blueprint).length > 0) {
o.blueprint = slot.m.blueprint;
}
return o;
}
return null;
@@ -48,12 +90,12 @@ export function toDetailedBuild(buildName, ship) {
code = ship.toString();
let data = {
$schema: 'http://cdn.coriolis.io/schemas/ship-loadout/3.json#',
$schema: 'https://coriolis.edcd.io/schemas/ship-loadout/4.json#',
name: buildName,
ship: ship.name,
references: [{
name: 'Coriolis.io',
url: `https://coriolis.io/outfit/${ship.id}/${code}?bn=${encodeURIComponent(buildName)}`,
url: 'https://coriolis.edcd.io' + outfitURL(ship.id, code, buildName),
code,
shipId: ship.id
}],
@@ -61,13 +103,13 @@ export function toDetailedBuild(buildName, ship) {
standard: {
bulkheads: BulkheadNames[ship.bulkheads.m.index],
cargoHatch: { enabled: Boolean(ship.cargoHatch.enabled), priority: ship.cargoHatch.priority + 1 },
powerPlant: { class: standard[0].m.class, rating: standard[0].m.rating, enabled: Boolean(standard[0].enabled), priority: standard[0].priority + 1 },
thrusters: { class: standard[1].m.class, rating: standard[1].m.rating, enabled: Boolean(standard[1].enabled), priority: standard[1].priority + 1 },
frameShiftDrive: { class: standard[2].m.class, rating: standard[2].m.rating, enabled: Boolean(standard[2].enabled), priority: standard[2].priority + 1 },
lifeSupport: { class: standard[3].m.class, rating: standard[3].m.rating, enabled: Boolean(standard[3].enabled), priority: standard[3].priority + 1 },
powerDistributor: { class: standard[4].m.class, rating: standard[4].m.rating, enabled: Boolean(standard[4].enabled), priority: standard[4].priority + 1 },
sensors: { class: standard[5].m.class, rating: standard[5].m.rating, enabled: Boolean(standard[5].enabled), priority: standard[5].priority + 1 },
fuelTank: { class: standard[6].m.class, rating: standard[6].m.rating, enabled: Boolean(standard[6].enabled), priority: standard[6].priority + 1 }
powerPlant: standardToSchema(standard[0]),
thrusters: standardToSchema(standard[1]),
frameShiftDrive: standardToSchema(standard[2]),
lifeSupport: standardToSchema(standard[3]),
powerDistributor: standardToSchema(standard[4]),
sensors: standardToSchema(standard[5]),
fuelTank: standardToSchema(standard[6])
},
hardpoints: hardpoints.filter(slot => slot.maxClass > 0).map(slotToSchema),
utility: hardpoints.filter(slot => slot.maxClass === 0).map(slotToSchema),
@@ -92,54 +134,21 @@ export function toDetailedBuild(buildName, ship) {
*/
export function fromDetailedBuild(detailedBuild) {
let shipId = Object.keys(Ships).find((shipId) => Ships[shipId].properties.name.toLowerCase() == detailedBuild.ship.toLowerCase());
if (!shipId) {
throw 'No such ship: ' + detailedBuild.ship;
}
let comps = detailedBuild.components;
let stn = comps.standard;
let priorities = [stn.cargoHatch && stn.cargoHatch.priority !== undefined ? stn.cargoHatch.priority - 1 : 0];
let enabled = [stn.cargoHatch && stn.cargoHatch.enabled !== undefined ? stn.cargoHatch.enabled : true];
let shipData = Ships[shipId];
let ship = new Ship(shipId, shipData.properties, shipData.slots);
let bulkheads = ModuleUtils.bulkheadIndex(stn.bulkheads);
if (bulkheads < 0) {
throw 'Invalid bulkheads: ' + stn.bulkheads;
if (!detailedBuild.references[0] || !detailedBuild.references[0].code) {
throw 'Missing reference code';
}
let standard = STANDARD.map((c) => {
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);
ship.buildFrom(detailedBuild.references[0].code);
return ship;
};
}
/**
* 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,
p: predicate,
d: desc ? 1 : 0
})).replace(/\//g, '-');
}));
};
/**
@@ -187,5 +196,5 @@ export function fromComparison(name, builds, facets, predicate, desc) {
* @return {Object} Comparison data object
*/
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

@@ -15,7 +15,7 @@ export function multiPurpose(ship, shielded, bulkheadIndex) {
if (shielded) {
ship.internal.some(function(slot) {
if (canMount(slot, 'sg')) { // Assuming largest slot can hold an eligible shield
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;
@@ -35,11 +35,13 @@ export function trader(ship, shielded, standardOpts) {
for (let i = ship.internal.length; i--;) {
let slot = ship.internal[i];
if (sg && canMount(slot, 'sg', sg.class)) {
if (sg && canMount(ship, slot, 'sg', sg.class)) {
ship.use(slot, sg);
sg = null;
} else {
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
if (canMount(ship, slot, 'cr')) {
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
}
}
}
@@ -77,23 +79,23 @@ export function explorer(ship, planetary) {
let slot = ship.internal[i];
let nextSlot = (i + 1) < intLength ? ship.internal[i + 1] : null;
// Fit best possible Fuel Scoop
if (!fuelScoopSlot && canMount(slot, 'fs')) {
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(slot, 'sg', sg.class) && !canMount(nextSlot, 'sg', sg.class)) {
} 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(slot, 'pv') && (shieldNext || !canMount(nextSlot, 'pv', 2))) {
} 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(slot, 'am')) {
} 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
@@ -115,7 +117,7 @@ export function explorer(ship, planetary) {
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
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);

View File

@@ -11,6 +11,7 @@ const LS_KEY_MOD_DISCOUNT = 'moduleDiscount';
const LS_KEY_STATE = 'state';
const LS_KEY_SIZE_RATIO = 'sizeRatio';
const LS_KEY_TOOLTIPS = 'tooltips';
const LS_KEY_MODULE_RESISTANCES = 'moduleResistances';
let LS;
@@ -81,6 +82,7 @@ export class Persist extends EventEmitter {
LS = null;
}
let moduleResistances = _get(LS_KEY_MODULE_RESISTANCES);
let tips = _get(LS_KEY_TOOLTIPS);
let insurance = _getString(LS_KEY_INSURANCE);
let shipDiscount = _get(LS_KEY_SHIP_DISCOUNT);
@@ -99,6 +101,7 @@ export class Persist extends EventEmitter {
this.state = _get(LS_KEY_STATE);
this.sizeRatio = _get(LS_KEY_SIZE_RATIO) || 1;
this.tooltipsEnabled = tips === null ? true : tips;
this.moduleResistancesEnabled = moduleResistances === null ? true : moduleResistances;
if (LS) {
window.addEventListener('storage', this.onStorageChange);
@@ -143,6 +146,10 @@ export class Persist extends EventEmitter {
this.tooltipsEnabled = !!newValue && newValue.toLowerCase() == 'true';
this.emit('tooltips', this.tooltipsEnabled);
break;
case LS_KEY_MODULE_RESISTANCES:
this.moduleResistancesEnabled = !!newValue && newValue.toLowerCase() == 'true';
this.emit('moduleresistances', this.moduleResistancesEnabled);
break;
}
} catch (e) {
// On JSON.Parse Error - don't sync or do anything
@@ -183,6 +190,21 @@ export class Persist extends EventEmitter {
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.
*

View File

@@ -0,0 +1,425 @@
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') {
// This is an absolute number that acts as an override
module.setModValue('burstrof', modifiers.modifiers[i].value * 100);
} else if (modifiers.modifiers[i].name === 'mod_weapon_falloffrange_from_range') {
// Obtain the falloff value directly from the range
module.setModValue('fallofffromrange', 1);
} else if (modifiers.modifiers[i].name && modifiers.modifiers[i].name.startsWith('special_')) {
// We don't add special effects directly, but keep a note of them so they can be added when fetching values
special = Modifications.specials[modifiers.modifiers[i].name];
} 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);
}
}
}
}
// 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';
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
* @param {string} url The URL to shorten
* @param {function} success Success callback
* @param {function} error Failure/Error callback
*/
export default function shortenUrl(url, success, error) {
function shortenUrlGoogle(url, success, error) {
if (window.navigator.onLine) {
try {
request.post(SHORTEN_API + window.CORIOLIS_GAPI_KEY)
request.post(SHORTEN_API_GOOGLE + window.CORIOLIS_GAPI_KEY)
.send({ longUrl: url })
.end(function(err, response) {
if (err) {
@@ -27,3 +37,30 @@ export default function shortenUrl(url, success, error) {
error('Not Online');
}
}
const SHORTEN_API_EDDP = 'https://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,18 +1,24 @@
import React from 'react';
import cn from 'classnames';
import { isShieldGenerator } from '../shipyard/ModuleUtils';
import Module from '../shipyard/Module';
import { Infinite } from '../components/SvgIcons';
import Persist from '../stores/Persist';
/**
* Determine if a slot can mount a module of a particular class and group
* 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(slot, group, clazz) {
if (slot && (!slot.eligible || slot.eligible[group]) && (clazz === undefined || slot.maxClass >= clazz)) {
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;
@@ -87,69 +93,6 @@ export function slotComparator(translate, propComparator, desc) {
};
}
const PROP_BLACKLIST = {
eddbID: 1,
edID: 1,
id: 1,
index: 1,
'class': 1,
rating: 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
* @param {Number} a Potential Module (cannot be null)
@@ -175,7 +118,7 @@ function diffClass(a, b, negative) {
*/
function diff(format, mVal, mmVal) {
if (mVal == Infinity) {
return <Infinite/>;
return '∞';
} else {
let diff = mVal - mmVal;
if (!diff || mVal === undefined || diff == mVal || Math.abs(diff) == Infinity) {
@@ -197,42 +140,48 @@ function diff(format, mVal, mmVal) {
* @return {React.Component} Component to be rendered
*/
export function diffDetails(language, m, mm) {
mm = mm || {};
let { formats, translate, units } = language;
let propDiffs = [];
let mCost = m.cost || 0;
let mmCost = mm ? mm.cost : 0;
if (mCost != mmCost) propDiffs.push(<div key='cost'>{translate('cost')}: <span className={diffClass(mCost, mmCost, true) }>{mCost ? Math.round(mCost * (1 - Persist.getModuleDiscount())) : 0}{units.CR}</span></div>);
let mMass = m.mass || 0;
let mmMass = mm.mass || 0;
let massDiff = mMass - mmMass;
let capDiff = (m.fuel || m.cargo || 0) - (mm.fuel || mm.cargo || 0);
let mAffectsShield = isShieldGenerator(m.grp) || m.grp == 'sb';
let mmAffectsShield = isShieldGenerator(mm.grp) || mm.grp == 'sb';
let mmMass = mm ? mm.getMass() : 0;
if (mMass != mmMass) propDiffs.push(<div key='mass'>{translate('mass')}: <span className={diffClass(mMass, mmMass, true)}>{diff(formats.round, mMass, mmMass)}{units.T}</span></div>);
propDiffs.push(<div key='cost'>{translate('cost')}: <span className={diffClass(m.cost, mm.cost, true) }>{m.cost ? Math.round(m.cost * (1 - Persist.getModuleDiscount())) : 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 (m.grp === 'pp') {
let mPowerGeneration = m.pgen || 0;
let mmPowerGeneration = mm ? mm.getPowerGeneration() : 0;
if (mPowerGeneration != mmPowerGeneration) propDiffs.push(<div key='pgen'>{translate('pgen')}: <span className={diffClass(mPowerGeneration, mmPowerGeneration)}>{diff(formats.round, mPowerGeneration, mmPowerGeneration)}{units.MJ}</span></div>);
} else {
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 mmAffectsShield = isShieldGenerator(mm ? mm.grp : null) || mm && mm.grp == 'sb';
if (mAffectsShield || mmAffectsShield) {
let shield = this.calcShieldStrengthWith(); // Get shield strength regardless of slot active / inactive
let newShield = 0;
if (mAffectsShield) {
if (m.grp == 'sb') { // Both m and mm must be utility modules if this is true
newShield = this.calcShieldStrengthWith(null, m.shieldmul - (mm.shieldmul || 0));
newShield = this.calcShieldStrengthWith(null, m.shieldboost - (mm ? mm.getShieldBoost() || 0 : 0));
} else {
newShield = this.calcShieldStrengthWith(m);
}
} else {
// Old module must be a shield booster
newShield = this.calcShieldStrengthWith(null, -mm.getShieldBoost());
}
let sgDiffClass = Math.round((newShield - shield) * 100) / 100 == 0 ? 'muted' : (newShield > shield ? 'secondary' : 'warning');
propDiffs.push(<div key='shields'>{translate('shields')}: <span className={sgDiffClass}>{diff(formats.int, newShield, shield)}{units.MJ}</span></div>);
@@ -241,24 +190,28 @@ export function diffDetails(language, m, mm) {
if (m.grp == 'pd') {
propDiffs.push(<div key='wep'>
{`${translate('WEP')}: `}
<span className={diffClass(m.weaponcapacity, mm.weaponcapacity)}>{m.weaponcapacity}{units.MJ}</span>
<span className={diffClass(m.wepcap, mm.getWeaponsCapacity())}>{m.wepcap}{units.MJ}</span>
{' / '}
<span className={diffClass(m.weaponrecharge, mm.weaponrecharge)}>{m.weaponrecharge}{units.MW}</span>
<span className={diffClass(m.weprate, mm.getWeaponsRechargeRate())}>{m.weprate}{units.MW}</span>
</div>);
propDiffs.push(<div key='sys'>
{`${translate('SYS')}: `}
<span className={diffClass(m.systemcapacity, mm.systemcapacity)}>{m.systemcapacity}{units.MJ}</span>
<span className={diffClass(m.syscap, mm.getSystemsCapacity())}>{m.syscap}{units.MJ}</span>
{' / '}
<span className={diffClass(m.systemrecharge, mm.systemrecharge)}>{m.systemrecharge}{units.MW}</span>
<span className={diffClass(m.sysrate, mm.getSystemsRechargeRate())}>{m.sysrate}{units.MW}</span>
</div>);
propDiffs.push(<div key='eng'>
{`${translate('ENG')}: `}
<span className={diffClass(m.enginecapacity, mm.enginecapacity)}>{m.enginecapacity}{units.MJ}</span>
<span className={diffClass(m.engcap, mm.getEnginesCapacity())}>{m.engcap}{units.MJ}</span>
{' / '}
<span className={diffClass(m.enginerecharge, mm.enginerecharge)}>{m.enginerecharge}{units.MW}</span>
<span className={diffClass(m.engrate, mm.getEnginesRechargeRate())}>{m.engrate}{units.MW}</span>
</div>);
}
let massDiff = mMass - mmMass;
let mCap = m.fuel || m.cargo || 0;
let mmCap = mm ? mm.fuel || mm.cargo || 0 : 0;
let capDiff = mCap - mmCap;
if (m.grp == 'fsd' || massDiff || capDiff) {
let fsd = m.grp == 'fsd' ? m : null;
let maxRange = this.calcUnladenRange(massDiff, m.fuel, fsd);
@@ -272,5 +225,5 @@ export function diffDetails(language, m, mm) {
}
}
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
* @param {String} shipId Ship Id
@@ -7,15 +6,18 @@
* @return {String} URL
*/
export function outfitURL(shipId, code, buildName) {
let parts = ['/outfit/', shipId];
let path = '/outfit/' + shipId;
let sepChar = '?';
if (code) {
parts.push('/', code);
path = path + sepChar + 'code=' + encodeURIComponent(code);
sepChar = '&';
}
if (buildName) {
parts.push('?bn=', encodeURIComponent(buildName));
path = path + sepChar + 'bn=' + encodeURIComponent(buildName);
}
return parts.join('');
}
return path;
}

View File

@@ -58,3 +58,28 @@ export function shallowEqual(objA, objB) {
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;
}
/**
* Check if an object is empty
* @param {object} obj the object
* @return {bool} true if the object is empty, otherwise false
*/
export function isEmpty(obj) {
for (let key in obj) {
if (obj.hasOwnProperty(key)) return false;
}
return true;
};

View File

@@ -1,5 +1,5 @@
{
"name": "Coriolis.io",
"name": "Coriolis EDCD Edition",
"short_name": "Coriolis",
"icons": [
{
@@ -27,7 +27,7 @@
"density": "4.0"
}
],
"start_url": "http:\/\/coriolis.io",
"start_url": "https:\/\/edcd.coriolis.io",
"display": "standalone",
"orientation": "portrait"
}

View File

@@ -16,6 +16,7 @@
@import 'tooltip';
@import 'buttons';
@import 'error';
@import 'shipselector';
@import 'sortable';
@import 'loader';
@@ -41,8 +42,10 @@ div, a, li {
#coriolis {
width: 100%;
height: 100%;
padding-top: 48px;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
box-sizing: border-box;
}
.page {
@@ -163,4 +166,4 @@ footer {
float: right;
text-align: right;
}
}
}

View File

@@ -20,8 +20,12 @@ header {
line-height: 3em;
font-family: @fTitle;
vertical-align: middle;
position: relative;
position: absolute;
top: 0px;
left: 0px;
width: 100%;
z-index: 2;
box-sizing: border-box;
.user-select-none();
.menu {
@@ -186,6 +190,7 @@ header {
}
.no-wrap {
overflow-x: auto;
white-space: nowrap;
}
@@ -200,4 +205,4 @@ header {
margin:0px;
text-transform: uppercase;
}
}
}

View File

@@ -1,4 +1,5 @@
// Standard icons
.icon {
display: inline-block;
vertical-align: middle;
@@ -25,3 +26,41 @@
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;
}

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

View File

@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "http://cdn.coriolis.io/schemas/ship-loadout/1.json#",
"id": "https://coriolis.edcd.io/schemas/ship-loadout/1.json#",
"title": "Ship Loadout",
"type": "object",
"description": "The details for a specific ship build/loadout. DEPRECATED in favor of Version 3",
@@ -278,4 +278,4 @@
"standardRatings": { "enum": ["A", "B", "C", "D", "E"] },
"allRatings": { "enum": ["A", "B", "C", "D", "E", "F", "I" ] }
}
}
}

View File

@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "http://cdn.coriolis.io/schemas/ship-loadout/2.json#",
"id": "https://coriolis.edcd.io/schemas/ship-loadout/2.json#",
"title": "Ship Loadout",
"type": "object",
"description": "The details for a specific ship build/loadout. DEPRECATED in favor of Version 3",

View File

@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "http://cdn.coriolis.io/schemas/ship-loadout/3.json#",
"id": "https://coriolis.edcd.io/schemas/ship-loadout/3.json#",
"title": "Ship Loadout",
"type": "object",
"description": "The details for a specific ship build/loadout",
@@ -76,7 +76,11 @@
"class": { "type": "integer", "minimum": 2, "maximum": 8 },
"rating": { "$ref": "#/definitions/standardRatings" },
"enabled": { "type": "boolean" },
"priority": { "type": "integer", "minimum": 1, "maximum": 5 }
"priority": { "type": "integer", "minimum": 1, "maximum": 5 },
"name": {
"description": "The name identifing the thrusters (if applicable), e.g. 'Enhanced Performance'",
"type": "string"
}
}
},
"frameShiftDrive": {

View File

@@ -0,0 +1,366 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"id": "https://coriolis.edcd.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" ] }
}
}