Compare commits

...

309 Commits

Author SHA1 Message Date
Unknown
97f3bece33 Merge branch 'release/2.3.7' 2017-08-29 20:05:28 +01:00
Unknown
be02444487 Changelog
Bump Version
2017-08-29 20:04:52 +01:00
Kamen Litchev
fc012fe68a Fix stroke-width handling bug in Safari 11 (#138)
* Fix stroke-width handling bug in Safari 11

* Further fix Safari 11 SVG stroke-width issues
2017-08-29 13:29:47 +01:00
strideynet
7ac16d6d22 Update Tracking 2017-08-22 21:03:23 +01:00
strideynet
d82128690b Outdated URLs in tests don't match actual URLs 2017-08-17 19:47:13 +01:00
strideynet
9e57eb4262 Remove unwanted console.log and spacing fix 2017-08-17 15:00:13 +01:00
strideynet
6e0d45f419 ESLint issue
ESLint is picking a fault with the JSDoc despite it being correct. Will investigate further but for now this resolves the issue.
2017-08-17 14:42:44 +01:00
strideynet
645e86714e attempted fix for jsdoc detection 2017-08-17 14:31:00 +01:00
strideynet
00afd1cd6a Attempt to fix JSDoc 2017-08-17 14:12:25 +01:00
strideynet
77ae126a51 Compliance
As per #132
2017-08-17 14:05:22 +01:00
strideynet
9c82c7caed remove redundant LoC 2017-08-17 14:01:12 +01:00
strideynet
d9a92e7a78 Remove redundant LoC 2017-08-17 13:59:23 +01:00
strideynet
d8a87029a6 Compliance
As per #132
2017-08-17 13:42:12 +01:00
strideynet
f407d0f92a Compliance
Issue #132
2017-08-17 13:41:41 +01:00
strideynet
4ac42e62e6 Bumping NodeJS Version
See commit to travis.yml
2017-08-17 13:26:09 +01:00
strideynet
748c63fa0b Update NodeJS Version
Over the new few days I will test the newer major versions, for now just bumping to the latest V4 LTS
2017-08-17 13:24:21 +01:00
strideynet
78ca756cef Update package.json 2017-08-17 13:13:24 +01:00
strideynet
a8554b51c2 Uglify JS
Required for the build process for D3
2017-08-17 13:11:37 +01:00
sascha-dev
e54e4da289 Updated german translation (#127)
PR #127 merged to EDCD:Develop
2017-08-16 19:29:22 +01:00
Cmdr McDonald
f97cb5f5a7 Merge branch 'release/2.3.6' into develop 2017-06-24 09:34:41 +01:00
Cmdr McDonald
318e8077d0 Merge branch 'release/2.3.6' 2017-06-24 09:34:37 +01:00
Cmdr McDonald
c8355a532d Bump version 2017-06-24 09:33:21 +01:00
Cmdr McDonald
3686ccd4ed Update Russian translation 2017-06-24 09:24:38 +01:00
Cmdr McDonald
f02db0120a Fix for Spanish translation of Chaff Launcher 2017-06-24 09:18:27 +01:00
Cmdr McDonald
d9ad93d3cd Fix typo causing long range blueprint to not modify shot speed in some circumstances 2017-06-06 11:45:59 +01:00
Cmdr McDonald
77018cc1ad Fix issue where torpedo special effects were not showing 2017-05-30 19:44:35 +01:00
Cmdr McDonald
30a8a29ce3 Update miner role with better defaults 2017-05-30 19:44:11 +01:00
Cmdr McDonald
7b1aa646ac Fix 2017-05-17 20:56:13 +01:00
Cmdr McDonald
49e4409862 Merge branch 'release/2.3.5' into develop 2017-05-17 07:49:06 +01:00
Cmdr McDonald
ab87ddc649 Merge branch 'release/2.3.5' 2017-05-17 07:49:00 +01:00
Cmdr McDonald
57784329ac Bump version 2017-05-17 07:31:46 +01:00
Cmdr McDonald
1db613c56d Grey out modules that are powered off to provide a clearer visual indication 2017-05-16 20:31:56 +01:00
Cmdr McDonald
464cd4165f Added translation 2017-05-16 16:39:15 +01:00
Cmdr McDonald
420ebe4cc7 Merge branch 'feature/fixes' into develop 2017-05-11 13:14:27 +01:00
Cmdr McDonald
15ead440d6 Updated German translation; fix for #100 2017-05-11 13:14:19 +01:00
Cmdr McDonald
ef0614a62f Handle display when summary values show thrusters disabled but current mass keeps them enabled 2017-05-11 13:09:18 +01:00
Cmdr McDonald
2fd2af3e31 Ensure that hidden blueprint effects are applied when a blueprint is selected 2017-05-11 12:45:57 +01:00
Cmdr McDonald
f30a904a02 Remove logging 2017-05-03 19:18:50 +01:00
Cmdr McDonald
e00680113f Do not add hidden features 2017-05-03 19:15:07 +01:00
Cmdr McDonald
93e6a29057 Merge branch 'release/2.3.4' into develop 2017-05-03 17:12:16 +01:00
Cmdr McDonald
1fbba6a5f3 Merge branch 'release/2.3.4' 2017-05-03 17:12:13 +01:00
Cmdr McDonald
d1c0b635b3 Package bump 2017-05-03 17:12:07 +01:00
Cmdr McDonald
060cf75ec4 Handle different specials for dumbfire and seeker missiles 2017-05-03 12:37:49 +01:00
Cmdr McDonald
62e3b70581 Merge branch 'feature/edge' into develop 2017-04-28 07:20:23 -04:00
Cmdr McDonald
4accb901af Bump zlib 2017-04-26 07:58:05 -04:00
Cmdr McDonald
82725e1c5a Fixes 2017-04-25 12:10:44 +01:00
Cmdr McDonald
4dc8ab928c Remove findDOMNode as per warning 2017-04-23 18:50:13 +01:00
Cmdr McDonald
7db76ecba0 Lint fixes 2017-04-23 18:36:20 +01:00
Cmdr McDonald
16a5b2a72a Version bumps 2017-04-23 16:26:16 +01:00
Cmdr McDonald
59a5ba0227 Updaes 2017-04-23 16:06:14 +01:00
Cmdr McDonald
c130b52b7c Fix incorrect height of shipyard ship header 2017-04-21 22:39:07 +01:00
Cmdr McDonald
03f32097e9 Merge branch 'feature/ordering' into develop 2017-04-21 21:38:48 +01:00
Cmdr McDonald
8660dcbd2b Tidy up ordering 2017-04-21 21:38:42 +01:00
Cmdr McDonald
95f358f9d8 Upgrade to React 15.5 2017-04-21 16:07:41 +01:00
Cmdr McDonald
8f944bc4c8 Add racer role 2017-04-21 10:43:39 +01:00
Cmdr McDonald
e9cbe0d952 Bump package version 2017-04-20 15:11:59 +01:00
Cmdr McDonald
684ecfcafd Ensure that stock builds are saved with a code; ignore builds without codes in comparisons 2017-04-20 15:10:03 +01:00
Cmdr McDonald
1516bd4fc0 Fix crash when clearing a speical 2017-04-20 13:17:30 +01:00
Cmdr McDonald
4ea0bbfb93 Merge branch 'release/2.3.3' into develop 2017-04-15 20:46:50 +01:00
Cmdr McDonald
9b6df8ea4f Merge branch 'release/2.3.3' 2017-04-15 20:46:45 +01:00
Cmdr McDonald
9c6f5fb44e Bump version 2017-04-15 20:46:40 +01:00
Cmdr McDonald
c0a732a76b Merge branch 'feature/refit' into develop 2017-04-15 18:11:56 +01:00
Cmdr McDonald
9551b8b5de Add shopping list for refit 2017-04-15 18:11:50 +01:00
Cmdr McDonald
22f83b79af Encode blueprint information event when no active modifications 2017-04-15 18:11:09 +01:00
Cmdr McDonald
0a26f76fb4 Merge branch 'release/2.3.2' into develop 2017-04-14 13:09:55 +01:00
Cmdr McDonald
077f432daf Merge branch 'release/2.3.2' 2017-04-14 13:09:52 +01:00
Cmdr McDonald
e846b4508b Bump version 2017-04-14 11:06:09 +01:00
Cmdr McDonald
3be78885b8 Bump 2017-04-14 10:58:15 +01:00
Cmdr McDonald
e29a4b263d Fix scan time/scan rate confusion 2017-04-13 23:19:27 +01:00
Cmdr McDonald
061ab77de1 Merge branch 'release/23' into develop 2017-04-13 22:26:33 +01:00
Cmdr McDonald
c6a8e48fda Merge branch 'release/23' 2017-04-13 22:26:29 +01:00
Cmdr McDonald
f48af272c3 Bump for 2.3 2017-04-09 09:23:31 +01:00
Cmdr McDonald
2b1402d099 Bump for 2.3 2017-04-09 09:23:20 +01:00
Cmdr McDonald
1ad0afa0d7 Merge branch 'feature/fixes' into develop 2017-04-09 09:07:50 +01:00
Cmdr McDonald
93d6c1b871 Update defence tooltips - #93 2017-04-08 00:00:08 +01:00
Cmdr McDonald
243f1123ba Fix typo 2017-04-06 19:54:45 +01:00
Cmdr McDonald
2fa2625a8f Fix display and selection of modules near weight limit in menu 2017-04-04 23:55:08 +01:00
Cmdr McDonald
42c43d3f2d Use single calculation for shield metrics 2017-04-04 08:51:02 +01:00
Cmdr McDonald
1237833c7a Ensure that opponent piips are updated immediately when a new opponent is selecteD 2017-04-03 13:46:14 +01:00
Cmdr McDonald
171a84dd33 Add reload to hardpoint info 2017-04-01 21:20:44 +01:00
Cmdr McDonald
79dac7b38f Merge branch 'feature/fixes' into develop 2017-03-31 16:49:10 +01:00
Cmdr McDonald
0e86ae79c1 Revert 2.3 diminishing returns on boosters 2017-03-31 16:34:50 +01:00
Cmdr McDonald
7d7ea18447 Tidy-ups for modification 2017-03-31 12:28:29 +01:00
Cmdr McDonald
2a6da36aeb Fix names of features in tooltips 2017-03-31 12:04:38 +01:00
Cmdr McDonald
db1a976e20 Restyle modifications menu 2017-03-31 09:21:13 +01:00
Cmdr McDonald
3b058bda7f Consistency 2017-03-31 07:40:12 +01:00
Cmdr McDonald
da274f1b75 Fix issue for additive specials not showing up 2017-03-30 08:17:13 +01:00
Cmdr McDonald
d624663278 Remove unused components 2017-03-28 14:06:25 +01:00
Cmdr McDonald
d8ac185b4d Add optimal multiplier to thrusters 2017-03-27 11:04:17 +01:00
Cmdr McDonald
b66baef998 Add engineer info to blueprint tooltip 2017-03-27 10:17:30 +01:00
Cmdr McDonald
8a115f8323 Add component list to blueprint display tooltip 2017-03-26 20:48:21 +01:00
Cmdr McDonald
442da6f05b Use cargo hatch info on import if available 2017-03-25 18:19:31 +00:00
Cmdr McDonald
f536c037b1 Updates 2017-03-25 16:24:13 +00:00
Cmdr McDonald
33a678a200 Add SDPS totals 2017-03-24 22:30:55 +00:00
Cmdr McDonald
b0dc4d7864 Shirnk large labels on vertical bar chart 2017-03-24 12:04:41 +00:00
Cmdr McDonald
8b0e822ea9 Fixes 2017-03-23 10:14:43 +00:00
Cmdr McDonald
006811f5a9 Merge branch 'feature/battlecentre' into develop 2017-03-23 08:20:14 +00:00
Cmdr McDonald
873935c1d4 Update to documentation 2017-03-23 08:17:07 +00:00
Cmdr McDonald
94d876e934 Per-cell colour coding for blueprint tooltips 2017-03-22 21:38:33 +00:00
Cmdr McDonald
b055963fe0 Fixes 2017-03-22 18:34:57 +00:00
Cmdr McDonald
8ad9472d56 Provide correct blueprints limits for HRPs 2017-03-22 18:24:22 +00:00
Cmdr McDonald
d15e49f315 Ignore rpshot for eps and hps 2017-03-22 17:53:26 +00:00
Cmdr McDonald
c7ea1eb95a Use opponent's pip info 2017-03-22 14:19:21 +00:00
Cmdr McDonald
82ce86a374 Updates 2017-03-22 13:55:45 +00:00
Cmdr McDonald
a1a17bc836 Add blueprint info to modification tooltip 2017-03-22 11:44:08 +00:00
Cmdr McDonald
75a4e54453 Add tooltips for blueprints 2017-03-22 10:51:23 +00:00
Cmdr McDonald
32fb66139a Take base hull reinforcement resistances in to account when calculating modifiers 2017-03-22 09:20:51 +00:00
Cmdr McDonald
3f18987007 Rewrite vertical bar chart to use recharts 2017-03-21 14:46:35 +00:00
Cmdr McDonald
eb042b2778 Tidy-ups for build changes 2017-03-21 12:19:25 +00:00
Cmdr McDonald
85f108556a Fix for ship summary 2017-03-21 00:41:33 +00:00
Cmdr McDonald
73a75c69a3 Make various components stateless 2017-03-20 13:52:24 +00:00
Cmdr McDonald
2f5d123f02 Change chart label colour to black; persist select outfitting subpage 2017-03-20 07:48:44 +00:00
Cmdr McDonald
e278babee8 Use correct values for boost/speed when working out boost factor 2017-03-19 22:34:19 +00:00
Cmdr McDonald
86df6f20f6 Ensure ship picker z index is correct 2017-03-19 20:18:31 +00:00
Cmdr McDonald
117028875f Name change 2017-03-19 18:54:38 +00:00
Cmdr McDonald
cadd699bdf Manual merge of #90 2017-03-19 18:53:22 +00:00
Cmdr McDonald
6afd80002c Tidy-ups 2017-03-19 17:40:41 +00:00
Cmdr McDonald
2736e1df79 More metrics 2017-03-19 16:16:00 +00:00
Cmdr McDonald
0ff95ed1f1 Shrink movement profile 2017-03-19 08:49:38 +00:00
Cmdr McDonald
fcb8980a38 Tidy-ups 2017-03-19 08:41:36 +00:00
Cmdr McDonald
49a076fd9e Updates 2017-03-18 23:46:46 +00:00
Cmdr McDonald
eb83969015 Break out metric calculations 2017-03-18 13:42:32 +00:00
Cmdr McDonald
369d882354 Tidy-ups 2017-03-17 18:10:07 +00:00
Cmdr McDonald
23c4da55de Make ship summary numbers react to pips/fuel/cargo/etc 2017-03-17 14:04:07 +00:00
Cmdr McDonald
7259a666eb Tidy-ups 2017-03-17 12:34:36 +00:00
Cmdr McDonald
91cab5a4f1 Embed battle centre in main pages 2017-03-17 12:07:18 +00:00
Cmdr McDonald
d60a8f2625 Fix shield recovery/recharge calculations 2017-03-16 12:05:15 +00:00
Cmdr McDonald
69489aa267 Update shortcut information 2017-03-16 10:13:36 +00:00
Cmdr McDonald
750d23b10a Provide live sustained DPS for opponent 2017-03-16 07:35:52 +00:00
Cmdr McDonald
cd753af48e Fix issue putting 4A refinery in class 5 slot 2017-03-15 11:45:52 +00:00
Cmdr McDonald
365810a610 Working on shield recovery calculation 2017-03-15 11:40:12 +00:00
Cmdr McDonald
73d609610a Fix axis for effectice shield 2017-03-14 08:29:09 +00:00
Cmdr McDonald
a3c03266bf On-going tidy-ups 2017-03-13 22:59:01 +00:00
Cmdr McDonald
7b3ad555a1 Tidy-ups 2017-03-13 22:13:41 +00:00
Cmdr McDonald
1605e80884 Fix to calculate boost when engcap changes 2017-03-13 19:55:45 +00:00
Cmdr McDonald
0729fc29fa Add vertical bar chart and use it in battle centre 2017-03-13 17:07:39 +00:00
Cmdr McDonald
964cdd2b9a Move boost in to pips component 2017-03-12 19:11:40 +00:00
Cmdr McDonald
49c3e395db Merge branch 'feature/battlecentre' of https://github.com/EDCD/coriolis into feature/battlecentre 2017-03-12 18:37:21 +00:00
Cmdr McDonald
bdd2299335 Ignore swap files 2017-03-12 18:37:07 +00:00
Cmdr McDonald
f782adb21d Remove swap files 2017-03-12 18:37:07 +00:00
Cmdr McDonald
950c0c61f9 Start of shields component 2017-03-12 18:37:07 +00:00
Cmdr McDonald
f6ebaf7445 Set initial chart width 2017-03-12 18:37:07 +00:00
Cmdr McDonald
3b35d5030e Add ship picker 2017-03-12 18:37:07 +00:00
Cmdr McDonald
067b69f449 Link in components 2017-03-12 18:37:07 +00:00
Cmdr McDonald
6f67267fec Building components for battle centre 2017-03-12 18:37:07 +00:00
Cmdr McDonald
ec0cd37896 Merge branch 'feature/23' into develop 2017-03-12 18:36:45 +00:00
Cmdr McDonald
84719c997f Ignore swap files 2017-03-12 17:00:33 +00:00
Cmdr McDonald
2731ec3b90 Remove swap files 2017-03-12 17:00:19 +00:00
Cmdr McDonald
340121c6bd Start of shields component 2017-03-12 17:00:04 +00:00
Cmdr McDonald
ec4e70326a Set initial chart width 2017-03-11 22:22:12 +00:00
Cmdr McDonald
3a271e4b7b Add ship picker 2017-03-11 17:57:03 +00:00
Cmdr McDonald
02db800c7b Link in components 2017-03-11 13:28:24 +00:00
Cmdr McDonald
3a55c8cc0a Building components for battle centre 2017-03-10 17:17:37 +00:00
Cmdr McDonald
e00b07c2f6 Reset old modification values when a new roll is applied 2017-03-08 07:23:07 +00:00
Cmdr McDonald
fbfd0deb6c Show integrity 2017-03-06 09:19:05 +00:00
Cmdr McDonald
dc11cc182f Fixes 2017-03-04 15:42:24 +00:00
Cmdr McDonald
597344353a Update test with new hardness 2017-03-03 22:40:50 +00:00
Cmdr McDonald
9aef1ff8a6 Revert fixed header due to issues with ios 2017-03-03 22:03:47 +00:00
Cmdr McDonald
70f218f833 Tidy-ups for diffDetails 2017-03-03 21:57:00 +00:00
Cmdr McDonald
5937843be8 Fix PD/boost calculation 2017-03-02 14:37:03 +00:00
Cmdr McDonald
d4b384eded Updates for 2.3 2017-03-01 22:10:47 +00:00
Cmdr McDonald
2b5a42d4e2 Merge branch 'feature/x' into develop 2017-02-27 09:26:22 +00:00
Cmdr McDonald
afd8ad7678 Lint 2017-02-27 09:26:17 +00:00
Cmdr McDonald
cdc5c29458 Alter way that X axis for profile charts is calculated 2017-02-27 09:24:32 +00:00
Cmdr McDonald
5a351d4c0d Merge branch 'release/2.2.19' into develop 2017-02-26 21:26:00 +00:00
Cmdr McDonald
fac279d9dd Merge branch 'release/2.2.19' 2017-02-26 21:25:57 +00:00
Cmdr McDonald
f4e5254832 Relocate slider values 2017-02-26 21:22:53 +00:00
Cmdr McDonald
2fb3ee8cd8 Bump version 2017-02-26 21:14:12 +00:00
Cmdr McDonald
cc2f3fd1fe Merge branch 'feature/wp2' into develop 2017-02-26 21:06:17 +00:00
Cmdr McDonald
87146b2cf3 Add engine and FSD profiles 2017-02-26 21:06:07 +00:00
Cmdr McDonald
bec3ae3f89 Tidy ups 2017-02-26 09:14:10 +00:00
Cmdr McDonald
af2e0cbed3 Fixes 2017-02-25 22:18:09 +00:00
Cmdr McDonald
4bf30c0cd5 Updates 2017-02-25 14:45:47 +00:00
Cmdr McDonald
a9fdf73d86 Update to webpack 2 2017-02-25 13:15:24 +00:00
Cmdr McDonald
fe691d12c7 Remove shot speed modification 2017-02-23 18:55:44 +00:00
Cmdr McDonald
a5df542aa2 Power management default sort order is power usage in descending order 2017-02-22 10:44:56 +00:00
Cmdr McDonald
069959dabb Merge branch 'release/2.2.18' into develop 2017-02-22 08:29:47 +00:00
Cmdr McDonald
a0e8f19683 Merge branch 'release/2.2.18' 2017-02-22 08:29:43 +00:00
Cmdr McDonald
342ca7af05 Tidy-ups for release 2017-02-22 08:28:55 +00:00
Cmdr McDonald
d00c0c3904 Merge branch 'feature/ddgraph' into develop 2017-02-22 08:01:09 +00:00
Cmdr McDonald
8e0f1ca977 Tweaks 2017-02-22 07:58:51 +00:00
Cmdr McDonald
33360fd6cf Doc updates 2017-02-22 00:19:41 +00:00
Cmdr McDonald
78ad34b082 Update docs 2017-02-22 00:16:33 +00:00
Cmdr McDonald
ba98fe49a9 Reorder panels 2017-02-22 00:14:21 +00:00
Cmdr McDonald
11f5c04efa Updates for production version of d3 2017-02-21 23:54:27 +00:00
Cmdr McDonald
ee9f65052a Tidy-ups 2017-02-21 23:21:33 +00:00
Cmdr McDonald
a25dde8d2d Tweaks 2017-02-21 21:50:28 +00:00
Cmdr McDonald
8d813688f7 Merge branch 'feature/ddgraph' of https://github.com/EDCD/coriolis into feature/ddgraph 2017-02-21 21:36:41 +00:00
Cmdr McDonald
99fc55ba6c Fix up formats for d3 v4 2017-02-21 21:36:24 +00:00
Cmdr McDonald
402a4c1939 Move to newer version of d3 2017-02-21 21:36:24 +00:00
Cmdr McDonald
d1e16470b8 Fixes 2017-02-21 21:36:24 +00:00
Cmdr McDonald
76027b8537 Playing with damage dealt graph 2017-02-21 21:36:24 +00:00
Cmdr McDonald
fd5ff3b6a8 Fix up formats for d3 v4 2017-02-21 21:35:54 +00:00
Cmdr McDonald
d2d8f084d2 Move to newer version of d3 2017-02-21 19:22:14 +00:00
Cmdr McDonald
ea3d57399c Tidy up available modules layout 2017-02-21 16:27:09 +00:00
Cmdr McDonald
77d3053ff8 Handle 'higher better' modifications when both min and max are negative - fixes #80 2017-02-20 14:05:19 +00:00
Cmdr McDonald
aea3e43e1c Fixes and tidy-ups 2017-02-19 20:49:31 +00:00
Cmdr McDonald
a949bd6738 Merge branch 'feature/explorer' into develop 2017-02-19 19:26:32 +00:00
Cmdr McDonald
4981ffb908 Make integrity for module reinforcement packages visible 2017-02-19 19:26:16 +00:00
Cmdr McDonald
4859138053 Tidy up layout for module selection 2017-02-19 19:25:50 +00:00
Cmdr McDonald
1f3c66d9ba Tidy up explorer role 2017-02-19 12:50:21 +00:00
Cmdr McDonald
f2af463d00 Remove cruft 2017-02-19 12:50:09 +00:00
Cmdr McDonald
3858712613 Hardcode shield translation for internal slot optmul 2017-02-18 21:29:21 +00:00
Cmdr McDonald
d7941e0a8a Merge branch 'release/2.2.17' into develop 2017-02-18 10:00:14 +00:00
Cmdr McDonald
178d38f28d Merge branch 'release/2.2.17' 2017-02-18 10:00:10 +00:00
Cmdr McDonald
e42a0d1210 Bump 2017-02-18 10:00:07 +00:00
Cmdr McDonald
ddb89c47e7 Merge branch 'feature/multicrew' into develop 2017-02-18 09:51:24 +00:00
Cmdr McDonald
8b9aae342b Fix incorrect terminology for shield metrics. Fixes #5 2017-02-18 09:07:18 +00:00
Cmdr McDonald
ab1d73a6ea Add crew 2017-02-16 22:43:06 +00:00
Cmdr McDonald
d73a3cc2b4 Merge branch 'release/2.2.16' into develop 2017-02-15 20:44:21 +00:00
Cmdr McDonald
230351b959 Merge branch 'release/2.2.16' 2017-02-15 20:44:17 +00:00
Cmdr McDonald
4ee5c03cd1 Bump version 2017-02-15 20:44:09 +00:00
Cmdr McDonald
d8e9733170 Fix issue where extreme values didn't know if a given value was higher == better or higher == worse 2017-02-15 19:10:58 +00:00
Cmdr McDonald
aba2abe507 Merge branch 'release/2.2.15' into develop 2017-02-13 19:50:55 +00:00
Cmdr McDonald
45e6b71ec9 Merge branch 'release/2.2.15' 2017-02-13 19:50:51 +00:00
Cmdr McDonald
15a14dc280 Package version 2017-02-13 19:50:47 +00:00
Cmdr McDonald
cbac650b9e Added miner and shielded miner roles 2017-02-13 08:07:35 +00:00
Cmdr McDonald
f011f1f4d5 Add ability to export module list to EDDB to find where they can be purchased 2017-02-13 07:59:29 +00:00
Cmdr McDonald
d467ad5f7c Merge branch 'feature/fixes' into develop 2017-02-12 11:11:10 +00:00
Cmdr McDonald
b56ac177d9 Merge branch 'willyb321-master' into feature/fixes 2017-02-12 11:08:05 +00:00
Cmdr McDonald
5b13d64a1d Merge branch 'master' of https://github.com/willyb321/coriolis into willyb321-master 2017-02-12 11:07:46 +00:00
Cmdr McDonald
2620935745 Add additional explanation for import failures 2017-02-12 08:31:09 +00:00
Cmdr McDonald
45852db507 Add 'Extreme' roll 2017-02-11 19:57:46 +00:00
Cmdr McDonald
7fbcbb75ed Do not rely on slot sizes being correct 2017-02-11 19:45:28 +00:00
Cmdr McDonald
abf65ee436 Reload page if Safari throws a security error 2017-02-10 18:22:39 +00:00
Cmdr McDonald
24849cee08 Ensure that standard slots are repainted when any component changes 2017-02-08 10:09:30 +00:00
Cmdr McDonald
9e5efe50dc Merge branch 'release/2.2.14' into develop 2017-02-08 09:29:08 +00:00
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
William
588cfc3990 fix wiki link 2017-02-08 16:14:34 +11: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
bc31be5884 Fixes 2017-02-01 07:32:33 +00:00
Cmdr McDonald
df03102c35 Added time to drain calculation and display 2017-01-30 21:31:55 +00:00
Cmdr McDonald
55e4c51d77 Playing with damage dealt graph 2017-01-30 13:34:41 +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
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
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
116 changed files with 16549 additions and 2790 deletions

1
.gitignore vendored
View File

@@ -6,3 +6,4 @@ nginx.pid
.idea
/bin
env
*.swp

View File

@@ -3,7 +3,7 @@ notifications:
email: false
sudo: false
node_js:
- "4.2.6"
- "4.8.1"
cache:
directories:
- node_modules
@@ -12,4 +12,4 @@ before_script:
script:
- npm run lint
- npm test
- npm test

View File

@@ -1,3 +1,200 @@
#2.3.7
* Fixed Travis test issues
* Bumped NodeJS version to provide better compatability and support
* Added updated German Translation
* Fixed issues with Safari
* Use coriolis-data 2.3.7
* Fixed Orca mass-lock
#2.3.6
* Update miner role to provide better defaults
* Fix issue where torpedo special effects were not showing
* Fix typo causing long range blueprint to not modify shot speed in some circumstances
* Fix for Spanish translation of Chaff Launcher (thanks to DamonFstr)
* Update for Russian translation (thanks to LeeNTien)
* Use coriolis-data 2.3.6:
* Add shotspeed modifier to cannon/multi-cannon/fragment cannon
#2.3.5
* Ensure that hidden blueprint effects are applied when a blueprint is selected
* Handle display when summary values show thrusters disabled but current mass keeps them enabled
* Added updated German translations (thanks to @sweisgerber-dev)
* Power state (enabled and priority) now follows modules when they are swapped or copied
* Grey out modules that are powered off to provide a clearer visual indication
* Use coriolis-data 2.3.5:
* Fix list of available blueprints for Point Defence
* Fix integrity values for class 6 power plants
* Add shot speed for long range weapon
* Fix components for dirty drive grade 3
* Update values for Cytoscrambler
#2.3.4
* Fix crash when removing the special effect from a module
* Ensure comparisons with saved stock ships work correctly
* Add 'Racer' role
* Tidy up shipyard page; remove units from data columns and re-order for legibility
* Allow basic drag/drop functionality in Edge/Internet Explorer 11 browser
* Provide separate special effects for dumbfire and seeker missiles
* Include special effect modifiers in blueprint tooltip
* Use coriolis-data 2.3.4:
* Add missing Long Range blueprint to multi-cannon
* Fix values for thermal load of focused weapon grade 4
* Fix internal module information for power plant blueprints
* Add 'FSD Interrupt' special to dumbfire missile racks; this module now has `specials_S` and `specials_D` keys for specials to differentiate
#2.3.3
* Remove unused blueprint when hitting reset
* Add 'purchase module' external link to EDDB for refit items
* Use coriolis-data 2.3.3:
* Add Felicity Farseer to list of engineers that supply sensor and detailed surface scanner modifications
#2.3.2
* Use scan range for DSS rather than scan time
* Fix companion API import of Dolphin
* Use coriolis-data 2.3.2:
* Separate scan time and scan range
* Add Frontier IDs for new items in 2.3
* Update ownership of module blueprints for sensors and scanners
* Update railgun penetration
#2.3.0
* Make scan time visible on scanners where available
* Update power distributor able-to-boost calculation to take fractional MJ values in to account
* Revert to floating header due to issues on iOS
* Fix issue where new module added to a slot did not reset its enabled status
* Show integrity value for relevant modules
* Reset old modification values when a new roll is applied
* Fix issue with miner role where refinery would not be present in ships with class 5 slots but no class 4
* Ensure that boost value is set correctly when modifications to power distributor enable/disable boost
* Ensure that hull reinforcement modifications take the inherent resistance in to account when calculating modification percentages
* Add tooltip for blueprints providing details of the features they alter, the components required for the blueprint and the engineer(s) who cam craft them
* Use opponent's saved pips if available
* Ignore rounds per shot for EPS and HPS calculations; it's already factored in to the numbers
* Ensure that clip size modification imports result in whole numbers
* Rework of separate offence/defence/movement sections to a unified interface
* Use cargo hatch information on import if available
* Additional information of power distributor pips, boost, cargo and fuel loads added to build
* Additional information of opponent and engagement range added to build
* Reworking of offence, defence and movement information in to separate tabs as part of the outfitting screen:
* Power and costs section provides the existing 'Power' and 'Costs' sections
* Profiles section provides a number of graphs that show how various components of the build (top speed, sustained DPS against opponent's shields and armour etc) are affected by mass, range, etc.
* Offence section provides details of your build's damage distribution and per-weapon effectiveness. It also gives summary information for how long it will take for your build to wear down your opponent's shields and armour
* Defence section provides details of your build's defences against your selected opponent. It provides details of the effectiveness of your resistances of both shields and armour, and effective strength of each as a result. It also provides key metrics around shield longevity and recovery times, as well as module protection
* Fix power band marker to show safe power limit at 40% rather than 50%
* Restyle blueprint list to improve consistency with similar menus
* Use coriolis-data 2.3.0:
* Add Dolphin
* Add turreted mining lasers
* Add long range / wide angle / fast scan scanner blueprints
* Fix EDDB IDs for class 5 and 7 fighter hangars for correct shopping list
* Fix cost for rocket-propelled FSD disruptor
* Add module names for blueprints
* Fix erroneous value for grade 5 kinetic shield booster
* Add missing integrity values for some modules
* Update module reinforcement package integrity
* Update specs of Beluga as per 2.3
* Update specs of Asp Scout as per 2.3
* Update specs of Diamondback Explorer as per 2.3
* Add ED ID for Rocket Propelled FSD Disruptor
* Fix ED name for target lock breaker special
* Update scan range and angle information for sensors
* Tidy up shield cell bank information to allow for accurate calculations with modifications
* Update mine launcher stats
* Add appropriate engineers to per-module blueprint information
#2.2.19
* Power management panel now displays modules in descending order of power usage by default
* Shot speed can no longer be modified directly. Its value is derived from the range modifier for Long Range and Focused modifications
* Ensure that jump range chart updates when fuel slider is changed
* Add 'Engine profile' and 'FSD profile' charts. These show how your maximum speed/jump range will alter as you alter the mass of your build
* Use coriolis-data 2.2.19:
* Remove shot speed modification - it is directly tied to range
* Fix incorrect minimal mass for 3C bi-weave shield generator
#2.2.18
* Change methodology for calculating explorer role; can result in lighter builds
* Tidy up layout for module selection and lay everything out in a consistent best-to-worst for both class and grade
* Make integrity for module reinforcement packages visible
* Clean up breakpoints for modules in available modules list; stops 7- or 8- module long lines
* Add damager/range graphs to damage dealt
* Reorder panels
* Use coriolis-data 2.2.18:
* Correct lower efficiency value to be better, not worse
#2.2.17
* Use in-game terminology for shield generator optmul and optmass items
* Add crew to shipyard and outfitting page information
* Use coriolis-data 2.2.17:
* Add mass as potential SCB modification
* Fix mining laser statistics
* Remove non-existent grade 4 and 5 wake scanner modifications
* Add number of crew for each ship
#2.2.16
* Fix 'Extreme' blueprint roll where some incorrect ranges were chosen
* Use coriolis-data 2.2.16:
* Fix incorrect thermal load modifiers for dirty drives
* Provide explicit information about if values are higher numeric value == better or not
#2.2.15
* Ensure that standard slots are repainted when any component changes
* Reload page if Safari throws a security error
* Handle import of ships with incorrectly-sized slots
* Add 'Extreme' blueprint roll: best beneficial and worst detrimental outcome (in place of 'Average' roll)
* Display information about Microsoft browser issues when an import fails
* Add 'purchase this build' icon link to EDDB
* Add 'miner' and 'shielded miner' ship roles
* Use coriolis-data 2.2.15:
* Fix location of initial cargo rack for Vulture
* Fix broken regeneration rate for 6B shield generators
* Tidy up breach damage values
#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

View File

@@ -16,7 +16,7 @@ Chat to us on [Discord](https://discord.gg/0uwCh6R62aPRjk9w)!
## Development
See the [Developer's Guide](https://github.com/cmmcleod/coriolis/wiki/Developer's-Guide) in the wiki.
See the [Developer's Guide](https://github.com/EDCD/coriolis/wiki/Developing-for-Coriolis) in the wiki.
### Ship and Module Database

View File

@@ -25,7 +25,7 @@
"priority": 1,
"modifications": {
"pgen": 1000
}
}
},
"thrusters": {
"class": 6,
@@ -269,17 +269,19 @@
"topSpeed": 187.01,
"totalCost": 882362058,
"totalDpe": 142.68,
"totalDps": 103.8,
"totalEps": 22.71,
"totalHps": 677.29,
"totalDps": 101.13,
"totalEps": 18.71,
"totalExplDpe": 0,
"totalExplDps": 0,
"totalExplSDps": 0,
"totalHps": 33.62,
"totalAbsDpe": 3.57,
"totalAbsDps": 18.78,
"totalAbsSDps": 14.45,
"totalHps": 28.28,
"totalKinDpe": 117.48,
"totalKinDps": 24.94,
"totalKinSDps": 18.76,
"totalSDps": 91.84,
"totalKinDps": 22.27,
"totalKinSDps": 16.91,
"totalSDps": 89.99,
"totalThermDpe": 21.63,
"totalThermDps": 60.08,
"totalThermSDps": 58.64,
@@ -317,6 +319,7 @@
"shieldCells": 1840,
"shieldExplRes": 0.5,
"shieldKinRes": 0.4,
"shieldThermRes": -0.2
"shieldThermRes": -0.2,
"crew": 3
}
}

View File

@@ -0,0 +1,314 @@
{
"cargo": {
"capacity": 264
},
"free": false,
"fuel": {
"main": {
"capacity": 32
},
"reserve": {
"capacity": 0.52
}
},
"id": 4,
"modules": {
"Armour": {
"module": {
"free": false,
"id": 128049298,
"name": "Type7_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": 128667746,
"name": "Decal_Trade_Dealer",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 0
}
},
"Decal2": {
"module": {
"free": false,
"id": 128667738,
"name": "Decal_Combat_Competent",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 0
}
},
"Decal3": {
"module": {
"free": false,
"id": 128667753,
"name": "Decal_Explorer_Scout",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 0
}
},
"EngineColour": [],
"FrameShiftDrive": {
"module": {
"free": false,
"id": 128064122,
"name": "Int_Hyperdrive_Size5_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 5103953
}
},
"FuelTank": {
"module": {
"free": false,
"id": 128064350,
"name": "Int_FuelTank_Size5_Class3",
"on": true,
"priority": 1,
"unloaned": 97754,
"value": 97754
}
},
"LifeSupport": {
"module": {
"free": false,
"id": 128064154,
"name": "Int_LifeSupport_Size4_Class2",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 28373
}
},
"MainEngines": {
"module": {
"free": false,
"id": 128064087,
"name": "Int_Engine_Size5_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 5103953
}
},
"PaintJob": {
"module": {
"free": false,
"id": 128671422,
"name": "PaintJob_Type7_Tactical_White",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 0
}
},
"PlanetaryApproachSuite": {
"module": {
"free": false,
"id": 128672317,
"name": "Int_PlanetApproachSuite",
"on": true,
"priority": 1,
"unloaned": 500,
"value": 500
}
},
"PowerDistributor": {
"module": {
"free": false,
"id": 128064192,
"name": "Int_PowerDistributor_Size3_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 158331
}
},
"PowerPlant": {
"module": {
"free": false,
"id": 128064047,
"name": "Int_Powerplant_Size4_Class5",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 1610080
}
},
"Radar": {
"module": {
"free": false,
"id": 128064229,
"name": "Int_Sensors_Size3_Class2",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 10133
}
},
"Slot01_Size6": {
"module": {
"free": false,
"id": 128064343,
"name": "Int_CargoRack_Size6_Class1",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 362591
}
},
"Slot02_Size6": {
"module": {
"free": false,
"id": 128064343,
"name": "Int_CargoRack_Size6_Class1",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 362591
}
},
"Slot03_Size5": {
"module": {
"free": false,
"id": 128064343,
"name": "Int_CargoRack_Size6_Class1",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 362591
}
},
"Slot04_Size5": {
"module": {
"free": false,
"id": 128064342,
"name": "Int_CargoRack_Size5_Class1",
"on": true,
"priority": 1,
"unloaned": 111566,
"value": 111566
}
},
"Slot05_Size4": {
"module": {
"free": false,
"id": 128064342,
"name": "Int_CargoRack_Size5_Class1",
"on": true,
"priority": 1,
"unloaned": 111566,
"value": 111566
}
},
"Slot06_Size4": {
"module": {
"free": false,
"id": 128064279,
"name": "Int_ShieldGenerator_Size5_Class2",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 189035
}
},
"Slot07_Size2": {
"module": {
"free": false,
"id": 128049549,
"name": "Int_DockingComputer_Standard",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 4500
}
},
"Slot08_Size2": {
"module": {
"free": false,
"id": 128064340,
"name": "Int_CargoRack_Size3_Class1",
"on": true,
"priority": 1,
"unloaned": 0,
"value": 10563
}
},
"SmallHardpoint1": [],
"SmallHardpoint2": [],
"SmallHardpoint3": [],
"SmallHardpoint4": [],
"TinyHardpoint1": {
"module": {
"free": false,
"id": 128668536,
"name": "Hpt_ShieldBooster_Size0_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 281000
}
},
"TinyHardpoint2": {
"module": {
"free": false,
"id": 128668536,
"name": "Hpt_ShieldBooster_Size0_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 281000
}
},
"TinyHardpoint3": {
"module": {
"free": false,
"id": 128668536,
"name": "Hpt_ShieldBooster_Size0_Class5",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 281000
}
},
"TinyHardpoint4": {
"module": {
"free": false,
"id": 128049513,
"name": "Hpt_ChaffLauncher_Tiny",
"on": true,
"priority": 0,
"unloaned": 0,
"value": 8500
}
},
"WeaponColour": []
},
"name": "Type7",
"value": {
"hull": 16780009,
"modules": 14479580,
"unloaned": 321386
}
}

View File

@@ -0,0 +1,225 @@
{
"free": false,
"id": 2,
"modules": {
"Armour": {
"module": {
"free": false,
"id": 128049280,
"name": "CobraMkIII_Armour_Grade1",
"on": true,
"priority": 1,
"value": 0
}
},
"FrameShiftDrive": {
"module": {
"free": false,
"id": 128064117,
"name": "Int_Hyperdrive_Size4_Class5",
"on": true,
"priority": 4,
"value": 1610080
}
},
"FuelTank": {
"module": {
"free": false,
"id": 128064349,
"name": "Int_FuelTank_Size4_Class3",
"on": true,
"priority": 1,
"value": 24734
}
},
"LifeSupport": {
"module": {
"free": false,
"id": 128064149,
"name": "Int_LifeSupport_Size3_Class2",
"on": true,
"priority": 0,
"value": 10133
}
},
"MainEngines": {
"module": {
"free": false,
"id": 128064079,
"name": "Int_Engine_Size4_Class2",
"on": true,
"priority": 0,
"value": 59633
}
},
"PaintJob": {
"module": {
"free": false,
"id": 128741033,
"name": "PaintJob_CobraMKIII_Corrosive_05",
"on": true,
"priority": 1,
"value": 0
}
},
"PlanetaryApproachSuite": {
"module": {
"free": false,
"id": 128672317,
"name": "Int_PlanetApproachSuite",
"on": true,
"priority": 1,
"value": 500
}
},
"PowerDistributor": {
"module": {
"free": false,
"id": 128064179,
"name": "Int_PowerDistributor_Size1_Class2",
"on": true,
"priority": 2,
"value": 1293
}
},
"PowerPlant": {
"module": {
"free": false,
"id": 128064037,
"name": "Int_Powerplant_Size2_Class5",
"on": true,
"priority": 1,
"value": 160224
}
},
"Radar": {
"module": {
"free": false,
"id": 128064229,
"name": "Int_Sensors_Size3_Class2",
"on": true,
"priority": 0,
"value": 10133
}
},
"ShipID0": {
"module": {
"free": false,
"id": 128758976,
"name": "Nameplate_ShipID_Black",
"on": true,
"priority": 1,
"value": 0
}
},
"ShipID1": {
"module": {
"free": false,
"id": 128758976,
"name": "Nameplate_ShipID_Black",
"on": true,
"priority": 1,
"value": 0
}
},
"ShipKitBumper": {
"module": {
"free": false,
"id": 128740698,
"name": "CobraMkIII_ShipkitRaider1_Bumper1",
"on": true,
"priority": 1,
"value": 0
}
},
"ShipKitSpoiler": {
"module": {
"free": false,
"id": 128740701,
"name": "CobraMkIII_ShipkitRaider1_Spoiler1",
"on": true,
"priority": 1,
"value": 0
}
},
"ShipKitTail": {
"module": {
"free": false,
"id": 128740705,
"name": "CobraMkIII_ShipkitRaider1_Tail2",
"on": true,
"priority": 1,
"value": 0
}
},
"ShipKitWings": {
"module": {
"free": false,
"id": 128740707,
"name": "CobraMkIII_ShipkitRaider1_Wings1",
"on": true,
"priority": 1,
"value": 0
}
},
"ShipName0": {
"module": {
"free": false,
"id": 128758944,
"name": "Nameplate_Explorer01_Black",
"on": true,
"priority": 1,
"value": 0
}
},
"ShipName1": {
"module": {
"free": false,
"id": 128758944,
"name": "Nameplate_Explorer01_Black",
"on": true,
"priority": 1,
"value": 0
}
},
"Slot01_Size4": {
"module": {
"free": false,
"id": 128666663,
"name": "Int_FuelScoop_Size4_Class3",
"on": true,
"priority": 2,
"value": 178898
}
},
"Slot02_Size4": [],
"Slot03_Size4": [],
"Slot04_Size2": [],
"Slot05_Size2": {
"module": {
"free": false,
"id": 128663561,
"name": "Int_StellarBodyDiscoveryScanner_Advanced",
"on": true,
"priority": 2,
"value": 1545000
}
},
"Slot06_Size2": {
"module": {
"free": false,
"id": 128666634,
"name": "Int_DetailedSurfaceScanner_Tiny",
"on": true,
"priority": 2,
"value": 250000
}
}
},
"name": "CobraMkIII",
"value": {
"hull": 205287,
"modules": 3850628,
"unloaned": 1751109
}
}

View File

@@ -36,7 +36,7 @@
"Test": "A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.Iw18ZVA=.Aw18ZVA=."
},
"diamondback_explorer": {
"Explorer": "A0p0tdFfldddsdf5---0202--320p432i2f.AwRj4zTI.AwiMIypI."
"Explorer": "A0p0tdFfldddsdf5---0202--320p432i2f-.AwRj4zTYg===.AwiMIyoo."
},
"vulture": {
"Bounty Hunter": "A3patcFalddksff31e1e0404-0l4a-5d27662j.AwRj4z2I.MwBhBYy6oJmAjLIA."

View File

@@ -1,8 +1,10 @@
jest.dontMock('../src/app/stores/Persist');
jest.dontMock('../src/app/components/TranslatedComponent');
jest.dontMock('../src/app/components/ModalImport');
jest.unmock('../src/app/stores/Persist');
jest.unmock('../src/app/components/TranslatedComponent');
jest.unmock('../src/app/components/ModalImport');
jest.unmock('prop-types');
import React from 'react';
import PropTypes from 'prop-types';
import ReactDOM from 'react-dom';
import TU from 'react-testutils-additions';
import Utils from './testUtils';
@@ -167,7 +169,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.AwRj4zNLaA%3D%3D.CwBhCYzBGW9qCTSqq5xA.H4sIAAAAAAAAA2MUe8HMwPD%2FPwMcAABTINwTEgAAAA%3D%3D&bn=Test%20My%20Ship');
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.AwRj4zNLaA%3D%3D.CwBhCYzBGW9qCTSqq5xA.H4sIAAAAAAAAA2MUe8HMwPD%2FPwMAAGvB0AkAAAA%3D&bn=Test%20My%20Ship');
});
});
@@ -184,7 +186,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/asp?code=A0pftiFflfddsnf5------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');
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/asp?code=A0pftiFflfddsnf5------020202033c044002v62f2i.AwRj4yvI.CwRgDBldHnJA.H4sIAAAAAAAAA2P858DAwPCXEUhwHPvx%2F78YG5AltB7I%2F8%2F0TwImJboDSPJ%2F%2B%2Ff%2Fv%2FKlX%2F%2F%2Fi3AwMTBIfARK%2FGf%2BJwVSxArStVAYqOjvz%2F%2F%2FJVo5GRhE2IBc4SKQSSz%2FDGEmCa398P8%2F%2F2%2BgTf%2F%2FA7kMAExxqlSAAAAA&bn=Multi-purpose%20Asp%20Explorer');
});
it('imports a valid v4 build with modifications', function() {
@@ -196,7 +198,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/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');
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/imperial_courier?code=A0patzF5l0das8f31a1a270202000e402t0101-2f.AwRj4zKA.CwRgDBldLiQ%3D.H4sIAAAAAAAAA12OPUvDYBSFT1OTfkRJjUkbbC3Yj8mlODgUISAtdOlety5ODv0Vgji7O7kJ%2FgzBQX%2BEY7Gg0NKhfY%2FnHQLFDBdynufe9%2BRMCmCb06g29oCgacjiRx6gY6oWKUT8UgLaszqQfHmSnpVFN1uSeXNsJVcj%2FA2EHlZkspIUpUc6UjTXGT85qwHuSEuVc%2F16r99kDQeSSjvSbSjpyUpNK10uJJ3aYqk6smwm1lQ9bOxw71TMm8VanEqq9JW1r3Qo%2BREOLnQHvbWmb7rZIu5VLIyGQGOukPv%2F0WQk5LeEAjPOUDwtAP6bShy2HKAz0HPO%2B5KsP25I79O2I7LvD%2Bz4Il1XAQAA&bn=Multi-purpose%20Imperial%20Courier');
});
});
@@ -229,7 +231,7 @@ describe('Import Modal', function() {
beforeEach(reset);
it('imports a valid v4 build', function() {
it('imports a valid companion API build', function() {
const importData = require('./fixtures/companion-api-import-1');
pasteText(JSON.stringify(importData));
@@ -238,10 +240,10 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/federal_corvette?code=A2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifr--v66g2f.AwRj4zNaqA%3D%3D.CwRgDBldUExuBiIlUA%3D%3D.H4sIAAAAAAAAA02Svy9DURTHT1vvtfoat32eekVV9fm1kBgwSIw0YWYgBqmpMZkMBomFVfwFEoZKhBjE1qWTgegiDX%2BCQdKI1j2%2BR%2FJ4yzfnvu%2FnfO%2B979yQXiCi7xAkbRpEqsLMsRKWHNZpsSKQnppJVLAdIvc6DGiwxexMaWb7GDZHdJ%2BQaCf71Ia%2F88XsOp1EThk9bOh5P2kkahGN3qPM1wANbyOk87zNHH%2FBUs0gnWN61T9TOwfJ7EWJjMcms1lEo30Gx11BD8f1mh%2FcTkCMMvY0HZcoe4Wk5By%2BFcrrRL0N0OOlrd0Ntv57jGoc%2BH4%2F8EqHj3%2FCUXc4FicC5NFvsJBVIWeFvESlpuXSuCS5RRyLlV70z%2B4uQaw6ypSIJ6KOJDgZgFpQ60YgEU9EPQmUCkAfAj0IJOKJqC4wuYMY9rQD5CuubT0LSag8qdShxHUHoElcyWrAT4l4IsoCw65e%2BRv5BqKtC0mSJu8LH8OFT%2Bb%2BE8SZb0CcEn4AZ3TRDx5q4l1EJ%2BCP1bEM1WSaAwH%2FFkOLPoofwTo0LY8nr7O%2B37cp4yWIu4zHlHiXGfMPmat5gqMCAAA%3D&bn=Imported%20Federal%20Corvette');
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/federal_corvette?code=A2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifr--v66g2f.AwRj4zNaqA%3D%3D.CwRgDBldUExuBiIlUA%3D%3D.H4sIAAAAAAAAA12STy8DURTFb1szU53Ga8dg2qqqDmJDIoKFxJImumYjVrVqfAALC4lNbcUnkLCoDbEQu0bSlQVhI8JHsJBIQ73rXMkwMYuT9%2Bb87nl%2F7ovoRSL6ikD6TYNINZg5XsWUo7pfrBikr2USlRyXyDuLAhr6ZHanNLOzD5tjOiskysk5dOBvfTB7bjeRW0MNG3ohSBq1bKKxKwyLLUAjmwjpPu4wJx4xVbNI57heDfbUKUAy2xaRUQZpllHoHMHxKqjhhF4LgjtJiFHDmqbrEeVnUJOax7%2FSdRfRwBNotv9wo5kAuZMD2egKyDYcdYl1OBki6z%2BZQjaFnBPyFCM1LefF%2BcgrY0es9FKwbW8ZYj9gmBbxRVRdglMh6BNqnwsk4ouoO4HSIehNoBuBRHwR1QOmsBvHmk6IfMbd2fdCEka%2BjNSexPWGoEkcyX6CnxbxRZQtd%2BPpym%2B31xFtn0iSFPkf%2BBkttZlzB9KDFyBuFRfAGV0Ogoff8SSsCfjjD5hGWtLIwZB%2FgX5Zt%2BLHMI9My7sp6nzgZzekswTxVvCOkq%2FSXqb%2F3zfLxh6HrwIAAA%3D%3D&bn=Imported%20Federal%20Corvette');
});
it('imports a valid v4 build', function() {
it('imports a valid companion API build', function() {
const importData = require('./fixtures/companion-api-import-2');
pasteText(JSON.stringify(importData));
@@ -250,7 +252,31 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/beluga?code=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');
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/beluga?code=A0pktsFplCdpsnf70t0t2727270004040404043c4fmimlmm04mc0iv62i2f.AwRj4yukg%3D%3D%3D.CwRgDBldHi8IUA%3D%3D.H4sIAAAAAAAAA2P8Z8%2FAwPCXEUiIKTMxMPCv%2F%2Ff%2FP8cFIPGf6Z8YTEr0GjMDg%2FJWICERBOTzn%2Fn7%2F7%2FIO5Ai5n9SIEWsQEIoSxAolfbt%2F3%2BJPk4GBhE7YQYGYVmgcuVnf4Aq%2FwOVAAAyiFctbgAAAA%3D%3D&bn=Imported%20Beluga%20Liner');
});
it('imports a valid companion API build', function() {
const importData = require('./fixtures/companion-api-import-3');
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/type_7_transport?code=A0patfFflidasdf5----0404040005050504044d2402.AwRj4yrI.CwRgDBlVK7EiA%3D%3D%3D.&bn=Imported%20Type-7%20Transporter');
});
it('imports a valid companion API build', function() {
const importData = require('./fixtures/companion-api-import-4');
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/cobra_mk_iii?code=A0p0tdFaldd3sdf4------34---2f2i.AwRj4yKA.CwRgDMYExrezBUg%3D.&bn=Imported%20Cobra%20Mk%20III');
});
});

View File

@@ -1,4 +1,4 @@
jest.dontMock('../src/app/stores/Persist');
jest.unmock('../src/app/stores/Persist');
import React from 'react';
import ReactDOM from 'react-dom';

View File

@@ -1,11 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
const TestUtils = {
createContextProvider: function(context) {
var _contextTypes = {};
Object.keys(context).forEach(function(key) {
_contextTypes[key] = React.PropTypes.any;
_contextTypes[key] = PropTypes.any;
});
return React.createClass({
@@ -21,4 +22,4 @@ const TestUtils = {
};
export default TestUtils;
export default TestUtils;

11
d3-funcs.js vendored Normal file
View File

@@ -0,0 +1,11 @@
export {
axisBottom,
axisLeft,
axisTop,
formatLocale,
line,
scaleBand,
scaleLinear,
scaleOrdinal,
select
} from 'd3';

8160
d3.js vendored Normal file

File diff suppressed because it is too large Load Diff

3
d3.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -1,6 +1,6 @@
{
"name": "coriolis_shipyard",
"version": "2.2.10",
"version": "2.3.7",
"repository": {
"type": "git",
"url": "https://github.com/EDCD/coriolis"
@@ -8,9 +8,10 @@
"homepage": "https://coriolis.edcd.io",
"bugs": "https://github.com/EDCD/coriolis/issues",
"private": true,
"engine": "node >= 4.0.0",
"engine": "node >= 4.8.1",
"license": "MIT",
"scripts": {
"prepublish": "rollup -c && uglifyjs d3.js -c -m -o d3.min.js",
"extract-translations": "grep -hroE \"(translate\\('[^']+'\\))|(tip.bind\\(null, '[^']+')\" src/* | grep -oE \"'[^']+'\" | grep -oE \"[^']+\" | sort -u -f",
"clean": "rimraf build",
"start": "node devServer.js",
@@ -18,12 +19,12 @@
"test": "jest",
"prod-serve": "nginx -p $(pwd) -c nginx.conf",
"prod-stop": "kill -QUIT $(cat nginx.pid)",
"build": "npm run clean && NODE_ENV=production webpack -d -p --config webpack.config.prod.js",
"build": "npm run clean && NODE_ENV=production webpack -p --config webpack.config.prod.js",
"rsync": "rsync -ae \"ssh -i $CORIOLIS_PEM\" --delete build/ $CORIOLIS_USER@$CORIOLIS_HOST:~/wwws",
"deploy": "npm run lint && npm test && npm run build && npm run rsync"
},
"jest": {
"scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
"transform": {".*": "<rootDir>/node_modules/babel-jest"},
"testRegex": "(/__tests__/test-.*|\\.(test|spec))\\.js$",
"moduleFileExtensions": [
"js",
@@ -36,7 +37,6 @@
"<rootDir>/node_modules/lodash",
"<rootDir>/node_modules/react",
"<rootDir>/node_modules/react-dom",
"<rootDir>/node_modules/react-addons-test-utils",
"<rootDir>/node_modules/react-testutils-additions",
"<rootDir>/node_modules/fbjs",
"<rootDir>/node_modules/fbemitter",
@@ -53,7 +53,7 @@
]
},
"devDependencies": {
"appcache-webpack-plugin": "^1.2.1",
"appcache-webpack-plugin": "^1.3.0",
"babel-core": "*",
"babel-eslint": "*",
"babel-jest": "*",
@@ -61,39 +61,47 @@
"babel-preset-es2015": "*",
"babel-preset-react": "*",
"babel-preset-stage-0": "*",
"css-loader": "^0.23.0",
"eslint": "2.2.0",
"eslint-plugin-react": "^4.0.0",
"expose-loader": "^0.7.1",
"express": "^4.13.3",
"extract-text-webpack-plugin": "^0.9.1",
"file-loader": "^0.8.4",
"html-webpack-plugin": "^1.7.0",
"jest-cli": "^16.0.1",
"jsen": "^0.6.0",
"json-loader": "^0.5.3",
"less": "^2.5.3",
"less-loader": "^2.2.1",
"react-addons-test-utils": "^15.0.1",
"react-testutils-additions": "^15.1.0",
"rimraf": "^2.4.3",
"style-loader": "^0.13.0",
"url-loader": "^0.5.6",
"webpack": "^1.9.6",
"webpack-dev-server": "^1.14.0"
"css-loader": "^0.28.0",
"d3-selection": "1",
"eslint": "3.19.0",
"eslint-plugin-react": "^6.10.3",
"expose-loader": "^0.7.3",
"express": "^4.15.2",
"extract-text-webpack-plugin": "2.1.0",
"file-loader": "^0.11.1",
"html-webpack-plugin": "^2.28.0",
"jest-cli": "^19.0.2",
"jsen": "^0.6.4",
"json-loader": "^0.5.4",
"less": "^2.7.2",
"less-loader": "^4.0.3",
"react-addons-perf": "^15.4.2",
"react-measure": "^1.4.7",
"react-testutils-additions": "^15.2.0",
"rimraf": "^2.6.1",
"rollup": "0.41",
"rollup-plugin-node-resolve": "3",
"style-loader": "^0.16.1",
"url-loader": "^0.5.8",
"webpack": "^2.4.1",
"webpack-dev-server": "^2.4.4",
"uglify-js": "^2.4.11"
},
"dependencies": {
"babel-polyfill": "*",
"classnames": "^2.2.0",
"browserify-zlib": "ipfs/browserify-zlib",
"browserify-zlib-next": "^1.0.1",
"classnames": "^2.2.5",
"coriolis-data": "EDCD/coriolis-data",
"d3": "3.5.16",
"fbemitter": "^2.0.0",
"lodash": "^4.15.0",
"d3": "4.8.0",
"detect-browser": "^1.7.0",
"fbemitter": "^2.1.1",
"lodash": "^4.17.4",
"lz-string": "^1.4.4",
"prop-types": "^15.5.8",
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react-number-editor": "Athanasius/react-number-editor.git#miggy",
"react": "^15.0.1",
"react-dom": "^15.0.1",
"superagent": "^1.4.0"
"recharts": "^0.22.3",
"superagent": "^3.5.2"
}
}

9
rollup.config.js Normal file
View File

@@ -0,0 +1,9 @@
import nodeResolve from "rollup-plugin-node-resolve";
export default {
entry: "d3-funcs.js",
format: "umd",
moduleName: "d3",
plugins: [nodeResolve({jsnext: true})],
dest: "d3.js"
};

View File

@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import Router from './Router';
import { EventEmitter } from 'fbemitter';
import { getLanguage } from './i18n/Language';
@@ -6,9 +7,11 @@ 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';
@@ -25,18 +28,18 @@ const zlib = require('zlib');
export default class Coriolis extends React.Component {
static childContextTypes = {
closeMenu: React.PropTypes.func.isRequired,
hideModal: React.PropTypes.func.isRequired,
language: React.PropTypes.object.isRequired,
noTouch: React.PropTypes.bool.isRequired,
onCommand: React.PropTypes.func.isRequired,
onWindowResize: React.PropTypes.func.isRequired,
openMenu: React.PropTypes.func.isRequired,
route: React.PropTypes.object.isRequired,
showModal: React.PropTypes.func.isRequired,
sizeRatio: React.PropTypes.number.isRequired,
termtip: React.PropTypes.func.isRequired,
tooltip: React.PropTypes.func.isRequired
closeMenu: PropTypes.func.isRequired,
hideModal: PropTypes.func.isRequired,
language: PropTypes.object.isRequired,
noTouch: PropTypes.bool.isRequired,
onCommand: PropTypes.func.isRequired,
onWindowResize: PropTypes.func.isRequired,
openMenu: PropTypes.func.isRequired,
route: PropTypes.object.isRequired,
showModal: PropTypes.func.isRequired,
sizeRatio: PropTypes.number.isRequired,
termtip: PropTypes.func.isRequired,
tooltip: PropTypes.func.isRequired
};
/**
@@ -159,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');
}
@@ -227,14 +242,19 @@ export default class Coriolis extends React.Component {
/**
* Show the term tip
* @param {string} term Term or Phrase
* @param {Object} opts Options - dontCap, orientation (n,e,s,w)
* @param {Object} opts Options - dontCap, orientation (n,e,s,w) (can also be the event if no options supplied)
* @param {SyntheticEvent} event Event
* @param {SyntheticEvent} e2 Alternative location for synthetic event from charts (where 'Event' is actually a chart index)
*/
_termtip(term, opts, event) {
if (opts && opts.nativeEvent) { // Opts is a SyntheticEvent
_termtip(term, opts, event, e2) {
if (opts && opts.nativeEvent) { // Opts is the SyntheticEvent
event = opts;
opts = { cap: true };
}
if (e2 instanceof Object && e2.nativeEvent) { // E2 is the SyntheticEvent
event = e2;
}
this._tooltip(
<div className={'cen' + (opts.cap ? ' cap' : '')}>{this.state.language.translate(term)}</div>,
event.currentTarget.getBoundingClientRect(),

View File

@@ -76,7 +76,16 @@ Router.go = function(path, state) {
if (isStandAlone()) {
Persist.setState(ctx);
}
history.pushState(ctx.state, ctx.title, ctx.canonicalPath);
try {
history.pushState(ctx.state, ctx.title, ctx.canonicalPath);
} catch (ex) {
sessionStorage.setItem('__safari_history_fix', JSON.stringify({
state: ctx.state,
title: ctx.title,
path: ctx.canonicalPath
}));
location.reload();
}
}
return ctx;
};
@@ -99,7 +108,16 @@ Router.replace = function(path, state, dispatch) {
if (isStandAlone()) {
Persist.setState(ctx);
}
history.replaceState(ctx.state, ctx.title, ctx.canonicalPath);
try {
history.replaceState(ctx.state, ctx.title, ctx.canonicalPath);
} catch (ex) {
sessionStorage.setItem('__safari_history_fix', JSON.stringify({
state: ctx.state,
title: ctx.title,
path: ctx.canonicalPath
}));
location.reload();
}
return ctx;
};

View File

@@ -1,5 +1,6 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
import PropTypes from 'prop-types';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import TranslatedComponent from './TranslatedComponent';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
import cn from 'classnames';
@@ -7,18 +8,87 @@ import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
const PRESS_THRESHOLD = 500; // mouse/touch down threshold
/*
* Categorisation of module groups
*/
const GRPCAT = {
'sg': 'shields',
'bsg': 'shields',
'psg': 'shields',
'scb': 'shields',
'cc': 'limpet controllers',
'fx': 'limpet controllers',
'hb': 'limpet controllers',
'pc': 'limpet controllers',
'pce': 'passenger cabins',
'pci': 'passenger cabins',
'pcm': 'passenger cabins',
'pcq': 'passenger cabins',
'fh': 'hangars',
'pv': 'hangars',
'fs': 'fuel',
'ft': 'fuel',
'hr': 'structural reinforcement',
'mrp': 'structural reinforcement',
'bl': 'lasers',
'pl': 'lasers',
'ul': 'lasers',
'ml': 'lasers',
'c': 'projectiles',
'mc': 'projectiles',
'fc': 'projectiles',
'pa': 'projectiles',
'rg': 'projectiles',
'mr': 'ordnance',
'tp': 'ordnance',
'nl': 'ordnance',
'sc': 'scanners',
'ss': 'scanners',
// Utilities
'cs': 'scanners',
'kw': 'scanners',
'ws': 'scanners',
'ch': 'defence',
'po': 'defence',
'ec': 'defence',
};
// Order here is the order in which items will be shown in the modules menu
const CATEGORIES = {
// Internals
'am': ['am'],
'cr': ['cr'],
'fi': ['fi'],
'fuel': ['ft', 'fs'],
'hangars': ['fh', 'pv'],
'limpet controllers': ['cc', 'fx', 'hb', 'pc'],
'passenger cabins': ['pce', 'pci', 'pcm', 'pcq'],
'rf': ['rf'],
'shields': ['sg', 'bsg', 'psg', 'scb'],
'structural reinforcement': ['hr', 'mrp'],
'dc': ['dc'],
// Hardpoints
'lasers': ['pl', 'ul', 'bl', 'ml'],
'projectiles': ['mc', 'c', 'fc', 'pa', 'rg'],
'ordnance': ['mr', 'tp', 'nl'],
// Utilities
'sb': ['sb'],
'hs': ['hs'],
'defence': ['ch', 'po', 'ec'],
'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners
};
/**
* Available modules menu
*/
export default class AvailableModulesMenu extends TranslatedComponent {
static propTypes = {
modules: React.PropTypes.oneOfType([React.PropTypes.object, React.PropTypes.array]).isRequired,
onSelect: React.PropTypes.func.isRequired,
diffDetails: React.PropTypes.func,
m: React.PropTypes.object,
shipMass: React.PropTypes.number,
warning: React.PropTypes.func
modules: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
onSelect: PropTypes.func.isRequired,
diffDetails: PropTypes.func,
m: PropTypes.object,
shipMass: PropTypes.number,
warning: PropTypes.func
};
static defaultProps = {
@@ -63,15 +133,55 @@ export default class AvailableModulesMenu extends TranslatedComponent {
} else {
list = [];
// At present time slots with grouped options (Hardpoints and Internal) can be empty
list.push(<div className='empty-c upp' key='empty' onClick={onSelect.bind(null, null)} >{translate('empty')}</div>);
for (let g in modules) {
if (m && g == m.grp) {
list.push(<div ref={(elem) => this.groupElem = elem} key={g} className={'select-group cap'}>{translate(g)}</div>);
} else {
list.push(<div key={g} className={'select-group cap'}>{translate(g)}</div>);
}
if (m) {
list.push(<div className='empty-c upp' key='empty' onClick={onSelect.bind(null, null)} >{translate('empty')}</div>);
}
list.push(buildGroup(g, modules[g]));
// Need to regroup the modules by our own categorisation
let catmodules = {};
// Pre-create to preserve ordering
for (let cat in CATEGORIES) {
catmodules[cat] = [];
}
for (let g in modules) {
const moduleCategory = GRPCAT[g] || g;
const existing = catmodules[moduleCategory] || [];
catmodules[moduleCategory] = existing.concat(modules[g]);
}
for (let category in catmodules) {
let categoryHeader = false;
// Order through CATEGORIES if present
const categories = CATEGORIES[category] || [category];
if (categories && categories.length) {
for (let n in categories) {
const grp = categories[n];
// We now have the group and the category. We might not have any modules, though...
if (modules[grp]) {
// Decide if we need a category header as well as a group header
if (categories.length === 1) {
// Show category header instead of group header
if (m && grp == m.grp) {
list.push(<div ref={(elem) => this.groupElem = elem} key={category} className={'select-category upp'}>{translate(category)}</div>);
} else {
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
}
} else {
// Show category header as well as group header
if (!categoryHeader) {
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
categoryHeader = true;
}
if (m && grp == m.grp) {
list.push(<div ref={(elem) => this.groupElem = elem} key={grp} className={'select-group cap'}>{translate(grp)}</div>);
} else {
list.push(<div key={grp} className={'select-group cap'}>{translate(grp)}</div>);
}
}
list.push(buildGroup(grp, modules[grp]));
}
}
}
}
}
@@ -95,10 +205,23 @@ export default class AvailableModulesMenu extends TranslatedComponent {
const sortedModules = modules.sort(this._moduleOrder);
// Calculate the number of items per class. Used so we don't have long lists with only a few items in each row
const tmp = sortedModules.map((v, i) => v['class']).reduce((count, cls) => { count[cls] = ++count[cls] || 1; return count; }, {});
const itemsPerClass = Math.max.apply(null, Object.keys(tmp).map(key => tmp[key]));
let itemsOnThisRow = 0;
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 disabled = false;
if (ModuleUtils.isShieldGenerator(m.grp)) {
// Shield generators care about maximum hull mass
disabled = mass > m.maxmass;
} else if (m.maxmass) {
// Thrusters care about total mass
disabled = mass + m.mass > m.maxmass;
}
let active = mountedModule && mountedModule.id === m.id;
let classes = cn(m.name ? 'lc' : 'c', {
warning: !disabled && warningFunc && warningFunc(m),
@@ -128,8 +251,9 @@ export default class AvailableModulesMenu extends TranslatedComponent {
case 'T': mount = <MountTurret className={'lg'}/>; break;
}
if (i > 0 && sortedModules.length > 3 && m.class != prevClass && (m.rating != prevRating || m.mount) && m.grp != 'pa') {
if (itemsOnThisRow == 6 || i > 0 && sortedModules.length > 3 && itemsPerClass > 2 && m.class != prevClass && (m.rating != prevRating || m.mount)) {
elems.push(<br key={'b' + m.grp + i} />);
itemsOnThisRow = 0;
}
elems.push(
@@ -138,6 +262,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
{(mount ? ' ' : '') + m.class + m.rating + (m.missile ? '/' + m.missile : '') + (m.name ? ' ' + translate(m.name) : '')}
</li>
);
itemsOnThisRow++;
prevClass = m.class;
prevRating = m.rating;
}
@@ -232,12 +357,12 @@ export default class AvailableModulesMenu extends TranslatedComponent {
return 1;
}
}
// Rating ordered from lowest (E) to highest (A)
// Rating ordered from highest (A) to lowest (E)
if (a.rating < b.rating) {
return 1;
return -1;
}
if (a.rating > b.rating) {
return -1;
return 1;
}
// Do not attempt to order by name at this point, as that mucks up the order of armour
return 0;
@@ -248,7 +373,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
*/
componentDidMount() {
if (this.groupElem) { // Scroll to currently selected group
findDOMNode(this).scrollTop = this.groupElem.offsetTop;
this.node.scrollTop = this.groupElem.offsetTop;
}
}
@@ -267,7 +392,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
*/
render() {
return (
<div
<div ref={node => this.node = node}
className={cn('select', this.props.className)}
onScroll={this._hideDiff}
onClick={(e) => e.stopPropagation() }

View File

@@ -1,5 +1,6 @@
import React from 'react';
import d3 from 'd3';
import PropTypes from 'prop-types';
import * as d3 from 'd3';
import TranslatedComponent from './TranslatedComponent';
const MARGIN = { top: 15, right: 20, bottom: 40, left: 150 };
@@ -44,17 +45,17 @@ export default class BarChart extends TranslatedComponent {
unit: ''
};
static PropTypes = {
colors: React.PropTypes.array,
data: React.PropTypes.array.isRequired,
desc: React.PropTypes.bool,
format: React.PropTypes.string.isRequired,
labels: React.PropTypes.array,
predicate: React.PropTypes.string,
properties: React.PropTypes.array,
title: React.PropTypes.string.isRequired,
unit: React.PropTypes.string.isRequired,
width: React.PropTypes.number.isRequired
static propTypes = {
colors: PropTypes.array,
data: PropTypes.array.isRequired,
desc: PropTypes.bool,
format: PropTypes.string.isRequired,
labels: PropTypes.array,
predicate: PropTypes.string,
properties: PropTypes.array,
title: PropTypes.string.isRequired,
unit: PropTypes.string.isRequired,
width: PropTypes.number.isRequired
};
/**
@@ -68,13 +69,13 @@ export default class BarChart extends TranslatedComponent {
this._updateDimensions = this._updateDimensions.bind(this);
this._hideTip = this._hideTip.bind(this);
let scale = d3.scale.linear();
let y0 = d3.scale.ordinal();
let y1 = d3.scale.ordinal();
let scale = d3.scaleLinear();
let y0 = d3.scaleBand();
let y1 = d3.scaleBand();
this.xAxis = d3.svg.axis().scale(scale).ticks(5).outerTickSize(0).orient('bottom').tickFormat(context.language.formats.s2);
this.yAxis = d3.svg.axis().scale(y0).outerTickSize(0).orient('left');
this.state = { scale, y0, y1, color: d3.scale.ordinal().range(props.colors) };
this.xAxis = d3.axisBottom(scale).ticks(5).tickSizeOuter(0).tickFormat(context.language.formats.s2);
this.yAxis = d3.axisLeft(y0).tickSizeOuter(0);
this.state = { scale, y0, y1, color: d3.scaleOrdinal().range(props.colors) };
}
/**
@@ -131,8 +132,8 @@ export default class BarChart extends TranslatedComponent {
let max = data.reduce((max, build) => (properties.reduce(((m, p) => (m > build[p] ? m : build[p])), max)), 0);
this.state.scale.range([0, innerWidth]).domain([0, max]);
this.state.y0.domain(data.map(bName)).rangeRoundBands([0, innerHeight], 0.3);
this.state.y1.domain(properties).rangeRoundBands([0, this.state.y0.rangeBand()]);
this.state.y0.domain(data.map(bName)).range([0, innerHeight], 0.3).padding(0.4);
this.state.y1.domain(properties).range([0, this.state.y0.bandwidth()]).padding(0.1);
this.setState({
barHeight,
@@ -192,7 +193,7 @@ export default class BarChart extends TranslatedComponent {
x={0}
y={y1(p)}
width={scale(build[p])}
height={y1.rangeBand()}
height={y1.bandwidth()}
fill={color(p)}
onMouseOver={this._showTip.bind(this, build, p, propIndex)}
onMouseOut={this._hideTip}

View File

@@ -0,0 +1,90 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import { nameComparator } from '../utils/SlotFunctions';
import { Pip } from './SvgIcons';
import LineChart from '../components/LineChart';
import Slider from '../components/Slider';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import Module from '../shipyard/Module';
/**
* Boost displays a boost button that toggles bosot
* Requires an onChange() function of the form onChange(boost) which is triggered whenever the boost changes.
*/
export default class Boost extends TranslatedComponent {
static propTypes = {
marker: PropTypes.string.isRequired,
ship: PropTypes.object.isRequired,
boost: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
const { ship, boost } = props;
this._keyDown = this._keyDown.bind(this);
this._toggleBoost = this._toggleBoost.bind(this);
}
/**
* Add listeners after mounting
*/
componentDidMount() {
document.addEventListener('keydown', this._keyDown);
}
/**
* Remove listeners before unmounting
*/
componentWillUnmount() {
document.removeEventListener('keydown', this._keyDown);
}
/**
* Handle Key Down
* @param {Event} e Keyboard Event
*/
_keyDown(e) {
if (e.ctrlKey || e.metaKey) { // CTRL/CMD
switch (e.keyCode) {
case 66: // b == boost
if (this.props.ship.canBoost()) {
e.preventDefault();
this._toggleBoost();
}
break;
}
}
}
/**
* Toggle the boost feature
*/
_toggleBoost() {
this.props.onChange(!this.props.boost);
}
/**
* Render boost
* @return {React.Component} contents
*/
render() {
const { formats, translate, units } = this.context.language;
const { ship, boost } = this.props;
// TODO disable if ship cannot boost
return (
<span id='boost'>
<button id='boost' className={boost ? 'selected' : null} onClick={this._toggleBoost}>{translate('boost')}</button>
</span>
);
}
}

View File

@@ -0,0 +1,76 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import Slider from '../components/Slider';
/**
* Cargo slider
* Requires an onChange() function of the form onChange(cargo), providing the cargo in tonnes, which is triggered on cargo level change
*/
export default class Cargo extends TranslatedComponent {
static propTypes = {
cargo: PropTypes.number.isRequired,
cargoCapacity: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
this._cargoChange = this._cargoChange.bind(this);
}
/**
* Update cargo level
* @param {number} cargoLevel percentage level from 0 to 1
*/
_cargoChange(cargoLevel) {
const { cargo, cargoCapacity } = this.props;
if (cargoCapacity > 0) {
// We round the cargo to whole number of tonnes
const newCargo = Math.round(cargoLevel * cargoCapacity);
if (newCargo != cargo) {
this.props.onChange(newCargo);
}
}
}
/**
* Render cargo slider
* @return {React.Component} contents
*/
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { cargo, cargoCapacity } = this.props;
return (
<span>
<h3>{translate('cargo carried')}: {formats.int(cargo)}{units.T}</h3>
<table style={{ width: '100%', lineHeight: '1em', backgroundColor: 'transparent' }}>
<tbody >
<tr>
<td>
<Slider
axis={true}
onChange={this._cargoChange}
axisUnit={translate('T')}
percent={cargo / cargoCapacity}
max={cargoCapacity}
scale={sizeRatio}
onResize={onWindowResize}
/>
</td>
</tr>
</tbody>
</table>
</span>
);
}
}

View File

@@ -1,9 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import Link from './Link';
import cn from 'classnames';
import { outfitURL } from '../utils/UrlGenerators';
import { SizeMap } from '../shipyard/Constants';
/**
@@ -12,11 +12,11 @@ import { SizeMap } from '../shipyard/Constants';
export default class ComparisonTable extends TranslatedComponent {
static propTypes = {
facets: React.PropTypes.array.isRequired,
builds: React.PropTypes.array.isRequired,
onSort: React.PropTypes.func.isRequired,
predicate: React.PropTypes.string.isRequired, // Used only to test again prop changes for shouldRender
desc: React.PropTypes.oneOfType([React.PropTypes.bool.isRequired, React.PropTypes.number.isRequired]), // Used only to test again prop changes for shouldRender
facets: PropTypes.array.isRequired,
builds: PropTypes.array.isRequired,
onSort: PropTypes.func.isRequired,
predicate: PropTypes.string.isRequired, // Used only to test again prop changes for shouldRender
desc: PropTypes.oneOfType([PropTypes.bool.isRequired, PropTypes.number.isRequired]), // Used only to test again prop changes for shouldRender
};
/**
@@ -72,21 +72,22 @@ export default class ComparisonTable extends TranslatedComponent {
* @return {React.Component} Table row
*/
_buildRow(build, facets, formats, units) {
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>
];
if (build && build.id && 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>
];
for (let f of facets) {
if (f.active) {
for (let p of f.props) {
cells.push(<td key={p}>{formats[f.fmt](build[p])}{f.unit ? units[f.unit] : null}</td>);
for (let f of facets) {
if (f.active) {
for (let p of f.props) {
cells.push(<td key={p}>{formats[f.fmt](build[p])}{f.unit ? units[f.unit] : null}</td>);
}
}
}
return <tr key={build.id + build.buildName} className='tr'>{cells}</tr>;
}
return <tr key={build.id + build.buildName} className='tr'>{cells}</tr>;
}
/**

View File

@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import { Ships } from 'coriolis-data/dist';
import Persist from '../stores/Persist';
@@ -6,16 +7,17 @@ import Ship from '../shipyard/Ship';
import { Insurance } from '../shipyard/Constants';
import { slotName, slotComparator } from '../utils/SlotFunctions';
import TranslatedComponent from './TranslatedComponent';
import { ShoppingIcon } from '../components/SvgIcons';
/**
* Cost Section
*/
export default class CostSection extends TranslatedComponent {
static PropTypes = {
ship: React.PropTypes.object.isRequired,
code: React.PropTypes.string.isRequired,
buildName: React.PropTypes.string
static propTypes = {
ship: PropTypes.object.isRequired,
code: PropTypes.string.isRequired,
buildName: PropTypes.string
};
/**
@@ -31,6 +33,7 @@ export default class CostSection extends TranslatedComponent {
this._buildRetrofitShip = this._buildRetrofitShip.bind(this);
this._onBaseRetrofitChange = this._onBaseRetrofitChange.bind(this);
this._defaultRetrofitName = this._defaultRetrofitName.bind(this);
this._eddbShoppingList = this._eddbShoppingList.bind(this);
let data = Ships[props.ship.id]; // Retrieve the basic ship properties, slots and defaults
let retrofitName = this._defaultRetrofitName(props.ship.id, props.buildName);
@@ -325,12 +328,27 @@ export default class CostSection extends TranslatedComponent {
</div>;
}
/**
* Open up a window for EDDB with a shopping list of our retrofit components
*/
_eddbShoppingList() {
const { retrofitCosts } = this.state;
const { ship } = this.props;
// Provide unique list of non-PP module EDDB IDs to buy
const modIds = retrofitCosts.filter(item => item.retroItem.incCost && item.buyId && !item.buyPp).map(item => item.buyId).filter((v, i, a) => a.indexOf(v) === i);
// Open up the relevant URL
window.open('https://eddb.io/station?m=' + modIds.join(','));
}
/**
* Render the retofit tab
* @return {React.Component} Tab contents
*/
_retrofitTab() {
let { retrofitTotal, retrofitCosts, moduleDiscount, retrofitName } = this.state;
const { termtip, tooltip } = this.context;
let { translate, formats, units } = this.context.language;
let int = formats.int;
let rows = [], options = [<option key='stock' value=''>{translate('Stock')}</option>];
@@ -370,7 +388,8 @@ export default class CostSection extends TranslatedComponent {
<tbody>
{rows}
<tr className='ri'>
<td colSpan='4' className='lbl' >{translate('cost')}</td>
<td className='lbl' ><button onClick={this._eddbShoppingList} onMouseOver={termtip.bind(null, 'PHRASE_REFIT_SHOPPING_LIST')} onMouseOut={tooltip.bind(null, null)}><ShoppingIcon className='lg' style={{ fill: 'black' }}/></button></td>
<td colSpan='3' className='lbl' >{translate('cost')}</td>
<td colSpan='2' className={cn('val', retrofitTotal > 0 ? 'warning' : 'secondary-disabled')} style={{ borderBottom:'none' }}>
{int(retrofitTotal)}{units.CR}
</td>
@@ -403,6 +422,8 @@ export default class CostSection extends TranslatedComponent {
if (ship.bulkheads.m.index != retrofitShip.bulkheads.m.index) {
item = {
buyClassRating: ship.bulkheads.m.class + ship.bulkheads.m.rating,
buyId: ship.bulkheads.m.eddbID,
buyPp: ship.bulkheads.m.pp,
buyName: ship.bulkheads.m.name,
sellClassRating: retrofitShip.bulkheads.m.class + retrofitShip.bulkheads.m.rating,
sellName: retrofitShip.bulkheads.m.name,
@@ -424,6 +445,8 @@ export default class CostSection extends TranslatedComponent {
if (modId != retroModId) {
item = { netCost: 0, retroItem: retroSlotGroup[i] };
if (slotGroup[i].m) {
item.buyId = slotGroup[i].m.eddbID,
item.buyPp = slotGroup[i].m.pp,
item.buyName = slotGroup[i].m.name || slotGroup[i].m.grp;
item.buyClassRating = slotGroup[i].m.class + slotGroup[i].m.rating;
item.netCost = slotGroup[i].discountedCost;
@@ -507,7 +530,7 @@ export default class CostSection extends TranslatedComponent {
scoop = true;
break;
case 'scb':
q = slotGroup[i].m.getCells();
q = slotGroup[i].m.getAmmo() + 1;
break;
case 'am':
q = slotGroup[i].m.getAmmo();

View File

@@ -1,316 +0,0 @@
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) {
// Tidy up the range so that it's to 4 decimal places
range = Math.round(10000 * range) / 10000;
// Track totals
let totals = {};
totals.effectiveness = 0;
totals.effectiveDps = 0;
totals.effectiveSDps = 0;
let totalDps = 0;
let weapons = [];
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].m) {
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 : ''}`;
const effectiveness = (m.getPiercing() >= against.properties.hardness ? 1 : m.getPiercing() / against.properties.hardness) * dropoff;
const effectiveDps = m.getDps() * effectiveness * dropoff;
const effectiveSDps = (m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) * effectiveness : effectiveDps) * dropoff;
totals.effectiveDps += effectiveDps;
totals.effectiveSDps += effectiveSDps;
totalDps += m.getDps();
weapons.push({ id: i,
mount: m.mount,
name: m.name || m.grp,
classRating,
effectiveDps,
effectiveSDps,
effectiveness });
}
}
}
totals.effectiveness = totals.effectiveDps / 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.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 'edps': comp = comp((a, b) => a.effectiveDps - b.effectiveDps, desc); break;
case 'esdps': comp = comp((a, b) => a.effectiveSDps - b.effectiveSDps, desc); break;
case 'e': comp = comp((a, b) => a.effectiveness - b.effectiveness, desc); break;
}
this.state.weapons.sort(comp);
}
/**
* Render individual rows for hardpoints
* @param {Function} translate Translate function
* @param {Object} formats Localised formats map
* @return {array} The individual rows
*
*/
_renderRows(translate, formats) {
const { termtip, tooltip } = this.context;
let rows = [];
if (this.state.weapons) {
for (let i = 0; i < this.state.weapons.length; i++) {
const weapon = this.state.weapons[i];
rows.push(<tr key={weapon.id}>
<td className='ri'>
{weapon.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed className='icon'/></span> : null}
{weapon.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : null}
{weapon.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : null}
{weapon.classRating} {translate(weapon.name)}
</td>
<td className='ri'>{formats.round1(weapon.effectiveDps)}</td>
<td className='ri'>{formats.round1(weapon.effectiveSDps)}</td>
<td className='ri'>{formats.pct(weapon.effectiveness)}</td>
</tr>);
}
}
return rows;
}
/**
* 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'>
<td className='sortable' onClick={sortOrder.bind(this, 'n')}>{translate('weapon')}</td>
<td className='sortable' onClick={sortOrder.bind(this, 'edps')}>{translate('effective dps')}</td>
<td className='sortable' onClick={sortOrder.bind(this, 'esdps')}>{translate('effective sdps')}</td>
<td className='sortable' onClick={sortOrder.bind(this, 'e')}>{translate('effectiveness')}</td>
</tr>
</thead>
<tbody>
{this._renderRows(translate, formats)}
</tbody>
<tfoot>
<tr className='main'>
<td className='ri'><i>{translate('total')}</i></td>
<td className='ri'><i>{formats.round1(totals.effectiveDps)}</i></td>
<td className='ri'><i>{formats.round1(totals.effectiveSDps)}</i></td>
<td className='ri'><i>{formats.pct(totals.effectiveness)}</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

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

View File

@@ -0,0 +1,262 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import * as Calc from '../shipyard/Calculations';
import PieChart from './PieChart';
import VerticalBarChart from './VerticalBarChart';
/**
* Defence information
* Shield information consists of four panels:
* - textual information (time to lose shields etc.)
* - breakdown of shield sources (pie chart)
* - comparison of shield resistances (bar chart)
* - effective shield (bar chart)
*/
export default class Defence extends TranslatedComponent {
static propTypes = {
marker: PropTypes.string.isRequired,
ship: PropTypes.object.isRequired,
opponent: PropTypes.object.isRequired,
engagementrange: PropTypes.number.isRequired,
sys: PropTypes.number.isRequired,
opponentWep: PropTypes.number.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
const { shield, armour, shielddamage, armourdamage } = Calc.defenceMetrics(props.ship, props.opponent, props.sys, props.opponentWep, props.engagementrange);
this.state = { shield, armour, shielddamage, armourdamage };
}
/**
* Update the state if our properties change
* @param {Object} nextProps Incoming/Next properties
* @return {boolean} Returns true if the component should be rerendered
*/
componentWillReceiveProps(nextProps) {
if (this.props.marker != nextProps.marker || this.props.sys != nextProps.sys) {
const { shield, armour, shielddamage, armourdamage } = Calc.defenceMetrics(nextProps.ship, nextProps.opponent, nextProps.sys, nextProps.opponentWep, nextProps.engagementrange);
this.setState({ shield, armour, shielddamage, armourdamage });
}
return true;
}
/**
* Render defence
* @return {React.Component} contents
*/
render() {
const { ship, sys, opponentWep } = this.props;
const { language, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { shield, armour, shielddamage, armourdamage } = this.state;
const pd = ship.standard[4].m;
const shieldSourcesData = [];
const effectiveShieldData = [];
const shieldDamageTakenData = [];
const shieldSourcesTt = [];
const shieldDamageTakenAbsoluteTt = [];
const shieldDamageTakenExplosiveTt = [];
const shieldDamageTakenKineticTt = [];
const shieldDamageTakenThermalTt = [];
const effectiveShieldAbsoluteTt = [];
const effectiveShieldExplosiveTt = [];
const effectiveShieldKineticTt = [];
const effectiveShieldThermalTt = [];
let maxEffectiveShield = 0;
if (shield.total) {
shieldSourcesData.push({ value: Math.round(shield.generator), label: translate('generator') });
shieldSourcesData.push({ value: Math.round(shield.boosters), label: translate('boosters') });
shieldSourcesData.push({ value: Math.round(shield.cells), label: translate('cells') });
if (shield.generator > 0) {
shieldSourcesTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
effectiveShieldAbsoluteTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
effectiveShieldExplosiveTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
effectiveShieldKineticTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
effectiveShieldThermalTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
if (shield.boosters > 0) {
shieldSourcesTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
effectiveShieldAbsoluteTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
effectiveShieldExplosiveTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
effectiveShieldKineticTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
effectiveShieldThermalTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
}
if (shield.cells > 0) {
shieldSourcesTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
effectiveShieldAbsoluteTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
effectiveShieldExplosiveTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
effectiveShieldKineticTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
effectiveShieldThermalTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
}
// Add effective shield from resistances
const rawMj = shield.generator + shield.boosters + shield.cells;
const explosiveMj = rawMj / (shield.explosive.generator * shield.explosive.boosters) - rawMj;
if (explosiveMj != 0) effectiveShieldExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(explosiveMj)}{units.MJ}</div>);
const kineticMj = rawMj / (shield.kinetic.generator * shield.kinetic.boosters) - rawMj;
if (kineticMj != 0) effectiveShieldKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(kineticMj)}{units.MJ}</div>);
const thermalMj = rawMj / (shield.thermal.generator * shield.thermal.boosters) - rawMj;
if (thermalMj != 0) effectiveShieldThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(thermalMj)}{units.MJ}</div>);
// Add effective shield from power distributor SYS pips
if (shield.absolute.sys != 1) {
effectiveShieldAbsoluteTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.absolute.sys - rawMj)}{units.MJ}</div>);
effectiveShieldExplosiveTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.explosive.sys - rawMj)}{units.MJ}</div>);
effectiveShieldKineticTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.kinetic.sys - rawMj)}{units.MJ}</div>);
effectiveShieldThermalTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.thermal.sys - rawMj)}{units.MJ}</div>);
}
}
shieldDamageTakenAbsoluteTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.absolute.generator)}</div>);
shieldDamageTakenAbsoluteTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.absolute.boosters)}</div>);
shieldDamageTakenAbsoluteTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.absolute.sys)}</div>);
shieldDamageTakenExplosiveTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.explosive.generator)}</div>);
shieldDamageTakenExplosiveTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.explosive.boosters)}</div>);
shieldDamageTakenExplosiveTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.explosive.sys)}</div>);
shieldDamageTakenKineticTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.kinetic.generator)}</div>);
shieldDamageTakenKineticTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.kinetic.boosters)}</div>);
shieldDamageTakenKineticTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.kinetic.sys)}</div>);
shieldDamageTakenThermalTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.thermal.generator)}</div>);
shieldDamageTakenThermalTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.thermal.boosters)}</div>);
shieldDamageTakenThermalTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.thermal.sys)}</div>);
const effectiveAbsoluteShield = shield.total / shield.absolute.total;
effectiveShieldData.push({ value: Math.round(effectiveAbsoluteShield), label: translate('absolute'), tooltip: effectiveShieldAbsoluteTt });
const effectiveExplosiveShield = shield.total / shield.explosive.total;
effectiveShieldData.push({ value: Math.round(effectiveExplosiveShield), label: translate('explosive'), tooltip: effectiveShieldExplosiveTt });
const effectiveKineticShield = shield.total / shield.kinetic.total;
effectiveShieldData.push({ value: Math.round(effectiveKineticShield), label: translate('kinetic'), tooltip: effectiveShieldKineticTt });
const effectiveThermalShield = shield.total / shield.thermal.total;
effectiveShieldData.push({ value: Math.round(effectiveThermalShield), label: translate('thermal'), tooltip: effectiveShieldThermalTt });
shieldDamageTakenData.push({ value: Math.round(shield.absolute.total * 100), label: translate('absolute'), tooltip: shieldDamageTakenAbsoluteTt });
shieldDamageTakenData.push({ value: Math.round(shield.explosive.total * 100), label: translate('explosive'), tooltip: shieldDamageTakenExplosiveTt });
shieldDamageTakenData.push({ value: Math.round(shield.kinetic.total * 100), label: translate('kinetic'), tooltip: shieldDamageTakenKineticTt });
shieldDamageTakenData.push({ value: Math.round(shield.thermal.total * 100), label: translate('thermal'), tooltip: shieldDamageTakenThermalTt });
maxEffectiveShield = Math.max(shield.total / shield.absolute.max, shield.total / shield.explosive.max, shield.total / shield.kinetic.max, shield.total / shield.thermal.max);
}
const armourSourcesData = [];
armourSourcesData.push({ value: Math.round(armour.bulkheads), label: translate('bulkheads') });
armourSourcesData.push({ value: Math.round(armour.reinforcement), label: translate('reinforcement') });
const armourSourcesTt = [];
const effectiveArmourAbsoluteTt = [];
const effectiveArmourExplosiveTt = [];
const effectiveArmourKineticTt = [];
const effectiveArmourThermalTt = [];
if (armour.bulkheads > 0) {
armourSourcesTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
effectiveArmourAbsoluteTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
effectiveArmourExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
effectiveArmourKineticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
effectiveArmourThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
if (armour.reinforcement > 0) {
armourSourcesTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
effectiveArmourAbsoluteTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
effectiveArmourExplosiveTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
effectiveArmourKineticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
effectiveArmourThermalTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
}
}
const rawArmour = armour.bulkheads + armour.reinforcement;
const armourDamageTakenTt = [];
armourDamageTakenTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.absolute.bulkheads)}</div>);
armourDamageTakenTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.absolute.reinforcement)}</div>);
const armourDamageTakenExplosiveTt = [];
armourDamageTakenExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.explosive.bulkheads)}</div>);
armourDamageTakenExplosiveTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.explosive.reinforcement)}</div>);
if (armour.explosive.bulkheads * armour.explosive.reinforcement != 1) effectiveArmourExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.explosive.bulkheads * armour.explosive.reinforcement) - rawArmour)}</div>);
const armourDamageTakenKineticTt = [];
armourDamageTakenKineticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.kinetic.bulkheads)}</div>);
armourDamageTakenKineticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.kinetic.reinforcement)}</div>);
if (armour.kinetic.bulkheads * armour.kinetic.reinforcement != 1) effectiveArmourKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.kinetic.bulkheads * armour.kinetic.reinforcement) - rawArmour)}</div>);
const armourDamageTakenThermalTt = [];
armourDamageTakenThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.thermal.bulkheads)}</div>);
armourDamageTakenThermalTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.thermal.reinforcement)}</div>);
if (armour.thermal.bulkheads * armour.thermal.reinforcement != 1) effectiveArmourThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.thermal.bulkheads * armour.thermal.reinforcement) - rawArmour)}</div>);
const effectiveArmourData = [];
const effectiveAbsoluteArmour = armour.total / armour.absolute.total;
effectiveArmourData.push({ value: Math.round(effectiveAbsoluteArmour), label: translate('absolute'), tooltip: effectiveArmourAbsoluteTt });
const effectiveExplosiveArmour = armour.total / armour.explosive.total;
effectiveArmourData.push({ value: Math.round(effectiveExplosiveArmour), label: translate('explosive'), tooltip: effectiveArmourExplosiveTt });
const effectiveKineticArmour = armour.total / armour.kinetic.total;
effectiveArmourData.push({ value: Math.round(effectiveKineticArmour), label: translate('kinetic'), tooltip: effectiveArmourKineticTt });
const effectiveThermalArmour = armour.total / armour.thermal.total;
effectiveArmourData.push({ value: Math.round(effectiveThermalArmour), label: translate('thermal'), tooltip: effectiveArmourThermalTt });
const armourDamageTakenData = [];
armourDamageTakenData.push({ value: Math.round(armour.absolute.total * 100), label: translate('absolute'), tooltip: armourDamageTakenTt });
armourDamageTakenData.push({ value: Math.round(armour.explosive.total * 100), label: translate('explosive'), tooltip: armourDamageTakenExplosiveTt });
armourDamageTakenData.push({ value: Math.round(armour.kinetic.total * 100), label: translate('kinetic'), tooltip: armourDamageTakenKineticTt });
armourDamageTakenData.push({ value: Math.round(armour.thermal.total * 100), label: translate('thermal'), tooltip: armourDamageTakenThermalTt });
return (
<span id='defence'>
{shield.total ? <span>
<div className='group quarter'>
<h2>{translate('shield metrics')}</h2>
<br/>
<h2 onMouseOver={termtip.bind(null, <div>{shieldSourcesTt}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw shield strength')}<br/>{formats.int(shield.total)}{units.MJ}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_SHIELDS')}<br/>{shielddamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(shield.total, shielddamage.totalsdps, shielddamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * opponentWep / 4))}</h2>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECOVER'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECOVER_SHIELDS')}<br/>{shield.recover === Math.Inf ? translate('never') : formats.time(shield.recover)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECHARGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECHARGE_SHIELDS')}<br/>{shield.recharge === Math.Inf ? translate('never') : formats.time(shield.recharge)}</h2>
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_SOURCES'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield sources')}</h2>
<PieChart data={shieldSourcesData} />
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_DAMAGE_TAKEN'))} onMouseOut={tooltip.bind(null, null)}>{translate('damage taken')}(%)</h2>
<VerticalBarChart data={shieldDamageTakenData} yMax={140} />
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_EFFECTIVE_SHIELD'))} onMouseOut={tooltip.bind(null, null)}>{translate('effective shield')}(MJ)</h2>
<VerticalBarChart data={effectiveShieldData} yMax={maxEffectiveShield}/>
</div>
</span> : null }
<div className='group quarter'>
<h2>{translate('armour metrics')}</h2>
<h2 onMouseOver={termtip.bind(null, <div>{armourSourcesTt}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw armour strength')}<br/>{formats.int(armour.total)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_ARMOUR')}<br/>{armourdamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(armour.total, armourdamage.totalsdps, armourdamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * opponentWep / 4))}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('raw module armour')}<br/>{formats.int(armour.modulearmour)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_PROTECTION_EXTERNAL'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_MODULE_PROTECTION_EXTERNAL')}<br/>{formats.pct1(armour.moduleprotection / 2)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_PROTECTION_INTERNAL'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_MODULE_PROTECTION_INTERNAL')}<br/>{formats.pct1(armour.moduleprotection)}</h2>
<br/>
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_ARMOUR_SOURCES'))} onMouseOut={tooltip.bind(null, null)}>{translate('armour sources')}</h2>
<PieChart data={armourSourcesData} />
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_DAMAGE_TAKEN'))} onMouseOut={tooltip.bind(null, null)}>{translate('damage taken')}(%)</h2>
<VerticalBarChart data={armourDamageTakenData} yMax={100} />
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_EFFECTIVE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('effective armour')}</h2>
<VerticalBarChart data={effectiveArmourData} />
</div>
</span>);
}
}

View File

@@ -1,106 +0,0 @@
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

@@ -0,0 +1,102 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import Slider from '../components/Slider';
/**
* Engagement range slider
* Requires an onChange() function of the form onChange(range), providing the range in metres, which is triggered on range change
*/
export default class EngagementRange extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired,
engagementRange: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
const { ship } = props;
const maxRange = this._calcMaxRange(ship);
this.state = {
maxRange
};
}
/**
* Calculate the maximum range of a ship's weapons
* @param {Object} ship The ship
* @returns {int} The maximum range, in metres
*/
_calcMaxRange(ship) {
let maxRange = 1000;
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
const thisRange = ship.hardpoints[i].m.getRange();
if (thisRange > maxRange) {
maxRange = thisRange;
}
}
}
return maxRange;
}
/**
* Update range
* @param {number} rangeLevel percentage level from 0 to 1
*/
_rangeChange(rangeLevel) {
const { maxRange } = this.state;
// We round the range to an integer value
const range = Math.round(rangeLevel * maxRange);
if (range !== this.props.engagementRange) {
this.props.onChange(range);
}
}
/**
* Render range slider
* @return {React.Component} contents
*/
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { engagementRange } = this.props;
const { maxRange } = this.state;
return (
<span>
<h3>{translate('engagement range')}: {formats.int(engagementRange)}{translate('m')}</h3>
<table style={{ width: '100%', lineHeight: '1em', backgroundColor: 'transparent' }}>
<tbody >
<tr>
<td>
<Slider
axis={true}
onChange={this._rangeChange.bind(this)}
axisUnit={translate('m')}
percent={engagementRange / maxRange}
max={maxRange}
scale={sizeRatio}
onResize={onWindowResize}
/>
</td>
</tr>
</tbody>
</table>
</span>
);
}
}

View File

@@ -0,0 +1,106 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import { nameComparator } from '../utils/SlotFunctions';
import LineChart from '../components/LineChart';
import Slider from '../components/Slider';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import Module from '../shipyard/Module';
import * as Calc from '../shipyard/Calculations';
/**
* Engine profile for a given ship
*/
export default class EngineProfile extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired,
cargo: PropTypes.number.isRequired,
fuel: PropTypes.number.isRequired,
eng: PropTypes.number.isRequired,
boost: PropTypes.bool.isRequired,
marker: PropTypes.string.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
const ship = this.props.ship;
this.state = {
calcMaxSpeedFunc: this.calcMaxSpeed.bind(this, ship, this.props.eng, this.props.boost)
};
}
/**
* Update the 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.marker != this.props.marker) {
this.setState({ calcMaxSpeedFunc: this.calcMaxSpeed.bind(this, nextProps.ship, nextProps.eng, nextProps.boost) });
}
return true;
}
/**
* Calculate the top speed for this ship given thrusters, mass and pips to ENG
* @param {Object} ship The ship
* @param {Object} eng The number of pips to ENG
* @param {Object} boost If boost is enabled
* @param {Object} mass The mass at which to calculate the top speed
* @return {number} The maximum speed
*/
calcMaxSpeed(ship, eng, boost, mass) {
// Obtain the top speed
return Calc.calcSpeed(mass, ship.speed, ship.standard[1].m, ship.pipSpeed, eng, ship.boost / ship.speed, boost);
}
/**
* Render engine profile
* @return {React.Component} contents
*/
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { ship, cargo, eng, fuel, boost } = this.props;
// Calculate bounds for our line chart
const thrusters = ship.standard[1].m;
const minMass = ship.calcLowestPossibleMass({ th: thrusters });
const maxMass = thrusters.getMaxMass();
const mass = ship.unladenMass + fuel + cargo;
const minSpeed = Calc.calcSpeed(maxMass, ship.speed, thrusters, ship.pipSpeed, 0, ship.boost / ship.speed, false);
const maxSpeed = Calc.calcSpeed(minMass, ship.speed, thrusters, ship.pipSpeed, 4, ship.boost / ship.speed, true);
// Add a mark at our current mass
const mark = Math.min(mass, maxMass);
const code = `${ship.toString()}:${cargo}:${fuel}:${eng}:${boost}`;
// This graph can have a precipitous fall-off so we use lots of points to make it look a little smoother
return (
<LineChart
xMin={minMass}
xMax={maxMass}
yMin={minSpeed}
yMax={maxSpeed}
xMark={mark}
xLabel={translate('mass')}
xUnit={translate('T')}
yLabel={translate('maximum speed')}
yUnit={translate('m/s')}
func={this.state.calcMaxSpeedFunc}
points={1000}
code={code}
aspect={0.7}
/>
);
}
}

View File

@@ -0,0 +1,104 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import { nameComparator } from '../utils/SlotFunctions';
import LineChart from '../components/LineChart';
import Slider from '../components/Slider';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import Module from '../shipyard/Module';
import * as Calc from '../shipyard/Calculations';
/**
* FSD profile for a given ship
*/
export default class FSDProfile extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired,
cargo: PropTypes.number.isRequired,
fuel: PropTypes.number.isRequired,
marker: PropTypes.string.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
const ship = this.props.ship;
this.state = {
calcMaxRangeFunc: this._calcMaxRange.bind(this, ship, this.props.fuel)
};
}
/**
* Update the 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.marker != this.props.marker) {
this.setState({ calcMaxRangeFunc: this._calcMaxRange.bind(this, nextProps.ship, nextProps.fuel) });
}
return true;
}
/**
* Calculate the maximum range for this ship across its applicable mass
* @param {Object} ship The ship
* @param {Object} fuel The fuel on the ship
* @param {Object} mass The mass at which to calculate the maximum range
* @return {number} The maximum range
*/
_calcMaxRange(ship, fuel, mass) {
// Obtain the maximum range
return Calc.jumpRange(mass, ship.standard[2].m, Math.min(fuel, ship.standard[2].m.getMaxFuelPerJump()));
}
/**
* Render FSD profile
* @return {React.Component} contents
*/
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { ship, cargo, fuel } = this.props;
// Calculate bounds for our line chart - use thruster info for X
const thrusters = ship.standard[1].m;
const fsd = ship.standard[2].m;
const minMass = ship.calcLowestPossibleMass({ th: thrusters });
const maxMass = thrusters.getMaxMass();
const mass = ship.unladenMass + fuel + cargo;
const minRange = 0;
const maxRange = Calc.jumpRange(minMass + fsd.getMaxFuelPerJump(), fsd, fsd.getMaxFuelPerJump());
// Add a mark at our current mass
const mark = Math.min(mass, maxMass);
const code = ship.name + ship.toString() + '.' + fuel;
return (
<LineChart
xMin={minMass}
xMax={maxMass}
yMin={minRange}
yMax={maxRange}
xMark={mark}
xLabel={translate('mass')}
xUnit={translate('T')}
yLabel={translate('maximum range')}
yUnit={translate('LY')}
func={this.state.calcMaxRangeFunc}
points={200}
code={code}
aspect={0.7}
/>
);
}
}

View File

@@ -0,0 +1,75 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import Slider from '../components/Slider';
/**
* Fuel slider
* Requires an onChange() function of the form onChange(fuel), providing the fuel in tonnes, which is triggered on fuel level change
*/
export default class Fuel extends TranslatedComponent {
static propTypes = {
fuel: PropTypes.number.isRequired,
fuelCapacity: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
this._fuelChange = this._fuelChange.bind(this);
}
/**
* Update fuel level
* @param {number} fuelLevel percentage level from 0 to 1
*/
_fuelChange(fuelLevel) {
const { fuel, fuelCapacity } = this.props;
const newFuel = fuelLevel * fuelCapacity;
// Only send an update if the fuel has changed significantly
if (Math.round(fuel * 10) != Math.round(newFuel * 10)) {
this.props.onChange(Math.round(newFuel * 10) / 10);
}
}
/**
* Render fuel slider
* @return {React.Component} contents
*/
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { fuel, fuelCapacity } = this.props;
return (
<span>
<h3>{translate('fuel carried')}: {formats.f1(fuel)}{units.T}</h3>
<table style={{ width: '100%', lineHeight: '1em', backgroundColor: 'transparent' }}>
<tbody >
<tr>
<td>
<Slider
axis={true}
onChange={this._fuelChange}
axisUnit={translate('T')}
percent={fuel / fuelCapacity}
max={fuelCapacity}
scale={sizeRatio}
onResize={onWindowResize}
/>
</td>
</tr>
</tbody>
</table>
</span>
);
}
}

View File

@@ -1,9 +1,11 @@
import React from 'react';
import cn from 'classnames';
import Slot from './Slot';
import Persist from '../stores/Persist';
import { DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret, ListModifications, Modified } from './SvgIcons';
import { DamageAbsolute, DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret, ListModifications, Modified } from './SvgIcons';
import { Modifications } from 'coriolis-data/dist';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
import { blueprintTooltip } from '../utils/BlueprintFunctions';
/**
@@ -31,12 +33,13 @@ export default class HardpointSlot extends Slot {
/**
* Generate the slot contents
* @param {Object} m Mounted Module
* @param {Boolean} enabled Slot enabled
* @param {Function} translate Translate function
* @param {Object} formats Localized Formats map
* @param {Object} u Localized Units Map
* @return {React.Component} Slot contents
*/
_getSlotDetails(m, translate, formats, u) {
_getSlotDetails(m, enabled, translate, formats, u) {
if (m) {
let classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
let { drag, drop } = this.props;
@@ -48,12 +51,19 @@ export default class HardpointSlot extends Slot {
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) {
if (m.blueprint.special && m.blueprint.special.id >= 0) {
modTT += ', ' + translate(m.blueprint.special.name);
}
modTT = (
<div>
<div>{modTT}</div>
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
</div>
);
}
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
const className = cn('details', enabled ? '' : 'disabled');
return <div className={className} draggable='true' onDragStart={drag} onDragEnd={drop}>
<div className={'cb'}>
<div className={'l'}>
{m.mount && m.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed /></span> : ''}
@@ -62,6 +72,7 @@ export default class HardpointSlot extends Slot {
{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>
@@ -73,18 +84,20 @@ export default class HardpointSlot extends Slot {
{ 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.f1(m.getFalloff() / 1000)}{u.km}</div> : null }
{ m.getRange() ? <div className={'l'}>{translate('range', m.grp)} {formats.f1(m.getRange() / 1000)}{u.km}</div> : null }
{ m.getScanTime() ? <div className={'l'}>{translate('scantime')} {formats.f1(m.getScanTime())}{u.s}</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.getReload() ? <div className={'l'}>{translate('reload')}: {formats.round(m.getReload())}{u.s}</div> : null }
{ m.getShotSpeed() ? <div className={'l'}>{translate('shotspeed')}: {formats.int(m.getShotSpeed())}{u.mps}</div> : null }
{ m.getPiercing() ? <div className={'l'}>{translate('piercing')}: {formats.int(m.getPiercing())}</div> : null }
{ 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.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</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

@@ -8,7 +8,7 @@ import { stopCtxPropagation } from '../utils/UtilityFunctions';
/**
* Hardpoint slot section
*/
export default class HardpointsSlotSection extends SlotSection {
export default class HardpointSlotSection extends SlotSection {
/**
* Constructor
@@ -77,6 +77,7 @@ export default class HardpointsSlotSection extends SlotSection {
dropClass={this._dropClass(h, originSlot, targetSlot)}
ship={ship}
m={h.m}
enabled={h.enabled ? true : false}
/>);
}
}

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';
@@ -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 = {
@@ -248,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
@@ -343,7 +356,7 @@ export default class Header extends TranslatedComponent {
let comps = Object.keys(Persist.getComparisons()).sort();
for (let name of comps) {
comparisons.push(<ActiveLink key={name} href={'/compare/' + name} className='block name'>{name}</ActiveLink>);
comparisons.push(<ActiveLink key={name} href={'/compare/' + encodeURIComponent(name)} className='block name'>{name}</ActiveLink>);
}
} else {
comparisons = <span className='cap'>{translate('none created')}</span>;
@@ -521,6 +534,12 @@ 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,9 +1,11 @@
import React from 'react';
import cn from 'classnames';
import Slot from './Slot';
import Persist from '../stores/Persist';
import { ListModifications, Modified } from './SvgIcons';
import { Modifications } from 'coriolis-data/dist';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
import { blueprintTooltip } from '../utils/BlueprintFunctions';
/**
* Internal Slot
@@ -13,12 +15,13 @@ export default class InternalSlot extends Slot {
/**
* Generate the slot contents
* @param {Object} m Mounted Module
* @param {Boolean} enabled Slot enabled
* @param {Function} translate Translate function
* @param {Object} formats Localized Formats map
* @param {Object} u Localized Units Map
* @return {React.Component} Slot contents
*/
_getSlotDetails(m, translate, formats, u) {
_getSlotDetails(m, enabled, translate, formats, u) {
if (m) {
let classRating = m.class + m.rating;
let { drag, drop, ship } = this.props;
@@ -30,45 +33,55 @@ export default class InternalSlot extends Slot {
let modTT = translate('modified');
if (m && m.blueprint && m.blueprint.name) {
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
modTT = (
<div>
<div>{modTT}</div>
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
</div>
);
}
let mass = m.getMass() || m.cargo || m.fuel || 0;
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
const className = cn('details', enabled ? '' : 'disabled');
return <div className={className} draggable='true' onDragStart={drag} onDragEnd={drop}>
<div className={'cb'}>
<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.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.getOptMass() ? <div className={'l'}>{translate('optmass', 'sg')}: {formats.int(m.getOptMass())}{u.T}</div> : null }
{ m.getMaxMass() ? <div className={'l'}>{translate('maxmass', 'sg')}: {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.getAmmo() ? <div className={'l'}>{translate('ammunition')}: {formats.gen(m.getAmmo())}</div> : null }
{ m.cells ? <div className={'l'}>{translate('cells')}: {m.cells}</div> : null }
{ m.shieldreinforcement ? <div className={'l'}>{translate('shieldreinforcement')}: {formats.int(m.getShieldReinforcement())} <u>MJ</u>&nbsp;&nbsp;&nbsp;{translate('total')}: {formats.int(m.cells * m.getShieldReinforcement())}{u.MJ}</div> : null }
{ m.getAmmo() && m.grp !== 'scb' ? <div className={'l'}>{translate('ammunition')}: {formats.gen(m.getAmmo())}</div> : null }
{ m.getSpinup() ? <div className={'l'}>{translate('spinup')}: {formats.f1(m.getSpinup())}{u.s}</div> : null }
{ m.getDuration() ? <div className={'l'}>{translate('duration')}: {formats.f1(m.getDuration())}{u.s}</div> : null }
{ m.grp === 'scb' ? <div className={'l'}>{translate('cells')}: {formats.int(m.getAmmo() + 1)}</div> : null }
{ m.getShieldReinforcement() ? <div className={'l'}>{translate('shieldreinforcement')}: {formats.f1(m.getDuration() * m.getShieldReinforcement())}{u.MJ}</div> : null }
{ m.getShieldReinforcement() ? <div className={'l'}>{translate('total')}: {formats.int((m.getAmmo() + 1) * (m.getDuration() * m.getShieldReinforcement()))}{u.MJ}</div> : null }
{ m.repair ? <div className={'l'}>{translate('repair')}: {m.repair}</div> : null }
{ m.getFacingLimit() ? <div className={'l'}>{translate('facinglimit')} {formats.f1(m.getFacingLimit())}°</div> : null }
{ m.getRange() ? <div className={'l'}>{translate('range')} {formats.f2(m.getRange())}{u.km}</div> : null }
{ m.getRangeT() ? <div className={'l'}>{translate('ranget')} {formats.f1(m.getRangeT())}{u.s}</div> : null }
{ m.spinup ? <div className={'l'}>{translate('spinup')}: {formats.f1(m.spinup)}{u.s}</div> : null }
{ m.time ? <div className={'l'}>{translate('time')}: {formats.time(m.time)}</div> : null }
{ m.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'}>{u.Ls}</div> : null }
{ m.rangeRating ? <div className={'l'}>{translate('range')}: {m.rangeRating}</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.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
{ m.passengers ? <div className={'l'}>{translate('passengers')}: {m.passengers}</div> : null }
{ m.getRegenerationRate() ? <div className='l'>{translate('regen')}: {formats.round1(m.getRegenerationRate())}{u.ps}</div> : null }
{ m.getBrokenRegenerationRate() ? <div className='l'>{translate('brokenregen')}: {formats.round1(m.getBrokenRegenerationRate())}{u.ps}</div> : null }
{ 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.getHullReinforcement() ? <div className='l'>{translate('armour')}: {formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost') / 10000)}</div> : null }
{ m.getProtection() ? <div className='l'>{translate('protection')}: {formats.rPct(m.getProtection())}</div> : null }
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</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

@@ -216,7 +216,6 @@ export default class InternalSlotSection extends SlotSection {
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)}
@@ -225,6 +224,7 @@ export default class InternalSlotSection extends SlotSection {
dropClass={this._dropClass(s, originSlot, targetSlot)}
fuel={fuelCapacity}
ship={ship}
enabled={s.enabled ? true : false}
/>);
}

View File

@@ -0,0 +1,124 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import { nameComparator } from '../utils/SlotFunctions';
import LineChart from '../components/LineChart';
import Slider from '../components/Slider';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import Module from '../shipyard/Module';
import * as Calc from '../shipyard/Calculations';
/**
* Jump range for a given ship
*/
export default class JumpRange extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired,
code: PropTypes.string.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
const ship = this.props.ship;
this.state = {
fuelLevel: 1,
calcJumpRangeFunc: this._calcJumpRange.bind(this, ship)
};
}
/**
* Update the 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) {
this.setState({ fuelLevel: 1,
calcJumpRangeFunc: this._calcJumpRange.bind(this, nextProps.ship) });
}
return true;
}
/**
* Calculate the jump range this ship at a given cargo
* @param {Object} ship The ship
* @param {Object} cargo The cargo
* @return {number} The jump range
*/
_calcJumpRange(ship, cargo) {
// Obtain the FSD for this ship
const fsd = ship.standard[2].m;
const fuel = this.state.fuelLevel * ship.fuelCapacity;
// Obtain the jump range
return Calc.jumpRange(ship.unladenMass + fuel + cargo, fsd, fuel);
}
/**
* Update fuel level
* @param {number} fuelLevel Fuel level 0 - 1
*/
_fuelChange(fuelLevel) {
this.setState({
fuelLevel,
});
}
/**
* Render engine profile
* @return {React.Component} contents
*/
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { ship } = this.props;
const { fuelLevel } = this.state;
const code = ship.toString() + '.' + ship.getModificationsString() + '.' + fuelLevel;
return (
<span>
<h1>{translate('jump range')}</h1>
<LineChart
xMax={ship.cargoCapacity}
yMax={ship.unladenRange}
xLabel={translate('cargo')}
xUnit={translate('T')}
yLabel={translate('jump range')}
yUnit={translate('LY')}
func={this.state.calcJumpRangeFunc}
points={200}
code={code}
/>
<h3>{translate('fuel carried')}: {formats.f2(fuelLevel * ship.fuelCapacity)}{units.T}</h3>
<table style={{ width: '100%', lineHeight: '1em', backgroundColor: 'transparent' }}>
<tbody >
<tr>
<td>
<Slider
axis={true}
onChange={this._fuelChange.bind(this)}
axisUnit={translate('T')}
percent={fuelLevel}
max={ship.fuelCapacity}
scale={sizeRatio}
onResize={onWindowResize}
/>
</td>
</tr>
</tbody>
</table>
</span>
);
}
}

View File

@@ -1,8 +1,9 @@
import React from 'react';
import d3 from 'd3';
import PropTypes from 'prop-types';
import Measure from 'react-measure';
import * as d3 from 'd3';
import TranslatedComponent from './TranslatedComponent';
const RENDER_POINTS = 20; // Only render 20 points on the graph
const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 };
/**
@@ -11,24 +12,30 @@ const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 };
export default class LineChart extends TranslatedComponent {
static defaultProps = {
code: '',
xMin: 0,
yMin: 0,
colors: ['#ff8c0d']
points: 20,
colors: ['#ff8c0d'],
aspect: 0.5
};
static PropTypes = {
width: React.PropTypes.number.isRequired,
func: React.PropTypes.func.isRequired,
xLabel: React.PropTypes.string.isRequired,
xMin: React.PropTypes.number,
xMax: React.PropTypes.number.isRequired,
xUnit: React.PropTypes.string.isRequired,
yLabel: React.PropTypes.string.isRequired,
yMin: React.PropTypes.number,
yMax: React.PropTypes.number.isRequired,
yUnit: React.PropTypes.string.isRequired,
series: React.PropTypes.array,
colors: React.PropTypes.array,
static propTypes = {
func: PropTypes.func.isRequired,
xLabel: PropTypes.string.isRequired,
xMin: PropTypes.number,
xMax: PropTypes.number.isRequired,
xUnit: PropTypes.string.isRequired,
xMark: PropTypes.number,
yLabel: PropTypes.string.isRequired,
yMin: PropTypes.number,
yMax: PropTypes.number.isRequired,
yUnit: PropTypes.string,
series: PropTypes.array,
colors: PropTypes.array,
points: PropTypes.number,
aspect: PropTypes.number,
code: PropTypes.string,
};
/**
@@ -40,38 +47,30 @@ export default class LineChart extends TranslatedComponent {
super(props);
this._updateDimensions = this._updateDimensions.bind(this);
this._updateSeriesData = this._updateSeriesData.bind(this);
this._updateSeries = this._updateSeries.bind(this);
this._tooltip = this._tooltip.bind(this);
this._showTip = this._showTip.bind(this);
this._hideTip = this._hideTip.bind(this);
this._moveTip = this._moveTip.bind(this);
let markerElems = [];
let detailElems = [<text key='lbl' className='text-tip x' y='1.25em'/>];
let xScale = d3.scale.linear();
let xAxisScale = d3.scale.linear();
let yScale = d3.scale.linear();
let series = props.series;
let seriesLines = [];
const series = props.series;
this.xAxis = d3.svg.axis().scale(xAxisScale).outerTickSize(0).orient('bottom');
this.yAxis = d3.svg.axis().scale(yScale).ticks(6).outerTickSize(0).orient('left');
let xScale = d3.scaleLinear();
let yScale = d3.scaleLinear();
let xAxisScale = d3.scaleLinear();
for(let i = 0, l = series ? series.length : 1; i < l; i++) {
let yAccessor = series ? function(d) { return yScale(d[1][this]); }.bind(series[i]) : (d) => yScale(d[1]);
seriesLines.push(d3.svg.line().x((d) => xScale(d[0])).y(yAccessor));
detailElems.push(<text key={i} className='text-tip y' y={1.25 * (i + 2) + 'em'}/>);
markerElems.push(<circle key={i} className='marker' r='4' />);
}
this.xAxis = d3.axisBottom(xAxisScale).tickSizeOuter(0);
this.yAxis = d3.axisLeft(yScale).ticks(6).tickSizeOuter(0);
this.state = {
xScale,
xAxisScale,
yScale,
seriesLines,
detailElems,
markerElems,
tipHeight: 2 + (1.2 * (series ? series.length : 0.8))
tipHeight: 2 + (1.2 * (series ? series.length : 0.8)),
dimensions: {
width: 100,
height: 100
}
};
}
@@ -81,13 +80,14 @@ export default class LineChart extends TranslatedComponent {
*/
_tooltip(xPos) {
let { xLabel, yLabel, xUnit, yUnit, func, series } = this.props;
let { xScale, yScale, innerWidth } = this.state;
let { xScale, yScale } = this.state;
let { width } = this.state.dimensions;
let { formats, translate } = this.context.language;
let x0 = xScale.invert(xPos),
y0 = func(x0),
tips = this.tipContainer,
yTotal = 0,
flip = (xPos / innerWidth > 0.60),
flip = (xPos / width > 0.50),
tipWidth = 0,
tipHeightPx = tips.selectAll('rect').node().getBoundingClientRect().height;
@@ -98,7 +98,7 @@ export default class LineChart extends TranslatedComponent {
let yVal = series ? y0[series[i]] : y0;
yTotal += yVal;
return (series ? translate(series[i]) : '') + ' ' + formats.f2(yVal);
}).append('tspan').attr('class', 'metric').text(' ' + yUnit);
}).append('tspan').attr('class', 'metric').text(yUnit ? ' ' + yUnit : '');
tips.selectAll('text').each(function() {
if (this.getBBox().width > tipWidth) {
@@ -118,19 +118,21 @@ export default class LineChart extends TranslatedComponent {
/**
* Update dimensions based on properties and scale
* @param {Object} props React Component properties
* @param {Object} props React Component properties
* @param {number} scale size ratio / scale
* @returns {Object} calculated dimensions
*/
_updateDimensions(props, scale) {
let { width, xMax, xMin, yMin, yMax } = props;
let innerWidth = width - MARGIN.left - MARGIN.right;
let outerHeight = Math.round(width * 0.5 * scale);
let innerHeight = outerHeight - MARGIN.top - MARGIN.bottom;
const { xMax, xMin, yMin, yMax } = props;
const { width, height } = this.state.dimensions;
const innerWidth = width - MARGIN.left - MARGIN.right;
const outerHeight = Math.round(width * props.aspect);
const innerHeight = outerHeight - MARGIN.top - MARGIN.bottom;
this.state.xScale.range([0, innerWidth]).domain([xMin, xMax || 1]).clamp(true);
this.state.xAxisScale.range([0, innerWidth]).domain([xMin, xMax]).clamp(true);
this.state.yScale.range([innerHeight, 0]).domain([yMin, yMax]);
this.setState({ innerWidth, outerHeight, innerHeight });
this.state.yScale.range([innerHeight, 0]).domain([yMin, yMax + (yMax - yMin) * 0.1]); // 10% higher than maximum value for tooltip visibility
return { innerWidth, outerHeight, innerHeight };
}
/**
@@ -164,35 +166,47 @@ export default class LineChart extends TranslatedComponent {
}
/**
* Update series data generated from props
* @param {Object} props React Component properties
* Update series generated from props
* @param {Object} props React Component properties
* @param {Object} state React Component state
*/
_updateSeriesData(props) {
let { func, xMin, xMax, series } = props;
let delta = (xMax - xMin) / RENDER_POINTS;
let seriesData = new Array(RENDER_POINTS);
_updateSeries(props, state) {
let { func, xMin, xMax, series, points } = props;
let delta = (xMax - xMin) / points;
let seriesData = new Array(points);
if (delta) {
seriesData = new Array(RENDER_POINTS);
for (let i = 0, x = xMin; i < RENDER_POINTS; i++) {
seriesData = new Array(points);
for (let i = 0, x = xMin; i < points; i++) {
seriesData[i] = [x, func(x)];
x += delta;
}
seriesData[RENDER_POINTS - 1] = [xMax, func(xMax)];
seriesData[points - 1] = [xMax, func(xMax)];
} else {
let yVal = func(xMin);
seriesData = [[0, yVal], [1, yVal]];
}
this.setState({ seriesData });
const markerElems = [];
const detailElems = [<text key='lbl' className='text-tip x' y='1.25em'/>];
const seriesLines = [];
for (let i = 0, l = series ? series.length : 1; i < l; i++) {
const yAccessor = series ? function(d) { return state.yScale(d[1][this]); }.bind(series[i]) : (d) => state.yScale(d[1]);
seriesLines.push(d3.line().x((d, i) => this.state.xScale(d[0])).y(yAccessor));
detailElems.push(<text key={i} className='text-tip y' strokeWidth={0} fill={props.colors[i]} y={1.25 * (i + 2) + 'em'}/>);
markerElems.push(<circle key={i} className='marker' r='4' />);
}
const tipHeight = 2 + (1.2 * (seriesLines ? seriesLines.length : 0.8));
this.setState({ markerElems, detailElems, seriesLines, seriesData, tipHeight });
}
/**
* Update dimensions and series data based on props and context.
*/
componentWillMount() {
this._updateDimensions(this.props, this.context.sizeRatio);
this._updateSeriesData(this.props);
this._updateSeries(this.props, this.state);
}
/**
@@ -201,17 +215,10 @@ export default class LineChart extends TranslatedComponent {
* @param {Object} nextContext Incoming/Next conext
*/
componentWillReceiveProps(nextProps, nextContext) {
let { func, xMin, xMax, yMin, yMax, width } = nextProps;
let props = this.props;
const props = this.props;
let domainChanged = xMax != props.xMax || xMin != props.xMin || yMax != props.yMax || yMin != props.yMin || func != props.func;
if (width != props.width || domainChanged || this.context.sizeRatio != nextContext.sizeRatio) {
this._updateDimensions(nextProps, nextContext.sizeRatio);
}
if (domainChanged) {
this._updateSeriesData(nextProps);
if (props.code != nextProps.code) {
this._updateSeries(nextProps, this.state);
}
}
@@ -220,49 +227,57 @@ export default class LineChart extends TranslatedComponent {
* @return {React.Component} Chart SVG
*/
render() {
if (!this.props.width) {
return null;
}
const { innerWidth, outerHeight, innerHeight } = this._updateDimensions(this.props, this.context.sizeRatio);
const { width, height } = this.state.dimensions;
const { xMin, xMax, xLabel, yLabel, xUnit, yUnit, xMark, colors } = this.props;
const { tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state;
const line = this.line;
const lines = seriesLines.map((line, i) => <path key={i} className='line' fill='none' stroke={colors[i]} strokeWidth='1' d={line(seriesData)} />).reverse();
let { xLabel, yLabel, xUnit, yUnit, colors } = this.props;
let { innerWidth, outerHeight, innerHeight, tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state;
let line = this.line;
let lines = seriesLines.map((line, i) => <path key={i} className='line' stroke={colors[i]} strokeWidth='2' d={line(seriesData)} />);
const markX = xMark ? innerWidth * (xMark - xMin) / (xMax - xMin) : 0;
const xmark = xMark ? <path key={'mark'} className='line' fill='none' strokeDasharray='5,5' stroke={'#ff8c0d'} strokeWidth='1' d={'M ' + markX + ' ' + innerHeight + ' L ' + markX + ' 0'} /> : '';
return <svg style={{ width: '100%', height: outerHeight }}>
<g transform={`translate(${MARGIN.left},${MARGIN.top})`}>
<g>{lines}</g>
<g className='x axis' ref={(elem) => d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}>
<text className='cap' y='30' dy='.1em' x={innerWidth / 2} style={{ textAnchor: 'middle' }}>
<tspan>{xLabel}</tspan>
<tspan className='metric'> ({xUnit})</tspan>
</text>
</g>
<g className='y axis' ref={(elem) => d3.select(elem).call(this.yAxis)}>
<text className='cap' transform='rotate(-90)' y='-50' dy='.1em' x={innerHeight / -2} style={{ textAnchor: 'middle' }}>
<tspan>{yLabel}</tspan>
<tspan className='metric'> ({yUnit})</tspan>
</text>
</g>
<g ref={(g) => this.tipContainer = d3.select(g)} style={{ display: 'none' }}>
<rect className='tooltip' height={tipHeight + 'em'}></rect>
{detailElems}
</g>
<g ref={(g) => this.markersContainer = d3.select(g)} style={{ display: 'none' }}>
{markerElems}
</g>
<rect
fillOpacity='0'
height={innerHeight}
width={innerWidth + 1}
onMouseEnter={this._showTip}
onTouchStart={this._showTip}
onMouseLeave={this._hideTip}
onTouchEnd={this._hideTip}
onMouseMove={this._moveTip}
onTouchMove={this._moveTip}
/>
</g>
</svg>;
return (
<Measure width='100%' whitelist={['width', 'top']} onMeasure={ (dimensions) => { this.setState({ dimensions }); }}>
<div width={width} height={height}>
<svg style={{ width: '100%', height: outerHeight }}>
<g transform={`translate(${MARGIN.left},${MARGIN.top})`}>
<g>{xmark}</g>
<g>{lines}</g>
<g className='x axis' ref={(elem) => d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}>
<text className='cap' y='30' dy='.1em' x={innerWidth / 2} style={{ textAnchor: 'middle' }}>
<tspan>{xLabel}</tspan>
<tspan className='metric'> ({xUnit})</tspan>
</text>
</g>
<g className='y axis' ref={(elem) => d3.select(elem).call(this.yAxis)}>
<text className='cap' transform='rotate(-90)' y='-50' dy='.1em' x={innerHeight / -2} style={{ textAnchor: 'middle' }}>
<tspan>{yLabel}</tspan>
{ yUnit && <tspan className='metric'> ({yUnit})</tspan> }
</text>
</g>
<g ref={(g) => this.tipContainer = d3.select(g)} style={{ display: 'none' }}>
<rect className='tooltip' height={tipHeight + 'em'}></rect>
{detailElems}
</g>
<g ref={(g) => this.markersContainer = d3.select(g)} style={{ display: 'none' }}>
{markerElems}
</g>
<rect
fillOpacity='0'
height={innerHeight}
width={innerWidth + 1}
onMouseEnter={this._showTip}
onTouchStart={this._showTip}
onMouseLeave={this._hideTip}
onTouchEnd={this._hideTip}
onMouseMove={this._moveTip}
onTouchMove={this._moveTip}
/>
</g>
</svg>
</div>
</Measure>
);
}
}

View File

@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import Router from '../Router';
import { shallowEqual } from '../utils/UtilityFunctions';
@@ -8,9 +9,9 @@ import { shallowEqual } from '../utils/UtilityFunctions';
export default class Link extends React.Component {
static propTypes = {
children: React.PropTypes.any,
href: React.PropTypes.string.isRequired,
onClick: React.PropTypes.func
children: PropTypes.any,
href: PropTypes.string.isRequired,
onClick: PropTypes.func
};
/**
@@ -56,4 +57,4 @@ export default class Link extends React.Component {
return <a {...this.props} onClick={this.handler}>{this.props.children}</a>;
}
}
}

View File

@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import Persist from '../stores/Persist';
@@ -22,8 +23,8 @@ function buildComparator(a, b) {
export default class ModalCompare extends TranslatedComponent {
static propTypes = {
onSelect: React.PropTypes.func.isRequired,
builds: React.PropTypes.array
onSelect: PropTypes.func.isRequired,
builds: PropTypes.array
};
static defaultProps = {

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
/**
@@ -8,9 +8,9 @@ import TranslatedComponent from './TranslatedComponent';
export default class ModalExport extends TranslatedComponent {
static propTypes = {
title: React.PropTypes.string,
generator: React.PropTypes.func,
data: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.object, React.PropTypes.array])
title: PropTypes.string,
generator: PropTypes.func,
data: PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.array])
};
/**
@@ -45,10 +45,9 @@ export default class ModalExport extends TranslatedComponent {
* Focus on textarea and select all
*/
componentDidMount() {
let e = findDOMNode(this.refs.exportField);
if (e) {
e.focus();
e.select();
if (this.exportField) {
this.exportField.focus();
this.exportField.select();
}
}
@@ -68,7 +67,7 @@ export default class ModalExport extends TranslatedComponent {
<h2>{translate(this.props.title || 'Export')}</h2>
{description}
<div>
<textarea className='cb json' ref='exportField' readOnly value={this.state.exportJson} />
<textarea className='cb json' ref={node => this.exportField = node} readOnly value={this.state.exportJson} />
</div>
<button className='r dismiss cap' onClick={this.context.hideModal}>{translate('close')}</button>
</div>;

View File

@@ -0,0 +1,37 @@
/* eslint react/no-danger: 0 */
import PropTypes from 'prop-types';
import React from 'react';
import TranslatedComponent from './TranslatedComponent';
/**
* Help Modal
*/
export default class ModalHelp extends TranslatedComponent {
static propTypes = {
title: PropTypes.string
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
}
/**
* 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

@@ -1,5 +1,5 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
import PropTypes from 'prop-types';
import cn from 'classnames';
import TranslatedComponent from './TranslatedComponent';
import Router from '../Router';
@@ -86,7 +86,7 @@ export default class ModalImport extends TranslatedComponent {
static propTypes = {
builds: React.PropTypes.object, // Optional: Import object
builds: PropTypes.object, // Optional: Import object
};
/**
@@ -459,8 +459,8 @@ export default class ModalImport extends TranslatedComponent {
* If textarea is shown focus on mount
*/
componentDidMount() {
if (!this.props.builds && findDOMNode(this.refs.importField)) {
findDOMNode(this.refs.importField).focus();
if (!this.props.builds && this.importField) {
this.importField.focus();
}
}
@@ -476,7 +476,7 @@ export default class ModalImport extends TranslatedComponent {
if (!state.processed) {
importStage = (
<div>
<textarea className='cb json' ref='importField' onChange={this._validateImport} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} />
<textarea className='cb json' ref={node => this.importField = node} onChange={this._validateImport} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} />
<button id='proceed' className='l cap' onClick={this._process} disabled={!state.importValid} >{translate('proceed')}</button>
<div className='l warning' style={{ marginLeft:'3em' }}>{state.errorMsg}</div>
</div>

View File

@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import ShortenUrl from '../utils/ShortenUrl';
@@ -8,7 +9,7 @@ import ShortenUrl from '../utils/ShortenUrl';
export default class ModalPermalink extends TranslatedComponent {
static propTypes = {
url: React.PropTypes.string.isRequired
url: PropTypes.string.isRequired
};
/**

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import cn from 'classnames';
import NumberEditor from 'react-number-editor';
@@ -10,11 +10,11 @@ import NumberEditor from 'react-number-editor';
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
ship: PropTypes.object.isRequired,
m: PropTypes.object.isRequired,
name: PropTypes.string.isRequired,
value: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired
};
/**
@@ -38,7 +38,7 @@ export default class Modification extends TranslatedComponent {
const name = this.props.name;
let scaledValue = Math.round(Number(value) * 100);
// Limit to +1000% / -100%
// Limit to +1000% / -99.99%
if (scaledValue > 100000) {
scaledValue = 100000;
value = 1000;
@@ -50,9 +50,15 @@ export default class Modification extends TranslatedComponent {
let m = this.props.m;
let ship = this.props.ship;
ship.setModification(m, name, scaledValue);
ship.setModification(m, name, scaledValue, true);
this.setState({ value });
}
/**
* Triggered when an update to slider value is finished i.e. when losing focus
*/
_updateFinished() {
this.props.onChange();
}
@@ -72,7 +78,7 @@ export default class Modification extends TranslatedComponent {
let symbol;
if (name === 'jitter') {
symbol = '°';
} else if (name !== 'burst') {
} else if (name !== 'burst' && name != 'burstrof') {
symbol = '%';
}
if (symbol) {
@@ -80,8 +86,8 @@ export default class Modification extends TranslatedComponent {
}
return (
<div className={'cb'} key={name}>
<div className={'cb'}>{translate(name)}{symbol}</div>
<div onBlur={this._updateFinished.bind(this)} className={'cb'} key={name}>
<div className={'cb'}>{translate(name, m.grp)}{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

@@ -1,12 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
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';
import { getBlueprint, blueprintTooltip, setWorst, setBest, setExtreme, setRandom } from '../utils/BlueprintFunctions';
/**
* Modifications menu
@@ -14,9 +14,10 @@ import Modification from './Modification';
export default class ModificationsMenu extends TranslatedComponent {
static propTypes = {
ship: React.PropTypes.object.isRequired,
m: React.PropTypes.object.isRequired,
onChange: React.PropTypes.func.isRequired
ship: PropTypes.object.isRequired,
m: PropTypes.object.isRequired,
marker: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired
};
/**
@@ -26,54 +27,89 @@ export default class ModificationsMenu extends TranslatedComponent {
*/
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._rollBest = this._rollBest.bind(this);
this._rollExtreme = this._rollExtreme.bind(this);
this._reset = this._reset.bind(this);
this.state = {
blueprintMenuOpened: false,
specialMenuOpened: false
};
}
/**
* Initialise state
* @param {Object} props React Component properties
* @param {Object} context React Component context
* Render the blueprints
* @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;
_renderBlueprints(props, context) {
const { m } = props;
const { language, tooltip, termtip } = context;
const translate = language.translate;
let blueprints = [];
const 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 blueprint = getBlueprint(blueprintName, m);
let blueprintGrades = [];
for (let grade in Modifications.modules[m.grp].blueprints[blueprintName].grades) {
// Grade is a string in the JSON so make it a number
grade = Number(grade);
const classes = cn('c', {
active: m.blueprint && blueprint.id === m.blueprint.id && grade === m.blueprint.grade
});
const close = this._blueprintSelected.bind(this, blueprintName, grade);
const key = blueprintName + ':' + grade;
blueprints.push(<div style={{ cursor: 'pointer' }} key={ key } onClick={ close }>{Modifications.blueprints[blueprintName].name} grade {grade}</div>);
const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers, m.grp);
blueprintGrades.unshift(<li key={key} className={classes} style={{ width: '2em' }} onMouseOver={termtip.bind(null, tooltipContent)} onMouseOut={tooltip.bind(null, null)} onClick={close}>{grade}</li>);
}
if (blueprintGrades) {
blueprints.push(<div key={blueprint.name} className={'select-group cap'}>{translate(blueprint.name)}</div>);
blueprints.push(<ul key={blueprintName}>{blueprintGrades}</ul>);
}
}
// Set up the modifications
const modifications = this._setModifications(props);
const blueprintMenuOpened = false;
// Set up the specials for this module
// const specials = _selectSpecials(m);
return { blueprintMenuOpened, blueprints, modifications };
return blueprints;
}
/**
* Initialise the modifications
* Render the specials
* @param {Object} props React component properties
* @param {Object} context React component context
* @return {Object} list: Array of React Components
*/
_renderSpecials(props, context) {
const { m } = props;
const { language, tooltip, termtip } = context;
const translate = language.translate;
const specials = [];
const specialsId = m.missile && Modifications.modules[m.grp]['specials_' + m.missile] ? 'specials_' + m.missile : 'specials';
if (Modifications.modules[m.grp][specialsId] && Modifications.modules[m.grp][specialsId].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][specialsId]) {
const close = this._specialSelected.bind(this, specialName);
specials.push(<div style={{ cursor: 'pointer' }} key={ specialName } onClick={ close }>{translate(Modifications.specials[specialName].name)}</div>);
}
}
return specials;
}
/**
* Render the modifications
* @param {Object} props React Component properties
* @return {Object} list: Array of React Components
*/
_setModifications(props) {
_renderModifications(props) {
const { m, onChange, ship } = props;
let modifications = [];
const modifications = [];
for (const modName of Modifications.modules[m.grp].modifications) {
if (Modifications.modifications[modName].type === 'percentage' || Modifications.modifications[modName].type === 'numeric') {
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 }/>);
}
@@ -91,17 +127,43 @@ export default class ModificationsMenu extends TranslatedComponent {
/**
* Activated when a blueprint is selected
* @param {int} blueprintId The ID of the selected blueprint
* @param {int} grade The grade of the selected blueprint
* @param {int} fdname The Frontier name of the 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; }));
_blueprintSelected(fdname, grade) {
this.context.tooltip(null);
const { m, ship } = this.props;
const blueprint = getBlueprint(fdname, m);
blueprint.grade = grade;
m.blueprint = blueprint;
ship.setModuleBlueprint(m, blueprint);
const blueprintMenuOpened = false;
this.setState({ blueprintMenuOpened });
this.setState({ blueprintMenuOpened: false });
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) {
this.context.tooltip(null);
const { m, ship } = this.props;
if (special === null) {
ship.clearModuleSpecial(m);
} else {
ship.setModuleSpecial(m, Modifications.specials[special]);
}
this.setState({ specialMenuOpened: false });
this.props.onChange();
}
@@ -110,26 +172,7 @@ export default class ModificationsMenu extends TranslatedComponent {
*/
_rollWorst() {
const { m, ship } = this.props;
const features = m.blueprint.features[m.blueprint.grade];
for (const featureName in features) {
if (Modifications.modifications[featureName].method == 'overwrite') {
ship.setModification(m, featureName, features[featureName][1]);
} else {
let value = features[featureName][0];
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);
}
}
}
this.setState({ modifications: this._setModifications(this.props) });
setWorst(ship, m);
this.props.onChange();
}
@@ -138,26 +181,7 @@ export default class ModificationsMenu extends TranslatedComponent {
*/
_rollRandom() {
const { m, ship } = this.props;
const features = m.blueprint.features[m.blueprint.grade];
for (const featureName in features) {
if (Modifications.modifications[featureName].method == 'overwrite') {
ship.setModification(m, featureName, features[featureName][1]);
} else {
let value = features[featureName][0] + (Math.random() * (features[featureName][1] - features[featureName][0]));
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);
}
}
}
this.setState({ modifications: this._setModifications(this.props) });
setRandom(ship, m);
this.props.onChange();
}
@@ -166,26 +190,16 @@ export default class ModificationsMenu extends TranslatedComponent {
*/
_rollBest() {
const { m, ship } = this.props;
const features = m.blueprint.features[m.blueprint.grade];
for (const featureName in features) {
if (Modifications.modifications[featureName].method == 'overwrite') {
ship.setModification(m, featureName, features[featureName][1]);
} else {
let value = features[featureName][1];
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;
}
setBest(ship, m);
this.props.onChange();
}
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);
}
}
}
this.setState({ modifications: this._setModifications(this.props) });
/**
* Provide an 'extreme' roll within the information we have
*/
_rollExtreme() {
const { m, ship } = this.props;
setExtreme(ship, m);
this.props.onChange();
}
@@ -195,9 +209,8 @@ export default class ModificationsMenu extends TranslatedComponent {
_reset() {
const { m, ship } = this.props;
ship.clearModifications(m);
ship.clearBlueprint(m);
ship.clearModuleBlueprint(m);
this.setState({ modifications: this._setModifications(this.props) });
this.props.onChange();
}
@@ -206,51 +219,77 @@ export default class ModificationsMenu extends TranslatedComponent {
* @return {React.Component} List
*/
render() {
const language = this.context.language;
const { language, tooltip, termtip } = this.context;
const translate = language.translate;
const { tooltip, termtip } = this.context;
const { m } = this.props;
const { blueprintMenuOpened } = this.state;
const { blueprintMenuOpened, specialMenuOpened } = this.state;
const _toggleBlueprintsMenu = this._toggleBlueprintsMenu;
const _toggleSpecialsMenu = this._toggleSpecialsMenu;
const _rollBest = this._rollBest;
const _rollExtreme = this._rollExtreme;
const _rollWorst = this._rollWorst;
const _rollRandom = this._rollRandom;
const _reset = this._reset;
let blueprintLabel;
let haveBlueprint = false;
if (m.blueprint && !isEmpty(m.blueprint)) {
let blueprintTt;
if (m.blueprint && m.blueprint.name) {
blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
haveBlueprint = true;
} else {
blueprintLabel = translate('PHRASE_SELECT_BLUEPRINT');
blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade].engineers, m.grp);
}
let specialLabel;
if (m.blueprint && m.blueprint.special) {
specialLabel = m.blueprint.special.name;
} else {
specialLabel = translate('PHRASE_SELECT_SPECIAL');
}
const specials = this._renderSpecials(this.props, this.context);
const showBlueprintsMenu = blueprintMenuOpened;
const showSpecial = haveBlueprint && specials.length && !blueprintMenuOpened;
const showSpecialsMenu = specialMenuOpened;
const showRolls = haveBlueprint && !blueprintMenuOpened && !specialMenuOpened;
const showReset = !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: true })} style={{ cursor: 'pointer' }} onClick={_toggleBlueprintsMenu}>{blueprintLabel}</div>
{ blueprintMenuOpened ? this.state.blueprints : '' }
{ haveBlueprint ?
<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={_rollRandom} onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')} onMouseOut={tooltip.bind(null, null)}> { translate('random') } </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={_reset}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')} onMouseOut={tooltip.bind(null, null)}> { translate('reset') } </td>
</tr>
</tbody>
</table> : '' }
{ blueprintMenuOpened ? '' :
{ showBlueprintsMenu ? '' : haveBlueprint ?
<div className={ cn('section-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={termtip.bind(null, blueprintTt)} onMouseOut={tooltip.bind(null, null)} onClick={_toggleBlueprintsMenu}>{blueprintLabel}</div> :
<div className={ cn('section-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleBlueprintsMenu}>{translate('PHRASE_SELECT_BLUEPRINT')}</div> }
{ showBlueprintsMenu ? this._renderBlueprints(this.props, this.context) : null }
{ showSpecial ? <div className={ cn('section-menu', { selected: specialMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleSpecialsMenu}>{specialLabel}</div> : null }
{ showSpecialsMenu ? specials : null }
{ showRolls || showReset ?
<table style={{ width: '100%', backgroundColor: 'transparent' }}>
<tbody>
{ showRolls ?
<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={_rollBest}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')} onMouseOut={tooltip.bind(null, null)}> { translate('best') } </td>
<td style={{ cursor: 'pointer' }} onClick={_rollExtreme}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_EXTREME')} onMouseOut={tooltip.bind(null, null)}> { translate('extreme') } </td>
<td style={{ cursor: 'pointer' }} onClick={_rollRandom} onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')} onMouseOut={tooltip.bind(null, null)}> { translate('random') } </td>
</tr> : null }
{ showReset ?
<tr>
<td colSpan={'5'} style={{ cursor: 'pointer' }} onClick={_reset}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')} onMouseOut={tooltip.bind(null, null)}> { translate('reset') } </td>
</tr> : null }
</tbody>
</table> : null }
{ showMods ?
<span onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')} onMouseOut={tooltip.bind(null, null)} >
{ this.state.modifications }
</span> }
{ this._renderModifications(this.props) }
</span> : null }
</div>
);
}

View File

@@ -0,0 +1,71 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
/**
* Movement
*/
export default class Movement extends TranslatedComponent {
static propTypes = {
marker: PropTypes.string.isRequired,
ship: PropTypes.object.isRequired,
boost: PropTypes.bool.isRequired,
eng: PropTypes.number.isRequired,
fuel: PropTypes.number.isRequired,
cargo: PropTypes.number.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
}
/**
* Render movement
* @return {React.Component} contents
*/
render() {
const { ship, boost, eng, cargo, fuel } = this.props;
const { language } = this.context;
const { formats, translate, units } = language;
return (
<span id='movement'>
<svg viewBox='0 0 600 600' fillRule="evenodd" clipRule="evenodd">
// Axes
<path d="M150 250v300" strokeWidth='1'/>
<path d="M150 250l236 236" strokeWidth='1'/>
<path d="M150 250l350 -200" strokeWidth='1'/>
// End Arrow
<path d="M508 43.3L487 67l-10-17.3 31-6.4z"/>
// Axes arcs and arrows
<path d="M71.7 251.7C64.2 259.2 60 269.4 60 280c0 22 18 40 40 40s40-18 40-40c0-10.6-4.2-20.8-11.7-28.3 7.5 7.5 11.7 17.7 11.7 28.3 0 22-18 40-40 40s-40-18-40-40c0-10.6 4.2-20.8 11.7-28.3z" strokeWidth='4' transform="matrix(.6 0 0 .3 87.5 376.3)"/>
<path d="M142.8 453l-13.2 8.7-2.6-9.7 15.8 1z"/>
<path d="M144.7 451.6l.5 1.6-16.2 10.6h-.4l-3.5-13 .7-.4 19.3 1.2zm-14.2 7.7l7.7-5-9.2-.7 1.5 5.7zm25.7-6.3l15.8-1-2.6 9.7-13.2-8.8z"/>
<path d="M174 450.8l-3.6 13h-.4l-16.2-10.6.5-1.6 19.3-1.2.3.4zm-13.2 3.4l7.7 5 1.5-5.6-9.2.6z"/>
<path d="M407.7 119c2 .7 4.3 1 6.4 1 14 0 25-11.2 25-25s-11-25-25-25c-11 0-21 7.6-24 18.5 3-11 13-18.5 24-18.5 14 0 25 11.2 25 25s-11 25-25 25c-2 0-4-.3-6-1z" strokeWidth='2'/>
<path d="M388 99.7L387 84l9.8 2.5-8.7 13.2z"/>
<path d="M398.8 85.5l.2.5-10.7 16-1.6-.3-1.2-19.3.4-.3 12.5 3.8zm-9.5 9.7l5-7.7-5.6-1.6.6 9zm10 20.8l15.7-1-2.6 9.7-13.2-8.8z"/>
<path d="M417 113.8l-3.6 13h-.4l-16.2-10.6.5-1.6 19.3-1.2.3.4zm-13.2 3.4l7.7 5 1.5-5.6-9.2.6z"/>
<path d="M355 430c0-13.8-11.2-25-25-25s-25 11.2-25 25 11.2 25 25 25c-13.8 0-25-11.2-25-25s11.2-25 25-25 25 11.2 25 25z" strokeWidth='2'/>
<path d="M357 439.7l-8.8-13 9.7-2.7-1 15.7z"/>
<path d="M359.5 422.4l-1.2 19.3-1.6.4-10.7-16 .2-.2 13-3.4.3.4zm-9 5l5.2 7.8.6-9.3-5.7 1.2zm-10.5 24l-13.2 8.6-2.6-9.7 15.8 1z"/>
<path d="M342 450l.4 1.5-16.2 10.7-.4-.2-3.5-13 .3-.3L342 450zm-14.3 7.6l7.7-5-9.2-.6 1.5 5.6z"/>
// Speed
<text x="470" y="30" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcSpeed(eng, fuel, cargo, boost)) + 'm/s' : '-'}</text>
// Pitch
<text x="355" y="410" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcPitch(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
// Roll
<text x="450" y="110" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcRoll(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
// Yaw
<text x="160" y="430" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcYaw(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
</svg>
</span>);
}
}

View File

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

View File

@@ -0,0 +1,267 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import * as Calc from '../shipyard/Calculations';
import PieChart from './PieChart';
import { nameComparator } from '../utils/SlotFunctions';
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
import VerticalBarChart from './VerticalBarChart';
/**
* 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);
};
}
/**
* Offence information
* Offence information consists of four panels:
* - textual information (time to drain cap, time to take down shields etc.)
* - breakdown of damage sources (pie chart)
* - comparison of shield resistances (table chart)
* - effective sustained DPS of weapons (bar chart)
*/
export default class Offence extends TranslatedComponent {
static propTypes = {
marker: PropTypes.string.isRequired,
ship: PropTypes.object.isRequired,
opponent: PropTypes.object.isRequired,
engagementrange: PropTypes.number.isRequired,
wep: PropTypes.number.isRequired,
opponentSys: PropTypes.number.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this._sort = this._sort.bind(this);
const damage = Calc.offenceMetrics(props.ship, props.opponent, props.wep, props.opponentSys, props.engagementrange);
this.state = {
predicate: 'n',
desc: true,
damage
};
}
/**
* Update the state if our properties change
* @param {Object} nextProps Incoming/Next properties
* @return {boolean} Returns true if the component should be rerendered
*/
componentWillReceiveProps(nextProps) {
if (this.props.marker != nextProps.marker || this.props.eng != nextProps.eng) {
const damage = Calc.offenceMetrics(nextProps.ship, nextProps.opponent, nextProps.wep, nextProps.opponentSys, nextProps.engagementrange);
this.setState({ damage });
}
return true;
}
/**
* 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(predicate, desc);
this.setState({ predicate, desc });
}
/**
* Sorts the weapon list
* @param {string} predicate Sort predicate
* @param {Boolean} desc Sort order descending
*/
_sort(predicate, desc) {
let comp = weaponComparator.bind(null, this.context.language.translate);
switch (predicate) {
case 'n': comp = comp(null, desc); break;
case 'esdpss': comp = comp((a, b) => a.sdps.shields.total - b.sdps.shields.total, desc); break;
case 'es': comp = comp((a, b) => a.effectiveness.shields.total - b.effectiveness.shields.total, desc); break;
case 'esdpsh': comp = comp((a, b) => a.sdps.armour.total - b.sdps.armour.total, desc); break;
case 'eh': comp = comp((a, b) => a.effectiveness.armour.total - b.effectiveness.armour.total, desc); break;
}
this.state.damage.sort(comp);
}
/**
* Render offence
* @return {React.Component} contents
*/
render() {
const { ship, opponent, wep, engagementrange } = this.props;
const { language, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { damage } = this.state;
const sortOrder = this._sortOrder;
const pd = ship.standard[4].m;
const opponentShields = Calc.shieldMetrics(opponent, 4);
const opponentArmour = Calc.armourMetrics(opponent);
const timeToDrain = Calc.timeToDrainWep(ship, wep);
let absoluteShieldsSDps = 0;
let explosiveShieldsSDps = 0;
let kineticShieldsSDps = 0;
let thermalShieldsSDps = 0;
let absoluteArmourSDps = 0;
let explosiveArmourSDps = 0;
let kineticArmourSDps = 0;
let thermalArmourSDps = 0;
let totalSEps = 0;
const rows = [];
for (let i = 0; i < damage.length; i++) {
const weapon = damage[i];
totalSEps += weapon.seps;
absoluteShieldsSDps += weapon.sdps.shields.absolute;
explosiveShieldsSDps += weapon.sdps.shields.explosive;
kineticShieldsSDps += weapon.sdps.shields.kinetic;
thermalShieldsSDps += weapon.sdps.shields.thermal;
absoluteArmourSDps += weapon.sdps.armour.absolute;
explosiveArmourSDps += weapon.sdps.armour.explosive;
kineticArmourSDps += weapon.sdps.armour.kinetic;
thermalArmourSDps += weapon.sdps.armour.thermal;
const effectivenessShieldsTooltipDetails = [];
effectivenessShieldsTooltipDetails.push(<div key='range'>{translate('range') + ' ' + formats.pct1(weapon.effectiveness.shields.range)}</div>);
effectivenessShieldsTooltipDetails.push(<div key='resistance'>{translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.shields.resistance)}</div>);
effectivenessShieldsTooltipDetails.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(weapon.effectiveness.shields.sys)}</div>);
const effectiveShieldsSDpsTooltipDetails = [];
if (weapon.sdps.shields.absolute) effectiveShieldsSDpsTooltipDetails.push(<div key='absolute'>{translate('absolute') + ' ' + formats.f1(weapon.sdps.shields.absolute)}</div>);
if (weapon.sdps.shields.explosive) effectiveShieldsSDpsTooltipDetails.push(<div key='explosive'>{translate('explosive') + ' ' + formats.f1(weapon.sdps.shields.explosive)}</div>);
if (weapon.sdps.shields.kinetic) effectiveShieldsSDpsTooltipDetails.push(<div key='kinetic'>{translate('kinetic') + ' ' + formats.f1(weapon.sdps.shields.kinetic)}</div>);
if (weapon.sdps.shields.thermal) effectiveShieldsSDpsTooltipDetails.push(<div key='thermal'>{translate('thermal') + ' ' + formats.f1(weapon.sdps.shields.thermal)}</div>);
const effectivenessArmourTooltipDetails = [];
effectivenessArmourTooltipDetails.push(<div key='range'>{translate('range') + ' ' + formats.pct1(weapon.effectiveness.armour.range)}</div>);
effectivenessArmourTooltipDetails.push(<div key='resistance'>{translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.armour.resistance)}</div>);
effectivenessArmourTooltipDetails.push(<div key='hardness'>{translate('hardness') + ' ' + formats.pct1(weapon.effectiveness.armour.hardness)}</div>);
const effectiveArmourSDpsTooltipDetails = [];
if (weapon.sdps.armour.absolute) effectiveArmourSDpsTooltipDetails.push(<div key='absolute'>{translate('absolute') + ' ' + formats.f1(weapon.sdps.armour.absolute)}</div>);
if (weapon.sdps.armour.explosive) effectiveArmourSDpsTooltipDetails.push(<div key='explosive'>{translate('explosive') + ' ' + formats.f1(weapon.sdps.armour.explosive)}</div>);
if (weapon.sdps.armour.kinetic) effectiveArmourSDpsTooltipDetails.push(<div key='kinetic'>{translate('kinetic') + ' ' + formats.f1(weapon.sdps.armour.kinetic)}</div>);
if (weapon.sdps.armour.thermal) effectiveArmourSDpsTooltipDetails.push(<div key='thermal'>{translate('thermal') + ' ' + formats.f1(weapon.sdps.armour.thermal)}</div>);
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'><span onMouseOver={termtip.bind(null, effectiveShieldsSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.shields.total)}</span></td>
<td className='ri'><span onMouseOver={termtip.bind(null, effectivenessShieldsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(weapon.effectiveness.shields.total)}</span></td>
<td className='ri'><span onMouseOver={termtip.bind(null, effectiveArmourSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.armour.total)}</span></td>
<td className='ri'><span onMouseOver={termtip.bind(null, effectivenessArmourTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(weapon.effectiveness.armour.total)}</span></td>
</tr>);
}
const totalShieldsSDps = absoluteShieldsSDps + explosiveShieldsSDps + kineticShieldsSDps + thermalShieldsSDps;
const totalArmourSDps = absoluteArmourSDps + explosiveArmourSDps + kineticArmourSDps + thermalArmourSDps;
const shieldsSDpsData = [];
shieldsSDpsData.push({ value: Math.round(absoluteShieldsSDps), label: translate('absolute') });
shieldsSDpsData.push({ value: Math.round(explosiveShieldsSDps), label: translate('explosive') });
shieldsSDpsData.push({ value: Math.round(kineticShieldsSDps), label: translate('kinetic') });
shieldsSDpsData.push({ value: Math.round(thermalShieldsSDps), label: translate('thermal') });
const armourSDpsData = [];
armourSDpsData.push({ value: Math.round(absoluteArmourSDps), label: translate('absolute') });
armourSDpsData.push({ value: Math.round(explosiveArmourSDps), label: translate('explosive') });
armourSDpsData.push({ value: Math.round(kineticArmourSDps), label: translate('kinetic') });
armourSDpsData.push({ value: Math.round(thermalArmourSDps), label: translate('thermal') });
const timeToDepleteShields = Calc.timeToDeplete(opponentShields.total, totalShieldsSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4));
const timeToDepleteArmour = Calc.timeToDeplete(opponentArmour.total, totalArmourSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4));
return (
<span id='offence'>
<div className='group full'>
<table>
<thead>
<tr className='main'>
<th rowSpan='2' className='sortable' onClick={sortOrder.bind(this, 'n')}>{translate('weapon')}</th>
<th colSpan='2'>{translate('opponent\'s shields')}</th>
<th colSpan='2'>{translate('opponent\'s armour')}</th>
</tr>
<tr>
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')} onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'esdpss')}>{'sdps'}</th>
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_SHIELDS')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'es')}>{'eft'}</th>
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'esdpsh')}>{'sdps'}</th>
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'eh')}>{'eft'}</th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
</table>
</div>
<div className='group quarter'>
<h2>{translate('offence metrics')}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_DRAIN_WEP'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_DRAIN_WEP')}<br/>{timeToDrain === Infinity ? translate('never') : formats.time(timeToDrain)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_EFFECTIVE_SDPS_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_EFFECTIVE_SDPS_SHIELDS')}<br/>{formats.f1(totalShieldsSDps)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_REMOVE_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_REMOVE_SHIELDS')}<br/>{timeToDepleteShields === Infinity ? translate('never') : formats.time(timeToDepleteShields)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_EFFECTIVE_SDPS_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_EFFECTIVE_SDPS_ARMOUR')}<br/>{formats.f1(totalArmourSDps)}</h2>
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_REMOVE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_REMOVE_ARMOUR')}<br/>{timeToDepleteArmour === Infinity ? translate('never') : formats.time(timeToDepleteArmour)}</h2>
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield damage sources')}</h2>
<PieChart data={shieldsSDpsData} />
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_ARMOUR_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('armour damage sources')}</h2>
<PieChart data={armourSDpsData} />
</div>
</span>);
}
}

View File

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

View File

@@ -0,0 +1,187 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import { Ships } from 'coriolis-data/dist';
import Ship from '../shipyard/Ship';
import Persist from '../stores/Persist';
import TranslatedComponent from './TranslatedComponent';
import PowerManagement from './PowerManagement';
import CostSection from './CostSection';
import EngineProfile from './EngineProfile';
import FSDProfile from './FSDProfile';
import Movement from './Movement';
import Offence from './Offence';
import Defence from './Defence';
import WeaponDamageChart from './WeaponDamageChart';
/**
* Outfitting subpages
*/
export default class OutfittingSubpages extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired,
code: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired,
buildName: PropTypes.string,
sys: PropTypes.number.isRequired,
eng: PropTypes.number.isRequired,
wep: PropTypes.number.isRequired,
cargo: PropTypes.number.isRequired,
fuel: PropTypes.number.isRequired,
boost: PropTypes.bool.isRequired,
engagementRange: PropTypes.number.isRequired,
opponent: PropTypes.object.isRequired,
opponentBuild: PropTypes.string,
opponentSys: PropTypes.number.isRequired,
opponentEng: PropTypes.number.isRequired,
opponentWep: PropTypes.number.isRequired,
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this._powerTab = this._powerTab.bind(this);
this._profilesTab = this._profilesTab.bind(this);
this._offenceTab = this._offenceTab.bind(this);
this._defenceTab = this._defenceTab.bind(this);
this.state = {
tab: Persist.getOutfittingTab() || 'power',
};
}
/**
* Show selected tab
* @param {string} tab Tab name
*/
_showTab(tab) {
this.setState({ tab });
}
/**
* Render the power tab
* @return {React.Component} Tab contents
*/
_powerTab() {
let { ship, buildName, code, onChange } = this.props;
Persist.setOutfittingTab('power');
const powerMarker = `${ship.toString()}`;
const costMarker = `${ship.toString().split('.')[0]}`;
return <div>
<PowerManagement ship={ship} code={powerMarker} onChange={onChange} />
<CostSection ship={ship} buildName={buildName} code={costMarker} />
</div>;
}
/**
* Render the profiles tab
* @return {React.Component} Tab contents
*/
_profilesTab() {
const { ship, opponent, cargo, fuel, eng, boost, engagementRange, opponentSys } = this.props;
const { translate } = this.context.language;
let realBoost = boost && ship.canBoost(cargo, fuel);
Persist.setOutfittingTab('profiles');
const engineProfileMarker = `${ship.toString()}:${cargo}:${fuel}:${eng}:${realBoost}`;
const fsdProfileMarker = `${ship.toString()}:${cargo}:${fuel}`;
const movementMarker = `${ship.topSpeed}:${ship.pitch}:${ship.roll}:${ship.yaw}:${ship.canBoost(cargo, fuel)}`;
const damageMarker = `${ship.toString()}:${opponent.toString()}:${engagementRange}:${opponentSys}`;
return <div>
<div className='group third'>
<h1>{translate('engine profile')}</h1>
<EngineProfile ship={ship} marker={engineProfileMarker} fuel={fuel} cargo={cargo} eng={eng} boost={realBoost} />
</div>
<div className='group third'>
<h1>{translate('fsd profile')}</h1>
<FSDProfile ship={ship} marker={fsdProfileMarker} fuel={fuel} cargo={cargo} />
</div>
<div className='group third'>
<h1>{translate('movement profile')}</h1>
<Movement marker={movementMarker} ship={ship} boost={boost} eng={eng} cargo={cargo} fuel={fuel} />
</div>
<div className='group half'>
<h1>{translate('damage to opponent\'s shields')}</h1>
<WeaponDamageChart marker={damageMarker} ship={ship} opponent={opponent} opponentSys={opponentSys} hull={false} engagementRange={engagementRange} />
</div>
<div className='group half'>
<h1>{translate('damage to opponent\'s hull')}</h1>
<WeaponDamageChart marker={damageMarker} ship={ship} opponent={opponent} opponentSys={opponentSys} hull={true} engagementRange={engagementRange} />
</div>
</div>;
}
/**
* Render the offence tab
* @return {React.Component} Tab contents
*/
_offenceTab() {
const { ship, sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild, opponentSys } = this.props;
Persist.setOutfittingTab('offence');
const marker = `${ship.toString()}${opponent.toString()}${opponentBuild}${engagementRange}${opponentSys}`;
return <div>
<Offence marker={marker} ship={ship} opponent={opponent} wep={wep} opponentSys={opponentSys} engagementrange={engagementRange}/>
</div>;
}
/**
* Render the defence tab
* @return {React.Component} Tab contents
*/
_defenceTab() {
const { ship, sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild, opponentWep } = this.props;
Persist.setOutfittingTab('defence');
const marker = `${ship.toString()}${opponent.toString()}{opponentBuild}${engagementRange}${opponentWep}`;
return <div>
<Defence marker={marker} ship={ship} opponent={opponent} sys={sys} opponentWep={opponentWep} engagementrange={engagementRange}/>
</div>;
}
/**
* Render the section
* @return {React.Component} Contents
*/
render() {
const tab = this.state.tab;
const translate = this.context.language.translate;
let tabSection;
switch (tab) {
case 'power': tabSection = this._powerTab(); break;
case 'profiles': tabSection = this._profilesTab(); break;
case 'offence': tabSection = this._offenceTab(); break;
case 'defence': tabSection = this._defenceTab(); break;
}
return (
<div className='group full' style={{ minHeight: '1000px' }}>
<table className='tabs'>
<thead>
<tr>
<th style={{ width:'25%' }} className={cn({ active: tab == 'power' })} onClick={this._showTab.bind(this, 'power')} >{translate('power and costs')}</th>
<th style={{ width:'25%' }} className={cn({ active: tab == 'profiles' })} onClick={this._showTab.bind(this, 'profiles')} >{translate('profiles')}</th>
<th style={{ width:'25%' }} className={cn({ active: tab == 'offence' })} onClick={this._showTab.bind(this, 'offence')} >{translate('offence')}</th>
<th style={{ width:'25%' }} className={cn({ active: tab == 'defence' })} onClick={this._showTab.bind(this, 'defence')} >{translate('defence')}</th>
</tr>
</thead>
</table>
{tabSection}
</div>
);
}
}

View File

@@ -0,0 +1,97 @@
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Measure from 'react-measure';
import * as d3 from 'd3';
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
const LABEL_COLOUR = '#000000';
/**
* A pie chart
*/
export default class PieChart extends Component {
static propTypes = {
data : PropTypes.array.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
this.pie = d3.pie().value((d) => d.value);
this.colors = CORIOLIS_COLOURS;
this.arc = d3.arc();
this.arc.innerRadius(0);
this.state = {
dimensions: {
width: 100,
height: 100
}
};
}
/**
* Generate a slice of the pie chart
* @param {Object} d the data for this slice
* @param {number} i the index of this slice
* @returns {Object} the SVG for the slice
*/
sliceGenerator(d, i) {
if (!d || d.value == 0) {
// Ignore 0 values
return null;
}
const { width, height } = this.state.dimensions;
const { data } = this.props;
// Push the labels further out from the centre of the slice
let [labelX, labelY] = this.arc.centroid(d);
const labelTranslate = `translate(${labelX * 1.5}, ${labelY * 1.5})`;
// Put the keys in a line with equal spacing
const nonZeroItems = data.filter(d => d.value != 0).length;
const thisItemIndex = data.slice(0, i + 1).filter(d => d.value != 0).length - 1;
const keyX = -width / 2 + (width / nonZeroItems) * (thisItemIndex + 0.5);
const keyTranslate = `translate(${keyX}, ${width * 0.45})`;
return (
<g key={`group-${i}`}>
<path key={`arc-${i}`} d={this.arc(d)} style={{ fill: this.colors[i] }} />
<text key={`label-${i}`} transform={labelTranslate} style={{ strokeWidth: '0px', fill: LABEL_COLOUR }} textAnchor='middle'>{d.value}</text>
<text key={`key-${i}`} transform={keyTranslate} style={{ strokeWidth:'0px', fill: this.colors[i] }} textAnchor='middle'>{d.data.label}</text>
</g>
);
}
/**
* Render the component
* @returns {object} Markup
*/
render() {
const { width, height } = this.state.dimensions;
const pie = this.pie(this.props.data),
translate = `translate(${width / 2}, ${width * 0.4})`;
this.arc.outerRadius(width * 0.4);
return (
<Measure width='100%' whitelist={['width', 'top']} onMeasure={ (dimensions) => { this.setState({ dimensions }); }}>
<div width={width} height={width}>
<svg style={{ stroke: 'None' }} width={width} height={width * 0.9}>
<g transform={translate}>
{pie.map((d, i) => this.sliceGenerator(d, i))}
</g>
</svg>
</div>
</Measure>
);
}
}

299
src/app/components/Pips.jsx Normal file
View File

@@ -0,0 +1,299 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import { nameComparator } from '../utils/SlotFunctions';
import { Pip } from './SvgIcons';
import LineChart from '../components/LineChart';
import Slider from '../components/Slider';
import * as ModuleUtils from '../shipyard/ModuleUtils';
import Module from '../shipyard/Module';
/**
* Pips displays SYS/ENG/WEP pips and allows users to change them with key presses by clicking on the relevant area.
* Requires an onChange() function of the form onChange(sys, eng, wep) which is triggered whenever the pips change.
*/
export default class Pips extends TranslatedComponent {
static propTypes = {
sys: PropTypes.number.isRequired,
eng: PropTypes.number.isRequired,
wep: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
const { sys, eng, wep } = props;
this._keyDown = this._keyDown.bind(this);
}
/**
* Add listeners after mounting
*/
componentDidMount() {
document.addEventListener('keydown', this._keyDown);
}
/**
* Remove listeners before unmounting
*/
componentWillUnmount() {
document.removeEventListener('keydown', this._keyDown);
}
/**
* Handle Key Down
* @param {Event} e Keyboard Event
*/
_keyDown(e) {
if (e.ctrlKey || e.metaKey) { // CTRL/CMD
switch (e.keyCode) {
case 37: // Left arrow == increase SYS
e.preventDefault();
this._incSys();
break;
case 38: // Up arrow == increase ENG
e.preventDefault();
this._incEng();
break;
case 39: // Right arrow == increase WEP
e.preventDefault();
this._incWep();
break;
case 40: // Down arrow == reset
e.preventDefault();
this._reset();
break;
}
}
}
/**
* Handle a click
* @param {string} which Which item was clicked
*/
onClick(which) {
if (which == 'SYS') {
this._incSys();
} else if (which == 'ENG') {
this._incEng();
} else if (which == 'WEP') {
this._incWep();
} else if (which == 'RST') {
this._reset();
}
}
/**
* Reset the capacitor
*/
_reset() {
let { sys, eng, wep } = this.props;
if (sys != 2 || eng != 2 || wep != 2) {
sys = eng = wep = 2;
this.props.onChange(sys, eng, wep);
}
}
/**
* Increment the SYS capacitor
*/
_incSys() {
let { sys, eng, wep } = this.props;
const required = Math.min(1, 4 - sys);
if (required > 0) {
if (required == 0.5) {
// Take from whichever is larger
if (eng > wep) {
eng -= 0.5;
sys += 0.5;
} else {
wep -= 0.5;
sys += 0.5;
}
} else {
// Required is 1 - take from both if possible
if (eng == 0) {
wep -= 1;
sys += 1;
} else if (wep == 0) {
eng -= 1;
sys += 1;
} else {
eng -= 0.5;
wep -= 0.5;
sys += 1;
}
}
this.props.onChange(sys, eng, wep);
}
}
/**
* Increment the ENG capacitor
*/
_incEng() {
let { sys, eng, wep } = this.props;
const required = Math.min(1, 4 - eng);
if (required > 0) {
if (required == 0.5) {
// Take from whichever is larger
if (sys > wep) {
sys -= 0.5;
eng += 0.5;
} else {
wep -= 0.5;
eng += 0.5;
}
} else {
// Required is 1 - take from both if possible
if (sys == 0) {
wep -= 1;
eng += 1;
} else if (wep == 0) {
sys -= 1;
eng += 1;
} else {
sys -= 0.5;
wep -= 0.5;
eng += 1;
}
}
this.props.onChange(sys, eng, wep);
}
}
/**
* Increment the WEP capacitor
*/
_incWep() {
let { sys, eng, wep } = this.props;
const required = Math.min(1, 4 - wep);
if (required > 0) {
if (required == 0.5) {
// Take from whichever is larger
if (sys > eng) {
sys -= 0.5;
wep += 0.5;
} else {
eng -= 0.5;
wep += 0.5;
}
} else {
// Required is 1 - take from both if possible
if (sys == 0) {
eng -= 1;
wep += 1;
} else if (eng == 0) {
sys -= 1;
wep += 1;
} else {
sys -= 0.5;
eng -= 0.5;
wep += 1;
}
}
this.props.onChange(sys, eng, wep);
}
}
/**
* Set up the rendering for pips
* @param {int} sys the SYS pips
* @param {int} eng the ENG pips
* @param {int} wep the WEP pips
* @returns {Object} Object containing the rendering for the pips
*/
_renderPips(sys, eng, wep) {
const pipsSvg = {};
// SYS
pipsSvg['SYS'] = [];
for (let i = 0; i < Math.floor(sys); i++) {
pipsSvg['SYS'].push(<Pip className='full' key={i} />);
}
if (sys > Math.floor(sys)) {
pipsSvg['SYS'].push(<Pip className='half' key={'half'} />);
}
for (let i = Math.floor(sys + 0.5); i < 4; i++) {
pipsSvg['SYS'].push(<Pip className='empty' key={i} />);
}
// ENG
pipsSvg['ENG'] = [];
for (let i = 0; i < Math.floor(eng); i++) {
pipsSvg['ENG'].push(<Pip className='full' key={i} />);
}
if (eng > Math.floor(eng)) {
pipsSvg['ENG'].push(<Pip className='half' key={'half'} />);
}
for (let i = Math.floor(eng + 0.5); i < 4; i++) {
pipsSvg['ENG'].push(<Pip className='empty' key={i} />);
}
// WEP
pipsSvg['WEP'] = [];
for (let i = 0; i < Math.floor(wep); i++) {
pipsSvg['WEP'].push(<Pip className='full' key={i} />);
}
if (wep > Math.floor(wep)) {
pipsSvg['WEP'].push(<Pip className='half' key={'half'} />);
}
for (let i = Math.floor(wep + 0.5); i < 4; i++) {
pipsSvg['WEP'].push(<Pip className='empty' key={i} />);
}
return pipsSvg;
}
/**
* Render pips
* @return {React.Component} contents
*/
render() {
const { formats, translate, units } = this.context.language;
const { sys, eng, wep } = this.props;
const onSysClicked = this.onClick.bind(this, 'SYS');
const onEngClicked = this.onClick.bind(this, 'ENG');
const onWepClicked = this.onClick.bind(this, 'WEP');
const onRstClicked = this.onClick.bind(this, 'RST');
const pipsSvg = this._renderPips(sys, eng, wep);
return (
<span id='pips'>
<table>
<tbody>
<tr>
<td>&nbsp;</td>
<td>&nbsp;</td>
<td className='clickable' onClick={onEngClicked}>{pipsSvg['ENG']}</td>
<td>&nbsp;</td>
</tr>
<tr>
<td>&nbsp;</td>
<td className='clickable' onClick={onSysClicked}>{pipsSvg['SYS']}</td>
<td className='clickable' onClick={onEngClicked}>{translate('ENG')}</td>
<td className='clickable' onClick={onWepClicked}>{pipsSvg['WEP']}</td>
</tr>
<tr>
<td>&nbsp;</td>
<td className='clickable' onClick={onSysClicked}>{translate('SYS')}</td>
<td className='clickable' onClick={onRstClicked}>{translate('RST')}</td>
<td className='clickable' onClick={onWepClicked}>{translate('WEP')}</td>
</tr>
</tbody>
</table>
</span>
);
}
}

View File

@@ -1,5 +1,6 @@
import React from 'react';
import d3 from 'd3';
import PropTypes from 'prop-types';
import * as d3 from 'd3';
import cn from 'classnames';
import TranslatedComponent from './TranslatedComponent';
import { wrapCtxMenu } from '../utils/UtilityFunctions';
@@ -33,10 +34,10 @@ function bandText(val, index, wattScale) {
export default class PowerBands extends TranslatedComponent {
static propTypes = {
bands: React.PropTypes.array.isRequired,
available: React.PropTypes.number.isRequired,
width: React.PropTypes.number.isRequired,
code: React.PropTypes.string,
bands: PropTypes.array.isRequired,
available: PropTypes.number.isRequired,
width: PropTypes.number.isRequired,
code: PropTypes.string,
};
/**
@@ -46,10 +47,10 @@ export default class PowerBands extends TranslatedComponent {
*/
constructor(props, context) {
super(props);
this.wattScale = d3.scale.linear();
this.pctScale = d3.scale.linear().domain([0, 1]);
this.wattAxis = d3.svg.axis().scale(this.wattScale).outerTickSize(0).orient('top').tickFormat(context.language.formats.r2);
this.pctAxis = d3.svg.axis().scale(this.pctScale).outerTickSize(0).orient('bottom').tickFormat(context.language.formats.rPct);
this.wattScale = d3.scaleLinear();
this.pctScale = d3.scaleLinear().domain([0, 1]);
this.wattAxis = d3.axisTop(this.wattScale).tickSizeOuter(0).tickFormat(context.language.formats.r2);
this.pctAxis = d3.axisBottom(this.pctScale).tickSizeOuter(0).tickFormat(context.language.formats.rPct);
this._updateDimensions = this._updateDimensions.bind(this);
this._updateScales = this._updateScales.bind(this);
@@ -186,10 +187,10 @@ export default class PowerBands extends TranslatedComponent {
let { wattScale, pctScale, context, props, state } = this;
let { translate, formats } = context.language;
let { f2, pct1, rPct, r2 } = formats; // wattFmt, pctFmt, pctAxis, wattAxis
let { available, bands, width } = props;
let { innerWidth, maxPwr, ret, dep } = state;
let pwrWarningClass = cn('threshold', { exceeded: bands[0].retractedSum * 2 >= available });
let { f2, pct1 } = formats; // wattFmt, pctFmt
let { available, bands } = props;
let { innerWidth, ret, dep } = state;
let pwrWarningClass = cn('threshold', { exceeded: bands[0].retractedSum > available * 0.4 });
let deployed = [];
let retracted = [];
let retSelected = Object.keys(ret).length > 0;
@@ -268,7 +269,7 @@ export default class PowerBands extends TranslatedComponent {
axis.call(this.pctAxis);
axis.select('g:nth-child(6)').selectAll('line, text').attr('class', pwrWarningClass);
}} className='pct axis' transform={`translate(0,${state.innerHeight})`}></g>
<line x1={pctScale(0.5)} x2={pctScale(0.5)} y1='0' y2={state.innerHeight} className={pwrWarningClass} />
<line x1={pctScale(0.4)} x2={pctScale(0.4)} y1='0' y2={state.innerHeight} className={pwrWarningClass} />
<text dy='0.5em' x='-3' y={state.retY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'retracted')} onMouseLeave={this._hidetip}>{translate('ret')}</text>
<text dy='0.5em' x='-3' y={state.depY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'deployed', { orientation: 's', cap: 1 })} onMouseLeave={this._hidetip}>{translate('dep')}</text>
<text dy='0.5em' x={innerWidth + 5} y={state.retY} className={getClass(retSelected, retSum, available)}>{f2(Math.max(0, retSum))} ({pct1(Math.max(0, retSum / available))})</text>

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
import PropTypes from 'prop-types';
import cn from 'classnames';
import TranslatedComponent from './TranslatedComponent';
import PowerBands from './PowerBands';
@@ -17,10 +17,10 @@ const POWER = [
* Power Management Section
*/
export default class PowerManagement extends TranslatedComponent {
static PropTypes = {
ship: React.PropTypes.object.isRequired,
code: React.PropTypes.string.isRequired,
onChange: React.PropTypes.func.isRequired
static propTypes = {
ship: PropTypes.object.isRequired,
code: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired
};
/**
@@ -34,8 +34,8 @@ export default class PowerManagement extends TranslatedComponent {
this._sort = this._sort.bind(this);
this.state = {
predicate: 'n',
desc: true,
predicate: 'pwr',
desc: false,
width: 0
};
}
@@ -148,7 +148,7 @@ export default class PowerManagement extends TranslatedComponent {
* Update power bands width from DOM
*/
_updateWidth() {
this.setState({ width: findDOMNode(this).offsetWidth });
this.setState({ width: this.node.offsetWidth });
}
/**
@@ -196,7 +196,7 @@ export default class PowerManagement extends TranslatedComponent {
let sortOrder = this._sortOrder;
return (
<div className='group half' id='componentPriority'>
<div ref={node => this.node = node} className='group half' id='componentPriority'>
<table style={{ width: '100%' }}>
<thead>
<tr className='main'>

View File

@@ -0,0 +1,127 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import Ship from '../shipyard/Ship';
import { Ships } from 'coriolis-data/dist';
import { Rocket } from './SvgIcons';
import Persist from '../stores/Persist';
import cn from 'classnames';
/**
* Ship picker
* Requires an onChange() function of the form onChange(ship), providing the ship, which is triggered on ship change
*/
export default class ShipPicker extends TranslatedComponent {
static propTypes = {
onChange: PropTypes.func.isRequired,
ship: PropTypes.string.isRequired,
build: PropTypes.string
};
static defaultProps = {
ship: 'eagle'
}
/**
* constructor
* @param {object} props Properties react
* @param {object} context react context
*/
constructor(props, context) { // eslint-disable-line
super(props);
this.shipOrder = Object.keys(Ships).sort();
this._toggleMenu = this._toggleMenu.bind(this);
this._closeMenu = this._closeMenu.bind(this);
this.state = { menuOpen: false };
}
/**
* Update ship
* @param {object} ship the ship
* @param {string} build the build, if present
*/
_shipChange(ship, build) {
this._closeMenu();
// Ensure that the ship has changed
if (ship !== this.props.ship || build !== this.props.build) {
this.props.onChange(ship, build);
}
}
/**
* Render the menu for the picker
* @returns {object} the picker menu
*/
_renderPickerMenu() {
const { ship, build } = this.props;
const _shipChange = this._shipChange;
const builds = Persist.getBuilds();
const buildList = [];
for (let shipId of this.shipOrder) {
const shipBuilds = [];
// Add stock build
const stockSelected = (ship == shipId && !build);
shipBuilds.push(<li key={shipId} className={ cn({ 'selected': stockSelected })} onClick={_shipChange.bind(this, shipId, null)}>Stock</li>);
if (builds[shipId]) {
let buildNameOrder = Object.keys(builds[shipId]).sort();
for (let buildName of buildNameOrder) {
const buildSelected = ship === shipId && build === buildName;
shipBuilds.push(<li key={shipId + '-' + buildName} className={ cn({ 'selected': buildSelected })} onClick={_shipChange.bind(this, shipId, buildName)}>{buildName}</li>);
}
}
buildList.push(<ul key={shipId} className='block'>{Ships[shipId].properties.name}{shipBuilds}</ul>);
}
return buildList;
}
/**
* Toggle the menu state
*/
_toggleMenu() {
const { menuOpen } = this.state;
this.setState({ menuOpen: !menuOpen });
}
/**
* Close the menu
*/
_closeMenu() {
const { menuOpen } = this.state;
if (menuOpen) {
this._toggleMenu();
}
}
/**
* Render picker
* @return {React.Component} contents
*/
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { ship, build } = this.props;
const { menuOpen } = this.state;
const shipString = ship + ': ' + (build ? build : translate('stock'));
return (
<div className='shippicker' onClick={ (e) => e.stopPropagation() }>
<div className='menu'>
<div className={cn('menu-header', { selected: menuOpen })} onClick={this._toggleMenu}>
<span><Rocket className='warning' /></span>
<span className='menu-item-label'>{shipString}</span>
</div>
{ menuOpen ?
<div className='menu-list' onClick={ (e) => e.stopPropagation() }>
<div className='quad'>
{this._renderPickerMenu()}
</div>
</div> : null }
</div>
</div>
);
}
}

View File

@@ -1,90 +0,0 @@
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

@@ -1,8 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import cn from 'classnames';
import { SizeMap } from '../shipyard/Constants';
import { Warning } from './SvgIcons';
import * as Calc from '../shipyard/Calculations';
/**
* Ship Summary Table / Stats
@@ -10,7 +11,10 @@ import { Warning } from './SvgIcons';
export default class ShipSummaryTable extends TranslatedComponent {
static propTypes = {
ship: React.PropTypes.object.isRequired
ship: PropTypes.object.isRequired,
cargo: PropTypes.number.isRequired,
fuel: PropTypes.number.isRequired,
marker: PropTypes.string.isRequired,
};
/**
@@ -18,74 +22,76 @@ export default class ShipSummaryTable extends TranslatedComponent {
* @return {React.Component} Summary table
*/
render() {
let ship = this.props.ship;
const { ship, cargo, fuel } = this.props;
let { language, tooltip, termtip } = this.context;
let translate = language.translate;
let u = language.units;
let formats = language.formats;
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 { time, int, round, f1, f2 } = formats;
let hide = tooltip.bind(null, null);
if (ship.shield) {
sgRecover = time(ship.calcShieldRecovery());
sgRecharge = time(ship.calcShieldRecharge());
}
const shieldGenerator = ship.findInternalByGroup('sg');
const sgClassNames = cn({ warning: shieldGenerator && !ship.shield, muted: !shieldGenerator });
const sgTooltip = shieldGenerator ? 'TT_SUMMARY_SHIELDS' : 'TT_SUMMARY_SHIELDS_NONFUNCTIONAL';
const timeToDrain = Calc.timeToDrainWep(ship, 4);
const canThrust = ship.canThrust(cargo, ship.fuelCapacity);
const speedTooltip = canThrust ? 'TT_SUMMARY_SPEED' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
const canBoost = ship.canBoost(cargo, ship.fuelCapacity);
const boostTooltip = canBoost ? 'TT_SUMMARY_BOOST' : canThrust ? 'TT_SUMMARY_BOOST_NONFUNCTIONAL' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
return <div id='summary'>
<table id='summaryTable'>
<thead>
<tr className='main'>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !ship.canThrust() }) }>{translate('speed')}</th>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !ship.canBoost() }) }>{translate('boost')}</th>
<th onMouseEnter={termtip.bind(null, 'damage per second')} onMouseLeave={hide} rowSpan={2}>{translate('DPS')}</th>
<th onMouseEnter={termtip.bind(null, 'energy per second')} onMouseLeave={hide} rowSpan={2}>{translate('EPS')}</th>
<th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th>
<th rowSpan={2}>{translate('hardness')}</th>
<th rowSpan={2}>{translate('armour')}</th>
<th rowSpan={2}>{translate('shields')}</th>
<th colSpan={3}>{translate('mass')}</th>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canThrust }) }>{translate('speed')}</th>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canBoost }) }>{translate('boost')}</th>
<th colSpan={5}>{translate('jump range')}</th>
<th rowSpan={2}>{translate('shield')}</th>
<th rowSpan={2}>{translate('integrity')}</th>
<th rowSpan={2}>{translate('DPS')}</th>
<th rowSpan={2}>{translate('EPS')}</th>
<th rowSpan={2}>{translate('TTD')}</th>
{/* <th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th> */}
<th rowSpan={2}>{translate('cargo')}</th>
<th rowSpan={2}>{translate('fuel')}</th>
<th colSpan={3}>{translate('jump range')}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_FASTEST_RANGE')} onMouseLeave={hide} colSpan={3}>{translate('fastest range')}</th>
<th onMouseEnter={termtip.bind(null, 'mass lock factor')} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th>
<th colSpan={3}>{translate('mass')}</th>
<th onMouseEnter={termtip.bind(null, 'hull hardness', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('hrd')}</th>
<th rowSpan={2}>{translate('crew')}</th>
<th onMouseEnter={termtip.bind(null, 'mass lock factor', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th>
</tr>
<tr>
<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>
<th className='lft'>{translate('max')}</th>
<th>{translate('full tank')}</th>
<th>{translate('unladen')}</th>
<th>{translate('laden')}</th>
<th className='lft'>{translate('jumps')}</th>
<th>{translate('total unladen')}</th>
<th>{translate('total laden')}</th>
<th className='lft'>{translate('hull')}</th>
<th>{translate('unladen')}</th>
<th>{translate('laden')}</th>
</tr>
</thead>
<tbody>
<tr>
<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>{f1(ship.totalDps)}</td>
<td>{f1(ship.totalEps)}</td>
<td>{f1(ship.totalHps)}</td>
<td onMouseEnter={termtip.bind(null, speedTooltip, { cap: 0 })} onMouseLeave={hide}>{ canThrust ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, false))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td onMouseEnter={termtip.bind(null, boostTooltip, { cap: 0 })} onMouseLeave={hide}>{ canBoost ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, true))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_MAX_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.standard[2].m.getMaxFuelPerJump(), ship.standard[2].m, ship.standard[2].m.getMaxFuelPerJump()))}{u.LY}</span></td>
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</span></td>
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</span></td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</td>
<td className={sgClassNames} onMouseEnter={termtip.bind(null, sgTooltip, { cap: 0 })} onMouseLeave={hide}>{int(ship.shield)}{u.MJ}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_INTEGRITY', { cap: 0 })} onMouseLeave={hide}>{int(ship.armour)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_DPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalDps)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_EPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalEps)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_TTD', { cap: 0 })} onMouseLeave={hide}>{timeToDrain === Infinity ? '∞' : time(timeToDrain)}</td>
{/* <td>{f1(ship.totalHps)}</td> */}
<td>{round(ship.cargoCapacity)}{u.T}</td>
<td>{round(ship.fuelCapacity)}{u.T}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_HULL_MASS', { cap: 0 })} onMouseLeave={hide}>{ship.hullMass}{u.T}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.unladenMass)}{u.T}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.ladenMass)}{u.T}</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>{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>{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.crew}</td>
<td>{ship.masslock}</td>
</tr>
</tbody>

View File

@@ -1,5 +1,5 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
import PropTypes from 'prop-types';
const MARGIN_LR = 8; // Left/ Right margin
@@ -16,14 +16,14 @@ export default class Slider extends React.Component {
};
static propTypes = {
axis: React.PropTypes.bool,
axisUnit: React.PropTypes.string,
max: React.PropTypes.number,
min: React.PropTypes.number,
onChange: React.PropTypes.func.isRequired,
onResize: React.PropTypes.func,
percent: React.PropTypes.number.isRequired,
scale: React.PropTypes.number
axis: PropTypes.bool,
axisUnit: PropTypes.string,
max: PropTypes.number,
min: PropTypes.number,
onChange: PropTypes.func.isRequired,
onResize: PropTypes.func,
percent: PropTypes.number.isRequired,
scale: PropTypes.number
};
/**
@@ -100,7 +100,7 @@ export default class Slider extends React.Component {
*/
_updateDimensions() {
this.setState({
outerWidth: findDOMNode(this).getBoundingClientRect().width
outerWidth: this.node.getBoundingClientRect().width
});
}
@@ -144,14 +144,14 @@ export default class Slider extends React.Component {
};
if (!outerWidth) {
return <svg style={style} />;
return <svg style={style} ref={node => this.node = node} />;
}
let margin = MARGIN_LR * scale;
let width = outerWidth - (margin * 2);
let pctPos = width * this.props.percent;
return <svg onMouseUp={this._up} onMouseEnter={this._enter.bind(this)} onMouseMove={this._move} onTouchEnd={this._up} style={style}>
return <svg onMouseUp={this._up} onMouseEnter={this._enter.bind(this)} onMouseMove={this._move} onTouchEnd={this._up} style={style} ref={node => this.node = node}>
<rect className='primary' style={{ opacity: 0.3 }} x={margin} y='0.25em' rx='0.3em' ry='0.3em' width={width} height='0.7em' />
<rect className='primary-disabled' x={margin} y='0.45em' rx='0.15em' ry='0.15em' width={pctPos} height='0.3em' />
<circle className='primary' r={margin} cy='0.6em' cx={pctPos + margin} />

View File

@@ -1,10 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
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';
@@ -15,18 +14,19 @@ import { stopCtxPropagation } from '../utils/UtilityFunctions';
export default class Slot extends TranslatedComponent {
static propTypes = {
availableModules: React.PropTypes.func.isRequired,
onSelect: React.PropTypes.func.isRequired,
onOpen: React.PropTypes.func.isRequired,
maxClass: React.PropTypes.number.isRequired,
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,
dropClass: React.PropTypes.string
availableModules: PropTypes.func.isRequired,
onSelect: PropTypes.func.isRequired,
onOpen: PropTypes.func.isRequired,
maxClass: PropTypes.number.isRequired,
selected: PropTypes.bool,
m: PropTypes.object,
enabled: PropTypes.bool.isRequired,
ship: PropTypes.object.isRequired,
eligible: PropTypes.object,
warning: PropTypes.func,
drag: PropTypes.func,
drop: PropTypes.func,
dropClass: PropTypes.string
};
/**
@@ -80,8 +80,8 @@ export default class Slot extends TranslatedComponent {
render() {
let language = this.context.language;
let translate = language.translate;
let { ship, m, dropClass, dragOver, onOpen, onChange, selected, eligible, onSelect, warning, availableModules } = this.props;
let slotDetails, menu;
let { ship, m, enabled, dropClass, dragOver, onOpen, onChange, selected, eligible, onSelect, warning, availableModules } = this.props;
let slotDetails, modificationsMarker, menu;
if (!selected) {
// If not selected then sure that modifications flag is unset
@@ -89,9 +89,11 @@ export default class Slot extends TranslatedComponent {
}
if (m) {
slotDetails = this._getSlotDetails(m, translate, language.formats, language.units); // Must be implemented by sub classes
slotDetails = this._getSlotDetails(m, enabled, translate, language.formats, language.units); // Must be implemented by sub classes
modificationsMarker = JSON.stringify(m);
} else {
slotDetails = <div className={'empty'}>{translate(eligible ? 'emptyrestricted' : 'empty')}</div>;
modificationsMarker = '';
}
if (selected) {
@@ -101,6 +103,7 @@ export default class Slot extends TranslatedComponent {
onChange={onChange}
ship={ship}
m={m}
marker={modificationsMarker}
/>;
} else {
menu = <AvailableModulesMenu

View File

@@ -1,9 +1,11 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { wrapCtxMenu } from '../utils/UtilityFunctions';
import { canMount } from '../utils/SlotFunctions';
import { Equalizer } from '../components/SvgIcons';
import cn from 'classnames';
const browser = require('detect-browser');
/**
* Abstract Slot Section
@@ -11,10 +13,12 @@ import cn from 'classnames';
export default class SlotSection extends TranslatedComponent {
static propTypes = {
ship: React.PropTypes.object.isRequired,
onChange: React.PropTypes.func.isRequired,
code: React.PropTypes.string.isRequired,
togglePwr: React.PropTypes.func
ship: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
onCargoChange: PropTypes.func.isRequired,
onFuelChange: PropTypes.func.isRequired,
code: PropTypes.string.isRequired,
togglePwr: PropTypes.func
};
/**
@@ -64,7 +68,7 @@ export default class SlotSection extends TranslatedComponent {
* @param {Object} m Selected module
*/
_selectModule(slot, m) {
this.props.ship.use(slot, m);
this.props.ship.use(slot, m, false);
this.props.onChange();
this._close();
}
@@ -75,8 +79,10 @@ export default class SlotSection extends TranslatedComponent {
* @param {Event} e Drag Event
*/
_drag(originSlot, e) {
e.dataTransfer.setData('text/html', e.currentTarget);
e.dataTransfer.effectAllowed = 'all';
if (!browser || (browser.name !== 'edge' && browser.name !== 'ie')) {
e.dataTransfer.setData('text/html', e.currentTarget);
}
e.dataTransfer.effectAllowed = 'copyMove';
this.setState({ originSlot, copy: e.getModifierState('Alt') });
this._close();
}
@@ -93,10 +99,14 @@ export default class SlotSection extends TranslatedComponent {
if (os) {
// 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';
if (!browser || (browser.name !== 'edge' && browser.name !== 'ie')) {
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';
if (!browser || (browser.name !== 'edge' && browser.name !== 'ie')) {
e.dataTransfer.dropEffect = 'none';
}
}
}
@@ -106,7 +116,9 @@ export default class SlotSection extends TranslatedComponent {
*/
_dragOverNone(e) {
e.preventDefault();
e.dataTransfer.dropEffect = 'none';
if (!browser || (browser.name !== 'edge' && browser.name !== 'ie')) {
e.dataTransfer.dropEffect = 'none';
}
this.setState({ targetSlot: null });
}
@@ -119,24 +131,46 @@ export default class SlotSection extends TranslatedComponent {
let { originSlot, targetSlot, copy } = this.state;
let m = originSlot.m;
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
if (targetSlot && originSlot != targetSlot) {
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, false);
// Copy power info
targetSlot.enabled = originSlot.enabled;
targetSlot.priority = originSlot.priority;
this.props.onChange();
}
} else {
// Store power info
const originEnabled = targetSlot.enabled;
const originPriority = targetSlot.priority;
const targetEnabled = originSlot.enabled;
const targetPriority = originSlot.priority;
// 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);
this.props.ship.use(targetSlot, m);
// Swap power
originSlot.enabled = originEnabled;
originSlot.priority = originPriority;
targetSlot.enabled = targetEnabled;
targetSlot.priority = targetPriority;
} else { // Otherwise empty the origin slot
// Store power
const targetEnabled = originSlot.enabled;
this.props.ship.use(originSlot, null, true); // Empty but prevent summary update
this.props.ship.use(targetSlot, m);
originSlot.enabled = 0;
originSlot.priority = 0;
targetSlot.enabled = targetEnabled;
targetSlot.priority = targetPriority;
}
this.props.onChange();
}
this.props.ship.use(targetSlot, m); // update target slot
this.props.onChange();
}
}
this.setState({ originSlot: null, targetSlot: null, copy: null });

View File

@@ -1,14 +1,16 @@
import React from 'react';
import PropTypes from 'prop-types';
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 * as ModuleUtils from '../shipyard/ModuleUtils';
import { ListModifications, Modified } from './SvgIcons';
import { Modifications } from 'coriolis-data/dist';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
import { blueprintTooltip } from '../utils/BlueprintFunctions';
/**
* Standard Slot
@@ -16,14 +18,14 @@ import { stopCtxPropagation } from '../utils/UtilityFunctions';
export default class StandardSlot extends TranslatedComponent {
static propTypes = {
slot: React.PropTypes.object,
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,
slot: PropTypes.object,
modules: PropTypes.array.isRequired,
onSelect: PropTypes.func.isRequired,
onOpen: PropTypes.func.isRequired,
onChange: PropTypes.func.isRequired,
ship: PropTypes.object.isRequired,
selected: PropTypes.bool,
warning: PropTypes.func,
};
/**
@@ -54,6 +56,12 @@ export default class StandardSlot extends TranslatedComponent {
let modTT = translate('modified');
if (m && m.blueprint && m.blueprint.name) {
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
modTT = (
<div>
<div>{modTT}</div>
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
</div>
);
}
if (!selected) {
@@ -61,6 +69,8 @@ export default class StandardSlot extends TranslatedComponent {
this._modificationsSelected = false;
}
const modificationsMarker = JSON.stringify(m);
if (selected) {
if (this._modificationsSelected) {
menu = <ModificationsMenu
@@ -68,12 +78,13 @@ export default class StandardSlot extends TranslatedComponent {
onChange={onChange}
ship={ship}
m={m}
marker={modificationsMarker}
/>;
} else {
menu = <AvailableModulesMenu
className='standard'
modules={modules}
shipMass={ship.ladenMass}
shipMass={ModuleUtils.isShieldGenerator(m.grp) ? ship.hullMass : ship.unladenMass}
m={m}
onSelect={onSelect}
warning={warning}
@@ -84,16 +95,18 @@ export default class StandardSlot extends TranslatedComponent {
return (
<div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen} onContextMenu={stopCtxPropagation}>
<div className={cn('details-container', { warning: warning && warning(slot.m) })}>
<div className={cn('details-container', { warning: warning && warning(slot.m), disabled: m.grp !== 'bh' && !slot.enabled })}>
<div className={'sz'}>{slot.maxClass}</div>
<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.getOptimalMass() ? <div className='l'>{translate('optimal mass')}: {formats.int(m.getOptimalMass())}{units.T}</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.getOptMul() ? <div className='l'>{translate('optimal multiplier')}: {formats.rPct(m.getOptMul())}</div> : null }
{ m.getRange() ? <div className='l'>{translate('range', m.grp)}: {formats.f2(m.getRange())}{units.km}</div> : null }
{ m.time ? <div className='l'>{translate('time')}: {formats.time(m.time)}</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 }
@@ -104,7 +117,7 @@ export default class StandardSlot extends TranslatedComponent {
{ 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.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</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>

View File

@@ -3,7 +3,6 @@ import cn from 'classnames';
import SlotSection from './SlotSection';
import StandardSlot from './StandardSlot';
import Module from '../shipyard/Module';
import { diffDetails } from '../utils/SlotFunctions';
import * as ShipRoles from '../shipyard/ShipRoles';
import { stopCtxPropagation } from '../utils/UtilityFunctions';
@@ -29,6 +28,8 @@ export default class StandardSlotSection extends SlotSection {
_optimizeStandard() {
this.props.ship.useLightestStandard();
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
@@ -40,6 +41,8 @@ export default class StandardSlotSection extends SlotSection {
_multiPurpose(shielded, bulkheadIndex) {
ShipRoles.multiPurpose(this.props.ship, shielded, bulkheadIndex);
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
@@ -50,6 +53,20 @@ export default class StandardSlotSection extends SlotSection {
_optimizeCargo(shielded) {
ShipRoles.trader(this.props.ship, shielded);
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
/**
* Miner Build
* @param {Boolean} shielded True if shield generator should be included
*/
_optimizeMiner(shielded) {
ShipRoles.miner(this.props.ship, shielded);
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
@@ -60,6 +77,19 @@ export default class StandardSlotSection extends SlotSection {
_optimizeExplorer(planetary) {
ShipRoles.explorer(this.props.ship, planetary);
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
/**
* Racer role
*/
_optimizeRacer() {
ShipRoles.racer(this.props.ship);
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
@@ -86,7 +116,7 @@ export default class StandardSlotSection extends SlotSection {
* @return {Array} Array of Slots
*/
_getSlots() {
let { ship, currentMenu } = this.props;
let { ship, currentMenu, cargo, fuel } = this.props;
let slots = new Array(8);
let open = this._openMenu;
let select = this._selectModule;
@@ -126,7 +156,7 @@ export default class StandardSlotSection extends SlotSection {
selected={currentMenu == st[1]}
onChange={this.props.onChange}
ship={ship}
warning={m => m instanceof Module ? m.getMaxMass() < (ship.ladenMass - st[1].mass + m.mass) : m.maxmass < (ship.ladenMass - st[1].mass + m.mass)}
warning={m => m instanceof Module ? m.getMaxMass() < (ship.unladenMass + cargo + fuel - st[1].m.mass + m.mass) : m.maxmass < (ship.unladenMass + cargo + fuel - st[1].m.mass + m.mass)}
/>;
@@ -161,7 +191,7 @@ export default class StandardSlotSection extends SlotSection {
selected={currentMenu == st[4]}
onChange={this.props.onChange}
ship={ship}
warning={m => m instanceof Module ? m.getEnginesCapacity() < ship.boostEnergy : m.engcap < ship.boostEnergy}
warning={m => m instanceof Module ? m.getEnginesCapacity() <= ship.boostEnergy : m.engcap <= ship.boostEnergy}
/>;
slots[6] = <StandardSlot
@@ -209,6 +239,9 @@ export default class StandardSlotSection extends SlotSection {
<li className='lc' onClick={this._optimizeCargo.bind(this, true)}>{translate('Shielded Trader')}</li>
<li className='lc' onClick={this._optimizeExplorer.bind(this, false)}>{translate('Explorer')}</li>
<li className={cn('lc', { disabled: planetaryDisabled })} onClick={!planetaryDisabled && this._optimizeExplorer.bind(this, true)}>{translate('Planetary Explorer')}</li>
<li className='lc' onClick={this._optimizeMiner.bind(this, false)}>{translate('Miner')}</li>
<li className='lc' onClick={this._optimizeMiner.bind(this, true)}>{translate('Shielded Miner')}</li>
<li className='lc' onClick={this._optimizeRacer.bind(this)}>{translate('Racer')}</li>
</ul>
</div>;
}

View File

@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import { shallowEqual } from '../utils/UtilityFunctions';
@@ -8,8 +9,8 @@ import { shallowEqual } from '../utils/UtilityFunctions';
class SvgIcon extends React.Component {
static propTypes = {
className: React.PropTypes.any,
style: React.PropTypes.object
className: PropTypes.any,
style: PropTypes.object
};
/**
@@ -227,6 +228,26 @@ export class LinkIcon extends SvgIcon {
}
}
/**
* Shopping icon (dollar sign)
*/
export class ShoppingIcon 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='M94 188v-17c-9-1-16-3-21-6-6-3-11-7-15-14-4-6-6-14-6-23l17-3c2 9 4 16 7 21 5 6 11 9 18 10v-56c-7-1-14-4-22-8-6-3-10-8-13-13-3-6-4-12-4-19 0-13 4-23 13-31 6-5 15-8 26-9v-8h11v8c10 1 18 4 24 9 8 6 12 15 14 26l-18 3c-1-7-4-12-7-16s-8-6-13-7v50l17 6c6 2 10 5 13 8 4 4 7 8 8 13 2 4 3 10 3 15 0 12-4 22-11 31-8 8-18 12-30 13v17H94zm0-153c-7 1-12 3-16 8-4 4-6 9-6 15s2 11 5 16c4 4 9 7 17 9V35zm11 121a28 28 0 0 0 24-28c-1-6-2-11-6-15-3-4-9-7-18-10v53z'/>
</g>;
}
}
/**
* No Power - Lightning bolt + no entry
*/
@@ -319,6 +340,24 @@ 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
*/
@@ -534,6 +573,25 @@ 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)
*/
@@ -651,6 +709,24 @@ export class Switch extends SvgIcon {
}
}
/**
* Pip
*/
export class Pip 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 <rect x='10' y='10' width='180' height='180'/>;
}
}
/**
* In-game Coriolis Station logo
*/

View File

@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
/**
@@ -7,8 +8,8 @@ import TranslatedComponent from './TranslatedComponent';
export default class Tooltip extends TranslatedComponent {
static propTypes = {
rect: React.PropTypes.object.isRequired,
options: React.PropTypes.object
rect: PropTypes.object.isRequired,
options: PropTypes.object
};
static defaultProps = {
@@ -127,4 +128,4 @@ export default class Tooltip extends TranslatedComponent {
</div>;
}
}
}

View File

@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import { shallowEqual } from '../utils/UtilityFunctions';
/**
@@ -7,15 +8,15 @@ import { shallowEqual } from '../utils/UtilityFunctions';
export default class TranslatedComponent extends React.Component {
static contextTypes = {
language: React.PropTypes.object.isRequired,
sizeRatio: React.PropTypes.number.isRequired,
openMenu: React.PropTypes.func.isRequired,
closeMenu: React.PropTypes.func.isRequired,
showModal: React.PropTypes.func.isRequired,
hideModal: React.PropTypes.func.isRequired,
tooltip: React.PropTypes.func.isRequired,
termtip: React.PropTypes.func.isRequired,
onWindowResize: React.PropTypes.func.isRequired
language: PropTypes.object.isRequired,
sizeRatio: PropTypes.number.isRequired,
openMenu: PropTypes.func.isRequired,
closeMenu: PropTypes.func.isRequired,
showModal: PropTypes.func.isRequired,
hideModal: PropTypes.func.isRequired,
tooltip: PropTypes.func.isRequired,
termtip: PropTypes.func.isRequired,
onWindowResize: PropTypes.func.isRequired
};
/**

View File

@@ -74,9 +74,9 @@ export default class UtilitySlotSection extends SlotSection {
dragOver={this._dragOverSlot.bind(this, h)}
drop={this._drop}
dropClass={this._dropClass(h, originSlot, targetSlot)}
enabled={h.enabled}
ship={ship}
m={h.m}
enabled={h.enabled ? true : false}
/>);
}
}
@@ -99,11 +99,11 @@ export default class UtilitySlotSection extends SlotSection {
</ul>
<div className='select-group cap'>{translate('sb')}</div>
<ul>
<li className='c' onClick={_use.bind(this, 'sb', 'E', null)}>E</li>
<li className='c' onClick={_use.bind(this, 'sb', 'D', null)}>D</li>
<li className='c' onClick={_use.bind(this, 'sb', 'C', null)}>C</li>
<li className='c' onClick={_use.bind(this, 'sb', 'B', null)}>B</li>
<li className='c' onClick={_use.bind(this, 'sb', 'A', null)}>A</li>
<li className='c' onClick={_use.bind(this, 'sb', 'B', null)}>B</li>
<li className='c' onClick={_use.bind(this, 'sb', 'C', null)}>C</li>
<li className='c' onClick={_use.bind(this, 'sb', 'D', null)}>D</li>
<li className='c' onClick={_use.bind(this, 'sb', 'E', null)}>E</li>
</ul>
<div className='select-group cap'>{translate('hs')}</div>
<ul>

View File

@@ -0,0 +1,111 @@
import TranslatedComponent from './TranslatedComponent';
import React, { PropTypes } from 'react';
import Measure from 'react-measure';
import { BarChart, Bar, XAxis, YAxis } from 'recharts';
const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D'];
const LABEL_COLOUR = '#000000';
const AXIS_COLOUR = '#C06400';
const ASPECT = 1;
const merge = function(one, two) {
return Object.assign({}, one, two);
};
/**
* A vertical bar chart
*/
export default class VerticalBarChart extends TranslatedComponent {
static propTypes = {
data : PropTypes.array.isRequired,
yMax : PropTypes.number
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
this._termtip = this._termtip.bind(this);
this.state = {
dimensions: {
width: 300,
height: 300
}
};
}
/**
* Render the bar chart
* @returns {Object} the markup
*/
render() {
const { width, height } = this.state.dimensions;
const { tooltip, termtip } = this.context;
// Calculate maximum for Y
let dataMax = Math.max(...this.props.data.map(d => d.value));
if (dataMax == -Infinity) dataMax = 0;
let yMax = this.props.yMax ? Math.round(this.props.yMax) : 0;
const localMax = Math.max(dataMax, yMax);
return (
<Measure whitelist={['width', 'top']} onMeasure={ (dimensions) => this.setState({ dimensions }) }>
<div width='100%'>
<BarChart width={width} height={width * ASPECT} data={this.props.data} margin={{ top: 5, right: 5, left: 5, bottom: 5 }}>
<XAxis interval={0} fontSize='0.8em' stroke={AXIS_COLOUR} dataKey='label' />
<YAxis interval={'preserveStart'} tickCount={11} fontSize='0.8em' stroke={AXIS_COLOUR} type='number' domain={[0, localMax]}/>
<Bar dataKey='value' label={<ValueLabel />} fill={CORIOLIS_COLOURS[0]} isAnimationActive={false} onMouseOver={this._termtip} onMouseOut={tooltip.bind(null, null)}/>
</BarChart>
</div>
</Measure>
);
}
/**
* Generate a term tip
* @param {Object} d the data
* @param {number} i the index
* @param {Object} e the event
* @returns {Object} termtip markup
*/
_termtip(d, i, e) {
if (this.props.data[i].tooltip) {
return this.context.termtip(this.props.data[i].tooltip, e);
} else {
return null;
}
}
}
/**
* A label that displays the value within the bar of the chart
*/
class ValueLabel extends React.Component {
static propTypes = {
x: PropTypes.number,
y: PropTypes.number,
payload: PropTypes.object,
value: PropTypes.number
};
/**
* Render offence
* @return {React.Component} contents
*/
render() {
const { x, y, payload, value } = this.props;
const em = value < 1000 ? '1em' : value < 1000 ? '0.8em' : '0.7em';
return (
<text x={x} y={y} fill="#000000" textAnchor="middle" dy={20} style={{ fontSize: em }}>{value}</text>
);
}
};

View File

@@ -0,0 +1,204 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { Ships } from 'coriolis-data/dist';
import { nameComparator } from '../utils/SlotFunctions';
import { CollapseSection, ExpandSection, MountFixed, MountGimballed, MountTurret } from './SvgIcons';
import LineChart from '../components/LineChart';
import Slider from '../components/Slider';
import * as Calc from '../shipyard/Calculations';
import Module from '../shipyard/Module';
const DAMAGE_DEALT_COLORS = ['#FFFFFF', '#FF0000', '#00FF00', '#7777FF', '#FFFF00', '#FF00FF', '#00FFFF', '#777777'];
/**
* Weapon damage chart
*/
export default class WeaponDamageChart extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired,
opponent: PropTypes.object.isRequired,
hull: PropTypes.bool.isRequired,
engagementRange: PropTypes.number.isRequired,
opponentSys: PropTypes.number.isRequired,
marker: PropTypes.string.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
*/
constructor(props, context) {
super(props);
}
/**
* Set the initial weapons state
*/
componentWillMount() {
const weaponNames = this._weaponNames(this.props.ship, this.context);
const opponentShields = Calc.shieldMetrics(this.props.opponent, this.props.opponentSys);
const opponentArmour = Calc.armourMetrics(this.props.opponent);
const maxRange = this._calcMaxRange(this.props.ship);
const maxDps = this._calcMaxSDps(this.props.ship, this.props.opponent, opponentShields, opponentArmour);
this.setState({ maxRange, maxDps, weaponNames, opponentShields, opponentArmour, calcSDpsFunc: this._calcSDps.bind(this, this.props.ship, weaponNames, this.props.opponent, opponentShields, opponentArmour, this.props.hull) });
}
/**
* 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.marker != this.props.marker) {
const weaponNames = this._weaponNames(nextProps.ship, nextContext);
const opponentShields = Calc.shieldMetrics(nextProps.opponent, nextProps.opponentSys);
const opponentArmour = Calc.armourMetrics(nextProps.opponent);
const maxRange = this._calcMaxRange(nextProps.ship);
const maxDps = this._calcMaxSDps(nextProps.ship, nextProps.opponent, opponentShields, opponentArmour);
this.setState({ weaponNames,
opponentShields,
opponentArmour,
maxRange,
maxDps,
calcSDpsFunc: this._calcSDps.bind(this, nextProps.ship, weaponNames, nextProps.opponent, opponentShields, opponentArmour, nextProps.hull)
});
}
return true;
}
/**
* Calculate the maximum range of a ship's weapons
* @param {Object} ship The ship
* @returns {int} The maximum range, in metres
*/
_calcMaxRange(ship) {
let maxRange = 1000; // Minimum
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
const thisRange = ship.hardpoints[i].m.getRange();
if (thisRange > maxRange) {
maxRange = thisRange;
}
}
}
return maxRange;
}
/**
* Calculate the maximum sustained single-weapon DPS for this ship
* @param {Object} ship The ship
* @param {Object} opponent The opponent ship
* @param {Object} opponentShields The opponent's shields
* @param {Object} opponentArmour The opponent's armour
* @return {number} The maximum sustained single-weapon DPS
*/
_calcMaxSDps(ship, opponent, opponentShields, opponentArmour) {
// Additional information to allow effectiveness calculations
let maxSDps = 0;
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
const m = ship.hardpoints[i].m;
const sustainedDps = Calc._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, 0);
const thisSDps = sustainedDps.damage.armour.total > sustainedDps.damage.shields.total ? sustainedDps.damage.armour.total : sustainedDps.damage.shields.total;
if (thisSDps > maxSDps) {
maxSDps = thisSDps;
}
}
}
return maxSDps;
}
/**
* Obtain the weapon names for this ship
* @param {Object} ship The ship
* @param {Object} context The context
* @return {array} The weapon names
*/
_weaponNames(ship, context) {
const translate = context.language.translate;
let names = [];
let num = 1;
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
const m = ship.hardpoints[i].m;
let name = '' + num++ + ': ' + m.class + m.rating + (m.missile ? '/' + m.missile : '') + ' ' + translate(m.name || m.grp);
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) {
engineering += ', ' + translate(m.blueprint.special.name);
}
}
if (engineering) {
name = name + ' (' + engineering + ')';
}
names.push(name);
}
}
return names;
}
/**
* Calculate the per-weapon sustained DPS for this ship against another ship at a given range
* @param {Object} ship The ship
* @param {Object} weaponNames The names of the weapons for which to calculate DPS
* @param {Object} opponent The target
* @param {Object} opponentShields The opponent's shields
* @param {Object} opponentArmour The opponent's armour
* @param {bool} hull true if to calculate against hull, false if to calculate against shields
* @param {Object} engagementRange The engagement range
* @return {array} The array of weapon DPS
*/
_calcSDps(ship, weaponNames, opponent, opponentShields, opponentArmour, hull, engagementRange) {
let results = {};
let weaponNum = 0;
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
const m = ship.hardpoints[i].m;
const sustainedDps = Calc._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementRange);
results[weaponNames[weaponNum++]] = hull ? sustainedDps.damage.armour.total : sustainedDps.damage.shields.total;
}
}
return results;
}
/**
* Render damage dealt
* @return {React.Component} contents
*/
render() {
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
const { formats, translate, units } = language;
const { maxRange } = this.state;
const { ship, opponent, engagementRange } = this.props;
const sortOrder = this._sortOrder;
const onCollapseExpand = this._onCollapseExpand;
const code = `${ship.toString()}:${opponent.toString()}`;
return (
<span>
<LineChart
xMax={maxRange}
yMax={this.state.maxDps}
xLabel={translate('range')}
xUnit={translate('m')}
yLabel={translate('sdps')}
series={this.state.weaponNames}
xMark={this.props.engagementRange}
colors={DAMAGE_DEALT_COLORS}
func={this.state.calcSDpsFunc}
points={200}
code={code}
/>
</span>
);
}
}

View File

@@ -6,7 +6,7 @@ import * as FR from './fr';
import * as IT from './it';
import * as RU from './ru';
import * as PL from './pl';
import d3 from 'd3';
import * as d3 from 'd3';
let fallbackTerms = EN.terms;
@@ -30,48 +30,49 @@ export function getLanguage(langCode) {
}
let currentTerms = lang.terms;
let d3Locale = d3.locale(lang.formats);
let gen = d3Locale.numberFormat('n');
let d3Locale = d3.formatLocale(lang.formats);
let gen = d3Locale.format('');
const round = function(x, n) { const ten_n = Math.pow(10,n); return Math.round(x * ten_n) / ten_n; };
if(lang === EN) {
translate = (t) => { return currentTerms[t] || t; };
translate = (t, x) => { return currentTerms[t + '_' + x] || currentTerms[t] || t; };
} else {
translate = (t) => { return currentTerms[t] || fallbackTerms[t] || t; };
translate = (t, x) => { return currentTerms[t + '_' + x] || currentTerms[t] || fallbackTerms[t + '_' + x] || fallbackTerms[t] || t; };
}
return {
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)
gen, // General number format (.e.g 1,001,001.1234)
int: d3Locale.format(',.0f'), // Fixed to 0 decimal places (.e.g 1,001)
f1: d3Locale.format(',.1f'), // Fixed to 1 decimal place (.e.g 1,001.1)
f2: d3Locale.format(',.2f'), // Fixed to 2 decimal places (.e.g 1,001.10)
s2: d3Locale.format('.2s'), // SI Format to 2 decimal places (.e.g 1.1k)
pct: d3Locale.format('.2%'), // % to 2 decimal places (.e.g 5.40%)
pct1: d3Locale.format('.1%'), // % to 1 decimal places (.e.g 5.4%)
r1: d3Locale.format('.1r'), // Rounded to 1 significant number (.e.g 512 => 500, 4.122 => 4)
r2: d3Locale.format('.2r'), // Rounded to 2 significant numbers (.e.g 512 => 510, 4.122 => 4.1)
rPct: d3Locale.format('.0%'), // % to 0 decimal places (.e.g 5%)
round1: (d) => gen(round(d, 1)), // Round to 0-1 decimal places (e.g. 5.1, 4)
round: (d) => gen(round(d, 2)), // Rounded to 0-2 decimal places (.e.g 5.12, 4.1)
time: (d) => (d < 0 ? '-' : '') + Math.floor(Math.abs(d) / 60) + ':' + ('00' + Math.floor(Math.abs(d) % 60)).substr(-2, 2)
},
translate,
units: {
CR: <u> {translate('CR')}</u>, // Credits
kg: <u> {translate('kg')}</u>, // Kilograms
kgs: <u> {translate('kg/s')}</u>, // Kilograms per second
km: <u> {translate('km')}</u>, // Kilometers
Ls: <u> {translate('Ls')}</u>, // Light Seconds
LY: <u> {translate('LY')}</u>, // Light Years
MJ: <u> {translate('MJ')}</u>, // Mega Joules
'm/s': <u> {translate('m/s')}</u>, // Meters per second
'°/s': <u> {translate('°/s')}</u>, // Degrees per second
MW: <u> {translate('MW')}</u>, // Mega Watts (same as Mega Joules per second)
CR: <u>{translate('CR')}</u>, // Credits
kg: <u>{translate('kg')}</u>, // Kilograms
kgs: <u>{translate('kg/s')}</u>, // Kilograms per second
km: <u>{translate('km')}</u>, // Kilometers
Ls: <u>{translate('Ls')}</u>, // Light Seconds
LY: <u>{translate('LY')}</u>, // Light Years
MJ: <u>{translate('MJ')}</u>, // Mega Joules
'm/s': <u>{translate('m/s')}</u>, // Meters per second
'°/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
T: <u>{translate('T')}</u>, // Metric Tons
}
};
}

View File

@@ -1,166 +1,619 @@
export const formats = {
decimal: ',',
thousands: '.',
grouping: [3],
currency: ['', ' €'],
dateTime: '%A, der %e. %B %Y, %X',
date: '%d.%m.%Y',
time: '%H:%M:%S',
periods: ['AM', 'PM'], // unused
days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
shortDays: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
shortMonths: ['Jan', 'Feb', 'Mrz', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez']
};
export const terms = {
// Phrases
PHRASE_BACKUP_DESC: 'Export aller Coriolis-Daten, um sie zu sichern oder um sie zu einem anderen Browser/Gerät zu übertragen.', // Backup of all Coriolis data to save or transfer to another browser/device
PHRASE_CONFIRMATION: 'Sind Sie sicher?', // Are You Sure?
PHRASE_EXPORT_DESC: 'Ein detaillierter JSON-Export Ihrer Konfiguration für die Verwendung in anderen Websites und Tools', // A detailed JSON export of your build for use in other sites and tools
PHRASE_FASTEST_RANGE: 'aufeinanderfolgende maximale Reichweite/Sprünge', // Consecutive max range jumps
PHRASE_IMPORT: 'JSON hier einfügen oder importieren', // Paste JSON or import here
PHRASE_LADEN: 'Schiffsmasse + Treibstoff + Fracht', // Ship Mass + Fuel + Cargo
PHRASE_NO_BUILDS: 'Keine Konfigurationen zum Vergleich ausgewählt!', // No builds added to comparison!
PHRASE_NO_RETROCH: 'Keine Umrüständerungen', // No Retrofitting changes
PHRASE_SELECT_BUILDS: 'Ausstattung zum Vergleich auswählen', // Select Builds to Compare
PHRASE_SG_RECHARGE: 'Zeit von 50% bis 100% der Ladung', // Time from 50% to 100% Charge
PHRASE_SG_RECOVER: 'Erneuerung (zu 50%) nach Zusammenbruch', // Recovery (to 50%) after collapse
PHRASE_UNLADEN: 'Schiffsmasse ohne Treibstoff und Fracht', // Ship Mass excluding Fuel and Cargo
PHRASE_UPDATE_RDY: 'Update verfügbar! Klicken zum Aktualisieren', // Update Available! Click to Refresh
// Units / Metrics
LY: 'Lj', // Light Years
T: 't', // Tons (Metric Ton - 1000kg)
// Sizes
S: 'K', // Small Hardpoint (single Character)
L: 'G', // Large Hardpoint size (single character)
H: 'R', // Huge Hardpoint size (single character)
U: 'W', // Utility Hardpoint size (single character) - Kill warrant scanner, etc
small: 'klein', // Small ship size
medium: 'mittel', // Medium ship size
large: 'groß', // Large Ship Size
// Terms
about: 'über', // Link to about page / about Coriolis.io
action: 'Aktion',
added: 'hinzugefügt',
ammo: 'Munition', // Ammunition
armour: 'Panzerung',
available: 'verfügbar', // Available options
backup: 'Sicherungsdatei',
base: 'Basis', // Base speed, boost, etc - Base ship stats
bays: 'Lagerraum',
bins: 'Behälter', // Number of Mining Refinery bins
build: 'Ausstattung', // Shorthand for the build/configuration/design name
'build name': 'Ausstattungsname', // Ship build/configuration/design name
builds: 'Ausstattungen', // Ship build/configuration/design names
buy: 'kaufen',
cancel: 'abbrechen',
cargo: 'Fracht',
cells: 'Zellen', // Number of cells in a shield cell bank
close: 'schließen',
compare: 'vergleichen',
'compare all': 'alles vergleichen',
comparison: 'Vergleich',
comparisons: 'Vergleiche',
cost: 'Preis', // Cost / price of a module or price of a ship
costs: 'Kosten', // Costs / prices of a modules or prices of ships
create: 'erstellen',
'create new': 'neu erstellen',
credits: 'Credits',
damage: 'Schaden',
'damage per second': 'Schaden pro Sekunde',
delete: 'löschen',
'delete all': 'alles löschen',
dep: 'ausg', // Weapons/Hardpoints Deployed abbreviation
deployed: 'ausgefahren', // Weapons/Hardpoints Deployed
'detailed export': 'detailierter Export',
disabled: 'deaktiviert',
discount: 'Rabatt',
'edit data': 'bearbeiten',
efficiency: 'Effizienz', // Power Plant efficiency
empty: 'leer',
'empty all': 'alles entfernen',
ENG: 'ANT', // Abbreviation - Engine recharge rate for power distributor
'Enter Name': 'Namen eingeben',
Explorer: 'Forscher',
export: 'Export',
'fastest range': 'maximale Reichweite', // Fastet totaljump range - sum of succesive jumps
forum: 'Forum',
fuel: 'Treibstoff',
'fuel level': 'Tankfüllstand', // Percent of fuel (T) in the tank
'full tank': 'Tank voll',
hardpoints: 'Waffenaufhängungen',
hull: 'Rumpf', // Ships hull
import: 'importieren',
insurance: 'Versicherung',
'internal compartments': 'Innenbereich',
jump: 'Sprung', // Single jump range
'jump range': 'Sprungreichweite',
jumps: 'Sprünge',
laden: 'beladen',
language: 'Sprache',
maneuverability: 'Manövrierbarkeit',
manufacturer: 'Hersteller',
mass: 'Masse',
'mass lock factor': 'Massensperrefaktor',
'max mass': 'maximale Masse',
MLF: 'MSF', // Mass Lock Factor Abbreviation
module: 'Modul',
modules: 'Module',
'net cost': 'Nettokosten',
no: 'Nein',
'none created': 'nicht erstellt',
ok: 'OK',
'optimal mass': 'optimale Masse', // Lowest weight / best weight for jump distance, etc
optimize: 'optimieren',
pen: 'Durchdr.', // Armour peneration abbreviation
permalink: 'Permanentlink',
power: 'Energie', // Power = Energy / second. Power generated by the power plant, or power consumed (MW / Mega Watts). Used in the power plant section
pri: 'Prio', // Priority abbreviation for power management
proceed: 'fortfahren',
qty: 'Menge', // Quantity abbreviation
range: 'Reichweite',
rate: 'Rate',
recharge: 'aufladen', // Shield Recharge time from 50% -> 100%
recovery: 'Erneuerung', // Shield recovery time (after losing shields/turning on -> 50%)
'refuel time': 'Auftankzeit', // Time to refuel the tank when scooping
reload: 'aktualisieren', // Reload weapon/hardpoint
'reload costs': 'Nachladekosten',
rename: 'umbenennen',
repair: 'reparieren',
reset: 'zurücksetzen',
ret: 'eing', // Retracted abbreviation
retracted: 'eingefahren', // Weapons/Hardpoints retracted
'retrofit costs': 'Änderungskosten', // The cost difference when upgrading / downgrading a component
'retrofit from': 'nachrüsten von', // Retrofit from Build A against build B
ROF: 'Kad', // Rate of Fire abbreviation
roles: 'Rollen', // Commander/Ship build roles - e.g. Trader, Bounty-Hunter, Explorer, etc
save: 'speichern',
sell: 'verkaufen',
settings: 'Einstellungen', // Coriolis application settings
shields: 'Schilde',
ship: 'Schiff',
ships: 'Schiffe',
shortened: 'gekürzt', // Standard/Stock build of a ship when purchased new
size: 'Größe',
skip: 'überspringen', // Skip past something / ignore it
speed: 'Geschwindigkeit',
standard: 'Grundausstattung', // Standard / Common modules (FSD, power plant, life support, etc)
Stock: 'Standard', // Thermal-load abbreviation
strength: 'Stärke', // Strength in reference to Shield Strength
subtotal: 'Zwischensumme',
time: 'Dauer', // time it takes to complete something
tooltips: 'Tooltips', // Tooltips setting - show/hide
total: 'gesamt',
'total range': 'Gesamtbereich',
Trader: 'Händler', // Trader role
type: 'Typ',
'unit cost': 'Kosten pro Einheit',
unladen: 'unbeladen', // No cargo or fuel
'utility mounts': 'Werkzeug-Steckplätze',
WEP: 'WAF', // Abbreviation - Weapon recharge rate for power distributor
yes: 'ja'
};
export const formats = {
decimal: ',',
thousands: '.',
grouping: [3],
currency: ['', ' €'],
dateTime: '%A, der %e. %B %Y, %X',
date: '%d.%m.%Y',
time: '%H:%M:%S',
periods: ['AM', 'PM'], // unused
days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
shortDays: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
shortMonths: ['Jan', 'Feb', 'Mrz', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez']
};
export const terms = {
PHRASE_ALT_ALL: 'Alt + Klick um alle Plätze zu füllen',
PHRASE_BACKUP_DESC: 'Export aller Coriolis-Daten, um sie zu sichern oder um sie zu einem anderen Browser/Gerät zu übertragen.',
PHRASE_CONFIRMATION: 'Sind Sie sicher?',
PHRASE_EXPORT_DESC: 'Ein detaillierter JSON-Export Ihrer Konfiguration für die Verwendung in anderen Websites und Tools',
PHRASE_FASTEST_RANGE: 'aufeinanderfolgende maximale Reichweite/Sprünge',
PHRASE_IMPORT: 'JSON hier einfügen oder importieren',
PHRASE_LADEN: 'Schiffsmasse + Treibstoff + Fracht',
PHRASE_NO_BUILDS: 'Keine Konfigurationen zum Vergleich ausgewählt!',
PHRASE_NO_RETROCH: 'Keine Umrüständerungen',
PHRASE_SELECT_BUILDS: 'Ausstattung zum Vergleich auswählen',
PHRASE_SG_RECHARGE: 'Zeit von 50% bis 100% der Ladung bei vollem SYS Kondensator',
PHRASE_SG_RECOVER: 'Erneuerung (zu 50%) nach Zusammenbruch bei vollem SYS Kondensator',
PHRASE_UNLADEN: 'Schiffsmasse ohne Treibstoff und Fracht',
PHRASE_UPDATE_RDY: 'Update verfügbar! Klicken zum Aktualisieren',
PHRASE_ENGAGEMENT_RANGE: 'Die Distanz zwischen deinem Schiff und seinem Ziel',
PHRASE_SELECT_BLUEPRINT: 'Klicken um eine Bauplan auszuwählen',
PHRASE_BLUEPRINT_WORST: 'Schlechteste Primärwerte für diesen Bauplan',
PHRASE_BLUEPRINT_RANDOM: 'Zufällige Auswahl an Primärwerten für diesen Bauplan (Schlechteste <> Beste)',
PHRASE_BLUEPRINT_BEST: 'Beste Primärwerte für diesen Bauplan',
PHRASE_BLUEPRINT_EXTREME: 'Beste Positive Werte und schlechteste Negative Werte für diesen Bauplan',
PHRASE_BLUEPRINT_RESET: 'Entferne alle Modifikationen und experimentelle Effekte',
PHRASE_SELECT_SPECIAL: 'Klicken um einen experimentellen Effekt auszuwählen',
PHRASE_NO_SPECIAL: 'Keine Experimentellen Effekte',
PHRASE_SHOPPING_LIST: 'Stationen die diese Schiffskonfiguration verkaufen',
PHRASE_REFIT_SHOPPING_LIST: 'Stationen die die benötigten Module verkaufen',
PHRASE_TOTAL_EFFECTIVE_SHIELD: 'Gesamtschaden der von jeder Schadensart absorbiert werden kann (Bei Nutzung aller Schildzellen)',
PHRASE_TIME_TO_LOSE_SHIELDS: 'Schilde werden halten für',
PHRASE_TIME_TO_RECOVER_SHIELDS: 'Schilde werden sich erholen in',
PHRASE_TIME_TO_RECHARGE_SHIELDS: 'Schilde werden sich wieder aufgeladen haben in',
PHRASE_SHIELD_SOURCES: 'Aufschlüsselung der Schildenergiezusammensetzung',
PHRASE_EFFECTIVE_SHIELD: 'Effektive Schildstärke gegen die Unterschiedlichen Schadensarten',
PHRASE_ARMOUR_SOURCES: 'Aufschlüsselung der Hüllenpanzerungszusammensetzung',
PHRASE_EFFECTIVE_ARMOUR: 'Effektive Hüllenpanzerungsstärke gegen die unterschiedlichen Schadensarten',
PHRASE_DAMAGE_TAKEN: '% des rohen Schadens der unterschiedlichen Schadensarten',
PHRASE_TIME_TO_LOSE_ARMOUR: 'Hüllenpanzerung wird halten für',
PHRASE_MODULE_PROTECTION_EXTERNAL: 'Modulpanzerung der Waffenaufhängung',
PHRASE_MODULE_PROTECTION_INTERNAL: 'Modulpanzerung für alle anderen Module',
PHRASE_SHIELD_DAMAGE: 'Aufschlüsselung des kontinuierlichen SPS gegen Schilde',
PHRASE_ARMOUR_DAMAGE: 'Aufschlüsselung des kontinuierlichen SPS gegen Hüllenpanzerung',
PHRASE_TIME_TO_REMOVE_SHIELDS: 'Schilde werden zusammenbrechen in',
TT_TIME_TO_REMOVE_SHIELDS: 'Mit andauerndem Beschuss durch alle Waffen',
PHRASE_TIME_TO_REMOVE_ARMOUR: 'Hüllenpanzerung wird brechen in',
TT_TIME_TO_REMOVE_ARMOUR: 'Mit andauerndem Beschuss durch alle Waffen',
PHRASE_TIME_TO_DRAIN_WEP: 'Leert WAF Energie in',
TT_TIME_TO_DRAIN_WEP: 'Dauer um WAF Energie aufzubrauchen wenn alle Waffen gefeuert werden',
TT_TIME_TO_LOSE_SHIELDS: 'Gegen andauernden Beschuss durch alle Waffen des Gegners',
TT_TIME_TO_LOSE_ARMOUR: 'Gegen andauernden Beschuss durch alle Waffen des Gegners',
TT_MODULE_ARMOUR: 'ModulPanzerung für den Schutz interner Subsysteme (Module)',
TT_MODULE_PROTECTION_EXTERNAL: 'Prozensatz des Schadens der von den Waffenaufhängungen zu den Modulverstärkungen umgeleitet wird',
TT_MODULE_PROTECTION_INTERNAL: 'Prozensatz des Schadens der von den Subsystem (außer Waffen) zu den Modulverstärkungen umgeleitet wird',
TT_EFFECTIVE_SDPS_SHIELDS: 'Effektiver SPS solange die Waffenenergie nicht aufgebraucht wurde',
TT_EFFECTIVENESS_SHIELDS: 'Effektivität im Vergleich zu einem Ziel ohne Widerstände mit 0 PIPS in SYS bei 0m',
TT_EFFECTIVE_SDPS_ARMOUR: 'Effektiver kontinuierlicher Schaden solange der WAF Kondensator nicht aufgebraucht wurde',
TT_EFFECTIVENESS_ARMOUR: 'Effektivität im Vergleich zu einem Ziel ohne Widerstände bei 0m',
PHRASE_EFFECTIVE_SDPS_SHIELDS: 'Effektiver kontinuierlicher SPS gegen Schilde',
PHRASE_EFFECTIVE_SDPS_ARMOUR: 'Effektiver kontinuierlicher SPS gegen Hüllenpanzerung',
TT_SUMMARY_SPEED: 'Mit vollem Tank und 4 PIPS in WAF',
TT_SUMMARY_SPEED_NONFUNCTIONAL: 'Schubdüsen deaktiviert oder maximale Masse überschritten',
TT_SUMMARY_BOOST: 'Mit vollem Tank und 4 PIPS in ANT',
TT_SUMMARY_BOOST_NONFUNCTIONAL: 'Energieverteiler kann nicht genügend Energie für den Boost liefern',
TT_SUMMARY_SHIELDS: 'Rohe Schildstärke, inklusive Schildverstärker',
TT_SUMMARY_SHIELDS_NONFUNCTIONAL: 'Keine Schildgenerator oder Schilde deaktiviert',
TT_SUMMARY_INTEGRITY: 'Schiffsintegrität, einschließlich Hüllenpanzerung und Rumpfhüllenverstärkung',
TT_SUMMARY_HULL_MASS: 'Hüllenmasse, bevor jegliche Module installiert wurde',
TT_SUMMARY_UNLADEN_MASS: 'llenmasse ohne Ladung und Treibstoff',
TT_SUMMARY_LADEN_MASS: 'Hüllenmasse, einschließlich Treibstoff und Ladung',
TT_SUMMARY_DPS: 'Schaden pro Sekunde wenn alle Waffen feuern',
TT_SUMMARY_EPS: 'WAF Kondensator Verbrauch pro Sekunde wenn alle Waffen feuern',
TT_SUMMARY_TTD: 'Zeit um den WAF Kondensator aufzubrauchen wenn alle Waffen feuern und 4 PIPS auf dem WAF Kondesator',
TT_SUMMARY_MAX_SINGLE_JUMP: 'Weitest mögliche Sprungreichweite ohne Ladung und nur genügend Treibstoff für den Sprung selbst',
TT_SUMMARY_UNLADEN_SINGLE_JUMP: 'Weitest mögliche Sprungreichweite ohne Ladung und einem vollen Tank',
TT_SUMMARY_LADEN_SINGLE_JUMP: 'Weitest mögliche Sprungreichweite mit voller Ladung und einem vollen Tank',
TT_SUMMARY_UNLADEN_TOTAL_JUMP: 'Weitest mögliche Sprungreichweite ohne Ladung, einem vollen Tank und der weitest möglichen Sprungreichweite bei jedem Sprung',
TT_SUMMARY_LADEN_TOTAL_JUMP: 'Weitest mögliche Sprungreichweite mit maximaler Ladung, einem vollen Tank und der weitest möglichen Sprungreichweite bei jedem Sprung',
HELP_MODIFICATIONS_MENU: 'Klicke auf eine Zahl um einen neuen Wert einzutragen oder bewege den Regler',
// Other languages fallback to these values
// Only Translate to other languages if the name is different in-game
am: 'Automatische Feldwartung',
bh: 'Hüllenpanzerung',
bl: 'Strahlenlaser',
bsg: 'Bizellengenerator',
c: 'Kanone',
cc: 'Sammeldrohnensteuerung',
ch: 'Düppelwerfer',
cr: 'Laderaum',
cs: 'Ladungssensor',
dc: 'Landecomputer',
ec: 'Elektronische Gegenmaßnahme',
fc: ' Fragmentkanone',
fh: 'Jägerhangar',
fi: 'Frameshift Unterbrecher',
fs: 'Treibstoffsammler',
fsd: 'Frameshiftantrieb',
ft: 'Treibstofftank',
fx: 'Treibstoffdrohnencontroller',
hb: 'Ladelukenbrecherdrohnencontroller',
hr: 'Rumpfhüllenverstärkung',
hs: 'Kühlkörperwerfer',
kw: 'Tötungsbefehlscanner',
ls: 'Lebenserhaltung',
mc: 'Mehrzweckgeschütz ',
ml: 'Erzabbaulaser',
mr: 'Raketengestell',
mrp: 'Modulverstärkung',
nl: 'Minenwerfer',
pa: 'Plasmabeschleuniger',
pas: 'Planetare Annäherungseinheit',
pc: 'Erzsuchersteuerung',
pce: 'Touristen Passagierkabine',
pci: 'Business Klasse Passagierkabine',
pcm: 'Erste Klasse Passagierkabine',
pcq: 'Luxus Passagierkabine',
pd: 'Energieverteiler',
pl: 'Pulslaser',
po: 'Punktverteidigung',
pp: 'Kraftwerk',
psg: 'Prismatischer Schildgenerator',
pv: 'Planetarer Fahrzeughangar',
rf: 'Raffinerie',
rg: 'Schienenkanone',
s: 'Sensoren',
sb: 'Schildverstärker',
sc: 'Himmelskörperscanner',
scb: 'Schildzellenbatterie',
sg: 'Schildgenerator',
ss: 'Oberflächensensor',
t: 'Schubdüsen',
tp: 'Torpedopylone',
ul: 'Salvenlaser',
ws: 'Frameshiftwolkenscanner',
hrd: 'Hüllenhärte',
// Items on the outfitting page
// Notification of restricted slot
emptyrestricted: 'leer (eingeschränkt)',
'damage dealt to': 'Schaden gegen',
'damage received from': 'Schaden durch',
'against shields': 'Gegen Schilde',
'against hull': 'Gegen Hülle',
'total effective shield': 'Effektiver Schild (kombiniert)',
// 'ammo' was overloaded for outfitting page and modul info, so changed to ammunition for outfitting page
ammunition: 'Munition',
// Unit for seconds
secs: 's',
rebuildsperbay: 'Jäger pro Stellpaltz',
// Blueprint rolls
worst: 'Schlecht',
average: 'Durchschnitt',
random: 'Zufall',
best: 'Sehr gut',
extreme: 'Extrem',
reset: 'Zurücksetzen',
// Weapon, offence, defence and movement
dpe: 'Damage per MJ of energy',
dps: 'Schaden pro Sekunde',
sdps: 'Kontinuierlicher Schaden pro Sekunde',
dpssdps: 'Schaden pro Sekunde (kontinuierlicher Schaden pro Sekunde)',
eps: 'Energie pro Sekunde',
epsseps: 'Energie pro Sekunde (kontinuierliche Energie pro Sekunde)',
hps: 'Hitze pro Sekunde',
hpsshps: 'Hitze pro Sekunde (kontinuierliche Hitze pro Sekunde)',
'damage by': 'Schaden von',
'damage from': 'Schaden von',
'shield cells': 'Schildbatterien',
'recovery': 'Erholung',
'recharge': 'Auflaung',
'engine pips': 'Schubpriorität',
'4b': '4 PIPS und Boost',
'speed': 'Tempo',
'pitch': 'Kippen',
'roll': 'Rollen',
'yaw': 'Gieren',
'internal protection': 'Interner Schutz',
'external protection': 'Externer Schutz',
'engagement range': 'Gefechtsreichweite',
'total': 'Insg.',
// Modifications
ammo: 'Maximale Munition',
boot: 'Startzeit',
brokenregen: 'Regenrationsrate (Gebrochene Schilde)',
burst: 'Salve',
burstrof: 'Salven Feuerrate',
clip: 'Munnitionsmagazin',
damage: 'Schaden',
distdraw: 'Energieverteilerverbrauch',
duration: 'Dauer',
eff: 'Effizienz',
engcap: 'Antriebskapazität',
engrate: 'Antrieb Ladungsrate',
explres: 'Explosionswiderstand',
facinglimit: 'Facing limit',
hullboost: 'Hüllenboost',
hullreinforcement: 'Hüllenverstärkung',
integrity: 'Integrität',
jitter: 'Schwankungsbreite',
kinres: 'Kinetischer Widerstand',
maxfuel: 'Maximaler Treibstoff pro Sprung',
mass: 'Masse',
optmass: 'Optimale Masse',
optmul: 'Optimalmultiplikator',
pgen: 'Energiegewinnung',
piercing: 'Durchdringung',
power: 'Energieverbrauch',
protection: 'Schutz',
range: 'Reichweite',
ranget: 'Reichweite/s', // Range in time (for FSD interdictor)
regen: 'Wiederaufladungsrate',
reload: 'Wiederaufladung',
rof: 'Feuerrate',
angle: 'Abtastwinkel',
scanrate: 'Abtastrate',
scantime: 'Abtastzeit',
shield: 'Schild',
shieldboost: 'Schildverstärkung',
shieldreinforcement: 'Schildverstärkung',
shotspeed: 'Schussgeschwidndigkeit',
spinup: 'Aufwärmphase',
syscap: 'Systemkapazität',
sysrate: 'System Ladungsrate',
thermload: 'Thermische Last',
thermres: 'Thermischer Widerstand',
wepcap: 'Waffenkapazität',
weprate: 'Waffen Ladungsrate',
// Shield generators use a different terminology
minmass_sg: 'Minimale Hüllenmasse',
optmass_sg: 'Optimale Hüllenmasse',
maxmass_sg: 'Maximum Hüllenmasse',
minmul_sg: 'Minimale Stärke',
optmul_sg: 'Optimale Stärke',
maxmul_sg: 'Maximale Stärke',
minmass_psg: 'Minimale Hüllenmasse',
optmass_psg: 'Optimale Hüllenmasse',
maxmass_psg: 'Maximum Hüllenmasse',
minmul_psg: 'Minimale Stärke',
optmul_psg: 'Optimale Stärke',
maxmul_psg: 'Maximale Stärke',
minmass_bsg: 'Minimale Hüllenmasse',
optmass_bsg: 'Optimale Hüllenmasse',
maxmass_bsg: 'Maximum Hüllenmasse',
minmul_bsg: 'Minimale Stärke',
optmul_bsg: 'Optimale Stärke',
maxmul_bsg: 'Maximale Stärke',
range_s: 'Typische Emissionsreichweite',
// Damage types
absolute: 'Insgesamt',
explosive: 'Explosiv',
kinetic: 'Kinetisch',
thermal: 'Thermisch',
// Shield sources
generator: 'Generator',
boosters: 'Verstärker',
cells: 'Batterien',
// Armour sources
bulkheads: 'Hüllenpanzerung',
reinforcement: 'Hüllenverstärkung',
// Optional module groups (only these, that are not in the list with the short terms)
'hangars': 'Hangars',
'limpet controllers': 'Drohnensteuerung',
'passenger cabins': 'Passagierkabinen',
'structural reinforcement': 'Strukturverstärkungen',
// Hardpoint module groups
'lasers': 'Laser',
'projectiles': 'Projektilwaffen',
'ordnance': 'Artillerie',
// Armour modules
'Lightweight Alloy': 'leichte Legierung',
'Reinforced Alloy': 'verstärkte Legierung',
'Military Grade Composite': 'Militär-Komposit',
'Mirrored Surface Composite': 'Gespiegelte-Oberfläche-Komposit',
'Reactive Surface Composite': 'Reaktive-Oberfläche-Komposit',
// Scanner modules
'scanners': 'Scanner',
'Basic Discovery Scanner': 'Aufklärungsscanner (einf.)',
'Advanced Discovery Scanner': 'Aufklärungsscanner (fortgeschr.)',
'Detailed Surface Scanner': 'Detail-Oberflächenscanner',
'Intermedia Discovery Scanner': 'Intermedia Discovery Scanner',
// Docking modules
'Standard Docking Computer': 'Standard-Landecomputer',
// Point defence modules
'Point Defence': 'Punktverteidigung',
// Chaff launcher modules
'Chaff Launcher': 'Düppel-Werfer',
// Heat sink launcher modules
'Heat Sink Launcher': 'Kühlkörperwerfer',
// Panel headings and subheadings
'power and costs': 'Energie und Kosten',
'costs': 'Kosten',
'retrofit costs': 'Umrüstkosten',
'reload costs': 'Nachladekosten',
'profiles': 'Profile',
'engine profile': 'Antriebsprofil',
'fsd profile': 'Frameshit Antriebsprofil',
'movement profile': 'Bewegungsprofil',
'damage to opponent\'s shields': 'Gegnerischer Schildschaden',
'damage to opponent\'s hull': 'Gegnerischer Hüllenschaden',
'offence': 'Offensiv',
'defence': 'Defensiv',
'shield metrics': 'Schildwerte',
'raw shield strength': 'Pure Schildstärke',
'shield sources': 'Schildzusammensetzung',
'damage taken': 'Erhaltener Schaden',
'effective shield': 'Effektiver Schildwert',
'armour metrics': 'Panzerungswerte',
'raw armour strength': 'Pure Panzerungsstärke',
'armour sources': 'Panzerungszusammensetzung',
'raw module armour': 'Pure Modulpanzerung',
'effective armour': 'Effektive Panzerung',
'offence metrics': 'Offensivwerte',
'defence metrics': 'Defensivwerte',
// internal module panel header
'Maximize Jump Range': 'Sprungreichweite maximieren',
'roles': 'Rollen',
'Multi-purpose': 'Allrounder',
'Combat': 'Kampf',
'Trader': 'Handel',
'Shielded Trader': 'Handel (mit Schild)',
'Explorer': 'Entdecker',
'Planetary Explorer': 'Planetenentdecker',
'Miner': 'Erzabbau',
'Shielded Miner': 'Erzabbau (mit Schild)',
'Racer': 'Geschwindigkeit',
// Misc items
'fuel carried': 'geladener Treibstoff',
'cargo carried': 'geladene Fracht',
'ship control': 'Energieverteilung',
'opponent': 'Gegner',
'opponent\'s shields': 'Gegnerische Schilde',
'opponent\'s armour': 'Gegenerische Panzerung',
'shield damage sources': 'Schadensquellen (Schild)',
'armour damage sources': 'Schadensquellen (Panzerung)',
'never': 'Niemals',
'stock': 'Standard',
'boost': 'Boost',
'ship': 'Schiff',
'laden': 'Beladen',
'unladen': 'Leer',
'jump range': 'Sprungreichweite',
'total laden': 'gesamt beladen',
'total unladen': 'gesamt leer',
'cargo': 'Fracht',
'hull': 'Hülle',
'Enter Name': 'Name eingeben',
'fuel': 'Tank',
// Items on the ship list page
// Ship list
'manufacturer': 'Hersteller',
'cost': 'Preis',
'size': 'Größe',
'small': 'Klein',
'medium': 'Mittel',
'large': 'Groß',
'agility': 'MNV',
'base': 'Basis',
'armour': 'Panzerung',
'shields': 'Schilde',
'jump': 'Sprung',
'core module classes': 'Basismodulklassen',
'Power Plant': 'Kraftwerk',
th: 'Schubdüsen',
fsd: 'Frameshiftantrieb',
ls: 'Lebenserhaltung',
pd: 'Energieverteiler',
s: 'Sensoren',
'hardpoints': 'Aufhängungen',
'internal compartments': 'Optionale Modulklassen',
// Menu items
'ships': 'Schiffe',
'builds': 'eigene builds',
'compare': 'Vergleiche',
'compare all': 'Alle vergleichen',
'create new': 'Neu anlegen',
'none created': 'Keine angelegt',
'insurance': 'Versicherung',
'discount': 'Nachlass',
'settings': 'Einstellungen',
'module resistances': 'Modulwiderstände',
'comparisons': 'Vergleiche',
'backup': 'Sichern',
'detailed export': 'Detaillierter Export',
'import': 'Importieren',
'delete all': 'Alles löschen',
'about': 'Über',
// Items on the outfitting page
'core internal': 'Intern (basis)',
'optional internal': 'Intern (optional)',
'utility mounts': 'Werkzeug-Steckplätze',
'empty': 'leer',
'empty all': 'Alles leeren',
// Help text
HELP_TEXT: `
<h1>Einführung</h1>
Coriolis ist ein Schiffskonfigurator für Elite:Dangerous. Diese Hilfedatei versorgt dich mit allen Informationen die du benötigst um Coriolis zu benutzen.
<h1>Importiere dein Schiff nach 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>Importiere dein Schiff von 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>
Also, the imported information does not provide any data on the power priority or enabled status of your cargo hatch. Coriolis sets this item to have a power priority of "5" and to be disabled by default. You can change this after import in the Power Management section. </p>
<h2>Importiere dein Schiff von 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>Schlüsselwerte</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 an optimal setup for that particular value (maximum pips in ENG for speed, minimum fuel for jump range, etc.). This means that these values will not be affected by changes to pip settings. Details of the specific setup for each value are listed in the associated tootip.</p>
<h2>Module</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>
Clicking on the headings for each set of modules gives you the ability to either select an overall role for your ship (when clicking the core internal header) or a specific module with which you want to fill all applicable slots (when clicking the other headers). </p>
<h2>Schiffskontrolle</h2>
The ship controls allow you to set your pips, boost, and amount of fuel and cargo that your build carries. The changes made here will effect the information supplied in the subsequent panels, giving you a clearer view of what effect different changing these items will have. </p>
Ship control settings are saved as part of a build. </p>
<h2>Gegner</h2>
The opponet selection allows you to choose your opponent. The opponent can be either a stock build of a ship or one of your own saved builds. You can also set the engagement range between you and your opponent. Your selection here will effect the information supplied in the subsequent panels, specifically the Offence and Defence panels. </p>
Opponent settings are saved as part of a build. </p>
<h2>Energieverbrauch und Kosten Untermenü</h2>
<h3>Energie</h3>
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. Disabled modules will not be included in the build&apos;s statistics, with the exception of Shield Cell Banks as they are usually disabled when not in use and only enabled when required. </p>
<h3>Kosten</h3>
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>Profile</h2>
Profiles provide graphs that show the general performance of modules in your build
<h3>Antriebsprofil</h3>
The engine profile panel provides information about the capabilities of your current thrusters. The graph shows you how the maximum speed alters with the overall mass of your build. The vertical dashed line on the graph shows your current mass. Your engine profile can be altered by obtaining different thrusters or engineering your existing thrusters, and you can increase your maximum speed by adding pips to the ENG capacitor as well as reducing the amount of fuel and cargo you are carrying as well as reducing the overall weight of the build. You can also temporarily increase your speed by hitting the boost button. </p>
<h3>FSA Profil</h3>
The FSD profile panel provides information about the capabilities of your current frame shift drive. The graph shows you how the maximum jump range alters with the overall mass of your build. The vertical dashed line on the graph shows your current maximum single jump range. Your FSD profile can be altered by obtaining a different FSD or engineering your existing FSD, and you can increase your maximum jump range by reducing the amount of fuel and cargo you are carrying as well as reducing the overall weight of the build, </p>
<h3>Bewegungsprofil</h3>
The movement profile panel provides information about the capabilities of your current thrusters with your current overall mass and ENG pips settings. The diagram shows your ability to move and rotate in the different axes:
<dl>
<dt>Geschwindigkeit</dt><dd>The fastest the ship can move, in metres per second</dd>
<dt>Kippen</dt><dd>The fastest the ship can raise or lower its nose, in degrees per second</dd>
<dt>Rollen</dt><dd>The fastest the ship can roll its body, in degrees per second</dd>
<dt>Gieren</dt><dd>The fastest the ship can turn its nose left or right, in degrees per second</dd>
</dl>
Your movement profile can be altered by obtaining different thrusters or engineering your existing thrusters, and you can increase your movement values by adding pips to the ENG capacitor as well as reducing the amount of fuel and cargo you are carrying as well as reducing the overall weight of the build. You can also temporarily increase your movement profile by hitting the boost button. </p>
<h3>Schadensprofil</h3>
The damage profile provides two graphs showing how the the build&apos;s damage to the opponent&apos;s shields and hull change with engagement range. The vertical dashed line on the graph shows your current engagement range. This combines information about the build&apos;s weapons with the opponent&apos;s shields and hull to provide an accurate picture of sustained damage that can be inflicted on the opponent. </p>
<h2>Offensive</h2>
<h3>Zusammenfassung</h3>
The offence summary provides per-weapon information about sustained damage per second inflicted to shields and hull, along with a measure of effectiveness of that weapon. The effectiveness value has a tooltip that provides a breakdown of the effectiveness, and can include reductions or increases due to range, resistance, and either power distributor (for shields) or hardness (for hull). The final effectiveness value is calculated by multiplying these percentages together. </p>
<h3>Offensivwerte</h3>
The offence metrics panel provides information about your offence. </p>
Time to drain is a measure of how quickly your WEP capacitor will drain when firing all weapons. It is affected by the number of pips you have in your WEP capacitor, with more pips resulting in a higher WEP recharge rate and hence a longer time to drain. </p>
The next value is the time it will take you to remove your opponent&apos;s shields. This assumes that you have 100% time on target and that your engagement range stays constant. Note that if your time to remove shields is longer than your time to drain this assumes that you continue firing throughout, inflicting lower damage due to the reduced energy in your WEP capacitor. </p>
The next value is the time it will take you to remove your opponent&apos;s armour. This follows the same logic as the time to remove shields. </p>
<h3>Schadensquellen (Schilde)</h3>
The shield damage sources provides information about the sources of damage to your opponent by damage type. For each applicable type of damage (absolute explosive, kinetic, thermal) a sustained damage per second value is provided. </p>
<h3>Schadensquelle (Hülle)</h3>
The hull damage sources provides information about the sources of damage to your opponent by damage type. For each applicable type of damage (absolute explosive, kinetic, thermal) a sustained damage per second value is provided. </p>
<h2>Defensive</h2>
<h3>Schildwerte</h3>
The shield metrics provides information about your shield defence. </p>
Raw shield strength is the sum of the shield from your generator, boosters and shield cell banks. A tooltip provides a breakdown of these values. </p>
The time the shields will hold for is the time it will take your opponent&apos; to remove your shields. This assumes that they have 100% time on target and that the engagement range stays constant. It also assumes that you fire all of your shield cell banks prior to your shields being lost. </p>
The time the shields will recover in is the time it will take your shields to go from collapsed (0%) to recovered (50%). This is affected by the number of pips you have in your SYS capacitor. </p>
The time the shields will recharge in is the time it will take your shields to go from recovered (50%) to full (100%). This is affected by the number of pips you have in your SYS capacitor. </p>
</h3>Schildstärke (Zusammensetzung)</h3>
This chart provides information about the sources of your shields. For each applicable source of shields (generator, boosters, shield cell banks) a value is provided. </p>
</h3>Schadensauswirkung</h3>
This graph shows how the initial damage from the weapons of each type are reduced before their damage is applied to the shields. For each type of damage (absolute, explosive, kinetic, thermal) a percentage of the initial damage is provided. A tooltip provides a breakdown of these values. </p>
</h3>Effektiver Schildwert</h3>
This graph shows the effective shield for each damage type, found by dividing the raw shield value by the damage taken for that type. </p>
<h3>Panzerungswerte</h3>
The armour metrics provides information about your armour defence. </p>
Raw armour strength is the sum of the armour from your bulkheads and hull reinforcement packages. A tooltip provides a breakdown of these values. </p>
The time the armour will hold for is the time it will take your opponent&apos; to take your armour to 0. This assumes that they have 100% time on target, the engagement range stays constant, and that all damage is dealt to the armour rather than modules. </p>
Raw module armour is the sum of the protection from your module reinforcement packages. </p>
Protection for hardpoints is the amount of protection that your module reinforcement packages provide to hardpoints. This percentage of damage to the hardpoints will be diverted to the module reinforcement packages. </p>
Protection for all other modules is the amount of protection that your module reinforcement packages provide to everything other than hardpoints. This percentage of damage to the modules will be diverted to the module reinforcement packages. </p>
</h3>Hüllenpanzerung (Zusammensetzung)</h3>
This chart provides information about the sources of your armour. For each applicable source of shields (bulkheads, hull reinforcement packages) a value is provided. </p>
</h3>Schadensauswirkung</h3>
This graph shows how the initial damage from the weapons of each type are reduced before their damage is applied to the armour. For each type of damage (absolute, explosive, kinetic, thermal) a percentage of the initial damage is provided. A tooltip provides a breakdown of these values. </p>
</h3>Effektive Hüllenpanzerung</h3>
This graph shows the effective armour for each damage type, found by dividing the raw armour value by the damage taken for that type. </p>
<h1>Tastaturkommandos</h1>
<dl>
<dt>Ctrl-b</dt><dd>Boost an-/auschalten</dd>
<dt>Ctrl-e</dt><dd>open Export Dialog (ausschließlich Ausstattungsseite)</dd>
<dt>Ctrl-h</dt><dd>Öffne den Hilfedialog</dd>
<dt>Ctrl-i</dt><dd>Öffne den Importdialog</dd>
<dt>Ctrl-o</dt><dd>Öffne den Kurzlinkdialog</dd>
<dt>Ctrl-left-arrow</dt><dd>Erhöhe den SYS Kondensator</dd>
<dt>Ctrl-up-arrow</dt><dd>Erhöhe den ANT Kondensator</dd>
<dt>Ctrl-right-arrow</dt><dd>Erhöhe den WAF Kondensator</dd>
<dt>Ctrl-down-arrow</dt><dd>Setze den Energieverteiler zurück</dd>
<dt>Esc</dt><dd>Schließe jeden offenen Dialog</dd>
</dl>
<h1>Glossar</h1>
<dl>
<dt>Absoluter Schaden</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>SPS</dt><dd>Schaden pro Sekunde; 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>Energie pro Sekunde; the amount of energy that a weapon or a ship drains from the weapons capacitor per second when firing</dd>
<dt>HPS</dt><dd>Hitze pro Sekunde; the amount of heat that a weapon or a ship generates per second when firing</dd>
<dt>Effektivität</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>Explosiver Schaden</dt><dd>A type of damage, protected against by explosive resistance</dd>
<dt>Hüllenhärte</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>Schadensabfall</dt><dd>The distance at which a weapons starts to do less damage than its stated DPS</dd>
<dt>Kinetischer Schaden</dt><dd>A type of damage, protected against by kinetic resistance</dd>
<dt>KSPS</dt><dd>Kontinuierlicher Schaden pro Sekunde; 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>KEPS</dt><dd>Kontinuierliche Energie pro Sekunde; 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>KHPS</dt><dd>Kontinuierliche Hitze pro Sekunde; the amount of heat that a weapon or a ship generates per second when firing, taking in to account ammunition reload</dd>
<dt>Thermischer Schaden</dt><dd>A type of damage, protected against by thermal resistance</dd>
</dl>
`,
};

View File

@@ -24,8 +24,8 @@ export const terms = {
PHRASE_NO_BUILDS: 'No builds added to comparison!',
PHRASE_NO_RETROCH: 'No Retrofitting changes',
PHRASE_SELECT_BUILDS: 'Select builds to compare',
PHRASE_SG_RECHARGE: 'Time from 50% to 100% charge',
PHRASE_SG_RECOVER: 'Recovery (to 50%) after collapse',
PHRASE_SG_RECHARGE: 'Time from 50% to 100% charge, assuming full SYS capacitor to start with',
PHRASE_SG_RECOVER: 'Time from 0% to 50% charge, assuming full SYS capacitor to start with',
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',
@@ -33,7 +33,65 @@ export const terms = {
PHRASE_BLUEPRINT_WORST: 'Worst 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_EXTREME: 'Best beneficial and worst detrimental 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',
PHRASE_SHOPPING_LIST: 'Stations that sell this build',
PHRASE_REFIT_SHOPPING_LIST: 'Stations that sell required modules',
PHRASE_TOTAL_EFFECTIVE_SHIELD: 'Total amount of damage that can be taken from each damage type, if using all shield cells',
PHRASE_TIME_TO_LOSE_SHIELDS: 'Shields will hold for',
PHRASE_TIME_TO_RECOVER_SHIELDS: 'Shields will recover in',
PHRASE_TIME_TO_RECHARGE_SHIELDS: 'Shields will recharge in',
PHRASE_SHIELD_SOURCES: 'Breakdown of the supply of shield energy',
PHRASE_EFFECTIVE_SHIELD: 'Effective shield strength against different damage types',
PHRASE_ARMOUR_SOURCES: 'Breakdown of the supply of armour',
PHRASE_EFFECTIVE_ARMOUR: 'Effective armour strength against different damage types',
PHRASE_DAMAGE_TAKEN: '% of raw damage taken for different damage types',
PHRASE_TIME_TO_LOSE_ARMOUR: 'Armour will hold for',
PHRASE_MODULE_PROTECTION_EXTERNAL: 'Protection for hardpoints',
PHRASE_MODULE_PROTECTION_INTERNAL: 'Protection for all other modules',
PHRASE_SHIELD_DAMAGE: 'Breakdown of sources for sustained DPS against shields',
PHRASE_ARMOUR_DAMAGE: 'Breakdown of sources for sustained DPS against armour',
PHRASE_TIME_TO_REMOVE_SHIELDS: 'Will remove shields in',
TT_TIME_TO_REMOVE_SHIELDS: 'With sustained fire by all weapons',
PHRASE_TIME_TO_REMOVE_ARMOUR: 'Will remove armour in',
TT_TIME_TO_REMOVE_ARMOUR: 'With sustained fire by all weapons',
PHRASE_TIME_TO_DRAIN_WEP: 'Will drain WEP in',
TT_TIME_TO_DRAIN_WEP: 'Time to drain WEP capacitor with all weapons firing',
TT_TIME_TO_LOSE_SHIELDS: 'Against sustained fire from all opponent\'s weapons',
TT_TIME_TO_LOSE_ARMOUR: 'Against sustained fire from all opponent\'s weapons',
TT_MODULE_ARMOUR: 'Armour protecting against module damage',
TT_MODULE_PROTECTION_EXTERNAL: 'Percentage of damage diverted from hardpoints to module reinforcement packages',
TT_MODULE_PROTECTION_INTERNAL: 'Percentage of damage diverted from non-hardpoint modules to module reinforcement packages',
TT_EFFECTIVE_SDPS_SHIELDS: 'Actual sustained DPS whilst WEP capacitor is not empty',
TT_EFFECTIVENESS_SHIELDS: 'Effectivness compared to hitting a 0-resistance target with 0 pips to SYS at 0m',
TT_EFFECTIVE_SDPS_ARMOUR: 'Actual sustained DPS whilst WEP capacitor is not empty',
TT_EFFECTIVENESS_ARMOUR: 'Effectivness compared to hitting a 0-resistance target at 0m',
PHRASE_EFFECTIVE_SDPS_SHIELDS: 'SDPS against shields',
PHRASE_EFFECTIVE_SDPS_ARMOUR: 'SDPS against armour',
TT_SUMMARY_SPEED: 'With full fuel tank and 4 pips to ENG',
TT_SUMMARY_SPEED_NONFUNCTIONAL: 'Thrusters powered off or over maximum mass with full fuel and cargo loads',
TT_SUMMARY_BOOST: 'With full fuel tank and 4 pips to ENG',
TT_SUMMARY_BOOST_NONFUNCTIONAL: 'Power distributor not able to supply enough power to boost',
TT_SUMMARY_SHIELDS: 'Raw shield strength, including boosters',
TT_SUMMARY_SHIELDS_NONFUNCTIONAL: 'No shield generator or shield generator powered off',
TT_SUMMARY_INTEGRITY: 'Ship integrity, including bulkheads and hull reinforcement packages',
TT_SUMMARY_HULL_MASS: 'Mass of the hull prior to any modules being installed',
TT_SUMMARY_UNLADEN_MASS: 'Mass of the hull and modules prior to any fuel or cargo',
TT_SUMMARY_LADEN_MASS: 'Mass of the hull and modules with full fuel and cargo',
TT_SUMMARY_DPS: 'Damage per second with all weapons firing',
TT_SUMMARY_EPS: 'WEP capacitor consumed per second with all weapons firing',
TT_SUMMARY_TTD: 'Time to drain WEP capacitor with all weapons firing and 4 pips to WEP',
TT_SUMMARY_MAX_SINGLE_JUMP: 'Farthest possible jump range with no cargo and only enough fuel for the jump itself',
TT_SUMMARY_UNLADEN_SINGLE_JUMP: 'Farthest possible jump range with no cargo and a full fuel tank',
TT_SUMMARY_LADEN_SINGLE_JUMP: 'Farthest possible jump range with full cargo and a full fuel tank',
TT_SUMMARY_UNLADEN_TOTAL_JUMP: 'Farthest possible range with no cargo, a full fuel tank, and jumping as far as possible each time',
TT_SUMMARY_LADEN_TOTAL_JUMP: 'Farthest possible range with full cargo, a full fuel tank, and jumping as far as possible each time',
HELP_MODIFICATIONS_MENU: 'Click on a number to enter a new value, or drag along the bar for small changes',
@@ -84,9 +142,10 @@ export const terms = {
rg: 'Rail Gun',
s: 'Sensors',
sb: 'Shield Booster',
sc: 'Scanner',
sc: 'Stellar Scanners',
scb: 'Shield Cell Bank',
sg: 'Shield Generator',
ss: 'Surface Scanners',
t: 'thrusters',
tp: 'Torpedo Pylon',
ul: 'Burst Laser',
@@ -95,10 +154,12 @@ export const terms = {
// Items on the outfitting page
// Notification of restricted slot
emptyrestricted: 'empty (restricted)',
'damage dealt against': 'Damage dealt against',
'damage received by': 'Damage received by',
'damage dealt to': 'Damage dealt to',
'damage received from': 'Damage received from',
'against shields': 'Against shields',
'against hull': 'Against hull',
'total effective shield': 'Total effective shield',
// 'ammo' was overloaded for outfitting page and modul info, so changed to ammunition for outfitting page
ammunition: 'Ammo',
@@ -109,8 +170,10 @@ export const terms = {
// Blueprint rolls
worst: 'Worst',
average: 'Average',
random: 'Random',
best: 'Best',
extreme: 'Extreme',
reset: 'Reset',
// Weapon, offence, defence and movement
@@ -171,6 +234,9 @@ export const terms = {
regen: 'Regeneration rate',
reload: 'Reload',
rof: 'Rate of fire',
angle: 'Scan angle',
scanrate: 'Scan rate',
scantime: 'Scan time',
shield: 'Shield',
shieldboost: 'Shield boost',
shieldreinforcement: 'Shield reinforcement',
@@ -182,4 +248,261 @@ export const terms = {
thermres: 'Thermal resistance',
wepcap: 'Weapons capacity',
weprate: 'Weapons recharge rate',
// Shield generators use a different terminology
minmass_sg: 'Minimum hull mass',
optmass_sg: 'Optimal hull mass',
maxmass_sg: 'Maximum hull mass',
minmul_sg: 'Minimum strength',
optmul_sg: 'Optimal strength',
maxmul_sg: 'Minimum strength',
minmass_psg: 'Minimum hull mass',
optmass_psg: 'Optimal hull mass',
maxmass_psg: 'Maximum hull mass',
minmul_psg: 'Minimum strength',
optmul_psg: 'Optimal strength',
maxmul_psg: 'Minimum strength',
minmass_bsg: 'Minimum hull mass',
optmass_bsg: 'Optimal hull mass',
maxmass_bsg: 'Maximum hull mass',
minmul_bsg: 'Minimum strength',
optmul_bsg: 'Optimal strength',
maxmul_bsg: 'Minimum strength',
range_s: 'Typical emission range',
// Damage types
absolute: 'Absolute',
explosive: 'Explosive',
kinetic: 'Kinetic',
thermal: 'Thermal',
// Shield sources
generator: 'Generator',
boosters: 'Boosters',
cells: 'Cells',
// Armour sources
bulkheads: 'Bulkheads',
reinforcement: 'Reinforcement',
// Panel headings and subheadings
'power and costs': 'power and costs',
'costs': 'costs',
'retrofit costs': 'retrofit costs',
'reload costs': 'reload costs',
'profiles': 'profiles',
'engine profile': 'engine profile',
'fsd profile': 'fsd profile',
'movement profile': 'movement profile',
'damage to opponent\'s shields': 'damage to opponent\'s shields',
'damage to opponent\'s hull': 'damage to opponent\'s hull',
'offence': 'offence',
'defence': 'defence',
'shield metrics': 'shield metrics',
'raw shield strength': 'raw shield strength',
'shield sources': 'shield sources',
'damage taken': 'damage taken',
'effective shield': 'effective shield',
'armour metrics': 'armour metrics',
'raw armour strength': 'raw armour strength',
'armour sources': 'armour sources',
'raw module armour': 'raw module armour',
'effective armour': 'effective armour',
'offence metrics': 'offence metrics',
'defence metrics': 'defence metrics',
// Misc items
'fuel carried': 'fuel carried',
'cargo carried': 'cargo carried',
'ship control': 'ship control',
'opponent': 'opponent',
'opponent\'s shields': 'opponent\'s shields',
'opponent\'s armour': 'opponent\'s armour',
'shield damage sources': 'shield damage sources',
'armour damage sources': 'armour damage sources',
'never': 'never',
'stock': 'stock',
'boost': 'boost',
// 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>
Also, the imported information does not provide any data on the power priority or enabled status of your cargo hatch. Coriolis sets this item to have a power priority of "5" and to be disabled by default. You can change this after import in the Power Management section. </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 an optimal setup for that particular value (maximum pips in ENG for speed, minimum fuel for jump range, etc.). This means that these values will not be affected by changes to pip settings. Details of the specific setup for each value are listed in the associated tootip.</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>
Clicking on the headings for each set of modules gives you the ability to either select an overall role for your ship (when clicking the core internal header) or a specific module with which you want to fill all applicable slots (when clicking the other headers). </p>
<h2>Ship Controls</h2>
The ship controls allow you to set your pips, boost, and amount of fuel and cargo that your build carries. The changes made here will effect the information supplied in the subsequent panels, giving you a clearer view of what effect different changing these items will have. </p>
Ship control settings are saved as part of a build. </p>
<h2>Opponent</h2>
The opponet selection allows you to choose your opponent. The opponent can be either a stock build of a ship or one of your own saved builds. You can also set the engagement range between you and your opponent. Your selection here will effect the information supplied in the subsequent panels, specifically the Offence and Defence panels. </p>
Opponent settings are saved as part of a build. </p>
<h2>Power and Costs Sub-panels</h2>
<h3>Power</h3>
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. Disabled modules will not be included in the build&apos;s statistics, with the exception of Shield Cell Banks as they are usually disabled when not in use and only enabled when required. </p>
<h3>Costs</h3>
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>Profiles</h2>
Profiles provide graphs that show the general performance of modules in your build
<h3>Engine Profile</h3>
The engine profile panel provides information about the capabilities of your current thrusters. The graph shows you how the maximum speed alters with the overall mass of your build. The vertical dashed line on the graph shows your current mass. Your engine profile can be altered by obtaining different thrusters or engineering your existing thrusters, and you can increase your maximum speed by adding pips to the ENG capacitor as well as reducing the amount of fuel and cargo you are carrying as well as reducing the overall weight of the build. You can also temporarily increase your speed by hitting the boost button. </p>
<h3>FSD Profile</h3>
The FSD profile panel provides information about the capabilities of your current frame shift drive. The graph shows you how the maximum jump range alters with the overall mass of your build. The vertical dashed line on the graph shows your current maximum single jump range. Your FSD profile can be altered by obtaining a different FSD or engineering your existing FSD, and you can increase your maximum jump range by reducing the amount of fuel and cargo you are carrying as well as reducing the overall weight of the build, </p>
<h3>Movement Profile</h3>
The movement profile panel provides information about the capabilities of your current thrusters with your current overall mass and ENG pips settings. The diagram shows your ability to move and rotate in the different axes:
<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>
Your movement profile can be altered by obtaining different thrusters or engineering your existing thrusters, and you can increase your movement values by adding pips to the ENG capacitor as well as reducing the amount of fuel and cargo you are carrying as well as reducing the overall weight of the build. You can also temporarily increase your movement profile by hitting the boost button. </p>
<h3>Damage Profile</h3>
The damage profile provides two graphs showing how the the build&apos;s damage to the opponent&apos;s shields and hull change with engagement range. The vertical dashed line on the graph shows your current engagement range. This combines information about the build&apos;s weapons with the opponent&apos;s shields and hull to provide an accurate picture of sustained damage that can be inflicted on the opponent. </p>
<h2>Offence</h2>
<h3>Summary</h3>
The offence summary provides per-weapon information about sustained damage per second inflicted to shields and hull, along with a measure of effectiveness of that weapon. The effectiveness value has a tooltip that provides a breakdown of the effectiveness, and can include reductions or increases due to range, resistance, and either power distributor (for shields) or hardness (for hull). The final effectiveness value is calculated by multiplying these percentages together. </p>
<h3>Offence Metrics</h3>
The offence metrics panel provides information about your offence. </p>
Time to drain is a measure of how quickly your WEP capacitor will drain when firing all weapons. It is affected by the number of pips you have in your WEP capacitor, with more pips resulting in a higher WEP recharge rate and hence a longer time to drain. </p>
The next value is the time it will take you to remove your opponent&apos;s shields. This assumes that you have 100% time on target and that your engagement range stays constant. Note that if your time to remove shields is longer than your time to drain this assumes that you continue firing throughout, inflicting lower damage due to the reduced energy in your WEP capacitor. </p>
The next value is the time it will take you to remove your opponent&apos;s armour. This follows the same logic as the time to remove shields. </p>
<h3>Shield Damage Sources</h3>
The shield damage sources provides information about the sources of damage to your opponent by damage type. For each applicable type of damage (absolute explosive, kinetic, thermal) a sustained damage per second value is provided. </p>
<h3>Hull Damage Sources</h3>
The hull damage sources provides information about the sources of damage to your opponent by damage type. For each applicable type of damage (absolute explosive, kinetic, thermal) a sustained damage per second value is provided. </p>
<h2>Defence</h2>
<h3>Shield Metrics</h3>
The shield metrics provides information about your shield defence. </p>
Raw shield strength is the sum of the shield from your generator, boosters and shield cell banks. A tooltip provides a breakdown of these values. </p>
The time the shields will hold for is the time it will take your opponent&apos; to remove your shields. This assumes that they have 100% time on target and that the engagement range stays constant. It also assumes that you fire all of your shield cell banks prior to your shields being lost. </p>
The time the shields will recover in is the time it will take your shields to go from collapsed (0%) to recovered (50%). This is affected by the number of pips you have in your SYS capacitor. </p>
The time the shields will recharge in is the time it will take your shields to go from recovered (50%) to full (100%). This is affected by the number of pips you have in your SYS capacitor. </p>
</h3>Shield Sources</h3>
This chart provides information about the sources of your shields. For each applicable source of shields (generator, boosters, shield cell banks) a value is provided. </p>
</h3>Damage Taken</h3>
This graph shows how the initial damage from the weapons of each type are reduced before their damage is applied to the shields. For each type of damage (absolute, explosive, kinetic, thermal) a percentage of the initial damage is provided. A tooltip provides a breakdown of these values. </p>
</h3>Effective Shield</h3>
This graph shows the effective shield for each damage type, found by dividing the raw shield value by the damage taken for that type. </p>
<h3>Amour Metrics</h3>
The armour metrics provides information about your armour defence. </p>
Raw armour strength is the sum of the armour from your bulkheads and hull reinforcement packages. A tooltip provides a breakdown of these values. </p>
The time the armour will hold for is the time it will take your opponent&apos; to take your armour to 0. This assumes that they have 100% time on target, the engagement range stays constant, and that all damage is dealt to the armour rather than modules. </p>
Raw module armour is the sum of the protection from your module reinforcement packages. </p>
Protection for hardpoints is the amount of protection that your module reinforcement packages provide to hardpoints. This percentage of damage to the hardpoints will be diverted to the module reinforcement packages. </p>
Protection for all other modules is the amount of protection that your module reinforcement packages provide to everything other than hardpoints. This percentage of damage to the modules will be diverted to the module reinforcement packages. </p>
</h3>Armour Sources</h3>
This chart provides information about the sources of your armour. For each applicable source of shields (bulkheads, hull reinforcement packages) a value is provided. </p>
</h3>Damage Taken</h3>
This graph shows how the initial damage from the weapons of each type are reduced before their damage is applied to the armour. For each type of damage (absolute, explosive, kinetic, thermal) a percentage of the initial damage is provided. A tooltip provides a breakdown of these values. </p>
</h3>Effective Armour</h3>
This graph shows the effective armour for each damage type, found by dividing the raw armour value by the damage taken for that type. </p>
<h1>Keyboard Shortcuts</h1>
<dl>
<dt>Ctrl-b</dt><dd>toggle boost</dd>
<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>Ctrl-left-arrow</dt><dd>increase SYS capacitor</dd>
<dt>Ctrl-up-arrow</dt><dd>increase ENG capacitor</dd>
<dt>Ctrl-right-arrow</dt><dd>increase WEP capacitor</dd>
<dt>Ctrl-down-arrow</dt><dd>reset power distributor</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>
`,
};

View File

@@ -46,7 +46,7 @@ export const terms = {
'cr': 'Compartimento de carga',
'cs': 'Esc\u00e1ner de carga',
'cells': 'celdas',
'Chaff Launcher': 'Lanzador de birutas',
'Chaff Launcher': 'Lanzador de virutas',
'close': 'Cerrar',
'cc': 'Controlador de Drones de Recogida',
'compare': 'Comparar',

View File

@@ -14,20 +14,315 @@ export const formats = {
};
export const terms = {
// Phrases
PHRASE_BACKUP_DESC: 'Сохраните все данные перед переносом в другой браузер или устройство', // Backup of all Coriolis data to save or transfer to another browser/device
PHRASE_CONFIRMATION: 'Вы уверены?', // Are You Sure?
PHRASE_EXPORT_DESC: 'Детальный JSON-экспорт вашей сборки для использования в других местах и инструментах', // A detailed JSON export of your build for use in other sites and tools
PHRASE_FASTEST_RANGE: 'Последовательные прыжки максимальной дальности', // Consecutive max range jumps
PHRASE_IMPORT: 'Для импорта вставьте код в эту форму', // Paste JSON or import here
PHRASE_LADEN: 'Масса корабля с учётом топлива и грузов', // Ship Mass + Fuel + Cargo
PHRASE_NO_BUILDS: 'Нечего сравнивать', // No builds added to comparison!
PHRASE_NO_RETROCH: 'нет ранних версий сборки\\конфигурации', // No Retrofitting changes
PHRASE_SELECT_BUILDS: 'Выберите конфигурацию для сравнения', // Select Builds to Compare
PHRASE_SG_RECHARGE: 'восстановление с 60% до 100% объема щита', // Time from 50% to 100% Charge
PHRASE_SG_RECOVER: 'восстановление [до 60%] после снятия щита', // Recovery (to 50%) after collapse
PHRASE_UNLADEN: 'Масса корабля без учета топлива и грузов', // Ship Mass excluding Fuel and Cargo
PHRASE_UPDATE_RDY: 'Доступно обновление. Нажмите для обновления.', // Update Available! Click to Refresh
PHRASE_ALT_ALL: 'Alt + Нажатие для заполнения всех слотов',
PHRASE_BACKUP_DESC: 'Сохраните все данные перед переносом в другой браузер или устройство',
PHRASE_CONFIRMATION: 'Вы уверены?',
PHRASE_EXPORT_DESC: 'Детальный JSON-экспорт вашей сборки для использования в других местах и инструментах',
PHRASE_FASTEST_RANGE: 'Последовательные прыжки максимальной дальности',
PHRASE_IMPORT: 'Для импорта вставьте код в эту форму',
PHRASE_LADEN: 'Масса корабля с учётом топлива и грузов',
PHRASE_NO_BUILDS: 'Нечего сравнивать',
PHRASE_NO_RETROCH: 'Нет ранних версий сборки',
PHRASE_SELECT_BUILDS: 'Выберите конфигурацию для сравнения',
PHRASE_SG_RECHARGE: 'Восстановление с 50% до 100% объема щита, учитывая полный аккумулятор СИС в начале',
PHRASE_SG_RECOVER: 'Восстановление с 0% до 50% объема щита, учитывая полный аккумулятор СИС в начале',
PHRASE_UNLADEN: 'Масса корабля без учета топлива и грузов',
PHRASE_UPDATE_RDY: 'Доступна новая версия. Нажмите для обновления.',
PHRASE_ENGAGEMENT_RANGE: 'Дистанция между кораблём и целью',
PHRASE_SELECT_BLUEPRINT: 'Нажмите чтобы выбрать чертёж',
PHRASE_BLUEPRINT_WORST: 'Худшие основные значения для чертежа',
PHRASE_BLUEPRINT_RANDOM: 'Случайный выбор между худшими и лучшими значениями для этого чертежа',
PHRASE_BLUEPRINT_BEST: 'Лучшие основные значения для чертежа',
PHRASE_BLUEPRINT_EXTREME: 'Лучшие положительные и худшие отрицательные основные значения для чертежа',
PHRASE_BLUEPRINT_RESET: 'Убрать все изменения и чертёж',
PHRASE_SELECT_SPECIAL: 'Нажмите чтобы выбрать экспериментальный эффект',
PHRASE_NO_SPECIAL: 'Без экспериментального эффекта',
PHRASE_SHOPPING_LIST: 'Станции что продают эту сборку',
PHRASE_REFIT_SHOPPING_LIST: 'Станции что продают необходимые модули',
PHRASE_TOTAL_EFFECTIVE_SHIELD: 'Общий урон что может быть нанесён в каждым типе, если используются все щитонакопители',
PHRASE_TIME_TO_LOSE_SHIELDS: 'Щиты продержатся',
PHRASE_TIME_TO_RECOVER_SHIELDS: 'Щиты восстановятся за',
PHRASE_TIME_TO_RECHARGE_SHIELDS: 'Щиты будут заряжены за',
PHRASE_SHIELD_SOURCES: 'Подробности энергии щита',
PHRASE_EFFECTIVE_SHIELD: 'Эффективная сила щита против разных типов урона',
PHRASE_ARMOUR_SOURCES: 'Подробности состава брони',
PHRASE_EFFECTIVE_ARMOUR: 'Эффективная сила брони против разных типов урона',
PHRASE_DAMAGE_TAKEN: '% общих повреждений полученных в разных типах урона',
PHRASE_TIME_TO_LOSE_ARMOUR: 'Броня продержится',
PHRASE_MODULE_PROTECTION_EXTERNAL: 'Защита гнёзд',
PHRASE_MODULE_PROTECTION_INTERNAL: 'Защита всех остальных модулей',
PHRASE_SHIELD_DAMAGE: 'Подробности источников поддерживаемого ДПС против щитов',
PHRASE_ARMOUR_DAMAGE: 'Подробности источников поддерживаемого ДПС против брони',
PHRASE_TIME_TO_REMOVE_SHIELDS: 'Снимет щиты за',
TT_TIME_TO_REMOVE_SHIELDS: 'Непрерывным огнём из всех орудий',
PHRASE_TIME_TO_REMOVE_ARMOUR: 'Снимет броню за',
TT_TIME_TO_REMOVE_ARMOUR: 'Непрерывным огнём из всех орудий',
PHRASE_TIME_TO_DRAIN_WEP: 'Опустошит ОРУЖ за',
TT_TIME_TO_DRAIN_WEP: 'Время за которое опустошится аккумулятор ОРУЖ при стрельбе из всех орудий',
TT_TIME_TO_LOSE_SHIELDS: 'Против поддерживаемой стрельбы из всех орудий противника',
TT_TIME_TO_LOSE_ARMOUR: 'Против поддерживаемой стрельбы из всех орудий противника',
TT_MODULE_ARMOUR: 'Броня защищаюшае модули от урона',
TT_MODULE_PROTECTION_EXTERNAL: 'Процент урона перенаправленного от гнёзд на наборы для усиления модулей',
TT_MODULE_PROTECTION_INTERNAL: 'Процент урона перенаправленного от модулей вне гнёзд на наборы для усиления модулей',
TT_EFFECTIVE_SDPS_SHIELDS: 'Реальный поддерживаемый ДПС пока аккумулятор ОРУЖ не пуст',
TT_EFFECTIVENESS_SHIELDS: 'Эффективность в сравнении с попаданием по цели с 0-сопротивляемостью без пунктов в СИС на 0 метрах',
TT_EFFECTIVE_SDPS_ARMOUR: 'Реальный поддерживаемый ДПС пока аккумулятор ОРУЖ не пуст',
TT_EFFECTIVENESS_ARMOUR: 'Эффективность в сравнении с попаданием по цели с 0-сопротивляемостью на 0 метрах',
PHRASE_EFFECTIVE_SDPS_SHIELDS: 'ПДПС против щитов',
PHRASE_EFFECTIVE_SDPS_ARMOUR: 'ПДПС против брони',
TT_SUMMARY_SPEED: 'С полным топливным баком и 4 пунктами в ДВИ',
TT_SUMMARY_SPEED_NONFUNCTIONAL: 'маневровые двигатели выключены или превышена максимальная масса с топливом и грузом',
TT_SUMMARY_BOOST: 'С полным топливным баком и 4 пунктами в ДВИ',
TT_SUMMARY_BOOST_NONFUNCTIONAL: 'Распределитель питания не может обеспечить достаточно энергии для форсажа',
TT_SUMMARY_SHIELDS: 'Чистая сила щита, включая усилители',
TT_SUMMARY_SHIELDS_NONFUNCTIONAL: 'Шитогенератор отсутствует или выключен',
TT_SUMMARY_INTEGRITY: 'Целостность корабля, включая переборки и наборы для усиления корпуса',
TT_SUMMARY_HULL_MASS: 'Масса корпуса без каких-либо модулей',
TT_SUMMARY_UNLADEN_MASS: 'Масса корпуса и модулей без топлива и груза',
TT_SUMMARY_LADEN_MASS: 'Масса корпуса и модулей с топливом и грузом',
TT_SUMMARY_DPS: 'Урон в секунду при стрельбе из всех орудий',
TT_SUMMARY_EPS: 'Расход аккумулятора ОРУЖ в секунду при стрельбе из всех орудий',
TT_SUMMARY_TTD: 'Время расхода аккумулятора ОРУЖ при стрельбе из всех орудий и с 4 пунктами в ОРУЖ',
TT_SUMMARY_MAX_SINGLE_JUMP: 'Самый дальний возможный прыжок без груза и с топливом достаточным только на сам прыжок',
TT_SUMMARY_UNLADEN_SINGLE_JUMP: 'Самый дальний возможный прыжок без груза и с полным топливным баком',
TT_SUMMARY_LADEN_SINGLE_JUMP: 'Самый дальний возможный прыжок с полным грузовым отсеком и с полным топливным баком',
TT_SUMMARY_UNLADEN_TOTAL_JUMP: 'Самая дальняя общая дистанция без груза, с полным топливным баком и при прыжках на максимальное расстояние',
TT_SUMMARY_LADEN_TOTAL_JUMP: 'Самая дальняя общая дистанция с полным грузовым отсеком, с полным топливным баком и при прыжках на максимальное расстояние',
HELP_MODIFICATIONS_MENU: 'Ткните на номер чтобы ввести новое значение, или потяните вдоль полосы для малых изменений',
// Other languages fallback to these values
// Only Translate to other languages if the name is different in-game
am: 'Блок Автом. Полевого Ремонта',
bh: 'Переборки',
bl: 'Пучковый Лазер',
bsg: 'Двухпоточный Щитогенератор',
c: 'Орудие',
cc: 'Контроллер магнитного снаряда для сбора',
ch: 'Разбрасыватель дипольных отражателей',
cr: 'Грузовой стеллаж',
cs: 'Сканер содержимого',
dc: 'Стыковочный компьютер',
ec: 'Электр. противодействие',
fc: 'Залповое орудие',
fh: 'Ангар для истребителя',
fi: 'FSD-перехватчик',
fs: 'Топливозаборник',
fsd: 'Рамочно Сместительный двигатель',
ft: 'Топливный бак',
fx: 'Контроллер магнитного снаряда для топлива',
hb: 'Контроллер магнитного снаряда для взлома трюма',
hr: 'Набор для усиления корпуса',
hs: 'Теплоотводная катапульта',
kw: 'Сканер преступников',
ls: 'Система жизнеобеспечения',
mc: 'Многоствольное орудие',
ml: 'Проходочный лазер',
mr: 'Ракетный лоток',
mrp: 'Набор для усиления модуля',
nl: 'Мины',
pa: 'Ускоритель плазмы',
pas: 'Комплект для сближения с планетой',
pc: 'Контроллер магнитного снаряда для геологоразведки',
pce: 'Каюта пассажира эконом-класса',
pci: 'Каюта пассажира бизнес-класса',
pcm: 'Каюта пассажира первого класса',
pcq: 'Каюта пассажира класса люкс',
pd: 'Распределитель питания',
pl: 'Ипмульсный лазер',
po: 'Точечная оборона',
pp: 'Силовая установка',
psg: 'Призматический щитогенератор',
pv: 'Гараж для планетарного транспорта',
rf: 'Устройство переработки',
rg: 'Электромагнитная пушка',
s: 'Сенсоры',
sb: 'Усилитель щита',
sc: 'Сканер обнаружения',
scb: 'Щитонакопитель',
sg: 'Щитогенератор',
ss: 'Сканер Поверхностей',
t: 'Маневровые двигатели',
tp: 'Торпедная стойка',
ul: 'Пульсирующие лазеры',
ws: 'Сканер следа FSD',
// Items on the outfitting page
// Notification of restricted slot
emptyrestricted: 'пусто (ограниченно)',
'damage dealt to': 'Урон нанесён',
'damage received from': 'Урон получен от',
'against shields': 'Против шитов',
'against hull': 'Против корпуса',
'total effective shield': 'Общие эффективные щиты',
// 'ammo' was overloaded for outfitting page and modul info, so changed to ammunition for outfitting page
ammunition: 'Припасы',
// Unit for seconds
secs: 'с',
rebuildsperbay: 'Построек за полосу',
// Blueprint rolls
worst: 'Худшее',
average: 'Среднее',
random: 'Случайное',
best: 'Лучшее',
extreme: 'Экстремальное',
reset: 'Обнулить',
// Weapon, offence, defence and movement
dpe: 'Урон на МДж энергии',
dps: 'Урон в Секунду',
sdps: 'Поддерживаемый урон в секунду',
dpssdps: 'Урон в секунду (поддерживаемый урон в секунду)',
eps: 'Энергия в секунду',
epsseps: 'Энергия в секунду (поддерживаемая энергия в секунду)',
hps: 'Нагрев в секунду',
hpsshps: 'Heat per second (sustained heat per second)',
'damage by': 'Урон',
'damage from': 'Урон от',
'shield cells': 'Щитонакопители',
'recovery': 'Восстановление',
'recharge': 'Перезарядка',
'engine pips': 'Пункты в двигателе',
'4b': '4 пункта и Форсаж',
'speed': 'Скорость',
'pitch': 'Тангаж',
'roll': 'Крен',
'yaw': 'Рыскание',
'internal protection': 'Внутренняя защита',
'external protection': 'Внешняя защита',
'engagement range': 'Боевое расстояние',
'total': 'Общее',
// Modifications
ammo: 'Макс. боекомплект',
boot: 'Время загрузки',
brokenregen: 'Скорость восстановления при пробое',
burst: 'Длина очереди',
burstrof: 'Скорострельность очереди',
clip: 'Боекомплект',
damage: 'Урон',
distdraw: 'Тяга распределителя',
duration: 'Продолжительность',
eff: 'Эффективность',
engcap: 'Ресурс двигателей',
engrate: 'Перезарядка двигателей',
explres: 'Сопротивление взрывам',
facinglimit: 'Ограничение по направлению',
hullboost: 'Увеличение корпуса',
hullreinforcement: 'Укрепление корпуса',
integrity: 'Целостность',
jitter: 'Дрожание',
kinres: 'Сопротивление китетическому урону',
maxfuel: 'Макс. топлива на прыжок',
mass: 'Масса',
optmass: 'Оптимизированная масса',
optmul: 'Оптимальный усилитель',
pgen: 'Мощность',
piercing: 'Бронебойность',
power: 'Энергопотребление',
protection: 'Защита от повреждений',
range: 'Дальность',
ranget: 'Дальность', // Range in time (for FSD interdictor)
regen: 'Скорость восстановления',
reload: 'Время перезарядки',
rof: 'Скорострельность',
angle: 'Угол сканера',
scanrate: 'Скорость сканера',
scantime: 'Время сканирования',
shield: 'Щит',
shieldboost: 'Усиление щитов',
shieldreinforcement: 'Усилитель щита',
shotspeed: 'Скорость выстрела',
spinup: 'Время раскрутки',
syscap: 'Ресурс систем',
sysrate: 'Перезарядка систем',
thermload: 'Тепловая нагрузка',
thermres: 'Сопротивление термическому урону',
wepcap: 'Орудийный ресурс',
weprate: 'Перезарядка оружия',
// Shield generators use a different terminology
minmass_sg: 'Мин. масса корпуса',
optmass_sg: 'Опт. масса корпуса',
maxmass_sg: 'Макс. масса корпуса',
minmul_sg: 'Минимальная прочность',
optmul_sg: 'Оптимальная прочность',
maxmul_sg: 'Максимальная прочность',
minmass_psg: 'Мин. масса корпуса',
optmass_psg: 'Опт. масса корпуса',
maxmass_psg: 'Макс. масса корпуса',
minmul_psg: 'Минимальная прочность',
optmul_psg: 'Оптимальная прочность',
maxmul_psg: 'Максимальная прочность',
minmass_bsg: 'Мин. масса корпуса',
optmass_bsg: 'Опт. масса корпуса',
maxmass_bsg: 'Макс. масса корпуса',
minmul_bsg: 'Минимальная прочность',
optmul_bsg: 'Оптимальная прочность',
maxmul_bsg: 'Максимальная прочность',
range_s: 'Типовой диапозон выброса',
// Damage types
absolute: 'Общий',
explosive: 'Взрывч.',
kinetic: 'Механич.',
thermal: 'Тепл.',
// Shield sources
generator: 'Генератор',
boosters: 'Усилители',
cells: 'Накопители',
// Armour sources
bulkheads: 'Переборки',
reinforcement: 'Усилители',
// Panel headings and subheadings
'power and costs': 'Энергия и стоимость',
'costs': 'Цены',
'retrofit costs': 'Стоимость модификации',
'reload costs': 'Стоимость перезарядки',
'profiles': 'Графики',
'engine profile': 'Двигатели',
'fsd profile': 'FSD',
'movement profile': 'Движение',
'damage to opponent\'s shields': 'Урон щиту противника',
'damage to opponent\'s hull': 'Урон корпусу противника',
'offence': 'Нападение',
'defence': 'Оборона',
'shield metrics': 'Данные щита',
'raw shield strength': 'Чистая мощность щита',
'shield sources': 'Ресурсы щита',
'damage taken': 'Полученный урон',
'effective shield': 'Эффективный щит',
'armour metrics': 'Данные брони',
'raw armour strength': 'Чистая мощность брони',
'armour sources': 'Ресурсы брони',
'raw module armour': 'Чистая броня модулей',
'effective armour': 'Эффективная броня',
'offence metrics': 'Данные нападения',
'defence metrics': 'Данные обороны',
// Misc items
'fuel carried': 'Топливо на борту',
'cargo carried': 'Груз на борту',
'ship control': 'Управление кораблём',
'opponent': 'Противник',
'opponent\'s shields': 'Щит противника',
'opponent\'s armour': 'Броня противника',
'shield damage sources': 'источники урона по щиту',
'armour damage sources': 'источники урона по броне',
'never': 'Никогда',
'stock': 'базовый',
'boost': 'Форсаж',
// Units / Metrics
'/s': '/с', // Per second
@@ -105,7 +400,7 @@ export const terms = {
DPS: 'УВС', // Damage per second abbreviation
efficiency: 'Эффективность', // Power Plant efficiency
empty: 'пусто',
ENG: 'ДВГ', // Abbreviation - Engine recharge rate for power distributor
ENG: 'ДВИ', // Abbreviation - Engine recharge rate for power distributor
export: 'Экспорт',
forum: 'Форум',
fuel: 'Топливо',
@@ -153,12 +448,12 @@ export const terms = {
speed: 'скорость',
standard: 'Стандартный', // Standard / Common modules (FSD, power plant, life support, etc)
Stock: 'Стандартная комплектация', // Thermal-load abbreviation
SYS: 'СИСТЕМЫ', // Abbreviation - System recharge rate for power distributor
SYS: 'СИС', // Abbreviation - System recharge rate for power distributor
time: 'Время', // time it takes to complete something
total: 'Всего',
type: 'Тип',
unladen: 'Пустой', // No cargo or fuel
URL: 'Ссылка', // Link, Uniform Resource Locator
WEP: 'ОРУДИЯ', // Abbreviation - Weapon recharge rate for power distributor
WEP: 'ОРУЖ', // Abbreviation - Weapon recharge rate for power distributor
yes: 'Да'
};

View File

@@ -1,5 +1,4 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
import Page from './Page';
import Router from '../Router';
import cn from 'classnames';
@@ -17,7 +16,7 @@ import ModalImport from '../components/ModalImport';
import { FloppyDisk, Bin, Download, Embed, Rocket, LinkIcon } from '../components/SvgIcons';
import ShortenUrl from '../utils/ShortenUrl';
import { comparisonBBCode } from '../utils/BBCode';
const browser = require('detect-browser');
/**
* Creates a comparator based on the specified predicate
@@ -81,7 +80,9 @@ export default class ComparisonPage extends Page {
newName = name;
for (let shipId in allBuilds) {
for (let buildName in allBuilds[shipId]) {
builds.push(this._createBuild(shipId, buildName, allBuilds[shipId][buildName]));
if (buildName && allBuilds[shipId][buildName]) {
builds.push(this._createBuild(shipId, buildName, allBuilds[shipId][buildName]));
}
}
}
} else {
@@ -226,8 +227,10 @@ export default class ComparisonPage extends Page {
let placeholder = this.placeholder = document.createElement('li');
placeholder.style.width = Math.round(this.dragged.offsetWidth) + 'px';
placeholder.className = 'facet-placeholder';
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', e.currentTarget);
if (!browser || (browser.name !== 'edge' && browser.name !== 'ie')) {
e.dataTransfer.effectAllowed = 'move';
e.dataTransfer.setData('text/html', e.currentTarget);
}
}
/**
@@ -381,7 +384,7 @@ export default class ComparisonPage extends Page {
*/
_updateDimensions() {
this.setState({
chartWidth: findDOMNode(this.refs.chartRef).offsetWidth
chartWidth: this.chartRef.offsetWidth
});
}
@@ -496,9 +499,9 @@ export default class ComparisonPage extends Page {
<ComparisonTable builds={builds} facets={facets} onSort={this._sortShips} predicate={predicate} desc={desc} />
{!builds.length ?
<div className='chart' ref={'chartRef'}>{translate('PHRASE_NO_BUILDS')}</div> :
<div className='chart' ref={node => this.chartRef = node}>{translate('PHRASE_NO_BUILDS')}</div> :
facets.filter((f) => f.active).map((f, i) =>
<div key={f.title} className='chart' ref={ i == 0 ? 'chartRef' : null}>
<div key={f.title} className='chart' ref={ i == 0 ? node => this.chartRef = node : null}>
<h3 className='ptr' onClick={this._sortShips.bind(this, f.props[0])}>{translate(f.title)}</h3>
<BarChart
width={chartWidth}

View File

@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
/**
* Unexpected Error page / block
@@ -6,12 +7,12 @@ import React from 'react';
export default class ErrorDetails extends React.Component {
static contextTypes = {
route: React.PropTypes.object.isRequired,
language: React.PropTypes.object.isRequired
route: PropTypes.object.isRequired,
language: PropTypes.object.isRequired
};
static propTypes = {
error: React.PropTypes.object.isRequired
error: PropTypes.object.isRequired
};
/**
@@ -39,10 +40,16 @@ export default class ErrorDetails extends React.Component {
</div>;
}
const importerror = ed && ed.scriptUrl && ed.scriptUrl.indexOf('/import') != -1;
return <div className='error'>
<h1>Jameson, we have a problem..</h1>
<h1><small>{error.message}</small></h1>
<br/>
{importerror ? <div>If you are attempting to import a ship from EDDI or EDMC and are seeing a 'Z_BUF_ERROR' it means that the URL has not been provided correctly. This is a common problem when using Microsoft Internet Explorer or Microsoft Edge, and you should use another browser instead.</div> : null }
<br/>
<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>
<br/>
{content}
</div>;
}

View File

@@ -1,33 +1,30 @@
import React from 'react';
import { findDOMNode } from 'react-dom';
// import Perf from 'react-addons-perf';
import { Ships } from 'coriolis-data/dist';
import cn from 'classnames';
import Page from './Page';
import Router from '../Router';
import Persist from '../stores/Persist';
import * as Utils from '../utils/UtilityFunctions';
import Ship from '../shipyard/Ship';
import { toDetailedBuild } from '../shipyard/Serializer';
import { outfitURL } from '../utils/UrlGenerators';
import { FloppyDisk, Bin, Switch, Download, Reload, Fuel, LinkIcon } from '../components/SvgIcons';
import { FloppyDisk, Bin, Switch, Download, Reload, LinkIcon, ShoppingIcon } from '../components/SvgIcons';
import LZString from 'lz-string';
import ShipSummaryTable from '../components/ShipSummaryTable';
import StandardSlotSection from '../components/StandardSlotSection';
import HardpointsSlotSection from '../components/HardpointsSlotSection';
import HardpointSlotSection from '../components/HardpointSlotSection';
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 Pips from '../components/Pips';
import Boost from '../components/Boost';
import Fuel from '../components/Fuel';
import Cargo from '../components/Cargo';
import ShipPicker from '../components/ShipPicker';
import EngagementRange from '../components/EngagementRange';
import OutfittingSubpages from '../components/OutfittingSubpages';
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'];
const SPEED_COLORS = ['#0088d2', '#ff8c0d', '#D26D00', '#c06400'];
/**
* Document Title Generator
@@ -36,7 +33,7 @@ const SPEED_COLORS = ['#0088d2', '#ff8c0d', '#D26D00', '#c06400'];
* @return {String} Document title
*/
function getTitle(shipName, buildName) {
return `${shipName}${buildName ? ` - ${buildName}` : ''}`;
return buildName ? buildName : shipName;
}
/**
@@ -51,15 +48,25 @@ export default class OutfittingPage extends Page {
*/
constructor(props, context) {
super(props, context);
this.state = this._initState(context);
// window.Perf = Perf;
this.state = this._initState(props, context);
this._keyDown = this._keyDown.bind(this);
this._exportBuild = this._exportBuild.bind(this);
this._pipsUpdated = this._pipsUpdated.bind(this);
this._boostUpdated = this._boostUpdated.bind(this);
this._cargoUpdated = this._cargoUpdated.bind(this);
this._fuelUpdated = this._fuelUpdated.bind(this);
this._opponentUpdated = this._opponentUpdated.bind(this);
this._engagementRangeUpdated = this._engagementRangeUpdated.bind(this);
}
/**
* [Re]Create initial state from context
* @param {Object} props React component properties
* @param {context} context React component context
* @return {Object} New state object
*/
_initState(context) {
_initState(props, context) {
let params = context.route.params;
let shipId = params.ship;
let code = params.code;
@@ -79,9 +86,10 @@ export default class OutfittingPage extends Page {
ship.buildWith(data.defaults); // Populate with default components
}
let fuelCapacity = ship.fuelCapacity;
this._getTitle = getTitle.bind(this, data.properties.name);
// Obtain ship control from code
const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange } = this._obtainControlFromCode(ship, code);
return {
error: null,
title: this._getTitle(buildName),
@@ -92,11 +100,18 @@ export default class OutfittingPage extends Page {
ship,
code,
savedCode,
fuelCapacity,
fuelLevel: 1,
jumpRangeChartFunc: ship.calcJumpRangeWith.bind(ship, fuelCapacity),
fastestRangeChartFunc: ship.calcFastestRangeWith.bind(ship, fuelCapacity),
speedChartFunc: ship.calcSpeedsWith.bind(ship, fuelCapacity)
sys,
eng,
wep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
opponentSys,
opponentEng,
opponentWep,
engagementRange
};
}
@@ -118,35 +133,216 @@ export default class OutfittingPage extends Page {
this.setState(stateChanges);
}
/**
* Update the control part of the route
*/
_updateRouteOnControlChange() {
const { ship, shipId, buildName } = this.state;
const code = this._fullCode(ship);
this._updateRoute(shipId, buildName, code);
this.setState({ code });
}
/**
* Provide a full code for this ship, including any additions due to the outfitting page
* @param {Object} ship the ship
* @param {number} fuel the fuel carried by the ship (if different from that in state)
* @param {number} cargo the cargo carried by the ship (if different from that in state)
* @returns {string} the code for this ship
*/
_fullCode(ship, fuel, cargo) {
return `${ship.toString()}.${LZString.compressToBase64(this._controlCode(fuel, cargo))}`;
}
/**
* Obtain the control information from the build code
* @param {Object} ship The ship
* @param {string} code The build code
* @returns {Object} The control information
*/
_obtainControlFromCode(ship, code) {
// Defaults
let sys = 2;
let eng = 2;
let wep = 2;
let boost = false;
let fuel = ship.fuelCapacity;
let cargo = ship.cargoCapacity;
let opponent = new Ship('eagle', Ships['eagle'].properties, Ships['eagle'].slots).buildWith(Ships['eagle'].defaults);
let opponentSys = 2;
let opponentEng = 2;
let opponentWep = 2;
let opponentBuild;
let engagementRange = 1000;
// Obtain updates from code, if available
if (code) {
const parts = code.split('.');
if (parts.length >= 5) {
// We have control information in the code
const control = LZString.decompressFromBase64(Utils.fromUrlSafe(parts[4])).split('/');
sys = parseFloat(control[0]);
eng = parseFloat(control[1]);
wep = parseFloat(control[2]);
boost = control[3] == 1 ? true : false;
fuel = parseFloat(control[4]);
cargo = parseInt(control[5]);
if (control[6]) {
const shipId = control[6];
opponent = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots);
if (control[7] && Persist.getBuild(shipId, control[7])) {
// Ship is a particular build
const opponentCode = Persist.getBuild(shipId, control[7]);
opponent.buildFrom(opponentCode);
opponentBuild = control[7];
if (opponentBuild) {
// Obtain opponent's sys/eng/wep pips from their code
const opponentParts = opponentCode.split('.');
if (opponentParts.length >= 5) {
const opponentControl = LZString.decompressFromBase64(Utils.fromUrlSafe(opponentParts[4])).split('/');
opponentSys = parseFloat(opponentControl[0]);
opponentEng = parseFloat(opponentControl[1]);
opponentWep = parseFloat(opponentControl[2]);
}
}
} else {
// Ship is a stock build
opponent.buildWith(Ships[shipId].defaults);
}
}
engagementRange = parseInt(control[8]);
}
}
return { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange };
}
/**
* Triggered when pips have been updated
* @param {number} sys SYS pips
* @param {number} eng ENG pips
* @param {number} wep WEP pips
*/
_pipsUpdated(sys, eng, wep) {
this.setState({ sys, eng, wep }, () => this._updateRouteOnControlChange());
}
/**
* Triggered when boost has been updated
* @param {boolean} boost true if boosting
*/
_boostUpdated(boost) {
this.setState({ boost }, () => this._updateRouteOnControlChange());
}
/**
* Triggered when fuel has been updated
* @param {number} fuel the amount of fuel, in T
*/
_fuelUpdated(fuel) {
this.setState({ fuel }, () => this._updateRouteOnControlChange());
}
/**
* Triggered when cargo has been updated
* @param {number} cargo the amount of cargo, in T
*/
_cargoUpdated(cargo) {
this.setState({ cargo }, () => this._updateRouteOnControlChange());
}
/**
* Triggered when engagement range has been updated
* @param {number} engagementRange the engagement range, in m
*/
_engagementRangeUpdated(engagementRange) {
this.setState({ engagementRange }, () => this._updateRouteOnControlChange());
}
/**
* Triggered when target ship has been updated
* @param {string} opponent the opponent's ship model
* @param {string} opponentBuild the name of the opponent's build
*/
_opponentUpdated(opponent, opponentBuild) {
const opponentShip = new Ship(opponent, Ships[opponent].properties, Ships[opponent].slots);
let opponentSys = this.state.opponentSys;
let opponentEng = this.state.opponentEng;
let opponentWep = this.state.opponentWep;
if (opponentBuild && Persist.getBuild(opponent, opponentBuild)) {
// Ship is a particular build
opponentShip.buildFrom(Persist.getBuild(opponent, opponentBuild));
// Set pips for opponent
const opponentParts = Persist.getBuild(opponent, opponentBuild).split('.');
if (opponentParts.length >= 5) {
const opponentControl = LZString.decompressFromBase64(Utils.fromUrlSafe(opponentParts[4])).split('/');
opponentSys = parseFloat(opponentControl[0]);
opponentEng = parseFloat(opponentControl[1]);
opponentWep = parseFloat(opponentControl[2]);
}
} else {
// Ship is a stock build
opponentShip.buildWith(Ships[opponent].defaults);
opponentSys = 2;
opponentEng = 2;
opponentWep = 2;
}
this.setState({ opponent: opponentShip, opponentBuild, opponentSys, opponentEng, opponentWep }, () => this._updateRouteOnControlChange());
}
/**
* Set the control code for this outfitting page
* @param {number} fuel the fuel carried by the ship (if different from that in state)
* @param {number} cargo the cargo carried by the ship (if different from that in state)
* @returns {string} The control code
*/
_controlCode(fuel, cargo) {
const { sys, eng, wep, boost, opponent, opponentBuild, engagementRange } = this.state;
const code = `${sys}/${eng}/${wep}/${boost ? 1 : 0}/${fuel || this.state.fuel}/${cargo || this.state.cargo}/${opponent.id}/${opponentBuild ? opponentBuild : ''}/${engagementRange}`;
return code;
}
/**
* Save the current build
*/
_saveBuild() {
let code = this.state.ship.toString();
let { buildName, newBuildName, shipId } = this.state;
const { ship, buildName, newBuildName, shipId } = this.state;
if (buildName === newBuildName) {
Persist.saveBuild(shipId, buildName, code);
this._updateRoute(shipId, buildName, code);
// If this is a stock ship the code won't be set, so ensure that we have it
const code = this.state.code || ship.toString();
Persist.saveBuild(shipId, newBuildName, code);
this._updateRoute(shipId, newBuildName, code);
let opponent, opponentBuild, opponentSys, opponentEng, opponentWep;
if (shipId === this.state.opponent.id && buildName === this.state.opponentBuild) {
// This is a save of our current opponent build; update it
opponentBuild = newBuildName;
opponent = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots).buildFrom(code);
opponentSys = this.state.sys;
opponentEng = this.state.eng;
opponentWep = this.state.wep;
} else {
Persist.saveBuild(shipId, newBuildName, code);
this._updateRoute(shipId, newBuildName, code);
opponentBuild = this.state.opponentBuild;
opponent = this.state.opponent;
opponentSys = this.state.opponentSys;
opponentEng = this.state.opponentEng;
opponentWep = this.state.opponentWep;
}
this.setState({ buildName: newBuildName, code, savedCode: code, title: this._getTitle(newBuildName) });
this.setState({ buildName: newBuildName, code, savedCode: code, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, title: this._getTitle(newBuildName) });
}
/**
* Rename the current build
*/
_renameBuild() {
let { buildName, newBuildName, shipId, ship } = this.state;
const { code, buildName, newBuildName, shipId, ship } = this.state;
if (buildName != newBuildName && newBuildName.length) {
let code = ship.toString();
Persist.deleteBuild(shipId, buildName);
Persist.saveBuild(shipId, newBuildName, code);
this._updateRoute(shipId, newBuildName, code);
this.setState({ buildName: newBuildName, code, savedCode: code });
this.setState({ buildName: newBuildName, code, savedCode: code, opponentBuild: newBuildName });
}
}
@@ -154,24 +350,50 @@ export default class OutfittingPage extends Page {
* Reload build from last save
*/
_reloadBuild() {
this.state.ship.buildFrom(this.state.savedCode);
this._shipUpdated();
this.setState({ code: this.state.savedCode }, () => this._codeUpdated());
}
/**
* Reset build to Stock/Factory defaults
*/
_resetBuild() {
this.state.ship.buildWith(Ships[this.state.shipId].defaults);
this._shipUpdated();
const { ship, shipId, buildName } = this.state;
// Rebuild ship
ship.buildWith(Ships[shipId].defaults);
// Reset controls
const code = ship.toString();
const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = this._obtainControlFromCode(ship, code);
// Update state, and refresh the ship
this.setState({
sys,
eng,
wep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
engagementRange
}, () => this._updateRoute(shipId, buildName, code));
}
/**
* Delete the build
*/
_deleteBuild() {
Persist.deleteBuild(this.state.shipId, this.state.buildName);
const { shipId, buildName } = this.state;
Persist.deleteBuild(shipId, buildName);
let opponentBuild;
if (shipId === this.state.opponent.id && buildName === this.state.opponentBuild) {
// Our current opponent has been deleted; revert to stock
opponentBuild = null;
} else {
opponentBuild = this.state.opponentBuild;
}
Router.go(outfitURL(this.state.shipId));
this.setState({ opponentBuild });
}
/**
@@ -181,25 +403,53 @@ 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())}
/>);
}
/**
* Trigger render on ship model change
* Called when the code for the ship has been updated, to synchronise the rest of the data
*/
_codeUpdated() {
const { code, ship, shipId, buildName } = this.state;
// Rebuild ship from the code
this.state.ship.buildFrom(code);
// Obtain controls from the code
const { sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, engagementRange } = this._obtainControlFromCode(ship, code);
// Update state, and refresh the route when complete
this.setState({
sys,
eng,
wep,
boost,
fuel,
cargo,
opponent,
opponentBuild,
engagementRange
}, () => this._updateRoute(shipId, buildName, code));
}
/**
* Called when the ship has been updated, to set the code and then update accordingly
*/
_shipUpdated() {
let { shipId, buildName, ship, fuelCapacity } = this.state;
let code = ship.toString();
if (fuelCapacity != ship.fuelCapacity) {
this._fuelChange(this.state.fuelLevel);
let { ship, shipId, buildName, cargo, fuel } = this.state;
if (cargo > ship.cargoCapacity) {
cargo = ship.cargoCapacity;
}
if (fuel > ship.fuelCapacity) {
fuel = ship.fuelCapacity;
}
const code = this._fullCode(ship, fuel, cargo);
// Only update the state if this really has been updated
if (this.state.code != code || this.state.cargo != cargo || this.state.fuel != fuel) {
this.setState({ code, cargo, fuel }, () => this._updateRoute(shipId, buildName, code));
}
this._updateRoute(shipId, buildName, code);
this.setState({ code });
}
/**
@@ -212,36 +462,6 @@ export default class OutfittingPage extends Page {
Router.replace(outfitURL(shipId, code, buildName));
}
/**
* Update current fuel level
* @param {number} fuelLevel Fuel leval 0 - 1
*/
_fuelChange(fuelLevel) {
let ship = this.state.ship;
let fuelCapacity = ship.fuelCapacity;
let fuel = fuelCapacity * fuelLevel;
this.setState({
fuelLevel,
fuelCapacity,
jumpRangeChartFunc: ship.calcJumpRangeWith.bind(ship, fuel),
fastestRangeChartFunc: ship.calcFastestRangeWith.bind(ship, fuel),
speedChartFunc: ship.calcSpeedsWith.bind(ship, fuel)
});
}
/**
* Update dimenions from rendered DOM
*/
_updateDimensions() {
let elem = findDOMNode(this.refs.chartThird);
if (elem) {
this.setState({
chartWidth: findDOMNode(this.refs.chartThird).offsetWidth
});
}
}
/**
* Update state based on context changes
* @param {Object} nextProps Incoming/Next properties
@@ -249,7 +469,7 @@ export default class OutfittingPage extends Page {
*/
componentWillReceiveProps(nextProps, nextContext) {
if (this.context.route !== nextContext.route) { // Only reinit state if the route has changed
this.setState(this._initState(nextContext));
this.setState(this._initState(nextProps, nextContext));
}
}
@@ -257,21 +477,14 @@ export default class OutfittingPage extends Page {
* Add listeners when about to mount
*/
componentWillMount() {
this.resizeListener = this.context.onWindowResize(this._updateDimensions);
}
/**
* Trigger DOM updates on mount
*/
componentDidMount() {
this._updateDimensions();
document.addEventListener('keydown', this._keyDown);
}
/**
* Remove listeners on unmount
*/
componentWillUnmount() {
this.resizeListener.remove();
document.removeEventListener('keydown', this._keyDown);
}
/**
@@ -281,6 +494,36 @@ export default class OutfittingPage extends Page {
this.context.showModal(<ModalPermalink url={window.location.href}/>);
}
/**
* Open up a window for EDDB with a shopping list of our components
*/
_eddbShoppingList() {
const ship = this.state.ship;
const shipId = Ships[ship.id].eddbID;
// Provide unique list of non-PP module EDDB IDs
const modIds = ship.internal.concat(ship.bulkheads, ship.standard, ship.hardpoints).filter(slot => slot !== null && slot.m !== null && !slot.m.pp).map(slot => slot.m.eddbID).filter((v, i, a) => a.indexOf(v) === i);
// Open up the relevant URL
window.open('https://eddb.io/station?s=' + shipId + '&m=' + modIds.join(','));
}
/**
* 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
@@ -289,16 +532,29 @@ export default class OutfittingPage extends Page {
let state = this.state,
{ language, termtip, tooltip, sizeRatio, onWindowResize } = this.context,
{ translate, units, formats } = language,
{ ship, code, savedCode, buildName, newBuildName, chartWidth, fuelCapacity, fuelLevel } = state,
{ ship, code, savedCode, buildName, newBuildName, sys, eng, wep, boost, fuel, cargo, opponent, opponentBuild, opponentSys, opponentEng, opponentWep, engagementRange } = state,
hide = tooltip.bind(null, null),
menu = this.props.currentMenu,
shipUpdated = this._shipUpdated,
canSave = (newBuildName || buildName) && code !== savedCode,
canRename = buildName && newBuildName && buildName != newBuildName,
canReload = savedCode && canSave,
hStr = ship.getHardpointsString() + '.' + ship.getModificationsString(),
sStr = ship.getStandardString() + '.' + ship.getModificationsString(),
iStr = ship.getInternalString() + '.' + ship.getModificationsString();
canReload = savedCode && canSave;
// Code can be blank for a default loadout. Prefix it with the ship name to ensure that changes in default ships is picked up
code = ship.name + (code || '');
// Markers are used to propagate state changes without requiring a deep comparison of the ship, as that takes a long time
const _sStr = ship.getStandardString();
const _iStr = ship.getInternalString();
const _hStr = ship.getHardpointsString();
const _pStr = `${ship.getPowerEnabledString()}${ship.getPowerPrioritiesString()}`;
const _mStr = ship.getModificationsString();
const standardSlotMarker = `${ship.name}${_sStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`;
const internalSlotMarker = `${ship.name}${_iStr}${_pStr}${_mStr}`;
const hardpointsSlotMarker = `${ship.name}${_hStr}${_pStr}${_mStr}`;
const boostMarker = `${ship.canBoost(cargo, fuel)}`;
const shipSummaryMarker = `${ship.name}${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`;
return (
<div id='outfit' className={'page'} style={{ fontSize: (sizeRatio * 0.9) + 'em' }}>
@@ -324,131 +580,72 @@ 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._eddbShoppingList} onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_LIST')} onMouseOut={hide}>
<ShoppingIcon className='lg' />
</button>
<button onClick={this._genShortlink} onMouseOver={termtip.bind(null, 'shortlink')} onMouseOut={hide}>
<LinkIcon className='lg' />
</button>
</div>
</div>
<ShipSummaryTable ship={ship} code={code} />
<StandardSlotSection ship={ship} code={sStr} onChange={shipUpdated} currentMenu={menu} />
<InternalSlotSection ship={ship} code={iStr} onChange={shipUpdated} currentMenu={menu} />
<HardpointsSlotSection ship={ship} code={hStr} onChange={shipUpdated} currentMenu={menu} />
<UtilitySlotSection ship={ship} code={hStr} onChange={shipUpdated} currentMenu={menu} />
<PowerManagement ship={ship} code={code} onChange={shipUpdated} />
<CostSection ship={ship} buildName={buildName} code={sStr + hStr + iStr} />
{/* Main tables */}
<ShipSummaryTable ship={ship} fuel={fuel} cargo={cargo} marker={shipSummaryMarker} />
<StandardSlotSection ship={ship} fuel={fuel} cargo={cargo} code={standardSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} />
<InternalSlotSection ship={ship} code={internalSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} />
<HardpointSlotSection ship={ship} code={hardpointsSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} />
<UtilitySlotSection ship={ship} code={hardpointsSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} />
<div className='group third'>
<OffenceSummary ship={ship} code={code}/>
{/* Control of ship and opponent */}
<div className='group quarter'>
<div className='group half'>
<h2 style={{ verticalAlign: 'middle', textAlign: 'left' }}>{translate('ship control')}</h2>
</div>
<div className='group half'>
<Boost marker={boostMarker} ship={ship} boost={boost} onChange={this._boostUpdated} />
</div>
</div>
<div className='group quarter'>
<Pips sys={sys} eng={eng} wep={wep} onChange={this._pipsUpdated} />
</div>
<div className='group quarter'>
<Fuel fuelCapacity={ship.fuelCapacity} fuel={fuel} onChange={this._fuelUpdated}/>
</div>
<div className='group quarter'>
{ ship.cargoCapacity > 0 ? <Cargo cargoCapacity={ship.cargoCapacity} cargo={cargo} onChange={this._cargoUpdated}/> : null }
</div>
<div className='group half'>
<div className='group quarter'>
<h2 style={{ verticalAlign: 'middle', textAlign: 'left' }}>{translate('opponent')}</h2>
</div>
<div className='group threequarters'>
<ShipPicker ship={opponent.id} build={opponentBuild} onChange={this._opponentUpdated}/>
</div>
</div>
<div className='group half'>
<EngagementRange ship={ship} engagementRange={engagementRange} onChange={this._engagementRangeUpdated}/>
</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
width={chartWidth}
xMax={ship.cargoCapacity}
yMax={ship.unladenRange}
xUnit={translate('T')}
yUnit={translate('LY')}
yLabel={translate('jump range')}
xLabel={translate('cargo')}
func={state.jumpRangeChartFunc}
/>
<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>
<div>
<DamageDealt ship={ship} code={code} currentMenu={menu}/>
</div>
<div>
<DamageReceived ship={ship} code={code} currentMenu={menu}/>
</div>
{/* Tabbed subpages */}
<OutfittingSubpages
ship={ship}
code={code}
buildName={buildName}
onChange={shipUpdated}
sys={sys}
eng={eng}
wep={wep}
boost={boost}
cargo={cargo}
fuel={fuel}
engagementRange={engagementRange}
opponent={opponent}
opponentBuild={opponentBuild}
opponentSys={opponentSys}
opponentEng={opponentEng}
opponentWep={opponentWep}
/>
</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

@@ -1,4 +1,5 @@
import React from 'react';
import PropTypes from 'prop-types';
import ErrorDetails from './ErrorDetails';
import { shallowEqual } from '../utils/UtilityFunctions';
@@ -8,22 +9,22 @@ import { shallowEqual } from '../utils/UtilityFunctions';
export default class Page extends React.Component {
static contextTypes = {
closeMenu: React.PropTypes.func.isRequired,
hideModal: React.PropTypes.func.isRequired,
language: React.PropTypes.object.isRequired,
noTouch: React.PropTypes.bool.isRequired,
onCommand: React.PropTypes.func.isRequired,
onWindowResize: React.PropTypes.func.isRequired,
openMenu: React.PropTypes.func.isRequired,
route: React.PropTypes.object.isRequired,
showModal: React.PropTypes.func.isRequired,
sizeRatio: React.PropTypes.number.isRequired,
termtip: React.PropTypes.func.isRequired,
tooltip: React.PropTypes.func.isRequired
closeMenu: PropTypes.func.isRequired,
hideModal: PropTypes.func.isRequired,
language: PropTypes.object.isRequired,
noTouch: PropTypes.bool.isRequired,
onCommand: PropTypes.func.isRequired,
onWindowResize: PropTypes.func.isRequired,
openMenu: PropTypes.func.isRequired,
route: PropTypes.object.isRequired,
showModal: PropTypes.func.isRequired,
sizeRatio: PropTypes.number.isRequired,
termtip: PropTypes.func.isRequired,
tooltip: PropTypes.func.isRequired
};
static propTypes = {
currentMenu: React.PropTypes.any
currentMenu: PropTypes.any
};
/**
@@ -39,6 +40,14 @@ export default class Page extends React.Component {
this[prop] = this[prop].bind(this);
}
});
let fix = sessionStorage.getItem('__safari_history_fix');
sessionStorage.removeItem('__safari_history_fix');
if (fix) {
fix = JSON.parse(fix);
history.replaceState(history.state, document.title, location.href);
history.pushState(fix.state, fix.title, fix.path);
}
}
/**
@@ -82,4 +91,4 @@ export default class Page extends React.Component {
return this.renderPage();
}
}
}

View File

@@ -54,7 +54,18 @@ function shipSummary(shipId, shipData) {
summary.retailCost = ship.totalCost; // Record Stock/Default/retail cost
ship.optimizeMass({ pd: '1D' }); // Optimize Mass with 1D PD for maximum possible jump range
summary.maxJumpRange = ship.unladenRange; // Record Jump Range
ship.optimizeMass({ th: ship.standard[1].maxClass + 'A', fsd: '2D', ft: '1C' }); // Optmize mass with Max Thrusters
// Best thrusters
let th;
if (ship.standard[1].maxClass === 3) {
th = 'tz';
} else if (ship.standard[1].maxClass === 2) {
th = 'u0';
} else {
th = ship.standard[1].maxClass + 'A';
}
ship.optimizeMass({ th, fsd: '2D', ft: '1C' }); // Optmize mass with Max Thrusters
summary.topSpeed = ship.topSpeed;
summary.topBoost = ship.topBoost;
summary.baseArmour = ship.armour;
@@ -139,18 +150,22 @@ export default class ShipyardPage extends Page {
className={cn({ highlighted: noTouch && this.state.shipId === s.id })}
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
>
<td className='le'>{s.manufacturer}</td>
<td className='cap'>{translate(SizeMap[s.class])}</td>
<td className='ri'>{s.manufacturer}</td>
<td className='ri'>{fInt(s.retailCost)}</td>
<td className='ri cap'>{translate(SizeMap[s.class])}</td>
<td className='ri'>{fInt(s.crew)}</td>
<td className='ri'>{s.masslock}</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'>{fInt(s.hullMass)}</td>
<td className='ri'>{fInt(s.speed)}</td>
<td className='ri'>{fInt(s.boost)}</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='ri'>{fInt(s.baseShieldStrength)}</td>
<td className='ri'>{fInt(s.topSpeed)}</td>
<td className='ri'>{fInt(s.topBoost)}</td>
<td className='ri'>{fRound(s.maxJumpRange)}</td>
<td className='ri'>{fInt(s.maxCargo)}</td>
<td className='cn'>{s.standard[0]}</td>
<td className='cn'>{s.standard[1]}</td>
<td className='cn'>{s.standard[2]}</td>
@@ -170,9 +185,6 @@ export default class ShipyardPage extends Page {
<td className={cn({ disabled: !s.int[5] })}>{s.int[5]}</td>
<td className={cn({ disabled: !s.int[6] })}>{s.int[6]}</td>
<td className={cn({ disabled: !s.int[7] })}>{s.int[7]}</td>
<td className='ri'>{fInt(s.hullMass)}{u.T}</td>
<td>{s.masslock}</td>
<td className='ri'>{fInt(s.retailCost)}{u.CR}</td>
</tr>;
}
@@ -235,7 +247,7 @@ export default class ShipyardPage extends Page {
let detailRows = new Array(shipSummaries.length);
for (let s of shipSummaries) {
detailRows[i] = this._shipRowElement(s, translate, units, fInt, fRound);
detailRows[i] = this._shipRowElement(s, translate, units, fInt, formats.f1);
shipRows[i] = (
<tr
key={i}
@@ -251,12 +263,17 @@ 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.2. For more info see Settings / <Link href="/about" className='block'>About</Link></p>
<div style={{ whiteSpace: 'nowrap', margin: '0 auto', fontSize: '0.8em', position: 'relative', display: 'inline-block', maxWidth: '100%' }}>
<table style={{ width: '12em', position: 'absolute', zIndex: 1 }}>
<thead>
<tr>
<th className='le rgt'>&nbsp;</th>
</tr>
<tr className='main'>
<th style={{ height: '2.6em', padding: '2px 0.4em 1px' }} className='sortable le rgt' onClick={sortShips('name')}>{translate('ship')}</th>
<th className='sortable le rgt' onClick={sortShips('name')}>{translate('ship')}</th>
</tr>
<tr>
<th className='le rgt invisible'>{units['m/s']}</th>
</tr>
</thead>
<tbody onMouseLeave={this._highlightShip.bind(this, null)}>
@@ -267,20 +284,23 @@ export default class ShipyardPage extends Page {
<table style={{ marginLeft: 'calc(12em - 1px)', zIndex: 0 }}>
<thead>
<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' onClick={sortShips('agility')}>{translate('agility')}</th>
<th rowSpan={2} className='sortable' onClick={sortShips('hardness')}>{translate('hardness')}</th>
<th rowSpan={3} className='sortable' onClick={sortShips('manufacturer')}>{translate('manufacturer')}</th>
<th>&nbsp;</th>
<th rowSpan={3} className='sortable' onClick={sortShips('class')}>{translate('size')}</th>
<th rowSpan={3} className='sortable' onClick={sortShips('crew')}>{translate('crew')}</th>
<th rowSpan={3} className='sortable' onMouseEnter={termtip.bind(null, 'mass lock factor')} onMouseLeave={hide} onClick={sortShips('masslock')} >{translate('MLF')}</th>
<th rowSpan={3} className='sortable' onClick={sortShips('agility')}>{translate('agility')}</th>
<th rowSpan={3} className='sortable' onMouseEnter={termtip.bind(null, 'hardness')} onMouseLeave={hide} onClick={sortShips('hardness')}>{translate('hrd')}</th>
<th>&nbsp;</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>
<th rowSpan={2} className='sortable' onMouseEnter={termtip.bind(null, 'mass lock factor')} onMouseLeave={hide} onClick={sortShips('masslock')} >{translate('MLF')}</th>
<th rowSpan={2} className='sortable' onClick={sortShips('retailCost')}>{translate('cost')}</th>
<th className='lft' colSpan={6}></th>
<th className='lft' colSpan={5}></th>
<th className='lft' colSpan={8}></th>
</tr>
<tr>
<th className='sortable lft' onClick={sortShips('retailCost')}>{translate('cost')}</th>
<th className='sortable lft' onClick={sortShips('hullMass')}>{translate('hull')}</th>
<th className='sortable lft' onClick={sortShips('speed')}>{translate('speed')}</th>
<th className='sortable' onClick={sortShips('boost')}>{translate('boost')}</th>
<th className='sortable' onClick={sortShips('baseArmour')}>{translate('armour')}</th>
@@ -291,6 +311,21 @@ 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='lft' colSpan={6}>{translate('core module classes')}</th>
<th colSpan={5} className='sortable lft' onClick={sortShips('hpCount')}>{translate('hardpoints')}</th>
<th colSpan={8} className='sortable lft' onClick={sortShips('intCount')}>{translate('internal compartments')}</th>
</tr>
<tr>
<th className='sortable lft' onClick={sortShips('retailCost')}>{units.CR}</th>
<th className='sortable lft' onClick={sortShips('hullMass')}>{units.T}</th>
<th className='sortable lft' onClick={sortShips('speed')}>{units['m/s']}</th>
<th className='sortable' onClick={sortShips('boost')}>{units['m/s']}</th>
<th>&nbsp;</th>
<th className='sortable' onClick={sortShips('baseShieldStrength')}>{units.MJ}</th>
<th className='sortable lft' onClick={sortShips('topSpeed')}>{units['m/s']}</th>
<th className='sortable' onClick={sortShips('topBoost')}>{units['m/s']}</th>
<th className='sortable' onClick={sortShips('maxJumpRange')}>{units.LY}</th>
<th className='sortable' onClick={sortShips('maxCargo')}>{units.T}</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>

View File

@@ -9,33 +9,33 @@ import Module from './Module';
* @return {number} Distance in Light Years
*/
export function jumpRange(mass, fsd, fuel) {
let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
let fsdOptimalMass = fsd instanceof Module ? fsd.getOptimalMass() : fsd.optmass;
const fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
const 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;
}
/**
* Calculate the fastest (total) range based on mass and a specific FSD, and all fuel available
* Calculate the total jump range based on mass and a specific FSD, and all fuel available
*
* @param {number} mass Mass of a ship: laden, unlanden, partially laden, etc
* @param {object} fsd The FDS object/component with maxfuel, fuelmul, fuelpower, optmass
* @param {number} fuel The total fuel available
* @return {number} Distance in Light Years
*/
export function fastestRange(mass, fsd, fuel) {
let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
let fsdOptimalMass = fsd instanceof Module ? fsd.getOptimalMass() : fsd.optmass;
let fuelRemaining = fuel % fsdMaxFuelPerJump; // Fuel left after making N max jumps
let jumps = Math.floor(fuel / fsdMaxFuelPerJump);
mass += fuelRemaining;
// Going backwards, start with the last jump using the remaining fuel
let fastestRange = fuelRemaining > 0 ? Math.pow(fuelRemaining / fsd.fuelmul, 1 / fsd.fuelpower) * 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(fsdMaxFuelPerJump / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass;
export function totalJumpRange(mass, fsd, fuel) {
const fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
const fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass;
let fuelRemaining = fuel;
let totalRange = 0;
while (fuelRemaining > 0) {
const fuelForThisJump = Math.min(fuelRemaining, fsdMaxFuelPerJump);
totalRange += this.jumpRange(mass, fsd, fuelForThisJump);
// Mass is reduced
mass -= fuelForThisJump;
fuelRemaining -= fuelForThisJump;
}
return fastestRange;
return totalRange;
};
/**
@@ -168,8 +168,688 @@ function normValues(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, bas
const res = base * mul;
return [res * (1 - (engpip * 4)),
res * (1 - (engpip * 3)),
res * (1 - (engpip * 2)),
res * (1 - (engpip * 1)),
res];
res * (1 - (engpip * 3)),
res * (1 - (engpip * 2)),
res * (1 - (engpip * 1)),
res];
}
/**
* Calculate a single value
* @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
* @param {number} eng the pips to engines
* @returns {number} the resultant value
*/
function calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, base, engpip, eng) {
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 - eng)));
}
/**
* Calculate speed for a given setup
* @param {number} mass the mass of the ship
* @param {number} baseSpeed the base speed of the ship
* @param {ojbect} thrusters the thrusters of the ship
* @param {number} engpip the multiplier per pip to engines
* @param {number} eng the pips to engines
* @param {number} boostFactor the boost factor for ths ship
* @param {boolean} boost true if the boost is activated
* @returns {number} the resultant speed
*/
export function calcSpeed(mass, baseSpeed, thrusters, engpip, eng, boostFactor, boost) {
// 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);
let result = calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseSpeed, engpip, eng);
if (boost == true) {
result *= boostFactor;
}
return result;
}
/**
* Calculate pitch for a given setup
* @param {number} mass the mass of the ship
* @param {number} basePitch the base pitch of the ship
* @param {ojbect} thrusters the thrusters of the ship
* @param {number} engpip the multiplier per pip to engines
* @param {number} eng the pips to engines
* @param {number} boostFactor the boost factor for ths ship
* @param {boolean} boost true if the boost is activated
* @returns {number} the resultant pitch
*/
export function calcPitch(mass, basePitch, thrusters, engpip, eng, boostFactor, boost) {
// 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);
let result = calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, basePitch, engpip, eng);
if (boost == true) {
result *= boostFactor;
}
return result;
}
/**
* Calculate roll for a given setup
* @param {number} mass the mass of the ship
* @param {number} baseRoll the base roll of the ship
* @param {ojbect} thrusters the thrusters of the ship
* @param {number} engpip the multiplier per pip to engines
* @param {number} eng the pips to engines
* @param {number} boostFactor the boost factor for ths ship
* @param {boolean} boost true if the boost is activated
* @returns {number} the resultant roll
*/
export function calcRoll(mass, baseRoll, thrusters, engpip, eng, boostFactor, boost) {
// 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);
let result = calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseRoll, engpip, eng);
if (boost == true) {
result *= boostFactor;
}
return result;
}
/**
* Calculate yaw for a given setup
* @param {number} mass the mass of the ship
* @param {number} baseYaw the base yaw of the ship
* @param {ojbect} thrusters the thrusters of the ship
* @param {number} engpip the multiplier per pip to engines
* @param {number} eng the pips to engines
* @param {number} boostFactor the boost factor for ths ship
* @param {boolean} boost true if the boost is activated
* @returns {number} the resultant yaw
*/
export function calcYaw(mass, baseYaw, thrusters, engpip, eng, boostFactor, boost) {
// 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);
let result = calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, baseYaw, engpip, eng);
if (boost == true) {
result *= boostFactor;
}
return result;
}
/**
* Calculate shield metrics
* @param {Object} ship The ship
* @param {int} sys The pips to SYS
* @returns {Object} Shield metrics
*/
export function shieldMetrics(ship, sys) {
const sysResistance = this.sysResistance(sys);
const maxSysResistance = this.sysResistance(4);
let shield = {};
const shieldGeneratorSlot = ship.findInternalByGroup('sg');
if (shieldGeneratorSlot && shieldGeneratorSlot.enabled && shieldGeneratorSlot.m) {
const shieldGenerator = shieldGeneratorSlot.m;
// Boosters
let boost = 1;
let boosterExplDmg = 1;
let boosterKinDmg = 1;
let boosterThermDmg = 1;
for (let slot of ship.hardpoints) {
if (slot.enabled && slot.m && slot.m.grp == 'sb') {
boost += slot.m.getShieldBoost();
boosterExplDmg = boosterExplDmg * (1 - slot.m.getExplosiveResistance());
boosterKinDmg = boosterKinDmg * (1 - slot.m.getKineticResistance());
boosterThermDmg = boosterThermDmg * (1 - slot.m.getThermalResistance());
}
}
// Calculate diminishing returns for boosters
// Diminishing returns not currently in-game
// boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5);
// Remove base shield generator strength
boost -= 1;
// Apply diminishing returns
boosterExplDmg = boosterExplDmg > 0.7 ? boosterExplDmg : 0.7 - (0.7 - boosterExplDmg) / 2;
boosterKinDmg = boosterKinDmg > 0.7 ? boosterKinDmg : 0.7 - (0.7 - boosterKinDmg) / 2;
boosterThermDmg = boosterThermDmg > 0.7 ? boosterThermDmg : 0.7 - (0.7 - boosterThermDmg) / 2;
const generatorStrength = this.shieldStrength(ship.hullMass, ship.baseShieldStrength, shieldGenerator, 1);
const boostersStrength = generatorStrength * boost;
// Recover time is the time taken to go from 0 to 50%. It includes a 16-second wait before shields start to recover
const shieldToRecover = (generatorStrength + boostersStrength) / 2;
const powerDistributor = ship.standard[4].m;
const sysRechargeRate = this.sysRechargeRate(powerDistributor, sys);
// Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes
// 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration
let capacitorDrain = (shieldGenerator.getBrokenRegenerationRate() * 0.6) - sysRechargeRate;
let capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain;
let recover = 16;
if (capacitorDrain <= 0 || shieldToRecover < capacitorLifetime * shieldGenerator.getBrokenRegenerationRate()) {
// We can recover the entire shield from the capacitor store
recover += shieldToRecover / shieldGenerator.getBrokenRegenerationRate();
} else {
// We can recover some of the shield from the capacitor store
recover += capacitorLifetime;
const remainingShieldToRecover = shieldToRecover - capacitorLifetime * shieldGenerator.getBrokenRegenerationRate();
if (sys === 0) {
// No system pips so will never recover shields
recover = Math.Inf;
} else {
// Recover remaining shields at the rate of the power distributor's recharge
recover += remainingShieldToRecover / (sysRechargeRate / 0.6);
}
}
// Recharge time is the time taken to go from 50% to 100%
const shieldToRecharge = (generatorStrength + boostersStrength) / 2;
// Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes
// 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration
capacitorDrain = (shieldGenerator.getRegenerationRate() * 0.6) - sysRechargeRate;
capacitorLifetime = powerDistributor.getSystemsCapacity() / capacitorDrain;
let recharge = 0;
if (capacitorDrain <= 0 || shieldToRecharge < capacitorLifetime * shieldGenerator.getRegenerationRate()) {
// We can recharge the entire shield from the capacitor store
recharge += shieldToRecharge / shieldGenerator.getRegenerationRate();
} else {
// We can recharge some of the shield from the capacitor store
recharge += capacitorLifetime;
const remainingShieldToRecharge = shieldToRecharge - capacitorLifetime * shieldGenerator.getRegenerationRate();
if (sys === 0) {
// No system pips so will never recharge shields
recharge = Math.Inf;
} else {
// Recharge remaining shields at the rate of the power distributor's recharge
recharge += remainingShieldToRecharge / (sysRechargeRate / 0.6);
}
}
shield = {
generator: generatorStrength,
boosters: boostersStrength,
cells: ship.shieldCells,
total: generatorStrength + boostersStrength + ship.shieldCells,
recover,
recharge,
};
// Shield resistances have three components: the shield generator, the shield boosters and the SYS pips.
// We re-cast these as damage percentages
shield.absolute = {
generator: 1,
boosters: 1,
sys: 1 - sysResistance,
total: 1 - sysResistance,
max: 1 - maxSysResistance
};
shield.explosive = {
generator: 1 - shieldGenerator.getExplosiveResistance(),
boosters: boosterExplDmg,
sys: (1 - sysResistance),
total: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - sysResistance),
max: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - maxSysResistance)
};
shield.kinetic = {
generator: 1 - shieldGenerator.getKineticResistance(),
boosters: boosterKinDmg,
sys: (1 - sysResistance),
total: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - sysResistance),
max: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - maxSysResistance)
};
shield.thermal = {
generator: 1 - shieldGenerator.getThermalResistance(),
boosters: boosterThermDmg,
sys: (1 - sysResistance),
total: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - sysResistance),
max: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - maxSysResistance)
};
}
return shield;
}
/**
* Calculate armour metrics
* @param {Object} ship The ship
* @returns {Object} Armour metrics
*/
export function armourMetrics(ship) {
// Armour from bulkheads
const armourBulkheads = ship.baseArmour + (ship.baseArmour * ship.bulkheads.m.getHullBoost());
let armourReinforcement = 0;
let moduleArmour = 0;
let moduleProtection = 1;
let hullExplDmg = 1;
let hullKinDmg = 1;
let hullThermDmg = 1;
// Armour from HRPs and module armour from MRPs
for (let slot of ship.internal) {
if (slot.m && slot.m.grp == 'hr') {
armourReinforcement += slot.m.getHullReinforcement();
// Hull boost for HRPs is applied against the ship's base armour
armourReinforcement += ship.baseArmour * slot.m.getModValue('hullboost') / 10000;
hullExplDmg = hullExplDmg * (1 - slot.m.getExplosiveResistance());
hullKinDmg = hullKinDmg * (1 - slot.m.getKineticResistance());
hullThermDmg = hullThermDmg * (1 - slot.m.getThermalResistance());
}
if (slot.m && slot.m.grp == 'mrp') {
moduleArmour += slot.m.getIntegrity();
moduleProtection = moduleProtection * (1 - slot.m.getProtection());
}
}
moduleProtection = 1 - moduleProtection;
// Apply diminishing returns
hullExplDmg = hullExplDmg > 0.7 ? hullExplDmg : 0.7 - (0.7 - hullExplDmg) / 2;
hullKinDmg = hullKinDmg > 0.7 ? hullKinDmg : 0.7 - (0.7 - hullKinDmg) / 2;
hullThermDmg = hullThermDmg > 0.7 ? hullThermDmg : 0.7 - (0.7 - hullThermDmg) / 2;
const armour = {
bulkheads: armourBulkheads,
reinforcement: armourReinforcement,
modulearmour: moduleArmour,
moduleprotection: moduleProtection,
total: armourBulkheads + armourReinforcement
};
// Armour resistances have two components: bulkheads and HRPs
// We re-cast these as damage percentages
armour.absolute = {
bulkheads: 1,
reinforcement: 1,
total: 1
};
armour.explosive = {
bulkheads: 1 - ship.bulkheads.m.getExplosiveResistance(),
reinforcement: hullExplDmg,
total: (1 - ship.bulkheads.m.getExplosiveResistance()) * hullExplDmg
};
armour.kinetic = {
bulkheads: 1 - ship.bulkheads.m.getKineticResistance(),
reinforcement: hullKinDmg,
total: (1 - ship.bulkheads.m.getKineticResistance()) * hullKinDmg
};
armour.thermal = {
bulkheads: 1 - ship.bulkheads.m.getThermalResistance(),
reinforcement: hullThermDmg,
total: (1 - ship.bulkheads.m.getThermalResistance()) * hullThermDmg
};
return armour;
}
/**
* Calculate defence metrics for a ship
* @param {Object} ship The ship
* @param {Object} opponent The opponent ship
* @param {int} sys The pips to SYS
* @param {int} opponentWep The pips to pponent's WEP
* @param {int} engagementrange The range between the ship and opponent
* @returns {Object} Defence metrics
*/
export function defenceMetrics(ship, opponent, sys, opponentWep, engagementrange) {
// Obtain the shield metrics
const shield = this.shieldMetrics(ship, sys);
// Obtain the armour metrics
const armour = this.armourMetrics(ship);
// Obtain the opponent's sustained DPS on us
const sustainedDps = this.sustainedDps(opponent, ship, sys, engagementrange);
const shielddamage = shield.generator ? {
absolutesdps: sustainedDps.shieldsdps.absolute,
explosivesdps: sustainedDps.shieldsdps.explosive,
kineticsdps: sustainedDps.shieldsdps.kinetic,
thermalsdps: sustainedDps.shieldsdps.thermal,
totalsdps: sustainedDps.shieldsdps.absolute + sustainedDps.shieldsdps.explosive + sustainedDps.shieldsdps.kinetic + sustainedDps.shieldsdps.thermal,
totalseps: sustainedDps.eps
} : {};
const armourdamage = {
absolutesdps: sustainedDps.armoursdps.absolute,
explosivesdps: sustainedDps.armoursdps.explosive,
kineticsdps: sustainedDps.armoursdps.kinetic,
thermalsdps: sustainedDps.armoursdps.thermal,
totalsdps: sustainedDps.armoursdps.absolute + sustainedDps.armoursdps.explosive + sustainedDps.armoursdps.kinetic + sustainedDps.armoursdps.thermal,
totalseps: sustainedDps.eps
};
return { shield, armour, shielddamage, armourdamage };
}
/**
* Calculate offence metrics for a ship
* @param {Object} ship The ship
* @param {Object} opponent The opponent ship
* @param {int} wep The pips to WEP
* @param {int} opponentSys The pips to opponent's SYS
* @param {int} engagementrange The range between the ship and opponent
* @returns {array} Offence metrics
*/
export function offenceMetrics(ship, opponent, wep, opponentSys, engagementrange) {
// Per-weapon and total damage
const damage = [];
// Obtain the opponent's shield and armour metrics
const opponentShields = this.shieldMetrics(opponent, opponentSys);
const opponentArmour = this.armourMetrics(opponent);
// Per-weapon and total damage to shields
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
const m = ship.hardpoints[i].m;
const classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
let engineering;
if (m.blueprint && m.blueprint.name) {
engineering = m.blueprint.name + ' ' + 'grade' + ' ' + m.blueprint.grade;
if (m.blueprint.special && m.blueprint.special.id >= 0) {
engineering += ', ' + m.blueprint.special.name;
}
}
const weaponSustainedDps = this._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementrange);
damage.push({
id: i,
mount: m.mount,
name: m.name || m.grp,
classRating,
engineering,
sdps: weaponSustainedDps.damage,
seps: weaponSustainedDps.eps,
effectiveness: weaponSustainedDps.effectiveness
});
}
}
return damage;
}
/**
* Calculate the resistance provided by SYS pips
* @param {integer} sys the value of the SYS pips
* @returns {integer} the resistance for the given pips
*/
export function sysResistance(sys) {
return Math.pow(sys, 0.85) * 0.6 / Math.pow(4, 0.85);
}
/**
* Obtain the recharge rate of the SYS capacitor of a power distributor given pips
* @param {Object} pd The power distributor
* @param {number} sys The number of pips to SYS
* @returns {number} The recharge rate in MJ/s
*/
export function sysRechargeRate(pd, sys) {
return pd.getSystemsRechargeRate() * Math.pow(sys, 1.1) / Math.pow(4, 1.1);
}
/**
* Calculate the sustained DPS for a ship against an opponent at a given range
* @param {Object} ship The ship
* @param {Object} opponent The opponent ship
* @param {number} sys Pips to opponent's SYS
* @param {int} engagementrange The range between the ship and opponent
* @returns {Object} Sustained DPS for shield and armour
*/
export function sustainedDps(ship, opponent, sys, engagementrange) {
// Obtain the opponent's shield and armour metrics
const opponentShields = this.shieldMetrics(opponent, sys);
const opponentArmour = this.armourMetrics(opponent);
return this._sustainedDps(ship, opponent, opponentShields, opponentArmour, engagementrange);
}
/**
* Calculate the sustained DPS for a ship against an opponent at a given range
* @param {Object} ship The ship
* @param {Object} opponent The opponent ship
* @param {Object} opponentShields The opponent's shield resistances
* @param {Object} opponentArmour The opponent's armour resistances
* @param {int} engagementrange The range between the ship and opponent
* @returns {Object} Sustained DPS for shield and armour
*/
export function _sustainedDps(ship, opponent, opponentShields, opponentArmour, engagementrange) {
const shieldsdps = {
absolute: 0,
explosive: 0,
kinetic: 0,
thermal: 0
};
const armoursdps = {
absolute: 0,
explosive: 0,
kinetic: 0,
thermal: 0
};
let eps = 0;
for (let i = 0; i < ship.hardpoints.length; i++) {
if (ship.hardpoints[i].m && ship.hardpoints[i].enabled && ship.hardpoints[i].maxClass > 0) {
const m = ship.hardpoints[i].m;
const sustainedDps = this._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementrange);
shieldsdps.absolute += sustainedDps.damage.shields.absolute;
shieldsdps.explosive += sustainedDps.damage.shields.explosive;
shieldsdps.kinetic += sustainedDps.damage.shields.kinetic;
shieldsdps.thermal += sustainedDps.damage.shields.thermal;
armoursdps.absolute += sustainedDps.damage.armour.absolute;
armoursdps.explosive += sustainedDps.damage.armour.explosive;
armoursdps.kinetic += sustainedDps.damage.armour.kinetic;
armoursdps.thermal += sustainedDps.damage.armour.thermal;
eps += sustainedDps.eps;
}
}
return { shieldsdps, armoursdps, eps };
}
/**
* Calculate the sustained DPS for a weapon at a given range
* @param {Object} m The weapon
* @param {Object} opponent The opponent ship
* @param {Object} opponentShields The opponent's shield resistances
* @param {Object} opponentArmour The opponent's armour resistances
* @param {int} engagementrange The range between the ship and opponent
* @returns {Object} Sustained DPS for shield and armour
*/
export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementrange) {
const opponentHasShields = opponentShields.generator ? true : false;
const weapon = {
eps: 0,
damage: {
shields: {
absolute: 0,
explosive: 0,
kinetic: 0,
thermal: 0,
total: 0
},
armour: {
absolute: 0,
explosive: 0,
kinetic: 0,
thermal: 0,
total: 0
},
},
effectiveness: {
shields: {
range: 1,
sys: opponentHasShields ? opponentShields.absolute.sys : 1,
resistance: 1
},
armour: {
range: 1,
hardness: 1,
resistance: 1
}
}
};
// EPS
weapon.eps = m.getClip() ? (m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getEps();
// Initial sustained DPS
let sDps = m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getDps();
// Take fall-off in to account
const falloff = m.getFalloff();
if (falloff && engagementrange > falloff) {
const dropoffRange = m.getRange() - falloff;
const dropoff = 1 - Math.min((engagementrange - falloff) / dropoffRange, 1);
weapon.effectiveness.shields.range = weapon.effectiveness.armour.range = dropoff;
sDps *= dropoff;
}
// Piercing/hardness modifier (for armour only)
const armourMultiple = m.getPiercing() >= opponent.hardness ? 1 : m.getPiercing() / opponent.hardness;
weapon.effectiveness.armour.hardness = armourMultiple;
// Break out the damage according to type
let shieldsResistance = 0;
let armourResistance = 0;
if (m.getDamageDist().A) {
weapon.damage.shields.absolute += sDps * m.getDamageDist().A * (opponentHasShields ? opponentShields.absolute.total : 1);
weapon.damage.armour.absolute += sDps * m.getDamageDist().A * armourMultiple * opponentArmour.absolute.total;
shieldsResistance += m.getDamageDist().A * (opponentHasShields ? opponentShields.absolute.generator * opponentShields.absolute.boosters : 1);
armourResistance += m.getDamageDist().A * opponentArmour.absolute.bulkheads * opponentArmour.absolute.reinforcement;
}
if (m.getDamageDist().E) {
weapon.damage.shields.explosive += sDps * m.getDamageDist().E * (opponentHasShields ? opponentShields.explosive.total : 1);
weapon.damage.armour.explosive += sDps * m.getDamageDist().E * armourMultiple * opponentArmour.explosive.total;
shieldsResistance += m.getDamageDist().E * (opponentHasShields ? opponentShields.explosive.generator * opponentShields.explosive.boosters : 1);
armourResistance += m.getDamageDist().E * opponentArmour.explosive.bulkheads * opponentArmour.explosive.reinforcement;
}
if (m.getDamageDist().K) {
weapon.damage.shields.kinetic += sDps * m.getDamageDist().K * (opponentHasShields ? opponentShields.kinetic.total : 1);
weapon.damage.armour.kinetic += sDps * m.getDamageDist().K * armourMultiple * opponentArmour.kinetic.total;
shieldsResistance += m.getDamageDist().K * (opponentHasShields ? opponentShields.kinetic.generator * opponentShields.kinetic.boosters : 1);
armourResistance += m.getDamageDist().K * opponentArmour.kinetic.bulkheads * opponentArmour.kinetic.reinforcement;
}
if (m.getDamageDist().T) {
weapon.damage.shields.thermal += sDps * m.getDamageDist().T * (opponentHasShields ? opponentShields.thermal.total : 1);
weapon.damage.armour.thermal += sDps * m.getDamageDist().T * armourMultiple * opponentArmour.thermal.total;
shieldsResistance += m.getDamageDist().T * (opponentHasShields ? opponentShields.thermal.generator * opponentShields.thermal.boosters : 1);
armourResistance += m.getDamageDist().T * opponentArmour.thermal.bulkheads * opponentArmour.thermal.reinforcement;
}
weapon.damage.shields.total = weapon.damage.shields.absolute + weapon.damage.shields.explosive + weapon.damage.shields.kinetic + weapon.damage.shields.thermal;
weapon.damage.armour.total = weapon.damage.armour.absolute + weapon.damage.armour.explosive + weapon.damage.armour.kinetic + weapon.damage.armour.thermal;
weapon.effectiveness.shields.resistance *= shieldsResistance;
weapon.effectiveness.armour.resistance *= armourResistance;
weapon.effectiveness.shields.total = weapon.effectiveness.shields.range * weapon.effectiveness.shields.sys * weapon.effectiveness.shields.resistance;
weapon.effectiveness.armour.total = weapon.effectiveness.armour.range * weapon.effectiveness.armour.resistance * weapon.effectiveness.armour.hardness;
return weapon;
}
/**
* Calculate time to drain WEP capacitor
* @param {object} ship The ship
* @param {number} wep Pips to WEP
* @returns {number} The time to drain the WEP capacitor, in seconds
*/
export function timeToDrainWep(ship, wep) {
let totalSEps = 0;
for (let slotNum in ship.hardpoints) {
const slot = ship.hardpoints[slotNum];
if (slot.maxClass > 0 && slot.m && slot.enabled && slot.type === 'WEP' && slot.m.getDps()) {
totalSEps += slot.m.getClip() ? (slot.m.getClip() * slot.m.getEps() / slot.m.getRoF()) / ((slot.m.getClip() / slot.m.getRoF()) + slot.m.getReload()) : slot.m.getEps();
}
}
// Calculate the drain time
const drainPerSecond = totalSEps - ship.standard[4].m.getWeaponsRechargeRate() * wep / 4;
if (drainPerSecond <= 0) {
// Can fire forever
return Infinity;
} else {
const initialCharge = ship.standard[4].m.getWeaponsCapacity();
return initialCharge / drainPerSecond;
}
}
/**
* Calculate the time to deplete an amount of shields or armour
* @param {number} amount The amount to be depleted
* @param {number} dps The depletion per second
* @param {number} eps The energy drained per second
* @param {number} capacity The initial energy capacity
* @param {number} recharge The energy recharged per second
* @returns {number} The number of seconds to deplete to 0
*/
export function timeToDeplete(amount, dps, eps, capacity, recharge) {
const drainPerSecond = eps - recharge;
if (drainPerSecond <= 0) {
// Simple result
return amount / dps;
} else {
// We are draining the capacitor, but can we deplete before we run out
const timeToDrain = capacity / drainPerSecond;
const depletedBeforeDrained = dps * timeToDrain;
if (depletedBeforeDrained >= amount) {
return amount / dps;
} else {
const restToDeplete = amount - depletedBeforeDrained;
// We delete the rest at the reduced rate
const reducedDps = dps * (recharge / eps);
return timeToDrain + (restToDeplete / reducedDps);
}
}
}

View File

@@ -47,6 +47,7 @@ export const ModuleGroupToName = {
pcm: 'First Class Passenger Cabin',
pcq: 'Luxury Passenger Cabin',
cc: 'Collector Limpet Controller',
ss: 'Surface Scanner',
// Hard Points
bl: 'Beam Laser',
@@ -104,115 +105,115 @@ export const BulkheadNames = [
* @type {Array}
*/
export const ShipFacets = [
{ // 0
title: 'agility',
props: ['topPitch', 'topRoll', 'topYaw'],
lbls: ['pitch', 'roll', 'yaw'],
fmt: 'f1',
i: 0
},
{ // 1
title: 'speed',
props: ['topSpeed', 'topBoost'],
lbls: ['thrusters', 'boost'],
unit: 'm/s',
fmt: 'int',
i: 1
},
{ // 2
title: 'armour',
props: ['armour'],
fmt: 'int',
i: 2
},
{ // 3
title: 'shields',
props: ['shield'],
unit: 'MJ',
fmt: 'int',
i: 3
},
{ // 4
title: 'jump range',
props: ['unladenRange', 'fullTankRange', 'ladenRange'],
lbls: ['max', 'full tank', 'laden'],
unit: 'LY',
fmt: 'round',
i: 4
},
{ // 5
title: 'mass',
props: ['unladenMass', 'ladenMass'],
lbls: ['unladen', 'laden'],
unit: 'T',
fmt: 'round',
i: 5
},
{ // 6
title: 'cargo',
props: ['cargoCapacity'],
unit: 'T',
fmt: 'int',
i: 6
},
{ // 7
title: 'fuel',
props: ['fuelCapacity'],
unit: 'T',
fmt: 'int',
i: 7
},
{ // 8
title: 'power',
props: ['powerRetracted', 'powerDeployed', 'powerAvailable'],
lbls: ['retracted', 'deployed', 'available'],
unit: 'MW',
fmt: 'f2',
i: 8
},
{ // 9
title: 'cost',
props: ['totalCost'],
unit: 'CR',
fmt: 'int',
i: 9
},
{ // 10
title: 'fastest range',
props: ['unladenFastestRange', 'ladenFastestRange'],
lbls: ['unladen', 'laden'],
unit: 'LY',
fmt: 'round',
i: 10
},
{ // 11
title: '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
}
{ // 0
title: 'agility',
props: ['topPitch', 'topRoll', 'topYaw'],
lbls: ['pitch', 'roll', 'yaw'],
fmt: 'f1',
i: 0
},
{ // 1
title: 'speed',
props: ['topSpeed', 'topBoost'],
lbls: ['thrusters', 'boost'],
unit: 'm/s',
fmt: 'int',
i: 1
},
{ // 2
title: 'armour',
props: ['armour'],
fmt: 'int',
i: 2
},
{ // 3
title: 'shields',
props: ['shield'],
unit: 'MJ',
fmt: 'int',
i: 3
},
{ // 4
title: 'jump range',
props: ['unladenRange', 'fullTankRange', 'ladenRange'],
lbls: ['max', 'full tank', 'laden'],
unit: 'LY',
fmt: 'round',
i: 4
},
{ // 5
title: 'mass',
props: ['unladenMass', 'ladenMass'],
lbls: ['unladen', 'laden'],
unit: 'T',
fmt: 'round',
i: 5
},
{ // 6
title: 'cargo',
props: ['cargoCapacity'],
unit: 'T',
fmt: 'int',
i: 6
},
{ // 7
title: 'fuel',
props: ['fuelCapacity'],
unit: 'T',
fmt: 'int',
i: 7
},
{ // 8
title: 'power',
props: ['powerRetracted', 'powerDeployed', 'powerAvailable'],
lbls: ['retracted', 'deployed', 'available'],
unit: 'MW',
fmt: 'f2',
i: 8
},
{ // 9
title: 'cost',
props: ['totalCost'],
unit: 'CR',
fmt: 'int',
i: 9
},
{ // 10
title: 'fastest range',
props: ['unladenFastestRange', 'ladenFastestRange'],
lbls: ['unladen', 'laden'],
unit: 'LY',
fmt: 'round',
i: 10
},
{ // 11
title: '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

@@ -1,5 +1,4 @@
import * as ModuleUtils from './ModuleUtils';
import * as _ from 'lodash';
import { Modifications } from 'coriolis-data/dist';
/**
@@ -36,88 +35,133 @@ export default class Module {
/**
* Get a value for a given modification
* @param {Number} name The name of the modification
* @return {object} The value of the modification. If it is a numeric value then it is returned as an integer value scaled so that 1.23% == 123
* @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) {
return this.mods && this.mods[name] ? this.mods[name] : null;
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] * 100;
} 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 {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) {
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 {
if (isNaN(value)) {
this.mods[name] = value;
} else {
// Round just to be sure
this.mods[name] = Math.round(value);
}
this.mods[name] = value;
}
}
/**
* Helper to obtain a modified value using standard multipliers
* @param {String} name the name of the modifier to obtain
* @param {Boolean} additive Optional true if the value is additive rather than multiplicative
* @return {Number} the mass of this module
*/
_getModifiedValue(name, additive) {
let result = this[name] || (additive ? 0 : null); // Additive NULL === 0
if (result != null) {
const modification = Modifications.modifications[name];
if (!modification) {
return result;
}
// We store percentages as decimals, so to get them back we need to divide by 10000. Otherwise
// we divide by 100. Both ways we end up with a value with two decimal places
let modValue;
if (modification.type === 'percentage') {
modValue = this.getModValue(name) / 10000;
} else if (modification.type === 'numeric') {
modValue = this.getModValue(name) / 100;
_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 {
modValue = this.getModValue(name);
result = null;
}
if (modValue) {
if (additive) {
result = result + modValue;
}
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 {
result = result * (1 + modValue);
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
const modValue = this.getModValue(name) / 100;
return modValue;
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
const modValue = this.getModValue(name) / 100;
return modValue;
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
@@ -158,30 +202,6 @@ export default class Module {
return this._getModifiedValue('eff');
}
/**
* Get the maximum mass of this module, taking in to account modifications
* @return {Number} the maximum mass of this module
*/
getMaxMass() {
return this._getModifiedValue('maxmass');
}
/**
* Get the optimal mass of this module, taking in to account modifications
* @return {Number} the optimal mass of this module
*/
getOptimalMass() {
return this._getModifiedValue('optmass');
}
/**
* Get the optimal multiplier of this module, taking in to account modifications
* @return {Number} the optimal multiplier of this module
*/
getOptimalMultiplier() {
return this._getModifiedValue('optmult');
}
/**
* Get the maximum fuel per jump for this module, taking in to account modifications
* @return {Number} the maximum fuel per jump of this module
@@ -243,7 +263,7 @@ export default class Module {
* @return {Number} the kinetic resistance of this module
*/
getKineticResistance() {
return this._getModifiedValue('kinres', true);
return this._getModifiedValue('kinres');
}
/**
@@ -251,7 +271,7 @@ export default class Module {
* @return {Number} the thermal resistance of this module
*/
getThermalResistance() {
return this._getModifiedValue('thermres', true);
return this._getModifiedValue('thermres');
}
/**
@@ -259,7 +279,7 @@ export default class Module {
* @return {Number} the explosive resistance of this module
*/
getExplosiveResistance() {
return this._getModifiedValue('explres', true);
return this._getModifiedValue('explres');
}
/**
@@ -292,11 +312,19 @@ export default class Module {
*/
getFalloff() {
if (this.getModValue('fallofffromrange')) {
// Falloff from range means what it says, so use range instead of falloff
return this.getRange();
} else {
const falloff = this._getModifiedValue('falloff');
const range = this.getRange();
return (falloff > range ? range : falloff);
// 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);
}
}
}
@@ -308,6 +336,14 @@ export default class Module {
return this._getModifiedValue('ranget');
}
/**
* Get the scan time for this module, taking in to account modifications
* @return {Number} the scan time of this module
*/
getScanTime() {
return this._getModifiedValue('scantime');
}
/**
* Get the capture arc for this module, taking in to account modifications
* @return {Number} the capture arc of this module
@@ -510,10 +546,10 @@ export default class Module {
getEps() {
// EPS is a synthetic value
let distdraw = this.getDistDraw();
let rpshot = this.roundspershot || 1;
// We don't use rpshot here as dist draw is per combined shot
let rof = this.getRoF() || 1;
return distdraw * rpshot * rof;
return distdraw * rof;
}
/**
@@ -523,10 +559,10 @@ export default class Module {
getHps() {
// HPS is a synthetic value
let heat = this.getThermalLoad();
let rpshot = this.roundspershot || 1;
// We don't use rpshot here as dist draw is per combined shot
let rof = this.getRoF() || 1;
return heat * rpshot * rof;
return heat * rof;
}
/**
@@ -534,7 +570,10 @@ export default class Module {
* @return {Number} the clip size of this module
*/
getClip() {
return this._getModifiedValue('clip');
// Clip size is always rounded up
let result = this._getModifiedValue('clip');
if (result) { result = Math.ceil(result); }
return result;
}
/**
@@ -633,14 +672,6 @@ export default class Module {
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
@@ -659,9 +690,35 @@ export default class Module {
/**
* Get the shot speed for this module, taking in to account modifications
* @return {string} the damage distribution for this module
* @return {string} the shot speed for this module
*/
getShotSpeed() {
if (this.blueprint && (this.blueprint.name === 'Focused' || this.blueprint.name === 'Long range')) {
// If the modification is focused or long range then the shot speed
// uses the range modifier
const rangemod = this.getModValue('range') / 10000;
let result = this['shotspeed'];
if (!result) {
return null;
}
return result * (1 + rangemod);
}
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

@@ -119,20 +119,33 @@ export default class ModuleSet {
/**
* Find the lightest Power Distributor that provides sufficient
* energy to boost.
* @param {number} boostEnergy [description]
* @param {number} boostEnergy The energy that is required to boost
* @return {Object} Power Distributor
*/
lightestPowerDist(boostEnergy) {
let pd = this.standard[4][0];
for (let p of this.standard[4]) {
if (p.mass < pd.mass && p.engcap >= boostEnergy) {
if (p.mass < pd.mass && p.engcap > boostEnergy) {
pd = p;
}
}
return new Module({ template: pd });
};
/** Find the power distributor that matches the requirements
* @param {Object} requirements The requirements to be met (currently only support 'weprate')
* @return {Object} Power distributor
*/
matchingPowerDist(requirements) {
let pd = this.standard[4][0];
for (let p of this.standard[4]) {
if (p.weprate >= requirements.weprate || p.weprate >= pd.weprate) {
pd = p;
}
}
return new Module({ template: pd });
}
/**
* Finds the lightest Thruster that can handle the specified tonnage
* @param {number} ladenMass Ship laden mass (mass + cargo + fuel)
@@ -168,14 +181,15 @@ export default class ModuleSet {
/**
* Find the lightest Power Plant that provides sufficient power
* @param {number} powerNeeded Power requirements in MJ
* @param {string} rating The optional rating of the power plant
* @return {Object} Power Plant
*/
lightestPowerPlant(powerNeeded) {
lightestPowerPlant(powerNeeded, rating) {
let pp = this.standard[0][0];
for (let p of this.standard[0]) {
// Provides enough power, is lighter or the same mass as current power plant but better output/efficiency
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)) && (!rating || rating == p.rating)) {
pp = p;
}
}

View File

@@ -1,15 +1,10 @@
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
@@ -90,7 +85,7 @@ export function toDetailedBuild(buildName, ship) {
code = ship.toString();
let data = {
$schema: 'http://cdn.coriolis.io/schemas/ship-loadout/4.json#',
$schema: 'https://coriolis.edcd.io/schemas/ship-loadout/4.json#',
name: buildName,
ship: ship.name,
references: [{

View File

@@ -1,6 +1,7 @@
import * as Calc from './Calculations';
import * as ModuleUtils from './ModuleUtils';
import * as Utils from '../utils/UtilityFunctions';
import { getBlueprint } from '../utils/BlueprintFunctions';
import Module from './Module';
import LZString from 'lz-string';
import * as _ from 'lodash';
@@ -122,31 +123,24 @@ export default class Ship {
/**
* Can the ship thrust/move
* @param {Number} cargo Amount of cargo in the ship
* @param {Number} fuel Amount of fuel in the ship
* @return {[type]} True if thrusters operational
*/
canThrust() {
canThrust(cargo, fuel) {
return this.getSlotStatus(this.standard[1]) == 3 && // Thrusters are powered
this.ladenMass < this.standard[1].m.getMaxMass(); // Max mass not exceeded
this.unladenMass + cargo + fuel < this.standard[1].m.getMaxMass(); // Max mass not exceeded
}
/**
* Can the ship boost
* @param {Number} cargo Amount of cargo in the ship
* @param {Number} fuel Amount of fuel in the ship
* @return {[type]} True if boost capable
*/
canBoost() {
return this.canThrust() && // Thrusters operational
this.boostEnergy <= this.standard[4].m.getEnginesCapacity(); // PD capacitor is sufficient for boost
}
/**
* Calculate hypothetical jump range using the installed FSD and the
* specified mass which can be more or less than ships actual mass
* @param {Number} fuel Fuel available in tons
* @param {Number} cargo Cargo in tons
* @return {Number} Jump range in Light Years
*/
calcJumpRangeWith(fuel, cargo) {
return Calc.jumpRange(this.unladenMass + fuel + cargo, this.standard[2].m, fuel);
canBoost(cargo, fuel) {
return this.canThrust(cargo, fuel) && // Thrusters operational
this.standard[4].m.getEnginesCapacity() > this.boostEnergy; // PD capacitor is sufficient for boost
}
/**
@@ -173,17 +167,6 @@ export default class Ship {
return Calc.jumpRange(this.unladenMass + (massDelta || 0) + Math.min(fsdMaxFuelPerJump, fuel || this.fuelCapacity), fsd || this.standard[2].m, fuel);
}
/**
* Calculate cumulative (total) jump range when making longest jumps using the installed FSD and the
* specified mass which can be more or less than ships actual mass
* @param {Number} fuel Fuel available in tons
* @param {Number} cargo Cargo in tons
* @return {Number} Total/Cumulative Jump range in Light Years
*/
calcFastestRangeWith(fuel, cargo) {
return Calc.fastestRange(this.unladenMass + fuel + cargo, this.standard[2].m, fuel);
}
/**
* Calculate the hypothetical top speeds at cargo and fuel tonnage
* @param {Number} fuel Fuel available in tons
@@ -195,36 +178,51 @@ export default class Ship {
}
/**
* Calculate the recovery time after losing or turning on shields
* Thanks to CMDRs Al Gray, GIF, and Nomad Enigma for providing Shield recharge data and formulas
*
* @return {Number} Recovery time in seconds
* Calculate the speed for a given configuration
* @param {Number} eng Number of pips in ENG
* @param {Number} fuel Amount of fuel carried
* @param {Number} cargo Amount of cargo carried
* @param {boolean} boost true if boost is applied
* @return {Number} Speed
*/
calcShieldRecovery() {
const shieldGenerator = this.findShieldGenerator();
if (shieldGenerator) {
const brokenRegenRate = shieldGenerator.getBrokenRegenerationRate();
// 50% of shield strength / broken recharge rate + 15 second delay before recharge starts
return ((this.shield / 2) / brokenRegenRate) + 15;
}
return 0;
calcSpeed(eng, fuel, cargo, boost) {
return Calc.calcSpeed(this.unladenMass + fuel + cargo, this.speed, this.standard[1].m, this.pipSpeed, eng, this.boost / this.speed, boost);
}
/**
* Calculate the recharge time for a shield going from 50% to 100%
* Thanks to CMDRs Al Gray, GIF, and Nomad Enigma for providing Shield recharge data and formulas
*
* @return {Number} 50 - 100% Recharge time in seconds
* Calculate the pitch for a given configuration
* @param {Number} eng Number of pips in ENG
* @param {Number} fuel Amount of fuel carried
* @param {Number} cargo Amount of cargo carried
* @param {boolean} boost true if boost is applied
* @return {Number} Pitch
*/
calcShieldRecharge() {
const shieldGenerator = this.findShieldGenerator();
if (shieldGenerator) {
const regenRate = shieldGenerator.getRegenerationRate();
calcPitch(eng, fuel, cargo, boost) {
return Calc.calcPitch(this.unladenMass + fuel + cargo, this.pitch, this.standard[1].m, this.pipSpeed, eng, this.boost / this.speed, boost);
}
// 50% of shield strength / recharge rate
return (this.shield / 2) / regenRate;
}
return 0;
/**
* Calculate the roll for a given configuration
* @param {Number} eng Number of pips in ENG
* @param {Number} fuel Amount of fuel carried
* @param {Number} cargo Amount of cargo carried
* @param {boolean} boost true if boost is applied
* @return {Number} Roll
*/
calcRoll(eng, fuel, cargo, boost) {
return Calc.calcRoll(this.unladenMass + fuel + cargo, this.roll, this.standard[1].m, this.pipSpeed, eng, this.boost / this.speed, boost);
}
/**
* Calculate the yaw for a given configuration
* @param {Number} eng Number of pips in ENG
* @param {Number} fuel Amount of fuel carried
* @param {Number} cargo Amount of cargo carried
* @param {boolean} boost true if boost is applied
* @return {Number} Yaw
*/
calcYaw(eng, fuel, cargo, boost) {
return Calc.calcYaw(this.unladenMass + fuel + cargo, this.yaw, this.standard[1].m, this.pipSpeed, eng, this.boost / this.speed, boost);
}
/**
@@ -419,6 +417,7 @@ export default class Ship {
m.mods = {};
this.updatePowerGenerated()
.updatePowerUsed()
.recalculateMass()
.updateJumpStats()
.recalculateShield()
.recalculateShieldCells()
@@ -430,20 +429,63 @@ export default class Ship {
}
/**
* Clear blueprint for a module
* @param {Number} m The module for which to clear the modifications
* Set blueprint for a module
* @param {Object} m The module for which to set the blueprint
* @param {Object} bp The blueprint
*/
clearBlueprint(m) {
m.blueprint = {};
setModuleBlueprint(m, bp) {
m.blueprint = bp;
this.clearModifications(m);
// Set any hidden items for the blueprint now
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
if (Modifications.modifications[featureName].hidden) {
this.setModification(m, featureName, bp.grades[bp.grade].features[featureName][0]);
}
}
this.updateModificationsString();
}
/**
* Set a modification value
* @param {Object} m The module to change
* @param {Object} name The name of the modification to change
* @param {Number} value The new value of the modification. The value of the modification is scaled to provide two decimal places of precision in an integer. For example 1.23% is stored as 123
* Clear blueprint for a module
* @param {Object} m The module for which to clear the blueprint
*/
setModification(m, name, value) {
clearModuleBlueprint(m) {
m.blueprint = {};
this.updateModificationsString();
}
/**
* Set special for a module
* @param {Object} m The module for which to set the blueprint
* @param {Object} special The special
*/
setModuleSpecial(m, special) {
if (m.blueprint) {
m.blueprint.special = special;
}
this.recalculateDps().recalculateHps().recalculateEps();
}
/**
* Clear special for a module
* @param {Object} m The module for which to clear the blueprint
*/
clearModuleSpecial(m) {
if (m.blueprint) {
m.blueprint.special = null;
}
this.recalculateDps().recalculateHps().recalculateEps();
}
/**
* Set a modification value and update ship stats
* @param {Object} m The module to change
* @param {Object} name The name of the modification to change
* @param {Number} value The new value of the modification. The value of the modification is scaled to provide two decimal places of precision in an integer. For example 1.23% is stored as 123
* @param {bool} sentfromui True if this update was sent from the UI
*/
setModification(m, name, value, sentfromui) {
if (isNaN(value)) {
// Value passed is invalid; reset it to 0
value = 0;
@@ -452,58 +494,59 @@ export default class Ship {
// Handle special cases
if (name === 'pgen') {
// Power generation
m.setModValue(name, value);
m.setModValue(name, value, sentfromui);
this.updatePowerGenerated();
} else if (name === 'power') {
// Power usage
m.setModValue(name, value);
m.setModValue(name, value, sentfromui);
this.updatePowerUsed();
} else if (name === 'mass') {
// Mass
let oldMass = m.getMass();
m.setModValue(name, value);
let newMass = m.getMass();
this.unladenMass = this.unladenMass - oldMass + newMass;
this.ladenMass = this.ladenMass - oldMass + newMass;
m.setModValue(name, value, sentfromui);
this.recalculateMass();
this.updateMovement();
this.updateJumpStats();
} else if (name === 'maxfuel') {
m.setModValue(name, value);
m.setModValue(name, value, sentfromui);
this.updateJumpStats();
} else if (name === 'optmass') {
m.setModValue(name, value);
m.setModValue(name, value, sentfromui);
// Could be for any of thrusters, FSD or shield
this.updateMovement();
this.updateJumpStats();
this.recalculateShield();
} else if (name === 'optmul') {
m.setModValue(name, value);
m.setModValue(name, value, sentfromui);
// Could be for any of thrusters, FSD or shield
this.updateMovement();
this.updateJumpStats();
this.recalculateShield();
} else if (name === 'shieldboost') {
m.setModValue(name, value);
m.setModValue(name, value, sentfromui);
this.recalculateShield();
} else if (name === 'hullboost' || name === 'hullreinforcement' || name === 'modulereinforcement') {
m.setModValue(name, value);
m.setModValue(name, value, sentfromui);
this.recalculateArmour();
} else if (name === 'shieldreinforcement') {
m.setModValue(name, value);
m.setModValue(name, value, sentfromui);
this.recalculateShieldCells();
} else if (name === 'burst' || name == 'burstrof' || name === 'clip' || name === 'damage' || name === 'distdraw' || name === 'jitter' || name === 'piercing' || name === 'range' || name === 'reload' || name === 'rof' || name === 'thermload') {
m.setModValue(name, value);
m.setModValue(name, value, sentfromui);
this.recalculateDps();
this.recalculateHps();
this.recalculateEps();
} else if (name === 'explres' || name === 'kinres' || name === 'thermres') {
m.setModValue(name, value);
m.setModValue(name, value, sentfromui);
// Could be for shields or armour
this.recalculateArmour();
this.recalculateShield();
} else if (name === 'engcap') {
m.setModValue(name, value, sentfromui);
// Might have resulted in a change in boostability
this.updateMovement();
} else {
// Generic
m.setModValue(name, value);
m.setModValue(name, value, sentfromui);
}
}
@@ -533,14 +576,17 @@ export default class Ship {
this.totalCost = this.m.incCost ? this.m.discountedCost : 0;
this.unladenMass = this.hullMass;
this.totalDpe = 0;
this.totalAbsDpe = 0;
this.totalExplDpe = 0;
this.totalKinDpe = 0;
this.totalThermDpe = 0;
this.totalDps = 0;
this.totalAbsDps = 0;
this.totalExplDps = 0;
this.totalKinDps = 0;
this.totalThermDps = 0;
this.totalSDps = 0;
this.totalAbsSDps = 0;
this.totalExplSDps = 0;
this.totalKinSDps = 0;
this.totalThermSDps = 0;
@@ -556,13 +602,18 @@ export default class Ship {
this.bulkheads.m = null;
this.useBulkhead(comps && comps.bulkheads ? comps.bulkheads : 0, true);
this.bulkheads.m.mods = mods && mods[0] ? mods[0] : {};
this.bulkheads.m.blueprint = blueprints && blueprints[0] ? blueprints[0] : {};
if (blueprints && blueprints[0]) {
this.bulkheads.m.blueprint = getBlueprint(blueprints[0].fdname, this.bulkheads.m);
this.bulkheads.m.blueprint.grade = blueprints[0].grade;
this.bulkheads.m.blueprint.special = blueprints[0].special;
} else {
this.bulkheads.m.blueprint = {};
}
this.cargoHatch.priority = priorities ? priorities[0] * 1 : 0;
this.cargoHatch.enabled = enabled ? enabled[0] * 1 : true;
for (i = 0; i < cl; i++) {
standard[i].cat = 0;
standard[i].enabled = enabled ? enabled[i + 1] * 1 : true;
standard[i].priority = priorities && priorities[i + 1] ? priorities[i + 1] * 1 : 0;
standard[i].type = 'SYS';
standard[i].m = null; // Resetting 'old' modul if there was one
@@ -571,10 +622,17 @@ export default class Ship {
let module = ModuleUtils.standard(i, comps.standard[i]);
if (module != null) {
module.mods = mods && mods[i + 1] ? mods[i + 1] : {};
module.blueprint = blueprints && blueprints[i + 1] ? blueprints[i + 1] : {};
if (blueprints && blueprints[i + 1]) {
module.blueprint = getBlueprint(blueprints[i + 1].fdname, module);
module.blueprint.grade = blueprints[i + 1].grade;
module.blueprint.special = blueprints[i + 1].special;
} else {
module.blueprint = {};
}
}
this.use(standard[i], module, true);
}
standard[i].enabled = enabled ? enabled[i + 1] * 1 : true;
}
standard[1].type = 'ENG'; // Thrusters
@@ -583,7 +641,6 @@ export default class Ship {
for (i = 0, l = hps.length; i < l; i++) {
hps[i].cat = 1;
hps[i].enabled = enabled ? enabled[cl + i] * 1 : true;
hps[i].priority = priorities && priorities[cl + i] ? priorities[cl + i] * 1 : 0;
hps[i].type = hps[i].maxClass ? 'WEP' : 'SYS';
hps[i].m = null; // Resetting 'old' modul if there was one
@@ -593,17 +650,23 @@ export default class Ship {
let module = ModuleUtils.hardpoints(comps.hardpoints[i]);
if (module != null) {
module.mods = mods && mods[cl + i] ? mods[cl + i] : {};
module.blueprint = blueprints && blueprints[cl + i] ? blueprints[cl + i] : {};
if (blueprints && blueprints[cl + i]) {
module.blueprint = getBlueprint(blueprints[cl + i].fdname, module);
module.blueprint.grade = blueprints[cl + i].grade;
module.blueprint.special = blueprints[cl + i].special;
} else {
module.blueprint = {};
}
}
this.use(hps[i], module, true);
}
hps[i].enabled = enabled ? enabled[cl + i] * 1 : true;
}
cl += hps.length; // Increase accounts for hardpoints
for (i = 0, l = internal.length; i < l; i++) {
internal[i].cat = 2;
internal[i].enabled = enabled ? enabled[cl + i] * 1 : true;
internal[i].priority = priorities && priorities[cl + i] ? priorities[cl + i] * 1 : 0;
internal[i].type = 'SYS';
internal[i].m = null; // Resetting 'old' modul if there was one
@@ -613,16 +676,24 @@ export default class Ship {
let module = ModuleUtils.internal(comps.internal[i]);
if (module != null) {
module.mods = mods && mods[cl + i] ? mods[cl + i] : {};
module.blueprint = blueprints && blueprints[cl + i] ? blueprints[cl + i] : {};
if (blueprints && blueprints[cl + i]) {
module.blueprint = getBlueprint(blueprints[cl + i].fdname, module);
module.blueprint.grade = blueprints[cl + i].grade;
module.blueprint.special = blueprints[cl + i].special;
} else {
module.blueprint = {};
}
}
this.use(internal[i], module, true);
}
internal[i].enabled = enabled ? enabled[cl + i] * 1 : true;
}
// Update aggragated stats
if (comps) {
this.updatePowerGenerated()
.updatePowerUsed()
.recalculateMass()
.updateJumpStats()
.recalculateShield()
.recalculateShieldCells()
@@ -644,6 +715,11 @@ export default class Ship {
* @return {this} The current ship instance for chaining
*/
buildFrom(serializedString) {
if (!serializedString) {
// Empty serialized string; nothing to do
return this;
}
let standard = new Array(this.standard.length),
hardpoints = new Array(this.hardpoints.length),
internal = new Array(this.internal.length),
@@ -825,7 +901,6 @@ export default class Ship {
*/
setSlotPriority(slot, newPriority) {
if (newPriority >= 0 && newPriority < this.priorityBands.length) {
let oldPriority = slot.priority;
slot.priority = newPriority;
this.updatePowerPrioritesString();
@@ -847,6 +922,7 @@ export default class Ship {
*/
updateStats(slot, n, old, preventUpdate) {
let powerGeneratedChange = slot == this.standard[0];
let powerDistributorChange = slot == this.standard[4];
let powerUsedChange = false;
let dpsChanged = n && n.getDps() || old && old.getDps();
let epsChanged = n && n.getEps() || old && old.getEps();
@@ -859,15 +935,6 @@ export default class Ship {
let shieldCellsChange = (n && n.grp === 'scb') || (old && old.grp === 'scb');
if (old) { // Old modul now being removed
switch (old.grp) {
case 'ft':
this.fuelCapacity -= old.fuel;
break;
case 'cr':
this.cargoCapacity -= old.cargo;
break;
}
if (slot.incCost && old.cost) {
this.totalCost -= old.cost * this.moduleCostMultiplier;
}
@@ -875,20 +942,9 @@ export default class Ship {
if (old.getPowerUsage() > 0 && slot.enabled) {
powerUsedChange = true;
}
this.unladenMass -= old.getMass() || 0;
}
if (n) {
switch (n.grp) {
case 'ft':
this.fuelCapacity += n.fuel;
break;
case 'cr':
this.cargoCapacity += n.cargo;
break;
}
if (slot.incCost && n.cost) {
this.totalCost += n.cost * this.moduleCostMultiplier;
}
@@ -896,13 +952,11 @@ export default class Ship {
if (n.power && slot.enabled) {
powerUsedChange = true;
}
this.unladenMass += n.getMass() || 0;
}
this.ladenMass = this.unladenMass + this.cargoCapacity + this.fuelCapacity;
if (!preventUpdate) {
// Must recalculate mass first, as movement, jump etc. relies on it
this.recalculateMass();
if (dpsChanged) {
this.recalculateDps();
}
@@ -953,26 +1007,29 @@ export default class Ship {
}
/**
* Calculate damage per second for weapons
* Calculate damage per second and related items for weapons
* @return {this} The ship instance (for chaining operations)
*/
recalculateDps() {
let totalDpe = 0;
let totalAbsDpe = 0;
let totalExplDpe = 0;
let totalKinDpe = 0;
let totalThermDpe = 0;
let totalDps = 0;
let totalAbsDps = 0;
let totalExplDps = 0;
let totalKinDps = 0;
let totalThermDps = 0;
let totalSDps = 0;
let totalAbsSDps = 0;
let totalExplSDps = 0;
let totalKinSDps = 0;
let totalThermSDps = 0;
for (let slotNum in this.hardpoints) {
const slot = this.hardpoints[slotNum];
if (slot.m && slot.enabled && slot.m.getDps()) {
if (slot.m && slot.enabled && slot.type === 'WEP' && slot.m.getDps()) {
const dpe = slot.m.getEps() === 0 ? 0 : slot.m.getDps() / slot.m.getEps();
const dps = slot.m.getDps();
const sdps = slot.m.getClip() ? (slot.m.getClip() * slot.m.getDps() / slot.m.getRoF()) / ((slot.m.getClip() / slot.m.getRoF()) + slot.m.getReload()) : dps;
@@ -981,6 +1038,11 @@ export default class Ship {
totalDps += dps;
totalSDps += sdps;
if (slot.m.getDamageDist()) {
if (slot.m.getDamageDist().A) {
totalAbsDpe += dpe * slot.m.getDamageDist().A;
totalAbsDps += dps * slot.m.getDamageDist().A;
totalAbsSDps += sdps * slot.m.getDamageDist().A;
}
if (slot.m.getDamageDist().E) {
totalExplDpe += dpe * slot.m.getDamageDist().E;
totalExplDps += dps * slot.m.getDamageDist().E;
@@ -1001,14 +1063,17 @@ export default class Ship {
}
this.totalDpe = totalDpe;
this.totalAbsDpe = totalAbsDpe;
this.totalExplDpe = totalExplDpe;
this.totalKinDpe = totalKinDpe;
this.totalThermDpe = totalThermDpe;
this.totalDps = totalDps;
this.totalAbsDps = totalAbsDps;
this.totalExplDps = totalExplDps;
this.totalKinDps = totalKinDps;
this.totalThermDps = totalThermDps;
this.totalSDps = totalSDps;
this.totalAbsSDps = totalAbsSDps;
this.totalExplSDps = totalExplSDps;
this.totalKinSDps = totalKinSDps;
this.totalThermSDps = totalThermSDps;
@@ -1025,7 +1090,7 @@ export default class Ship {
for (let slotNum in this.hardpoints) {
const slot = this.hardpoints[slotNum];
if (slot.m && slot.enabled && slot.m.getHps()) {
if (slot.m && slot.enabled && slot.type === 'WEP' && slot.m.getHps()) {
totalHps += slot.m.getHps();
}
}
@@ -1043,7 +1108,7 @@ export default class Ship {
for (let slotNum in this.hardpoints) {
const slot = this.hardpoints[slotNum];
if (slot.m && slot.enabled && slot.m.getEps()) {
if (slot.m && slot.enabled && slot.m.getEps() && slot.type === 'WEP') {
totalEps += slot.m.getEps();
}
}
@@ -1115,6 +1180,55 @@ export default class Ship {
return this;
}
/**
* Eecalculate mass
* @return {this} The ship instance (for chaining operations)
*/
recalculateMass() {
let unladenMass = this.hullMass;
let cargoCapacity = 0;
let fuelCapacity = 0;
unladenMass += this.bulkheads.m.getMass();
for (let slotNum in this.standard) {
const slot = this.standard[slotNum];
if (slot.m) {
unladenMass += slot.m.getMass();
if (slot.m.grp === 'ft') {
fuelCapacity += slot.m.fuel;
}
}
}
for (let slotNum in this.internal) {
const slot = this.internal[slotNum];
if (slot.m) {
unladenMass += slot.m.getMass();
if (slot.m.grp === 'ft') {
fuelCapacity += slot.m.fuel;
} else if (slot.m.grp === 'cr') {
cargoCapacity += slot.m.cargo;
}
}
}
for (let slotNum in this.hardpoints) {
const slot = this.hardpoints[slotNum];
if (slot.m) {
unladenMass += slot.m.getMass();
}
}
// Update global stats
this.unladenMass = unladenMass;
this.cargoCapacity = cargoCapacity;
this.fuelCapacity = fuelCapacity;
this.ladenMass = unladenMass + fuelCapacity + cargoCapacity;
return this;
}
/**
* Update movement values
* @return {this} The ship instance (for chaining operations)
@@ -1122,7 +1236,7 @@ export default class Ship {
updateMovement() {
this.speeds = Calc.speed(this.unladenMass + this.fuelCapacity, this.speed, this.standard[1].m, this.pipSpeed);
this.topSpeed = this.speeds[4];
this.topBoost = this.canBoost() ? this.speeds[4] * this.boost / this.speed : 0;
this.topBoost = this.canBoost(0, 0) ? this.speeds[4] * this.boost / this.speed : 0;
this.pitches = Calc.pitch(this.unladenMass + this.fuelCapacity, this.pitch, this.standard[1].m, this.pipSpeed);
this.topPitch = this.pitches[4];
@@ -1141,54 +1255,13 @@ export default class Ship {
* @return {this} The ship instance (for chaining operations)
*/
recalculateShield() {
let shield = 0;
let shieldBoost = 1;
let shieldExplRes = null;
let shieldKinRes = null;
let shieldThermRes = null;
let shieldExplDRStart = null;
let shieldExplDREnd = null;
let shieldKinDRStart = null;
let shieldKinDREnd = null;
let shieldThermDRStart = null;
let shieldThermDREnd = null;
const sgSlot = this.findInternalByGroup('sg');
if (sgSlot && sgSlot.enabled) {
// Shield from generator
shield = Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sgSlot.m, 1);
shieldExplRes = 1 - sgSlot.m.getExplosiveResistance();
shieldExplDRStart = shieldExplRes * 0.7;
shieldExplDREnd = shieldExplRes * 0; // Currently don't know where this is
shieldKinRes = 1 - sgSlot.m.getKineticResistance();
shieldKinDRStart = shieldKinRes * 0.7;
shieldKinDREnd = shieldKinRes * 0; // Currently don't know where this is
shieldThermRes = 1 - sgSlot.m.getThermalResistance();
shieldThermDRStart = shieldThermRes * 0.7;
shieldThermDREnd = shieldThermRes * 0; // Currently don't know where this is
// Shield from boosters
for (let slot of this.hardpoints) {
if (slot.enabled && slot.m && slot.m.grp == 'sb') {
shieldBoost += slot.m.getShieldBoost();
shieldExplRes *= (1 - slot.m.getExplosiveResistance());
shieldKinRes *= (1 - slot.m.getKineticResistance());
shieldThermRes *= (1 - slot.m.getThermalResistance());
}
}
}
// We apply diminishing returns to the boosted value
// (no we don't; FD pulled back on this idea. But leave this here in case they reinstate it)
// shieldBoost = Math.min(shieldBoost, (1 - Math.pow(Math.E, -0.7 * shieldBoost)) * 2.5);
shield = shield * shieldBoost;
this.shield = shield;
this.shieldExplRes = shieldExplRes ? 1 - this.diminishingReturns(shieldExplRes, shieldExplDREnd, shieldExplDRStart) : null;
this.shieldKinRes = shieldKinRes ? 1 - this.diminishingReturns(shieldKinRes, shieldKinDREnd, shieldKinDRStart) : null;
this.shieldThermRes = shieldThermRes ? 1 - this.diminishingReturns(shieldThermRes, shieldThermDREnd, shieldThermDRStart) : null;
// Obtain shield metrics with 0 pips to sys (parts affected by SYS aren't used here)
const metrics = Calc.shieldMetrics(this, 0);
this.shield = metrics.generator ? metrics.generator + metrics.boosters : 0;
this.shieldExplRes = this.shield > 0 ? 1 - metrics.explosive.total : null;
this.shieldKinRes = this.shield > 0 ? 1 - metrics.kinetic.total : null;
this.shieldThermRes = this.shield > 0 ? 1 - metrics.thermal.total : null;
return this;
}
@@ -1201,7 +1274,9 @@ export default class Ship {
for (let slot of this.internal) {
if (slot.m && slot.m.grp == 'scb') {
shieldCells += slot.m.getShieldReinforcement() * slot.m.getCells();
// There is currently a bug with Elite where you can have a clip > 1 thanks to engineering but it doesn't do anything,
// so we need to hard-code clip to 1
shieldCells += slot.m.getShieldReinforcement() * slot.m.getDuration() * (slot.m.getAmmo() + 1);
}
}
@@ -1222,13 +1297,13 @@ export default class Ship {
let moduleprotection = 1;
let hullExplRes = 1 - bulkhead.getExplosiveResistance();
const hullExplResDRStart = hullExplRes * 0.7;
const hullExplResDREnd = hullExplRes * 0; // Currently don't know where this is
const hullExplResDREnd = hullExplRes * 0;
let hullKinRes = 1 - bulkhead.getKineticResistance();
const hullKinResDRStart = hullKinRes * 0.7;
const hullKinResDREnd = hullKinRes * 0; // Currently don't know where this is
const hullKinResDREnd = hullKinRes * 0;
let hullThermRes = 1 - bulkhead.getThermalResistance();
const hullThermResDRStart = hullThermRes * 0.7;
const hullThermResDREnd = hullThermRes * 0; // Currently don't know where this is
const hullThermResDREnd = hullThermRes * 0;
// Armour from HRPs and module armour from MRPs
for (let slot of this.internal) {
@@ -1268,9 +1343,9 @@ export default class Ship {
this.unladenRange = this.calcUnladenRange(); // Includes fuel weight for jump
this.fullTankRange = Calc.jumpRange(unladenMass + fuelCapacity, fsd); // Full Tank
this.ladenRange = this.calcLadenRange(); // Includes full tank and caro
this.unladenFastestRange = Calc.fastestRange(unladenMass, fsd, fuelCapacity);
this.ladenFastestRange = Calc.fastestRange(unladenMass + this.cargoCapacity, fsd, fuelCapacity);
this.maxJumpCount = Math.ceil(fuelCapacity / fsd.maxfuel);
this.unladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity, fsd, fuelCapacity);
this.ladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity + this.cargoCapacity, fsd, fuelCapacity);
this.maxJumpCount = Math.ceil(fuelCapacity / fsd.getMaxFuelPerJump());
return this;
}
@@ -1357,12 +1432,11 @@ export default class Ship {
let bulkheadMods = new Array();
let bulkheadBlueprint = null;
let bulkheadBlueprintGrade = null;
if (this.bulkheads.m && this.bulkheads.m.mods) {
for (let modKey in this.bulkheads.m.mods) {
// Filter out invalid modifications
if (Modifications.modules['bh'] && Modifications.modules['bh'].modifications.indexOf(modKey) != -1) {
bulkheadMods.push({ id: Modifications.modifications[modKey].id, value: this.bulkheads.m.getModValue(modKey) });
bulkheadMods.push({ id: Modifications.modifications[modKey].id, value: this.bulkheads.m.getModValue(modKey, true) });
}
}
bulkheadBlueprint = this.bulkheads.m.blueprint;
@@ -1377,7 +1451,7 @@ export default class Ship {
for (let modKey in slot.m.mods) {
// Filter out invalid modifications
if (Modifications.modules[slot.m.grp] && Modifications.modules[slot.m.grp].modifications.indexOf(modKey) != -1) {
slotMods.push({ id: Modifications.modifications[modKey].id, value: slot.m.getModValue(modKey) });
slotMods.push({ id: Modifications.modifications[modKey].id, value: slot.m.getModValue(modKey, true) });
}
}
}
@@ -1392,7 +1466,7 @@ export default class Ship {
for (let modKey in slot.m.mods) {
// Filter out invalid modifications
if (Modifications.modules[slot.m.grp] && Modifications.modules[slot.m.grp].modifications.indexOf(modKey) != -1) {
slotMods.push({ id: Modifications.modifications[modKey].id, value: slot.m.getModValue(modKey) });
slotMods.push({ id: Modifications.modifications[modKey].id, value: slot.m.getModValue(modKey, true) });
}
}
}
@@ -1407,7 +1481,7 @@ export default class Ship {
for (let modKey in slot.m.mods) {
// Filter out invalid modifications
if (Modifications.modules[slot.m.grp] && Modifications.modules[slot.m.grp].modifications.indexOf(modKey) != -1) {
slotMods.push({ id: Modifications.modifications[modKey].id, value: slot.m.getModValue(modKey) });
slotMods.push({ id: Modifications.modifications[modKey].id, value: slot.m.getModValue(modKey, true) });
}
}
}
@@ -1418,16 +1492,20 @@ export default class Ship {
// Now work out the size of the binary buffer from our modifications array
let bufsize = 0;
for (let slot of slots) {
if (slot.length > 0) {
// Length is 1 for the slot ID, 10 for the blueprint name and grade, 5 for each modification, and 1 for the end marker
bufsize = bufsize + 1 + 10 + (5 * slot.length) + 1;
}
}
for (let special of specials) {
if (special) {
// Length is 5 for each special
bufsize += 5;
for (let i = 0; i < slots.length; i++) {
if (slots[i].length > 0 || (blueprints[i] && blueprints[i].id)) {
// Length is 1 for the slot ID, 5 for each modification, 1 for the end marker of the modifications and 1 for the end marker of the slot
bufsize = bufsize + 1 + (5 * slots[i].length) + 1 + 1;
if (blueprints[i] && blueprints[i].id) {
// Additional 10 for the blueprint and grade
bufsize += 10;
}
if (specials[i]) {
// Additional 5 for each special
bufsize += 5;
}
}
}
@@ -1438,7 +1516,7 @@ export default class Ship {
let curpos = 0;
let i = 0;
for (let slot of slots) {
if (slot.length > 0) {
if (slot.length > 0 || (blueprints[i] && blueprints[i].id)) {
buffer.writeInt8(i, curpos++);
if (blueprints[i] && blueprints[i].id) {
buffer.writeInt8(MODIFICATION_ID_BLUEPRINT, curpos++);
@@ -1563,6 +1641,7 @@ export default class Ship {
}
let oldModule = slot.m;
slot.m = m;
slot.enabled = true;
slot.discountedCost = (m && m.cost) ? m.cost * this.moduleCostMultiplier : 0;
this.updateStats(slot, m, oldModule, preventUpdate);
@@ -1604,6 +1683,25 @@ export default class Ship {
return this;
}
/**
* Calculate the lowest possible mass for this ship.
* @param {Object} m Module override set (standard type => Module)
* @return {number} The lowest possible mass for this ship
*/
calcLowestPossibleMass(m) {
m = m || {};
let mass = this.hullMass;
mass += m.pp ? m.pp.getMass() : ModuleUtils.standard(0, '2D').getMass();
mass += m.th ? m.th.getMass() : ModuleUtils.standard(1, '2D').getMass();
mass += m.fsd ? m.fsd.getMass() : ModuleUtils.standard(2, '2D').getMass();
mass += m.ls ? m.ls.getMass() : ModuleUtils.standard(3, this.standard[3].maxClass + 'D').getMass() * 0.3; // Lightweight grade 4 mod reduces mass by up to 70%
mass += m.pd ? m.pd.getMass() : ModuleUtils.standard(4, '1D').getMass();
mass += m.s ? m.s.getMass() : ModuleUtils.standard(5, this.standard[5].maxClass + 'D').getMass() * 0.2; // Lightweight grade 5 mod reduces mass by up to 80%
// Ignore fuel tank as it could be empty
return mass;
}
/**
* Use the lightest standard ModuleUtils unless otherwise specified
* @param {Object} m Module override set (standard type => module ID)
@@ -1628,6 +1726,11 @@ export default class Ship {
.use(standard[4], pd) // Power Distributor
.use(standard[6], ft); // Fuel Tank
// Turn off nearly everything
if (m.fsdDisabled) this.setSlotEnabled(this.standard[2], false);
if (m.pdDisabled) this.setSlotEnabled(this.standard[4], false);
if (m.sDisabled) this.setSlotEnabled(this.standard[5], false);
// Thrusters and Powerplant must be determined after all other ModuleUtils are mounted
// Loop at least once to determine absolute lightest PD and TH
do {

View File

@@ -31,20 +31,38 @@ export function multiPurpose(ship, shielded, bulkheadIndex) {
* @param {Object} standardOpts [Optional] Standard module optional overrides
*/
export function trader(ship, shielded, standardOpts) {
let sg = shielded ? ship.getAvailableModules().lightestShieldGenerator(ship.hullMass) : null;
for (let i = ship.internal.length; i--;) {
let slot = ship.internal[i];
if (sg && canMount(ship, slot, 'sg', sg.class)) {
ship.use(slot, sg);
sg = null;
} else {
if (canMount(ship, slot, 'cr')) {
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
let usedSlots = [],
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
// Shield generator if required
if (shielded) {
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class)
.sort((a,b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
for (let i = 0; i < shieldInternals.length; i++) {
if (canMount(ship, shieldInternals[i], 'sg')) {
ship.use(shieldInternals[i], sg);
usedSlots.push(shieldInternals[i]);
break;
}
}
}
// Fill the empty internals with cargo racks
for (let i = ship.internal.length; i--;) {
let slot = ship.internal[i];
if (usedSlots.indexOf(slot) == -1 && canMount(ship, slot, 'cr')) {
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
}
}
// Empty the hardpoints
for (let s of ship.hardpoints) {
ship.use(s, null);
}
ship.useLightestStandard(standardOpts);
}
@@ -55,53 +73,102 @@ export function trader(ship, shielded, standardOpts) {
*/
export function explorer(ship, planetary) {
let standardOpts = { ppRating: 'A' },
intLength = ship.internal.length,
heatSinkCount = 2, // Fit 2 heat sinks if possible
afmUnitCount = 2, // Fit 2 AFM Units if possible
shieldNext = planetary,
usedSlots = [],
sgSlot,
fuelScoopSlot,
pvhSlot,
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
if (!planetary) { // Non-planetary explorers don't really need to boost
standardOpts.pd = '1D';
}
ship.setSlotEnabled(ship.cargoHatch, false)
.use(ship.internal[--intLength], ModuleUtils.internal('2f')); // Advanced Discovery Scanner
// Cargo hatch can be disabled
ship.setSlotEnabled(ship.cargoHatch, false);
if (!planetary || intLength > 3) { // Don't mount a DDS on planetary explorer ships too small for both a PVH and DDS
ship.use(ship.internal[--intLength], ModuleUtils.internal('2i')); // Detailed Surface Scanner
// Advanced Discovery Scanner - class 1 or higher
const adsOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const adsInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sc)
.sort((a,b) => adsOrder.indexOf(a.maxClass) - adsOrder.indexOf(b.maxClass));
for (let i = 0; i < adsInternals.length; i++) {
if (canMount(ship, adsInternals[i], 'sc')) {
ship.use(adsInternals[i], ModuleUtils.internal('2f'));
usedSlots.push(adsInternals[i]);
break;
}
}
for (let i = 0; i < intLength; i++) {
let slot = ship.internal[i];
let nextSlot = (i + 1) < intLength ? ship.internal[i + 1] : null;
// Fit best possible Fuel Scoop
if (!fuelScoopSlot && canMount(ship, slot, 'fs')) {
fuelScoopSlot = slot;
ship.use(slot, ModuleUtils.findInternal('fs', slot.maxClass, 'A'));
ship.setSlotEnabled(slot, true);
// Mount a Shield generator if possible AND an AFM Unit has been mounted already (Guarantees at least 1 AFM Unit)
} else if (!sgSlot && shieldNext && canMount(ship, slot, 'sg', sg.class) && !canMount(ship, nextSlot, 'sg', sg.class)) {
sgSlot = slot;
shieldNext = false;
ship.use(slot, sg);
ship.setSlotEnabled(slot, true);
// if planetary explorer and the next slot cannot mount a PVH or the next modul to mount is a SG
} else if (planetary && !pvhSlot && canMount(ship, slot, 'pv') && (shieldNext || !canMount(ship, nextSlot, 'pv', 2))) {
pvhSlot = slot;
ship.use(slot, ModuleUtils.findInternal('pv', Math.min(Math.floor(pvhSlot.maxClass / 2) * 2, 6), 'G'));
ship.setSlotEnabled(slot, false); // Disabled power for PVH
shieldNext = !sgSlot;
} else if (afmUnitCount > 0 && canMount(ship, slot, 'am')) {
afmUnitCount--;
ship.use(slot, ModuleUtils.findInternal('am', slot.maxClass, 'A'));
ship.setSlotEnabled(slot, false); // Disabled power for AFM Unit
shieldNext = !sgSlot;
} else {
ship.use(slot, null);
if (planetary) {
// Planetary Vehicle Hangar - class 2 or higher
const pvhOrder = [2, 3, 4, 5, 6, 7, 8, 1];
const pvhInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.pv)
.sort((a,b) => pvhOrder.indexOf(a.maxClass) - pvhOrder.indexOf(b.maxClass));
for (let i = 0; i < pvhInternals.length; i++) {
if (canMount(ship, pvhInternals[i], 'pv')) {
// Planetary Vehical Hangar only has even classes
const pvhClass = pvhInternals[i].maxClass % 2 === 1 ? pvhInternals[i].maxClass - 1 : pvhInternals[i].maxClass;
ship.use(pvhInternals[i], ModuleUtils.findInternal('pv', pvhClass, 'G')); // G is lower mass
ship.setSlotEnabled(pvhInternals[i], false); // Disable power for Planetary Vehical Hangar
usedSlots.push(pvhInternals[i]);
break;
}
}
}
// Shield generator
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class)
.sort((a,b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
for (let i = 0; i < shieldInternals.length; i++) {
if (canMount(ship, shieldInternals[i], 'sg')) {
ship.use(shieldInternals[i], sg);
usedSlots.push(shieldInternals[i]);
sgSlot = shieldInternals[i];
break;
}
}
// Detailed Surface Scanner
const dssOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const dssInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sc)
.sort((a,b) => dssOrder.indexOf(a.maxClass) - dssOrder.indexOf(b.maxClass));
for (let i = 0; i < dssInternals.length; i++) {
if (canMount(ship, dssInternals[i], 'sc')) {
ship.use(dssInternals[i], ModuleUtils.internal('2i'));
usedSlots.push(dssInternals[i]);
break;
}
}
// Fuel scoop - best possible
const fuelScoopOrder = [8, 7, 6, 5, 4, 3, 2, 1];
const fuelScoopInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.fs)
.sort((a,b) => fuelScoopOrder.indexOf(a.maxClass) - fuelScoopOrder.indexOf(b.maxClass));
for (let i = 0; i < fuelScoopInternals.length; i++) {
if (canMount(ship, fuelScoopInternals[i], 'fs')) {
ship.use(fuelScoopInternals[i], ModuleUtils.findInternal('fs', fuelScoopInternals[i].maxClass, 'A'));
usedSlots.push(fuelScoopInternals[i]);
fuelScoopSlot = fuelScoopInternals[i];
break;
}
}
// AFMUs - fill as they are 0-weight
const afmuOrder = [8, 7, 6, 5, 4, 3, 2, 1];
const afmuInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.pc)
.sort((a,b) => afmuOrder.indexOf(a.maxClass) - afmuOrder.indexOf(b.maxClass));
for (let i = 0; i < afmuInternals.length; i++) {
if (canMount(ship, afmuInternals[i], 'am')) {
ship.use(afmuInternals[i], ModuleUtils.findInternal('am', afmuInternals[i].maxClass, 'A'));
usedSlots.push(afmuInternals[i]);
ship.setSlotEnabled(afmuInternals[i], false); // Disable power for AFM Unit
}
}
@@ -115,7 +182,7 @@ export function explorer(ship, planetary) {
}
}
if (sgSlot) {
if (sgSlot && fuelScoopSlot) {
// The SG and Fuel scoop to not need to be powered at the same time
if (sgSlot.m.getPowerUsage() > fuelScoopSlot.m.getPowerUsage()) { // The Shield generator uses the most power
ship.setSlotEnabled(fuelScoopSlot, false);
@@ -126,3 +193,209 @@ export function explorer(ship, planetary) {
ship.useLightestStandard(standardOpts);
}
/**
* Miner Role
* @param {Ship} ship Ship instance
* @param {Boolean} shielded True if shield generator should be included
*/
export function miner(ship, shielded) {
let standardOpts = { ppRating: 'A' },
miningLaserCount = 2,
usedSlots = [],
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
// Cargo hatch should be enabled
ship.setSlotEnabled(ship.cargoHatch, true);
// Largest possible refinery
const refineryOrder = [4, 5, 6, 7, 8, 3, 2, 1];
const refineryInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.rf)
.sort((a,b) => refineryOrder.indexOf(a.maxClass) - refineryOrder.indexOf(b.maxClass));
for (let i = 0; i < refineryInternals.length; i++) {
if (canMount(ship, refineryInternals[i], 'rf')) {
ship.use(refineryInternals[i], ModuleUtils.findInternal('rf', Math.min(refineryInternals[i].maxClass, 4), 'A'));
usedSlots.push(refineryInternals[i]);
break;
}
}
// Prospector limpet controller - 3A if possible
const prospectorOrder = [3, 4, 5, 6, 7, 8, 2, 1];
const prospectorInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.pc)
.sort((a,b) => prospectorOrder.indexOf(a.maxClass) - prospectorOrder.indexOf(b.maxClass));
for (let i = 0; i < prospectorInternals.length; i++) {
if (canMount(ship, prospectorInternals[i], 'pc')) {
// Prospector only has odd classes
const prospectorClass = prospectorInternals[i].maxClass % 2 === 0 ? prospectorInternals[i].maxClass - 1 : prospectorInternals[i].maxClass;
ship.use(prospectorInternals[i], ModuleUtils.findInternal('pc', prospectorClass, 'A'));
usedSlots.push(prospectorInternals[i]);
break;
}
}
// Shield generator if required
if (shielded) {
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class)
.sort((a,b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
for (let i = 0; i < shieldInternals.length; i++) {
if (canMount(ship, shieldInternals[i], 'sg')) {
ship.use(shieldInternals[i], sg);
usedSlots.push(shieldInternals[i]);
break;
}
}
}
// Dual mining lasers of highest possible class; remove anything else
const miningLaserOrder = [2, 3, 4, 1, 0];
const miningLaserHardpoints = ship.hardpoints.concat().sort(function(a,b) {
return miningLaserOrder.indexOf(a.maxClass) - miningLaserOrder.indexOf(b.maxClass);
});
for (let s of miningLaserHardpoints) {
if (s.maxClass >= 1 && miningLaserCount) {
ship.use(s, ModuleUtils.hardpoints(s.maxClass >= 2 ? '2m' : '2l'));
miningLaserCount--;
} else {
ship.use(s, null);
}
}
// Number of collector limpets required to be active is a function of the size of the ship and the power of the lasers
const miningLaserDps = ship.hardpoints.filter(h => h.m != null)
.reduce(function(a, b) {
return a + b.m.getDps();
}, 0);
// Find out how many internal slots we have, and their potential cargo size
const potentialCargo = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.cr)
.map(b => Math.pow(2, b.maxClass));
// One collector for each 1.25 DPS, multiply by 1.25 for medium ships and 1.5 for large ships as they have further to travel
// 0 if we only have 1 cargo slot, otherwise minium of 1 and maximum of 6 (excluding size modifier)
const sizeModifier = ship.class == 2 ? 1.2 : ship.class == 3 ? 1.5 : 1;
let collectorLimpetsRequired = potentialCargo.length == 1 ? 0 : Math.ceil(sizeModifier * Math.min(6, Math.floor(miningLaserDps / 1.25)));
if (collectorLimpetsRequired > 0) {
const collectorOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const collectorInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.cc)
.sort((a,b) => collectorOrder.indexOf(a.maxClass) - collectorOrder.indexOf(b.maxClass));
// Always keep at least 2 slots free for cargo racks (1 for shielded)
for (let i = 0; i < collectorInternals.length - (shielded ? 1 : 2) && collectorLimpetsRequired > 0; i++) {
if (canMount(ship, collectorInternals[i], 'cc')) {
// Collector only has odd classes
const collectorClass = collectorInternals[i].maxClass % 2 === 0 ? collectorInternals[i].maxClass - 1 : collectorInternals[i].maxClass;
ship.use(collectorInternals[i], ModuleUtils.findInternal('cc', collectorClass, 'D'));
usedSlots.push(collectorInternals[i]);
collectorLimpetsRequired -= collectorInternals[i].m.maximum;
}
}
}
// Power distributor to power the mining lasers indefinitely
const wepRateRequired = ship.hardpoints.filter(h => h.m != null)
.reduce(function(a, b) {
return a + b.m.getEps();
}, 0);
standardOpts.pd = ship.getAvailableModules().matchingPowerDist({ weprate: wepRateRequired }).id;
// Fill the empty internals with cargo racks
for (let i = ship.internal.length; i--;) {
let slot = ship.internal[i];
if (usedSlots.indexOf(slot) == -1 && canMount(ship, slot, 'cr')) {
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
}
}
ship.useLightestStandard(standardOpts);
}
/**
* Racer Role
* @param {Ship} ship Ship instance
*/
export function racer(ship) {
let standardOpts = {},
usedSlots = [],
sgSlot,
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
// Cargo hatch can be disabled
ship.setSlotEnabled(ship.cargoHatch, false);
// Shield generator
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class)
.sort((a,b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
for (let i = 0; i < shieldInternals.length; i++) {
if (canMount(ship, shieldInternals[i], 'sg')) {
ship.use(shieldInternals[i], sg);
usedSlots.push(shieldInternals[i]);
sgSlot = shieldInternals[i];
break;
}
}
// Empty the hardpoints
for (let s of ship.hardpoints) {
ship.use(s, null);
}
// Empty the internals
for (let i = ship.internal.length; i--;) {
let slot = ship.internal[i];
if (usedSlots.indexOf(slot) == -1) {
ship.use(slot, null);
}
}
// Best thrusters
if (ship.standard[1].maxClass === 3) {
standardOpts.th = 'tz';
} else if (ship.standard[1].maxClass === 2) {
standardOpts.th = 'u0';
} else {
standardOpts.th = ship.standard[1].maxClass + 'A';
}
// Best power distributor for more boosting
standardOpts.pd = ship.standard[4].maxClass + 'A';
// Smallest possible FSD drive
standardOpts.fsd = '2D';
// Minimal fuel tank
standardOpts.ft = '1C';
// Disable nearly everything
standardOpts.fsdDisabled = true;
standardOpts.sDisabled = true;
standardOpts.pdDisabled = true;
standardOpts.lsDisabled = true;
ship.useLightestStandard(standardOpts);
// Apply engineering to each module
// ship.standard[1].m.blueprint = getBlueprint('Engine_Dirty', ship.standard[0]);
// ship.standard[1].m.blueprint.grade = 5;
// setBest(ship, ship.standard[1].m);
// ship.standard[3].m.blueprint = getBlueprint('LifeSupport_LightWeight', ship.standard[3]);
// ship.standard[3].m.blueprint.grade = 4;
// setBest(ship, ship.standard[3].m);
// ship.standard[4].m.blueprint = getBlueprint('PowerDistributor_PriorityEngines', ship.standard[4]);
// ship.standard[4].m.blueprint.grade = 3;
// setBest(ship, ship.standard[4].m);
// ship.standard[5].m.blueprint = getBlueprint('Sensor_Sensor_LightWeight', ship.standard[5]);
// ship.standard[5].m.blueprint.grade = 5;
// setBest(ship, ship.standard[5].m);
}

View File

@@ -5,6 +5,7 @@ const LS_KEY_BUILDS = 'builds';
const LS_KEY_COMPARISONS = 'comparisons';
const LS_KEY_LANG = 'NG_TRANSLATE_LANG_KEY';
const LS_KEY_COST_TAB = 'costTab';
const LS_KEY_OUTFITTING_TAB = 'outfittingTab';
const LS_KEY_INSURANCE = 'insurance';
const LS_KEY_SHIP_DISCOUNT = 'shipDiscount';
const LS_KEY_MOD_DISCOUNT = 'moduleDiscount';
@@ -98,6 +99,7 @@ export class Persist extends EventEmitter {
this.builds = buildJson && typeof buildJson == 'object' ? buildJson : {};
this.comparisons = comparisonJson && typeof comparisonJson == 'object' ? comparisonJson : {};
this.costTab = _getString(LS_KEY_COST_TAB);
this.outfittingTab = _getString(LS_KEY_OUTFITTING_TAB);
this.state = _get(LS_KEY_STATE);
this.sizeRatio = _get(LS_KEY_SIZE_RATIO) || 1;
this.tooltipsEnabled = tips === null ? true : tips;
@@ -472,6 +474,22 @@ export class Persist extends EventEmitter {
return this.costTab;
}
/**
* Persist selected outfitting tab
* @param {string} tabName Cost tab name
*/
setOutfittingTab(tabName) {
this.outfittingTab = tabName;
_put(LS_KEY_OUTFITTING_TAB, tabName);
}
/**
* Get the current outfitting tab
* @return {string} the current outfitting tab
*/
getOutfittingTab() {
return this.outfittingTab;
}
/**
* Retrieve the last router state from local storage
* @return {Object} state State object containing state name and params

View File

@@ -0,0 +1,371 @@
import React from 'react';
import { Modifications } from 'coriolis-data/dist';
/**
* Generate a tooltip with details of a blueprint's effects
* @param {Object} translate The translate object
* @param {Object} blueprint The blueprint at the required grade
* @param {Array} engineers The engineers supplying this blueprint
* @param {string} grp The group of the module
* @param {Object} m The module to compare with
* @returns {Object} The react components
*/
export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
const effects = [];
for (const feature in blueprint.features) {
const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]);
const featureDef = Modifications.modifications[feature];
if (!featureDef.hidden) {
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let lowerBound = blueprint.features[feature][0];
let upperBound = blueprint.features[feature][1];
if (featureDef.type === 'percentage') {
lowerBound = Math.round(lowerBound * 1000) / 10;
upperBound = Math.round(upperBound * 1000) / 10;
}
const lowerIsBeneficial = isValueBeneficial(feature, lowerBound);
const upperIsBeneficial = isValueBeneficial(feature, upperBound);
if (m) {
// We have a module - add in the current value
let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
</tr>
);
} else {
// We do not have a module, no value
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
</tr>
);
}
}
}
if (m) {
// Because we have a module add in any benefits that aren't part of the primary blueprint
for (const feature in m.mods) {
if (!blueprint.features[feature]) {
const featureDef = Modifications.modifications[feature];
if (featureDef && !featureDef.hidden) {
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td>
</tr>
);
}
}
}
// We also add in any benefits from specials that aren't covered above
if (m.blueprint && m.blueprint.special) {
for (const feature in Modifications.modifierActions[m.blueprint.special.edname]) {
if (!blueprint.features[feature] && !m.mods.feature) {
const featureDef = Modifications.modifications[feature];
if (featureDef && !featureDef.hidden) {
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td>
</tr>
);
}
}
}
}
}
let components;
if (!m) {
components = [];
for (const component in blueprint.components) {
components.push(
<tr key={component}>
<td style={{ textAlign: 'left' }}>{translate(component)}</td>
<td style={{ textAlign: 'right' }}>{blueprint.components[component]}</td>
</tr>
);
}
}
let engineersList;
if (engineers) {
engineersList = [];
for (const engineer of engineers) {
engineersList.push(
<tr key={engineer}>
<td style={{ textAlign: 'left' }}>{engineer}</td>
</tr>
);
}
}
return (
<div>
<table width='100%'>
<thead>
<tr>
<td>{translate('feature')}</td>
<td>{translate('worst')}</td>
{m ? <td>{translate('current')}</td> : null }
<td>{translate('best')}</td>
</tr>
</thead>
<tbody>
{effects}
</tbody>
</table>
{ components ? <table width='100%'>
<thead>
<tr>
<td>{translate('component')}</td>
<td>{translate('amount')}</td>
</tr>
</thead>
<tbody>
{components}
</tbody>
</table> : null }
{ engineersList ? <table width='100%'>
<thead>
<tr>
<td>{translate('engineers')}</td>
</tr>
</thead>
<tbody>
{engineersList}
</tbody>
</table> : null }
</div>
);
}
/**
* Is this blueprint feature beneficial?
* @param {string} feature The name of the feature
* @param {array} values The value of the feature
* @returns {boolean} True if this feature is beneficial
*/
export function isBeneficial(feature, values) {
const fact = (values[0] < 0 || (values[0] === 0 && values[1] < 0));
if (Modifications.modifications[feature].higherbetter) {
return !fact;
} else {
return fact;
}
}
/**
* Is this feature value beneficial?
* @param {string} feature The name of the feature
* @param {number} value The value of the feature
* @returns {boolean} True if this value is beneficial
*/
export function isValueBeneficial(feature, value) {
if (Modifications.modifications[feature].higherbetter) {
return value > 0;
} else {
return value < 0;
}
}
/**
* Get a blueprint with a given name and an optional module
* @param {string} name The name of the blueprint
* @param {Object} module The module for which to obtain this blueprint
* @returns {Object} The matching blueprint
*/
export function getBlueprint(name, module) {
// Start with a copy of the blueprint
const blueprint = JSON.parse(JSON.stringify(Modifications.blueprints[name]));
if (module) {
if (module.grp === 'bh' || module.grp === 'hr' || module.grp === 'sg' || module.grp === 'psg' || module.grp === 'bsg') {
// Bulkheads, hull reinforcements and shield generators need to have their resistances altered by the base values
for (const grade in blueprint.grades) {
for (const feature in blueprint.grades[grade].features) {
if (feature === 'explres') {
blueprint.grades[grade].features[feature][0] *= (1 - module.explres);
blueprint.grades[grade].features[feature][1] *= (1 - module.explres);
}
if (feature === 'kinres') {
blueprint.grades[grade].features[feature][0] *= (1 - module.kinres);
blueprint.grades[grade].features[feature][1] *= (1 - module.kinres);
}
if (feature === 'thermres') {
blueprint.grades[grade].features[feature][0] *= (1 - module.thermres);
blueprint.grades[grade].features[feature][1] *= (1 - module.thermres);
}
}
}
}
if (module.grp === 'sb') {
// Shield boosters are treated internally as straight modifiers, so rather than (for example)
// being a 4% boost they are a 104% multiplier. We need to fix the values here so that they look
// accurate as per the information in Elite
for (const grade in blueprint.grades) {
for (const feature in blueprint.grades[grade].features) {
if (feature === 'shieldboost') {
blueprint.grades[grade].features[feature][0] = ((1 + blueprint.grades[grade].features[feature][0]) * (1 + module.shieldboost) - 1) / module.shieldboost - 1;
blueprint.grades[grade].features[feature][1] = ((1 + blueprint.grades[grade].features[feature][1]) * (1 + module.shieldboost) - 1) / module.shieldboost - 1;
}
}
}
}
}
return blueprint;
}
/**
* Provide 'worst' primary modifications
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
*/
export function setWorst(ship, m) {
ship.clearModifications(m);
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
const value = features[featureName][0];
_setValue(ship, m, featureName, value);
}
}
/**
* Provide 'best' primary modifications
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
*/
export function setBest(ship, m) {
ship.clearModifications(m);
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
const value = features[featureName][1];
_setValue(ship, m, featureName, value);
}
}
/**
* Provide 'extreme' primary modifications
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
*/
export function setExtreme(ship, m) {
ship.clearModifications(m);
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
let value;
if (Modifications.modifications[featureName].higherbetter) {
// Higher is better, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
value = features[featureName][0];
} else {
value = features[featureName][1];
}
} else {
// Higher is worse, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
value = features[featureName][1];
} else {
value = features[featureName][0];
}
}
_setValue(ship, m, featureName, value);
}
}
/**
* Provide 'random' primary modifications
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
*/
export function setRandom(ship, m) {
ship.clearModifications(m);
// Pick a single value for our randomness
const mult = Math.random();
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
let value;
if (Modifications.modifications[featureName].higherbetter) {
// Higher is better, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
} else {
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
}
} else {
// Higher is worse, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
} else {
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
}
}
_setValue(ship, m, featureName, value);
}
}
/**
* Set a modification feature value
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
* @param {string} featureName The feature being set
* @param {number} value The value being set for the feature
*/
function _setValue(ship, m, featureName, value) {
if (Modifications.modifications[featureName].type == 'percentage') {
ship.setModification(m, featureName, value * 10000);
} else if (Modifications.modifications[featureName].type == 'numeric') {
ship.setModification(m, featureName, value * 100);
} else {
ship.setModification(m, featureName, value);
}
}

View File

@@ -2,6 +2,8 @@ import React from 'react';
import { Modifications, Modules, Ships } from 'coriolis-data/dist';
import Module from '../shipyard/Module';
import Ship from '../shipyard/Ship';
import { getBlueprint } from '../utils/BlueprintFunctions';
import * as ModuleUtils from '../shipyard/ModuleUtils';
// mapping from fd's ship model names to coriolis'
const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
@@ -15,6 +17,7 @@ const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
'Cutter': 'imperial_cutter',
'DiamondBackXL': 'diamondback_explorer',
'DiamondBack': 'diamondback',
'Dolphin': 'dolphin',
'Eagle': 'eagle',
'Empire_Courier': 'imperial_courier',
'Empire_Eagle': 'imperial_eagle',
@@ -129,9 +132,15 @@ export function shipFromJson(json) {
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;
// Set the cargo hatch
if (json.modules.CargoHatch) {
ship.cargoHatch.enabled = json.modules.CargoHatch.module.on == true;
ship.cargoHatch.priority = json.modules.CargoHatch.module.priority;
} else {
// 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;
@@ -243,7 +252,6 @@ export function shipFromJson(json) {
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
@@ -253,11 +261,15 @@ export function shipFromJson(json) {
internalSlot = json.modules[internalName];
militarySlotNum++;
} else {
// Slot numbers are not contiguous so handle skips.
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];
// Slot sizes have no relationship to the actual size, either, so check all possibilities
for (let slotsize = 0; slotsize < 9; slotsize++) {
const internalName = 'Slot' + (internalSlotNum <= 9 ? '0' : '') + internalSlotNum + '_Size' + slotsize;
if (json.modules[internalName]) {
internalSlot = json.modules[internalName];
break;
}
}
internalSlotNum++;
}
@@ -303,11 +315,14 @@ function _addModifications(module, modifiers, blueprint, grade) {
// This is an absolute number that acts as an override
module.setModValue('burst', modifiers.modifiers[i].value * 100);
} else if (modifiers.modifiers[i].name === 'mod_weapon_burst_rof') {
// For some reason this is a non-normalised percentage (i.e. 12.23% is 12.23 value rather than 0.1223 as everywhere else), so fix that here
// 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];
@@ -327,16 +342,11 @@ function _addModifications(module, modifiers, blueprint, grade) {
}
}
}
// Note the special if present
if (modifiers.modifiers[i].name && modifiers.modifiers[i].name.startsWith('special_')) {
special = Modifications.specials[modifiers.modifiers[i].name];
}
}
// Add the blueprint ID, grade and special
// Add the blueprint definition, grade and special
if (blueprint) {
module.blueprint = Object.assign({}, Modifications.blueprints[blueprint]);
module.blueprint = getBlueprint(blueprint, module);
if (grade) {
module.blueprint.grade = Number(grade);
}
@@ -370,7 +380,7 @@ function _addModifications(module, modifiers, blueprint, grade) {
// 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 (ModuleUtils.isShieldGenerator(module.grp)) {
if (module.getModValue('explres')) {
module.setModValue('explres', ((1 - (1 - module.explres) * (1 + module.getModValue('explres') / 10000)) - module.explres) * 10000);
}
@@ -383,15 +393,16 @@ function _addModifications(module, modifiers, blueprint, grade) {
}
// Hull reinforcement package 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 === 'hr') {
if (module.getModValue('explres')) {
module.setModValue('explres', ((module.getModValue('explres') / 10000) * -1) * 10000);
module.setModValue('explres', ((1 - (1 - module.explres) * (1 + module.getModValue('explres') / 10000)) - module.explres) * 10000);
}
if (module.getModValue('kinres')) {
module.setModValue('kinres', ((module.getModValue('kinres') / 10000) * -1) * 10000);
module.setModValue('kinres', ((1 - (1 - module.kinres) * (1 + module.getModValue('kinres') / 10000)) - module.kinres) * 10000);
}
if (module.getModValue('thermres')) {
module.setModValue('thermres', ((module.getModValue('thermres') / 10000) * -1) * 10000);
module.setModValue('thermres', ((1 - (1 - module.thermres) * (1 + module.getModValue('thermres') / 10000)) - module.thermres) * 10000);
}
}
@@ -424,4 +435,10 @@ function _addModifications(module, modifiers, blueprint, grade) {
if (module.getModValue('rof')) {
module.setModValue('rof', ((1 / (1 + module.getModValue('rof') / 10000)) - 1) * 10000);
}
// Clip size is rounded up so that the result is a whole number
if (module.getModValue('clip')) {
const individual = 1 / (module.clip || 1);
module.setModValue('clip', Math.ceil((module.getModValue('clip') / 10000) / individual) * individual * 10000);
}
}

View File

@@ -1,9 +1,9 @@
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';
import * as ModuleUtils from '../shipyard/ModuleUtils';
/**
* Determine if a slot on a ship can mount a module of a particular class and group
@@ -143,28 +143,24 @@ export function diffDetails(language, m, 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>);
// Module-specific items
let mMass = m.mass || 0;
let mmMass = mm ? mm.getMass() : 0;
if (mMass != mmMass) propDiffs.push(<div key='mass'>{translate('mass')}: <span className={diffClass(mMass, mmMass, true)}>{diff(formats.round, mMass, mmMass)}{units.T}</span></div>);
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 : 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 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>);
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 mDps = m.damage * (m.rpshot || 1) * (m.rof || 1);
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>);
if (mDps && 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';
let mAffectsShield = ModuleUtils.isShieldGenerator(m.grp) || m.grp == 'sb';
let mmAffectsShield = mm ? ModuleUtils.isShieldGenerator(m.grp) || mm.grp == 'sb' : false;
if (mAffectsShield || mmAffectsShield) {
let shield = this.calcShieldStrengthWith(); // Get shield strength regardless of slot active / inactive
let newShield = 0;
@@ -185,6 +181,20 @@ export function diffDetails(language, m, mm) {
propDiffs.push(<div key='shields'>{translate('shields')}: <span className={sgDiffClass}>{diff(formats.int, newShield, shield)}{units.MJ}</span></div>);
}
if (m.grp === 'mrp') {
let mProtection = m.protection;
let mmProtection = mm ? mm.getProtection() || 0 : 0;
if (mProtection != mmProtection) {
propDiffs.push(<div key='protection'>{translate('protection')}: <span className={diffClass(mmProtection, mProtection, true)}>{diff(formats.pct, mProtection, mmProtection)}</span></div>);
}
}
if (m.grp === 'hr') {
let mHullReinforcement = m.hullreinforcement;
let mmHullReinforcement = mm ? mm.getHullReinforcement() || 0 : 0;
if (mHullReinforcement && mHullReinforcement != mmHullReinforcement) propDiffs.push(<div key='hullreinforcement'>{translate('hullreinforcement')}: <span className={diffClass(mmHullReinforcement, mHullReinforcement, true)}>{diff(formats.round, mHullReinforcement, mmHullReinforcement)}</span></div>);
}
if (m.grp == 'pd') {
propDiffs.push(<div key='wep'>
{`${translate('WEP')}: `}
@@ -206,6 +216,16 @@ export function diffDetails(language, m, mm) {
</div>);
}
// Common items
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 ? mm.getMass() : 0;
if (mMass != mmMass) propDiffs.push(<div key='mass'>{translate('mass')}: <span className={diffClass(mMass, mmMass, true)}>{diff(formats.round, mMass, mmMass)}{units.T}</span></div>);
let massDiff = mMass - mmMass;
let mCap = m.fuel || m.cargo || 0;
let mmCap = mm ? mm.fuel || mm.cargo || 0 : 0;
@@ -223,5 +243,11 @@ export function diffDetails(language, m, mm) {
}
}
return propDiffs ? <div className='cap' style={{ whiteSpace: 'nowrap' }}>{propDiffs}</div> : null;
let mIntegrity = m.integrity || 0;
let mmIntegrity = mm ? mm.getIntegrity() || 0 : 0;
if (mIntegrity != mmIntegrity) {
propDiffs.push(<div key='integrity'>{translate('integrity')}: <span className={diffClass(mmIntegrity, mIntegrity, true)}>{diff(formats.round, mIntegrity, mmIntegrity)}</span></div>);
}
return propDiffs.length > 0 ? <div className='cap' style={{ whiteSpace: 'nowrap' }}>{propDiffs}</div> : null;
}

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"
}

62
src/index.ejs Normal file
View File

@@ -0,0 +1,62 @@
<!DOCTYPE html>
<html <%= htmlWebpackPlugin.options.appCache ? 'manifest=/' + htmlWebpackPlugin.options.appCache : '' %> >
<head>
<title>Coriolis EDCD Edition</title>
<meta charset="UTF-8">
<link rel="stylesheet" href="<%= htmlWebpackPlugin.files.css[0] %>">
<!-- Standard headers -->
<meta name="description" content="A ship builder, outfitting and comparison tool for Elite Dangerous">
<meta name="mobile-web-app-capable" content="yes">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<link rel="manifest" href="/manifest.json">
<link rel="shortcut icon" href=/favicon2.ico>
<link rel="icon" sizes="152x152 192x192" type="image/png" href="/192x192.png">
<!-- Apple/iOS headers -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-title" content="Coriolis">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<!-- Microsoft Windows Phone/Tablet headers -->
<meta name="msapplication-TileColor" content="#000000">
<meta name="msapplication-TileImage" content="/mstile-144x144.png">
<meta name="msapplication-config" content="/browserconfig.xml">
<meta name="theme-color" content="#000000">
<script>
window.CORIOLIS_GAPI_KEY = '<%- htmlWebpackPlugin.options.gapiKey %>';
window.CORIOLIS_VERSION = '<%- htmlWebpackPlugin.options.version %>';
window.CORIOLIS_DATE = '<%- new Date().toISOString().slice(0, 10) %>';
</script>
<% if (htmlWebpackPlugin.options.uaTracking) { %>
<script>
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
ga('create', '<%- htmlWebpackPlugin.options.uaTracking %>', 'auto');
ga('send', 'pageview');
</script>
<script async src='https://www.google-analytics.com/analytics.js'></script>
<% } %>
<!-- Piwik -->
<script type="text/javascript">
var _paq = _paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(["setCookieDomain", "*.coriolis.edcd.io"]);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="//stats.isadankme.me/";
_paq.push(['setTrackerUrl', u+'piwik.php']);
_paq.push(['setSiteId', '4']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
})();
</script>
<!-- End Piwik Code -->
</head>
<body style="background-color:#000;">
<section id="coriolis"></section>
<script src="<%= htmlWebpackPlugin.files.chunks.lib.entry %>" charset="utf-8" crossorigin="anonymous"></script>
<script src="<%= htmlWebpackPlugin.files.chunks.app.entry %>" charset="utf-8" crossorigin="anonymous"></script>
</body>
</html>

View File

@@ -1,45 +0,0 @@
<!DOCTYPE html>
<html {%= o.htmlWebpackPlugin.options.appCache ? 'manifest=/' + o.htmlWebpackPlugin.options.appCache : '' %} >
<head>
<title>Coriolis EDCD Edition</title>
<meta charset="UTF-8">
<link rel="stylesheet" href="{%= o.htmlWebpackPlugin.files.css[0] %}">
<!-- Standard headers -->
<meta name="description" content="A ship builder, outfitting and comparison tool for Elite Dangerous">
<meta name="mobile-web-app-capable" content="yes">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
<link rel="manifest" href="/manifest.json">
<link rel="shortcut icon" href=/favicon2.ico>
<link rel="icon" sizes="152x152 192x192" type="image/png" href="/192x192.png">
<!-- Apple/iOS headers -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-title" content="Coriolis">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<!-- Microsoft Windows Phone/Tablet headers -->
<meta name="msapplication-TileColor" content="#000000">
<meta name="msapplication-TileImage" content="/mstile-144x144.png">
<meta name="msapplication-config" content="/browserconfig.xml">
<meta name="theme-color" content="#000000">
</head>
<body style="background-color:#000;">
<section id="coriolis"></section>
<script>
window.CORIOLIS_GAPI_KEY = '{%= o.htmlWebpackPlugin.options.gapiKey %}';
window.CORIOLIS_VERSION = '{%= o.htmlWebpackPlugin.options.version %}';
window.CORIOLIS_DATE = '{%= new Date().toISOString().slice(0, 10) %}';
</script>
<script src="{%= o.htmlWebpackPlugin.files.chunks.lib.entry %}" charset="utf-8" crossorigin="anonymous"></script>
<script src="{%= o.htmlWebpackPlugin.files.chunks.app.entry %}" charset="utf-8" crossorigin="anonymous"></script>
<script>
{% if (o.htmlWebpackPlugin.options.uaTracking) { %}
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', '{%= o.htmlWebpackPlugin.options.uaTracking %}', 'auto');
{% } %}
</script>
</body>
</html>

View File

@@ -16,9 +16,14 @@
@import 'tooltip';
@import 'buttons';
@import 'error';
@import 'shipselector';
@import 'sortable';
@import 'loader';
@import 'pips';
@import 'boost';
@import 'movement';
@import 'shippicker';
@import 'defence';
@import 'offence';
html, body {
height: 100%;
@@ -42,7 +47,6 @@ div, a, li {
#coriolis {
width: 100%;
height: 100%;
padding-top: 48px;
overflow-y: scroll;
-webkit-overflow-scrolling: touch;
box-sizing: border-box;

14
src/less/boost.less Executable file
View File

@@ -0,0 +1,14 @@
#boost {
button {
font-size: 1.2em;
background: @primary-bg;
color: @primary;
border: 1px solid @primary;
&.selected {
// Shown when button is selected
background: @primary;
color: @primary-bg;
}
}
}

View File

@@ -35,7 +35,8 @@ svg {
}
text {
font-size: 0.8em;
font-size: 1.2em;
font-family: @fStandard;
fill: @primary-disabled;
}
@@ -43,6 +44,9 @@ svg {
.label, .text-tip {
text-transform: capitalize;
}
.x {
fill: @fg;
}
@@ -55,7 +59,7 @@ svg {
}
.label {
font-size: 0.75em;
font-size: 1.1em;
}
.text-tip {

View File

@@ -24,51 +24,61 @@
.fg {
color: @fg;
stroke: @fg;
fill: @fg;
}
.muted {
color: @muted;
stroke: @muted;
fill: @muted;
}
.disabled {
color: @disabled;
stroke: @disabled;
fill: @disabled;
}
.primary {
color: @primary;
stroke: @primary;
fill: @primary;
}
.primary-bg {
color: @primary-bg;
stroke: @primary-bg;
fill: @primary-bg;
}
.primary-disabled {
color: @primary-disabled;
stroke: @primary-disabled;
fill: @primary-disabled;
}
.secondary {
color: @secondary;
stroke: @secondary;
fill: @secondary;
}
.secondary-disabled {
color: @secondary-disabled;
stroke: @secondary-disabled;
fill: @secondary-disabled;
}
.warning {
color: @warning;
stroke: @warning;
fill: @warning;
}
.warning-disabled {
color: @warning-disabled;
stroke: @warning-disabled;
fill: @warning-disabled;
}

14
src/less/defence.less Executable file
View File

@@ -0,0 +1,14 @@
#defence {
table {
background-color: @bgBlack;
color: @primary;
margin: 0 auto;
}
.icon {
stroke: @primary;
stroke-width: 20;
fill: transparent;
}
}

View File

@@ -20,10 +20,7 @@ header {
line-height: 3em;
font-family: @fTitle;
vertical-align: middle;
position: absolute;
top: 0px;
left: 0px;
width: 100%;
position: relative;
z-index: 2;
box-sizing: border-box;
.user-select-none();

Some files were not shown because too many files have changed in this diff Show More