Compare commits

...

253 Commits

Author SHA1 Message Date
willyb321
124bd62d2c 3.0.0 2018-09-23 11:42:38 +10:00
willyb321
975846f4ab Merge branch 'master' into develop 2018-09-23 11:41:23 +10:00
willyb321
3f73f9be10 Merge branch 'release/2.9.18' 2018-09-23 11:41:22 +10:00
willyb321
d218101708 2.9.18 2018-09-23 11:41:16 +10:00
Felix Linker
608ce12156 Undefined variable reference fix 2018-09-17 00:21:13 +02:00
William
80d653483a Merge pull request #386 from EDCD/feature/absolute-mods
Absolute modding
2018-09-17 07:30:44 +10:00
Felix Linker
f9a1e5afd9 Merge branch 'develop' into feature/absolute-mods 2018-09-16 23:29:59 +02:00
Felix Linker
7f5181a9c7 Improved modification menu design 2018-09-15 01:54:17 +02:00
Felix Linker
93d8f15f26 Moved diminishing returns for resistance modding on hrp into getModValue 2018-09-15 01:32:41 +02:00
Felix Linker
06d8930777 Typos fixed 2018-09-15 01:22:22 +02:00
William
23f4024e09 Merge pull request #385 from EDCD/feature/fixes
Fix experimental effect reverse engineering for resistance modding
2018-09-15 09:14:39 +10:00
Felix Linker
a5e9a71037 Clamp modification values to -50k and 50k 2018-09-15 01:12:39 +02:00
willyb321
423d42bc8a remove package-lock 2018-09-15 08:56:08 +10:00
willyb321
4075bab3d0 whoops 2018-09-15 08:54:31 +10:00
willyb321
469f7a0a48 format and request permission to store data
fixes people losing builds
2018-09-15 08:54:06 +10:00
Felix Linker
25d9f8ec61 Fix experimental effect reverse engineering for resistance modding 2018-09-15 00:50:14 +02:00
Felix Linker
1151bd1614 optmul changes are displayed additively 2018-09-15 00:42:39 +02:00
Felix Linker
2456ce330b Change module modding to have absolute values entered 2018-09-15 00:40:49 +02:00
Felix Linker
f86ce62c27 Optimized statistics calculation in shipyard/Ship 2018-09-15 00:38:38 +02:00
Felix Linker
c295a9f4e4 Added percentage as unit 2018-09-15 00:37:50 +02:00
Felix Linker
c4186faa4a Allow to set an absolute value for a module 2018-09-15 00:31:00 +02:00
William
00f4889c93 Merge pull request #381 from EDCD/feature/stats
Modules return null instead of undefined when a value is not given
2018-09-14 09:10:53 +10:00
Felix Linker
b6c83f4aba fix: hullboost mod imports 2018-09-13 23:42:51 +02:00
Felix Linker
3fa040c210 Modules return null instead of undefined when a value is not given 2018-09-13 23:02:35 +02:00
Felix Linker
8c4957c21e Merge pull request #364 from EDCD/feature/stats
Show absolute values next to modification values
2018-09-13 00:56:08 +02:00
Felix Linker
a178c59aa3 Merge branch 'develop' into feature/stats 2018-09-13 00:54:35 +02:00
William
0d9fcd97d7 Merge pull request #377 from EDCD/feature/please-willy
wait for load event before registering service worker
2018-09-12 08:40:39 +10:00
Felix Linker
b02ca117be wait for load event before registering service worker 2018-09-12 00:38:13 +02:00
William
ca2e2a22dd Merge pull request #371 from EDCD/feature/fixes
Various fixes
2018-09-12 08:17:48 +10:00
William
035e217124 Merge pull request #370 from pavchip/feature/update-russian-localization
Update Russian localization
2018-09-12 08:12:47 +10:00
Felix Linker
6424ab283a maxmass is not modified for shield generators now 2018-09-11 21:27:38 +02:00
Felix Linker
f30f10c904 hullboost modified values is calculated like shieldboost now 2018-09-11 20:44:23 +02:00
Felix Linker
8ce09a607f Implemented diminishing returns for hrp resistance modding 2018-09-11 13:09:29 +02:00
Pavel Chistyakov
5b48e30798 Update Russian localization 2018-09-11 13:31:11 +03:00
Felix Linker
0d7e6af2e6 Boost time now considers modifications 2018-09-10 01:03:13 +02:00
Felix Linker
be3ca23aee Consider guardian module reinforcement packages when calculation module protection 2018-09-10 01:00:36 +02:00
Felix Linker
28f464ba5a fix: falloff scaling now only applies for focused modification 2018-09-10 00:45:46 +02:00
Felix Linker
8cedd1b8bc When formating a value for a Module assign it to zero if it is undefined 2018-09-09 17:20:21 +02:00
Felix Linker
56cbc49d04 fix: when querying falloff mods is checked to be not undefined 2018-09-09 16:26:03 +02:00
Felix Linker
5d1ccd2a20 Removed synthetic values from modifications menu 2018-09-09 16:07:01 +02:00
Felix Linker
f898fbc55e Optimized _getModifiedValue 2018-09-09 16:01:54 +02:00
Felix Linker
63acad3aeb Sort mods in modifications menu by editable first 2018-09-09 16:01:54 +02:00
Felix Linker
5b4b0b8dff range and ranget are rounded with fixed decimal places now 2018-09-09 16:01:54 +02:00
Felix Linker
8492dd74f9 falloff and range are formated rounded to two decimal places 2018-09-09 16:01:54 +02:00
Felix Linker
77e652cd34 Allow for SE prefixes when formating module stats 2018-09-09 16:01:53 +02:00
felixlinker
5069d7e464 Modifications menu shows absolute stat values 2018-09-08 11:39:28 +02:00
felixlinker
8b3e9c0f63 Added method to retrieve a formatted stat from a module 2018-09-08 11:34:33 +02:00
felixlinker
99e76e480b Introduced translation for fallofffromrange 2018-09-08 11:33:26 +02:00
felixlinker
c161ed2298 Added units meters and angle 2018-09-08 11:32:56 +02:00
William Blythe
464770f096 Merge branch 'develop' 2018-09-06 09:57:34 +10:00
felixlinker
9fba1be814 Allow to retrieve unmodded vals from module 2018-09-05 17:58:16 +02:00
William
b419b8b104 Merge pull request #356 from EDCD/feature/caustic
Label fix
2018-09-01 09:12:20 +10:00
William
0cae955ca2 Merge pull request #359 from EDCD/feature/measure-charts
Fixed resize observer loop limit exceeded
2018-09-01 08:51:33 +10:00
felixlinker
ec70ad5d29 WeaponDamageChart wrapped in div instead of span 2018-08-31 15:02:44 +02:00
felixlinker
8a386c4ece Use react-container-dimensions instead of react-measure 2018-08-31 15:02:17 +02:00
felixlinker
013460ada4 Ship summary table header fix 2018-08-30 19:14:16 +02:00
William Blythe
d46ad89dc5 fix sw waiting 2018-08-30 08:25:50 +10:00
William
86c53c8e46 Merge pull request #354 from EDCD/feature/caustic
Bug fixes introduced with caustic damage calculation
2018-08-30 08:20:01 +10:00
felixlinker
df14786e79 Fixed bulkheads value for caustic damage 2018-08-29 11:37:28 +02:00
felixlinker
24f206ad82 Fixed ship summary table width 2018-08-29 11:37:14 +02:00
William Blythe
242c3efe45 Merge branch 'develop' of github.com:edcd/coriolis into develop 2018-08-29 09:24:27 +10:00
William
07a848b906 Merge pull request #353 from EDCD/feature/caustic
Added support for armour caustic resistances
2018-08-29 09:24:20 +10:00
felixlinker
1b5402fd2d Added support for caustic armour resistance 2018-08-28 12:38:47 +02:00
felixlinker
903d791549 Fixed tooltip for module protection in ShipSummaryTable 2018-08-28 10:53:40 +02:00
William Blythe
f03c164f1c Merge branch 'feature/alliance-crusader' into develop 2018-08-28 14:15:08 +10:00
William Blythe
107bab0192 Merge branch 'develop' 2018-08-27 08:42:21 +10:00
William Blythe
41f5ebb2f1 Merge remote-tracking branch 'origin/master' 2018-08-27 08:41:35 +10:00
William
266090dc2a Update sw.js 2018-08-27 08:34:10 +10:00
William
83f29f3d62 Merge pull request #351 from EDCD/feature/modules
Added support for research-limpet-controller
2018-08-27 08:30:43 +10:00
Felix Linker
3e6137ca96 Merge branch 'develop' into feature/modules 2018-08-26 20:13:33 +02:00
Felix Linker
58510bbd22 Added support for research-limpet-controller 2018-08-26 20:12:00 +02:00
William Blythe
2f7be75bcf update orbis integration 2018-08-26 09:48:12 +10:00
willyb321
0aecbbf892 [test] switch to service workers
dont merge to live until can be confirmed to work well
2018-08-26 09:44:44 +10:00
William Blythe
9ef2f4179b add alliance crusader 2018-08-25 18:09:58 +10:00
William Blythe
afbfe3ea12 update orbis integration 2018-08-25 13:59:14 +10:00
William Blythe
b145a2da85 update orbis integration 2018-08-25 13:58:55 +10:00
William
a679f7dd98 Merge pull request #348 from EDCD/feature/modules
Added support for new modules
2018-08-24 08:55:28 +10:00
William
f343366ea1 Merge pull request #350 from EDCD/feature/offence-table
Feature/offence table
2018-08-24 08:55:05 +10:00
Felix Linker
1bbea7dda0 Improved docs 2018-08-23 18:21:00 +02:00
Felix Linker
da097e0955 Added piechart for overall damage in offence tab 2018-08-23 18:09:18 +02:00
Felix Linker
1307474755 Added tooltips to offence table summary 2018-08-23 18:08:50 +02:00
Felix Linker
80f5a95297 Added support for new modules 2018-08-23 02:30:39 +02:00
William
e619966679 Merge pull request #346 from EDCD/feature/defence-proportions
Shield defence proportions calculated correctly
2018-08-22 07:04:13 +10:00
William
3f94382925 Merge pull request #345 from EDCD/feature/offence-table
Offence table improved (+Bug fix)
2018-08-22 07:03:57 +10:00
Felix Linker
ea7a6f8872 Added object documentation to shield damage multiplier objects 2018-08-20 03:45:05 +02:00
Felix Linker
4e87b3a0b8 Fixed shield proportion calc in defence panel 2018-08-20 03:44:24 +02:00
Felix Linker
8463dd46f7 Removed unused variable 2018-08-20 03:43:38 +02:00
Felix Linker
3febe465f6 reinforcement/booster contribution to defences now calulcated as multiplier 2018-08-20 02:13:11 +02:00
Felix Linker
45b834c424 Added summary and overall sdps to offence table 2018-08-20 02:12:06 +02:00
William
1ca041097b Merge pull request #343 from felixlinker/summary-shield-regen
Ship summary correctly calculates shield recovery/recharge time
2018-08-20 07:33:04 +10:00
William
587f7acd5b Merge pull request #344 from felixlinker/doc-fixes
doc fixes
2018-08-20 07:32:53 +10:00
Felix Linker
9e64e4a26b doc fixes 2018-08-19 23:26:17 +02:00
Felix Linker
96e09ab36c Ship summary correctly calculates shield recovery/recharge time 2018-08-19 15:41:27 +02:00
willyb321
4268d3f07b Merge branch 'develop' 2018-08-19 08:47:40 +10:00
William
1767a2aed5 Merge pull request #342 from felixlinker/import-shieldboost
Shieldboosters imported correctly now
2018-08-19 07:00:55 +10:00
William
9f061506bb Merge pull request #341 from felixlinker/shotspeed-blueprints
No special treatment for shotspeed modifications anymore
2018-08-19 07:00:19 +10:00
willyb321
0eb9a2048d Fix dev build
Closes #340
2018-08-19 06:59:28 +10:00
Felix Linker
faeafb2402 Shieldboosters imported correctly now 2018-08-18 22:28:13 +02:00
Felix Linker
2a0acfa6cb No special treatment for shotspeed modifications anymore 2018-08-18 21:15:37 +02:00
willyb321
0ac44ac267 (hopefully) fix bugsnag crashing the build 2018-08-17 08:05:04 +10:00
willyb321
b9ae3a4d5a just delete builds that are invalid when importing
temp fix until i figure out a better way
2018-08-17 07:49:49 +10:00
willyb321
b1aefb0003 just delete builds that are invalid when importing
temp fix until i figure out a better way
2018-08-17 07:48:48 +10:00
willyb321
f6a41ec55c make orbis auth checker better 2018-08-10 08:10:38 +10:00
willyb321
39650cc584 add orbis auth status 2018-08-09 16:43:21 +10:00
willyb321
d837287da7 add link to migrate to official domains 2018-08-07 11:46:18 +10:00
willyb321
6e718a39d1 add link to migrate to official domains 2018-08-07 11:46:01 +10:00
willyb321
2e7db02238 fix url 2018-08-07 11:30:23 +10:00
willyb321
dbfd108819 upload all builds to orbis under settings 2018-08-07 11:29:42 +10:00
willyb321
82c0f6fc0f hopefully fix beta.coriolis.edcd.io 2018-08-07 07:47:47 +10:00
willyb321
9e6bbaa67d fix url 2018-08-05 11:55:02 +10:00
willyb321
012a203c4a fix url 2018-08-05 11:54:51 +10:00
willyb321
c7ba129ed7 use different url shortener 2018-08-05 11:52:53 +10:00
willyb321
fbe57d00db use different url shortener 2018-08-05 11:41:53 +10:00
willyb321
dc4fdf215c fix compose again 2018-08-05 11:35:06 +10:00
willyb321
2166ac1584 fix docker-compose file 2018-08-05 10:41:41 +10:00
willyb321
504756de09 update orbis login 2018-08-02 12:56:45 +10:00
willyb321
90f04dba94 add link to orbis data 2018-08-01 16:35:44 +10:00
willyb321
bfd7645fb7 fix localhost uri 2018-08-01 14:27:09 +10:00
willyb321
c37c6983c3 orbis auth 2018-08-01 14:14:30 +10:00
willyb321
736c700aa0 orbis trial 2018-07-31 15:13:25 +10:00
willyb321
de1cb901fc more WIP for orbis 2018-07-28 11:36:44 +10:00
willyb321
3d9d6397f6 fix url shortener 2018-07-28 07:09:16 +10:00
willyb321
dbe836729f WIP for orbis 2018-07-27 11:44:14 +10:00
William Blythe
82fa93e676 Merge branch 'develop' into 'master' 2018-07-26 11:31:08 +10:00
willyb321
90a3392b80 oops i forgot a thing 2018-07-26 11:11:45 +10:00
willyb321
b7f715bd5e fix dockerfile to copy the right config 2018-07-26 08:58:27 +10:00
willyb321
5f88f46770 oops, remove ports and fix branch 2018-07-26 08:39:32 +10:00
willyb321
2244c91a64 Merge remote-tracking branch 'origin/develop' into develop 2018-07-26 08:33:54 +10:00
willyb321
78134404c3 prettify nginx, dockerfile updates 2018-07-26 08:33:05 +10:00
William
c9cd6b175d Merge pull request #333 from felixlinker/autoloader-sdps
SDPS takes autoloader into account
2018-07-26 08:06:09 +10:00
willyb321
5f70d283e0 do reset to branch 2018-07-26 07:25:32 +10:00
willyb321
cd1e27fd11 fix nginx conf 2018-07-25 11:48:01 +10:00
willyb321
0fd1c19514 dont reset 2018-07-25 11:25:21 +10:00
willyb321
a82dffd77d initial docker 2018-07-25 11:22:58 +10:00
Felix Linker
d3eed87077 new module method getSDps that takes autoloader into account 2018-07-25 02:07:53 +02:00
willyb321
d99f8ad7e7 remove preinstall script 2018-07-25 10:05:23 +10:00
William
e933305407 Merge pull request #329 from felixlinker/resistance-stacking
Caps for resistances introduced
2018-07-22 07:36:46 +10:00
felixlinker
690bc5a64a Resistance special effects now applied correctly 2018-07-20 17:54:07 +02:00
felixlinker
d2380a5c9c Armour resistances caps introduced 2018-07-20 14:11:33 +02:00
felixlinker
c2d0a6e9e0 Removed duplicate code on armour calculations 2018-07-20 14:11:33 +02:00
Felix Linker
a8c44fddca Shield resistances caps introduced 2018-07-20 14:11:33 +02:00
willyb321
0e6b306bdc Merge branch 'develop' 2018-07-20 07:31:29 +10:00
William
9b2a47ba0c Merge pull request #327 from felixlinker/sb-fixes
Fixes for shieldboost mods and special effects on shield booster
2018-07-20 07:29:49 +10:00
Felix Linker
3038ebc1c7 sb mod displays percentage change relative to 1+boost; sb special effect applied correctly 2018-07-19 17:37:34 +02:00
willyb321
5a48d5b400 Merge branch 'develop' 2018-07-19 10:26:21 +10:00
willyb321
f36edfe98d Fix guardian HRPs not calculating
Fixes #325
2018-07-19 10:22:42 +10:00
willyb321
3e80d6e13b Merge branch 'develop' 2018-07-18 08:30:07 +10:00
willyb321
c8f0a1ccc6 Fix #137
Recalculate stats after drag and drop
2018-07-18 08:29:08 +10:00
William
79325a3129 Merge pull request #319 from felixlinker/blueprint-special-resets
Resetting special effects for blueprints now re-calculates all stats
2018-07-18 08:04:39 +10:00
felixlinker
1ca0fffe5e Resetting special effects for blueprints now re-calculates all stats 2018-07-17 18:21:10 +02:00
willyb321
f5b9842b2e Merge branch 'develop' 2018-07-14 10:54:46 +10:00
willyb321
72ea3f125e guardian shard cannon 2018-07-14 10:52:28 +10:00
willyb321
f7a2408e44 add shock cannon 2018-07-14 10:32:47 +10:00
willyb321
c05de9a085 Merge branch 'develop' 2018-07-14 10:10:45 +10:00
willyb321
fc657f3b2d add guardian mrps and hrps 2018-07-14 10:09:45 +10:00
willyb321
e388de0364 Merge branch 'develop' 2018-07-11 12:02:30 +10:00
willyb321
26e55afb29 fix #315 2018-07-10 14:38:29 +10:00
willyb321
9b8e1039f6 also hide / disable buttons on firefox 2018-07-09 06:51:15 +10:00
willyb321
082d14ba50 tell user sending to edengineer doesnt work with firefox 2018-07-09 06:44:54 +10:00
willyb321
87ead595c6 possible fix for shopping list 2018-07-06 07:28:23 +10:00
willyb321
fb073acdc3 fix send to edengineer button disabled for some people 2018-07-06 07:09:05 +10:00
willyb321
3dda4e24bf update readme 2018-07-04 08:43:18 +10:00
willyb321
c966bd08ed preinstall clone coriolis-data 2018-07-04 08:37:11 +10:00
willyb321
692c73a6d0 add esdoc 2018-07-04 07:28:39 +10:00
willyb321
849b484b4d register specials in shopping list
ref msarilar/EDEngineer#431
2018-07-03 08:27:12 +10:00
William Blythe
9489baccda update .gitignore 2018-07-02 10:34:24 +10:00
willyb321
62cac15222 a couple fixes on /import 2018-07-02 06:41:01 +10:00
willyb321
0ee9ce2958 fix more args 2018-07-01 13:41:23 +10:00
willyb321
a83007fc6c fix parameter in the wrong function 2018-07-01 13:34:29 +10:00
willyb321
901f266dad dont allow stacking guardian fsd boosters 2018-07-01 13:31:59 +10:00
willyb321
6009426f04 add logic to handle guardian fsd boosters 2018-07-01 12:06:12 +10:00
willyb321
6fb89df4cd fix gsrp not updating shield strength 2018-07-01 11:44:53 +10:00
willyb321
48b99425a3 fix guardian shield reinforcement packages
add shield addition logic
2018-07-01 11:18:20 +10:00
willyb321
9569f9b09b more work on fixing resistances
hopefully getting close
2018-07-01 08:56:07 +10:00
willyb321
7421896902 store selected commander and also the list of commanders 2018-07-01 08:14:55 +10:00
willyb321
b837c8d66c add a cmdr dropdown instead of typing it manually 2018-06-30 11:11:08 +10:00
willyb321
e180f96eab hopefully much more accurate resistance calculation 2018-06-29 10:16:55 +10:00
willyb321
de1f1f790e gsrp 2018-06-29 07:39:59 +10:00
willyb321
fefa74ce0f add krait and challenger to /import 2018-06-29 07:39:37 +10:00
willyb321
a2ceb70aa4 simplify calcPipSpeed
also fix the order of parameters
2018-06-28 06:52:23 +10:00
willyb321
634103f144 update eslintrc to allow es6 2018-06-28 06:48:56 +10:00
willyb321
f3379de81e add formula to calculate pip speed
not used yet, but will be used eventually
2018-06-28 06:48:37 +10:00
willyb321
064ee74c1a fix jsdoc typo 2018-06-28 06:48:16 +10:00
willyb321
0e0c15fe3b lint a tad 2018-06-27 09:11:12 +10:00
willyb321
3c855c608d Merge branch 'develop' 2018-06-27 07:47:44 +10:00
willyb321
414516289c add message if sending to edengineer failed 2018-06-27 07:47:22 +10:00
willyb321
d79fa71337 Merge branch 'develop' 2018-06-27 05:55:18 +10:00
willyb321
39c2ca94c8 add "send to edengineer" button 2018-06-27 05:54:52 +10:00
willyb321
acf1c5ce04 Merge remote-tracking branch 'origin/develop' into develop 2018-06-26 08:02:58 +10:00
willyb321
fd7923155f more prep for gpd 2018-06-26 08:02:18 +10:00
willyb321
2aa6a6daa2 translations update 2018-06-26 08:01:57 +10:00
willyb321
9662621980 fix name of guardian pd 2018-06-26 08:01:46 +10:00
willyb321
37d007d9ab apply module blueprint and special before setting value
Fixes #297
2018-06-26 07:53:14 +10:00
willyb321
a4e48c359a Merge branch 'develop' 2018-06-17 08:24:58 +10:00
willyb321
8ef0101a6e apply module blueprint and special before setting value
Fixes #297
2018-06-17 08:24:24 +10:00
willyb321
e21f109026 Merge branch 'develop' 2018-06-15 10:58:19 +10:00
willyb321
737837eebd Merge remote-tracking branch 'origin/develop' into develop 2018-06-15 10:57:56 +10:00
willyb321
ba09b54409 fix jsdoc 2018-06-15 10:57:48 +10:00
willyb321
417091c648 fix lint 2018-06-15 10:45:00 +10:00
William Blythe
5a02368298 Merge branch 'develop' 2018-06-14 13:02:47 +10:00
William Blythe
da7c739497 add paypal button 2018-06-14 13:01:37 +10:00
willyb321
0842281466 Merge branch 'release/2.9.17' into develop 2018-06-14 07:08:21 +10:00
willyb321
b480f879b1 Merge branch 'release/2.9.17' 2018-06-14 07:08:21 +10:00
willyb321
f8a215d790 2.9.17 2018-06-14 07:08:17 +10:00
willyb321
da69f3b2c8 New URL Shortener
eddp.co shortened links are considered End-Of-Life from now on
2018-06-14 07:00:05 +10:00
willyb321
fc442c1a42 Merge branch 'master' into develop 2018-06-14 06:50:46 +10:00
willyb321
cf59a6b9fd whoops 2018-06-14 06:50:08 +10:00
willyb321
34afcd511a Merge branch 'develop' 2018-06-14 06:44:13 +10:00
willyb321
e2444a2e4e track ship in ga 2018-06-14 06:43:54 +10:00
willyb321
692516de9b Merge branch 'develop' 2018-06-13 08:37:31 +10:00
willyb321
d51009c823 add time between boosts
Fixes #269
2018-06-13 08:36:56 +10:00
willyb321
2f121bef5e Merge branch 'develop' 2018-06-13 06:46:27 +10:00
willyb321
61f7d376d2 store number of rolls per grade 2018-06-13 06:46:12 +10:00
willyb321
9e253012e6 Merge branch 'develop' 2018-06-13 06:23:57 +10:00
willyb321
e4e5b1327b fix rank link showing on things that dont need a rank link 2018-06-13 06:22:45 +10:00
willyb321
6ef3b227b8 Merge branch 'develop' 2018-06-13 06:16:37 +10:00
willyb321
bf657a0945 fix view changes 2018-06-13 06:16:21 +10:00
willyb321
c3dd1886c9 Merge branch 'develop' 2018-06-12 12:12:29 +10:00
willyb321
32498bb8a7 properly fix super cap 2018-06-12 11:32:37 +10:00
willyb321
69bb90a0e4 Merge branch 'release/2.9.16' 2018-06-12 11:03:18 +10:00
willyb321
8e2988edf0 Merge branch 'release/2.9.16' into develop 2018-06-12 11:03:18 +10:00
willyb321
876a352cfd 2.9.16 2018-06-12 11:03:13 +10:00
willyb321
84e44cabfa mats list modal allow setting # of rolls 2018-06-12 09:49:07 +10:00
willyb321
36a838d565 add material shopping list 2018-06-12 09:09:05 +10:00
willyb321
9ee8693f40 add material icon 2018-06-12 09:08:44 +10:00
willyb321
6f02965149 Merge branch 'develop' 2018-06-12 07:56:19 +10:00
willyb321
27ce82de3b switch to google analytics from piwik 2018-06-12 07:56:01 +10:00
Pat Nellesen
3d5a9ef220 Cleanup for linting issues, and added last bit of focus handling for … (#304)
* Cleanup for linting issues, and added last bit of focus handling for selected modifications and specials

* added correct value for selectedRefId for utility slot section menu when Empty All selected
2018-06-11 08:23:03 +10:00
Pat Nellesen
9b81f6efd2 Feature/#293 header keynav (#303)
Added keydown and focus handlers for Slot Section Menus ("Core Internal", "Optional Internal", etc.)

When focus is on the header, Enter key will open the menu and set focus to either the first option, or else the currently selected option, such as "Planetary Explorer" in Core Internal menu (if one has been previously selected).

While menu is open, Tab and Shift-Tab will move the focus up and down as expected. Shift-tab on first option will move focus to last option in the menu, and Tab on the last option will move focus to the top. Focus will stay inside the menu until menu is closed.

When focus is on a menu options, hitting the Enter key will trigger the onClick function for that option, and will set the option as the currently selected option for that menu.

Esc key will close the menu and set focus to the menu header H1 element.
2018-06-10 09:00:43 +10:00
willyb321
3e77e23d71 Merge remote-tracking branch 'origin/develop' into develop 2018-06-10 06:26:17 +10:00
willyb321
120c032c82 Fix approximately a lot of lint issues 2018-06-10 06:24:55 +10:00
William
46e15b8ecd Merge pull request #301 from BenJuan26/feature/linting
Fix obvious lint issues
2018-06-08 07:15:39 +10:00
William
d71d87041b Merge pull request #300 from BenJuan26/feature/travis-install-coriolis-data
Clone coriolis-data into Travis before npm install
2018-06-08 07:14:54 +10:00
Benjamin Schubert
124bd58b9f Fix obvious linting issues 2018-06-07 10:50:52 -04:00
Benjamin Schubert
257b9b0562 Clone coriolis-data into Travis before npm install 2018-06-07 10:00:46 -04:00
willyb321
b8e15f691d Merge branch 'develop' 2018-06-07 06:57:33 +10:00
willyb321
2255e3bfc4 Temp fix for #273
Don't allow engineering on guardian PP
2018-06-07 06:57:00 +10:00
willyb321
8797d84605 Merge branch 'develop' 2018-06-06 06:27:18 +10:00
willyb321
719179a923 Add link to ranks page
Fixes #298
2018-06-06 06:26:02 +10:00
willyb321
1d544099f6 Merge branch 'release/2.9.15' 2018-05-30 07:43:16 +10:00
willyb321
9b131a762a Merge branch 'release/2.9.15' into develop 2018-05-30 07:43:15 +10:00
willyb321
49a6c5f2c4 Merge branch 'release/2.9.14' 2018-05-29 07:03:18 +10:00
willyb321
298eaa8b4b Merge remote-tracking branch 'origin/develop' 2018-05-03 10:58:46 +10:00
willyb321
7b87038a8c Merge branch 'develop' 2018-05-03 07:39:08 +10:00
willyb321
fdc9171c69 Merge branch 'release/2.9.13' 2018-05-03 07:14:53 +10:00
willyb321
f43bd100e6 Merge branch 'release/2.9.12' 2018-04-28 12:16:21 +10:00
willyb321
fbba0e3ea5 Merge branch 'release/2.9.11' 2018-04-26 09:21:01 +10:00
willyb321
b15695128f Merge branch 'release/release/v2.9.10' 2018-04-25 20:00:47 +10:00
willyb321
0870b90443 Merge branch 'release/2.9.8' 2018-04-23 07:38:34 +10:00
willyb321
48ccab152b Merge branch 'release/2.9.7' 2018-04-22 12:27:20 +10:00
willyb321
f3bc900f16 Merge branch 'release/2.9.6' 2018-04-22 07:00:47 +10:00
willyb321
ec148847a9 Merge branch 'develop' 2018-04-21 11:39:19 +10:00
willyb321
0474af912a Merge branch 'release/2.9.5' 2018-04-21 11:05:29 +10:00
67 changed files with 4823 additions and 15577 deletions

2
.docker/.dockerignore Normal file
View File

@@ -0,0 +1,2 @@
node_modules
npm-debug.log

37
.docker/Dockerfile Normal file
View File

@@ -0,0 +1,37 @@
### STAGE 1: Build ###
FROM node:9.11.1-alpine as builder
ARG branch=develop
ENV BRANCH=$branch
WORKDIR /src/app
RUN mkdir -p /src/app/coriolis
RUN mkdir -p /src/app/coriolis-data
COPY ./coriolis/ /src/app/coriolis
COPY ./coriolis-data/ /src/app/coriolis-data
RUN apk update
RUN apk add git
RUN npm i -g npm
# Set up coriolis-data
WORKDIR /src/app/coriolis-data
RUN git fetch --all
RUN git reset --hard origin/$BRANCH
RUN npm install --no-package-lock
RUN npm start
WORKDIR /src/app/coriolis
RUN git fetch --all
RUN git reset --hard origin/$BRANCH
RUN npm install --no-package-lock
RUN npm run build
### STAGE 2: Production Environment ###
FROM nginx:1.13.12-alpine as web
COPY coriolis/.docker/nginx.conf /etc/nginx/nginx.conf
COPY --from=builder /src/app/coriolis/build /usr/share/nginx/html
WORKDIR /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-c", "/etc/nginx/nginx.conf", "-g", "daemon off;"]

View File

@@ -0,0 +1,34 @@
version: '2.2'
services:
coriolis_prod:
image: edcd/coriolis:master
restart: always
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
networks:
- web
labels:
- "traefik.docker.network=web"
- "traefik.enable=true"
- "traefik.basic.frontend.rule=Host:coriolis.io,coriolis.edcd.io"
- "traefik.basic.port=80"
- "traefik.basic.protocol=http"
coriolis_dev:
image: edcd/coriolis:develop
restart: always
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
networks:
- web
labels:
- "traefik.docker.network=web"
- "traefik.enable=true"
- "traefik.basic.frontend.rule=Host:beta.coriolis.io,beta.coriolis.edcd.io"
- "traefik.basic.port=80"
- "traefik.basic.protocol=http"
networks:
web:
external: true

45
.docker/nginx.conf Normal file
View File

@@ -0,0 +1,45 @@
worker_processes 1;
user nobody nobody;
error_log /tmp/error.log;
pid /tmp/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
client_body_temp_path /tmp/client_body;
fastcgi_temp_path /tmp/fastcgi_temp;
proxy_temp_path /tmp/proxy_temp;
scgi_temp_path /tmp/scgi_temp;
uwsgi_temp_path /tmp/uwsgi_temp;
access_log /tmp/access.log;
error_log /tmp/error.log;
keepalive_timeout 3000;
server {
listen 80;
listen [::]:80;
index index.html;
server_name localhost;
root /usr/share/nginx/html;
autoindex on;
location ~* \.(?:manifest|appcache|html?|xml|json|css|js|map|jpg|jpeg|gif|png|ico|svg|eot|ttf|woff|woff2)$ {
expires -1;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
access_log off;
}
location / {
try_files $uri $uri/ /index.html =404;
}
}
}

103
.esdoc.json Normal file
View File

@@ -0,0 +1,103 @@
{
"source": "./src/app",
"includes": ["\\.js$", "\\.jsx$"],
"destination": "./docs",
"index": "./README.md",
"plugins": [
{
"name": "esdoc-standard-plugin",
"option": {
"lint": {
"enable": false
},
"coverage": {
"enable": false
},
"accessor": {
"access": [
"public",
"protected",
"private"
],
"autoPrivate": true
},
"undocumentIdentifier": {
"enable": true
},
"unexportedIdentifier": {
"enable": false
},
"typeInference": {
"enable": true
},
"brand": {
"logo": "./src/images/logo/192x192.png",
"title": "Coriolis",
"description": "Coriolis Shipyard for Elite Dangerous",
"repository": "https://github.com/EDCD/coriolis",
"site": "https://coriolis.edcd.io",
"author": "https://github.com/edcd",
"image": "./src/images/logo/192x192.png"
}
}
},
{
"name": "esdoc-ecmascript-proposal-plugin",
"option": {
"all": true
}
},
{
"name": "esdoc-react-plugin"
},
{
"name": "esdoc-standard-plugin",
"option": {
"lint": {
"enable": false
},
"coverage": {
"enable": false
},
"accessor": {
"access": [
"public",
"protected",
"private"
],
"autoPrivate": true
},
"undocumentIdentifier": {
"enable": true
},
"unexportedIdentifier": {
"enable": false
},
"typeInference": {
"enable": true
},
"brand": {
"logo": "./src/images/logo/192x192.png",
"title": "Coriolis",
"description": "Coriolis Shipyard for Elite Dangerous",
"repository": "https://github.com/EDCD/coriolis",
"site": "https://coriolis.edcd.io",
"author": "https://github.com/edcd",
"image": "./src/images/logo/192x192.png"
}
}
},
{
"name": "esdoc-jsx-plugin",
"option": {
"enable": true
}
},
{
"name": "esdoc-publish-html-plugin",
"option": {
"template": "./node_modules/esdoc-custom-theme/template"
}
}
]
}

View File

@@ -5,11 +5,12 @@
"jsx": true, "jsx": true,
"classes": true, "classes": true,
"modules": true "modules": true
}, }
}, },
"env": { "env": {
"browser": true, "browser": true,
"node": true "node": true,
"es6": true
}, },
"plugins": [ "plugins": [
"react" "react"
@@ -33,7 +34,6 @@
"ClassDeclaration": true "ClassDeclaration": true
} }
}], }],
"no-console": 2,
"brace-style": [2, "1tbs", { "allowSingleLine": true }], "brace-style": [2, "1tbs", { "allowSingleLine": true }],
"comma-style": [2, "last"], "comma-style": [2, "last"],
"indent": [2, 2, { "SwitchCase": 1, "VariableDeclarator": 2 }], "indent": [2, 2, { "SwitchCase": 1, "VariableDeclarator": 2 }],

3
.gitignore vendored
View File

@@ -8,3 +8,6 @@ nginx.pid
env env
*.swp *.swp
.project .project
.vscode/
docs/
package-lock.json

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
package-lock=false

View File

@@ -8,7 +8,8 @@ cache:
directories: directories:
- node_modules - node_modules
before_script: before_install:
- git clone https://github.com/EDCD/coriolis-data.git ../coriolis-data
script: script:
- npm run lint - npm run lint

View File

@@ -23,6 +23,7 @@ Chat to us on [Discord](https://discord.gg/0uwCh6R62aPRjk9w)!
See the [Developer's Guide](https://github.com/EDCD/coriolis/wiki/Developing-for-Coriolis) in the wiki. See the [Developer's Guide](https://github.com/EDCD/coriolis/wiki/Developing-for-Coriolis) in the wiki.
Also see [the documentation site.](https://coriolis.willb.info/)
### Ship and Module Database ### Ship and Module Database

12853
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "coriolis_shipyard", "name": "coriolis_shipyard",
"version": "2.9.15", "version": "3.0.0",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/EDCD/coriolis" "url": "https://github.com/EDCD/coriolis"
@@ -65,9 +65,16 @@
"babel-preset-react": "*", "babel-preset-react": "*",
"babel-preset-stage-0": "*", "babel-preset-stage-0": "*",
"create-react-class": "^15.6.2", "create-react-class": "^15.6.2",
"css-loader": "^0.28.0",
"cross-env": "^5.1.4", "cross-env": "^5.1.4",
"css-loader": "^0.28.0",
"d3-selection": "1", "d3-selection": "1",
"esdoc": "^1.1.0",
"esdoc-custom-theme": "^1.4.2",
"esdoc-ecmascript-proposal-plugin": "^1.0.0",
"esdoc-jsx-plugin": "^1.0.0",
"esdoc-publish-html-plugin": "^1.1.2",
"esdoc-react-plugin": "^1.0.1",
"esdoc-standard-plugin": "^1.0.0",
"eslint": "3.19.0", "eslint": "3.19.0",
"eslint-plugin-react": "^6.10.3", "eslint-plugin-react": "^6.10.3",
"expose-loader": "^0.7.3", "expose-loader": "^0.7.3",
@@ -81,7 +88,7 @@
"less": "^2.7.2", "less": "^2.7.2",
"less-loader": "^4.0.3", "less-loader": "^4.0.3",
"react-addons-perf": "^15.4.2", "react-addons-perf": "^15.4.2",
"react-measure": "^1.4.7", "react-container-dimensions": "^1.4.1",
"react-testutils-additions": "^15.2.0", "react-testutils-additions": "^15.2.0",
"react-transition-group": "^1.1.2", "react-transition-group": "^1.1.2",
"rimraf": "^2.6.1", "rimraf": "^2.6.1",
@@ -91,9 +98,10 @@
"uglify-js": "^2.4.11", "uglify-js": "^2.4.11",
"url-loader": "^0.5.8", "url-loader": "^0.5.8",
"webpack": "^2.4.1", "webpack": "^2.4.1",
"webpack-bugsnag-plugins": "^1.1.1",
"webpack-dev-server": "^2.4.4", "webpack-dev-server": "^2.4.4",
"webpack-notifier": "^1.6.0", "webpack-notifier": "^1.6.0",
"webpack-bugsnag-plugins": "^1.1.1" "workbox-webpack-plugin": "^3.4.1"
}, },
"dependencies": { "dependencies": {
"babel-polyfill": "*", "babel-polyfill": "*",
@@ -103,12 +111,13 @@
"d3": "4.8.0", "d3": "4.8.0",
"detect-browser": "^1.7.0", "detect-browser": "^1.7.0",
"fbemitter": "^2.1.1", "fbemitter": "^2.1.1",
"lodash": "^4.17.4", "lodash": "^4.17.10",
"lz-string": "^1.4.4", "lz-string": "^1.4.4",
"pako": "^1.0.6", "pako": "^1.0.6",
"prop-types": "^15.5.8", "prop-types": "^15.5.8",
"react": "^15.5.4", "react": "^15.5.4",
"react-dom": "^15.5.4", "react-dom": "^15.5.4",
"react-ga": "^2.5.3",
"react-number-editor": "Athanasius/react-number-editor.git#miggy", "react-number-editor": "Athanasius/react-number-editor.git#miggy",
"recharts": "^0.22.3", "recharts": "^0.22.3",
"superagent": "^3.5.2" "superagent": "^3.5.2"

View File

@@ -93,8 +93,8 @@ export default class Coriolis extends React.Component {
// Need to decode and gunzip the data, then build the ship // Need to decode and gunzip the data, then build the ship
const data = zlib.inflate(new Buffer(r.params.data, 'base64'), { to: 'string' }); const data = zlib.inflate(new Buffer(r.params.data, 'base64'), { to: 'string' });
const json = JSON.parse(data); const json = JSON.parse(data);
console.log('Ship import data: '); console.info('Ship import data: ');
console.log(json); console.info(json);
let ship; let ship;
if (json && json.modules) { if (json && json.modules) {
ship = CompanionApiUtils.shipFromJson(json); ship = CompanionApiUtils.shipFromJson(json);
@@ -134,9 +134,9 @@ export default class Coriolis extends React.Component {
console && console.error && console.error(arguments); // eslint-disable-line no-console console && console.error && console.error(arguments); // eslint-disable-line no-console
if (errObj) { if (errObj) {
if (errObj instanceof Error) { if (errObj instanceof Error) {
bugsnagClient.notify(errObj) // eslint-disable-line bugsnagClient.notify(errObj); // eslint-disable-line
} else if (errObj instanceof String) { } else if (errObj instanceof String) {
bugsnagClient.notify(msg, errObj) // eslint-disable-line bugsnagClient.notify(msg, errObj); // eslint-disable-line
} }
} }
this.setState({ this.setState({
@@ -180,13 +180,13 @@ export default class Coriolis extends React.Component {
case 72: // 'h' case 72: // 'h'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + h if (e.ctrlKey || e.metaKey) { // CTRL/CMD + h
e.preventDefault(); e.preventDefault();
this._showModal(<ModalHelp />); this._showModal(<ModalHelp/>);
} }
break; break;
case 73: // 'i' case 73: // 'i'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + i if (e.ctrlKey || e.metaKey) { // CTRL/CMD + i
e.preventDefault(); e.preventDefault();
this._showModal(<ModalImport />); this._showModal(<ModalImport/>);
} }
break; break;
case 79: // 'o' case 79: // 'o'
@@ -208,7 +208,7 @@ export default class Coriolis extends React.Component {
* @param {React.Component} content Modal Content * @param {React.Component} content Modal Content
*/ */
_showModal(content) { _showModal(content) {
let modal = <div className='modal-bg' onClick={(e) => this._hideModal() }>{content}</div>; let modal = <div className='modal-bg' onClick={(e) => this._hideModal()}>{content}</div>;
this.setState({ modal }); this.setState({ modal });
} }
@@ -286,7 +286,7 @@ export default class Coriolis extends React.Component {
return this.emitter.addListener('windowResize', listener); return this.emitter.addListener('windowResize', listener);
} }
/** /**
* Add a listener to global commands such as save, * Add a listener to global commands such as save,
* @param {Function} listener Listener callback * @param {Function} listener Listener callback
* @return {Object} Subscription token * @return {Object} Subscription token
@@ -322,14 +322,60 @@ export default class Coriolis extends React.Component {
*/ */
componentWillMount() { componentWillMount() {
// Listen for appcache updated event, present refresh to update view // Listen for appcache updated event, present refresh to update view
if (window.applicationCache) { // Check that service workers are registered
window.applicationCache.addEventListener('updateready', () => { if (navigator.storage && navigator.storage.persist) {
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) { window.addEventListener('load', () => {
this.setState({ appCacheUpdate: true }); // Browser downloaded a new app cache. navigator.storage.persist().then(granted => {
} if (granted)
console.log('Storage will not be cleared except by explicit user action');
else
console.log('Storage may be cleared by the UA under storage pressure.');
});
}); });
} }
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
// Your service-worker.js *must* be located at the top-level directory relative to your site.
// It won't be able to control pages unless it's located at the same level or higher than them.
// *Don't* register service worker file in, e.g., a scripts/ sub-directory!
// See https://github.com/slightlyoff/ServiceWorker/issues/468
const self = this;
navigator.serviceWorker.register('/service-worker.js').then(function(reg) {
// updatefound is fired if service-worker.js changes.
reg.onupdatefound = function() {
// The updatefound event implies that reg.installing is set; see
// https://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html#service-worker-container-updatefound-event
var installingWorker = reg.installing;
installingWorker.onstatechange = function() {
switch (installingWorker.state) {
case 'installed':
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and the fresh content will
// have been added to the cache.
// It's the perfect time to display a "New content is available; please refresh."
// message in the page's interface.
console.log('New or updated content is available.');
self.setState({ appCacheUpdate: true }); // Browser downloaded a new app cache.
} else {
// At this point, everything has been precached.
// It's the perfect time to display a "Content is cached for offline use." message.
console.log('Content is now available offline!');
self.setState({ appCacheUpdate: true }); // Browser downloaded a new app cache.
}
break;
case 'redundant':
console.error('The installing service worker became redundant.');
break;
}
};
};
}).catch(function(e) {
console.error('Error during service worker registration:', e);
});
});
}
window.onerror = this._onError.bind(this); window.onerror = this._onError.bind(this);
window.addEventListener('resize', () => this.emitter.emit('windowResize')); window.addEventListener('resize', () => this.emitter.emit('windowResize'));
document.getElementById('coriolis').addEventListener('scroll', () => this._tooltip()); document.getElementById('coriolis').addEventListener('scroll', () => this._tooltip());
@@ -347,16 +393,22 @@ export default class Coriolis extends React.Component {
render() { render() {
let currentMenu = this.state.currentMenu; let currentMenu = this.state.currentMenu;
return <div style={{ minHeight: '100%' }} onClick={this._closeMenu} className={ this.state.noTouch ? 'no-touch' : null }> return <div style={{ minHeight: '100%' }} onClick={this._closeMenu}
<Header appCacheUpdate={this.state.appCacheUpdate} currentMenu={currentMenu} /> className={this.state.noTouch ? 'no-touch' : null}>
{ this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) : <NotFoundPage/> } <Header appCacheUpdate={this.state.appCacheUpdate} currentMenu={currentMenu}/>
{ this.state.modal } {this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) :
{ this.state.tooltip } <NotFoundPage/>}
{this.state.modal}
{this.state.tooltip}
<footer> <footer>
<div className="right cap"> <div className="right cap">
<a href="https://github.com/EDCD/coriolis" target="_blank" title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a> <a href="https://github.com/EDCD/coriolis" target="_blank"
title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>
<br/> <br/>
<a href={"https://github.com/EDCD/coriolis/compare/edcd:develop@{" + window.CORIOLIS_DATE + "}...edcd:develop"} target="_blank" title={"Coriolis Commits since" + window.CORIOLIS_DATE}>Commits since last release ({window.CORIOLIS_DATE})</a> <a
href={'https://github.com/EDCD/coriolis/compare/edcd:develop@{' + window.CORIOLIS_DATE + '}...edcd:develop'}
target="_blank" title={'Coriolis Commits since' + window.CORIOLIS_DATE}>Commits since last release
({window.CORIOLIS_DATE})</a>
</div> </div>
</footer> </footer>
</div>; </div>;

View File

@@ -1,5 +1,6 @@
import Persist from './stores/Persist'; import Persist from './stores/Persist';
import ReactGA from 'react-ga';
ReactGA.initialize('UA-55840909-18');
let standalone = undefined; let standalone = undefined;
/** /**
@@ -257,9 +258,16 @@ Route.prototype.match = function(path, params) {
* @param {string} path Path to track * @param {string} path Path to track
*/ */
function gaTrack(path) { function gaTrack(path) {
if (window.ga) { const match = path.match(/\/outfit\/(.*)(\?code=.*)/);
window.ga('send', 'pageview', path); if (match) {
if (match[1]) {
ReactGA.ga('set', 'contentGroup1', match[1]);
}
if (match[2]) {
ReactGA.ga('set', 'contentGroup2', match[2]);
}
} }
ReactGA.pageview(path);
} }
/** /**

View File

@@ -45,6 +45,12 @@ const GRPCAT = {
'mr': 'ordnance', 'mr': 'ordnance',
'axmr': 'experimental', 'axmr': 'experimental',
'rcpl': 'experimental', 'rcpl': 'experimental',
'dtl': 'experimental',
'tbsc': 'experimental',
'tbem': 'experimental',
'tbrfl': 'experimental',
'mahr': 'experimental',
'rsl': 'experimental',
'tp': 'ordnance', 'tp': 'ordnance',
'nl': 'ordnance', 'nl': 'ordnance',
'sc': 'scanners', 'sc': 'scanners',
@@ -58,10 +64,15 @@ const GRPCAT = {
'po': 'defence', 'po': 'defence',
'ec': 'defence', 'ec': 'defence',
'sfn': 'defence', 'sfn': 'defence',
// Standard // Guardian
'gpp': 'guardian', 'gpp': 'guardian',
'gpc': 'guardian', 'gpc': 'guardian',
'ggc': 'guardian' 'gsrp': 'guardian',
'ggc': 'guardian',
'gfsb': 'guardian',
'gmrp': 'guardian',
'gsc': 'guardian',
'ghrp': 'guardian'
}; };
// Order here is the order in which items will be shown in the modules menu // Order here is the order in which items will be shown in the modules menu
const CATEGORIES = { const CATEGORIES = {
@@ -87,10 +98,10 @@ const CATEGORIES = {
'defence': ['ch', 'po', 'ec'], 'defence': ['ch', 'po', 'ec'],
'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners 'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners
// Experimental // Experimental
'experimental': ['axmc', 'axmr', 'rfl', 'xs', 'sfn', 'rcpl'], 'experimental': ['axmc', 'axmr', 'rfl', 'tbrfl', 'tbsc', 'tbem', 'xs', 'sfn', 'rcpl', 'dtl', 'rsl', 'mahr', ],
// Guardian // Guardian
'guardian': ['gpp', 'gpc', 'ggc'] 'guardian': ['gpp', 'gpd', 'gpc', 'ggc', 'gsrp', 'gfsb', 'ghrp', 'gmrp', 'gsc']
}; };
/** /**
@@ -137,7 +148,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
let translate = context.language.translate; let translate = context.language.translate;
let { m, warning, shipMass, onSelect, modules, firstSlotId, lastSlotId } = props; let { m, warning, shipMass, onSelect, modules, firstSlotId, lastSlotId } = props;
let list, currentGroup; let list, currentGroup;
let buildGroup = this._buildGroup.bind( let buildGroup = this._buildGroup.bind(
this, this,
translate, translate,
@@ -149,7 +160,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
onSelect(m); onSelect(m);
} }
); );
if (modules instanceof Array) { if (modules instanceof Array) {
list = buildGroup(modules[0].grp, modules); list = buildGroup(modules[0].grp, modules);
} else { } else {
@@ -210,7 +221,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
} }
} }
let trackingFocus = false; let trackingFocus = false;
return { list, currentGroup, trackingFocus}; return { list, currentGroup, trackingFocus };
} }
/** /**
@@ -222,15 +233,17 @@ export default class AvailableModulesMenu extends TranslatedComponent {
* @param {function} onSelect Select/Mount callback * @param {function} onSelect Select/Mount callback
* @param {string} grp Group name * @param {string} grp Group name
* @param {Array} modules Available modules * @param {Array} modules Available modules
* @param {string} firstSlotId id of first slot item
* @param {string} lastSlotId id of last slot item
* @return {React.Component} Available Module Group contents * @return {React.Component} Available Module Group contents
*/ */
_buildGroup(translate, mountedModule, warningFunc, mass, onSelect, grp, modules, firstSlotId, lastSlotId) { _buildGroup(translate, mountedModule, warningFunc, mass, onSelect, grp, modules, firstSlotId, lastSlotId) {
let prevClass = null, prevRating = null, prevName; let prevClass = null, prevRating = null, prevName;
let elems = []; let elems = [];
const sortedModules = modules.sort(this._moduleOrder); 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 // 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 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])); const itemsPerClass = Math.max.apply(null, Object.keys(tmp).map(key => tmp[key]));
@@ -240,7 +253,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
let m = sortedModules[i]; let m = sortedModules[i];
let mount = null; let mount = null;
let disabled = false; let disabled = false;
prevName = m.name prevName = m.name;
if (ModuleUtils.isShieldGenerator(m.grp)) { if (ModuleUtils.isShieldGenerator(m.grp)) {
// Shield generators care about maximum hull mass // Shield generators care about maximum hull mass
disabled = mass > m.maxmass; disabled = mass > m.maxmass;
@@ -305,7 +318,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
{(mount ? ' ' : '') + m.class + m.rating + (m.missile ? '/' + m.missile : '') + (m.name ? ' ' + translate(m.name) : '')} {(mount ? ' ' : '') + m.class + m.rating + (m.missile ? '/' + m.missile : '') + (m.name ? ' ' + translate(m.name) : '')}
</li> </li>
); );
itemsOnThisRow++; itemsOnThisRow++;
prevClass = m.class; prevClass = m.class;
prevRating = m.rating; prevRating = m.rating;
@@ -367,23 +380,22 @@ export default class AvailableModulesMenu extends TranslatedComponent {
* @param {Function} select Select module callback * @param {Function} select Select module callback
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_keyDown(select, event) { _keyDown(select, event) {
var className = event.currentTarget.attributes['class'].value; let className = event.currentTarget.attributes['class'].value;
if (event.key == 'Enter' && className.indexOf('disabled') < 0 && className.indexOf('active') < 0) { if (event.key == 'Enter' && className.indexOf('disabled') < 0 && className.indexOf('active') < 0) {
select(); select();
return return;
} }
var elemId = event.currentTarget.attributes['data-id'].value; let elemId = event.currentTarget.attributes['data-id'].value;
if (className.indexOf('disabled') < 0 && event.key == 'Tab') { if (className.indexOf('disabled') < 0 && event.key == 'Tab') {
if (event.shiftKey && elemId == this.firstSlotId) { if (event.shiftKey && elemId == this.firstSlotId) {
event.preventDefault(); event.preventDefault();
this.slotItems[this.lastSlotId].focus(); this.slotItems[this.lastSlotId].focus();
return; return;
} }
if (!event.shiftKey && elemId == this.lastSlotId) { if (!event.shiftKey && elemId == this.lastSlotId) {
event.preventDefault(); event.preventDefault();
this.slotItems[this.firstSlotId].focus(); this.slotItems[this.firstSlotId].focus();
return; return;
} }
} }
@@ -391,10 +403,11 @@ export default class AvailableModulesMenu extends TranslatedComponent {
/** /**
* Key Up * Key Up
* * @param {Function} select Select module callback
* @param {SytheticEvent} event Event
*/ */
_keyUp(select,event) { _keyUp(select,event) {
//nothing here yet // nothing here yet
} }
/** /**
@@ -463,11 +476,11 @@ export default class AvailableModulesMenu extends TranslatedComponent {
this.slotItems[this.firstSlotId].focus(); this.slotItems[this.firstSlotId].focus();
} }
} }
/**
* Handle focus if the component updates
*
*/
componentWillUnmount() { componentWillUnmount() {
/**
* Set focus to slot element ref (if we have one) after modules component unmounts
*/
if(this.props.slotDiv) { if(this.props.slotDiv) {
this.props.slotDiv.focus(); this.props.slotDiv.focus();
} }
@@ -487,7 +500,6 @@ export default class AvailableModulesMenu extends TranslatedComponent {
* @return {React.Component} List * @return {React.Component} List
*/ */
render() { render() {
console.log("Tracking focus? " + this.state.trackingFocus);
return ( return (
<div ref={node => this.node = node} <div ref={node => this.node = node}
className={cn('select', this.props.className)} className={cn('select', this.props.className)}

View File

@@ -76,6 +76,7 @@ export default class Defence extends TranslatedComponent {
shieldSourcesData.push({ value: Math.round(shield.generator), label: translate('generator') }); 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.boosters), label: translate('boosters') });
shieldSourcesData.push({ value: Math.round(shield.cells), label: translate('cells') }); shieldSourcesData.push({ value: Math.round(shield.cells), label: translate('cells') });
shieldSourcesData.push({ value: Math.round(shield.addition), label: translate('shield addition') });
if (shield.generator > 0) { if (shield.generator > 0) {
shieldSourcesTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>); shieldSourcesTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
@@ -101,19 +102,19 @@ export default class Defence extends TranslatedComponent {
// Add effective shield from resistances // Add effective shield from resistances
const rawMj = shield.generator + shield.boosters + shield.cells; const rawMj = shield.generator + shield.boosters + shield.cells;
const explosiveMj = rawMj / (shield.explosive.generator * shield.explosive.boosters) - rawMj; const explosiveMj = rawMj / (shield.explosive.base) - rawMj;
if (explosiveMj != 0) effectiveShieldExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(explosiveMj)}{units.MJ}</div>); 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; const kineticMj = rawMj / (shield.kinetic.base) - rawMj;
if (kineticMj != 0) effectiveShieldKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(kineticMj)}{units.MJ}</div>); 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; const thermalMj = rawMj / (shield.thermal.base) - rawMj;
if (thermalMj != 0) effectiveShieldThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(thermalMj)}{units.MJ}</div>); if (thermalMj != 0) effectiveShieldThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(thermalMj)}{units.MJ}</div>);
// Add effective shield from power distributor SYS pips // Add effective shield from power distributor SYS pips
if (shield.absolute.sys != 1) { if (shield.absolute.sys != 1) {
effectiveShieldAbsoluteTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.absolute.sys - rawMj)}{units.MJ}</div>); effectiveShieldAbsoluteTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.absolute.total - rawMj)}{units.MJ}</div>);
effectiveShieldExplosiveTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.explosive.sys - rawMj)}{units.MJ}</div>); effectiveShieldExplosiveTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.explosive.total - rawMj / shield.explosive.base)}{units.MJ}</div>);
effectiveShieldKineticTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.kinetic.sys - rawMj)}{units.MJ}</div>); effectiveShieldKineticTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.kinetic.total - rawMj / shield.kinetic.base)}{units.MJ}</div>);
effectiveShieldThermalTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.thermal.sys - rawMj)}{units.MJ}</div>); effectiveShieldThermalTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.int(rawMj / shield.thermal.total - rawMj / shield.thermal.base)}{units.MJ}</div>);
} }
} }
@@ -159,18 +160,21 @@ export default class Defence extends TranslatedComponent {
const effectiveArmourExplosiveTt = []; const effectiveArmourExplosiveTt = [];
const effectiveArmourKineticTt = []; const effectiveArmourKineticTt = [];
const effectiveArmourThermalTt = []; const effectiveArmourThermalTt = [];
const effectiveArmourCausticTt = [];
if (armour.bulkheads > 0) { if (armour.bulkheads > 0) {
armourSourcesTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>); armourSourcesTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
effectiveArmourAbsoluteTt.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>); effectiveArmourExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
effectiveArmourKineticTt.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>); effectiveArmourThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
effectiveArmourCausticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
if (armour.reinforcement > 0) { if (armour.reinforcement > 0) {
armourSourcesTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>); armourSourcesTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
effectiveArmourAbsoluteTt.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>); effectiveArmourExplosiveTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
effectiveArmourKineticTt.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>); effectiveArmourThermalTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
effectiveArmourCausticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
} }
} }
@@ -183,17 +187,22 @@ export default class Defence extends TranslatedComponent {
const armourDamageTakenExplosiveTt = []; const armourDamageTakenExplosiveTt = [];
armourDamageTakenExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.explosive.bulkheads)}</div>); 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>); 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>); if (armour.explosive.total != 1) effectiveArmourExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.explosive.total - rawArmour)}</div>);
const armourDamageTakenKineticTt = []; const armourDamageTakenKineticTt = [];
armourDamageTakenKineticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.kinetic.bulkheads)}</div>); 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>); 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>); if (armour.kinetic.total != 1) effectiveArmourKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.kinetic.total - rawArmour)}</div>);
const armourDamageTakenThermalTt = []; const armourDamageTakenThermalTt = [];
armourDamageTakenThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.thermal.bulkheads)}</div>); 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>); 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>); if (armour.thermal.total != 1) effectiveArmourThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.thermal.total - rawArmour)}</div>);
const armourDamageTakenCausticTt = [];
armourDamageTakenCausticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.caustic.bulkheads)}</div>);
armourDamageTakenCausticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.caustic.reinforcement)}</div>);
if (armour.thermal.total != 1) effectiveArmourCausticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / armour.caustic.total - rawArmour)}</div>);
const effectiveArmourData = []; const effectiveArmourData = [];
const effectiveAbsoluteArmour = armour.total / armour.absolute.total; const effectiveAbsoluteArmour = armour.total / armour.absolute.total;
@@ -204,12 +213,15 @@ export default class Defence extends TranslatedComponent {
effectiveArmourData.push({ value: Math.round(effectiveKineticArmour), label: translate('kinetic'), tooltip: effectiveArmourKineticTt }); effectiveArmourData.push({ value: Math.round(effectiveKineticArmour), label: translate('kinetic'), tooltip: effectiveArmourKineticTt });
const effectiveThermalArmour = armour.total / armour.thermal.total; const effectiveThermalArmour = armour.total / armour.thermal.total;
effectiveArmourData.push({ value: Math.round(effectiveThermalArmour), label: translate('thermal'), tooltip: effectiveArmourThermalTt }); effectiveArmourData.push({ value: Math.round(effectiveThermalArmour), label: translate('thermal'), tooltip: effectiveArmourThermalTt });
const effectiveCausticArmour = armour.total / armour.caustic.total;
effectiveArmourData.push({ value: Math.round(effectiveCausticArmour), label: translate('caustic'), tooltip: effectiveArmourCausticTt });
const armourDamageTakenData = []; const armourDamageTakenData = [];
armourDamageTakenData.push({ value: Math.round(armour.absolute.total * 100), label: translate('absolute'), tooltip: armourDamageTakenTt }); 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.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.kinetic.total * 100), label: translate('kinetic'), tooltip: armourDamageTakenKineticTt });
armourDamageTakenData.push({ value: Math.round(armour.thermal.total * 100), label: translate('thermal'), tooltip: armourDamageTakenThermalTt }); armourDamageTakenData.push({ value: Math.round(armour.thermal.total * 100), label: translate('thermal'), tooltip: armourDamageTakenThermalTt });
armourDamageTakenData.push({ value: Math.round(armour.caustic.total * 100), label: translate('caustic'), tooltip: armourDamageTakenCausticTt });
return ( return (
<span id='defence'> <span id='defence'>

View File

@@ -57,7 +57,7 @@ export default class FSDProfile extends TranslatedComponent {
*/ */
_calcMaxRange(ship, fuel, mass) { _calcMaxRange(ship, fuel, mass) {
// Obtain the maximum range // Obtain the maximum range
return Calc.jumpRange(mass, ship.standard[2].m, Math.min(fuel, ship.standard[2].m.getMaxFuelPerJump())); return Calc.jumpRange(mass, ship.standard[2].m, Math.min(fuel, ship.standard[2].m.getMaxFuelPerJump()), ship);
} }
/** /**
@@ -77,7 +77,7 @@ export default class FSDProfile extends TranslatedComponent {
const maxMass = thrusters.getMaxMass(); const maxMass = thrusters.getMaxMass();
const mass = ship.unladenMass + fuel + cargo; const mass = ship.unladenMass + fuel + cargo;
const minRange = 0; const minRange = 0;
const maxRange = Calc.jumpRange(minMass + fsd.getMaxFuelPerJump(), fsd, fsd.getMaxFuelPerJump()); const maxRange = Calc.jumpRange(minMass + fsd.getMaxFuelPerJump(), fsd, fsd.getMaxFuelPerJump(), ship);
// Add a mark at our current mass // Add a mark at our current mass
const mark = Math.min(mass, maxMass); const mark = Math.min(mass, maxMass);

View File

@@ -79,7 +79,7 @@ export default class HardpointSlot extends Slot {
<div className={'r'}>{formats.round(m.getMass())}{u.T}</div> <div className={'r'}>{formats.round(m.getMass())}{u.T}</div>
</div> </div>
<div className={'cb'}> <div className={'cb'}>
{ m.getDps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'dpssdps' : 'dps')} onMouseOut={tooltip.bind(null, null)}>{translate('DPS')}: {formats.round1(m.getDps())} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) })</span> : null }</div> : null } { m.getDps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'dpssdps' : 'dps')} onMouseOut={tooltip.bind(null, null)}>{translate('DPS')}: {formats.round1(m.getDps())} { m.getClip() ? <span>({formats.round1(m.getSDps()) })</span> : null }</div> : null }
{ m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'epsseps' : 'eps')} onMouseOut={tooltip.bind(null, null)}>{translate('EPS')}: {formats.round1(m.getEps())}{u.MW} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) }{u.MW})</span> : null }</div> : null } { m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'epsseps' : 'eps')} onMouseOut={tooltip.bind(null, null)}>{translate('EPS')}: {formats.round1(m.getEps())}{u.MW} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) }{u.MW})</span> : null }</div> : null }
{ m.getHps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'hpsshps' : 'hps')} onMouseOut={tooltip.bind(null, null)}>{translate('HPS')}: {formats.round1(m.getHps())} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getHps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) })</span> : null }</div> : null } { m.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.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 }

View File

@@ -17,14 +17,25 @@ export default class HardpointSlotSection extends SlotSection {
*/ */
constructor(props, context) { constructor(props, context) {
super(props, context, 'hardpoints', 'hardpoints'); super(props, context, 'hardpoints', 'hardpoints');
this._empty = this._empty.bind(this); this._empty = this._empty.bind(this);
this.selectedRefId = null;
this.firstRefId = 'emptyall';
this.lastRefId = 'nl-F';
}
/**
* Handle focus when component updates
* @param {Object} prevProps React Component properties
*/
componentDidUpdate(prevProps) {
this._handleSectionFocus(prevProps,this.firstRefId, this.lastRefId);
} }
/** /**
* Empty all slots * Empty all slots
*/ */
_empty() { _empty() {
this.selectedRefId = 'emptyall';
this.props.ship.emptyWeapons(); this.props.ship.emptyWeapons();
this.props.onChange(); this.props.onChange();
this._close(); this._close();
@@ -37,6 +48,7 @@ export default class HardpointSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_fill(group, mount, event) { _fill(group, mount, event) {
this.selectedRefId = group + '-' + mount;
this.props.ship.useWeapon(group, mount, null, event.getModifierState('Alt')); this.props.ship.useWeapon(group, mount, null, event.getModifierState('Alt'));
this.props.onChange(); this.props.onChange();
this._close(); this._close();
@@ -95,52 +107,52 @@ export default class HardpointSlotSection extends SlotSection {
return <div className='select hardpoint' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}> return <div className='select hardpoint' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul> <ul>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li> <li className='lc' tabIndex='0' onClick={this._empty} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['emptyall'] = smRef}>{translate('empty all')}</li>
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li> <li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
</ul> </ul>
<div className='select-group cap'>{translate('pl')}</div> <div className='select-group cap'>{translate('pl')}</div>
<ul> <ul>
<li className='c' onClick={_fill.bind(this, 'pl', 'F')}><MountFixed className='lg'/></li> <li className='c' tabIndex='0' onClick={_fill.bind(this, 'pl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pl-F'] = smRef}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'pl', 'G')}><MountGimballed className='lg'/></li> <li className='c' tabIndex='0' onClick={_fill.bind(this, 'pl', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pl-G'] = smRef}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'pl', 'T')}><MountTurret className='lg'/></li> <li className='c' tabIndex='0' onClick={_fill.bind(this, 'pl', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pl-T'] = smRef}><MountTurret className='lg'/></li>
</ul> </ul>
<div className='select-group cap'>{translate('ul')}</div> <div className='select-group cap'>{translate('ul')}</div>
<ul> <ul>
<li className='c' onClick={_fill.bind(this, 'ul', 'F')}><MountFixed className='lg'/></li> <li className='c' tabIndex='0' onClick={_fill.bind(this, 'ul', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ul-F'] = smRef}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'ul', 'G')}><MountGimballed className='lg'/></li> <li className='c' tabIndex='0' onClick={_fill.bind(this, 'ul', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ul-G'] = smRef}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'ul', 'T')}><MountTurret className='lg'/></li> <li className='c' tabIndex='0' onClick={_fill.bind(this, 'ul', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ul-T'] = smRef}><MountTurret className='lg'/></li>
</ul> </ul>
<div className='select-group cap'>{translate('bl')}</div> <div className='select-group cap'>{translate('bl')}</div>
<ul> <ul>
<li className='c' onClick={_fill.bind(this, 'bl', 'F')}><MountFixed className='lg'/></li> <li className='c' tabIndex='0' onClick={_fill.bind(this, 'bl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['bl-F'] = smRef}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'bl', 'G')}><MountGimballed className='lg'/></li> <li className='c' tabIndex='0' onClick={_fill.bind(this, 'bl', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['bl-G'] = smRef}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'bl', 'T')}><MountTurret className='lg'/></li> <li className='c' tabIndex='0' onClick={_fill.bind(this, 'bl', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['bl-T'] = smRef}><MountTurret className='lg'/></li>
</ul> </ul>
<div className='select-group cap'>{translate('mc')}</div> <div className='select-group cap'>{translate('mc')}</div>
<ul> <ul>
<li className='c' onClick={_fill.bind(this, 'mc', 'F')}><MountFixed className='lg'/></li> <li className='c' tabIndex='0' onClick={_fill.bind(this, 'mc', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['mc-F'] = smRef}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'mc', 'G')}><MountGimballed className='lg'/></li> <li className='c' tabIndex='0' onClick={_fill.bind(this, 'mc', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['mc-G'] = smRef}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'mc', 'T')}><MountTurret className='lg'/></li> <li className='c' tabIndex='0' onClick={_fill.bind(this, 'mc', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['mc-T'] = smRef}><MountTurret className='lg'/></li>
</ul> </ul>
<div className='select-group cap'>{translate('c')}</div> <div className='select-group cap'>{translate('c')}</div>
<ul> <ul>
<li className='c' onClick={_fill.bind(this, 'c', 'F')}><MountFixed className='lg'/></li> <li className='c' tabIndex='0' onClick={_fill.bind(this, 'c', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['c-F'] = smRef}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'c', 'G')}><MountGimballed className='lg'/></li> <li className='c' tabIndex='0' onClick={_fill.bind(this, 'c', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['c-G'] = smRef}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'c', 'T')}><MountTurret className='lg'/></li> <li className='c' tabIndex='0' onClick={_fill.bind(this, 'c', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['c-T'] = smRef}><MountTurret className='lg'/></li>
</ul> </ul>
<div className='select-group cap'>{translate('fc')}</div> <div className='select-group cap'>{translate('fc')}</div>
<ul> <ul>
<li className='c' onClick={_fill.bind(this, 'fc', 'F')}><MountFixed className='lg'/></li> <li className='c' tabIndex='0' onClick={_fill.bind(this, 'fc', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['fc-F'] = smRef}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'fc', 'G')}><MountGimballed className='lg'/></li> <li className='c' tabIndex='0' onClick={_fill.bind(this, 'fc', 'G')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['fc-G'] = smRef}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'fc', 'T')}><MountTurret className='lg'/></li> <li className='c' tabIndex='0' onClick={_fill.bind(this, 'fc', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['fc-T'] = smRef}><MountTurret className='lg'/></li>
</ul> </ul>
<div className='select-group cap'>{translate('pa')}</div> <div className='select-group cap'>{translate('pa')}</div>
<ul> <ul>
<li className='lc' onClick={_fill.bind(this, 'pa', 'F')}>{translate('pa')}</li> <li className='lc' tabIndex='0' onClick={_fill.bind(this, 'pa', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pa-F'] = smRef}>{translate('pa')}</li>
</ul> </ul>
<div className='select-group cap'>{translate('nl')}</div> <div className='select-group cap'>{translate('nl')}</div>
<ul> <ul>
<li className='lc' onClick={_fill.bind(this, 'nl', 'F')}>{translate('nl')}</li> <li className='lc' tabIndex='0' onClick={_fill.bind(this, 'nl', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['nl-F'] = smRef}>{translate('nl')}</li>
</ul> </ul>
</div>; </div>;
} }

View File

@@ -9,6 +9,8 @@ import { Cogs, CoriolisLogo, Hammer, Help, Rocket, StatsBars } from './SvgIcons'
import { Ships } from 'coriolis-data/dist'; import { Ships } from 'coriolis-data/dist';
import Persist from '../stores/Persist'; import Persist from '../stores/Persist';
import { toDetailedExport } from '../shipyard/Serializer'; import { toDetailedExport } from '../shipyard/Serializer';
import Ship from '../shipyard/Ship';
import ModalBatchOrbis from './ModalBatchOrbis';
import ModalDeleteAll from './ModalDeleteAll'; import ModalDeleteAll from './ModalDeleteAll';
import ModalExport from './ModalExport'; import ModalExport from './ModalExport';
import ModalHelp from './ModalHelp'; import ModalHelp from './ModalHelp';
@@ -235,6 +237,43 @@ export default class Header extends TranslatedComponent {
/>); />);
}; };
/**
* Uploads all ship-builds to orbis
* @param {e} e Event
*/
_uploadAllBuildsToOrbis(e) {
e.preventDefault();
const data = Persist.getBuilds();
let postObject = [];
for (const ship in data) {
for (const code in data[ship]) {
const shipModel = ship;
if (!shipModel) {
throw 'No such ship found: "' + ship + '"';
}
const shipTemplate = Ships[shipModel];
const shipPostObject = {};
let shipInstance = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots);
shipInstance.buildWith(null);
shipInstance.buildFrom(data[ship][code]);
shipPostObject.coriolisId = shipInstance.id;
shipPostObject.coriolisShip = shipInstance;
shipPostObject.coriolisShip.url = window.location.origin + outfitURL(shipModel, data[ship][code], code);
shipPostObject.title = code || shipInstance.id;
shipPostObject.description = code || shipInstance.id;
shipPostObject.ShipName = shipInstance.id;
shipPostObject.Ship = shipInstance.id;
postObject.push(shipPostObject);
}
}
console.log(postObject);
this.context.showModal(<ModalBatchOrbis
ships={postObject}
/>);
}
/** /**
* Show export modal with detailed export * Show export modal with detailed export
* @param {SyntheticEvent} e Event * @param {SyntheticEvent} e Event
@@ -430,6 +469,7 @@ export default class Header extends TranslatedComponent {
{translate('builds')} & {translate('comparisons')} {translate('builds')} & {translate('comparisons')}
<li><Link href="#" className='block' onClick={this._showBackup.bind(this)}>{translate('backup')}</Link></li> <li><Link href="#" className='block' onClick={this._showBackup.bind(this)}>{translate('backup')}</Link></li>
<li><Link href="#" className='block' onClick={this._showDetailedExport.bind(this)}>{translate('detailed export')}</Link></li> <li><Link href="#" className='block' onClick={this._showDetailedExport.bind(this)}>{translate('detailed export')}</Link></li>
<li><Link href="#" className='block' onClick={this._uploadAllBuildsToOrbis.bind(this)}>{translate('upload all builds to orbis')}</Link></li>
<li><Link href="#" className='block' onClick={this._showImport.bind(this)}>{translate('import')}</Link></li> <li><Link href="#" className='block' onClick={this._showImport.bind(this)}>{translate('import')}</Link></li>
<li><Link href="#" className='block' onClick={this._showDeleteAll.bind(this)}>{translate('delete all')}</Link></li> <li><Link href="#" className='block' onClick={this._showDeleteAll.bind(this)}>{translate('delete all')}</Link></li>
</ul> </ul>
@@ -505,8 +545,8 @@ export default class Header extends TranslatedComponent {
return ( return (
<header> <header>
{this.props.appCacheUpdate && <div id="app-update" onClick={() => window.location.reload() }>{translate('PHRASE_UPDATE_RDY')}</div>} {this.props.appCacheUpdate && <div id="app-update" onClick={() => window.location.reload() }>{translate('PHRASE_UPDATE_RDY')}</div>}
{this.props.appCache ? <a className={"view-changes"} href={"https://github.com/EDCD/coriolis/compare/edcd:develop@{" + window.CORIOLIS_DATE + "}...edcd:develop"} target="_blank"> {this.props.appCacheUpdate ? <a className={'view-changes'} href={'https://github.com/EDCD/coriolis/compare/edcd:develop@{' + window.CORIOLIS_DATE + '}...edcd:develop'} target="_blank">
{"View Release Changes"} {'View Release Changes'}
</a> : null} </a> : null}
<Link className='l' href='/' style={{ marginRight: '1em' }} title='Home'><CoriolisLogo className='icon xl' /></Link> <Link className='l' href='/' style={{ marginRight: '1em' }} title='Home'><CoriolisLogo className='icon xl' /></Link>
@@ -531,6 +571,16 @@ export default class Header extends TranslatedComponent {
{openedMenu == 'comp' ? this._getComparisonsMenu() : null} {openedMenu == 'comp' ? this._getComparisonsMenu() : null}
</div> </div>
{window.location.origin.search('.edcd.io') >= 0 ?
<div className='l menu'>
<a href="https://youtu.be/4SvnLcefhtI" target="_blank">
<div className={cn('menu-header')}>
<Rocket className='warning'/><span className='menu-item-label'>{translate('please migrate to coriolis.io')}</span>
</div>
</a>
</div> : null
}
<div className='r menu'> <div className='r menu'>
<div className={cn('menu-header', { selected: openedMenu == 'settings' })} onClick={this._openSettings}> <div className={cn('menu-header', { selected: openedMenu == 'settings' })} onClick={this._openSettings}>
<Cogs className='xl warning'/><span className='menu-item-label'>{translate('settings')}</span> <Cogs className='xl warning'/><span className='menu-item-label'>{translate('settings')}</span>

View File

@@ -21,8 +21,6 @@ export default class InternalSlot extends Slot {
* @param {Object} u Localized Units Map * @param {Object} u Localized Units Map
* @return {React.Component} Slot contents * @return {React.Component} Slot contents
*/ */
_getSlotDetails(m, enabled, translate, formats, u) { _getSlotDetails(m, enabled, translate, formats, u) {
if (m) { if (m) {
let classRating = m.class + m.rating; let classRating = m.class + m.rating;
@@ -65,6 +63,9 @@ export default class InternalSlot extends Slot {
{ m.getSpinup() ? <div className={'l'}>{translate('spinup')}: {formats.f1(m.getSpinup())}{u.s}</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.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.grp === 'scb' ? <div className={'l'}>{translate('cells')}: {formats.int(m.getAmmo() + 1)}</div> : null }
{ m.grp === 'gsrp' ? <div className={'l'}>{translate('shield addition')}: {formats.f1(m.getShieldAddition())}{u.MJ}</div> : null }
{ m.grp === 'gfsb' ? <div className={'l'}>{translate('jump addition')}: {formats.f1(m.getJumpBoost())}{u.LY}</div> : null }
{ m.grp === 'gs' ? <div className={'l'}>{translate('shield addition')}: {formats.f1(m.getShieldAddition())}{u.MJ}</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('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.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.repair ? <div className={'l'}>{translate('repair')}: {m.repair}</div> : null }
@@ -84,6 +85,7 @@ export default class InternalSlot extends Slot {
{ showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</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.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 } { showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
{ showModuleResistances && m.getCausticResistance() ? <div className='l'>{translate('causres')}: {formats.pct(m.getCausticResistance())}</div> : null }
{ m.getHullReinforcement() ? <div className='l'>{translate('armour')}: {formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost') / 10000)}</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.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.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }

View File

@@ -18,7 +18,6 @@ export default class InternalSlotSection extends SlotSection {
*/ */
constructor(props, context) { constructor(props, context) {
super(props, context, 'internal', 'optional internal'); super(props, context, 'internal', 'optional internal');
this._empty = this._empty.bind(this); this._empty = this._empty.bind(this);
this._fillWithCargo = this._fillWithCargo.bind(this); this._fillWithCargo = this._fillWithCargo.bind(this);
this._fillWithCells = this._fillWithCells.bind(this); this._fillWithCells = this._fillWithCells.bind(this);
@@ -29,12 +28,24 @@ export default class InternalSlotSection extends SlotSection {
this._fillWithFirstClassCabins = this._fillWithFirstClassCabins.bind(this); this._fillWithFirstClassCabins = this._fillWithFirstClassCabins.bind(this);
this._fillWithBusinessClassCabins = this._fillWithBusinessClassCabins.bind(this); this._fillWithBusinessClassCabins = this._fillWithBusinessClassCabins.bind(this);
this._fillWithEconomyClassCabins = this._fillWithEconomyClassCabins.bind(this); this._fillWithEconomyClassCabins = this._fillWithEconomyClassCabins.bind(this);
this.selectedRefId = null;
this.firstRefId = 'emptyall';
this.lastRefId = this.sectionRefArr['pcq'] ? 'pcq' : 'pcm';
}
/**
* Handle focus when component updates
* @param {Object} prevProps React Component properties
*/
componentDidUpdate(prevProps) {
this._handleSectionFocus(prevProps,this.firstRefId, this.lastRefId);
} }
/** /**
* Empty all slots * Empty all slots
*/ */
_empty() { _empty() {
this.selectedRefId = 'emptyall';
this.props.ship.emptyInternal(); this.props.ship.emptyInternal();
this.props.onChange(); this.props.onChange();
this._close(); this._close();
@@ -45,6 +56,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_fillWithCargo(event) { _fillWithCargo(event) {
this.selectedRefId = 'cargo';
let clobber = event.getModifierState('Alt'); let clobber = event.getModifierState('Alt');
let ship = this.props.ship; let ship = this.props.ship;
ship.internal.forEach((slot) => { ship.internal.forEach((slot) => {
@@ -61,6 +73,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_fillWithFuelTanks(event) { _fillWithFuelTanks(event) {
this.selectedRefId = 'ft';
let clobber = event.getModifierState('Alt'); let clobber = event.getModifierState('Alt');
let ship = this.props.ship; let ship = this.props.ship;
ship.internal.forEach((slot) => { ship.internal.forEach((slot) => {
@@ -77,6 +90,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_fillWithLuxuryCabins(event) { _fillWithLuxuryCabins(event) {
this.selectedRefId = 'pcq';
let clobber = event.getModifierState('Alt'); let clobber = event.getModifierState('Alt');
let ship = this.props.ship; let ship = this.props.ship;
ship.internal.forEach((slot) => { ship.internal.forEach((slot) => {
@@ -93,6 +107,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_fillWithFirstClassCabins(event) { _fillWithFirstClassCabins(event) {
this.selectedRefId = 'pcm';
let clobber = event.getModifierState('Alt'); let clobber = event.getModifierState('Alt');
let ship = this.props.ship; let ship = this.props.ship;
ship.internal.forEach((slot) => { ship.internal.forEach((slot) => {
@@ -109,6 +124,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_fillWithBusinessClassCabins(event) { _fillWithBusinessClassCabins(event) {
this.selectedRefId = 'pci';
let clobber = event.getModifierState('Alt'); let clobber = event.getModifierState('Alt');
let ship = this.props.ship; let ship = this.props.ship;
ship.internal.forEach((slot) => { ship.internal.forEach((slot) => {
@@ -125,6 +141,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_fillWithEconomyClassCabins(event) { _fillWithEconomyClassCabins(event) {
this.selectedRefId = 'pce';
let clobber = event.getModifierState('Alt'); let clobber = event.getModifierState('Alt');
let ship = this.props.ship; let ship = this.props.ship;
ship.internal.forEach((slot) => { ship.internal.forEach((slot) => {
@@ -141,6 +158,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_fillWithCells(event) { _fillWithCells(event) {
this.selectedRefId = 'scb';
let clobber = event.getModifierState('Alt'); let clobber = event.getModifierState('Alt');
let ship = this.props.ship; let ship = this.props.ship;
let chargeCap = 0; // Capacity of single activation let chargeCap = 0; // Capacity of single activation
@@ -160,6 +178,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_fillWithArmor(event) { _fillWithArmor(event) {
this.selectedRefId = 'hr';
let clobber = event.getModifierState('Alt'); let clobber = event.getModifierState('Alt');
let ship = this.props.ship; let ship = this.props.ship;
ship.internal.forEach((slot) => { ship.internal.forEach((slot) => {
@@ -176,6 +195,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_fillWithModuleReinforcementPackages(event) { _fillWithModuleReinforcementPackages(event) {
this.selectedRefId = 'mrp';
let clobber = event.getModifierState('Alt'); let clobber = event.getModifierState('Alt');
let ship = this.props.ship; let ship = this.props.ship;
ship.internal.forEach((slot) => { ship.internal.forEach((slot) => {
@@ -240,16 +260,16 @@ export default class InternalSlotSection extends SlotSection {
_getSectionMenu(translate, ship) { _getSectionMenu(translate, ship) {
return <div className='select' onClick={e => e.stopPropagation()} onContextMenu={stopCtxPropagation}> return <div className='select' onClick={e => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul> <ul>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li> <li className='lc' tabIndex='0' onClick={this._empty} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['emptyall'] = smRef}>{translate('empty all')}</li>
<li className='lc' onClick={this._fillWithCargo}>{translate('cargo')}</li> <li className='lc' tabIndex='0' onClick={this._fillWithCargo} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['cargo'] = smRef}>{translate('cargo')}</li>
<li className='lc' onClick={this._fillWithCells}>{translate('scb')}</li> <li className='lc' tabIndex='0' onClick={this._fillWithCells} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['scb'] = smRef}>{translate('scb')}</li>
<li className='lc' onClick={this._fillWithArmor}>{translate('hr')}</li> <li className='lc' tabIndex='0' onClick={this._fillWithArmor} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['hr'] = smRef}>{translate('hr')}</li>
<li className='lc' onClick={this._fillWithModuleReinforcementPackages}>{translate('mrp')}</li> <li className='lc' tabIndex='0' onClick={this._fillWithModuleReinforcementPackages} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['mrp'] = smRef}>{translate('mrp')}</li>
<li className='lc' onClick={this._fillWithFuelTanks}>{translate('ft')}</li> <li className='lc' tabIndex='0' onClick={this._fillWithFuelTanks} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ft'] = smRef}>{translate('ft')}</li>
<li className='lc' onClick={this._fillWithEconomyClassCabins}>{translate('pce')}</li> <li className='lc' tabIndex='0' onClick={this._fillWithEconomyClassCabins} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pce'] = smRef}>{translate('pce')}</li>
<li className='lc' onClick={this._fillWithBusinessClassCabins}>{translate('pci')}</li> <li className='lc' tabIndex='0' onClick={this._fillWithBusinessClassCabins} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pci'] = smRef}>{translate('pci')}</li>
<li className='lc' onClick={this._fillWithFirstClassCabins}>{translate('pcm')}</li> <li className='lc' tabIndex='0' onClick={this._fillWithFirstClassCabins} onKeyDown={ship.luxuryCabins ? '' : this._keyDown} ref={smRef => this.sectionRefArr['pcm'] = smRef}>{translate('pcm')}</li>
{ ship.luxuryCabins ? <li className='lc' onClick={this._fillWithLuxuryCabins}>{translate('pcq')}</li> : ''} { ship.luxuryCabins ? <li className='lc' tabIndex='0' onClick={this._fillWithLuxuryCabins} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pcq'] = smRef}>{translate('pcq')}</li> : ''}
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li> <li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
</ul> </ul>
</div>; </div>;

View File

@@ -61,7 +61,7 @@ export default class JumpRange extends TranslatedComponent {
const fuel = this.state.fuelLevel * ship.fuelCapacity; const fuel = this.state.fuelLevel * ship.fuelCapacity;
// Obtain the jump range // Obtain the jump range
return Calc.jumpRange(ship.unladenMass + fuel + cargo, fsd, fuel); return Calc.jumpRange(ship.unladenMass + fuel + cargo, fsd, fuel, ship);
} }
/** /**

View File

@@ -1,283 +1,281 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import Measure from 'react-measure'; import ContainerDimensions from 'react-container-dimensions';
import * as d3 from 'd3'; import * as d3 from 'd3';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 }; const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 };
/** /**
* Line Chart * Line Chart
*/ */
export default class LineChart extends TranslatedComponent { export default class LineChart extends TranslatedComponent {
static defaultProps = { static defaultProps = {
code: '', code: '',
xMin: 0, xMin: 0,
yMin: 0, yMin: 0,
points: 20, points: 20,
colors: ['#ff8c0d'], colors: ['#ff8c0d'],
aspect: 0.5 aspect: 0.5
}; };
static propTypes = { static propTypes = {
func: PropTypes.func.isRequired, func: PropTypes.func.isRequired,
xLabel: PropTypes.string.isRequired, xLabel: PropTypes.string.isRequired,
xMin: PropTypes.number, xMin: PropTypes.number,
xMax: PropTypes.number.isRequired, xMax: PropTypes.number.isRequired,
xUnit: PropTypes.string.isRequired, xUnit: PropTypes.string.isRequired,
xMark: PropTypes.number, xMark: PropTypes.number,
yLabel: PropTypes.string.isRequired, yLabel: PropTypes.string.isRequired,
yMin: PropTypes.number, yMin: PropTypes.number,
yMax: PropTypes.number.isRequired, yMax: PropTypes.number.isRequired,
yUnit: PropTypes.string, yUnit: PropTypes.string,
series: PropTypes.array, series: PropTypes.array,
colors: PropTypes.array, colors: PropTypes.array,
points: PropTypes.number, points: PropTypes.number,
aspect: PropTypes.number, aspect: PropTypes.number,
code: PropTypes.string, code: PropTypes.string,
}; };
/** /**
* Constructor * Constructor
* @param {Object} props React Component properties * @param {Object} props React Component properties
* @param {Object} context React Component context * @param {Object} context React Component context
*/ */
constructor(props, context) { constructor(props, context) {
super(props); super(props);
this._updateDimensions = this._updateDimensions.bind(this); this._updateDimensions = this._updateDimensions.bind(this);
this._updateSeries = this._updateSeries.bind(this); this._updateSeries = this._updateSeries.bind(this);
this._tooltip = this._tooltip.bind(this); this._tooltip = this._tooltip.bind(this);
this._showTip = this._showTip.bind(this); this._showTip = this._showTip.bind(this);
this._hideTip = this._hideTip.bind(this); this._hideTip = this._hideTip.bind(this);
this._moveTip = this._moveTip.bind(this); this._moveTip = this._moveTip.bind(this);
const series = props.series; const series = props.series;
let xScale = d3.scaleLinear(); let xScale = d3.scaleLinear();
let yScale = d3.scaleLinear(); let yScale = d3.scaleLinear();
let xAxisScale = d3.scaleLinear(); let xAxisScale = d3.scaleLinear();
this.xAxis = d3.axisBottom(xAxisScale).tickSizeOuter(0); this.xAxis = d3.axisBottom(xAxisScale).tickSizeOuter(0);
this.yAxis = d3.axisLeft(yScale).ticks(6).tickSizeOuter(0); this.yAxis = d3.axisLeft(yScale).ticks(6).tickSizeOuter(0);
this.state = { this.state = {
xScale, xScale,
xAxisScale, xAxisScale,
yScale, yScale,
tipHeight: 2 + (1.2 * (series ? series.length : 0.8)), tipHeight: 2 + (1.2 * (series ? series.length : 0.8)),
dimensions: { };
width: 100, }
height: 100
} /**
}; * Update tooltip content
} * @param {number} xPos x coordinate
* @param {number} width current container width
/** */
* Update tooltip content _tooltip(xPos, width) {
* @param {number} xPos x coordinate let { xLabel, yLabel, xUnit, yUnit, func, series } = this.props;
*/ let { xScale, yScale } = this.state;
_tooltip(xPos) { let { formats, translate } = this.context.language;
let { xLabel, yLabel, xUnit, yUnit, func, series } = this.props; let x0 = xScale.invert(xPos),
let { xScale, yScale } = this.state; y0 = func(x0),
let { width } = this.state.dimensions; tips = this.tipContainer,
let { formats, translate } = this.context.language; yTotal = 0,
let x0 = xScale.invert(xPos), flip = (xPos / width > 0.50),
y0 = func(x0), tipWidth = 0,
tips = this.tipContainer, tipHeightPx = tips.selectAll('rect').node().getBoundingClientRect().height;
yTotal = 0,
flip = (xPos / width > 0.50),
tipWidth = 0, xPos = xScale(x0); // Clamp xPos
tipHeightPx = tips.selectAll('rect').node().getBoundingClientRect().height;
tips.selectAll('text.text-tip.y').text(function(d, i) {
let yVal = series ? y0[series[i]] : y0;
xPos = xScale(x0); // Clamp xPos yTotal += yVal;
return (series ? translate(series[i]) : '') + ' ' + formats.f2(yVal);
tips.selectAll('text.text-tip.y').text(function(d, i) { }).append('tspan').attr('class', 'metric').text(yUnit ? ' ' + yUnit : '');
let yVal = series ? y0[series[i]] : y0;
yTotal += yVal; tips.selectAll('text').each(function() {
return (series ? translate(series[i]) : '') + ' ' + formats.f2(yVal); if (this.getBBox().width > tipWidth) {
}).append('tspan').attr('class', 'metric').text(yUnit ? ' ' + yUnit : ''); tipWidth = Math.ceil(this.getBBox().width);
}
tips.selectAll('text').each(function() { });
if (this.getBBox().width > tipWidth) {
tipWidth = Math.ceil(this.getBBox().width); let tipY = Math.floor(yScale(yTotal / (series ? series.length : 1)) - (tipHeightPx / 2));
}
}); tipWidth += 8;
tips.attr('transform', 'translate(' + xPos + ',' + tipY + ')');
let tipY = Math.floor(yScale(yTotal / (series ? series.length : 1)) - (tipHeightPx / 2)); tips.selectAll('text.text-tip').attr('x', flip ? -12 : 12).style('text-anchor', flip ? 'end' : 'start');
tips.selectAll('text.text-tip.x').text(formats.f2(x0)).append('tspan').attr('class', 'metric').text(' ' + xUnit);
tipWidth += 8; tips.selectAll('rect').attr('width', tipWidth + 4).attr('x', flip ? -tipWidth - 12 : 8).attr('y', 0).style('text-anchor', flip ? 'end' : 'start');
tips.attr('transform', 'translate(' + xPos + ',' + tipY + ')'); this.markersContainer.selectAll('circle').attr('cx', xPos).attr('cy', (d, i) => yScale(series ? y0[series[i]] : y0));
tips.selectAll('text.text-tip').attr('x', flip ? -12 : 12).style('text-anchor', flip ? 'end' : 'start'); }
tips.selectAll('text.text-tip.x').text(formats.f2(x0)).append('tspan').attr('class', 'metric').text(' ' + xUnit);
tips.selectAll('rect').attr('width', tipWidth + 4).attr('x', flip ? -tipWidth - 12 : 8).attr('y', 0).style('text-anchor', flip ? 'end' : 'start'); /**
this.markersContainer.selectAll('circle').attr('cx', xPos).attr('cy', (d, i) => yScale(series ? y0[series[i]] : y0)); * Update dimensions based on properties and scale
} * @param {Object} props React Component properties
* @param {number} scale size ratio / scale
/** * @param {number} width current width of the container
* Update dimensions based on properties and scale * @returns {Object} calculated dimensions
* @param {Object} props React Component properties */
* @param {number} scale size ratio / scale _updateDimensions(props, scale, width) {
* @returns {Object} calculated dimensions const { xMax, xMin, yMin, yMax } = props;
*/ const innerWidth = width - MARGIN.left - MARGIN.right;
_updateDimensions(props, scale) { const outerHeight = Math.round(width * props.aspect);
const { xMax, xMin, yMin, yMax } = props; const innerHeight = outerHeight - MARGIN.top - MARGIN.bottom;
const { width, height } = this.state.dimensions;
const innerWidth = width - MARGIN.left - MARGIN.right; this.state.xScale.range([0, innerWidth]).domain([xMin, xMax || 1]).clamp(true);
const outerHeight = Math.round(width * props.aspect); this.state.xAxisScale.range([0, innerWidth]).domain([xMin, xMax]).clamp(true);
const innerHeight = outerHeight - MARGIN.top - MARGIN.bottom; 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 };
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 + (yMax - yMin) * 0.1]); // 10% higher than maximum value for tooltip visibility /**
return { innerWidth, outerHeight, innerHeight }; * Show tooltip
} * @param {SyntheticEvent} e Event
*/
/** _showTip(e) {
* Show tooltip e.preventDefault();
* @param {SyntheticEvent} e Event this.tipContainer.style('display', null);
*/ this.markersContainer.style('display', null);
_showTip(e) { this._moveTip(e);
e.preventDefault(); }
this.tipContainer.style('display', null);
this.markersContainer.style('display', null); /**
this._moveTip(e); * Move and update tooltip
} * @param {SyntheticEvent} e Event
* @param {number} width current container width
/** */
* Move and update tooltip _moveTip(e, width) {
* @param {SyntheticEvent} e Event let clientX = e.touches ? e.touches[0].clientX : e.clientX;
*/ this._tooltip(Math.round(clientX - e.currentTarget.getBoundingClientRect().left), width);
_moveTip(e) { }
let clientX = e.touches ? e.touches[0].clientX : e.clientX;
this._tooltip(Math.round(clientX - e.currentTarget.getBoundingClientRect().left)); /**
} * Hide tooltip
* @param {SyntheticEvent} e Event
/** */
* Hide tooltip _hideTip(e) {
* @param {SyntheticEvent} e Event e.preventDefault();
*/ this.tipContainer.style('display', 'none');
_hideTip(e) { this.markersContainer.style('display', 'none');
e.preventDefault(); }
this.tipContainer.style('display', 'none');
this.markersContainer.style('display', 'none'); /**
} * Update series generated from props
* @param {Object} props React Component properties
/** * @param {Object} state React Component state
* Update series generated from props */
* @param {Object} props React Component properties _updateSeries(props, state) {
* @param {Object} state React Component state let { func, xMin, xMax, series, points } = props;
*/ let delta = (xMax - xMin) / points;
_updateSeries(props, state) { let seriesData = new Array(points);
let { func, xMin, xMax, series, points } = props;
let delta = (xMax - xMin) / points; if (delta) {
let seriesData = new Array(points); seriesData = new Array(points);
for (let i = 0, x = xMin; i < points; i++) {
if (delta) { seriesData[i] = [x, func(x)];
seriesData = new Array(points); x += delta;
for (let i = 0, x = xMin; i < points; i++) { }
seriesData[i] = [x, func(x)]; seriesData[points - 1] = [xMax, func(xMax)];
x += delta; } else {
} let yVal = func(xMin);
seriesData[points - 1] = [xMax, func(xMax)]; seriesData = [[0, yVal], [1, yVal]];
} else { }
let yVal = func(xMin);
seriesData = [[0, yVal], [1, yVal]]; const markerElems = [];
} const detailElems = [<text key='lbl' className='text-tip x' y='1.25em'/>];
const seriesLines = [];
const markerElems = []; for (let i = 0, l = series ? series.length : 1; i < l; i++) {
const detailElems = [<text key='lbl' className='text-tip x' y='1.25em'/>]; const yAccessor = series ? function(d) { return state.yScale(d[1][this]); }.bind(series[i]) : (d) => state.yScale(d[1]);
const seriesLines = []; seriesLines.push(d3.line().x((d, i) => this.state.xScale(d[0])).y(yAccessor));
for (let i = 0, l = series ? series.length : 1; i < l; i++) { detailElems.push(<text key={i} className='text-tip y' strokeWidth={0} fill={props.colors[i]} y={1.25 * (i + 2) + 'em'}/>);
const yAccessor = series ? function(d) { return state.yScale(d[1][this]); }.bind(series[i]) : (d) => state.yScale(d[1]); markerElems.push(<circle key={i} className='marker' r='4' />);
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 });
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() {
* Update dimensions and series data based on props and context. this._updateSeries(this.props, this.state);
*/ }
componentWillMount() {
this._updateSeries(this.props, this.state); /**
} * Update state based on property and context changes
* @param {Object} nextProps Incoming/Next properties
/** * @param {Object} nextContext Incoming/Next conext
* Update state based on property and context changes */
* @param {Object} nextProps Incoming/Next properties componentWillReceiveProps(nextProps, nextContext) {
* @param {Object} nextContext Incoming/Next conext const props = this.props;
*/
componentWillReceiveProps(nextProps, nextContext) { if (props.code != nextProps.code) {
const props = this.props; this._updateSeries(nextProps, this.state);
}
if (props.code != nextProps.code) { }
this._updateSeries(nextProps, this.state);
} /**
} * Render the chart
* @return {React.Component} Chart SVG
/** */
* Render the chart render() {
* @return {React.Component} Chart SVG return (
*/ <ContainerDimensions>
render() { { ({ width, height }) => {
const { innerWidth, outerHeight, innerHeight } = this._updateDimensions(this.props, this.context.sizeRatio); const { innerWidth, outerHeight, innerHeight } = this._updateDimensions(this.props, this.context.sizeRatio, width, height);
const { width, height } = this.state.dimensions; const { xMin, xMax, xLabel, yLabel, xUnit, yUnit, xMark, colors } = this.props;
const { xMin, xMax, xLabel, yLabel, xUnit, yUnit, xMark, colors } = this.props; const { tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state;
const { tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state; const lines = seriesLines.map((line, i) => <path key={i} className='line' fill='none' stroke={colors[i]} strokeWidth='1' d={line(seriesData)} />).reverse();
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(); 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'} /> : '';
const markX = xMark ? innerWidth * (xMark - xMin) / (xMax - xMin) : 0; return (
const xmark = xMark ? <path key={'mark'} className='line' fill='none' strokeDasharray='5,5' stroke={'#ff8c0d'} strokeWidth='1' d={'M ' + markX + ' ' + innerHeight + ' L ' + markX + ' 0'} /> : ''; <div width={width} height={height}>
<svg style={{ width: '100%', height: outerHeight }}>
return ( <g transform={`translate(${MARGIN.left},${MARGIN.top})`}>
<Measure width='100%' whitelist={['width', 'top']} onMeasure={ (dimensions) => { this.setState({ dimensions }); }}> <g>{xmark}</g>
<div width={width} height={height}> <g>{lines}</g>
<svg style={{ width: '100%', height: outerHeight }}> <g className='x axis' ref={(elem) => d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}>
<g transform={`translate(${MARGIN.left},${MARGIN.top})`}> <text className='cap' y='30' dy='.1em' x={innerWidth / 2} style={{ textAnchor: 'middle' }}>
<g>{xmark}</g> <tspan>{xLabel}</tspan>
<g>{lines}</g> <tspan className='metric'> ({xUnit})</tspan>
<g className='x axis' ref={(elem) => d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}> </text>
<text className='cap' y='30' dy='.1em' x={innerWidth / 2} style={{ textAnchor: 'middle' }}> </g>
<tspan>{xLabel}</tspan> <g className='y axis' ref={(elem) => d3.select(elem).call(this.yAxis)}>
<tspan className='metric'> ({xUnit})</tspan> <text className='cap' transform='rotate(-90)' y='-50' dy='.1em' x={innerHeight / -2} style={{ textAnchor: 'middle' }}>
</text> <tspan>{yLabel}</tspan>
</g> { yUnit && <tspan className='metric'> ({yUnit})</tspan> }
<g className='y axis' ref={(elem) => d3.select(elem).call(this.yAxis)}> </text>
<text className='cap' transform='rotate(-90)' y='-50' dy='.1em' x={innerHeight / -2} style={{ textAnchor: 'middle' }}> </g>
<tspan>{yLabel}</tspan> <g ref={(g) => this.tipContainer = d3.select(g)} style={{ display: 'none' }}>
{ yUnit && <tspan className='metric'> ({yUnit})</tspan> } <rect className='tooltip' height={tipHeight + 'em'}></rect>
</text> {detailElems}
</g> </g>
<g ref={(g) => this.tipContainer = d3.select(g)} style={{ display: 'none' }}> <g ref={(g) => this.markersContainer = d3.select(g)} style={{ display: 'none' }}>
<rect className='tooltip' height={tipHeight + 'em'}></rect> {markerElems}
{detailElems} </g>
</g> <rect
<g ref={(g) => this.markersContainer = d3.select(g)} style={{ display: 'none' }}> fillOpacity='0'
{markerElems} height={innerHeight}
</g> width={innerWidth + 1}
<rect onMouseEnter={this._showTip}
fillOpacity='0' onTouchStart={this._showTip}
height={innerHeight} onMouseLeave={this._hideTip}
width={innerWidth + 1} onTouchEnd={this._hideTip}
onMouseEnter={this._showTip} onMouseMove={e => this._moveTip(e, width)}
onTouchStart={this._showTip} onTouchMove={e => this._moveTip(e, width)}
onMouseLeave={this._hideTip} />
onTouchEnd={this._hideTip} </g>
onMouseMove={this._moveTip} </svg>
onTouchMove={this._moveTip} </div>
/> );
</g> }}
</svg> </ContainerDimensions>
</div> );
</Measure> }
); }
}
}

View File

@@ -0,0 +1,93 @@
import React from 'react';
import PropTypes from 'prop-types';
import request from 'superagent';
import TranslatedComponent from './TranslatedComponent';
import { orbisUpload } from '../utils/ShortenUrl';
import Persist from '../stores/Persist';
/**
* Permalink modal
*/
export default class ModalBatchOrbis extends TranslatedComponent {
static propTypes = {
ships: PropTypes.any.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this.state = {
orbisCreds: Persist.getOrbisCreds(),
resp: ''
};
}
/**
* Send ship to Orbis.zone
* @param {SyntheticEvent} e React Event
* @return {Promise} Promise sending post request to orbis
*/
sendToOrbis(e) {
let agent;
try {
agent = request.agent(); // apparently this crashes somehow
} catch (e) {
console.error(e);
}
if (!agent) {
agent = request;
}
const API_ORBIS = 'https://orbis.zone/api/builds/add/batch';
return new Promise((resolve, reject) => {
try {
agent
.post(API_ORBIS)
.withCredentials()
.redirects(0)
.set('Content-Type', 'application/json')
.send(this.props.ships)
.end((err, response) => {
console.log(response);
if (err) {
console.error(err);
this.setState({ resp: response.text });
reject('Bad Request');
} else {
this.setState({ resp: 'All builds uploaded. Check https://orbis.zone' });
resolve('All builds uploaded. Check https://orbis.zone');
}
});
} catch (e) {
console.log(e);
reject(e.message ? e.message : e);
}
});
}
/**
* Render the modal
* @return {React.Component} Modal Content
*/
render() {
let translate = this.context.language.translate;
this.sendToOrbis = this.sendToOrbis.bind(this);
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
<h2>{translate('permalink')}</h2>
<br/>
<a className='button' href="https://orbis.zone/api/auth">Log in / signup to Orbis</a>
<br/><br/>
<h3 >{translate('success')}</h3>
<input value={this.state.resp} readOnly size={25} onFocus={ (e) => e.target.select() }/>
<br/><br/>
<p>Orbis.zone is currently in a trial period, and may be wiped at any time as development progresses. Some elements are also still placeholders.</p>
<button className={'l cb dismiss cap'} disabled={!!this.state.failed} onClick={this.sendToOrbis}>{translate('PHASE_UPLOAD_ORBIS')}</button>
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
</div>;
}
}

View File

@@ -126,7 +126,11 @@ export default class ModalImport extends TranslatedComponent {
if (importData.builds && typeof importData.builds == 'object') { if (importData.builds && typeof importData.builds == 'object') {
for (let shipId in importData.builds) { for (let shipId in importData.builds) {
for (let buildName in importData.builds[shipId]) { for (let buildName in importData.builds[shipId]) {
validateBuild(shipId, importData.builds[shipId][buildName], buildName); try {
validateBuild(shipId, importData.builds[shipId][buildName], buildName);
} catch (err) {
delete importData.builds[shipId][buildName];
}
} }
} }
this.setState({ builds: importData.builds }); this.setState({ builds: importData.builds });

View File

@@ -0,0 +1,117 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import { orbisUpload } from '../utils/ShortenUrl';
import Persist from '../stores/Persist';
/**
* Permalink modal
*/
export default class ModalOrbis extends TranslatedComponent {
static propTypes = {
ship: PropTypes.any.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this.state = {
orbisCreds: Persist.getOrbisCreds(),
orbisUrl: '...',
authenticatedStatus: 'Checking...'
};
}
/**
* Send ship to Orbis.zone
* @param {SyntheticEvent} e React Event
*/
sendToOrbis(e) {
const target = e.target;
target.disabled = true;
this.setState({ orbisUrl: 'Sending...' }, () => {
orbisUpload(this.props.ship, this.state.orbisCreds)
.then(orbisUrl => {
target.disabled = false;
this.setState({ orbisUrl });
})
.catch(err => {
target.disabled = false;
this.setState({ orbisUrl: 'Error - ' + err });
});
});
}
/**
* Get Orbis.zone auth status
* @returns {Object} auth status
*/
getOrbisAuthStatus() {
return fetch('https://orbis.zone/api/checkauth', {
credentials: 'include',
mode: 'cors'
})
.then(data => data.json())
.then(res => {
this.setState({ authenticatedStatus: res.status || res.error });
})
.catch(err => {
console.error(err);
this.setState({ authenticatedStatus: err.message });
});
}
/**
* Handler for changing cmdr name
* @param {SyntheticEvent} e React Event
*/
orbisPasswordHandler(e) {
let password = e.target.value;
this.setState({ orbisCreds: { email: this.state.orbisCreds.email, password } }, () => {
Persist.setOrbisCreds(this.state.orbisCreds);
});
}
/**
* Handler for changing cmdr name
* @param {SyntheticEvent} e React Event
*/
orbisUsername(e) {
let orbisUsername = e.target.value;
this.setState({ orbisCreds: { email: orbisUsername, password: this.state.orbisCreds.password } }, () => {
Persist.setOrbisCreds(this.state.orbisCreds);
});
}
/**
* Render the modal
* @return {React.Component} Modal Content
*/
render() {
let translate = this.context.language.translate;
this.orbisPasswordHandler = this.orbisPasswordHandler.bind(this);
this.orbisUsername = this.orbisUsername.bind(this);
this.sendToOrbis = this.sendToOrbis.bind(this);
this.getOrbisAuthStatus();
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
<h2>{translate('upload to orbis')}</h2>
<br/>
<label>Orbis auth status: </label>
<input value={this.state.authenticatedStatus} readOnly size={25} onFocus={ (e) => e.target.select() }/>
<br/><br/>
<a className='button' href="https://orbis.zone/api/auth">Log in / signup to Orbis</a>
<br/><br/>
<h3 >{translate('Orbis link')}</h3>
<input value={this.state.orbisUrl} readOnly size={25} onFocus={ (e) => e.target.select() }/>
<br/><br/>
<p>Orbis.zone is currently in a trial period, and may be wiped at any time as development progresses. Some elements are also still placeholders.</p>
<button className={'l cb dismiss cap'} disabled={!!this.state.failed} onClick={this.sendToOrbis}>{translate('PHASE_UPLOAD_ORBIS')}</button>
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
</div>;
}
}

View File

@@ -50,6 +50,7 @@ export default class ModalPermalink extends TranslatedComponent {
<h3 >{translate('shortened')}</h3> <h3 >{translate('shortened')}</h3>
<input value={this.state.shortenedUrl} readOnly size={25} onFocus={ (e) => e.target.select() }/> <input value={this.state.shortenedUrl} readOnly size={25} onFocus={ (e) => e.target.select() }/>
<br/><br/> <br/><br/>
<p>s.orbis.zone is the new URL shortener domain, old eddp.co urls are considered end of life and could go down at any moment. Sorry for any inconvenience.</p>
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button> <button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
</div>; </div>;
} }

View File

@@ -0,0 +1,262 @@
import React from 'react';
import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent';
import request from 'superagent';
import Persist from '../stores/Persist';
/**
* Permalink modal
*/
export default class ModalShoppingList extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this.state = {
matsList: '',
mats: {},
failed: false,
cmdrName: Persist.getCmdr().selected,
cmdrs: Persist.getCmdr().cmdrs,
matsPerGrade: Persist.getRolls(),
blueprints: []
};
}
/**
* React component did mount
*/
componentDidMount() {
this.renderMats();
if (this.checkBrowserIsCompatible()) {
this.getCommanders();
this.registerBPs();
}
}
/**
* Find all blueprints needed to make a build.
*/
registerBPs() {
const ship = this.props.ship;
let blueprints = [];
for (const module of ship.costList) {
if (module.type === 'SHIP') {
continue;
}
if (module.m && module.m.blueprint) {
if (!module.m.blueprint.grade || !module.m.blueprint.grades) {
continue;
}
if (module.m.blueprint.special) {
console.log(module.m.blueprint.special);
blueprints.push({ uuid: module.m.blueprint.special.uuid, number: 1 });
}
for (const g in module.m.blueprint.grades) {
if (!module.m.blueprint.grades.hasOwnProperty(g)) {
continue;
}
if (g > module.m.blueprint.grade) {
continue;
}
blueprints.push({ uuid: module.m.blueprint.grades[g].uuid, number: this.state.matsPerGrade[g] });
}
}
}
this.setState({ blueprints });
}
/**
* Check browser isn't firefox.
* @return {boolean} true if compatible, false if not.
*/
checkBrowserIsCompatible() {
// Firefox 1.0+
return typeof InstallTrigger === 'undefined';
}
/**
* Get a list of commanders from EDEngineer.
*/
getCommanders() {
request
.get('http://localhost:44405/commanders')
.end((err, res) => {
if (err) {
console.log(err);
return this.setState({ failed: true });
}
const cmdrs = JSON.parse(res.text);
if (!this.state.cmdrName) {
this.setState({ cmdrName: cmdrs[0] });
}
this.setState({ cmdrs }, () => {
Persist.setCmdr({ selected: this.state.cmdrName, cmdrs });
});
});
}
/**
* Send all blueprints to ED Engineer
* @param {Event} event React event
*/
sendToEDEng(event) {
event.preventDefault();
const target = event.target;
target.disabled = this.state.blueprints.length > 0;
if (this.state.blueprints.length === 0) {
target.innerText = 'No modded components.';
target.disabled = true;
setTimeout(() => {
target.innerText = 'Send to EDEngineer';
target.disabled = false;
}, 3000);
} else {
target.innerText = 'Sending...';
}
let countSent = 0;
let countTotal = this.state.blueprints.length;
for (const i of this.state.blueprints) {
request
.patch(`http://localhost:44405/${this.state.cmdrName}/shopping-list`)
.field('uuid', i.uuid)
.field('size', i.number)
.end(err => {
if (err) {
console.log(err);
if (err.message !== 'Bad Request') {
this.setState({ failed: true });
}
}
countSent++;
if (countSent === countTotal) {
target.disabled = false;
target.innerText = 'Send to EDEngineer';
}
});
}
}
/**
* Convert mats object to string
*/
renderMats() {
const ship = this.props.ship;
let mats = {};
for (const module of ship.costList) {
if (module.type === 'SHIP') {
continue;
}
if (module.m && module.m.blueprint) {
if (!module.m.blueprint.grade || !module.m.blueprint.grades) {
continue;
}
for (const g in module.m.blueprint.grades) {
if (!module.m.blueprint.grades.hasOwnProperty(g)) {
continue;
}
if (g > module.m.blueprint.grade) {
continue;
}
for (const i in module.m.blueprint.grades[g].components) {
if (!module.m.blueprint.grades[g].components.hasOwnProperty(i)) {
continue;
}
if (mats[i]) {
mats[i] += module.m.blueprint.grades[g].components[i] * this.state.matsPerGrade[g];
} else {
mats[i] = module.m.blueprint.grades[g].components[i] * this.state.matsPerGrade[g];
}
}
}
}
}
let matsString = '';
for (const i in mats) {
if (!mats.hasOwnProperty(i)) {
continue;
}
if (mats[i] === 0) {
delete mats[i];
continue;
}
matsString += `${i}: ${mats[i]}\n`;
}
this.setState({ matsList: matsString, mats });
}
/**
* Handler for changing roll amounts
* @param {SyntheticEvent} e React Event
*/
changeHandler(e) {
let grade = e.target.id;
let newState = this.state.matsPerGrade;
newState[grade] = parseInt(e.target.value);
this.setState({ matsPerGrade: newState });
Persist.setRolls(newState);
this.renderMats();
this.registerBPs();
}
/**
* Handler for changing cmdr name
* @param {SyntheticEvent} e React Event
*/
cmdrChangeHandler(e) {
let cmdrName = e.target.value;
this.setState({ cmdrName }, () => {
Persist.setCmdr({ selected: this.state.cmdrName, cmdrs: this.state.cmdrs });
});
}
/**
* Render the modal
* @return {React.Component} Modal Content
*/
render() {
let translate = this.context.language.translate;
this.changeHandler = this.changeHandler.bind(this);
const compatible = this.checkBrowserIsCompatible();
this.cmdrChangeHandler = this.cmdrChangeHandler.bind(this);
this.sendToEDEng = this.sendToEDEng.bind(this);
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
<h2>{translate('PHRASE_SHOPPING_MATS')}</h2>
<label>Grade 1 rolls </label>
<input id={1} type={'number'} min={0} defaultValue={this.state.matsPerGrade[1]} onChange={this.changeHandler} />
<br/>
<label>Grade 2 rolls </label>
<input id={2} type={'number'} min={0} defaultValue={this.state.matsPerGrade[2]} onChange={this.changeHandler} />
<br/>
<label>Grade 3 rolls </label>
<input id={3} type={'number'} min={0} value={this.state.matsPerGrade[3]} onChange={this.changeHandler} />
<br/>
<label>Grade 4 rolls </label>
<input id={4} type={'number'} min={0} value={this.state.matsPerGrade[4]} onChange={this.changeHandler} />
<br/>
<label>Grade 5 rolls </label>
<input id={5} type={'number'} min={0} value={this.state.matsPerGrade[5]} onChange={this.changeHandler} />
<div>
<textarea className='cb json' readOnly value={this.state.matsList} />
</div>
<label hidden={!compatible} className={'l cap'}>CMDR Name </label>
<br/>
<select hidden={!compatible} className={'cmdr-select l cap'} onChange={this.cmdrChangeHandler} defaultValue={this.state.cmdrName}>
{this.state.cmdrs.map(e => <option key={e}>{e}</option>)}
</select>
<br/>
<p hidden={!this.state.failed} id={'failed'} className={'l'}>Failed to send to EDEngineer (Launch EDEngineer and make sure the API is started then refresh the page.)</p>
<p hidden={compatible} id={'browserbad'} className={'l'}>Sending to EDEngineer is not compatible with Firefox's security settings. Please try again with Chrome.</p>
<button className={'l cb dismiss cap'} disabled={!!this.state.failed || !compatible} onClick={this.sendToEDEng}>{translate('Send To EDEngineer')}</button>
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
</div>;
}
}

View File

@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
import TranslatedComponent from './TranslatedComponent'; import TranslatedComponent from './TranslatedComponent';
import cn from 'classnames'; import cn from 'classnames';
import NumberEditor from 'react-number-editor'; import NumberEditor from 'react-number-editor';
import { isValueBeneficial } from '../utils/BlueprintFunctions';
/** /**
* Modification * Modification
@@ -38,30 +39,17 @@ export default class Modification extends TranslatedComponent {
* in a value by hand * in a value by hand
*/ */
_updateValue(value) { _updateValue(value) {
const name = this.props.name; let { m, name, ship } = this.props;
let scaledValue = Math.round(Number(value) * 100); value = Math.max(Math.min(value, 50000), -50000);
// Limit to +1000% / -99.99% ship.setModification(m, name, value, true, true);
if (scaledValue > 100000) {
scaledValue = 100000;
value = 1000;
}
if (scaledValue < -9999) {
scaledValue = -9999;
value = -99.99;
}
let m = this.props.m;
let ship = this.props.ship;
ship.setModification(m, name, scaledValue, true);
this.setState({ value }); this.setState({ value });
} }
/** /**
* Triggered when an update to slider value is finished i.e. when losing focus * Triggered when an update to slider value is finished i.e. when losing focus
* *
* pnellesen (24/05/2018): added value check below - this should prevent experimental effects from being recalculated * pnellesen (24/05/2018): added value check below - this should prevent experimental effects from being recalculated
* with each onBlur event, even when no change has actually been made to the field. * with each onBlur event, even when no change has actually been made to the field.
*/ */
_updateFinished() { _updateFinished() {
if (this.props.value != this.state.value) { if (this.props.value != this.state.value) {
@@ -75,28 +63,50 @@ export default class Modification extends TranslatedComponent {
* @return {React.Component} modification * @return {React.Component} modification
*/ */
render() { render() {
let translate = this.context.language.translate; let { translate, formats, units } = this.context.language;
let { m, name } = this.props; let { m, name } = this.props;
let modValue = m.getChange(name);
if (name === 'damagedist') { if (name === 'damagedist') {
// We don't show damage distribution // We don't show damage distribution
return null; return null;
} }
let symbol;
if (name === 'jitter') {
symbol = '°';
} else if (name !== 'burst' && name != 'burstrof') {
symbol = '%';
}
if (symbol) {
symbol = ' (' + symbol + ')';
}
return ( return (
<div onBlur={this._updateFinished.bind(this)} className={'cb'} key={name} ref={ modItem => this.props.modItems[name] = modItem }> <div onBlur={this._updateFinished.bind(this)} key={name}
<div className={'cb'}>{translate(name, m.grp)}{symbol}</div> className={cn('cb', 'modification-container')}
<NumberEditor className={'cb'} style={{ width: '90%', textAlign: 'center' }} step={0.01} stepModifier={1} decimals={2} value={this.state.value} onValueChange={this._updateValue.bind(this)} onKeyDown={ this.props.onKeyDown } /> ref={ modItem => this.props.modItems[name] = modItem }>
<span className={'cb'}>{translate(name, m.grp)}</span>
<span className={'header-adjuster'}></span>
<table style={{ width: '100%' }}>
<tbody>
<tr>
<td className={'input-container'}>
<span>
{this.props.editable ?
<NumberEditor className={'cb'} value={this.state.value}
decimals={2} style={{ textAlign: 'right' }} step={0.01}
stepModifier={1} onKeyDown={ this.props.onKeyDown }
onValueChange={this._updateValue.bind(this)} /> :
<input type="text" value={formats.f2(this.state.value)}
disabled className={'number-editor'}
style={{ textAlign: 'right', cursor: 'inherit' }}/>
}
<span className={'unit-container'}>
{units[m.getStoredUnitFor(name)]}
</span>
</span>
</td>
<td style={{ textAlign: 'center' }} className={
modValue ?
isValueBeneficial(name, modValue) ? 'secondary': 'warning':
''
}>
{formats.f2(modValue / 100) || 0}%
</td>
</tr>
</tbody>
</table>
</div> </div>
); );
} }

View File

@@ -13,7 +13,11 @@ import {
getPercent, getPercent,
setRandom, setRandom,
specialToolTip specialToolTip
} from '../utils/BlueprintFunctions' } from '../utils/BlueprintFunctions';
const MODIFICATIONS_COMPARATOR = (mod1, mod2) => {
return mod1.props.name.localeCompare(mod2.props.name);
};
/** /**
* Modifications menu * Modifications menu
@@ -43,15 +47,15 @@ export default class ModificationsMenu extends TranslatedComponent {
this._rollBest = this._rollBest.bind(this); this._rollBest = this._rollBest.bind(this);
this._rollWorst = this._rollWorst.bind(this); this._rollWorst = this._rollWorst.bind(this);
this._reset = this._reset.bind(this); this._reset = this._reset.bind(this);
this._keyDown = this._keyDown.bind(this); this._keyDown = this._keyDown.bind(this);
this.modItems = [];// Array to hold various element refs (<li>, <div>, <ul>, etc.) this.modItems = [];// Array to hold various element refs (<li>, <div>, <ul>, etc.)
this.firstModId = null; this.firstModId = null;
this.firstBPLabel = null;// First item in mod menu this.firstBPLabel = null;// First item in mod menu
this.lastModId = null; this.lastModId = null;
this.lastNeId = null;//Last number editor id. Used to set focus to last number editor when shift-tab pressed on first element in mod menu. this.selectedModId = null;
this.modValDidChange = false; //used to determine if component update was caused by change in modification value. this.selectedSpecialId = null;
this.lastNeId = null;// Last number editor id. Used to set focus to last number editor when shift-tab pressed on first element in mod menu.
this.modValDidChange = false; // used to determine if component update was caused by change in modification value.
this._handleModChange = this._handleModChange.bind(this); this._handleModChange = this._handleModChange.bind(this);
this.state = { this.state = {
@@ -78,19 +82,18 @@ export default class ModificationsMenu extends TranslatedComponent {
// Grade is a string in the JSON so make it a number // Grade is a string in the JSON so make it a number
grade = Number(grade); grade = Number(grade);
const classes = cn('c', { const classes = cn('c', {
active: m.blueprint && blueprint.id === m.blueprint.id && grade === m.blueprint.grade active: m.blueprint && blueprint.id === m.blueprint.id && grade === m.blueprint.grade
}); });
const close = this._blueprintSelected.bind(this, blueprintName, grade); const close = this._blueprintSelected.bind(this, blueprintName, grade);
const key = blueprintName + ':' + grade; const key = blueprintName + ':' + grade;
const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers, m.grp); const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers, m.grp);
if (classes.indexOf('active') >= 0) this.selectedModId = key;
blueprintGrades.unshift(<li key={key} tabIndex="0" data-id={key} className={classes} style={{ width: '2em' }} onMouseOver={termtip.bind(null, tooltipContent)} onMouseOut={tooltip.bind(null, null)} onClick={close} onKeyDown={this._keyDown} ref={modItem => this.modItems[key] = modItem}>{grade}</li>); blueprintGrades.unshift(<li key={key} tabIndex="0" data-id={key} className={classes} style={{ width: '2em' }} onMouseOver={termtip.bind(null, tooltipContent)} onMouseOut={tooltip.bind(null, null)} onClick={close} onKeyDown={this._keyDown} ref={modItem => this.modItems[key] = modItem}>{grade}</li>);
} }
if (blueprintGrades) { if (blueprintGrades) {
const thisLen = blueprintGrades.length; const thisLen = blueprintGrades.length;
if (this.firstModId == null) this.firstModId = blueprintGrades[0].key; if (this.firstModId == null) this.firstModId = blueprintGrades[0].key;
this.lastModId = blueprintGrades[thisLen-1].key; this.lastModId = blueprintGrades[thisLen - 1].key;
blueprints.push(<div key={blueprint.name} className={'select-group cap'}>{translate(blueprint.name)}</div>); blueprints.push(<div key={blueprint.name} className={'select-group cap'}>{translate(blueprint.name)}</div>);
blueprints.push(<ul key={blueprintName}>{blueprintGrades}</ul>); blueprints.push(<ul key={blueprintName}>{blueprintGrades}</ul>);
} }
@@ -100,41 +103,38 @@ export default class ModificationsMenu extends TranslatedComponent {
/** /**
* Key down - select module on Enter key, move to next/previous module on Tab/Shift-Tab, close on Esc * Key down - select module on Enter key, move to next/previous module on Tab/Shift-Tab, close on Esc
* @param {Function} select Select module callback
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
* *
*/ */
_keyDown(event) { _keyDown(event) {
var className = null; let className = null;
var elemId = null; let elemId = null;
if (event.currentTarget.attributes['class']) className = event.currentTarget.attributes['class'].value; if (event.currentTarget.attributes['class']) className = event.currentTarget.attributes['class'].value;
if (event.currentTarget.attributes['data-id']) elemId = event.currentTarget.attributes['data-id'].value; if (event.currentTarget.attributes['data-id']) elemId = event.currentTarget.attributes['data-id'].value;
if (event.key == 'Enter' && className.indexOf('disabled') < 0 && className.indexOf('active') < 0) { if (event.key == 'Enter' && className.indexOf('disabled') < 0 && className.indexOf('active') < 0) {
event.stopPropagation(); event.stopPropagation();
if (elemId != null) { if (elemId != null) {
this.modItems[elemId].click(); this.modItems[elemId].click();
} else { } else {
event.currentTarget.click(); event.currentTarget.click();
} }
return return;
} }
if (event.key == 'Tab') { if (event.key == 'Tab') {
//Shift-Tab // Shift-Tab
if(event.shiftKey) { if(event.shiftKey) {
if (elemId == this.firstModId && elemId != null) { if (elemId == this.firstModId && elemId != null) {
// Initial modification menu // Initial modification menu
event.preventDefault(); event.preventDefault();
this.modItems[this.lastModId].focus(); this.modItems[this.lastModId].focus();
return; return;
} else if (event.currentTarget.className.indexOf("button-inline-menu") >= 0 && event.currentTarget.previousElementSibling == null && this.lastNeId != null && this.modItems[this.lastNeId] != null) { } else if (event.currentTarget.className.indexOf('button-inline-menu') >= 0 && event.currentTarget.previousElementSibling == null && this.lastNeId != null && this.modItems[this.lastNeId] != null) {
// shift-tab on first element in modifications menu. set focus to last number editor field if open // shift-tab on first element in modifications menu. set focus to last number editor field if open
event.preventDefault(); event.preventDefault();
this.modItems[this.lastNeId].lastChild.focus(); this.modItems[this.lastNeId].lastChild.focus();
return; return;
} else if (event.currentTarget.className.indexOf("button-inline-menu") >= 0 && event.currentTarget.previousElementSibling == null) { } else if (event.currentTarget.className.indexOf('button-inline-menu') >= 0 && event.currentTarget.previousElementSibling == null) {
// shift-tab on button-inline-menu with no number editor // shift-tab on button-inline-menu with no number editor
event.preventDefault(); event.preventDefault();
event.currentTarget.parentElement.lastElementChild.focus(); event.currentTarget.parentElement.lastElementChild.focus();
@@ -143,9 +143,9 @@ export default class ModificationsMenu extends TranslatedComponent {
if (elemId == this.lastModId && elemId != null) { if (elemId == this.lastModId && elemId != null) {
// Initial modification menu // Initial modification menu
event.preventDefault(); event.preventDefault();
this.modItems[this.firstModId].focus(); this.modItems[this.firstModId].focus();
return; return;
} else if (event.currentTarget.className.indexOf("button-inline-menu") >= 0 && event.currentTarget.nextSibling == null && event.currentTarget.nodeName != "TD") { } else if (event.currentTarget.className.indexOf('button-inline-menu') >= 0 && event.currentTarget.nextSibling == null && event.currentTarget.nodeName != 'TD') {
// Experimental menu // Experimental menu
event.preventDefault(); event.preventDefault();
event.currentTarget.parentElement.firstElementChild.focus(); event.currentTarget.parentElement.firstElementChild.focus();
@@ -154,11 +154,10 @@ export default class ModificationsMenu extends TranslatedComponent {
event.preventDefault(); event.preventDefault();
this.modItems[this.firstBPLabel].focus(); this.modItems[this.firstBPLabel].focus();
} }
} }
} }
} }
/** /**
* Render the specials * Render the specials
@@ -167,7 +166,6 @@ export default class ModificationsMenu extends TranslatedComponent {
* @return {Object} list: Array of React Components * @return {Object} list: Array of React Components
*/ */
_renderSpecials(props, context) { _renderSpecials(props, context) {
const { m } = props; const { m } = props;
const { language, tooltip, termtip } = context; const { language, tooltip, termtip } = context;
const translate = language.translate; const translate = language.translate;
@@ -181,8 +179,9 @@ export default class ModificationsMenu extends TranslatedComponent {
continue; continue;
} }
const classes = cn('button-inline-menu', { const classes = cn('button-inline-menu', {
active: m.blueprint && m.blueprint.special && m.blueprint.special.edname == specialName active: m.blueprint && m.blueprint.special && m.blueprint.special.edname == specialName
}); });
if (classes.indexOf('active') >= 0) this.selectedSpecialId = specialName;
const close = this._specialSelected.bind(this, specialName); const close = this._specialSelected.bind(this, specialName);
if (m.blueprint && m.blueprint.name) { if (m.blueprint && m.blueprint.name) {
let tmp = {}; let tmp = {};
@@ -200,7 +199,6 @@ export default class ModificationsMenu extends TranslatedComponent {
} }
} }
} }
console.log("_renderSpecials. specials: %O", specials);
return specials; return specials;
} }
@@ -211,16 +209,26 @@ export default class ModificationsMenu extends TranslatedComponent {
*/ */
_renderModifications(props) { _renderModifications(props) {
const { m, onChange, ship } = props; const { m, onChange, ship } = props;
const modifiableModifications = [];
const modifications = []; const modifications = [];
for (const modName of Modifications.modules[m.grp].modifications) { for (const modName of Modifications.modules[m.grp].modifications) {
if (!Modifications.modifications[modName].hidden) { if (!Modifications.modifications[modName].hidden) {
const key = modName + (m.getModValue(modName) / 100 || 0); const key = modName + (m.getModValue(modName) / 100 || 0);
const editable = modName !== 'fallofffromrange' &&
m.blueprint.grades[m.blueprint.grade].features[modName];
this.lastNeId = modName; this.lastNeId = modName;
modifications.push(<Modification key={ key } ship={ ship } m={ m } name={ modName } value={ m.getModValue(modName) / 100 || 0 } onChange={ onChange } onKeyDown={ this._keyDown } modItems={ this.modItems } handleModChange = {this._handleModChange} />); (editable ? modifiableModifications : modifications).push(
<Modification key={ key } ship={ ship } m={ m }
value={m.getPretty(modName) || 0} modItems={this.modItems}
onChange={onChange} onKeyDown={this._keyDown} name={modName}
editable={editable} handleModChange = {this._handleModChange} />
);
} }
} }
console.log("_renderModifications. modItems: %O", this.modItems);
return modifications; modifiableModifications.sort(MODIFICATIONS_COMPARATOR);
modifications.sort(MODIFICATIONS_COMPARATOR);
return modifiableModifications.concat(modifications);
} }
/** /**
@@ -280,10 +288,10 @@ export default class ModificationsMenu extends TranslatedComponent {
_rollFifty() { _rollFifty() {
const { m, ship } = this.props; const { m, ship } = this.props;
setPercent(ship, m, 50); setPercent(ship, m, 50);
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates // this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
this._handleModChange(true); this._handleModChange(true);
this.props.onChange(); this.props.onChange();
} }
@@ -293,10 +301,10 @@ export default class ModificationsMenu extends TranslatedComponent {
_rollRandom() { _rollRandom() {
const { m, ship } = this.props; const { m, ship } = this.props;
setRandom(ship, m); setRandom(ship, m);
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates // this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
this._handleModChange(true); this._handleModChange(true);
this.props.onChange(); this.props.onChange();
} }
@@ -306,10 +314,10 @@ export default class ModificationsMenu extends TranslatedComponent {
_rollBest() { _rollBest() {
const { m, ship } = this.props; const { m, ship } = this.props;
setPercent(ship, m, 100); setPercent(ship, m, 100);
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates // this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
this._handleModChange(true); this._handleModChange(true);
this.props.onChange(); this.props.onChange();
} }
@@ -319,13 +327,9 @@ export default class ModificationsMenu extends TranslatedComponent {
_rollWorst() { _rollWorst() {
const { m, ship } = this.props; const { m, ship } = this.props;
setPercent(ship, m, 0); setPercent(ship, m, 0);
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates // this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
this._handleModChange(true); this._handleModChange(true);
this.props.onChange(); this.props.onChange();
} }
/** /**
@@ -335,21 +339,24 @@ export default class ModificationsMenu extends TranslatedComponent {
const { m, ship } = this.props; const { m, ship } = this.props;
ship.clearModifications(m); ship.clearModifications(m);
ship.clearModuleBlueprint(m); ship.clearModuleBlueprint(m);
this.selectedModId = null;
this.selectedSpecialId = null;
this.props.onChange(); this.props.onChange();
} }
/** /**
* set mod did change boolean * set mod did change boolean
* @param {boolean} b Boolean to determine if a change has been made to a module
*/ */
_handleModChange(b) { _handleModChange(b) {
this.modValDidChange = b; this.modValDidChange = b;
} }
/**
* Set focus on first element in modifications menu
* after it first mounts
*/
componentDidMount() { componentDidMount() {
/**
* Set focus on first element in modifications menu
* after it first mounts
*/
let firstEleCn = this.modItems['modMainDiv'].children.length > 0 ? this.modItems['modMainDiv'].children[0].className : null; let firstEleCn = this.modItems['modMainDiv'].children.length > 0 ? this.modItems['modMainDiv'].children[0].className : null;
if (firstEleCn.indexOf('select-group cap') >= 0) { if (firstEleCn.indexOf('select-group cap') >= 0) {
this.modItems['modMainDiv'].children[1].firstElementChild.focus(); this.modItems['modMainDiv'].children[1].firstElementChild.focus();
@@ -358,14 +365,21 @@ export default class ModificationsMenu extends TranslatedComponent {
} }
} }
/**
* Set focus on first element in modifications menu
* if component updates, unless update is due to value change
* in a modification
*/
componentDidUpdate() { componentDidUpdate() {
/**
* Set focus on first element in modifications menu
* if component updates, unless update is due to value change
* in a modification
*/
if (!this.modValDidChange) { if (!this.modValDidChange) {
if (this.modItems['modMainDiv'].children.length > 0) { if (this.modItems['modMainDiv'].children.length > 0) {
if (this.modItems[this.selectedModId]) {
this.modItems[this.selectedModId].focus();
return;
} else if (this.modItems[this.selectedSpecialId]) {
this.modItems[this.selectedSpecialId].focus();
return;
}
let firstEleCn = this.modItems['modMainDiv'].children[0].className; let firstEleCn = this.modItems['modMainDiv'].children[0].className;
if (firstEleCn.indexOf('button-inline-menu') >= 0) { if (firstEleCn.indexOf('button-inline-menu') >= 0) {
this.modItems['modMainDiv'].firstElementChild.focus(); this.modItems['modMainDiv'].firstElementChild.focus();
@@ -374,14 +388,15 @@ export default class ModificationsMenu extends TranslatedComponent {
} }
} }
} else { } else {
this._handleModChange(false);//Need to reset if component update due to value change this._handleModChange(false);// Need to reset if component update due to value change
} }
} }
/**
* set focus to the modification menu icon after mod menu is unmounted.
*/
componentWillUnmount() { componentWillUnmount() {
if (this.props.modButton) { if (this.props.modButton) {
this.props.modButton.focus();// set focus to the modification menu icon after mod menu is unmounted. this.props.modButton.focus();
} }
} }
@@ -407,7 +422,7 @@ export default class ModificationsMenu extends TranslatedComponent {
let haveBlueprint = false; let haveBlueprint = false;
let blueprintTt; let blueprintTt;
let blueprintCv; let blueprintCv;
//TODO: Fix this to actually find the correct blueprint. // TODO: Fix this to actually find the correct blueprint.
if (!m.blueprint || !m.blueprint.name || !m.blueprint.fdname || !Modifications.modules[m.grp].blueprints || !Modifications.modules[m.grp].blueprints[m.blueprint.fdname]) { if (!m.blueprint || !m.blueprint.name || !m.blueprint.fdname || !Modifications.modules[m.grp].blueprints || !Modifications.modules[m.grp].blueprints[m.blueprint.fdname]) {
this.props.ship.clearModuleBlueprint(m); this.props.ship.clearModuleBlueprint(m);
this.props.ship.clearModuleSpecial(m); this.props.ship.clearModuleSpecial(m);
@@ -440,7 +455,7 @@ export default class ModificationsMenu extends TranslatedComponent {
const showReset = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint; const showReset = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint;
const showMods = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint; const showMods = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint;
if (haveBlueprint) { if (haveBlueprint) {
this.firstBPLabel = blueprintLabel this.firstBPLabel = blueprintLabel;
} else { } else {
this.firstBPLabel = 'selectBP'; this.firstBPLabel = 'selectBP';
} }
@@ -451,21 +466,21 @@ export default class ModificationsMenu extends TranslatedComponent {
onContextMenu={stopCtxPropagation} onContextMenu={stopCtxPropagation}
ref={modItem => this.modItems['modMainDiv'] = modItem} ref={modItem => this.modItems['modMainDiv'] = modItem}
> >
{ showBlueprintsMenu | showSpecialsMenu ? '' : haveBlueprint ? { showBlueprintsMenu | showSpecialsMenu ? '' : haveBlueprint ?
<div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={termtip.bind(null, blueprintTt)} onMouseOut={tooltip.bind(null, null)} onClick={_toggleBlueprintsMenu} onKeyDown={ this._keyDown } ref={modItems => this.modItems[this.firstBPLabel] = modItems}>{blueprintLabel}</div> : <div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={termtip.bind(null, blueprintTt)} onMouseOut={tooltip.bind(null, null)} onClick={_toggleBlueprintsMenu} onKeyDown={ this._keyDown } ref={modItems => this.modItems[this.firstBPLabel] = modItems}>{blueprintLabel}</div> :
<div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleBlueprintsMenu} onKeyDown={ this._keyDown } ref={modItems => this.modItems[this.firstBPLabel] = modItems}>{translate('PHRASE_SELECT_BLUEPRINT')}</div> } <div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleBlueprintsMenu} onKeyDown={ this._keyDown } ref={modItems => this.modItems[this.firstBPLabel] = modItems}>{translate('PHRASE_SELECT_BLUEPRINT')}</div> }
{ showBlueprintsMenu ? this._renderBlueprints(this.props, this.context) : null } { showBlueprintsMenu ? this._renderBlueprints(this.props, this.context) : null }
{ showSpecial & !showSpecialsMenu ? <div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: specialMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={specialTt ? termtip.bind(null, specialTt) : null} onMouseOut={specialTt ? tooltip.bind(null, null) : null} onClick={_toggleSpecialsMenu} onKeyDown={ this._keyDown }>{specialLabel}</div> : null } { showSpecial & !showSpecialsMenu ? <div tabIndex="0" className={ cn('section-menu button-inline-menu', { selected: specialMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={specialTt ? termtip.bind(null, specialTt) : null} onMouseOut={specialTt ? tooltip.bind(null, null) : null} onClick={_toggleSpecialsMenu} onKeyDown={ this._keyDown }>{specialLabel}</div> : null }
{ showSpecialsMenu ? specials : null } { showSpecialsMenu ? specials : null }
{ showReset ? <div tabIndex="0" className={'section-menu button-inline-menu warning'} style={{ cursor: 'pointer' }} onClick={_reset} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')} onMouseOut={tooltip.bind(null, null)}> { translate('reset') } </div> : null } { showReset ? <div tabIndex="0" className={'section-menu button-inline-menu warning'} style={{ cursor: 'pointer' }} onClick={_reset} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')} onMouseOut={tooltip.bind(null, null)}> { translate('reset') } </div> : null }
{ showRolls ? { showRolls ?
<table style={{ width: '100%', backgroundColor: 'transparent' }}> <table style={{ width: '100%', backgroundColor: 'transparent' }}>
<tbody> <tbody>
{ showRolls ? { showRolls ?
<tr> <tr>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', {active: false})}> { translate('roll') }: </td> <td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: false }) }> { translate('roll') }: </td>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 0 })} style={{ cursor: 'pointer' }} onClick={_rollWorst} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')} onMouseOut={tooltip.bind(null, null)}> { translate('0%') } </td> <td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 0 }) } style={{ cursor: 'pointer' }} onClick={_rollWorst} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')} onMouseOut={tooltip.bind(null, null)}> { translate('0%') } </td>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 50 })} style={{ cursor: 'pointer' }} onClick={_rollFifty} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_FIFTY')} onMouseOut={tooltip.bind(null, null)}> { translate('50%') } </td> <td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 50 })} style={{ cursor: 'pointer' }} onClick={_rollFifty} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_FIFTY')} onMouseOut={tooltip.bind(null, null)}> { translate('50%') } </td>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 100 })} style={{ cursor: 'pointer' }} onClick={_rollFull} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')} onMouseOut={tooltip.bind(null, null)}> { translate('100%') } </td> <td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === 100 })} style={{ cursor: 'pointer' }} onClick={_rollFull} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')} onMouseOut={tooltip.bind(null, null)}> { translate('100%') } </td>
<td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === null || blueprintCv % 50 != 0 })} style={{ cursor: 'pointer' }} onClick={_rollRandom} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')} onMouseOut={tooltip.bind(null, null)}> { translate('random') } </td> <td tabIndex="0" className={ cn('section-menu button-inline-menu', { active: blueprintCv === null || blueprintCv % 50 != 0 })} style={{ cursor: 'pointer' }} onClick={_rollRandom} onKeyDown={ this._keyDown } onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')} onMouseOut={tooltip.bind(null, null)}> { translate('random') } </td>

View File

@@ -42,6 +42,63 @@ export function weaponComparator(translate, propComparator, desc) {
}; };
} }
/**
* Creates a tooltip that shows damage by type.
* @param {function} translate Translation function
* @param {Object} formats Object that holds format functions
* @param {Calc.SDps} sdpsObject Object that holds sdps split up by type
* @returns {Array} Tooltip
*/
function getSDpsTooltip(translate, formats, sdpsObject) {
return Object.keys(sdpsObject).filter(key => sdpsObject[key])
.map(key => {
return (
<div key={key}>
{translate(key) + ' ' + formats.f1(sdpsObject[key])}
</div>
);
});
}
/**
* Returns a data object used by {@link PieChart} that shows damage by type.
* @param {function} translate Translation function
* @param {Calc.SDps} sdpsObject Object that holds sdps split up by type
* @returns {Object} Data object
*/
function getSDpsData(translate, sdpsObject) {
return Object.keys(sdpsObject).map(key => {
return {
value: Math.round(sdpsObject[key]),
label: translate(key)
};
});
}
/**
* Adds all damage of `add` onto `addOn`.
* @param {Calc.SDps} addOn Object that holds sdps split up by type (will be mutated)
* @param {Calc.SDps} add Object that holds sdps split up by type
*/
function addSDps(addOn, add) {
Object.keys(addOn).map(k => addOn[k] += (add[k] ? add[k] : 0));
}
/**
* Calculates the overall sdps of an sdps object.
* @param {Calc.SDps} sdpsObject Object that holds sdps spluit up by type
*/
function sumSDps(sdpsObject) {
if (sdpsObject.total) {
return sdpsObject.total;
}
return Object.keys(sdpsObject).reduce(
(acc, k) => acc + (sdpsObject[k] ? sdpsObject[k] : 0),
0
);
}
/** /**
* Offence information * Offence information
* Offence information consists of four panels: * Offence information consists of four panels:
@@ -70,7 +127,7 @@ export default class Offence extends TranslatedComponent {
this._sort = this._sort.bind(this); this._sort = this._sort.bind(this);
const damage = Calc.offenceMetrics(props.ship, props.opponent, props.wep, props.opponentSys, props.engagementrange); const damage = Calc.offenceMetrics(props.ship, props.opponent, props.wep, props.opponentSys, props.engagementrange);
this.state = { this.state = {
predicate: 'n', predicate: 'n',
desc: true, desc: true,
damage damage
@@ -144,51 +201,36 @@ export default class Offence extends TranslatedComponent {
const timeToDrain = Calc.timeToDrainWep(ship, wep); 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; let totalSEps = 0;
let totalSDpsObject = {'absolute': 0, 'explosive': 0, 'kinetic': 0, 'thermal': 0};
let shieldsSDpsObject = {'absolute': 0, 'explosive': 0, 'kinetic': 0, 'thermal': 0};
let armourSDpsObject = {'absolute': 0, 'explosive': 0, 'kinetic': 0, 'thermal': 0};
const rows = []; const rows = [];
for (let i = 0; i < damage.length; i++) { for (let i = 0; i < damage.length; i++) {
const weapon = damage[i]; const weapon = damage[i];
totalSEps += weapon.seps; totalSEps += weapon.seps;
absoluteShieldsSDps += weapon.sdps.shields.absolute; addSDps(totalSDpsObject, weapon.sdps.base);
explosiveShieldsSDps += weapon.sdps.shields.explosive; addSDps(shieldsSDpsObject, weapon.sdps.shields);
kineticShieldsSDps += weapon.sdps.shields.kinetic; addSDps(armourSDpsObject, weapon.sdps.armour);
thermalShieldsSDps += weapon.sdps.shields.thermal;
absoluteArmourSDps += weapon.sdps.armour.absolute; const baseSDpsTooltipDetails = getSDpsTooltip(translate, formats, weapon.sdps.base);
explosiveArmourSDps += weapon.sdps.armour.explosive;
kineticArmourSDps += weapon.sdps.armour.kinetic;
thermalArmourSDps += weapon.sdps.armour.thermal;
const effectivenessShieldsTooltipDetails = []; const effectivenessShieldsTooltipDetails = [];
effectivenessShieldsTooltipDetails.push(<div key='range'>{translate('range') + ' ' + formats.pct1(weapon.effectiveness.shields.range)}</div>); 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='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>); effectivenessShieldsTooltipDetails.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(weapon.effectiveness.shields.sys)}</div>);
const effectiveShieldsSDpsTooltipDetails = []; const effectiveShieldsSDpsTooltipDetails = getSDpsTooltip(translate, formats, weapon.sdps.armour);
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 = []; const effectivenessArmourTooltipDetails = [];
effectivenessArmourTooltipDetails.push(<div key='range'>{translate('range') + ' ' + formats.pct1(weapon.effectiveness.armour.range)}</div>); 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='resistance'>{translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.armour.resistance)}</div>);
effectivenessArmourTooltipDetails.push(<div key='hardness'>{translate('hardness') + ' ' + formats.pct1(weapon.effectiveness.armour.hardness)}</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>); const effectiveArmourSDpsTooltipDetails = getSDpsTooltip(translate, formats, weapon.sdps.armour);
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( rows.push(
<tr key={weapon.id}> <tr key={weapon.id}>
@@ -199,27 +241,25 @@ export default class Offence extends TranslatedComponent {
{weapon.classRating} {translate(weapon.name)} {weapon.classRating} {translate(weapon.name)}
{weapon.engineering ? ' (' + weapon.engineering + ')' : null } {weapon.engineering ? ' (' + weapon.engineering + ')' : null }
</td> </td>
<td className='ri'><span onMouseOver={termtip.bind(null, baseSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.base.total)}</span></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, 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, 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, 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> <td className='ri'><span onMouseOver={termtip.bind(null, effectivenessArmourTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(weapon.effectiveness.armour.total)}</span></td>
</tr>); </tr>);
} }
const totalShieldsSDps = absoluteShieldsSDps + explosiveShieldsSDps + kineticShieldsSDps + thermalShieldsSDps; const totalSDps = sumSDps(totalSDpsObject);
const totalArmourSDps = absoluteArmourSDps + explosiveArmourSDps + kineticArmourSDps + thermalArmourSDps; const totalSDpsTooltipDetails = getSDpsTooltip(translate, formats, totalSDpsObject);
const totalSDpsData = getSDpsData(translate, totalSDpsObject);
const shieldsSDpsData = []; const totalShieldsSDps = sumSDps(shieldsSDpsObject);
shieldsSDpsData.push({ value: Math.round(absoluteShieldsSDps), label: translate('absolute') }); const totalShieldsSDpsTooltipDetails = getSDpsTooltip(translate, formats, shieldsSDpsObject);
shieldsSDpsData.push({ value: Math.round(explosiveShieldsSDps), label: translate('explosive') }); const shieldsSDpsData = getSDpsData(translate, shieldsSDpsObject);
shieldsSDpsData.push({ value: Math.round(kineticShieldsSDps), label: translate('kinetic') });
shieldsSDpsData.push({ value: Math.round(thermalShieldsSDps), label: translate('thermal') });
const armourSDpsData = []; const totalArmourSDps = sumSDps(armourSDpsObject);
armourSDpsData.push({ value: Math.round(absoluteArmourSDps), label: translate('absolute') }); const totalArmourSDpsTooltipDetails = getSDpsTooltip(translate, formats, armourSDpsObject);
armourSDpsData.push({ value: Math.round(explosiveArmourSDps), label: translate('explosive') }); const armourSDpsData = getSDpsData(translate, armourSDpsObject);
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 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)); const timeToDepleteArmour = Calc.timeToDeplete(opponentArmour.total, totalArmourSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4));
@@ -231,19 +271,31 @@ export default class Offence extends TranslatedComponent {
<thead> <thead>
<tr className='main'> <tr className='main'>
<th rowSpan='2' className='sortable' onClick={sortOrder.bind(this, 'n')}>{translate('weapon')}</th> <th rowSpan='2' className='sortable' onClick={sortOrder.bind(this, 'n')}>{translate('weapon')}</th>
<th colSpan='1'>{translate('overall')}</th>
<th colSpan='2'>{translate('opponent\'s shields')}</th> <th colSpan='2'>{translate('opponent\'s shields')}</th>
<th colSpan='2'>{translate('opponent\'s armour')}</th> <th colSpan='2'>{translate('opponent\'s armour')}</th>
</tr> </tr>
<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='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')} onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'esdpss')}>{'sdps'}</th> <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='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='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> <th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'eh')}>{'eft'}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{rows} {rows}
</tbody> {rows.length > 0 &&
<tr>
<td></td>
<td className='ri'><span onMouseOver={termtip.bind(null, totalSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>={formats.f1(totalSDps)}</span></td>
<td className='ri'><span onMouseOver={termtip.bind(null, totalShieldsSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>={formats.f1(totalShieldsSDps)}</span></td>
<td></td>
<td className='ri'><span onMouseOver={termtip.bind(null, totalArmourSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>={formats.f1(totalArmourSDps)}</span></td>
<td></td>
</tr>
}
</tbody>
</table> </table>
</div> </div>
<div className='group quarter'> <div className='group quarter'>
@@ -254,6 +306,10 @@ export default class Offence extends TranslatedComponent {
<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_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> <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>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_OVERALL_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('overall damage')}</h2>
<PieChart data={totalSDpsData} />
</div>
<div className='group quarter'> <div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield damage sources')}</h2> <h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield damage sources')}</h2>
<PieChart data={shieldsSDpsData} /> <PieChart data={shieldsSDpsData} />

View File

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

View File

@@ -18,12 +18,16 @@ export default class ShipSummaryTable extends TranslatedComponent {
pips: PropTypes.object.isRequired pips: PropTypes.object.isRequired
}; };
/**
* The ShipSummaryTable constructor
* @param {Object} props The props
*/
constructor(props) { constructor(props) {
super(props) super(props);
this.didContextChange = this.didContextChange.bind(this); this.didContextChange = this.didContextChange.bind(this);
this.state = { this.state = {
shieldColour: 'blue' shieldColour: 'blue'
} };
} }
/** /**
@@ -46,7 +50,8 @@ export default class ShipSummaryTable extends TranslatedComponent {
const speedTooltip = canThrust ? 'TT_SUMMARY_SPEED' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL'; const speedTooltip = canThrust ? 'TT_SUMMARY_SPEED' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
const canBoost = ship.canBoost(cargo, ship.fuelCapacity); const canBoost = ship.canBoost(cargo, ship.fuelCapacity);
const boostTooltip = canBoost ? 'TT_SUMMARY_BOOST' : canThrust ? 'TT_SUMMARY_BOOST_NONFUNCTIONAL' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL'; const boostTooltip = canBoost ? 'TT_SUMMARY_BOOST' : canThrust ? 'TT_SUMMARY_BOOST_NONFUNCTIONAL' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
const sgMetrics = Calc.shieldMetrics(ship, pips.sys || 2); const sgMetrics = Calc.shieldMetrics(ship, pips.sys);
const shipBoost = canBoost ? Calc.calcBoost(ship) : 'No Boost';
const armourMetrics = Calc.armourMetrics(ship); const armourMetrics = Calc.armourMetrics(ship);
let shieldColour = 'blue'; let shieldColour = 'blue';
if (shieldGenerator && shieldGenerator.m.grp === 'psg') { if (shieldGenerator && shieldGenerator.m.grp === 'psg') {
@@ -56,130 +61,152 @@ export default class ShipSummaryTable extends TranslatedComponent {
} }
this.state = { this.state = {
shieldColour shieldColour
} };
return <div id='summary'> return <div id='summary'>
<table className={'summaryTable'}> <div style={{display: "table", width: "100%"}}>
<thead> <div style={{display: "table-row"}}>
<tr className='main'> <table className={'summaryTable'}>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canThrust }) }>{translate('speed')}</th> <thead>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canBoost }) }>{translate('boost')}</th> <tr className='main'>
<th colSpan={5}>{translate('jump range')}</th> <th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canThrust }) }>{translate('speed')}</th>
<th rowSpan={2}>{translate('shield')}</th> <th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canBoost }) }>{translate('boost')}</th>
<th rowSpan={2}>{translate('integrity')}</th> <th colSpan={5}>{translate('jump range')}</th>
<th rowSpan={2}>{translate('DPS')}</th> <th rowSpan={2}>{translate('shield')}</th>
<th rowSpan={2}>{translate('EPS')}</th> <th rowSpan={2}>{translate('integrity')}</th>
<th rowSpan={2}>{translate('TTD')}</th> <th rowSpan={2}>{translate('DPS')}</th>
{/* <th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th> */} <th rowSpan={2}>{translate('EPS')}</th>
<th rowSpan={2}>{translate('cargo')}</th> <th rowSpan={2}>{translate('TTD')}</th>
<th rowSpan={2}>{translate('pax')}</th> {/* <th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th> */}
<th rowSpan={2}>{translate('fuel')}</th> <th rowSpan={2}>{translate('cargo')}</th>
<th colSpan={3}>{translate('mass')}</th> <th rowSpan={2}>{translate('pax')}</th>
<th onMouseEnter={termtip.bind(null, 'hull hardness', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('hrd')}</th> <th rowSpan={2}>{translate('fuel')}</th>
<th rowSpan={2}>{translate('crew')}</th> <th colSpan={3}>{translate('mass')}</th>
<th onMouseEnter={termtip.bind(null, 'mass lock factor', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th> <th onMouseEnter={termtip.bind(null, 'hull hardness', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('hrd')}</th>
</tr> <th rowSpan={2}>{translate('crew')}</th>
<tr> <th onMouseEnter={termtip.bind(null, 'mass lock factor', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th>
<th className='lft'>{translate('max')}</th> <th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_BOOST_TIME', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('boost time')}</th>
<th>{translate('unladen')}</th> </tr>
<th>{translate('laden')}</th> <tr>
<th>{translate('total unladen')}</th> <th className='lft'>{translate('max')}</th>
<th>{translate('total laden')}</th> <th>{translate('unladen')}</th>
<th className='lft'>{translate('hull')}</th> <th>{translate('laden')}</th>
<th>{translate('unladen')}</th> <th>{translate('total unladen')}</th>
<th>{translate('laden')}</th> <th>{translate('total laden')}</th>
</tr> <th className='lft'>{translate('hull')}</th>
</thead> <th>{translate('unladen')}</th>
<tbody> <th>{translate('laden')}</th>
<tr> </tr>
<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> </thead>
<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> <tbody>
<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> <tr>
<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 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><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, 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 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><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(), ship))}{u.LY}</span></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><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, ship))}{u.LY}</span></td>
<td className={sgClassNames} onMouseEnter={termtip.bind(null, sgTooltip, { cap: 0 })} onMouseLeave={hide}>{int(ship.shield)}{u.MJ}</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, ship))}{u.LY}</span></td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_INTEGRITY', { cap: 0 })} onMouseLeave={hide}>{int(ship.armour)}</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, ship))}{u.LY}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_DPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalDps)}</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, ship))}{u.LY}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_EPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalEps)}</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_TTD', { cap: 0 })} onMouseLeave={hide}>{timeToDrain === Infinity ? '∞' : time(timeToDrain)}</td> <td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_INTEGRITY', { cap: 0 })} onMouseLeave={hide}>{int(ship.armour)}</td>
{/* <td>{f1(ship.totalHps)}</td> */} <td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_DPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalDps)}</td>
<td>{round(ship.cargoCapacity)}{u.T}</td> <td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_EPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalEps)}</td>
<td>{ship.passengerCapacity}</td> <td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_TTD', { cap: 0 })} onMouseLeave={hide}>{timeToDrain === Infinity ? '∞' : time(timeToDrain)}</td>
<td>{round(ship.fuelCapacity)}{u.T}</td> {/* <td>{f1(ship.totalHps)}</td> */}
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_HULL_MASS', { cap: 0 })} onMouseLeave={hide}>{ship.hullMass}{u.T}</td> <td>{round(ship.cargoCapacity)}{u.T}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.unladenMass)}{u.T}</td> <td>{ship.passengerCapacity}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.ladenMass)}{u.T}</td> <td>{round(ship.fuelCapacity)}{u.T}</td>
<td>{int(ship.hardness)}</td> <td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_HULL_MASS', { cap: 0 })} onMouseLeave={hide}>{ship.hullMass}{u.T}</td>
<td>{ship.crew}</td> <td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.unladenMass)}{u.T}</td>
<td>{ship.masslock}</td> <td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.ladenMass)}{u.T}</td>
</tr> <td>{int(ship.hardness)}</td>
</tbody> <td>{ship.crew}</td>
</table> <td>{ship.masslock}</td>
<table className={'summaryTable'}> <td>{shipBoost !== 'No Boost' ? formats.time(shipBoost) : 'No Boost'}</td>
<thead className={this.state.shieldColour}> </tr>
<tr> </tbody>
<th onMouseEnter={termtip.bind(null, 'shield', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('shield')}</th> </table>
<th onMouseEnter={termtip.bind(null, 'explres', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('explres')}</th> <table className={'summaryTable'}>
<th onMouseEnter={termtip.bind(null, 'kinres', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('kinres')}</th> <thead className={this.state.shieldColour}>
<th onMouseEnter={termtip.bind(null, 'thermres', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('thermres')}</th> <tr>
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'shield', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('shield')}</th>
<th colSpan={4} className='lft'>{translate('resistance')}</th>
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_SHIELDS_SCB', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('absolute')} ${translate('HP')}`}</th> <th colSpan={5} onMouseEnter={termtip.bind(null, 'TT_SUMMARY_SHIELDS_SCB', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('HP')}`}</th>
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_SHIELDS_SCB', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('explosive')} ${translate('HP')}`}</th> <th rowSpan={2} onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECOVER', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('recovery')}</th>
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_SHIELDS_SCB', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('kinetic')} ${translate('HP')}`}</th> <th rowSpan={2} onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECHARGE', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('recharge')}</th>
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_SHIELDS_SCB', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('thermal')} ${translate('HP')}`}</th> </tr>
<th onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECOVER', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('recovery')}</th> <tr>
<th onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECHARGE', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('recharge')}</th> <th>{`${translate('explosive')}`}</th>
</tr> <th>{`${translate('kinetic')}`}</th>
</thead> <th>{`${translate('thermal')}`}</th>
<tbody> <th></th>
<tr>
<td>{translate(shieldGenerator && shieldGenerator.m.grp || 'No Shield')}</td>
<td>{int(ship.shieldExplRes * 100) + '%'}</td>
<td>{int(ship.shieldKinRes * 100) + '%'}</td>
<td>{int(ship.shieldThermRes * 100) + '%'}</td>
<td>{int(sgMetrics && sgMetrics.generator ? sgMetrics.total / sgMetrics.absolute.total : 0)}</td>
<td>{int(sgMetrics && sgMetrics.generator ? sgMetrics.total / sgMetrics.explosive.total : 0)}</td>
<td>{int(sgMetrics && sgMetrics.generator ? sgMetrics.total / sgMetrics.kinetic.total : 0 )}</td>
<td>{int(sgMetrics && sgMetrics.generator ? sgMetrics.total / sgMetrics.thermal.total : 0 )}</td>
<td>{sgMetrics && sgMetrics.recover ? formats.time(sgMetrics.recover) : 0}</td>
<td>{sgMetrics && sgMetrics.recharge ? formats.time(sgMetrics.recharge) : 0}</td>
</tr>
</tbody>
<thead>
<tr>
<th onMouseEnter={termtip.bind(null, 'armour', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('armour')}</th>
<th onMouseEnter={termtip.bind(null, 'explres', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('explres')}</th>
<th onMouseEnter={termtip.bind(null, 'kinres', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('kinres')}</th>
<th onMouseEnter={termtip.bind(null, 'thermres', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('thermres')}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_EFFECTIVE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('absolute')} ${translate('HP')}`}</th> <th className={'bordered'}>{`${translate('absolute')}`}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_EFFECTIVE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('explosive')} ${translate('HP')}`}</th> <th>{`${translate('explosive')}`}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_EFFECTIVE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('kinetic')} ${translate('HP')}`}</th> <th>{`${translate('kinetic')}`}</th>
<th onMouseEnter={termtip.bind(null, 'PHRASE_EFFECTIVE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('thermal')} ${translate('HP')}`}</th> <th>{`${translate('thermal')}`}</th>
<th onMouseEnter={termtip.bind(null, 'TT_MODULE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('raw module armour')}</th> <th></th>
<th onMouseEnter={termtip.bind(null, 'armour', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('internal protection')}</th> </tr>
</thead>
<tbody>
<tr>
<td>{translate(shieldGenerator && shieldGenerator.m.grp || 'No Shield')}</td>
<td>{formats.pct1(ship.shieldExplRes)}</td>
<td>{formats.pct1(ship.shieldKinRes)}</td>
<td>{formats.pct1(ship.shieldThermRes)}</td>
<td></td>
<td>{int(ship && ship.shield > 0 ? ship.shield : 0)}{u.MJ}</td>
<td>{int(ship && ship.shield > 0 ? ship.shield * ((1 / (1 - (ship.shieldExplRes)))) : 0)}{u.MJ}</td>
<td>{int(ship && ship.shield > 0 ? ship.shield * ((1 / (1 - (ship.shieldKinRes)))) : 0)}{u.MJ}</td>
<td>{int(ship && ship.shield > 0 ? ship.shield * ((1 / (1 - (ship.shieldThermRes)))) : 0)}{u.MJ}</td>
<td></td>
<td>{sgMetrics && sgMetrics.recover === Math.Inf ? translate('Never') : formats.time(sgMetrics.recover)}</td>
<td>{sgMetrics && sgMetrics.recharge === Math.Inf ? translate('Never') : formats.time(sgMetrics.recharge)}</td>
</tr>
</tbody>
<thead>
<tr>
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'armour', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('armour')}</th>
<th colSpan={4} className='lft'>{translate('resistance')}</th>
</tr> <th colSpan={5} onMouseEnter={termtip.bind(null, 'PHRASE_EFFECTIVE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('HP')}`}</th>
</thead> <th rowSpan={2} onMouseEnter={termtip.bind(null, 'TT_MODULE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('raw module armour')}</th>
<tbody> <th rowSpan={2} onMouseEnter={termtip.bind(null, 'TT_MODULE_PROTECTION_INTERNAL', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('internal protection')}</th>
<tr> </tr>
<td>{translate(ship && ship.bulkheads && ship.bulkheads.m && ship.bulkheads.m.name || 'No Armour')}</td> <tr>
<td>{int(ship.hullExplRes * 100) + '%'}</td> <th>{`${translate('explosive')}`}</th>
<td>{int(ship.hullKinRes * 100) + '%'}</td> <th>{`${translate('kinetic')}`}</th>
<td>{int(ship.hullThermRes * 100) + '%'}</td> <th>{`${translate('thermal')}`}</th>
<td>{int(armourMetrics.total / armourMetrics.absolute.total)}</td> <th>{`${translate('caustic')}`}</th>
<td>{int(armourMetrics.total / armourMetrics.explosive.total)}</td>
<td>{int(armourMetrics.total / armourMetrics.kinetic.total)}</td>
<td>{int(armourMetrics.total / armourMetrics.thermal.total)}</td>
<td>{int(armourMetrics.modulearmour)}</td>
<td>{int(armourMetrics.moduleprotection * 100) + '%'}</td>
</tr> <th className={'bordered'}>{`${translate('absolute')}`}</th>
</tbody> <th>{`${translate('explosive')}`}</th>
</table> <th>{`${translate('kinetic')}`}</th>
<th>{`${translate('thermal')}`}</th>
<th>{`${translate('caustic')}`}</th>
</tr>
</thead>
<tbody>
<tr>
<td>{translate(ship && ship.bulkheads && ship.bulkheads.m && ship.bulkheads.m.name || 'No Armour')}</td>
<td>{formats.pct1(ship.hullExplRes)}</td>
<td>{formats.pct1(ship.hullKinRes)}</td>
<td>{formats.pct1(ship.hullThermRes)}</td>
<td>{formats.pct1(ship.hullCausRes)}</td>
<td>{int(ship.armour)}</td>
<td>{int(ship.armour * ((1 / (1 - (ship.hullExplRes)))))}</td>
<td>{int(ship.armour * ((1 / (1 - (ship.hullKinRes)))))}</td>
<td>{int(ship.armour * ((1 / (1 - (ship.hullThermRes)))))}</td>
<td>{int(ship.armour * ((1 / (1 - (ship.hullCausRes)))))}</td>
<td>{int(armourMetrics.modulearmour)}</td>
<td>{int(armourMetrics.moduleprotection * 100) + '%'}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>; </div>;
} }
} }

View File

@@ -17,12 +17,12 @@ export default class Slider extends React.Component {
static propTypes = { static propTypes = {
axis: PropTypes.bool, axis: PropTypes.bool,
axisUnit: PropTypes.string,//units (T, M, etc.) axisUnit: PropTypes.string,// units (T, M, etc.)
max: PropTypes.number, max: PropTypes.number,
min: PropTypes.number, min: PropTypes.number,
onChange: PropTypes.func.isRequired,// function which determins percent value onChange: PropTypes.func.isRequired,// function which determins percent value
onResize: PropTypes.func, onResize: PropTypes.func,
percent: PropTypes.number.isRequired,//value of slider percent: PropTypes.number.isRequired,// value of slider
scale: PropTypes.number scale: PropTypes.number
}; };
@@ -51,7 +51,6 @@ export default class Slider extends React.Component {
* @param {SyntheticEvent} event Event * @param {SyntheticEvent} event Event
*/ */
_down(event) { _down(event) {
let rect = event.currentTarget.getBoundingClientRect(); let rect = event.currentTarget.getBoundingClientRect();
this.left = rect.left; this.left = rect.left;
this.width = rect.width; this.width = rect.width;
@@ -95,28 +94,26 @@ export default class Slider extends React.Component {
case 'Enter': case 'Enter':
event.preventDefault(); event.preventDefault();
this.sliderInputBox._setDisplay('block'); this.sliderInputBox._setDisplay('block');
//this.enterTimer = setTimeout(() => this.sliderInputBox.sliderVal.focus(), 10);
return; return;
default: default:
return; return;
} }
} }
/** /**
* Key down handler * Key down handler
* increment slider position by +/- 1 when right/left arrow key is pressed or held * increment slider position by +/- 1 when right/left arrow key is pressed or held
* @param {Event} event * @param {Event} event Keyboard even
*/ */
_keydown(event) { _keydown(event) {
let newVal = this.props.percent * this.props.max;
switch (event.key) { switch (event.key) {
case 'ArrowRight': case 'ArrowRight':
var newVal = this.props.percent*this.props.max + 1; newVal += 1;
if (newVal <= this.props.max) this.props.onChange(newVal/this.props.max); if (newVal <= this.props.max) this.props.onChange(newVal / this.props.max);
return; return;
case 'ArrowLeft': case 'ArrowLeft':
var newVal = this.props.percent*this.props.max - 1; newVal -= 1;
if (newVal >= 0) this.props.onChange(newVal/this.props.max); if (newVal >= 0) this.props.onChange(newVal / this.props.max);
return; return;
default: default:
return; return;
@@ -131,7 +128,12 @@ export default class Slider extends React.Component {
_touchstart(event) { _touchstart(event) {
this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500); this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500);
} }
/**
* Touch end handler
* @param {Event} event DOM Event
*
*/
_touchend(event) { _touchend(event) {
this.sliderInputBox.sliderVal.focus(); this.sliderInputBox.sliderVal.focus();
clearTimeout(this.touchStartTimer); clearTimeout(this.touchStartTimer);
@@ -181,7 +183,6 @@ export default class Slider extends React.Component {
*/ */
componentDidMount() { componentDidMount() {
this._updateDimensions(); this._updateDimensions();
} }
/** /**
@@ -200,21 +201,17 @@ export default class Slider extends React.Component {
render() { render() {
let outerWidth = this.state.outerWidth; let outerWidth = this.state.outerWidth;
let { axis, axisUnit, min, max, scale } = this.props; let { axis, axisUnit, min, max, scale } = this.props;
let style = { let style = {
width: '100%', width: '100%',
height: axis ? '2.5em' : '1.5em', height: axis ? '2.5em' : '1.5em',
boxSizing: 'border-box' boxSizing: 'border-box'
}; };
if (!outerWidth) { if (!outerWidth) {
return <svg style={style} ref={node => this.node = node} />; return <svg style={style} ref={node => this.node = node} />;
} }
let margin = MARGIN_LR * scale; let margin = MARGIN_LR * scale;
let width = outerWidth - (margin * 2); let width = outerWidth - (margin * 2);
let pctPos = width * this.props.percent; let pctPos = width * this.props.percent;
return <div><svg return <div><svg
onMouseUp={this._up} onMouseEnter={this._enter.bind(this)} onMouseMove={this._move} onKeyUp={this._keyup} onKeyDown={this._keydown} style={style} ref={node => this.node = node} tabIndex="0"> onMouseUp={this._up} onMouseEnter={this._enter.bind(this)} onMouseMove={this._move} onKeyUp={this._keyup} onKeyDown={this._keydown} style={style} ref={node => this.node = node} tabIndex="0">
<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' style={{ opacity: 0.3 }} x={margin} y='0.25em' rx='0.3em' ry='0.3em' width={width} height='0.7em' />
@@ -233,110 +230,136 @@ export default class Slider extends React.Component {
axisUnit={this.props.axisUnit} axisUnit={this.props.axisUnit}
scale={this.props.scale} scale={this.props.scale}
max={this.props.max} max={this.props.max}
/> />
</div>; </div>;
} }
} }
/** /**
* New component to add keyboard support for sliders - works on all devices (desktop, iOS, Android) * New component to add keyboard support for sliders - works on all devices (desktop, iOS, Android)
**/ **/
class TextInputBox extends React.Component { class TextInputBox extends React.Component {
static propTypes = { static propTypes = {
axisUnit: PropTypes.string,//units (T, M, etc.) axisUnit: PropTypes.string,// units (T, M, etc.)
max: PropTypes.number, max: PropTypes.number,
onChange: PropTypes.func.isRequired,// function which determins percent value onChange: PropTypes.func.isRequired,// function which determins percent value
percent: PropTypes.number.isRequired,//value of slider percent: PropTypes.number.isRequired,// value of slider
scale: PropTypes.number scale: PropTypes.number
}; };
/**
* Determine if the user is still dragging
* @param {Object} props React Component properties
*/
constructor(props) { constructor(props) {
super(props); super(props);
this._handleFocus = this._handleFocus.bind(this); this._handleFocus = this._handleFocus.bind(this);
this._handleBlur = this._handleBlur.bind(this); this._handleBlur = this._handleBlur.bind(this);
this._handleChange = this._handleChange.bind(this); this._handleChange = this._handleChange.bind(this);
//this._keydown = this._keydown.bind(this); this._keyup = this._keyup.bind(this);
this._keyup = this._keyup.bind(this); this.state = this._getInitialState();
this.state = this._getInitialState(); }
this.percent = this.props.percent; /**
this.max = this.props.max; * Update input value if slider changes will change props/state
this.state.inputValue = this.percent * this.max; * @param {Object} nextProps React Component properites
* @param {Object} nextState React Component state values
*/
componentWillReceiveProps(nextProps, nextState) {
let nextValue = nextProps.percent * nextProps.max;
// See https://stackoverflow.com/questions/32414308/updating-state-on-props-change-in-react-form
if (nextValue !== this.state.inputValue && nextValue <= nextProps.max) {
this.setState({ inputValue: nextValue });
} }
}
componentWillReceiveProps(nextProps, nextState) { /**
var nextValue = nextProps.percent * nextProps.max; * Update slider textbox visibility/values if changes are made to slider
// See https://stackoverflow.com/questions/32414308/updating-state-on-props-change-in-react-form * @param {Object} prevProps React Component properites
if (nextValue !== this.state.inputValue && nextValue <= nextProps.max) { * @param {Object} prevState React Component state values
this.setState({ inputValue: nextValue }); */
} componentDidUpdate(prevProps, prevState) {
if (prevState.divStyle.display == 'none' && this.state.divStyle.display == 'block') {
this.enterTimer = setTimeout(() => this.sliderVal.focus(), 10);
} }
componentDidUpdate(prevProps, prevState) { if (prevProps.max !== this.props.max && this.state.inputValue > this.props.max) {
// they chose a different module
if (prevState.divStyle.display == 'none' && this.state.divStyle.display == 'block') { this.setState({ inputValue: this.props.max });
this.enterTimer = setTimeout(() => this.sliderVal.focus(), 10);
}
if (prevProps.max !== this.props.max && this.state.inputValue > this.props.max) {
// they chose a different module
this.setState({ inputValue: this.props.max });
}
if (this.state.inputValue != prevState.inputValue && prevProps.max == this.props.max) {
this.props.onChange(this.state.inputValue/this.props.max);
}
} }
if (this.state.inputValue != prevState.inputValue && prevProps.max == this.props.max) {
_getInitialState() { this.props.onChange(this.state.inputValue / this.props.max);
return {
divStyle: {display:'none'},
inputStyle: {width:'4em'},
labelStyle: {marginLeft: '.1em'},
maxLength:5,
size:5,
min:0,
tabIndex:-1,
type:'number',
readOnly: true
}
} }
}
_setDisplay(val) { /**
* Set initial state for the textbox.
* We may want to rethink this to
* try and make it a stateless component
* @returns {object} React state object with initial values set
*/
_getInitialState() {
return {
divStyle: { display:'none' },
inputStyle: { width:'4em' },
labelStyle: { marginLeft: '.1em' },
maxLength:5,
size:5,
min:0,
tabIndex:-1,
type:'number',
readOnly: true,
inputValue: this.props.percent * this.props.max
};
}
/**
*
* @param {string} val block or none
*/
_setDisplay(val) {
this.setState({
divStyle: { display:val }
});
}
/**
* Update the input value
* when textbox gets focus
*/
_handleFocus() {
this.setState({
inputValue:this._getValue()
});
}
/**
* Update inputValue when textbox loses focus
*/
_handleBlur() {
this._setDisplay('none');
if (this.state.inputValue !== '') {
this.props.onChange(this.state.inputValue / this.props.max);
} else {
this.setState({ this.setState({
divStyle: {display:val} inputValue: this.props.percent * this.props.max
}); });
} }
}
_handleFocus() { /**
this.setState({ * Get the value in the text box
inputValue:this._getValue() * @returns {number} inputValue Value of the input box
}); */
_getValue() {
return this.state.inputValue;
}
/**
* Update and set limits on input box
* values depending on what user
* has selected
*
* @param {SyntheticEvent} event ReactJs onChange event
*/
_handleChange(event) {
if (event.target.value < 0) {
this.setState({ inputValue: 0 });
} else if (event.target.value <= this.props.max) {
this.setState({ inputValue: event.target.value });
} else {
this.setState({ inputValue: this.props.max });
} }
}
_handleBlur() {
this._setDisplay('none');
if (this.state.inputValue !== '') {
this.props.onChange(this.state.inputValue/this.props.max);
} else {
this.state.inputValue = this.props.percent * this.props.max;
}
}
_getValue() {
return this.state.inputValue;
}
_handleChange(event) {
if (event.target.value < 0) {
this.setState({inputValue: 0});
} else if (event.target.value <= this.props.max) {
this.setState({inputValue: event.target.value});
} else {
this.setState({inputValue: this.props.max});
}
}
/** /**
* Key up handler for input field. * Key up handler for input field.
* If user hits Enter key, blur/close the input field * If user hits Enter key, blur/close the input field
@@ -350,12 +373,14 @@ export default class Slider extends React.Component {
default: default:
return; return;
} }
} }
/**
render() { * Get the value in the text box
let { axisUnit, onChange, percent, scale } = this.props; * @return {React.Component} Text Input component for Slider
return <div style={this.state.divStyle}><input style={this.state.inputStyle} value={this._getValue()} min={this.state.min} max={this.props.max} onChange={this._handleChange} onKeyUp={this._keyup} tabIndex={this.state.tabIndex} maxLength={this.state.maxLength} size={this.state.size} onBlur={() => {this._handleBlur()}} onFocus={() => {this._handleFocus()}} type={this.state.type} ref={(ip) => this.sliderVal = ip}/><text className="primary upp" style={this.state.labelStyle}>{this.props.axisUnit}</text></div>; */
} render() {
} let { axisUnit, onChange, percent, scale } = this.props;
return <div style={this.state.divStyle}><input style={this.state.inputStyle} value={this._getValue()} min={this.state.min} max={this.props.max} onChange={this._handleChange} onKeyUp={this._keyup} tabIndex={this.state.tabIndex} maxLength={this.state.maxLength} size={this.state.size} onBlur={() => {this._handleBlur();}} onFocus={() => {this._handleFocus();}} type={this.state.type} ref={(ip) => this.sliderVal = ip}/><text className="primary upp" style={this.state.labelStyle}>{this.props.axisUnit}</text></div>;
}
}

View File

@@ -81,17 +81,14 @@ export default class Slot extends TranslatedComponent {
* we do more or less the same thing * we do more or less the same thing
* in every section when Enter key is pressed * in every section when Enter key is pressed
* on a focusable item * on a focusable item
* *
*/ */
_keyDown(event) { _keyDown(event) {
if (event.key == 'Enter') { if (event.key == 'Enter') {
if(event.target.className == 'r') { if(event.target.className == 'r') {
console.log("Slot: Enter key pressed on mod icon"); this._toggleModifications();
this._toggleModifications(); }
} else { this.props.onOpen(event);
console.log("Slot: Enter key pressed on: %O", event.target);
}
this.props.onOpen(event);
} }
} }
/** /**
@@ -154,7 +151,7 @@ export default class Slot extends TranslatedComponent {
); );
} }
/** /**
* Toggle the modifications flag when selecting the modifications icon * Toggle the modifications flag when selecting the modifications icon
*/ */

View File

@@ -18,7 +18,8 @@ export default class SlotSection extends TranslatedComponent {
onCargoChange: PropTypes.func.isRequired, onCargoChange: PropTypes.func.isRequired,
onFuelChange: PropTypes.func.isRequired, onFuelChange: PropTypes.func.isRequired,
code: PropTypes.string.isRequired, code: PropTypes.string.isRequired,
togglePwr: PropTypes.func togglePwr: PropTypes.func,
sectionMenuRefs: PropTypes.object
}; };
/** /**
@@ -32,7 +33,10 @@ export default class SlotSection extends TranslatedComponent {
super(props); super(props);
this.sectionId = sectionId; this.sectionId = sectionId;
this.sectionName = sectionName; this.sectionName = sectionName;
this.ssHeadRef = null;
this.sectionRefArr = this.props.sectionMenuRefs[this.sectionId] = [];
this.sectionRefArr['selectedRef'] = null;
this._getSlots = this._getSlots.bind(this); this._getSlots = this._getSlots.bind(this);
this._selectModule = this._selectModule.bind(this); this._selectModule = this._selectModule.bind(this);
this._getSectionMenu = this._getSectionMenu.bind(this); this._getSectionMenu = this._getSectionMenu.bind(this);
@@ -40,6 +44,8 @@ export default class SlotSection extends TranslatedComponent {
this._drop = this._drop.bind(this); this._drop = this._drop.bind(this);
this._dragOverNone = this._dragOverNone.bind(this); this._dragOverNone = this._dragOverNone.bind(this);
this._close = this._close.bind(this); this._close = this._close.bind(this);
this._keyDown = this._keyDown.bind(this);
this._handleSectionFocus = this._handleSectionFocus.bind(this);
this.state = {}; this.state = {};
} }
@@ -47,7 +53,59 @@ export default class SlotSection extends TranslatedComponent {
// _getSlots() // _getSlots()
// _getSectionMenu() // _getSectionMenu()
// _contextMenu() // _contextMenu()
// componentDidUpdate(prevProps)
/**
* TODO: May either need to send the function to be triggered when Enter key is pressed, or else
* may need a separate keyDown handler for each subclass (StandardSlotSection, HardpointSlotSection, etc.)
* ex: _keyDown(_keyDownfn, event)
*
* @param {SyntheticEvent} event KeyDown event
*/
_keyDown(event) {
if (event.key == 'Enter') {
event.stopPropagation();
if (event.currentTarget.nodeName === 'H1') {
this._openMenu(this.sectionName, event);
} else {
event.currentTarget.click();
}
return;
}
if (event.key == 'Tab') {
if (event.shiftKey) {
if ((event.currentTarget === this.sectionRefArr[this.firstRefId]) && this.sectionRefArr[this.lastRefId]) {
event.preventDefault();
this.sectionRefArr[this.lastRefId].focus();
}
} else {
if ((event.currentTarget === this.sectionRefArr[this.lastRefId]) && this.sectionRefArr[this.firstRefId]) {
event.preventDefault();
this.sectionRefArr[this.firstRefId].focus();
}
}
}
}
/**
* Set focus on appropriate Slot Section Menu element
* @param {Object} focusPrevProps prevProps for componentDidUpdate() from ...SlotSection.jsx
* @param {String} firstRef id of the first ref in ...SlotSection.jsx
* @param {String} lastRef id of the last ref in ...SlotSection.jsx
*
*/
_handleSectionFocus(focusPrevProps, firstRef, lastRef) {
if (this.selectedRefId !== null && this.sectionRefArr[this.selectedRefId]) {
// set focus on the previously selected option for the currently open section menu
this.sectionRefArr[this.selectedRefId].focus();
} else if (this.sectionRefArr[firstRef] && this.sectionRefArr[firstRef] != null) {
// set focus on the first option in the currently open section menu if none have been selected previously
this.sectionRefArr[firstRef].focus();
} else if (this.props.currentMenu == null && focusPrevProps.currentMenu == this.sectionName && this.sectionRefArr['ssHeadRef']) {
// set focus on the section menu header when section menu is closed
this.sectionRefArr['ssHeadRef'].focus();
}
}
/** /**
* Open a menu * Open a menu
* @param {string} menu Menu name * @param {string} menu Menu name
@@ -170,6 +228,18 @@ export default class SlotSection extends TranslatedComponent {
targetSlot.priority = targetPriority; targetSlot.priority = targetPriority;
} }
this.props.onChange(); this.props.onChange();
this.props.ship
.updatePowerGenerated()
.updatePowerUsed()
.recalculateMass()
.updateJumpStats()
.recalculateShield()
.recalculateShieldCells()
.recalculateArmour()
.recalculateDps()
.recalculateEps()
.recalculateHps()
.updateMovement();
} }
} }
} }
@@ -225,7 +295,7 @@ export default class SlotSection extends TranslatedComponent {
return ( return (
<div id={this.sectionId} className={'group'} onDragLeave={this._dragOverNone}> <div id={this.sectionId} className={'group'} onDragLeave={this._dragOverNone}>
<div className={cn('section-menu', { selected: sectionMenuOpened })} onClick={open} onContextMenu={ctx}> <div className={cn('section-menu', { selected: sectionMenuOpened })} onClick={open} onContextMenu={ctx}>
<h1>{translate(this.sectionName)} <Equalizer/></h1> <h1 tabIndex="0" onKeyDown={this._keyDown} ref={ssHead => this.sectionRefArr['ssHeadRef'] = ssHead}>{translate(this.sectionName)} <Equalizer/></h1>
{sectionMenuOpened ? this._getSectionMenu(translate, this.props.ship) : null } {sectionMenuOpened ? this._getSectionMenu(translate, this.props.ship) : null }
</div> </div>
{this._getSlots()} {this._getSlots()}

View File

@@ -39,13 +39,16 @@ export default class StandardSlot extends TranslatedComponent {
this.modButton = null; this.modButton = null;
this.slotDiv = null; this.slotDiv = null;
} }
/**
_keyDown(event) { * Handle Enter key
* @param {SyntheticEvent} event KeyDown event
*/
_keyDown(event) {
if (event.key == 'Enter') { if (event.key == 'Enter') {
if(event.target.className == 'r') { if(event.target.className == 'r') {
this._toggleModifications(); this._toggleModifications();
} }
this.props.onOpen(event); this.props.onOpen(event);
} }
} }
@@ -61,6 +64,12 @@ export default class StandardSlot extends TranslatedComponent {
let classRating = m.class + m.rating; let classRating = m.class + m.rating;
let menu; let menu;
let validMods = m == null || !Modifications.modules[m.grp] ? [] : (Modifications.modules[m.grp].modifications || []); let validMods = m == null || !Modifications.modules[m.grp] ? [] : (Modifications.modules[m.grp].modifications || []);
if (m && m.name && m.name === 'Guardian Hybrid Power Plant') {
validMods = [];
}
if (m && m.name && m.name === 'Guardian Power Distributor') {
validMods = [];
}
let showModuleResistances = Persist.showModuleResistances(); let showModuleResistances = Persist.showModuleResistances();
let mass = m.getMass() || m.cargo || m.fuel || 0; let mass = m.getMass() || m.cargo || m.fuel || 0;
@@ -148,7 +157,6 @@ export default class StandardSlot extends TranslatedComponent {
* Toggle the modifications flag when selecting the modifications icon * Toggle the modifications flag when selecting the modifications icon
*/ */
_toggleModifications() { _toggleModifications() {
this._modificationsSelected = !this._modificationsSelected; this._modificationsSelected = !this._modificationsSelected;
} }
} }

View File

@@ -20,12 +20,23 @@ export default class StandardSlotSection extends SlotSection {
super(props, context, 'standard', 'core internal'); super(props, context, 'standard', 'core internal');
this._optimizeStandard = this._optimizeStandard.bind(this); this._optimizeStandard = this._optimizeStandard.bind(this);
this._selectBulkhead = this._selectBulkhead.bind(this); this._selectBulkhead = this._selectBulkhead.bind(this);
this.selectedRefId = null;
this.firstRefId = 'maxjump';
this.lastRefId = 'racer';
}
/**
* Handle focus if the component updates
* @param {Object} prevProps React Component properties
*/
componentDidUpdate(prevProps) {
this._handleSectionFocus(prevProps,this.firstRefId, this.lastRefId);
} }
/** /**
* Use the lightest/optimal available standard modules * Use the lightest/optimal available standard modules
*/ */
_optimizeStandard() { _optimizeStandard() {
this.selectedRefId = 'maxjump';
this.props.ship.useLightestStandard(); this.props.ship.useLightestStandard();
this.props.onChange(); this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity); this.props.onCargoChange(this.props.ship.cargoCapacity);
@@ -39,6 +50,8 @@ export default class StandardSlotSection extends SlotSection {
* @param {integer} bulkheadIndex Bulkhead to use see Constants.BulkheadNames * @param {integer} bulkheadIndex Bulkhead to use see Constants.BulkheadNames
*/ */
_multiPurpose(shielded, bulkheadIndex) { _multiPurpose(shielded, bulkheadIndex) {
this.selectedRefId = 'multipurpose';
if (bulkheadIndex === 2) this.selectedRefId = 'combat';
ShipRoles.multiPurpose(this.props.ship, shielded, bulkheadIndex); ShipRoles.multiPurpose(this.props.ship, shielded, bulkheadIndex);
this.props.onChange(); this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity); this.props.onCargoChange(this.props.ship.cargoCapacity);
@@ -51,6 +64,7 @@ export default class StandardSlotSection extends SlotSection {
* @param {Boolean} shielded True if shield generator should be included * @param {Boolean} shielded True if shield generator should be included
*/ */
_optimizeCargo(shielded) { _optimizeCargo(shielded) {
this.selectedRefId = 'trader';
ShipRoles.trader(this.props.ship, shielded); ShipRoles.trader(this.props.ship, shielded);
this.props.onChange(); this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity); this.props.onCargoChange(this.props.ship.cargoCapacity);
@@ -63,6 +77,7 @@ export default class StandardSlotSection extends SlotSection {
* @param {Boolean} shielded True if shield generator should be included * @param {Boolean} shielded True if shield generator should be included
*/ */
_optimizeMiner(shielded) { _optimizeMiner(shielded) {
this.selectedRefId = 'miner';
ShipRoles.miner(this.props.ship, shielded); ShipRoles.miner(this.props.ship, shielded);
this.props.onChange(); this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity); this.props.onCargoChange(this.props.ship.cargoCapacity);
@@ -75,6 +90,8 @@ export default class StandardSlotSection extends SlotSection {
* @param {Boolean} planetary True if Planetary Vehicle Hangar (PVH) should be included * @param {Boolean} planetary True if Planetary Vehicle Hangar (PVH) should be included
*/ */
_optimizeExplorer(planetary) { _optimizeExplorer(planetary) {
this.selectedRefId = 'explorer';
if (planetary) this.selectedRefId = 'planetary';
ShipRoles.explorer(this.props.ship, planetary); ShipRoles.explorer(this.props.ship, planetary);
this.props.onChange(); this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity); this.props.onCargoChange(this.props.ship.cargoCapacity);
@@ -86,6 +103,7 @@ export default class StandardSlotSection extends SlotSection {
* Racer role * Racer role
*/ */
_optimizeRacer() { _optimizeRacer() {
this.selectedRefId = 'racer';
ShipRoles.racer(this.props.ship); ShipRoles.racer(this.props.ship);
this.props.onChange(); this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity); this.props.onCargoChange(this.props.ship.cargoCapacity);
@@ -229,17 +247,17 @@ export default class StandardSlotSection extends SlotSection {
let planetaryDisabled = this.props.ship.internal.length < 4; let planetaryDisabled = this.props.ship.internal.length < 4;
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}> return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul> <ul>
<li className='lc' onClick={this._optimizeStandard}>{translate('Maximize Jump Range')}</li> <li className='lc' tabIndex="0" onClick={this._optimizeStandard} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['maxjump'] = smRef}>{translate('Maximize Jump Range')}</li>
</ul> </ul>
<div className='select-group cap'>{translate('roles')}</div> <div className='select-group cap'>{translate('roles')}</div>
<ul> <ul>
<li className='lc' onClick={this._multiPurpose.bind(this, false, 0)}>{translate('Multi-purpose')}</li> <li className='lc' tabIndex="0" onClick={this._multiPurpose.bind(this, false, 0)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['multipurpose'] = smRef}>{translate('Multi-purpose')}</li>
<li className='lc' onClick={this._multiPurpose.bind(this, true, 2)}>{translate('Combat')}</li> <li className='lc' tabIndex="0" onClick={this._multiPurpose.bind(this, true, 2)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['combat'] = smRef}>{translate('Combat')}</li>
<li className='lc' onClick={this._optimizeCargo.bind(this, true)}>{translate('Trader')}</li> <li className='lc' tabIndex="0" onClick={this._optimizeCargo.bind(this, true)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['trader'] = smRef}>{translate('Trader')}</li>
<li className='lc' onClick={this._optimizeExplorer.bind(this, false)}>{translate('Explorer')}</li> <li className='lc' tabIndex="0" onClick={this._optimizeExplorer.bind(this, false)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['explorer'] = smRef}>{translate('Explorer')}</li>
<li className={cn('lc', { disabled: planetaryDisabled })} onClick={!planetaryDisabled && this._optimizeExplorer.bind(this, true)}>{translate('Planetary Explorer')}</li> <li className={cn('lc', { disabled: planetaryDisabled })} tabIndex={planetaryDisabled ? '' : '0'} onClick={!planetaryDisabled && this._optimizeExplorer.bind(this, true)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['planetary'] = smRef}>{translate('Planetary Explorer')}</li>
<li className='lc' onClick={this._optimizeMiner.bind(this, true)}>{translate('Miner')}</li> <li className='lc' tabIndex="0" onClick={this._optimizeMiner.bind(this, true)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['miner'] = smRef}>{translate('Miner')}</li>
<li className='lc' onClick={this._optimizeRacer.bind(this)}>{translate('Racer')}</li> <li className='lc' tabIndex="0" onClick={this._optimizeRacer.bind(this)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['racer'] = smRef}>{translate('Racer')}</li>
</ul> </ul>
</div>; </div>;
} }

View File

@@ -228,6 +228,96 @@ export class LinkIcon extends SvgIcon {
} }
} }
/**
* Link / Permalink / Chain
*/
export class OrbisIcon extends SvgIcon {
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return (
<g transform="scale(.037296)">
<path d="m429 319c60.75 0 110 49.248 110 110 0 60.75-49.247 110-110 110s-110-49.247-110-110c0-60.749 49.248-110 110-110m0-20c-34.724 0-67.369 13.522-91.922 38.075s-38.075 57.198-38.075 91.922 13.522 67.369 38.075 91.922c24.553 24.554 57.198 38.075 91.922 38.075s67.369-13.521 91.922-38.075c24.554-24.553 38.075-57.198 38.075-91.922s-13.521-67.369-38.075-91.922c-24.553-24.553-57.198-38.075-91.922-38.075z" />
<path d="m429 235c107.14 0 194 86.855 194 194s-86.855 194-194 194-194-86.855-194-194 86.855-194 194-194m0-20c-28.881 0-56.908 5.661-83.304 16.825-25.485 10.779-48.368 26.207-68.016 45.853-19.646 19.647-35.074 42.53-45.853 68.015-11.163 26.396-16.824 54.423-16.824 83.304s5.661 56.908 16.825 83.304c10.779 25.484 26.207 48.368 45.853 68.016 19.647 19.646 42.53 35.073 68.015 45.854 26.396 11.164 54.423 16.825 83.304 16.825s56.908-5.661 83.304-16.825c25.484-10.779 48.368-26.206 68.016-45.854 19.646-19.646 35.073-42.53 45.854-68.016 11.164-26.396 16.825-54.423 16.825-83.304s-5.661-56.908-16.825-83.304c-10.779-25.485-26.206-48.368-45.854-68.015-19.646-19.646-42.53-35.074-68.016-45.853-26.396-11.164-54.423-16.825-83.304-16.825z" />
<path d="m429 63c202.14 0 366 163.86 366 366s-163.86 366-366 366-366-163.86-366-366 163.86-366 366-366m0-20c-52.101 0-102.65 10.208-150.25 30.342-45.966 19.442-87.244 47.271-122.69 82.714s-63.272 76.721-82.714 122.69c-20.134 47.601-30.342 98.153-30.342 150.25s10.208 102.65 30.342 150.25c19.442 45.967 47.271 87.244 82.714 122.69 35.443 35.442 76.721 63.271 122.69 82.715 47.601 20.133 98.153 30.342 150.25 30.342s102.65-10.209 150.25-30.342c45.967-19.442 87.244-47.271 122.69-82.715 35.441-35.442 63.271-76.721 82.714-122.69 20.133-47.601 30.342-98.153 30.342-150.25s-10.209-102.65-30.342-150.25c-19.442-45.966-47.271-87.244-82.714-122.69s-76.722-63.272-122.69-82.714c-47.601-20.134-98.153-30.342-150.25-30.342z"/>
<path d="m429 63c202.14 0 366 163.86 366 366s-163.86 366-366 366-366-163.86-366-366 163.86-366 366-366m0-20c-52.101 0-102.65 10.208-150.25 30.342-45.966 19.442-87.244 47.271-122.69 82.714s-63.272 76.721-82.714 122.69c-20.134 47.601-30.342 98.153-30.342 150.25s10.208 102.65 30.342 150.25c19.442 45.967 47.271 87.244 82.714 122.69 35.443 35.442 76.721 63.271 122.69 82.715 47.601 20.133 98.153 30.342 150.25 30.342s102.65-10.209 150.25-30.342c45.967-19.442 87.244-47.271 122.69-82.715 35.441-35.442 63.271-76.721 82.714-122.69 20.133-47.601 30.342-98.153 30.342-150.25s-10.209-102.65-30.342-150.25c-19.442-45.966-47.271-87.244-82.714-122.69s-76.722-63.272-122.69-82.714c-47.601-20.134-98.153-30.342-150.25-30.342z" />
<path d="m429 20c225.88 0 409 183.11 409 409s-183.11 409-409 409-409-183.11-409-409 183.11-409 409-409m0-20c-57.905 0-114.09 11.345-166.99 33.721-51.087 21.608-96.963 52.538-136.36 91.93s-70.321 85.269-91.93 136.36c-22.376 52.902-33.721 109.09-33.721 166.99s11.345 114.09 33.721 166.99c21.608 51.087 52.538 96.964 91.93 136.35 39.392 39.392 85.269 70.321 136.36 91.931 52.902 22.375 109.09 33.721 166.99 33.721s114.09-11.346 166.99-33.721c51.087-21.608 96.964-52.538 136.35-91.931 39.392-39.392 70.321-85.269 91.931-136.35 22.375-52.902 33.721-109.09 33.721-166.99s-11.346-114.09-33.721-166.99c-21.608-51.087-52.538-96.963-91.931-136.36-39.392-39.392-85.269-70.321-136.35-91.93-52.902-22.376-109.09-33.721-166.99-33.721z"/>
<path d="m155.34 679.12 173.25-190.21-15.626-13.721-170.9 190.4zm31.01 31.714 202.41-169.1-16.418-14.417-198.76 170.43z"/>
<path d="m702.66 178.87-173.25 190.21 15.625 13.721 170.9-190.4zm-31.01-31.714-202.41 169.1 16.418 14.417 198.76-170.43z" />
<rect transform="matrix(-.7071 -.7071 .7071 -.7071 429.34 1036.2)" x="387.09" y="420.77" width="84.379" height="16.859" />
</g>);
}
}
/**
* Material
*/
export class MatIcon extends SvgIcon {
/**
* Generate the SVG
* @return {React.Component} SVG Contents
*/
svg() {
return<g xmlns="http://www.w3.org/2000/svg">
<path fill="#FF7100" d="M 24.86,4.18
C 24.86,4.18 17.17,7.82 17.17,7.82
17.17,7.82 15.35,14.55 15.35,14.55
15.35,14.55 24.70,9.75 24.70,9.75
24.70,9.75 24.86,4.18 24.86,4.18 Z
M 32.21,17.45
C 32.21,17.45 26.41,11.18 26.41,11.18
26.41,11.18 19.51,11.51 19.51,11.51
19.51,11.51 26.92,19.01 26.92,19.01
26.92,19.01 32.21,17.45 32.21,17.45 Z
M 21.99,28.62
C 21.99,28.62 26.10,21.10 26.10,21.10
26.10,21.10 23.66,14.57 23.66,14.57
23.66,14.57 18.89,24.01 18.89,24.01
18.89,24.01 21.99,28.62 21.99,28.62 Z
M 8.33,22.24
C 8.33,22.24 16.67,23.87 16.67,23.87
16.67,23.87 22.06,19.51 22.06,19.51
22.06,19.51 11.70,17.84 11.70,17.84
11.70,17.84 8.33,22.24 8.33,22.24 Z
M 10.11,7.14
C 10.11,7.14 11.15,15.66 11.15,15.66
11.15,15.66 16.92,19.49 16.92,19.49
16.92,19.49 15.29,9.02 15.29,9.02
15.29,9.02 10.11,7.14 10.11,7.14 Z
M 27.69,2.67
C 27.69,2.67 35.89,16.00 35.89,16.00
35.89,16.00 27.69,29.33 27.69,29.33
27.69,29.33 11.31,29.33 11.31,29.33
11.31,29.33 3.11,16.00 3.11,16.00
3.11,16.00 11.31,2.67 11.31,2.67
11.31,2.67 27.67,2.67 27.67,2.67M 29.16,0.00
C 29.16,0.00 27.69,0.00 27.69,0.00
27.69,0.00 11.31,0.00 11.31,0.00
11.31,0.00 9.84,0.00 9.84,0.00
9.84,0.00 9.06,1.25 9.06,1.25
9.06,1.25 0.87,14.57 0.87,14.57
0.87,14.57 0.00,15.98 0.00,15.98
0.00,15.98 0.87,17.39 0.87,17.39
0.87,17.39 9.06,30.73 9.06,30.73
9.06,30.73 9.84,32.00 9.84,32.00
9.84,32.00 11.31,32.00 11.31,32.00
11.31,32.00 27.69,32.00 27.69,32.00
27.69,32.00 29.16,32.00 29.16,32.00
29.16,32.00 29.94,30.73 29.94,30.73
29.94,30.73 38.13,17.39 38.13,17.39
38.13,17.39 39.00,15.98 39.00,15.98
39.00,15.98 38.13,14.57 38.13,14.57
38.13,14.57 29.94,1.25 29.94,1.25
29.94,1.25 29.16,0.00 29.16,0.00
29.16,0.00 29.16,0.00 29.16,0.00 Z" />
</g>;
}
}
/** /**
* Shopping icon (dollar sign) * Shopping icon (dollar sign)
*/ */

View File

@@ -17,12 +17,23 @@ export default class UtilitySlotSection extends SlotSection {
constructor(props, context) { constructor(props, context) {
super(props, context, 'utility', 'utility mounts'); super(props, context, 'utility', 'utility mounts');
this._empty = this._empty.bind(this); this._empty = this._empty.bind(this);
this.selectedRefId = null;
this.firstRefId = 'emptyall';
this.lastRefId = 'po';
}
/**
* Handle focus if the component updates
* @param {Object} prevProps React Component properties
*/
componentDidUpdate(prevProps) {
this._handleSectionFocus(prevProps,this.firstRefId, this.lastRefId);
} }
/** /**
* Empty all utility slots and close the menu * Empty all utility slots and close the menu
*/ */
_empty() { _empty() {
this.selectedRefId = this.firstRefId;
this.props.ship.emptyUtility(); this.props.ship.emptyUtility();
this.props.onChange(); this.props.onChange();
this._close(); this._close();
@@ -36,6 +47,9 @@ export default class UtilitySlotSection extends SlotSection {
* @param {Synthetic} event Event * @param {Synthetic} event Event
*/ */
_use(group, rating, name, event) { _use(group, rating, name, event) {
this.selectedRefId = group;
if (rating !== null) this.selectedRefId += '-' + rating;
this.props.ship.useUtility(group, rating, name, event.getModifierState('Alt')); this.props.ship.useUtility(group, rating, name, event.getModifierState('Alt'));
this.props.onChange(); this.props.onChange();
this._close(); this._close();
@@ -94,28 +108,28 @@ export default class UtilitySlotSection extends SlotSection {
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}> return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul> <ul>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li> <li className='lc' tabIndex='0' onClick={this._empty} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['emptyall'] = smRef}>{translate('empty all')}</li>
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li> <li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
</ul> </ul>
<div className='select-group cap'>{translate('sb')}</div> <div className='select-group cap'>{translate('sb')}</div>
<ul> <ul>
<li className='c' onClick={_use.bind(this, 'sb', 'A', null)}>A</li> <li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'A', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-A'] = smRef}>A</li>
<li className='c' onClick={_use.bind(this, 'sb', 'B', null)}>B</li> <li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'B', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-B'] = smRef}>B</li>
<li className='c' onClick={_use.bind(this, 'sb', 'C', null)}>C</li> <li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'C', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-C'] = smRef}>C</li>
<li className='c' onClick={_use.bind(this, 'sb', 'D', null)}>D</li> <li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'D', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-D'] = smRef}>D</li>
<li className='c' onClick={_use.bind(this, 'sb', 'E', null)}>E</li> <li className='c' tabIndex='0' onClick={_use.bind(this, 'sb', 'E', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-E'] = smRef}>E</li>
</ul> </ul>
<div className='select-group cap'>{translate('hs')}</div> <div className='select-group cap'>{translate('hs')}</div>
<ul> <ul>
<li className='lc' onClick={_use.bind(this, 'hs', null, 'Heat Sink Launcher')}>{translate('Heat Sink Launcher')}</li> <li className='lc' tabIndex='0' onClick={_use.bind(this, 'hs', null, 'Heat Sink Launcher')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['hs'] = smRef}>{translate('Heat Sink Launcher')}</li>
</ul> </ul>
<div className='select-group cap'>{translate('ch')}</div> <div className='select-group cap'>{translate('ch')}</div>
<ul> <ul>
<li className='lc' onClick={_use.bind(this, 'ch', null, 'Chaff Launcher')}>{translate('Chaff Launcher')}</li> <li className='lc' tabIndex='0' onClick={_use.bind(this, 'ch', null, 'Chaff Launcher')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ch'] = smRef}>{translate('Chaff Launcher')}</li>
</ul> </ul>
<div className='select-group cap'>{translate('po')}</div> <div className='select-group cap'>{translate('po')}</div>
<ul> <ul>
<li className='lc' onClick={_use.bind(this, 'po', null, 'Point Defence')}>{translate('Point Defence')}</li> <li className='lc' tabIndex='0' onClick={_use.bind(this, 'po', null, 'Point Defence')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['po'] = smRef}>{translate('Point Defence')}</li>
</ul> </ul>
</div>; </div>;
} }

View File

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

@@ -74,7 +74,7 @@ export default class WeaponDamageChart extends TranslatedComponent {
* Calculate the maximum range of a ship's weapons * Calculate the maximum range of a ship's weapons
* @param {Object} ship The ship * @param {Object} ship The ship
* @returns {int} The maximum range, in metres * @returns {int} The maximum range, in metres
*/ */
_calcMaxRange(ship) { _calcMaxRange(ship) {
let maxRange = 1000; // Minimum let maxRange = 1000; // Minimum
for (let i = 0; i < ship.hardpoints.length; i++) { for (let i = 0; i < ship.hardpoints.length; i++) {
@@ -184,7 +184,7 @@ export default class WeaponDamageChart extends TranslatedComponent {
const code = `${ship.toString()}:${opponent.toString()}`; const code = `${ship.toString()}:${opponent.toString()}`;
return ( return (
<span> <div>
<LineChart <LineChart
xMax={maxRange} xMax={maxRange}
yMax={this.state.maxDps} yMax={this.state.maxDps}
@@ -198,7 +198,7 @@ export default class WeaponDamageChart extends TranslatedComponent {
points={200} points={200}
code={code} code={code}
/> />
</span> </div>
); );
} }
} }

View File

@@ -60,17 +60,20 @@ export function getLanguage(langCode) {
}, },
translate, translate,
units: { units: {
ang: '°', // Angle
CR: <u>{translate('CR')}</u>, // Credits CR: <u>{translate('CR')}</u>, // Credits
kg: <u>{translate('kg')}</u>, // Kilograms kg: <u>{translate('kg')}</u>, // Kilograms
kgs: <u>{translate('kg/s')}</u>, // Kilograms per second kgs: <u>{translate('kg/s')}</u>, // Kilograms per second
km: <u>{translate('km')}</u>, // Kilometers km: <u>{translate('km')}</u>, // Kilometers
Ls: <u>{translate('Ls')}</u>, // Light Seconds Ls: <u>{translate('Ls')}</u>, // Light Seconds
LY: <u>{translate('LY')}</u>, // Light Years LY: <u>{translate('LY')}</u>, // Light Years
m: <u>{translate('m')}</u>, // Meters
MJ: <u>{translate('MJ')}</u>, // Mega Joules MJ: <u>{translate('MJ')}</u>, // Mega Joules
'm/s': <u>{translate('m/s')}</u>, // Meters per second 'm/s': <u>{translate('m/s')}</u>, // Meters per second
'°/s': <u>{translate('°/s')}</u>, // Degrees per second '°/s': <u>{translate('°/s')}</u>, // Degrees per second
MW: <u>{translate('MW')}</u>, // Mega Watts (same as Mega Joules per second) MW: <u>{translate('MW')}</u>, // Mega Watts (same as Mega Joules per second)
mps: <u>{translate('m/s')}</u>, // Metres per second mps: <u>{translate('m/s')}</u>, // Metres per second
pct: '%', // Percent
ps: <u>{translate('/s')}</u>, // per second ps: <u>{translate('/s')}</u>, // per second
pm: <u>{translate('/min')}</u>, // per minute pm: <u>{translate('/min')}</u>, // per minute
s: <u>{translate('secs')}</u>, // Seconds s: <u>{translate('secs')}</u>, // Seconds

File diff suppressed because one or more lines are too long

View File

@@ -20,11 +20,11 @@
"PHRASE_BLUEPRINT_BEST": "Лучшие основные значения для чертежа", "PHRASE_BLUEPRINT_BEST": "Лучшие основные значения для чертежа",
"PHRASE_BLUEPRINT_EXTREME": "Лучшие положительные и худшие отрицательные основные значения для чертежа", "PHRASE_BLUEPRINT_EXTREME": "Лучшие положительные и худшие отрицательные основные значения для чертежа",
"PHRASE_BLUEPRINT_RESET": "Убрать все изменения и чертёж", "PHRASE_BLUEPRINT_RESET": "Убрать все изменения и чертёж",
"PHRASE_SELECT_SPECIAL": "Нажмите чтобы выбрать экспериментальный эффект", "PHRASE_SELECT_SPECIAL": "Нажмите, чтобы выбрать экспериментальный эффект",
"PHRASE_NO_SPECIAL": "Без экспериментального эффекта", "PHRASE_NO_SPECIAL": "Без экспериментального эффекта",
"PHRASE_SHOPPING_LIST": "Станции что продают эту сборку", "PHRASE_SHOPPING_LIST": "Станции, что продают эту сборку",
"PHRASE_REFIT_SHOPPING_LIST": "Станции что продают необходимые модули", "PHRASE_REFIT_SHOPPING_LIST": "Станции, что продают необходимые модули",
"PHRASE_TOTAL_EFFECTIVE_SHIELD": "Общий урон что может быть нанесён в каждым типе, если используются все щитонакопители", "PHRASE_TOTAL_EFFECTIVE_SHIELD": "Общий урон, что может быть нанесён в каждым типе, если используются все щитонакопители",
"PHRASE_TIME_TO_LOSE_SHIELDS": "Щиты продержатся", "PHRASE_TIME_TO_LOSE_SHIELDS": "Щиты продержатся",
"PHRASE_TIME_TO_RECOVER_SHIELDS": "Щиты восстановятся за", "PHRASE_TIME_TO_RECOVER_SHIELDS": "Щиты восстановятся за",
"PHRASE_TIME_TO_RECHARGE_SHIELDS": "Щиты будут заряжены за", "PHRASE_TIME_TO_RECHARGE_SHIELDS": "Щиты будут заряжены за",
@@ -43,12 +43,12 @@
"PHRASE_TIME_TO_REMOVE_ARMOUR": "Снимет броню за", "PHRASE_TIME_TO_REMOVE_ARMOUR": "Снимет броню за",
"TT_TIME_TO_REMOVE_ARMOUR": "Непрерывным огнём из всех орудий", "TT_TIME_TO_REMOVE_ARMOUR": "Непрерывным огнём из всех орудий",
"PHRASE_TIME_TO_DRAIN_WEP": "Опустошит ОРУЖ за", "PHRASE_TIME_TO_DRAIN_WEP": "Опустошит ОРУЖ за",
"TT_TIME_TO_DRAIN_WEP": "Время за которое опустошится аккумулятор ОРУЖ при стрельбе из всех орудий", "TT_TIME_TO_DRAIN_WEP": "Время, за которое опустошится аккумулятор ОРУЖ при стрельбе из всех орудий",
"TT_TIME_TO_LOSE_SHIELDS": "Против поддерживаемой стрельбы из всех орудий противника", "TT_TIME_TO_LOSE_SHIELDS": "Против поддерживаемой стрельбы из всех орудий противника",
"TT_TIME_TO_LOSE_ARMOUR": "Против поддерживаемой стрельбы из всех орудий противника", "TT_TIME_TO_LOSE_ARMOUR": "Против поддерживаемой стрельбы из всех орудий противника",
"TT_MODULE_ARMOUR": "Броня защищаюшае модули от урона", "TT_MODULE_ARMOUR": "Броня, защищающая модули от урона",
"TT_MODULE_PROTECTION_EXTERNAL": "Процент урона перенаправленного от гнёзд на наборы для усиления модулей", "TT_MODULE_PROTECTION_EXTERNAL": "Процент урона, перенаправленного от гнёзд на наборы для усиления модулей",
"TT_MODULE_PROTECTION_INTERNAL": "Процент урона перенаправленного от модулей вне гнёзд на наборы для усиления модулей", "TT_MODULE_PROTECTION_INTERNAL": "Процент урона, перенаправленного от модулей вне гнёзд на наборы для усиления модулей",
"TT_EFFECTIVE_SDPS_SHIELDS": "Реальный поддерживаемый ДПС пока аккумулятор ОРУЖ не пуст", "TT_EFFECTIVE_SDPS_SHIELDS": "Реальный поддерживаемый ДПС пока аккумулятор ОРУЖ не пуст",
"TT_EFFECTIVENESS_SHIELDS": "Эффективность в сравнении с попаданием по цели с 0-сопротивляемостью без пунктов в СИС на 0 метрах", "TT_EFFECTIVENESS_SHIELDS": "Эффективность в сравнении с попаданием по цели с 0-сопротивляемостью без пунктов в СИС на 0 метрах",
"TT_EFFECTIVE_SDPS_ARMOUR": "Реальный поддерживаемый ДПС пока аккумулятор ОРУЖ не пуст", "TT_EFFECTIVE_SDPS_ARMOUR": "Реальный поддерживаемый ДПС пока аккумулятор ОРУЖ не пуст",
@@ -56,7 +56,7 @@
"PHRASE_EFFECTIVE_SDPS_SHIELDS": "ПДПС против щитов", "PHRASE_EFFECTIVE_SDPS_SHIELDS": "ПДПС против щитов",
"PHRASE_EFFECTIVE_SDPS_ARMOUR": "ПДПС против брони", "PHRASE_EFFECTIVE_SDPS_ARMOUR": "ПДПС против брони",
"TT_SUMMARY_SPEED": "С полным топливным баком и 4 пунктами в ДВИ", "TT_SUMMARY_SPEED": "С полным топливным баком и 4 пунктами в ДВИ",
"TT_SUMMARY_SPEED_NONFUNCTIONAL": "маневровые двигатели выключены или превышена максимальная масса с топливом и грузом", "TT_SUMMARY_SPEED_NONFUNCTIONAL": "Маневровые двигатели выключены или превышена максимальная масса с топливом и грузом",
"TT_SUMMARY_BOOST": "С полным топливным баком и 4 пунктами в ДВИ", "TT_SUMMARY_BOOST": "С полным топливным баком и 4 пунктами в ДВИ",
"TT_SUMMARY_BOOST_NONFUNCTIONAL": "Распределитель питания не может обеспечить достаточно энергии для форсажа", "TT_SUMMARY_BOOST_NONFUNCTIONAL": "Распределитель питания не может обеспечить достаточно энергии для форсажа",
"TT_SUMMARY_SHIELDS": "Чистая сила щита, включая усилители", "TT_SUMMARY_SHIELDS": "Чистая сила щита, включая усилители",
@@ -68,16 +68,16 @@
"TT_SUMMARY_DPS": "Урон в секунду при стрельбе из всех орудий", "TT_SUMMARY_DPS": "Урон в секунду при стрельбе из всех орудий",
"TT_SUMMARY_EPS": "Расход аккумулятора ОРУЖ в секунду при стрельбе из всех орудий", "TT_SUMMARY_EPS": "Расход аккумулятора ОРУЖ в секунду при стрельбе из всех орудий",
"TT_SUMMARY_TTD": "Время расхода аккумулятора ОРУЖ при стрельбе из всех орудий и с 4 пунктами в ОРУЖ", "TT_SUMMARY_TTD": "Время расхода аккумулятора ОРУЖ при стрельбе из всех орудий и с 4 пунктами в ОРУЖ",
"TT_SUMMARY_MAX_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с топливом достаточным только на сам прыжок", "TT_SUMMARY_MAX_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с топливом, достаточным только на сам прыжок",
"TT_SUMMARY_UNLADEN_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с полным топливным баком", "TT_SUMMARY_UNLADEN_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с полным топливным баком",
"TT_SUMMARY_LADEN_SINGLE_JUMP": "Самый дальний возможный прыжок с полным грузовым отсеком и с полным топливным баком", "TT_SUMMARY_LADEN_SINGLE_JUMP": "Самый дальний возможный прыжок с полным грузовым отсеком и с полным топливным баком",
"TT_SUMMARY_UNLADEN_TOTAL_JUMP": "Самая дальняя общая дистанция без груза, с полным топливным баком и при прыжках на максимальное расстояние", "TT_SUMMARY_UNLADEN_TOTAL_JUMP": "Самая дальняя общая дистанция без груза, с полным топливным баком и при прыжках на максимальное расстояние",
"TT_SUMMARY_LADEN_TOTAL_JUMP": "Самая дальняя общая дистанция с полным грузовым отсеком, с полным топливным баком и при прыжках на максимальное расстояние", "TT_SUMMARY_LADEN_TOTAL_JUMP": "Самая дальняя общая дистанция с полным грузовым отсеком, с полным топливным баком и при прыжках на максимальное расстояние",
"HELP_MODIFICATIONS_MENU": "Ткните на номер чтобы ввести новое значение, или потяните вдоль полосы для малых изменений", "HELP_MODIFICATIONS_MENU": "Нажмите на номер, чтобы ввести новое значение, или потяните вдоль полосы для малых изменений",
"am": "Блок Автом. Полевого Ремонта", "am": "Блок Автом. Полевого Ремонта",
"bh": "Переборки", "bh": "Переборки",
"bl": "Пучковый Лазер", "bl": "Пучковый лазер",
"bsg": "Двухпоточный Щитогенератор", "bsg": "Двухпоточный щитогенератор",
"c": "Орудие", "c": "Орудие",
"cc": "Контроллер магнитного снаряда для сбора", "cc": "Контроллер магнитного снаряда для сбора",
"ch": "Разбрасыватель дипольных отражателей", "ch": "Разбрасыватель дипольных отражателей",
@@ -89,7 +89,7 @@
"fh": "Ангар для истребителя", "fh": "Ангар для истребителя",
"fi": "FSD-перехватчик", "fi": "FSD-перехватчик",
"fs": "Топливозаборник", "fs": "Топливозаборник",
"fsd": "Рамочно Сместительный двигатель", "fsd": "Рамочно-сместительный двигатель",
"ft": "Топливный бак", "ft": "Топливный бак",
"fx": "Контроллер магнитного снаряда для топлива", "fx": "Контроллер магнитного снаряда для топлива",
"hb": "Контроллер магнитного снаряда для взлома трюма", "hb": "Контроллер магнитного снаряда для взлома трюма",
@@ -110,7 +110,7 @@
"pcm": "Каюта пассажира первого класса", "pcm": "Каюта пассажира первого класса",
"pcq": "Каюта пассажира класса люкс", "pcq": "Каюта пассажира класса люкс",
"pd": "Распределитель питания", "pd": "Распределитель питания",
"pl": пмульсный лазер", "pl": "Импульсный лазер",
"po": "Точечная оборона", "po": "Точечная оборона",
"pp": "Силовая установка", "pp": "Силовая установка",
"psg": "Призматический щитогенератор", "psg": "Призматический щитогенератор",
@@ -122,7 +122,7 @@
"sc": "Сканер обнаружения", "sc": "Сканер обнаружения",
"scb": "Щитонакопитель", "scb": "Щитонакопитель",
"sg": "Щитогенератор", "sg": "Щитогенератор",
"ss": "Сканер Поверхностей", "ss": "Сканер поверхностей",
"t": "Маневровые двигатели", "t": "Маневровые двигатели",
"tp": "Торпедная стойка", "tp": "Торпедная стойка",
"ul": "Пульсирующие лазеры", "ul": "Пульсирующие лазеры",
@@ -130,7 +130,7 @@
"emptyrestricted": "пусто (ограниченно)", "emptyrestricted": "пусто (ограниченно)",
"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": "Общие эффективные щиты",
"ammunition": "Припасы", "ammunition": "Припасы",
@@ -149,7 +149,7 @@
"eps": "Энергия в секунду", "eps": "Энергия в секунду",
"epsseps": "Энергия в секунду (поддерживаемая энергия в секунду)", "epsseps": "Энергия в секунду (поддерживаемая энергия в секунду)",
"hps": "Нагрев в секунду", "hps": "Нагрев в секунду",
"hpsshps": "Heat per second (sustained heat per second)", "hpsshps": "Нагрев в секунду (поддерживаемый нагрев в секунду)",
"damage by": "Урон", "damage by": "Урон",
"damage from": "Урон от", "damage from": "Урон от",
"shield cells": "Щитонакопители", "shield cells": "Щитонакопители",
@@ -183,7 +183,7 @@
"hullreinforcement": "Укрепление корпуса", "hullreinforcement": "Укрепление корпуса",
"integrity": "Целостность", "integrity": "Целостность",
"jitter": "Дрожание", "jitter": "Дрожание",
"kinres": "Сопротивление китетическому урону", "kinres": "Сопротивление кинетическому урону",
"maxfuel": "Макс. топлива на прыжок", "maxfuel": "Макс. топлива на прыжок",
"mass": "Масса", "mass": "Масса",
"optmass": "Оптимизированная масса", "optmass": "Оптимизированная масса",
@@ -381,4 +381,4 @@
"URL": "Ссылка", "URL": "Ссылка",
"WEP": "ОРУЖ", "WEP": "ОРУЖ",
"yes": "Да" "yes": "Да"
} }

View File

@@ -44,6 +44,12 @@ export default class AboutPage extends Page {
<h3>Supporting Coriolis</h3> <h3>Supporting Coriolis</h3>
<p>Coriolis is an open source project, and I work on it in my free time. I have set up a patreon at <a href='https://www.patreon.com/coriolis_elite'>patreon.com/coriolis_elite</a>, which will be used to keep Coriolis up to date and the servers running.</p> <p>Coriolis is an open source project, and I work on it in my free time. I have set up a patreon at <a href='https://www.patreon.com/coriolis_elite'>patreon.com/coriolis_elite</a>, which will be used to keep Coriolis up to date and the servers running.</p>
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
<input type="hidden" name="cmd" value="_s-xclick"/>
<input type="hidden" name="hosted_button_id" value="SJBKT2SWEEU68" />
<input type="image" src="https://www.paypalobjects.com/en_AU/i/btn/btn_donate_SM.gif" border="0" name="submit" alt="PayPal The safer, easier way to pay online!" />
<img alt="" border="0" src="https://www.paypalobjects.com/en_AU/i/scr/pixel.gif" width="1" height="1" />
</form>
</div>; </div>;
} }
} }

View File

@@ -7,9 +7,20 @@ import Router from '../Router';
import Persist from '../stores/Persist'; import Persist from '../stores/Persist';
import * as Utils from '../utils/UtilityFunctions'; import * as Utils from '../utils/UtilityFunctions';
import Ship from '../shipyard/Ship'; import Ship from '../shipyard/Ship';
import * as _ from 'lodash';
import { toDetailedBuild } from '../shipyard/Serializer'; import { toDetailedBuild } from '../shipyard/Serializer';
import { outfitURL } from '../utils/UrlGenerators'; import { outfitURL } from '../utils/UrlGenerators';
import { FloppyDisk, Bin, Switch, Download, Reload, LinkIcon, ShoppingIcon } from '../components/SvgIcons'; import {
FloppyDisk,
Bin,
Switch,
Download,
Reload,
LinkIcon,
ShoppingIcon,
MatIcon,
OrbisIcon
} from '../components/SvgIcons';
import LZString from 'lz-string'; import LZString from 'lz-string';
import ShipSummaryTable from '../components/ShipSummaryTable'; import ShipSummaryTable from '../components/ShipSummaryTable';
import StandardSlotSection from '../components/StandardSlotSection'; import StandardSlotSection from '../components/StandardSlotSection';
@@ -25,6 +36,8 @@ import EngagementRange from '../components/EngagementRange';
import OutfittingSubpages from '../components/OutfittingSubpages'; import OutfittingSubpages from '../components/OutfittingSubpages';
import ModalExport from '../components/ModalExport'; import ModalExport from '../components/ModalExport';
import ModalPermalink from '../components/ModalPermalink'; import ModalPermalink from '../components/ModalPermalink';
import ModalShoppingList from '../components/ModalShoppingList';
import ModalOrbis from '../components/ModalOrbis';
/** /**
* Document Title Generator * Document Title Generator
@@ -58,6 +71,7 @@ export default class OutfittingPage extends Page {
this._fuelUpdated = this._fuelUpdated.bind(this); this._fuelUpdated = this._fuelUpdated.bind(this);
this._opponentUpdated = this._opponentUpdated.bind(this); this._opponentUpdated = this._opponentUpdated.bind(this);
this._engagementRangeUpdated = this._engagementRangeUpdated.bind(this); this._engagementRangeUpdated = this._engagementRangeUpdated.bind(this);
this._sectionMenuRefs = {};
} }
/** /**
@@ -491,6 +505,23 @@ export default class OutfittingPage extends Page {
this.context.showModal(<ModalPermalink url={window.location.href}/>); this.context.showModal(<ModalPermalink url={window.location.href}/>);
} }
/**
* Generate Orbis link
*/
_genOrbis() {
const data = {};
const ship = this.state.ship;
ship.coriolisId = ship.id;
data.coriolisShip = ship;
data.url = window.location.href;
data.title = this.state.buildName || ship.id;
data.description = this.state.buildName || ship.id;
data.ShipName = ship.id;
data.Ship = ship.id;
console.log(data);
this.context.showModal(<ModalOrbis ship={data}/>);
}
/** /**
* Open up a window for EDDB with a shopping list of our components * Open up a window for EDDB with a shopping list of our components
*/ */
@@ -505,6 +536,13 @@ export default class OutfittingPage extends Page {
window.open('https://eddb.io/station?s=' + shipId + '&m=' + modIds.join(',')); window.open('https://eddb.io/station?s=' + shipId + '&m=' + modIds.join(','));
} }
/**
* Generates the shopping list
*/
_genShoppingList() {
this.context.showModal(<ModalShoppingList ship={this.state.ship}/>);
}
/** /**
* Handle Key Down * Handle Key Down
* @param {Event} e Keyboard Event * @param {Event} e Keyboard Event
@@ -554,17 +592,26 @@ export default class OutfittingPage extends Page {
const shipSummaryMarker = `${ship.name}${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`; const shipSummaryMarker = `${ship.name}${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`;
const requirements = Ships[ship.id].requirements; const requirements = Ships[ship.id].requirements;
var requirementElements = []; let requirementElements = [];
/**
* Render the requirements for a ship / etc
* @param {string} className Class names
* @param {string} textKey The key for translating
* @param {String} tooltipTextKey Tooltip key
*/
function renderRequirement(className, textKey, tooltipTextKey) { function renderRequirement(className, textKey, tooltipTextKey) {
requirementElements.push(<div key={textKey} className={className} onMouseEnter={termtip.bind(null, tooltipTextKey)} onMouseLeave={hide}>{translate(textKey)}</div>); if (textKey.startsWith('empire') || textKey.startsWith('federation')) {
requirementElements.push(<div key={textKey} className={className} onMouseEnter={termtip.bind(null, tooltipTextKey)} onMouseLeave={hide}><a href={textKey.startsWith('empire') ? 'http://elite-dangerous.wikia.com/wiki/Empire/Ranks' : 'http://elite-dangerous.wikia.com/wiki/Federation/Ranks'} target="_blank" rel="noopener">{translate(textKey)}</a></div>);
} else {
requirementElements.push(<div key={textKey} className={className} onMouseEnter={termtip.bind(null, tooltipTextKey)} onMouseLeave={hide}>{translate(textKey)}</div>);
}
} }
if (requirements) { if (requirements) {
requirements.federationRank && renderRequirement('federation', 'federation rank ' + requirements.federationRank, 'federation rank required'); requirements.federationRank && renderRequirement('federation', 'federation rank ' + requirements.federationRank, 'federation rank required');
requirements.empireRank && renderRequirement('empire', 'empire rank ' + requirements.empireRank, 'empire rank required'); requirements.empireRank && renderRequirement('empire', 'empire rank ' + requirements.empireRank, 'empire rank required');
requirements.horizons && renderRequirement('horizons', 'horizons', 'horizons required'); requirements.horizons && renderRequirement('horizons', 'horizons', 'horizons required');
requirements.horizonsEarlyAdoption && renderRequirement('horizons', 'horizons early adoption', 'horizons early adoption required'); requirements.horizonsEarlyAdoption && renderRequirement('horizons', 'horizons early adoption', 'horizons early adoption required');
} }
return ( return (
@@ -598,15 +645,21 @@ export default class OutfittingPage extends Page {
<button onClick={this._genShortlink} onMouseOver={termtip.bind(null, 'shortlink')} onMouseOut={hide}> <button onClick={this._genShortlink} onMouseOver={termtip.bind(null, 'shortlink')} onMouseOut={hide}>
<LinkIcon className='lg' /> <LinkIcon className='lg' />
</button> </button>
<button onClick={this._genOrbis} onMouseOver={termtip.bind(null, 'PHASE_UPLOAD_ORBIS')} onMouseOut={hide}>
<OrbisIcon className='lg' />
</button>
<button onClick={this._genShoppingList} onMouseOver={termtip.bind(null, 'PHRASE_SHOPPING_MATS')} onMouseOut={hide}>
<MatIcon className='lg' />
</button>
</div> </div>
</div> </div>
{/* Main tables */} {/* Main tables */}
<ShipSummaryTable ship={ship} fuel={fuel} cargo={cargo} marker={shipSummaryMarker} pips={{sys: this.state.sys, wep: this.state.wep, eng: this.state.eng}} /> <ShipSummaryTable ship={ship} fuel={fuel} cargo={cargo} marker={shipSummaryMarker} pips={{ sys: this.state.sys, wep: this.state.wep, eng: this.state.eng }} />
<StandardSlotSection ship={ship} fuel={fuel} cargo={cargo} code={standardSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} /> <StandardSlotSection ship={ship} fuel={fuel} cargo={cargo} code={standardSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} sectionMenuRefs={this._sectionMenuRefs}/>
<InternalSlotSection ship={ship} code={internalSlotMarker} 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} sectionMenuRefs={this._sectionMenuRefs}/>
<HardpointSlotSection ship={ship} code={hardpointsSlotMarker} 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} sectionMenuRefs={this._sectionMenuRefs}/>
<UtilitySlotSection 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} sectionMenuRefs={this._sectionMenuRefs}/>
{/* Control of ship and opponent */} {/* Control of ship and opponent */}
<div className='group quarter'> <div className='group quarter'>

View File

@@ -274,11 +274,11 @@ export default class ShipyardPage extends Page {
for (let s of shipSummaries) { for (let s of shipSummaries) {
let shipSortValue = s[shipPredicate]; let shipSortValue = s[shipPredicate];
if( shipPredicateIndex != undefined ) { if(shipPredicateIndex != undefined) {
shipSortValue = shipSortValue[shipPredicateIndex]; shipSortValue = shipSortValue[shipPredicateIndex];
} }
if( shipSortValue != lastShipSortValue ) { if(shipSortValue != lastShipSortValue) {
backgroundHighlight = !backgroundHighlight; backgroundHighlight = !backgroundHighlight;
lastShipSortValue = shipSortValue; lastShipSortValue = shipSortValue;
} }
@@ -396,4 +396,4 @@ export default class ShipyardPage extends Page {
</div> </div>
); );
} }
} }

View File

@@ -7,11 +7,20 @@ import Module from './Module';
* @param {object} fsd The FDS object/component with maxfuel, fuelmul, fuelpower, optmass * @param {object} fsd The FDS object/component with maxfuel, fuelmul, fuelpower, optmass
* @param {number} fuel Optional - The fuel consumed during the jump * @param {number} fuel Optional - The fuel consumed during the jump
* @return {number} Distance in Light Years * @return {number} Distance in Light Years
* @param {object} ship Ship instance
*/ */
export function jumpRange(mass, fsd, fuel) { export function jumpRange(mass, fsd, fuel, ship) {
const fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel; const fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
const fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass; 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; let jumpAddition = 0;
if (ship) {
for (const module of ship.internal) {
if (module && module.m && module.m.grp === 'gfsb') {
jumpAddition += module.m.getJumpBoost();
}
}
}
return (Math.pow(Math.min(fuel === undefined ? fsdMaxFuelPerJump : fuel, fsdMaxFuelPerJump) / fsd.fuelmul, 1 / fsd.fuelpower) * fsdOptimalMass / mass) + jumpAddition;
} }
/** /**
@@ -21,8 +30,9 @@ export function jumpRange(mass, fsd, fuel) {
* @param {object} fsd The FDS object/component with maxfuel, fuelmul, fuelpower, optmass * @param {object} fsd The FDS object/component with maxfuel, fuelmul, fuelpower, optmass
* @param {number} fuel The total fuel available * @param {number} fuel The total fuel available
* @return {number} Distance in Light Years * @return {number} Distance in Light Years
* @param {object} ship Ship instance
*/ */
export function totalJumpRange(mass, fsd, fuel) { export function totalJumpRange(mass, fsd, fuel, ship) {
const fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel; const fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
const fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass; const fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass;
@@ -30,7 +40,7 @@ export function totalJumpRange(mass, fsd, fuel) {
let totalRange = 0; let totalRange = 0;
while (fuelRemaining > 0) { while (fuelRemaining > 0) {
const fuelForThisJump = Math.min(fuelRemaining, fsdMaxFuelPerJump); const fuelForThisJump = Math.min(fuelRemaining, fsdMaxFuelPerJump);
totalRange += this.jumpRange(mass, fsd, fuelForThisJump); totalRange += this.jumpRange(mass, fsd, fuelForThisJump, ship);
// Mass is reduced // Mass is reduced
mass -= fuelForThisJump; mass -= fuelForThisJump;
fuelRemaining -= fuelForThisJump; fuelRemaining -= fuelForThisJump;
@@ -45,9 +55,9 @@ export function totalJumpRange(mass, fsd, fuel) {
* @param {number} baseShield Base Shield strength MJ for ship * @param {number} baseShield Base Shield strength MJ for ship
* @param {object} sg The shield generator used * @param {object} sg The shield generator used
* @param {number} multiplier Shield multiplier for ship (1 + shield boosters if any) * @param {number} multiplier Shield multiplier for ship (1 + shield boosters if any)
* @return {number} Approximate shield strengh in MJ * @return {number} Approximate shield strengh in MJ
*/ */
export function shieldStrength(mass, baseShield, sg, multiplier, ship) { export function shieldStrength(mass, baseShield, sg, multiplier) {
// sg might be a module or a template; handle either here // sg might be a module or a template; handle either here
let minMass = sg instanceof Module ? sg.getMinMass() : sg.minmass; let minMass = sg instanceof Module ? sg.getMinMass() : sg.minmass;
let optMass = sg instanceof Module ? sg.getOptMass() : sg.optmass; let optMass = sg instanceof Module ? sg.getOptMass() : sg.optmass;
@@ -55,23 +65,12 @@ export function shieldStrength(mass, baseShield, sg, multiplier, ship) {
let minMul = sg instanceof Module ? sg.getMinMul() : sg.minmul; let minMul = sg instanceof Module ? sg.getMinMul() : sg.minmul;
let optMul = sg instanceof Module ? sg.getOptMul() : sg.optmul; let optMul = sg instanceof Module ? sg.getOptMul() : sg.optmul;
let maxMul = sg instanceof Module ? sg.getMaxMul() : sg.maxmul; let maxMul = sg instanceof Module ? sg.getMaxMul() : sg.maxmul;
if (ship) {
for (const i of ship.hardpoints) {
if (!i.maxClass) {
if (i.grp === 'sb' || (i.m && i.m.grp === 'sb')) {
if (!isNaN(i.m.getModValue('optmul'))) {
optMul += i.m.getModValue('optmul') / 10000;
}
}
}
}
}
let xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass)); let xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass));
let exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass))); let exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass)));
let ynorm = Math.pow(xnorm, exponent); let ynorm = Math.pow(xnorm, exponent);
let mul = minMul + ynorm * (maxMul - minMul); let mul = minMul + ynorm * (maxMul - minMul);
return baseShield * mul * multiplier; return (baseShield * mul * multiplier);
} }
/** /**
@@ -97,6 +96,16 @@ export function speed(mass, baseSpeed, thrusters, engpip) {
return results; return results;
} }
/**
* Calculate pip multiplier for speed.
* @param {number} baseSpeed The base speed of ship in data
* @param {number} topSpeed The top speed of ship in data
* @return {number} The multiplier that pips affect speed.
*/
export function calcPipSpeed(baseSpeed, topSpeed) {
return (topSpeed - baseSpeed) / (4 * topSpeed);
}
/** /**
* Calculate pitch of a ship based on mass and thrusters * Calculate pitch of a ship based on mass and thrusters
* @param {number} mass the mass of the ship * @param {number} mass the mass of the ship
@@ -212,7 +221,7 @@ function calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, base
* Calculate speed for a given setup * Calculate speed for a given setup
* @param {number} mass the mass of the ship * @param {number} mass the mass of the ship
* @param {number} baseSpeed the base speed of the ship * @param {number} baseSpeed the base speed of the ship
* @param {ojbect} thrusters the thrusters of the ship * @param {object} thrusters the thrusters of the ship
* @param {number} engpip the multiplier per pip to engines * @param {number} engpip the multiplier per pip to engines
* @param {number} eng the pips to engines * @param {number} eng the pips to engines
* @param {number} boostFactor the boost factor for ths ship * @param {number} boostFactor the boost factor for ths ship
@@ -240,7 +249,7 @@ export function calcSpeed(mass, baseSpeed, thrusters, engpip, eng, boostFactor,
* Calculate pitch for a given setup * Calculate pitch for a given setup
* @param {number} mass the mass of the ship * @param {number} mass the mass of the ship
* @param {number} basePitch the base pitch of the ship * @param {number} basePitch the base pitch of the ship
* @param {ojbect} thrusters the thrusters of the ship * @param {object} thrusters the thrusters of the ship
* @param {number} engpip the multiplier per pip to engines * @param {number} engpip the multiplier per pip to engines
* @param {number} eng the pips to engines * @param {number} eng the pips to engines
* @param {number} boostFactor the boost factor for ths ship * @param {number} boostFactor the boost factor for ths ship
@@ -331,41 +340,76 @@ export function shieldMetrics(ship, sys) {
const maxSysResistance = this.sysResistance(4); const maxSysResistance = this.sysResistance(4);
let shield = {}; let shield = {};
const dimReturnLine = (res) => 1 - (1 - res) * 0.7;
const shieldGeneratorSlot = ship.findInternalByGroup('sg'); const shieldGeneratorSlot = ship.findInternalByGroup('sg');
if (shieldGeneratorSlot && shieldGeneratorSlot.enabled && shieldGeneratorSlot.m) { if (shieldGeneratorSlot && shieldGeneratorSlot.enabled && shieldGeneratorSlot.m) {
const shieldGenerator = shieldGeneratorSlot.m; const shieldGenerator = shieldGeneratorSlot.m;
let res = {
kin: shieldGenerator.kinres,
therm: shieldGenerator.thermres,
expl: shieldGenerator.explres
};
// Boosters // Boosters
let boost = 1; let boost = 1;
let boosterExplDmg = 1; let boosterExplDmg = 1;
let boosterKinDmg = 1; let boosterKinDmg = 1;
let boosterThermDmg = 1; let boosterThermDmg = 1;
// const explDim = dimReturnLine(shieldGenerator.explres);
// const thermDim = dimReturnLine(shieldGenerator.thermres);
// const kinDim = dimReturnLine(shieldGenerator.kinres);
for (let slot of ship.hardpoints) { for (let slot of ship.hardpoints) {
if (slot.enabled && slot.m && slot.m.grp == 'sb') { if (slot.enabled && slot.m && slot.m.grp == 'sb') {
boost += slot.m.getShieldBoost(); boost += slot.m.getShieldBoost();
res.expl += slot.m.getExplosiveResistance();
res.kin += slot.m.getKineticResistance();
res.therm += slot.m.getThermalResistance();
boosterExplDmg = boosterExplDmg * (1 - slot.m.getExplosiveResistance()); boosterExplDmg = boosterExplDmg * (1 - slot.m.getExplosiveResistance());
boosterKinDmg = boosterKinDmg * (1 - slot.m.getKineticResistance()); boosterKinDmg = boosterKinDmg * (1 - slot.m.getKineticResistance());
boosterThermDmg = boosterThermDmg * (1 - slot.m.getThermalResistance()); boosterThermDmg = boosterThermDmg * (1 - slot.m.getThermalResistance());
} }
} if (slot.m && slot.m.grp == 'gsrp') {
}
}
// Calculate diminishing returns for boosters // Calculate diminishing returns for boosters
// Diminishing returns not currently in-game // Diminishing returns not currently in-game
// boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5); // boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5);
// Remove base shield generator strength // Remove base shield generator strength
boost -= 1; 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, ship); // if (res.expl > explDim) {
// const overage = (res.expl - explDim) * 0.5;
// res.expl = explDim + overage;
// boosterExplDmg = explDim + overage;
// }
//
// if (res.therm > thermDim) {
// const overage = (res.therm - thermDim) * 0.5;
// res.therm = thermDim + overage;
// boosterThermDmg = thermDim + overage;
// }
//
// if (res.kin > kinDim) {
// const overage = (res.kin - kinDim) * 0.5;
// res.kin = kinDim + overage;
// boosterKinDmg = kinDim + overage;
// }
let shieldAddition = 0;
if (ship) {
for (const module of ship.internal) {
if (module && module.m && module.m.grp === 'gsrp') {
shieldAddition += module.m.getShieldAddition();
}
}
}
let generatorStrength = this.shieldStrength(ship.hullMass, ship.baseShieldStrength, shieldGenerator, 1);
const boostersStrength = generatorStrength * boost; 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 // 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 shieldToRecover = (generatorStrength + boostersStrength + shieldAddition) / 2;
const powerDistributor = ship.standard[4].m; const powerDistributor = ship.standard[4].m;
const sysRechargeRate = this.sysRechargeRate(powerDistributor, sys); const sysRechargeRate = this.sysRechargeRate(powerDistributor, sys);
@@ -384,7 +428,7 @@ export function shieldMetrics(ship, sys) {
const remainingShieldToRecover = shieldToRecover - capacitorLifetime * shieldGenerator.getBrokenRegenerationRate(); const remainingShieldToRecover = shieldToRecover - capacitorLifetime * shieldGenerator.getBrokenRegenerationRate();
if (sys === 0) { if (sys === 0) {
// No system pips so will never recover shields // No system pips so will never recover shields
recover = Math.Inf; recover = Math.Infinity;
} else { } else {
// Recover remaining shields at the rate of the power distributor's recharge // Recover remaining shields at the rate of the power distributor's recharge
recover += remainingShieldToRecover / (sysRechargeRate / 0.6); recover += remainingShieldToRecover / (sysRechargeRate / 0.6);
@@ -392,7 +436,7 @@ export function shieldMetrics(ship, sys) {
} }
// Recharge time is the time taken to go from 50% to 100% // Recharge time is the time taken to go from 50% to 100%
const shieldToRecharge = (generatorStrength + boostersStrength) / 2; const shieldToRecharge = (generatorStrength + boostersStrength + shieldAddition) / 2;
// Our initial regeneration comes from the SYS capacitor store, which is replenished as it goes // 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 // 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration
@@ -419,8 +463,9 @@ export function shieldMetrics(ship, sys) {
shield = { shield = {
generator: generatorStrength, generator: generatorStrength,
boosters: boostersStrength, boosters: boostersStrength,
addition: shieldAddition,
cells: ship.shieldCells, cells: ship.shieldCells,
total: generatorStrength + boostersStrength + ship.shieldCells, total: generatorStrength + boostersStrength + ship.shieldCells + shieldAddition,
recover, recover,
recharge, recharge,
}; };
@@ -435,34 +480,74 @@ export function shieldMetrics(ship, sys) {
max: 1 - maxSysResistance max: 1 - maxSysResistance
}; };
/**
* An object that stores a selection of difference damage multipliers that
* deal with a ship's shield strength.
* @typedef {Object} ShieldDamageMults
* @property {number} generator Base damage multiplier of the shield
* contributing it's base resistance.
* @property {number} boosters Damage multiplier contributed by all
* boosters, i.e. `rawMj / (generator * boosters)` equals shield strength
* with 0 pips to sys.
* @property {number} sys Damage multiplier contributed by pips to sys.
* @property {number} base Damage multiplier with 0 pips to sys; just
* boosters and shield generator. Equals `generator * boosters`.
* @property {number} total Damage multiplier with current pip settings.
* @property {number} max Damage multiplier with 4 pips to sys.
*/
let sgExplosiveDmg = 1 - shieldGenerator.getExplosiveResistance();
let sgSbExplosiveDmg = diminishDamageMult(sgExplosiveDmg * 0.7, (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg);
/** @type {ShieldDamageMults} */
shield.explosive = { shield.explosive = {
generator: 1 - shieldGenerator.getExplosiveResistance(), generator: sgExplosiveDmg,
boosters: boosterExplDmg, boosters: sgSbExplosiveDmg / sgExplosiveDmg,
sys: (1 - sysResistance), sys: (1 - sysResistance),
total: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - sysResistance), base: sgSbExplosiveDmg,
max: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - maxSysResistance) total: sgSbExplosiveDmg * (1 - sysResistance),
max: sgSbExplosiveDmg * (1 - maxSysResistance),
}; };
let sgKineticDmg = 1 - shieldGenerator.getKineticResistance();
let sgSbKineticDmg = diminishDamageMult(sgKineticDmg * 0.7, (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg);
/** @type {ShieldDamageMults} */
shield.kinetic = { shield.kinetic = {
generator: 1 - shieldGenerator.getKineticResistance(), generator: sgKineticDmg,
boosters: boosterKinDmg, boosters: sgSbKineticDmg / sgKineticDmg,
sys: (1 - sysResistance), sys: (1 - sysResistance),
total: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - sysResistance), base: sgSbKineticDmg,
max: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - maxSysResistance) total: sgSbKineticDmg * (1 - sysResistance),
max: sgSbKineticDmg * (1 - maxSysResistance),
}; };
let sgThermalDmg = 1 - shieldGenerator.getThermalResistance();
let sgSbThermalDmg = diminishDamageMult(sgThermalDmg * 0.7, (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg);
/** @type {ShieldDamageMults} */
shield.thermal = { shield.thermal = {
generator: 1 - shieldGenerator.getThermalResistance(), generator: sgThermalDmg,
boosters: boosterThermDmg, boosters: sgSbThermalDmg / sgThermalDmg,
sys: (1 - sysResistance), sys: (1 - sysResistance),
total: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - sysResistance), base: sgSbThermalDmg,
max: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - maxSysResistance) total: sgSbThermalDmg * (1 - sysResistance),
max: sgSbThermalDmg * (1 - maxSysResistance),
}; };
} }
return shield; return shield;
} }
/**
* Calculate time from one boost to another
* @return {number} Boost frequency in seconds
* @param {Ship} ship Ship object
*/
export function calcBoost(ship) {
if (!ship.boostEnergy || !ship.standard[4] || !ship.standard[4].m) {
return undefined;
}
return ship.boostEnergy / ship.standard[4].m.getEnginesRechargeRate();
}
/** /**
* Calculate armour metrics * Calculate armour metrics
* @param {Object} ship The ship * @param {Object} ship The ship
@@ -475,33 +560,58 @@ export function armourMetrics(ship) {
let moduleArmour = 0; let moduleArmour = 0;
let moduleProtection = 1; let moduleProtection = 1;
const bulkheads = ship.bulkheads.m;
let hullExplDmg = 1; let hullExplDmg = 1;
let hullKinDmg = 1; let hullKinDmg = 1;
let hullThermDmg = 1; let hullThermDmg = 1;
let hullCausDmg = 1;
// const dimReturnLine = (res) => 1 - (1 - res) * 0.7;
// let res = {
// kin: 0,
// therm: 0,
// expl: 0
// };
// Armour from HRPs and module armour from MRPs // Armour from HRPs and module armour from MRPs
for (let slot of ship.internal) { for (let slot of ship.internal) {
if (slot.m && slot.m.grp == 'hr') { if (slot.m && (slot.m.grp === 'hr' || slot.m.grp === 'ghrp' || slot.m.grp == 'mahr')) {
armourReinforcement += slot.m.getHullReinforcement(); armourReinforcement += slot.m.getHullReinforcement();
// Hull boost for HRPs is applied against the ship's base armour // Hull boost for HRPs is applied against the ship's base armour
armourReinforcement += ship.baseArmour * slot.m.getModValue('hullboost') / 10000; armourReinforcement += ship.baseArmour * slot.m.getModValue('hullboost') / 10000;
// res.expl += slot.m.getExplosiveResistance();
// res.kin += slot.m.getKineticResistance();
// res.therm += slot.m.getThermalResistance();
hullExplDmg = hullExplDmg * (1 - slot.m.getExplosiveResistance()); hullExplDmg = hullExplDmg * (1 - slot.m.getExplosiveResistance());
hullKinDmg = hullKinDmg * (1 - slot.m.getKineticResistance()); hullKinDmg = hullKinDmg * (1 - slot.m.getKineticResistance());
hullThermDmg = hullThermDmg * (1 - slot.m.getThermalResistance()); hullThermDmg = hullThermDmg * (1 - slot.m.getThermalResistance());
hullCausDmg = hullCausDmg * (1 - slot.m.getCausticResistance());
} }
if (slot.m && slot.m.grp == 'mrp') { if (slot.m && (slot.m.grp == 'mrp' || slot.m.grp == 'gmrp')) {
moduleArmour += slot.m.getIntegrity(); moduleArmour += slot.m.getIntegrity();
moduleProtection = moduleProtection * (1 - slot.m.getProtection()); moduleProtection = moduleProtection * (1 - slot.m.getProtection());
} }
} }
moduleProtection = 1 - moduleProtection; moduleProtection = 1 - moduleProtection;
// Apply diminishing returns // const explDim = dimReturnLine(bulkheads.explres);
hullExplDmg = hullExplDmg > 0.7 ? hullExplDmg : 0.7 - (0.7 - hullExplDmg) / 2; // const thermDim = dimReturnLine(bulkheads.thermres);
hullKinDmg = hullKinDmg > 0.7 ? hullKinDmg : 0.7 - (0.7 - hullKinDmg) / 2; // const kinDim = dimReturnLine(bulkheads.kinres);
hullThermDmg = hullThermDmg > 0.7 ? hullThermDmg : 0.7 - (0.7 - hullThermDmg) / 2; // if (res.expl > explDim) {
// const overage = (res.expl - explDim) * 0.5;
// res.expl = explDim + overage;
// hullExplDmg = explDim + overage;
// }
//
// if (res.therm > thermDim) {
// const overage = (res.therm - thermDim) * 0.5;
// res.therm = thermDim + overage;
// hullThermDmg = thermDim + overage;
// }
//
// if (res.kin > kinDim) {
// const overage = (res.kin - kinDim) * 0.5;
// res.kin = kinDim + overage;
// hullKinDmg = kinDim + overage;
// }
const armour = { const armour = {
bulkheads: armourBulkheads, bulkheads: armourBulkheads,
@@ -519,24 +629,41 @@ export function armourMetrics(ship) {
total: 1 total: 1
}; };
let armourExplDmg = diminishDamageMult(0.7, 1 - ship.bulkheads.m.getExplosiveResistance());
let armourReinforcedExplDmg = diminishDamageMult(0.7, (1 - ship.bulkheads.m.getExplosiveResistance()) * hullExplDmg);
armour.explosive = { armour.explosive = {
bulkheads: 1 - ship.bulkheads.m.getExplosiveResistance(), bulkheads: armourExplDmg,
reinforcement: hullExplDmg, reinforcement: armourReinforcedExplDmg / armourExplDmg,
total: (1 - ship.bulkheads.m.getExplosiveResistance()) * hullExplDmg total: armourReinforcedExplDmg,
res: 1 - armourReinforcedExplDmg
}; };
let armourKinDmg = diminishDamageMult(0.7, 1 - ship.bulkheads.m.getKineticResistance());
let armourReinforcedKinDmg = diminishDamageMult(0.7, (1 - ship.bulkheads.m.getKineticResistance()) * hullKinDmg);
armour.kinetic = { armour.kinetic = {
bulkheads: 1 - ship.bulkheads.m.getKineticResistance(), bulkheads: armourKinDmg,
reinforcement: hullKinDmg, reinforcement: armourReinforcedKinDmg / armourKinDmg,
total: (1 - ship.bulkheads.m.getKineticResistance()) * hullKinDmg total: armourReinforcedKinDmg,
res: 1 - armourReinforcedKinDmg
}; };
let armourThermDmg = diminishDamageMult(0.7, 1 - ship.bulkheads.m.getThermalResistance());
let armourReinforcedThermDmg = diminishDamageMult(0.7, (1 - ship.bulkheads.m.getThermalResistance()) * hullThermDmg);
armour.thermal = { armour.thermal = {
bulkheads: 1 - ship.bulkheads.m.getThermalResistance(), bulkheads: armourThermDmg,
reinforcement: hullThermDmg, reinforcement: armourReinforcedThermDmg / armourThermDmg,
total: (1 - ship.bulkheads.m.getThermalResistance()) * hullThermDmg total: armourReinforcedThermDmg,
res: 1 - armourReinforcedThermDmg
}; };
let armourCausDmg = diminishDamageMult(0.7, 1 - ship.bulkheads.m.getCausticResistance());
let armourReinforcedCausDmg = diminishDamageMult(0.7, (1 - ship.bulkheads.m.getCausticResistance()) * hullCausDmg);
armour.caustic = {
bulkheads: armourCausDmg,
reinforcement: armourReinforcedCausDmg / armourCausDmg,
total: armourReinforcedCausDmg,
res: 1 - armourReinforcedCausDmg,
};
return armour; return armour;
} }
@@ -708,20 +835,55 @@ export function _sustainedDps(ship, opponent, opponentShields, opponentArmour, e
return { shieldsdps, armoursdps, eps }; return { shieldsdps, armoursdps, eps };
} }
/**
* Stores SDPS split up by type.
* @typedef {Object} SDps
* @property {number} absolute Damage of type absolute
* @property {number} explosive Damage of type explosive
* @property {number} kinetic Damage of type kinetic
* @property {number} thermal Damage of type thermal
* @property {number} [total] Sum of all damage types
*/
/**
* An object that holds information about SDPS for a given weapon and opponent.
* @typedef {Object} WeaponDamage
* @property {number} eps Energy per second
* @property {Object} damage An object that stores damage inflicted by
* the weapon.
* @property {Object} effectiveness An object that stores the effectiveness of
* the weapon against the opponent given.
*/
/**
* Stores overall SDPS and against a given opponent's shields and armour.
* @typedef {Object} WeaponDamage~damage
* @property {SDps} base Overall SDPS.
* @property {SDps} shields SDPS against the given opponent's shields.
* @property {SDps} armour SDPS against the given opponent's armour.
*/
/** /**
* Calculate the sustained DPS for a weapon at a given range * Calculate the sustained DPS for a weapon at a given range
* @param {Object} m The weapon * @param {Object} m The weapon
* @param {Object} opponent The opponent ship * @param {Object} opponent The opponent ship
* @param {Object} opponentShields The opponent's shield resistances * @param {Object} opponentShields The opponent's shield resistances
* @param {Object} opponentArmour The opponent's armour resistances * @param {Object} opponentArmour The opponent's armour resistances
* @param {int} engagementrange The range between the ship and opponent * @param {int} engagementrange The range between the ship and opponent
* @returns {Object} Sustained DPS for shield and armour * @returns {WeaponDamage} Sustained DPS for shield and armour
*/ */
export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementrange) { export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementrange) {
const opponentHasShields = opponentShields.generator ? true : false; const opponentHasShields = opponentShields.generator ? true : false;
const weapon = { const weapon = {
eps: 0, eps: 0,
damage: { damage: {
base: {
absolute: 0,
explosive: 0,
kinetic: 0,
thermal: 0,
total: 0,
},
shields: { shields: {
absolute: 0, absolute: 0,
explosive: 0, explosive: 0,
@@ -755,7 +917,7 @@ export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour
weapon.eps = m.getClip() ? (m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getEps(); weapon.eps = m.getClip() ? (m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getEps();
// Initial sustained DPS // Initial sustained DPS
let sDps = m.getClip() ? (m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload()) : m.getDps(); let sDps = m.getSDps();
// Take fall-off in to account // Take fall-off in to account
const falloff = m.getFalloff(); const falloff = m.getFalloff();
@@ -766,6 +928,12 @@ export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour
sDps *= dropoff; sDps *= dropoff;
} }
weapon.damage.base.absolute = sDps * m.getDamageDist().A;
weapon.damage.base.explosive = sDps * m.getDamageDist().E;
weapon.damage.base.kinetic = sDps * m.getDamageDist().K;
weapon.damage.base.thermal = sDps * m.getDamageDist().T;
weapon.damage.base.total = sDps;
// Piercing/hardness modifier (for armour only) // Piercing/hardness modifier (for armour only)
const armourMultiple = m.getPiercing() >= opponent.hardness ? 1 : m.getPiercing() / opponent.hardness; const armourMultiple = m.getPiercing() >= opponent.hardness ? 1 : m.getPiercing() / opponent.hardness;
weapon.effectiveness.armour.hardness = armourMultiple; weapon.effectiveness.armour.hardness = armourMultiple;
@@ -863,3 +1031,17 @@ export function timeToDeplete(amount, dps, eps, capacity, recharge) {
} }
} }
} }
/**
* Applies diminishing returns to resistances.
* @param {number} diminishFrom The base resistance up to which no diminishing returns are applied.
* @param {number} damageMult Resistance as damage multiplier
* @returns {number} Actual damage multiplier
*/
export function diminishDamageMult(diminishFrom, damageMult) {
if (damageMult > diminishFrom) {
return damageMult;
} else {
return (diminishFrom / 2) + 0.5 * damageMult;
}
}

View File

@@ -18,7 +18,7 @@ export const ModuleGroupToName = {
// Standard // Standard
pp: 'Power Plant', pp: 'Power Plant',
gpp: 'Guardian Hybrid Power Plant', gpp: 'Guardian Hybrid Power Plant',
gpd: 'Guardian Hybrid Power Distributor', gpd: 'Guardian Power Distributor',
t: 'Thrusters', t: 'Thrusters',
fsd: 'Frame Shift Drive', fsd: 'Frame Shift Drive',
ls: 'Life Support', ls: 'Life Support',
@@ -52,6 +52,11 @@ export const ModuleGroupToName = {
pcq: 'Luxury Passenger Cabin', pcq: 'Luxury Passenger Cabin',
cc: 'Collector Limpet Controller', cc: 'Collector Limpet Controller',
ss: 'Surface Scanner', ss: 'Surface Scanner',
gsrp: 'Guardian Shield Reinforcement Packages',
gfsb: 'Guardian Frame Shift Drive Booster',
ghrp: 'Guardian Hull Reinforcement Package',
gmrp: 'Guardian Module Reinforcement Package',
mahr: 'Meta Alloy Hull Reinforcement Package',
// Hard Points // Hard Points
bl: 'Beam Laser', bl: 'Beam Laser',
@@ -81,8 +86,14 @@ export const ModuleGroupToName = {
sfn: 'Shutdown Field Neutraliser', sfn: 'Shutdown Field Neutraliser',
xs: 'Xeno Scanner', xs: 'Xeno Scanner',
rcpl: 'Recon Limpet Controller', rcpl: 'Recon Limpet Controller',
rsl: 'Research Limpet Controller',
dtl: 'Decontamination Limpet Controller',
gpc: 'Guardian Plasma Charger', gpc: 'Guardian Plasma Charger',
ggc: 'Guardian Gauss Cannon', ggc: 'Guardian Gauss Cannon',
tbsc: 'Shock Cannon',
gsc: 'Guardian Shard Cannon',
tbem: 'Enzyme Missile Rack',
tbrfl: 'Remote Release Flechette Launcher',
}; };
let GrpNameToCodeMap = {}; let GrpNameToCodeMap = {};

File diff suppressed because it is too large Load Diff

View File

@@ -7,9 +7,10 @@ import LZString from 'lz-string';
import * as _ from 'lodash'; import * as _ from 'lodash';
import isEqual from 'lodash/lang'; import isEqual from 'lodash/lang';
import { Ships, Modifications } from 'coriolis-data/dist'; import { Ships, Modifications } from 'coriolis-data/dist';
import { chain } from 'lodash';
const zlib = require('zlib'); const zlib = require('zlib');
const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs', 'fh']; const UNIQUE_MODULES = ['psg', 'sg', 'bsg', 'rf', 'fs', 'fh', 'gfsb'];
// Constants for modifications struct // Constants for modifications struct
const SLOT_ID_DONE = -1; const SLOT_ID_DONE = -1;
@@ -151,7 +152,7 @@ export default class Ship {
* @return {Number} Jump range in Light Years * @return {Number} Jump range in Light Years
*/ */
calcLadenRange(massDelta, fuel, fsd) { calcLadenRange(massDelta, fuel, fsd) {
return Calc.jumpRange(this.ladenMass + (massDelta || 0), fsd || this.standard[2].m, fuel); return Calc.jumpRange(this.ladenMass + (massDelta || 0), fsd || this.standard[2].m, fuel, this);
} }
/** /**
@@ -164,7 +165,7 @@ export default class Ship {
calcUnladenRange(massDelta, fuel, fsd) { calcUnladenRange(massDelta, fuel, fsd) {
fsd = fsd || this.standard[2].m; fsd = fsd || this.standard[2].m;
let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel; let fsdMaxFuelPerJump = fsd instanceof Module ? fsd.getMaxFuelPerJump() : fsd.maxfuel;
return Calc.jumpRange(this.unladenMass + (massDelta || 0) + Math.min(fsdMaxFuelPerJump, fuel || this.fuelCapacity), fsd || this.standard[2].m, fuel); return Calc.jumpRange(this.unladenMass + (massDelta || 0) + Math.min(fsdMaxFuelPerJump, fuel || this.fuelCapacity), fsd || this.standard[2].m, fuel, this);
} }
/** /**
@@ -239,9 +240,8 @@ export default class Ship {
} }
sg = sgSlot.m; sg = sgSlot.m;
} }
// TODO Not accurate if the ship has modified shield boosters // TODO Not accurate if the ship has modified shield boosters
return Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sg, 1 + (multiplierDelta || 0), this); return Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sg, 1 + (multiplierDelta || 0));
} }
/** /**
@@ -485,10 +485,7 @@ export default class Ship {
* @param {Object} m The module for which to clear the blueprint * @param {Object} m The module for which to clear the blueprint
*/ */
clearModuleSpecial(m) { clearModuleSpecial(m) {
if (m.blueprint) { this.setModuleSpecial(m, null);
m.blueprint.special = null;
}
this.recalculateDps().recalculateHps().recalculateEps();
} }
/** /**
@@ -497,68 +494,62 @@ export default class Ship {
* @param {Object} name The name of the modification 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 {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 * @param {bool} sentfromui True if this update was sent from the UI
* @param {bool} isAbsolute True if value is an absolute value and not a
* modification value
*/ */
setModification(m, name, value, sentfromui) { setModification(m, name, value, sentfromui, isAbsolute) {
if (isNaN(value)) { if (isNaN(value)) {
// Value passed is invalid; reset it to 0 // Value passed is invalid; reset it to 0
value = 0; value = 0;
} }
if (isAbsolute) {
m.setPretty(name, value, sentfromui);
} else {
m.setModValue(name, value, sentfromui);
}
// Handle special cases // Handle special cases
if (name === 'pgen') { if (name === 'pgen') {
// Power generation // Power generation
m.setModValue(name, value, sentfromui);
this.updatePowerGenerated(); this.updatePowerGenerated();
} else if (name === 'power') { } else if (name === 'power') {
// Power usage // Power usage
m.setModValue(name, value, sentfromui);
this.updatePowerUsed(); this.updatePowerUsed();
} else if (name === 'mass') { } else if (name === 'mass') {
// Mass // Mass
m.setModValue(name, value, sentfromui);
this.recalculateMass(); this.recalculateMass();
this.updateMovement(); this.updateMovement();
this.updateJumpStats(); this.updateJumpStats();
} else if (name === 'maxfuel') { } else if (name === 'maxfuel') {
m.setModValue(name, value, sentfromui);
this.updateJumpStats(); this.updateJumpStats();
} else if (name === 'optmass') { } else if (name === 'optmass') {
m.setModValue(name, value, sentfromui);
// Could be for any of thrusters, FSD or shield // Could be for any of thrusters, FSD or shield
this.updateMovement(); this.updateMovement();
this.updateJumpStats(); this.updateJumpStats();
this.recalculateShield(); this.recalculateShield();
} else if (name === 'optmul') { } else if (name === 'optmul') {
m.setModValue(name, value, sentfromui);
// Could be for any of thrusters, FSD or shield // Could be for any of thrusters, FSD or shield
this.updateMovement(); this.updateMovement();
this.updateJumpStats(); this.updateJumpStats();
this.recalculateShield(); this.recalculateShield();
} else if (name === 'shieldboost') { } else if (name === 'shieldboost') {
m.setModValue(name, value, sentfromui);
this.recalculateShield(); this.recalculateShield();
} else if (name === 'hullboost' || name === 'hullreinforcement' || name === 'modulereinforcement') { } else if (name === 'hullboost' || name === 'hullreinforcement' || name === 'modulereinforcement') {
m.setModValue(name, value, sentfromui);
this.recalculateArmour(); this.recalculateArmour();
} else if (name === 'shieldreinforcement') { } else if (name === 'shieldreinforcement') {
m.setModValue(name, value, sentfromui);
this.recalculateShieldCells(); 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') { } 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, sentfromui);
this.recalculateDps(); this.recalculateDps();
this.recalculateHps(); this.recalculateHps();
this.recalculateEps(); this.recalculateEps();
} else if (name === 'explres' || name === 'kinres' || name === 'thermres') { } else if (name === 'explres' || name === 'kinres' || name === 'thermres') {
m.setModValue(name, value, sentfromui);
// Could be for shields or armour // Could be for shields or armour
this.recalculateArmour(); this.recalculateArmour();
this.recalculateShield(); this.recalculateShield();
} else if (name === 'engcap') { } else if (name === 'engcap') {
m.setModValue(name, value, sentfromui);
// Might have resulted in a change in boostability // Might have resulted in a change in boostability
this.updateMovement(); this.updateMovement();
} else {
// Generic
m.setModValue(name, value, sentfromui);
} }
} }
@@ -941,9 +932,14 @@ export default class Ship {
let epsChanged = n && n.getEps() || old && old.getEps(); let epsChanged = n && n.getEps() || old && old.getEps();
let hpsChanged = n && n.getHps() || old && old.getHps(); let hpsChanged = n && n.getHps() || old && old.getHps();
let armourChange = (slot === this.bulkheads) || (n && n.grp === 'hr') || (old && old.grp === 'hr') || (n && n.grp === 'mrp') || (old && old.grp === 'mrp'); let armourChange = (slot === this.bulkheads) ||
(n && n.grp === 'hr') || (old && old.grp === 'hr') ||
(n && n.grp === 'ghrp') || (old && old.grp === 'ghrp') ||
(n && n.grp == 'mahr') || (old && old.grp == 'mahr') ||
(n && n.grp === 'mrp') || (old && old.grp === 'mrp') ||
(n && n.grp === 'gmrp') || (old && old.grp == 'gmrp');
let shieldChange = (n && n.grp === 'bsg') || (old && old.grp === 'bsg') || (n && n.grp === 'psg') || (old && old.grp === 'psg') || (n && n.grp === 'sg') || (old && old.grp === 'sg') || (n && n.grp === 'sb') || (old && old.grp === 'sb'); let shieldChange = (n && n.grp === 'bsg') || (old && old.grp === 'bsg') || (n && n.grp === 'psg') || (old && old.grp === 'psg') || (n && n.grp === 'sg') || (old && old.grp === 'sg') || (n && n.grp === 'sb') || (old && old.grp === 'sb') || (old && old.grp === 'gsrp') || (n && n.grp === 'gsrp');
let shieldCellsChange = (n && n.grp === 'scb') || (old && old.grp === 'scb'); let shieldCellsChange = (n && n.grp === 'scb') || (old && old.grp === 'scb');
@@ -1000,25 +996,6 @@ export default class Ship {
return this; return this;
} }
/**
* Calculate diminishing returns value, where values below a given limit are returned
* as-is, and values between the lower and upper limit of the diminishing returns are
* given at half value.
* Commonly used for resistances.
* @param {Number} val The value
* @param {Number} drll The lower limit for diminishing returns
* @param {Number} drul The upper limit for diminishing returns
* @return {this} The ship instance (for chaining operations)
*/
diminishingReturns(val, drll, drul) {
if (val < drll) {
val = drll;
} else if (val < drul) {
val = drul - (drul - val) / 2;
}
return val;
}
/** /**
* Calculate damage per second and related items for weapons * Calculate damage per second and related items for weapons
* @return {this} The ship instance (for chaining operations) * @return {this} The ship instance (for chaining operations)
@@ -1205,38 +1182,35 @@ export default class Ship {
unladenMass += this.bulkheads.m.getMass(); unladenMass += this.bulkheads.m.getMass();
for (let slotNum in this.standard) { let slots = this.standard.concat(this.internal, this.hardpoints);
const slot = this.standard[slotNum]; // TODO: create class for slot and also add slot.get
if (slot.m) { // handle unladen mass
unladenMass += slot.m.getMass(); unladenMass += chain(slots)
if (slot.m.grp === 'ft') { .map(slot => slot.m ? slot.m.get('mass') : null)
fuelCapacity += slot.m.fuel; .filter()
} .reduce((sum, mass) => sum + mass)
} .value();
}
for (let slotNum in this.internal) { // handle fuel capacity
const slot = this.internal[slotNum]; fuelCapacity += chain(slots)
if (slot.m) { .map(slot => slot.m ? slot.m.get('fuel') : null)
unladenMass += slot.m.getMass(); .filter()
if (slot.m.grp === 'ft') { .reduce((sum, fuel) => sum + fuel)
fuelCapacity += slot.m.fuel; .value();
} else if (slot.m.grp === 'cr') {
cargoCapacity += slot.m.cargo;
} else if (slot.m.grp.slice(0,2) === 'pc') {
if (slot.m.passengers) {
passengerCapacity += slot.m.passengers;
}
}
}
}
for (let slotNum in this.hardpoints) { // handle cargo capacity
const slot = this.hardpoints[slotNum]; cargoCapacity += chain(slots)
if (slot.m) { .map(slot => slot.m ? slot.m.get('cargo') : null)
unladenMass += slot.m.getMass(); .filter()
} .reduce((sum, cargo) => sum + cargo)
} .value();
// handle passenger capacity
passengerCapacity += chain(slots)
.map(slot => slot.m ? slot.m.get('passengers') : null)
.filter()
.reduce((sum, passengers) => sum + passengers)
.value();
// Update global stats // Update global stats
this.unladenMass = unladenMass; this.unladenMass = unladenMass;
@@ -1277,7 +1251,7 @@ export default class Ship {
// Obtain shield metrics with 0 pips to sys (parts affected by SYS aren't used here) // Obtain shield metrics with 0 pips to sys (parts affected by SYS aren't used here)
const metrics = Calc.shieldMetrics(this, 0); const metrics = Calc.shieldMetrics(this, 0);
this.shield = metrics.generator ? metrics.generator + metrics.boosters : 0; this.shield = metrics.generator ? metrics.generator + metrics.boosters + metrics.addition : 0;
this.shieldExplRes = this.shield > 0 ? 1 - metrics.explosive.total : null; this.shieldExplRes = this.shield > 0 ? 1 - metrics.explosive.total : null;
this.shieldKinRes = this.shield > 0 ? 1 - metrics.kinetic.total : null; this.shieldKinRes = this.shield > 0 ? 1 - metrics.kinetic.total : null;
this.shieldThermRes = this.shield > 0 ? 1 - metrics.thermal.total : null; this.shieldThermRes = this.shield > 0 ? 1 - metrics.thermal.total : null;
@@ -1310,45 +1284,15 @@ export default class Ship {
*/ */
recalculateArmour() { recalculateArmour() {
// Armour from bulkheads // Armour from bulkheads
let bulkhead = this.bulkheads.m; let metrics = Calc.armourMetrics(this);
let armour = this.baseArmour + (this.baseArmour * bulkhead.getHullBoost());
let modulearmour = 0;
let moduleprotection = 1;
let hullExplRes = 1 - bulkhead.getExplosiveResistance();
const hullExplResDRStart = hullExplRes * 0.7;
const hullExplResDREnd = hullExplRes * 0;
let hullKinRes = 1 - bulkhead.getKineticResistance();
const hullKinResDRStart = hullKinRes * 0.7;
const hullKinResDREnd = hullKinRes * 0;
let hullThermRes = 1 - bulkhead.getThermalResistance();
const hullThermResDRStart = hullThermRes * 0.7;
const hullThermResDREnd = hullThermRes * 0;
// Armour from HRPs and module armour from MRPs
for (let slot of this.internal) {
if (slot.m && slot.m.grp == 'hr') {
armour += slot.m.getHullReinforcement();
// Hull boost for HRPs is applied against the ship's base armour
armour += this.baseArmour * slot.m.getModValue('hullboost') / 10000;
hullExplRes *= (1 - slot.m.getExplosiveResistance());
hullKinRes *= (1 - slot.m.getKineticResistance());
hullThermRes *= (1 - slot.m.getThermalResistance());
}
if (slot.m && slot.m.grp == 'mrp') {
modulearmour += slot.m.getIntegrity();
moduleprotection = moduleprotection * (1 - slot.m.getProtection());
}
}
moduleprotection = 1 - moduleprotection;
this.armour = armour;
this.modulearmour = modulearmour;
this.moduleprotection = moduleprotection;
this.hullExplRes = 1 - this.diminishingReturns(hullExplRes, hullExplResDREnd, hullExplResDRStart);
this.hullKinRes = 1 - this.diminishingReturns(hullKinRes, hullKinResDREnd, hullKinResDRStart);
this.hullThermRes = 1 - this.diminishingReturns(hullThermRes, hullThermResDREnd, hullThermResDRStart);
this.armour = metrics.total ? metrics.total : 0;
this.modulearmour = metrics.modulearmour;
this.moduleprotection = metrics.moduleprotection;
this.hullExplRes = 1 - metrics.explosive.total;
this.hullKinRes = 1 - metrics.kinetic.total;
this.hullThermRes = 1 - metrics.thermal.total;
this.hullCausRes = 1 - metrics.caustic.total;
return this; return this;
} }
@@ -1360,10 +1304,10 @@ export default class Ship {
let fsd = this.standard[2].m; // Frame Shift Drive; let fsd = this.standard[2].m; // Frame Shift Drive;
let { unladenMass, fuelCapacity } = this; let { unladenMass, fuelCapacity } = this;
this.unladenRange = this.calcUnladenRange(); // Includes fuel weight for jump this.unladenRange = this.calcUnladenRange(); // Includes fuel weight for jump
this.fullTankRange = Calc.jumpRange(unladenMass + fuelCapacity, fsd); // Full Tank this.fullTankRange = Calc.jumpRange(unladenMass + fuelCapacity, fsd, this); // Full Tank
this.ladenRange = this.calcLadenRange(); // Includes full tank and caro this.ladenRange = this.calcLadenRange(); // Includes full tank and caro
this.unladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity, fsd, fuelCapacity); this.unladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity, fsd, fuelCapacity, this);
this.ladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity + this.cargoCapacity, fsd, fuelCapacity); this.ladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity + this.cargoCapacity, fsd, fuelCapacity, this);
this.maxJumpCount = Math.ceil(fuelCapacity / fsd.getMaxFuelPerJump()); this.maxJumpCount = Math.ceil(fuelCapacity / fsd.getMaxFuelPerJump());
return this; return this;
} }

View File

@@ -1,5 +1,5 @@
import * as ModuleUtils from './ModuleUtils' import * as ModuleUtils from './ModuleUtils';
import { canMount } from '../utils/SlotFunctions' import { canMount } from '../utils/SlotFunctions';
/** /**
* Standard / typical role for multi-purpose or combat (if shielded with better bulkheads) * Standard / typical role for multi-purpose or combat (if shielded with better bulkheads)
@@ -7,20 +7,20 @@ import { canMount } from '../utils/SlotFunctions'
* @param {Boolean} shielded True if shield generator should be included * @param {Boolean} shielded True if shield generator should be included
* @param {integer} bulkheadIndex Bulkhead to use see Constants.BulkheadNames * @param {integer} bulkheadIndex Bulkhead to use see Constants.BulkheadNames
*/ */
export function multiPurpose (ship, shielded, bulkheadIndex) { export function multiPurpose(ship, shielded, bulkheadIndex) {
ship.useStandard('A') ship.useStandard('A')
.use(ship.standard[3], ModuleUtils.standard(3, ship.standard[3].maxClass + 'D')) // D Life Support .use(ship.standard[3], ModuleUtils.standard(3, ship.standard[3].maxClass + 'D')) // D Life Support
.use(ship.standard[5], ModuleUtils.standard(5, ship.standard[5].maxClass + 'D')) // D Sensors .use(ship.standard[5], ModuleUtils.standard(5, ship.standard[5].maxClass + 'D')) // D Sensors
.useBulkhead(bulkheadIndex) .useBulkhead(bulkheadIndex);
if (shielded) { if (shielded) {
ship.internal.some(function (slot) { ship.internal.some(function(slot) {
if (canMount(ship, slot, 'sg')) { // Assuming largest slot can hold an eligible shield if (canMount(ship, slot, 'sg')) { // Assuming largest slot can hold an eligible shield
ship.use(slot, ModuleUtils.findInternal('sg', slot.maxClass, 'A')) ship.use(slot, ModuleUtils.findInternal('sg', slot.maxClass, 'A'));
ship.setSlotEnabled(slot, true) ship.setSlotEnabled(slot, true);
return true return true;
} }
}) });
} }
} }
@@ -30,51 +30,51 @@ export function multiPurpose (ship, shielded, bulkheadIndex) {
* @param {Boolean} shielded True if shield generator should be included * @param {Boolean} shielded True if shield generator should be included
* @param {Object} standardOpts [Optional] Standard module optional overrides * @param {Object} standardOpts [Optional] Standard module optional overrides
*/ */
export function trader (ship, shielded, standardOpts) { export function trader(ship, shielded, standardOpts) {
let usedSlots = [] let usedSlots = [];
let bstCount = 2 let bstCount = 2;
let sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass) let sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
ship.useStandard('A') ship.useStandard('A')
.use(ship.standard[3], ModuleUtils.standard(3, ship.standard[3].maxClass + 'D')) // D Life Support .use(ship.standard[3], ModuleUtils.standard(3, ship.standard[3].maxClass + 'D')) // D Life Support
.use(ship.standard[1], ModuleUtils.standard(1, ship.standard[1].maxClass + 'D')) // D Life Support .use(ship.standard[1], ModuleUtils.standard(1, ship.standard[1].maxClass + 'D')) // D Life Support
.use(ship.standard[4], ModuleUtils.standard(4, ship.standard[4].maxClass + 'D')) // D Life Support .use(ship.standard[4], ModuleUtils.standard(4, ship.standard[4].maxClass + 'D')) // D Life Support
.use(ship.standard[5], ModuleUtils.standard(5, ship.standard[5].maxClass + 'D')) // D Sensors .use(ship.standard[5], ModuleUtils.standard(5, ship.standard[5].maxClass + 'D')); // D Sensors
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8] const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sg) .filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class) .filter(a => a.maxClass >= sg.class)
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass)) .sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
shieldInternals.some(function (slot) { shieldInternals.some(function(slot) {
if (canMount(ship, slot, 'sg')) { // Assuming largest slot can hold an eligible shield if (canMount(ship, slot, 'sg')) { // Assuming largest slot can hold an eligible shield
const shield = ModuleUtils.findInternal('sg', slot.maxClass, 'A') const shield = ModuleUtils.findInternal('sg', slot.maxClass, 'A');
if (shield && shield.maxmass > ship.hullMass) { if (shield && shield.maxmass > ship.hullMass) {
ship.use(slot, shield) ship.use(slot, shield);
ship.setSlotEnabled(slot, true) ship.setSlotEnabled(slot, true);
usedSlots.push(slot) usedSlots.push(slot);
return true return true;
} }
} }
}) });
// Fill the empty internals with cargo racks // Fill the empty internals with cargo racks
for (let i = ship.internal.length; i--;) { for (let i = ship.internal.length; i--;) {
let slot = ship.internal[i] let slot = ship.internal[i];
if (usedSlots.indexOf(slot) == -1 && canMount(ship, slot, 'cr')) { if (usedSlots.indexOf(slot) == -1 && canMount(ship, slot, 'cr')) {
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E')) ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
} }
} }
// Empty the hardpoints // Empty the hardpoints
for (let s of ship.hardpoints) { for (let s of ship.hardpoints) {
ship.use(s, null) ship.use(s, null);
} }
for (let s of ship.hardpoints) { for (let s of ship.hardpoints) {
if (s.maxClass == 0 && bstCount) { // Mount up to 2 boosters if (s.maxClass == 0 && bstCount) { // Mount up to 2 boosters
ship.use(s, ModuleUtils.hardpoints('04')) ship.use(s, ModuleUtils.hardpoints('04'));
bstCount-- bstCount--;
} else { } else {
ship.use(s, null) ship.use(s, null);
} }
} }
// ship.useLightestStandard(standardOpts); // ship.useLightestStandard(standardOpts);
@@ -85,127 +85,127 @@ export function trader (ship, shielded, standardOpts) {
* @param {Ship} ship Ship instance * @param {Ship} ship Ship instance
* @param {Boolean} planetary True if Planetary Vehicle Hangar (PVH) should be included * @param {Boolean} planetary True if Planetary Vehicle Hangar (PVH) should be included
*/ */
export function explorer (ship, planetary) { export function explorer(ship, planetary) {
let standardOpts = {ppRating: 'A'}, let standardOpts = { ppRating: 'A' },
heatSinkCount = 2, // Fit 2 heat sinks if possible heatSinkCount = 2, // Fit 2 heat sinks if possible
usedSlots = [], usedSlots = [],
sgSlot, sgSlot,
fuelScoopSlot, fuelScoopSlot,
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass) sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
if (!planetary) { // Non-planetary explorers don't really need to boost if (!planetary) { // Non-planetary explorers don't really need to boost
standardOpts.pd = '1D' standardOpts.pd = '1D';
} }
// Cargo hatch can be disabled // Cargo hatch can be disabled
ship.setSlotEnabled(ship.cargoHatch, false) ship.setSlotEnabled(ship.cargoHatch, false);
// Advanced Discovery Scanner - class 1 or higher // Advanced Discovery Scanner - class 1 or higher
const adsOrder = [1, 2, 3, 4, 5, 6, 7, 8] const adsOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const adsInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const adsInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sc) .filter(a => (!a.eligible) || a.eligible.sc)
.sort((a, b) => adsOrder.indexOf(a.maxClass) - adsOrder.indexOf(b.maxClass)) .sort((a, b) => adsOrder.indexOf(a.maxClass) - adsOrder.indexOf(b.maxClass));
for (let i = 0; i < adsInternals.length; i++) { for (let i = 0; i < adsInternals.length; i++) {
if (canMount(ship, adsInternals[i], 'sc')) { if (canMount(ship, adsInternals[i], 'sc')) {
ship.use(adsInternals[i], ModuleUtils.internal('2f')) ship.use(adsInternals[i], ModuleUtils.internal('2f'));
usedSlots.push(adsInternals[i]) usedSlots.push(adsInternals[i]);
break break;
} }
} }
if (planetary) { if (planetary) {
// Planetary Vehicle Hangar - class 2 or higher // Planetary Vehicle Hangar - class 2 or higher
const pvhOrder = [2, 3, 4, 5, 6, 7, 8, 1] const pvhOrder = [2, 3, 4, 5, 6, 7, 8, 1];
const pvhInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const pvhInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.pv) .filter(a => (!a.eligible) || a.eligible.pv)
.sort((a, b) => pvhOrder.indexOf(a.maxClass) - pvhOrder.indexOf(b.maxClass)) .sort((a, b) => pvhOrder.indexOf(a.maxClass) - pvhOrder.indexOf(b.maxClass));
for (let i = 0; i < pvhInternals.length; i++) { for (let i = 0; i < pvhInternals.length; i++) {
if (canMount(ship, pvhInternals[i], 'pv')) { if (canMount(ship, pvhInternals[i], 'pv')) {
// Planetary Vehical Hangar only has even classes // Planetary Vehical Hangar only has even classes
const pvhClass = pvhInternals[i].maxClass % 2 === 1 ? pvhInternals[i].maxClass - 1 : pvhInternals[i].maxClass 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.use(pvhInternals[i], ModuleUtils.findInternal('pv', pvhClass, 'G')); // G is lower mass
ship.setSlotEnabled(pvhInternals[i], false) // Disable power for Planetary Vehical Hangar ship.setSlotEnabled(pvhInternals[i], false); // Disable power for Planetary Vehical Hangar
usedSlots.push(pvhInternals[i]) usedSlots.push(pvhInternals[i]);
break break;
} }
} }
} }
// Shield generator // Shield generator
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8] const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sg) .filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class) .filter(a => a.maxClass >= sg.class)
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass)) .sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
for (let i = 0; i < shieldInternals.length; i++) { for (let i = 0; i < shieldInternals.length; i++) {
if (canMount(ship, shieldInternals[i], 'sg')) { if (canMount(ship, shieldInternals[i], 'sg')) {
ship.use(shieldInternals[i], sg) ship.use(shieldInternals[i], sg);
usedSlots.push(shieldInternals[i]) usedSlots.push(shieldInternals[i]);
sgSlot = shieldInternals[i] sgSlot = shieldInternals[i];
break break;
} }
} }
// Detailed Surface Scanner // Detailed Surface Scanner
const dssOrder = [1, 2, 3, 4, 5, 6, 7, 8] const dssOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const dssInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const dssInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sc) .filter(a => (!a.eligible) || a.eligible.sc)
.sort((a, b) => dssOrder.indexOf(a.maxClass) - dssOrder.indexOf(b.maxClass)) .sort((a, b) => dssOrder.indexOf(a.maxClass) - dssOrder.indexOf(b.maxClass));
for (let i = 0; i < dssInternals.length; i++) { for (let i = 0; i < dssInternals.length; i++) {
if (canMount(ship, dssInternals[i], 'sc')) { if (canMount(ship, dssInternals[i], 'sc')) {
ship.use(dssInternals[i], ModuleUtils.internal('2i')) ship.use(dssInternals[i], ModuleUtils.internal('2i'));
usedSlots.push(dssInternals[i]) usedSlots.push(dssInternals[i]);
break break;
} }
} }
// Fuel scoop - best possible // Fuel scoop - best possible
const fuelScoopOrder = [8, 7, 6, 5, 4, 3, 2, 1] const fuelScoopOrder = [8, 7, 6, 5, 4, 3, 2, 1];
const fuelScoopInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const fuelScoopInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.fs) .filter(a => (!a.eligible) || a.eligible.fs)
.sort((a, b) => fuelScoopOrder.indexOf(a.maxClass) - fuelScoopOrder.indexOf(b.maxClass)) .sort((a, b) => fuelScoopOrder.indexOf(a.maxClass) - fuelScoopOrder.indexOf(b.maxClass));
for (let i = 0; i < fuelScoopInternals.length; i++) { for (let i = 0; i < fuelScoopInternals.length; i++) {
if (canMount(ship, fuelScoopInternals[i], 'fs')) { if (canMount(ship, fuelScoopInternals[i], 'fs')) {
ship.use(fuelScoopInternals[i], ModuleUtils.findInternal('fs', fuelScoopInternals[i].maxClass, 'A')) ship.use(fuelScoopInternals[i], ModuleUtils.findInternal('fs', fuelScoopInternals[i].maxClass, 'A'));
usedSlots.push(fuelScoopInternals[i]) usedSlots.push(fuelScoopInternals[i]);
fuelScoopSlot = fuelScoopInternals[i] fuelScoopSlot = fuelScoopInternals[i];
break break;
} }
} }
// AFMUs - fill as they are 0-weight // AFMUs - fill as they are 0-weight
const afmuOrder = [8, 7, 6, 5, 4, 3, 2, 1] const afmuOrder = [8, 7, 6, 5, 4, 3, 2, 1];
const afmuInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const afmuInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.pc) .filter(a => (!a.eligible) || a.eligible.pc)
.sort((a, b) => afmuOrder.indexOf(a.maxClass) - afmuOrder.indexOf(b.maxClass)) .sort((a, b) => afmuOrder.indexOf(a.maxClass) - afmuOrder.indexOf(b.maxClass));
for (let i = 0; i < afmuInternals.length; i++) { for (let i = 0; i < afmuInternals.length; i++) {
if (canMount(ship, afmuInternals[i], 'am')) { if (canMount(ship, afmuInternals[i], 'am')) {
ship.use(afmuInternals[i], ModuleUtils.findInternal('am', afmuInternals[i].maxClass, 'A')) ship.use(afmuInternals[i], ModuleUtils.findInternal('am', afmuInternals[i].maxClass, 'A'));
usedSlots.push(afmuInternals[i]) usedSlots.push(afmuInternals[i]);
ship.setSlotEnabled(afmuInternals[i], false) // Disable power for AFM Unit ship.setSlotEnabled(afmuInternals[i], false); // Disable power for AFM Unit
} }
} }
for (let s of ship.hardpoints) { for (let s of ship.hardpoints) {
if (s.maxClass == 0 && heatSinkCount) { // Mount up to 2 heatsinks if (s.maxClass == 0 && heatSinkCount) { // Mount up to 2 heatsinks
ship.use(s, ModuleUtils.hardpoints('02')) ship.use(s, ModuleUtils.hardpoints('02'));
ship.setSlotEnabled(s, heatSinkCount == 2) // Only enable a single Heatsink ship.setSlotEnabled(s, heatSinkCount == 2); // Only enable a single Heatsink
heatSinkCount-- heatSinkCount--;
} else { } else {
ship.use(s, null) ship.use(s, null);
} }
} }
if (sgSlot && fuelScoopSlot) { if (sgSlot && fuelScoopSlot) {
// The SG and Fuel scoop to not need to be powered at the same time // 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 if (sgSlot.m.getPowerUsage() > fuelScoopSlot.m.getPowerUsage()) { // The Shield generator uses the most power
ship.setSlotEnabled(fuelScoopSlot, false) ship.setSlotEnabled(fuelScoopSlot, false);
} else { // The Fuel scoop uses the most power } else { // The Fuel scoop uses the most power
ship.setSlotEnabled(sgSlot, false) ship.setSlotEnabled(sgSlot, false);
} }
} }
ship.useLightestStandard(standardOpts) ship.useLightestStandard(standardOpts);
} }
/** /**
@@ -213,188 +213,188 @@ export function explorer (ship, planetary) {
* @param {Ship} ship Ship instance * @param {Ship} ship Ship instance
* @param {Boolean} shielded True if shield generator should be included * @param {Boolean} shielded True if shield generator should be included
*/ */
export function miner (ship, shielded) { export function miner(ship, shielded) {
shielded = true shielded = true;
let standardOpts = {ppRating: 'A'}, let standardOpts = { ppRating: 'A' },
miningLaserCount = 2, miningLaserCount = 2,
usedSlots = [], usedSlots = [],
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass) sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
// Cargo hatch should be enabled // Cargo hatch should be enabled
ship.setSlotEnabled(ship.cargoHatch, true) ship.setSlotEnabled(ship.cargoHatch, true);
// Largest possible refinery // Largest possible refinery
const refineryOrder = [4, 5, 6, 7, 8, 3, 2, 1] const refineryOrder = [4, 5, 6, 7, 8, 3, 2, 1];
const refineryInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const refineryInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.rf) .filter(a => (!a.eligible) || a.eligible.rf)
.sort((a, b) => refineryOrder.indexOf(a.maxClass) - refineryOrder.indexOf(b.maxClass)) .sort((a, b) => refineryOrder.indexOf(a.maxClass) - refineryOrder.indexOf(b.maxClass));
for (let i = 0; i < refineryInternals.length; i++) { for (let i = 0; i < refineryInternals.length; i++) {
if (canMount(ship, refineryInternals[i], 'rf')) { if (canMount(ship, refineryInternals[i], 'rf')) {
ship.use(refineryInternals[i], ModuleUtils.findInternal('rf', Math.min(refineryInternals[i].maxClass, 4), 'A')) ship.use(refineryInternals[i], ModuleUtils.findInternal('rf', Math.min(refineryInternals[i].maxClass, 4), 'A'));
usedSlots.push(refineryInternals[i]) usedSlots.push(refineryInternals[i]);
break break;
} }
} }
// Prospector limpet controller - 3A if possible // Prospector limpet controller - 3A if possible
const prospectorOrder = [3, 4, 5, 6, 7, 8, 2, 1] const prospectorOrder = [3, 4, 5, 6, 7, 8, 2, 1];
const prospectorInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const prospectorInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.pc) .filter(a => (!a.eligible) || a.eligible.pc)
.sort((a, b) => prospectorOrder.indexOf(a.maxClass) - prospectorOrder.indexOf(b.maxClass)) .sort((a, b) => prospectorOrder.indexOf(a.maxClass) - prospectorOrder.indexOf(b.maxClass));
for (let i = 0; i < prospectorInternals.length; i++) { for (let i = 0; i < prospectorInternals.length; i++) {
if (canMount(ship, prospectorInternals[i], 'pc')) { if (canMount(ship, prospectorInternals[i], 'pc')) {
// Prospector only has odd classes // Prospector only has odd classes
const prospectorClass = prospectorInternals[i].maxClass % 2 === 0 ? prospectorInternals[i].maxClass - 1 : prospectorInternals[i].maxClass const prospectorClass = prospectorInternals[i].maxClass % 2 === 0 ? prospectorInternals[i].maxClass - 1 : prospectorInternals[i].maxClass;
ship.use(prospectorInternals[i], ModuleUtils.findInternal('pc', prospectorClass, 'A')) ship.use(prospectorInternals[i], ModuleUtils.findInternal('pc', prospectorClass, 'A'));
usedSlots.push(prospectorInternals[i]) usedSlots.push(prospectorInternals[i]);
break break;
} }
} }
// Shield generator if required // Shield generator if required
if (shielded) { if (shielded) {
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8] const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sg) .filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class) .filter(a => a.maxClass >= sg.class)
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass)) .sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
for (let i = 0; i < shieldInternals.length; i++) { for (let i = 0; i < shieldInternals.length; i++) {
if (canMount(ship, shieldInternals[i], 'sg')) { if (canMount(ship, shieldInternals[i], 'sg')) {
ship.use(shieldInternals[i], sg) ship.use(shieldInternals[i], sg);
usedSlots.push(shieldInternals[i]) usedSlots.push(shieldInternals[i]);
break break;
} }
} }
} }
// Dual mining lasers of highest possible class; remove anything else // Dual mining lasers of highest possible class; remove anything else
const miningLaserOrder = [2, 3, 4, 1, 0] const miningLaserOrder = [2, 3, 4, 1, 0];
const miningLaserHardpoints = ship.hardpoints.concat().sort(function (a, b) { const miningLaserHardpoints = ship.hardpoints.concat().sort(function(a, b) {
return miningLaserOrder.indexOf(a.maxClass) - miningLaserOrder.indexOf(b.maxClass) return miningLaserOrder.indexOf(a.maxClass) - miningLaserOrder.indexOf(b.maxClass);
}) });
for (let s of miningLaserHardpoints) { for (let s of miningLaserHardpoints) {
if (s.maxClass >= 1 && miningLaserCount) { if (s.maxClass >= 1 && miningLaserCount) {
ship.use(s, ModuleUtils.hardpoints(s.maxClass >= 2 ? '2m' : '2l')) ship.use(s, ModuleUtils.hardpoints(s.maxClass >= 2 ? '2m' : '2l'));
miningLaserCount-- miningLaserCount--;
} else { } else {
ship.use(s, null) 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 // 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) const miningLaserDps = ship.hardpoints.filter(h => h.m != null)
.reduce(function (a, b) { .reduce(function(a, b) {
return a + b.m.getDps() return a + b.m.getDps();
}, 0) }, 0);
// Find out how many internal slots we have, and their potential cargo size // Find out how many internal slots we have, and their potential cargo size
const potentialCargo = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const potentialCargo = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.cr) .filter(a => (!a.eligible) || a.eligible.cr)
.map(b => Math.pow(2, b.maxClass)) .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 // 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) // 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 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))) let collectorLimpetsRequired = potentialCargo.length == 1 ? 0 : Math.ceil(sizeModifier * Math.min(6, Math.floor(miningLaserDps / 1.25)));
if (collectorLimpetsRequired > 0) { if (collectorLimpetsRequired > 0) {
const collectorOrder = [1, 2, 3, 4, 5, 6, 7, 8] const collectorOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const collectorInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const collectorInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.cc) .filter(a => (!a.eligible) || a.eligible.cc)
.sort((a, b) => collectorOrder.indexOf(a.maxClass) - collectorOrder.indexOf(b.maxClass)) .sort((a, b) => collectorOrder.indexOf(a.maxClass) - collectorOrder.indexOf(b.maxClass));
// Always keep at least 2 slots free for cargo racks (1 for shielded) // 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++) { for (let i = 0; i < collectorInternals.length - (shielded ? 1 : 2) && collectorLimpetsRequired > 0; i++) {
if (canMount(ship, collectorInternals[i], 'cc')) { if (canMount(ship, collectorInternals[i], 'cc')) {
// Collector only has odd classes // Collector only has odd classes
const collectorClass = collectorInternals[i].maxClass % 2 === 0 ? collectorInternals[i].maxClass - 1 : collectorInternals[i].maxClass const collectorClass = collectorInternals[i].maxClass % 2 === 0 ? collectorInternals[i].maxClass - 1 : collectorInternals[i].maxClass;
ship.use(collectorInternals[i], ModuleUtils.findInternal('cc', collectorClass, 'D')) ship.use(collectorInternals[i], ModuleUtils.findInternal('cc', collectorClass, 'D'));
usedSlots.push(collectorInternals[i]) usedSlots.push(collectorInternals[i]);
collectorLimpetsRequired -= collectorInternals[i].m.maximum collectorLimpetsRequired -= collectorInternals[i].m.maximum;
} }
} }
} }
// Power distributor to power the mining lasers indefinitely // Power distributor to power the mining lasers indefinitely
const wepRateRequired = ship.hardpoints.filter(h => h.m != null) const wepRateRequired = ship.hardpoints.filter(h => h.m != null)
.reduce(function (a, b) { .reduce(function(a, b) {
return a + b.m.getEps() return a + b.m.getEps();
}, 0) }, 0);
standardOpts.pd = ship.getAvailableModules().matchingPowerDist({weprate: wepRateRequired}).id standardOpts.pd = ship.getAvailableModules().matchingPowerDist({ weprate: wepRateRequired }).id;
// Fill the empty internals with cargo racks // Fill the empty internals with cargo racks
for (let i = ship.internal.length; i--;) { for (let i = ship.internal.length; i--;) {
let slot = ship.internal[i] let slot = ship.internal[i];
if (usedSlots.indexOf(slot) == -1 && canMount(ship, slot, 'cr')) { if (usedSlots.indexOf(slot) == -1 && canMount(ship, slot, 'cr')) {
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E')) ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
} }
} }
ship.useLightestStandard(standardOpts) ship.useLightestStandard(standardOpts);
} }
/** /**
* Racer Role * Racer Role
* @param {Ship} ship Ship instance * @param {Ship} ship Ship instance
*/ */
export function racer (ship) { export function racer(ship) {
let standardOpts = {}, let standardOpts = {},
usedSlots = [], usedSlots = [],
sgSlot, sgSlot,
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass) sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
// Cargo hatch can be disabled // Cargo hatch can be disabled
ship.setSlotEnabled(ship.cargoHatch, false) ship.setSlotEnabled(ship.cargoHatch, false);
// Shield generator // Shield generator
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8] const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1) const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sg) .filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class) .filter(a => a.maxClass >= sg.class)
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass)) .sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
for (let i = 0; i < shieldInternals.length; i++) { for (let i = 0; i < shieldInternals.length; i++) {
if (canMount(ship, shieldInternals[i], 'sg')) { if (canMount(ship, shieldInternals[i], 'sg')) {
ship.use(shieldInternals[i], sg) ship.use(shieldInternals[i], sg);
usedSlots.push(shieldInternals[i]) usedSlots.push(shieldInternals[i]);
sgSlot = shieldInternals[i] sgSlot = shieldInternals[i];
break break;
} }
} }
// Empty the hardpoints // Empty the hardpoints
for (let s of ship.hardpoints) { for (let s of ship.hardpoints) {
ship.use(s, null) ship.use(s, null);
} }
// Empty the internals // Empty the internals
for (let i = ship.internal.length; i--;) { for (let i = ship.internal.length; i--;) {
let slot = ship.internal[i] let slot = ship.internal[i];
if (usedSlots.indexOf(slot) == -1) { if (usedSlots.indexOf(slot) == -1) {
ship.use(slot, null) ship.use(slot, null);
} }
} }
// Best thrusters // Best thrusters
if (ship.standard[1].maxClass === 3) { if (ship.standard[1].maxClass === 3) {
standardOpts.th = 'tz' standardOpts.th = 'tz';
} else if (ship.standard[1].maxClass === 2) { } else if (ship.standard[1].maxClass === 2) {
standardOpts.th = 'u0' standardOpts.th = 'u0';
} else { } else {
standardOpts.th = ship.standard[1].maxClass + 'A' standardOpts.th = ship.standard[1].maxClass + 'A';
} }
// Best power distributor for more boosting // Best power distributor for more boosting
standardOpts.pd = ship.standard[4].maxClass + 'A' standardOpts.pd = ship.standard[4].maxClass + 'A';
// Smallest possible FSD drive // Smallest possible FSD drive
standardOpts.fsd = '2D' standardOpts.fsd = '2D';
// Minimal fuel tank // Minimal fuel tank
standardOpts.ft = '1C' standardOpts.ft = '1C';
// Disable nearly everything // Disable nearly everything
standardOpts.fsdDisabled = true standardOpts.fsdDisabled = true;
standardOpts.sDisabled = true standardOpts.sDisabled = true;
standardOpts.pdDisabled = true standardOpts.pdDisabled = true;
standardOpts.lsDisabled = true standardOpts.lsDisabled = true;
ship.useLightestStandard(standardOpts) ship.useLightestStandard(standardOpts);
// Apply engineering to each module // Apply engineering to each module
// ship.standard[1].m.blueprint = getBlueprint('Engine_Dirty', ship.standard[0]); // ship.standard[1].m.blueprint = getBlueprint('Engine_Dirty', ship.standard[0]);

View File

@@ -0,0 +1,82 @@
export const SI_PREFIXES = {
'Y': 1e+24, // Yotta
'Z': 1e+21, // Zetta
'E': 1e+18, // Peta
'P': 1e+15, // Peta
'T': 1e+12, // Tera
'G': 1e+9, // Giga
'M': 1e+6, // Mega
'k': 1e+3, // Kilo
'h': 1e+2, // Hekto
'da': 1e+1, // Deka
'': 1,
'd': 1e-1, // Dezi
'c': 1e-2, // Zenti
'm': 1e-3, // Milli
'μ': 1e-6, // mikro not supported due to charset
'n': 10e-9, // Nano
'p': 1e-12, // Nano
'f': 1e-15, // Femto
'a': 1e-18, // Atto
'z': 1e-21, // Zepto
'y': 1e-24 // Yokto
};
export const STATS_FORMATTING = {
'ammo': { 'format': 'int', },
'boot': { 'format': 'int', 'unit': 'secs' },
'brokenregen': { 'format': 'round1', 'unit': 'ps' },
'burst': { 'format': 'int' },
'burstrof': { 'format': 'round1', 'unit': 'ps' },
'causres': { 'format': 'pct' },
'clip': { 'format': 'int' },
'damage': { 'format': 'round' },
'dps': { 'format': 'round', 'units': 'ps', 'synthetic': 'getDps' },
'dpe': { 'format': 'round', 'units': 'ps', 'synthetic': 'getDpe' },
'distdraw': { 'format': 'round', 'unit': 'MW' },
'duration': { 'format': 'round1', 'unit': 's' },
'eff': { 'format': 'round2' },
'engcap': { 'format': 'round1', 'unit': 'MJ' },
'engrate': { 'format': 'round1', 'unit': 'MW' },
'eps': { 'format': 'round', 'units': 'ps', 'synthetic': 'getEps' },
'explres': { 'format': 'pct' },
'facinglimit': { 'format': 'round1', 'unit': 'ang' },
'falloff': { 'format': 'round', 'unit': 'km', 'storedUnit': 'm' },
'fallofffromrange': { 'format': 'round', 'unit': 'km', 'storedUnit': 'm', 'synthetic': 'getFalloff' },
'hps': { 'format': 'round', 'units': 'ps', 'synthetic': 'getHps' },
'hullboost': { 'format': 'pct1', 'change': 'additive' },
'hullreinforcement': { 'format': 'int' },
'integrity': { 'format': 'round1' },
'jitter': { 'format': 'round', 'unit': 'ang' },
'kinres': { 'format': 'pct' },
'mass': { 'format': 'round1', 'unit': 'T' },
'maxfuel': { 'format': 'round1', 'unit': 'T' },
'optmass': { 'format': 'int', 'unit': 'T' },
'optmul': { 'format': 'pct', 'change': 'additive' },
'pgen': { 'format': 'round1', 'unit': 'MW' },
'piercing': { 'format': 'int' },
'power': { 'format': 'round', 'unit': 'MW' },
'protection': { 'format': 'pct' },
'range': { 'format': 'f2', 'unit': 'km', 'storedUnit': 'm' },
'ranget': { 'format': 'f1', 'unit': 's' },
'regen': { 'format': 'round1', 'unit': 'ps' },
'reload': { 'format': 'int', 'unit': 's' },
'rof': { 'format': 'round1', 'unit': 'ps' },
'angle': { 'format': 'round1', 'unit': 'ang' },
'scanrate': { 'format': 'int' },
'scantime': { 'format': 'round1', 'unit': 's' },
'sdps': { 'format': 'round1', 'units': 'ps', 'synthetic': 'getSDps' },
'shield': { 'format': 'int', 'unit': 'MJ' },
'shieldaddition': { 'format': 'round1', 'unit': 'MJ' },
'shieldboost': { 'format': 'pct1', 'change': 'additive' },
'shieldreinforcement': { 'format': 'round1', 'unit': 'MJ' },
'shotspeed': { 'format': 'int', 'unit': 'm/s' },
'spinup': { 'format': 'round1', 'unit': 's' },
'syscap': { 'format': 'round1', 'unit': 'MJ' },
'sysrate': { 'format': 'round1', 'unit': 'MW' },
'thermload': { 'format': 'round1' },
'thermres': { 'format': 'pct' },
'wepcap': { 'format': 'round1', 'unit': 'MJ' },
'weprate': { 'format': 'round1', 'unit': 'MW' },
'jumpboost': { 'format': 'round1', 'unit': 'LY' }
};

View File

@@ -5,6 +5,7 @@ const LS_KEY_BUILDS = 'builds';
const LS_KEY_COMPARISONS = 'comparisons'; const LS_KEY_COMPARISONS = 'comparisons';
const LS_KEY_LANG = 'NG_TRANSLATE_LANG_KEY'; const LS_KEY_LANG = 'NG_TRANSLATE_LANG_KEY';
const LS_KEY_COST_TAB = 'costTab'; const LS_KEY_COST_TAB = 'costTab';
const LS_KEY_CMDR_NAME = 'cmdrName';
const LS_KEY_OUTFITTING_TAB = 'outfittingTab'; const LS_KEY_OUTFITTING_TAB = 'outfittingTab';
const LS_KEY_INSURANCE = 'insurance'; const LS_KEY_INSURANCE = 'insurance';
const LS_KEY_SHIP_DISCOUNT = 'shipDiscount'; const LS_KEY_SHIP_DISCOUNT = 'shipDiscount';
@@ -13,6 +14,8 @@ const LS_KEY_STATE = 'state';
const LS_KEY_SIZE_RATIO = 'sizeRatio'; const LS_KEY_SIZE_RATIO = 'sizeRatio';
const LS_KEY_TOOLTIPS = 'tooltips'; const LS_KEY_TOOLTIPS = 'tooltips';
const LS_KEY_MODULE_RESISTANCES = 'moduleResistances'; const LS_KEY_MODULE_RESISTANCES = 'moduleResistances';
const LS_KEY_ROLLS = 'matsPerGrade';
const LS_KEY_ORBIS = 'orbis';
let LS; let LS;
@@ -84,6 +87,8 @@ export class Persist extends EventEmitter {
} }
let moduleResistances = _get(LS_KEY_MODULE_RESISTANCES); let moduleResistances = _get(LS_KEY_MODULE_RESISTANCES);
let matsPerGrade = _get(LS_KEY_ROLLS);
let cmdrName = _get(LS_KEY_CMDR_NAME);
let tips = _get(LS_KEY_TOOLTIPS); let tips = _get(LS_KEY_TOOLTIPS);
let insurance = _getString(LS_KEY_INSURANCE); let insurance = _getString(LS_KEY_INSURANCE);
let shipDiscount = _get(LS_KEY_SHIP_DISCOUNT); let shipDiscount = _get(LS_KEY_SHIP_DISCOUNT);
@@ -91,6 +96,7 @@ export class Persist extends EventEmitter {
let buildJson = _get(LS_KEY_BUILDS); let buildJson = _get(LS_KEY_BUILDS);
let comparisonJson = _get(LS_KEY_COMPARISONS); let comparisonJson = _get(LS_KEY_COMPARISONS);
this.orbisCreds = _get(LS_KEY_ORBIS) || { email: '', password: '' };
this.onStorageChange = this.onStorageChange.bind(this); this.onStorageChange = this.onStorageChange.bind(this);
this.langCode = _getString(LS_KEY_LANG) || 'en'; this.langCode = _getString(LS_KEY_LANG) || 'en';
this.insurance = insurance && Insurance[insurance.toLowerCase()] !== undefined ? insurance : 'standard'; this.insurance = insurance && Insurance[insurance.toLowerCase()] !== undefined ? insurance : 'standard';
@@ -102,6 +108,14 @@ export class Persist extends EventEmitter {
this.outfittingTab = _getString(LS_KEY_OUTFITTING_TAB); this.outfittingTab = _getString(LS_KEY_OUTFITTING_TAB);
this.state = _get(LS_KEY_STATE); this.state = _get(LS_KEY_STATE);
this.sizeRatio = _get(LS_KEY_SIZE_RATIO) || 1; this.sizeRatio = _get(LS_KEY_SIZE_RATIO) || 1;
this.matsPerGrade = matsPerGrade || {
1: 2,
2: 2,
3: 4,
4: 4,
5: 10
};
this.cmdrName = cmdrName || { selected: '', cmdrs: [] };
this.tooltipsEnabled = tips === null ? true : tips; this.tooltipsEnabled = tips === null ? true : tips;
this.moduleResistancesEnabled = moduleResistances === null ? true : moduleResistances; this.moduleResistancesEnabled = moduleResistances === null ? true : moduleResistances;
@@ -152,6 +166,14 @@ export class Persist extends EventEmitter {
this.moduleResistancesEnabled = !!newValue && newValue.toLowerCase() == 'true'; this.moduleResistancesEnabled = !!newValue && newValue.toLowerCase() == 'true';
this.emit('moduleresistances', this.moduleResistancesEnabled); this.emit('moduleresistances', this.moduleResistancesEnabled);
break; break;
case LS_KEY_ROLLS:
this.matsPerGrade = JSON.parse(newValue);
this.emit('matsPerGrade', this.matsPerGrade);
break;
case LS_KEY_ORBIS:
this.orbisCreds = JSON.parse(newValue);
this.emit('orbis', this.orbisCreds);
break;
} }
} catch (e) { } catch (e) {
// On JSON.Parse Error - don't sync or do anything // On JSON.Parse Error - don't sync or do anything
@@ -177,6 +199,24 @@ export class Persist extends EventEmitter {
this.emit('language', langCode); this.emit('language', langCode);
} }
/**
* Get the current orbis.zone credentials
* @return {String} language code
*/
getOrbisCreds() {
return this.orbisCreds;
};
/**
* Update and save the orbis.zone credentials
* @param {Object} creds object with username and password properties.
*/
setOrbisCreds(creds) {
this.langCode = creds;
_put(LS_KEY_ORBIS, creds);
this.emit('orbis', creds);
}
/** /**
* Show tooltips setting * Show tooltips setting
* @param {boolean} show Optional - update setting * @param {boolean} show Optional - update setting
@@ -457,6 +497,31 @@ export class Persist extends EventEmitter {
return this.moduleDiscount; return this.moduleDiscount;
} }
/**
* Get the saved ship discount
* @param {Object} matsPerGrade # of rolls per grade
*/
setRolls(matsPerGrade) {
this.matsPerGrade = matsPerGrade;
_put(LS_KEY_ROLLS, this.matsPerGrade);
this.emit('matsPerGrade');
}
/**
* Get the saved Mats per grade
* @return {Object} # of rolls per grade
*/
getRolls() {
return this.matsPerGrade;
}
/**
* Get the saved Mats per grade
* @return {Object} # of rolls per grade
*/
getCmdr() {
return this.cmdrName;
}
/** /**
* Persist selected cost tab * Persist selected cost tab
* @param {number} tabName Cost tab name * @param {number} tabName Cost tab name
@@ -466,6 +531,16 @@ export class Persist extends EventEmitter {
_put(LS_KEY_COST_TAB, tabName); _put(LS_KEY_COST_TAB, tabName);
} }
/**
* Persist cmdr name
* @param {Object} cmdrName Commander name for EDEngineer
*/
setCmdr(cmdrName) {
this.cmdrName = cmdrName;
_put(LS_KEY_CMDR_NAME, cmdrName);
this.emit('cmdr');
}
/** /**
* Get the saved discount * Get the saved discount
* @return {number} val Discount value/amount * @return {number} val Discount value/amount

View File

@@ -1,431 +1,417 @@
import React from 'react'; import React from 'react';
import { Modifications } from 'coriolis-data/dist'; import { Modifications } from 'coriolis-data/dist';
/** /**
* Generate a tooltip with details of a blueprint's specials * Generate a tooltip with details of a blueprint's specials
* @param {Object} translate The translate object * @param {Object} translate The translate object
* @param {Object} blueprint The blueprint at the required grade * @param {Object} blueprint The blueprint at the required grade
* @param {string} grp The group of the module * @param {string} grp The group of the module
* @param {Object} m The module to compare with * @param {Object} m The module to compare with
* @param specialName * @param {string} specialName The name of the special
* @returns {Object} The react components * @returns {Object} The react components
*/ */
export function specialToolTip(translate, blueprint, grp, m, specialName) { export function specialToolTip(translate, blueprint, grp, m, specialName) {
const effects = []; const effects = [];
if (!blueprint || !blueprint.features) { if (!blueprint || !blueprint.features) {
return undefined; return undefined;
} }
if (m) { if (m) {
// We also add in any benefits from specials that aren't covered above // We also add in any benefits from specials that aren't covered above
if (m.blueprint) { if (m.blueprint) {
for (const feature in Modifications.modifierActions[specialName]) { for (const feature in Modifications.modifierActions[specialName]) {
// if (!blueprint.features[feature] && !m.mods.feature) { // if (!blueprint.features[feature] && !m.mods.feature) {
const featureDef = Modifications.modifications[feature]; const featureDef = Modifications.modifications[feature];
if (featureDef && !featureDef.hidden) { if (featureDef && !featureDef.hidden) {
let symbol = ''; let symbol = '';
if (feature === 'jitter') { if (feature === 'jitter') {
symbol = '°'; symbol = '°';
} else if (featureDef.type === 'percentage') { } else if (featureDef.type === 'percentage') {
symbol = '%'; symbol = '%';
} }
let current = m.getModValue(feature) - m.getModValue(feature, true); let current = m.getModValue(feature) - m.getModValue(feature, true);
if (featureDef.type === 'percentage') { if (featureDef.type === 'percentage') {
current = Math.round(current / 10) / 10; current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') { } else if (featureDef.type === 'numeric') {
current /= 100; current /= 100;
} }
const currentIsBeneficial = isValueBeneficial(feature, current); const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push( effects.push(
<tr key={feature + '_specialTT'}> <tr key={feature + '_specialTT'}>
<td style={{textAlign: 'left'}}>{translate(feature, grp)}</td> <td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td>&nbsp;</td> <td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} <td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'}
style={{textAlign: 'right'}}>{current}{symbol}</td> style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td> <td>&nbsp;</td>
</tr> </tr>
); );
} }
} }
}
} }
}
return (
return ( <div>
<div> <table width='100%'>
<table width='100%'> <tbody>
<tbody> {effects}
{effects} </tbody>
</tbody> </table>
</table> </div>
</div> );
); }
}
/**
/** * Generate a tooltip with details of a blueprint's effects
* Generate a tooltip with details of a blueprint's effects * @param {Object} translate The translate object
* @param {Object} translate The translate object * @param {Object} blueprint The blueprint at the required grade
* @param {Object} blueprint The blueprint at the required grade * @param {Array} engineers The engineers supplying this blueprint
* @param {Array} engineers The engineers supplying this blueprint * @param {string} grp The group of the module
* @param {string} grp The group of the module * @param {Object} m The module to compare with
* @param {Object} m The module to compare with * @returns {Object} The react components
* @returns {Object} The react components */
*/ export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
export function blueprintTooltip(translate, blueprint, engineers, grp, m) { const effects = [];
const effects = []; if (!blueprint || !blueprint.features) {
if (!blueprint || !blueprint.features) { return undefined;
return undefined; }
} for (const feature in blueprint.features) {
for (const feature in blueprint.features) { const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]);
const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]); const featureDef = Modifications.modifications[feature];
const featureDef = Modifications.modifications[feature]; if (!featureDef.hidden) {
if (!featureDef.hidden) { let symbol = '';
let symbol = ''; if (feature === 'jitter') {
if (feature === 'jitter') { symbol = '°';
symbol = '°'; } else if (featureDef.type === 'percentage') {
} else if (featureDef.type === 'percentage') { symbol = '%';
symbol = '%'; }
} let lowerBound = blueprint.features[feature][0];
let lowerBound = blueprint.features[feature][0]; let upperBound = blueprint.features[feature][1];
let upperBound = blueprint.features[feature][1]; if (featureDef.type === 'percentage') {
if (featureDef.type === 'percentage') { lowerBound = Math.round(lowerBound * 1000) / 10;
lowerBound = Math.round(lowerBound * 1000) / 10; upperBound = Math.round(upperBound * 1000) / 10;
upperBound = Math.round(upperBound * 1000) / 10; }
} const lowerIsBeneficial = isValueBeneficial(feature, lowerBound);
const lowerIsBeneficial = isValueBeneficial(feature, lowerBound); const upperIsBeneficial = isValueBeneficial(feature, upperBound);
const upperIsBeneficial = isValueBeneficial(feature, upperBound); if (m) {
if (m) { // We have a module - add in the current value
// We have a module - add in the current value let current = m.getModValue(feature);
let current = m.getModValue(feature); if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { current = Math.round(current / 10) / 10;
current = Math.round(current / 10) / 10; } else if (featureDef.type === 'numeric') {
} else if (featureDef.type === 'numeric') { current /= 100;
current /= 100; }
} const currentIsBeneficial = isValueBeneficial(feature, current);
const currentIsBeneficial = isValueBeneficial(feature, current); effects.push(
effects.push( <tr key={feature}>
<tr key={feature}> <td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td> <td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</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={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td> <td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td> </tr>
</tr> );
); } else {
} else { // We do not have a module, no value
// We do not have a module, no value effects.push(
effects.push( <tr key={feature}>
<tr key={feature}> <td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td> <td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</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>
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td> </tr>
</tr> );
); }
} }
} }
} if (m) {
if (m) { // Because we have a module add in any benefits that aren't part of the primary blueprint
// Because we have a module add in any benefits that aren't part of the primary blueprint for (const feature in m.mods) {
for (const feature in m.mods) { if (!blueprint.features[feature]) {
if (!blueprint.features[feature]) { const featureDef = Modifications.modifications[feature];
const featureDef = Modifications.modifications[feature]; if (featureDef && !featureDef.hidden) {
if (featureDef && !featureDef.hidden) { let symbol = '';
let symbol = ''; if (feature === 'jitter') {
if (feature === 'jitter') { symbol = '°';
symbol = '°'; } else if (featureDef.type === 'percentage') {
} else if (featureDef.type === 'percentage') { symbol = '%';
symbol = '%'; }
} let current = m.getModValue(feature);
let current = m.getModValue(feature); if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { current = Math.round(current / 10) / 10;
current = Math.round(current / 10) / 10; } else if (featureDef.type === 'numeric') {
} else if (featureDef.type === 'numeric') { current /= 100;
current /= 100; }
} const currentIsBeneficial = isValueBeneficial(feature, current);
const currentIsBeneficial = isValueBeneficial(feature, current); effects.push(
effects.push( <tr key={feature}>
<tr key={feature}> <td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td> <td>&nbsp;</td>
<td>&nbsp;</td> <td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td> <td>&nbsp;</td>
<td>&nbsp;</td> </tr>
</tr> );
); }
} }
} }
}
// We also add in any benefits from specials that aren't covered above
// We also add in any benefits from specials that aren't covered above if (m.blueprint && m.blueprint.special) {
if (m.blueprint && m.blueprint.special) { for (const feature in Modifications.modifierActions[m.blueprint.special.edname]) {
for (const feature in Modifications.modifierActions[m.blueprint.special.edname]) { if (!blueprint.features[feature] && !m.mods.feature) {
if (!blueprint.features[feature] && !m.mods.feature) { const featureDef = Modifications.modifications[feature];
const featureDef = Modifications.modifications[feature]; if (featureDef && !featureDef.hidden) {
if (featureDef && !featureDef.hidden) { let symbol = '';
let symbol = ''; if (feature === 'jitter') {
if (feature === 'jitter') { symbol = '°';
symbol = '°'; } else if (featureDef.type === 'percentage') {
} else if (featureDef.type === 'percentage') { symbol = '%';
symbol = '%'; }
} let current = m.getModValue(feature);
let current = m.getModValue(feature); if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { current = Math.round(current / 10) / 10;
current = Math.round(current / 10) / 10; } else if (featureDef.type === 'numeric') {
} else if (featureDef.type === 'numeric') { current /= 100;
current /= 100; }
} const currentIsBeneficial = isValueBeneficial(feature, current);
const currentIsBeneficial = isValueBeneficial(feature, current); effects.push(
effects.push( <tr key={feature}>
<tr key={feature}> <td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td> <td>&nbsp;</td>
<td>&nbsp;</td> <td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td> <td>&nbsp;</td>
<td>&nbsp;</td> </tr>
</tr> );
); }
} }
} }
} }
} }
}
let components;
let components; if (!m) {
if (!m) { components = [];
components = []; for (const component in blueprint.components) {
for (const component in blueprint.components) { components.push(
components.push( <tr key={component}>
<tr key={component}> <td style={{ textAlign: 'left' }}>{translate(component)}</td>
<td style={{ textAlign: 'left' }}>{translate(component)}</td> <td style={{ textAlign: 'right' }}>{blueprint.components[component]}</td>
<td style={{ textAlign: 'right' }}>{blueprint.components[component]}</td> </tr>
</tr> );
); }
} }
}
let engineersList;
let engineersList; if (engineers) {
if (engineers) { engineersList = [];
engineersList = []; for (const engineer of engineers) {
for (const engineer of engineers) { engineersList.push(
engineersList.push( <tr key={engineer}>
<tr key={engineer}> <td style={{ textAlign: 'left' }}>{engineer}</td>
<td style={{ textAlign: 'left' }}>{engineer}</td> </tr>
</tr> );
); }
} }
}
return (
return ( <div>
<div> <table width='100%'>
<table width='100%'> <thead>
<thead> <tr>
<tr> <td>{translate('feature')}</td>
<td>{translate('feature')}</td> <td>{translate('worst')}</td>
<td>{translate('worst')}</td> {m ? <td>{translate('current')}</td> : null }
{m ? <td>{translate('current')}</td> : null } <td>{translate('best')}</td>
<td>{translate('best')}</td> </tr>
</tr> </thead>
</thead> <tbody>
<tbody> {effects}
{effects} </tbody>
</tbody> </table>
</table> { components ? <table width='100%'>
{ components ? <table width='100%'> <thead>
<thead> <tr>
<tr> <td>{translate('component')}</td>
<td>{translate('component')}</td> <td>{translate('amount')}</td>
<td>{translate('amount')}</td> </tr>
</tr> </thead>
</thead> <tbody>
<tbody> {components}
{components} </tbody>
</tbody> </table> : null }
</table> : null } { engineersList ? <table width='100%'>
{ engineersList ? <table width='100%'> <thead>
<thead> <tr>
<tr> <td>{translate('engineers')}</td>
<td>{translate('engineers')}</td> </tr>
</tr> </thead>
</thead> <tbody>
<tbody> {engineersList}
{engineersList} </tbody>
</tbody> </table> : null }
</table> : null } </div>
</div> );
); }
}
/**
/** * Is this blueprint feature beneficial?
* Is this blueprint feature beneficial? * @param {string} feature The name of the feature
* @param {string} feature The name of the feature * @param {array} values The value of the feature
* @param {array} values The value of the feature * @returns {boolean} True if this feature is beneficial
* @returns {boolean} True if this feature is beneficial */
*/ export function isBeneficial(feature, values) {
export function isBeneficial(feature, values) { const fact = (values[0] < 0 || (values[0] === 0 && values[1] < 0));
const fact = (values[0] < 0 || (values[0] === 0 && values[1] < 0)); if (Modifications.modifications[feature].higherbetter) {
if (Modifications.modifications[feature].higherbetter) { return !fact;
return !fact; } else {
} else { return fact;
return fact; }
} }
}
/**
/** * Is this feature value beneficial?
* Is this feature value beneficial? * @param {string} feature The name of the feature
* @param {string} feature The name of the feature * @param {number} value The value of the feature
* @param {number} value The value of the feature * @returns {boolean} True if this value is beneficial
* @returns {boolean} True if this value is beneficial */
*/ export function isValueBeneficial(feature, value) {
export function isValueBeneficial(feature, value) { if (Modifications.modifications[feature].higherbetter) {
if (Modifications.modifications[feature].higherbetter) { return value > 0;
return value > 0; } else {
} else { return value < 0;
return value < 0; }
} }
}
/**
/** * Get a blueprint with a given name and an optional module
* Get a blueprint with a given name and an optional module * @param {string} name The name of the blueprint
* @param {string} name The name of the blueprint * @param {Object} module The module for which to obtain this blueprint
* @param {Object} module The module for which to obtain this blueprint * @returns {Object} The matching blueprint
* @returns {Object} The matching blueprint */
*/ export function getBlueprint(name, module) {
export function getBlueprint(name, module) { // Start with a copy of the blueprint
// Start with a copy of the blueprint const findMod = val => Object.keys(Modifications.blueprints).find(elem => elem.toString().toLowerCase().search(val.toString().toLowerCase().replace(/(OutfittingFieldType_|persecond)/igm, '')) >= 0);
const findMod = val => Object.keys(Modifications.blueprints).find(elem => elem.toString().toLowerCase().search(val.toString().toLowerCase().replace(/(OutfittingFieldType_|persecond)/igm, '')) >= 0) const found = Modifications.blueprints[findMod(name)];
const found = Modifications.blueprints[findMod(name)]; if (!found || !found.fdname) {
if (!found || !found.fdname) { return {};
return {}; }
} const blueprint = JSON.parse(JSON.stringify(found));
const blueprint = JSON.parse(JSON.stringify(found)); return blueprint;
if (module) { }
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 * Provide 'percent' primary modifications
// accurate as per the information in Elite * @param {Object} ship The ship for which to perform the modifications
for (const grade in blueprint.grades) { * @param {Object} m The module for which to perform the modifications
for (const feature in blueprint.grades[grade].features) { * @param {Number} percent The percent to set values to of full.
if (feature === 'shieldboost') { */
blueprint.grades[grade].features[feature][0] = ((1 + blueprint.grades[grade].features[feature][0]) * (1 + module.shieldboost) - 1) / module.shieldboost - 1; export function setPercent(ship, m, percent) {
blueprint.grades[grade].features[feature][1] = ((1 + blueprint.grades[grade].features[feature][1]) * (1 + module.shieldboost) - 1) / module.shieldboost - 1; ship.clearModifications(m);
} // Pick given value as multiplier
} const mult = percent / 100;
} const features = m.blueprint.grades[m.blueprint.grade].features;
} for (const featureName in features) {
} let value;
return blueprint; 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);
* Provide 'percent' primary modifications } else {
* @param {Object} ship The ship for which to perform the modifications value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
* @param {Object} m The module for which to perform the modifications }
* @param {Number} percent The percent to set values to of full. } else {
*/ // Higher is worse, but is this making it better or worse?
export function setPercent(ship, m, percent) { if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
ship.clearModifications(m); value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
// Pick given value as multiplier } else {
const mult = percent / 100; value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
const features = m.blueprint.grades[m.blueprint.grade].features; }
for (const featureName in features) { }
let value;
if (Modifications.modifications[featureName].higherbetter) { _setValue(ship, m, featureName, value);
// 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); * Provide 'random' primary modifications
} * @param {Object} ship The ship for which to perform the modifications
} else { * @param {Object} m The module for which to perform the modifications
// Higher is worse, but is this making it better or worse? */
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { export function setRandom(ship, m) {
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult); // Pick a single value for our randomness
} else { setPercent(ship, m, Math.random() * 100);
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult); }
}
} /**
* Set a modification feature value
_setValue(ship, m, featureName, 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
/** */
* Provide 'random' primary modifications function _setValue(ship, m, featureName, value) {
* @param {Object} ship The ship for which to perform the modifications if (Modifications.modifications[featureName].type == 'percentage') {
* @param {Object} m The module for which to perform the modifications ship.setModification(m, featureName, value * 10000);
*/ } else if (Modifications.modifications[featureName].type == 'numeric') {
export function setRandom(ship, m) { ship.setModification(m, featureName, value * 100);
// Pick a single value for our randomness } else {
setPercent(ship, m, Math.random() * 100); ship.setModification(m, featureName, value);
} }
}
/**
* Set a modification feature value /**
* @param {Object} ship The ship for which to perform the modifications * Provide 'percent' primary query
* @param {Object} m The module for which to perform the modifications * @param {Object} m The module for which to perform the query
* @param {string} featureName The feature being set * @returns {Number} percent The percentage indicator of current applied values.
* @param {number} value The value being set for the feature */
*/ export function getPercent(m) {
function _setValue(ship, m, featureName, value) { let result = null;
if (Modifications.modifications[featureName].type == 'percentage') { const features = m.blueprint.grades[m.blueprint.grade].features;
ship.setModification(m, featureName, value * 10000); for (const featureName in features) {
} else if (Modifications.modifications[featureName].type == 'numeric') { if (features[featureName][0] === features[featureName][1]) {
ship.setModification(m, featureName, value * 100); continue;
} else { }
ship.setModification(m, featureName, value);
} let value = _getValue(m, featureName);
} let mult;
if (featureName == 'shieldboost') {
/** mult = ((1 + value) * (1 + m.shieldboost)) - 1 - m.shieldboost;
* Provide 'percent' primary query } else if (Modifications.modifications[featureName].higherbetter) {
* @param {Object} m The module for which to perform the query // Higher is better, but is this making it better or worse?
* @returns {Number} percent The percentage indicator of current applied values. if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
*/ mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100);
export function getPercent(m) { } else {
let result = null; mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100);
const features = m.blueprint.grades[m.blueprint.grade].features; }
for (const featureName in features) { } else {
// Higher is worse, but is this making it better or worse?
if (features[featureName][0] === features[featureName][1]) { if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
continue; mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100);
} } else {
mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100);
let value = _getValue(m, featureName); }
let mult; }
if (Modifications.modifications[featureName].higherbetter) {
// Higher is better, but is this making it better or worse? if (result && result != mult) {
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { return null;
mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100); } else if (result != mult) {
} else { result = mult;
mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100); }
} }
} else {
// Higher is worse, but is this making it better or worse? return result;
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { }
mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100);
} else { /**
mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100); * Query a feature value
} * @param {Object} m The module for which to perform the query
} * @param {string} featureName The feature being queried
* @returns {number} The value of the modification as a %
if (result && result != mult) { */
return null; function _getValue(m, featureName) {
} else if (result != mult) { if (Modifications.modifications[featureName].type == 'percentage') {
result = mult; return m.getModValue(featureName, true) / 10000;
} } else if (Modifications.modifications[featureName].type == 'numeric') {
} return m.getModValue(featureName, true) / 100;
} else {
return result; return m.getModValue(featureName, true);
} }
}
/**
* Query a feature value
* @param {Object} m The module for which to perform the query
* @param {string} featureName The feature being queried
*/
function _getValue(m, featureName) {
if (Modifications.modifications[featureName].type == 'percentage') {
return m.getModValue(featureName, true) / 10000;
} else if (Modifications.modifications[featureName].type == 'numeric') {
return m.getModValue(featureName, true) / 100;
} else {
return m.getModValue(featureName, true);
}
}

View File

@@ -6,7 +6,7 @@ import { getBlueprint } from '../utils/BlueprintFunctions';
import * as ModuleUtils from '../shipyard/ModuleUtils'; import * as ModuleUtils from '../shipyard/ModuleUtils';
// mapping from fd's ship model names to coriolis' // mapping from fd's ship model names to coriolis'
const SHIP_FD_NAME_TO_CORIOLIS_NAME = { export const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
'Adder': 'adder', 'Adder': 'adder',
'Anaconda': 'anaconda', 'Anaconda': 'anaconda',
'Asp': 'asp', 'Asp': 'asp',
@@ -29,6 +29,7 @@ const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
'FerDeLance': 'fer_de_lance', 'FerDeLance': 'fer_de_lance',
'Hauler': 'hauler', 'Hauler': 'hauler',
'Independant_Trader': 'keelback', 'Independant_Trader': 'keelback',
'Krait_MkII': 'krait_mkii',
'Orca': 'orca', 'Orca': 'orca',
'Python': 'python', 'Python': 'python',
'SideWinder': 'sidewinder', 'SideWinder': 'sidewinder',
@@ -37,6 +38,8 @@ const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
'Type9': 'type_9_heavy', 'Type9': 'type_9_heavy',
'Type9_Military': 'type_10_defender', 'Type9_Military': 'type_10_defender',
'TypeX': 'alliance_chieftain', 'TypeX': 'alliance_chieftain',
'TypeX_2': 'alliance_crusader',
'TypeX_3': 'alliance_challenger',
'Viper': 'viper', 'Viper': 'viper',
'Viper_MkIV': 'viper_mk_iv', 'Viper_MkIV': 'viper_mk_iv',
'Vulture': 'vulture' 'Vulture': 'vulture'
@@ -318,7 +321,7 @@ function _addModifications(module, modifiers, blueprint, grade, specialModificat
if (!modifiers) return; if (!modifiers) return;
let special; let special;
if (specialModifications) { if (specialModifications) {
special = Modifications.specials[Object.keys(specialModifications)[0]] special = Modifications.specials[Object.keys(specialModifications)[0]];
} }
for (const i in modifiers) { for (const i in modifiers) {
// Some special modifications // Some special modifications
@@ -345,7 +348,7 @@ function _addModifications(module, modifiers, blueprint, grade, specialModificat
let value; let value;
if (i === 'OutfittingFieldType_DefenceModifierShieldMultiplier') { if (i === 'OutfittingFieldType_DefenceModifierShieldMultiplier') {
value = modifiers[i].value - 1; value = modifiers[i].value - 1;
} else if (i === 'OutfittingFieldType_DefenceModifierHealthMultiplier' && blueprint.startsWith('Armour_')) { } else if (i === 'OutfittingFieldType_DefenceModifierHealthMultiplier' && blueprint.startsWith('Armour_')) {
value = (modifiers[i].value - module.hullboost) / module.hullboost; value = (modifiers[i].value - module.hullboost) / module.hullboost;
} else if (i === 'OutfittingFieldType_DefenceModifierHealthMultiplier') { } else if (i === 'OutfittingFieldType_DefenceModifierHealthMultiplier') {
value = modifiers[i].value / module.hullboost; value = modifiers[i].value / module.hullboost;

View File

@@ -1,292 +1,297 @@
import Ship from '../shipyard/Ship' import Ship from '../shipyard/Ship';
import { HARDPOINT_NUM_TO_CLASS, shipModelFromJson } from './CompanionApiUtils' import { HARDPOINT_NUM_TO_CLASS, shipModelFromJson } from './CompanionApiUtils';
import { Ships } from 'coriolis-data/dist' import { Ships } from 'coriolis-data/dist';
import Module from '../shipyard/Module' import Module from '../shipyard/Module';
import { Modules } from 'coriolis-data/dist' import { Modules } from 'coriolis-data/dist';
import { Modifications } from 'coriolis-data/dist' import { Modifications } from 'coriolis-data/dist';
import { getBlueprint } from './BlueprintFunctions' import { getBlueprint } from './BlueprintFunctions';
/** /**
* Obtain a module given its FD Name * Obtain a module given its FD Name
* @param {string} fdname the FD Name of the module * @param {string} fdname the FD Name of the module
* @return {Module} the module * @return {Module} the module
*/ */
function _moduleFromFdName (fdname) { function _moduleFromFdName(fdname) {
if (!fdname) return null if (!fdname) return null;
fdname = fdname.toLowerCase() fdname = fdname.toLowerCase();
// Check standard modules // Check standard modules
for (const grp in Modules.standard) { for (const grp in Modules.standard) {
if (Modules.standard.hasOwnProperty(grp)) { if (Modules.standard.hasOwnProperty(grp)) {
for (const i in Modules.standard[grp]) { for (const i in Modules.standard[grp]) {
if (Modules.standard[grp][i].symbol && Modules.standard[grp][i].symbol.toLowerCase() === fdname) { if (Modules.standard[grp][i].symbol && Modules.standard[grp][i].symbol.toLowerCase() === fdname) {
// Found it // Found it
return new Module({template: Modules.standard[grp][i]}) return new Module({ template: Modules.standard[grp][i] });
} }
} }
} }
} }
// Check hardpoint modules // Check hardpoint modules
for (const grp in Modules.hardpoints) { for (const grp in Modules.hardpoints) {
if (Modules.hardpoints.hasOwnProperty(grp)) { if (Modules.hardpoints.hasOwnProperty(grp)) {
for (const i in Modules.hardpoints[grp]) { for (const i in Modules.hardpoints[grp]) {
if (Modules.hardpoints[grp][i].symbol && Modules.hardpoints[grp][i].symbol.toLowerCase() === fdname) { if (Modules.hardpoints[grp][i].symbol && Modules.hardpoints[grp][i].symbol.toLowerCase() === fdname) {
// Found it // Found it
return new Module({template: Modules.hardpoints[grp][i]}) return new Module({ template: Modules.hardpoints[grp][i] });
} }
} }
} }
} }
// Check internal modules // Check internal modules
for (const grp in Modules.internal) { for (const grp in Modules.internal) {
if (Modules.internal.hasOwnProperty(grp)) { if (Modules.internal.hasOwnProperty(grp)) {
for (const i in Modules.internal[grp]) { for (const i in Modules.internal[grp]) {
if (Modules.internal[grp][i].symbol && Modules.internal[grp][i].symbol.toLowerCase() === fdname) { if (Modules.internal[grp][i].symbol && Modules.internal[grp][i].symbol.toLowerCase() === fdname) {
// Found it // Found it
return new Module({template: Modules.internal[grp][i]}) return new Module({ template: Modules.internal[grp][i] });
} }
} }
} }
} }
// Not found // Not found
return null return null;
} }
/** /**
* Build a ship from the journal Loadout event JSON * Build a ship from the journal Loadout event JSON
* @param {object} json the Loadout event JSON * @param {object} json the Loadout event JSON
* @return {Ship} the built ship * @return {Ship} the built ship
*/ */
export function shipFromLoadoutJSON (json) { export function shipFromLoadoutJSON(json) {
// Start off building a basic ship // Start off building a basic ship
const shipModel = shipModelFromJson(json) const shipModel = shipModelFromJson(json);
if (!shipModel) { if (!shipModel) {
throw 'No such ship found: "' + json.Ship + '"' throw 'No such ship found: "' + json.Ship + '"';
} }
const shipTemplate = Ships[shipModel] const shipTemplate = Ships[shipModel];
let ship = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots) let ship = new Ship(shipModel, shipTemplate.properties, shipTemplate.slots);
ship.buildWith(null) ship.buildWith(null);
// Initial Ship building, don't do engineering yet. // Initial Ship building, don't do engineering yet.
let opts = []; let modsToAdd = [];
for (const module of json.Modules) { for (const module of json.Modules) {
switch (module.Slot.toLowerCase()) { switch (module.Slot.toLowerCase()) {
// Cargo Hatch. // Cargo Hatch.
case 'cargohatch': case 'cargohatch':
ship.cargoHatch.enabled = module.On ship.cargoHatch.enabled = module.On;
ship.cargoHatch.priority = module.Priority ship.cargoHatch.priority = module.Priority;
break break;
// Add the bulkheads // Add the bulkheads
case 'armour': case 'armour':
if (module.Item.toLowerCase().endsWith('_armour_grade1')) { if (module.Item.toLowerCase().endsWith('_armour_grade1')) {
ship.useBulkhead(0, true) ship.useBulkhead(0, true);
} else if (module.Item.toLowerCase().endsWith('_armour_grade2')) { } else if (module.Item.toLowerCase().endsWith('_armour_grade2')) {
ship.useBulkhead(1, true) ship.useBulkhead(1, true);
} else if (module.Item.toLowerCase().endsWith('_armour_grade3')) { } else if (module.Item.toLowerCase().endsWith('_armour_grade3')) {
ship.useBulkhead(2, true) ship.useBulkhead(2, true);
} else if (module.Item.toLowerCase().endsWith('_armour_mirrored')) { } else if (module.Item.toLowerCase().endsWith('_armour_mirrored')) {
ship.useBulkhead(3, true) ship.useBulkhead(3, true);
} else if (module.Item.toLowerCase().endsWith('_armour_reactive')) { } else if (module.Item.toLowerCase().endsWith('_armour_reactive')) {
ship.useBulkhead(4, true) ship.useBulkhead(4, true);
} else { } else {
throw 'Unknown bulkheads "' + module.Item + '"' throw 'Unknown bulkheads "' + module.Item + '"';
} }
ship.bulkheads.enabled = true ship.bulkheads.enabled = true;
if (module.Engineering) _addModifications(ship.bulkheads.m, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect) if (module.Engineering) _addModifications(ship.bulkheads.m, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break break;
case 'powerplant': case 'powerplant':
const powerplant = _moduleFromFdName(module.Item) const powerplant = _moduleFromFdName(module.Item);
ship.use(ship.standard[0], powerplant, true) ship.use(ship.standard[0], powerplant, true);
ship.standard[0].enabled = module.On ship.standard[0].enabled = module.On;
ship.standard[0].priority = module.Priority ship.standard[0].priority = module.Priority;
if (module.Engineering) _addModifications(powerplant, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect) if (module.Engineering) _addModifications(powerplant, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break break;
case 'mainengines': case 'mainengines':
const thrusters = _moduleFromFdName(module.Item) const thrusters = _moduleFromFdName(module.Item);
ship.use(ship.standard[1], thrusters, true) ship.use(ship.standard[1], thrusters, true);
ship.standard[1].enabled = module.On ship.standard[1].enabled = module.On;
ship.standard[1].priority = module.Priority ship.standard[1].priority = module.Priority;
if (module.Engineering) _addModifications(thrusters, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect) if (module.Engineering) _addModifications(thrusters, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break break;
case 'frameshiftdrive': case 'frameshiftdrive':
const frameshiftdrive = _moduleFromFdName(module.Item) const frameshiftdrive = _moduleFromFdName(module.Item);
ship.use(ship.standard[2], frameshiftdrive, true) ship.use(ship.standard[2], frameshiftdrive, true);
ship.standard[2].enabled = module.On ship.standard[2].enabled = module.On;
ship.standard[2].priority = module.Priority ship.standard[2].priority = module.Priority;
if (module.Engineering) _addModifications(frameshiftdrive, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect) if (module.Engineering) _addModifications(frameshiftdrive, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break break;
case 'lifesupport': case 'lifesupport':
const lifesupport = _moduleFromFdName(module.Item) const lifesupport = _moduleFromFdName(module.Item);
ship.use(ship.standard[3], lifesupport, true) ship.use(ship.standard[3], lifesupport, true);
ship.standard[3].enabled = module.On === true ship.standard[3].enabled = module.On === true;
ship.standard[3].priority = module.Priority ship.standard[3].priority = module.Priority;
if (module.Engineering) _addModifications(lifesupport, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect) if (module.Engineering) _addModifications(lifesupport, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break break;
case 'powerdistributor': case 'powerdistributor':
const powerdistributor = _moduleFromFdName(module.Item) const powerdistributor = _moduleFromFdName(module.Item);
ship.use(ship.standard[4], powerdistributor, true) ship.use(ship.standard[4], powerdistributor, true);
ship.standard[4].enabled = module.On ship.standard[4].enabled = module.On;
ship.standard[4].priority = module.Priority ship.standard[4].priority = module.Priority;
if (module.Engineering) _addModifications(powerdistributor, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect) if (module.Engineering) _addModifications(powerdistributor, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break break;
case 'radar': case 'radar':
const sensors = _moduleFromFdName(module.Item) const sensors = _moduleFromFdName(module.Item);
ship.use(ship.standard[5], sensors, true) ship.use(ship.standard[5], sensors, true);
ship.standard[5].enabled = module.On ship.standard[5].enabled = module.On;
ship.standard[5].priority = module.Priority ship.standard[5].priority = module.Priority;
if (module.Engineering) _addModifications(sensors, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect) if (module.Engineering) _addModifications(sensors, module.Engineering.Modifiers, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect);
break break;
case 'fueltank': case 'fueltank':
const fueltank = _moduleFromFdName(module.Item) const fueltank = _moduleFromFdName(module.Item);
ship.use(ship.standard[6], fueltank, true) ship.use(ship.standard[6], fueltank, true);
ship.standard[6].enabled = true ship.standard[6].enabled = true;
ship.standard[6].priority = 0 ship.standard[6].priority = 0;
break break;
default: default:
} }
for (const module of json.Modules) { for (const module of json.Modules) {
if (module.Slot.toLowerCase().search(/hardpoint/) !== -1) { if (module.Slot.toLowerCase().search(/hardpoint/) !== -1) {
// Add hardpoints // Add hardpoints
let hardpoint; let hardpoint;
let hardpointClassNum = -1 let hardpointClassNum = -1;
let hardpointSlotNum = -1 let hardpointSlotNum = -1;
let hardpointArrayNum = 0 let hardpointArrayNum = 0;
for (let i in shipTemplate.slots.hardpoints) { for (let i in shipTemplate.slots.hardpoints) {
if (shipTemplate.slots.hardpoints[i] === hardpointClassNum) { if (shipTemplate.slots.hardpoints[i] === hardpointClassNum) {
// Another slot of the same class // Another slot of the same class
hardpointSlotNum++ hardpointSlotNum++;
} else { } else {
// The first slot of a new class // The first slot of a new class
hardpointClassNum = shipTemplate.slots.hardpoints[i] hardpointClassNum = shipTemplate.slots.hardpoints[i];
hardpointSlotNum = 1 hardpointSlotNum = 1;
} }
// Now that we know what we're looking for, find it // Now that we know what we're looking for, find it
const hardpointName = HARDPOINT_NUM_TO_CLASS[hardpointClassNum] + 'Hardpoint' + hardpointSlotNum const hardpointName = HARDPOINT_NUM_TO_CLASS[hardpointClassNum] + 'Hardpoint' + hardpointSlotNum;
const hardpointSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === hardpointName.toLowerCase()) const hardpointSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === hardpointName.toLowerCase());
if (!hardpointSlot) { if (!hardpointSlot) {
// This can happen with old imports that don't contain new hardpoints // This can happen with old imports that don't contain new hardpoints
} else if (!hardpointSlot) { } else if (!hardpointSlot) {
// No module // No module
} else { } else {
hardpoint = _moduleFromFdName(hardpointSlot.Item) hardpoint = _moduleFromFdName(hardpointSlot.Item);
ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true) ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true);
ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On;
ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority;
opts.push({coriolisMod: hardpoint, json: hardpointSlot}); modsToAdd.push({ coriolisMod: hardpoint, json: hardpointSlot });
} }
hardpointArrayNum++ hardpointArrayNum++;
} }
} }
if (module.Slot.toLowerCase().search(/slot\d/) !== -1) { if (module.Slot.toLowerCase().search(/slot\d/) !== -1) {
let internalSlotNum = 1 let internalSlotNum = 1;
let militarySlotNum = 1 let militarySlotNum = 1;
for (let i in shipTemplate.slots.internal) { for (let i in shipTemplate.slots.internal) {
const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name = 'military' : false if (!shipTemplate.slots.internal.hasOwnProperty(i)) {
continue;
// The internal slot might be a standard or a military slot. Military slots have a different naming system }
let internalSlot = null const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name = 'military' : false;
if (isMilitary) {
const internalName = 'Military0' + militarySlotNum // The internal slot might be a standard or a military slot. Military slots have a different naming system
internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase()) let internalSlot = null;
militarySlotNum++ if (isMilitary) {
} else { const internalName = 'Military0' + militarySlotNum;
// Slot numbers are not contiguous so handle skips. internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
while (internalSlot === null && internalSlotNum < 99) { militarySlotNum++;
// Slot sizes have no relationship to the actual size, either, so check all possibilities } else {
for (let slotsize = 0; slotsize < 9; slotsize++) { // Slot numbers are not contiguous so handle skips.
const internalName = 'Slot' + (internalSlotNum <= 9 ? '0' : '') + internalSlotNum + '_Size' + slotsize while (internalSlot === null && internalSlotNum < 99) {
if (json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase())) { // Slot sizes have no relationship to the actual size, either, so check all possibilities
internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase()); for (let slotsize = 0; slotsize < 9; slotsize++) {
break const internalName = 'Slot' + (internalSlotNum <= 9 ? '0' : '0') + internalSlotNum + '_Size' + slotsize;
} if (json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase())) {
} internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase());
internalSlotNum++ break;
} }
} }
internalSlotNum++;
if (!internalSlot) { }
// This can happen with old imports that don't contain new slots }
} else if (!internalSlot) {
// No module if (!internalSlot) {
} else { // This can happen with old imports that don't contain new slots
const internalJson = internalSlot } else {
const internal = _moduleFromFdName(internalJson.Item) const internalJson = internalSlot;
ship.use(ship.internal[i], internal, true) const internal = _moduleFromFdName(internalJson.Item);
ship.internal[i].enabled = internalJson.On === true ship.use(ship.internal[i], internal, true);
ship.internal[i].priority = internalJson.Priority ship.internal[i].enabled = internalJson.On === true;
opts.push({coriolisMod: internal, json: internalSlot}); ship.internal[i].priority = internalJson.Priority;
} modsToAdd.push({ coriolisMod: internal, json: internalSlot });
} }
} }
} }
} }
}
for (const i of opts) {
if (i.json.Engineering) _addModifications(i.coriolisMod, i.json.Engineering.Modifiers, i.json.Engineering.BlueprintName, i.json.Engineering.Level, i.json.Engineering.ExperimentalEffect) for (const i of modsToAdd) {
} if (i.json.Engineering) {
// We don't have any information on it so guess it's priority 5 and disabled _addModifications(i.coriolisMod, i.json.Engineering.Modifiers, i.json.Engineering.BlueprintName, i.json.Engineering.Level, i.json.Engineering.ExperimentalEffect);
if (!ship.cargoHatch) { }
ship.cargoHatch.enabled = false }
ship.cargoHatch.priority = 4 // We don't have any information on it so guess it's priority 5 and disabled
} if (!ship.cargoHatch) {
console.log(ship) ship.cargoHatch.enabled = false;
ship.cargoHatch.priority = 4;
// Now update the ship's codes before returning it }
return ship.updatePowerPrioritesString().updatePowerEnabledString().updateModificationsString()
} // Now update the ship's codes before returning it
return ship.updatePowerPrioritesString().updatePowerEnabledString().updateModificationsString();
/** }
* Add the modifications for a module
* @param {Module} module the module /**
* @param {Object} modifiers the modifiers * Add the modifications for a module
* @param {Object} blueprint the blueprint of the modification * @param {Module} module the module
* @param {Object} grade the grade of the modification * @param {Object} modifiers the modifiers
* @param {Object} specialModifications special modification * @param {Object} blueprint the blueprint of the modification
*/ * @param {Object} grade the grade of the modification
function _addModifications (module, modifiers, blueprint, grade, specialModifications) { * @param {Object} specialModifications special modification
if (!modifiers) return */
let special function _addModifications(module, modifiers, blueprint, grade, specialModifications) {
if (specialModifications) { if (!modifiers) return;
special = Modifications.specials[specialModifications] let special;
} if (specialModifications) {
for (const i in modifiers) { special = Modifications.specials[specialModifications];
// Some special modifications }
// Look up the modifiers to find what we need to do // Add the blueprint definition, grade and special
const findMod = val => Object.keys(Modifications.modifierActions).find(elem => elem.toString().toLowerCase().replace(/(outfittingfieldtype_|persecond)/igm, '') === val.toString().toLowerCase().replace(/(outfittingfieldtype_|persecond)/igm, '')) if (blueprint) {
const modifierActions = Modifications.modifierActions[findMod(modifiers[i].Label)] module.blueprint = getBlueprint(blueprint, module);
//TODO: Figure out how to scale this value. if (grade) {
if (!!modifiers[i].LessIsGood) { module.blueprint.grade = Number(grade);
}
} if (special) {
let value = (modifiers[i].Value / modifiers[i].OriginalValue * 100 - 100) * 100; module.blueprint.special = special;
if (value === Infinity) { }
value = modifiers[i].Value * 100; }
} for (const i in modifiers) {
if (modifiers[i].Label.search('Resistance') >= 0) { // Some special modifications
value = (modifiers[i].Value * 100) - (modifiers[i].OriginalValue * 100) // Look up the modifiers to find what we need to do
} const findMod = val => Object.keys(Modifications.modifierActions).find(elem => elem.toString().toLowerCase().replace(/(outfittingfieldtype_|persecond)/igm, '') === val.toString().toLowerCase().replace(/(outfittingfieldtype_|persecond)/igm, ''));
// Carry out the required changes const modifierActions = Modifications.modifierActions[findMod(modifiers[i].Label)];
for (const action in modifierActions) { // TODO: Figure out how to scale this value.
if (isNaN(modifierActions[action])) { if (!!modifiers[i].LessIsGood) {
module.setModValue(action, modifierActions[action])
} else { }
module.setModValue(action, value) let value = (modifiers[i].Value / modifiers[i].OriginalValue * 100 - 100) * 100;
} if (value === Infinity) {
} value = modifiers[i].Value * 100;
} }
if (modifiers[i].Label.search('Resistance') >= 0) {
// Add the blueprint definition, grade and special value = (modifiers[i].Value * 100) - (modifiers[i].OriginalValue * 100);
if (blueprint) { }
module.blueprint = getBlueprint(blueprint, module) if (modifiers[i].Label.search('ShieldMultiplier') >= 0 || modifiers[i].Label.search('DefenceModifierHealthMultiplier') >= 0) {
if (grade) { value = ((100 + modifiers[i].Value) / (100 + modifiers[i].OriginalValue) * 100 - 100) * 100;
module.blueprint.grade = Number(grade) }
}
if (special) { // Carry out the required changes
module.blueprint.special = special for (const action in modifierActions) {
} if (isNaN(modifierActions[action])) {
} module.setModValue(action, modifierActions[action]);
} } else {
module.setModValue(action, value, true);
}
}
}
}

View File

@@ -1,14 +1,23 @@
import request from 'superagent'; import request from 'superagent';
let agent;
try {
agent = request.agent(); // apparently this crashes somehow
} catch (e) {
console.error(e);
}
if (!agent) {
agent = request;
}
/** /**
* Shorten a URL * Shorten a URL
* @param {string} url The URL to shorten * @param {string} url The URL to shorten
* @param {function} success Success callback * @param {function} success Success callback
* @param {function} error Failure/Error callback * @param {function} error Failure/Error callback
*/ */
export default function shorternUrl(url, success, error) { export default function shorternUrl(url, success, error) {
shortenUrlEddp(url, success, error); orbisShorten(url, success, error);
} }
const SHORTEN_API_GOOGLE = 'https://www.googleapis.com/urlshortener/v1/url?key='; const SHORTEN_API_GOOGLE = 'https://www.googleapis.com/urlshortener/v1/url?key=';
@@ -64,3 +73,68 @@ function shortenUrlEddp(url, success, error) {
error('Not Online'); error('Not Online');
} }
} }
const SHORTEN_API_ORBIS = 'https://s.orbis.zone/api.php';
/**
* Shorten a URL using Orbis's URL shortener API
* @param {string} url The URL to shorten
* @param {function} success Success callback
* @param {function} error Failure/Error callback
*/
function orbisShorten(url, success, error) {
if (window.navigator.onLine) {
try {
request.post(SHORTEN_API_ORBIS)
.field('action', 'shorturl')
.field('url', url)
.field('format', 'json')
.end(function(err, response) {
if (err) {
console.error(err);
error('Bad Request');
} else {
success(response.body.shorturl);
}
});
} catch (e) {
console.log(e);
error(e.message ? e.message : e);
}
} else {
error('Not Online');
}
}
const API_ORBIS = 'https://orbis.zone/api/builds/add';
/**
* Upload to Orbis
* @param {object} ship The URL to shorten
* @param {object} creds Orbis credentials
* @return {Promise<any>} Either a URL or error message.
*/
export function orbisUpload(ship, creds) {
return new Promise(async (resolve, reject) => {
if (window.navigator.onLine) {
try {
agent
.post(API_ORBIS)
.withCredentials()
.redirects(0)
.set('Content-Type', 'application/json')
.send(ship)
.end(function(err, response) {
if (err) {
reject('Bad Request');
} else {
resolve(response.body.link);
}
});
} catch (e) {
console.log(e);
reject(e.message ? e.message : e);
}
} else {
reject('Not Online');
}
});
}

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html> <!DOCTYPE html>
<html <%= htmlWebpackPlugin.options.appCache ? 'manifest=/' + htmlWebpackPlugin.options.appCache : '' %> > <html manifest="/">
<head> <head>
<title>Coriolis EDCD Edition</title> <title>Coriolis EDCD Edition</title>
<meta charset="UTF-8"> <meta charset="UTF-8">
@@ -26,7 +26,8 @@
<script> <script>
window.CORIOLIS_GAPI_KEY = '<%- htmlWebpackPlugin.options.gapiKey %>'; window.CORIOLIS_GAPI_KEY = '<%- htmlWebpackPlugin.options.gapiKey %>';
window.CORIOLIS_VERSION = '<%- htmlWebpackPlugin.options.version %>'; window.CORIOLIS_VERSION = '<%- htmlWebpackPlugin.options.version %>';
window.CORIOLIS_DATE = '<%- new Date().toISOString().slice(0, 10) %>'; window.CORIOLIS_DATE = '<%- htmlWebpackPlugin.options.date.toISOString().slice(0, 10) %>';
window.BUGSNAG_VERSION = '<%- htmlWebpackPlugin.options.version + '-' + htmlWebpackPlugin.options.date.toISOString() %>';
</script> </script>
<% if (htmlWebpackPlugin.options.uaTracking) { %> <% if (htmlWebpackPlugin.options.uaTracking) { %>
<script> <script>
@@ -36,9 +37,9 @@
</script> </script>
<script async src='https://www.google-analytics.com/analytics.js'></script> <script async src='https://www.google-analytics.com/analytics.js'></script>
<% } %> <% } %>
<!-- Piwik --> <!-- Piwik -->
<script type="text/javascript"> <!-- <script type="text/javascript">
var _paq = _paq || []; var _paq = _paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */ /* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(["setCookieDomain", "*.coriolis.edcd.io"]); _paq.push(["setCookieDomain", "*.coriolis.edcd.io"]);
@@ -51,14 +52,14 @@
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0]; 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); g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
})(); })();
</script> </script>-->
<!-- End Piwik Code --> <!-- End Piwik Code -->
<!-- Bugsnag --> <!-- Bugsnag -->
<script src="//d2wy8f7a9ursnm.cloudfront.net/v4/bugsnag.min.js"></script> <script src="//d2wy8f7a9ursnm.cloudfront.net/v4/bugsnag.min.js"></script>
<script src="//d2wy8f7a9ursnm.cloudfront.net/bugsnag-plugins/v1/bugsnag-react.min.js"></script> <script src="//d2wy8f7a9ursnm.cloudfront.net/bugsnag-plugins/v1/bugsnag-react.min.js"></script>
<script> <script>
window.bugsnagClient = bugsnag('ba9fae819372850fb660755341fa6ef5', {appVersion: window.CORIOLIS_VERSION || undefined}) window.bugsnagClient = bugsnag('ba9fae819372850fb660755341fa6ef5', {appVersion: window.BUGSNAG_VERSION || undefined})
window.Bugsnag = window.bugsnagClient window.Bugsnag = window.bugsnagClient
</script> </script>
</head> </head>

View File

@@ -93,6 +93,9 @@
color: @primary-bg; color: @primary-bg;
} }
& thead th.bordered {
border-left: 1px solid @primary-bg;
}
} }
} }

View File

@@ -22,6 +22,11 @@ select {
} }
} }
.cmdr-select {
border: 1px solid @primary;
padding: 0.5em 0.5em;
}
.select { .select {
color: @primary-disabled; color: @primary-disabled;
position: absolute; position: absolute;

View File

@@ -37,6 +37,35 @@
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.modification-container {
@input-container-width: 75%;
td {
width: 100% - @input-container-width;
text-align: center;
}
.input-container {
width: @input-container-width;
text-align: right;
}
input {
width: 80%;
}
.unit-container {
width: 30px;
padding: 3px;
text-align: left;
display: inline-block;
}
.header-adjuster {
width: 100% - @input-container-width;
display: inline-block;
}
}
.cb { .cb {
overflow: hidden; overflow: hidden;
} }
@@ -45,6 +74,11 @@
border-color:#fff; border-color:#fff;
} }
input:disabled {
border-color: #888;
color: #888;
}
.l { .l {
text-transform: capitalize; text-transform: capitalize;
margin-right: 0.8em; margin-right: 0.8em;

46
src/sw.js Normal file
View File

@@ -0,0 +1,46 @@
console.log('Hello from sw.js');
if (workbox) {
workbox.skipWaiting();
workbox.clientsClaim();
console.log('Yay! Workbox is loaded 🎉');
workbox.routing.registerRoute(
new RegExp('https://fonts.(?:googleapis|gstatic).com/(.*)'),
workbox.strategies.cacheFirst({
cacheName: 'google-fonts',
plugins: [
new workbox.expiration.Plugin({
maxEntries: 30
}),
new workbox.cacheableResponse.Plugin({
statuses: [0, 200]
})
]
})
);
workbox.routing.registerRoute(
/\.(?:png|gif|jpg|jpeg|svg)$/,
workbox.strategies.cacheFirst({
cacheName: 'images',
plugins: [
new workbox.expiration.Plugin({
maxEntries: 60,
maxAgeSeconds: 30 * 24 * 60 * 60 // 30 Days
})
]
})
);
workbox.routing.registerRoute(
/\.(?:js|css)$/,
workbox.strategies.staleWhileRevalidate({
cacheName: 'static-resources'
})
);
try {
workbox.googleAnalytics.initialize();
} catch (e) {
console.log('Probably an ad-blocker');
}
} else {
console.log('Boo! Workbox didn\'t load 😬');
}

View File

@@ -1,27 +1,27 @@
const path = require('path') const path = require('path');
const exec = require('child_process').exec const exec = require('child_process').exec;
const webpack = require('webpack') const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin') const ExtractTextPlugin = require('extract-text-webpack-plugin');
const WebpackNotifierPlugin = require('webpack-notifier') const WebpackNotifierPlugin = require('webpack-notifier');
const pkgJson = require('./package') const pkgJson = require('./package');
const buildDate = new Date();
function CopyDirPlugin(source, destination) { function CopyDirPlugin(source, destination) {
this.source = source this.source = source;
this.destination = destination this.destination = destination;
} }
CopyDirPlugin.prototype.apply = function (compiler) { CopyDirPlugin.prototype.apply = function(compiler) {
compiler.plugin('done', () => { compiler.plugin('done', () => {
console.log(compiler.outputPath, this.destination) console.log(compiler.outputPath, this.destination);
exec('cp -r ' + this.source + ' ' + path.join(compiler.outputPath, this.destination)) exec('cp -r ' + this.source + ' ' + path.join(compiler.outputPath, this.destination));
}) });
} };
module.exports = { module.exports = {
devtool: 'source-map', devtool: 'source-map',
devServer: { devServer: {
headers: {'Access-Control-Allow-Origin': '*'} headers: { 'Access-Control-Allow-Origin': '*' }
}, },
entry: { entry: {
app: ['webpack-dev-server/client?http://0.0.0.0:3300', 'webpack/hot/only-dev-server', path.join(__dirname, 'src/app/index.js')], app: ['webpack-dev-server/client?http://0.0.0.0:3300', 'webpack/hot/only-dev-server', path.join(__dirname, 'src/app/index.js')],
@@ -46,6 +46,7 @@ module.exports = {
inject: false, inject: false,
template: path.join(__dirname, 'src/index.ejs'), template: path.join(__dirname, 'src/index.ejs'),
version: pkgJson.version, version: pkgJson.version,
date: buildDate,
gapiKey: process.env.CORIOLIS_GAPI_KEY || '' gapiKey: process.env.CORIOLIS_GAPI_KEY || ''
}), }),
new ExtractTextPlugin({ new ExtractTextPlugin({
@@ -53,20 +54,20 @@ module.exports = {
disable: false, disable: false,
allChunks: true allChunks: true
}), }),
new WebpackNotifierPlugin({alwaysNotify: true}), new WebpackNotifierPlugin({ alwaysNotify: true }),
new webpack.HotModuleReplacementPlugin(), new webpack.HotModuleReplacementPlugin(),
new webpack.NoEmitOnErrorsPlugin() new webpack.NoEmitOnErrorsPlugin()
], ],
module: { module: {
rules: [ rules: [
{test: /\.css$/, loader: ExtractTextPlugin.extract({fallback: 'style-loader', use: 'css-loader'})}, { test: /\.css$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader' }) },
{test: /\.less$/, loader: ExtractTextPlugin.extract({fallback: 'style-loader', use: 'css-loader!less-loader'})}, { test: /\.less$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader!less-loader' }) },
{test: /\.(js|jsx)$/, loaders: ['babel-loader'], include: path.join(__dirname, 'src')}, { test: /\.(js|jsx)$/, loaders: ['babel-loader'], include: path.join(__dirname, 'src') },
{test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff'}, { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' },
{test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff'}, { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' },
{test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream'}, { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream' },
{test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader'}, { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader' },
{test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=image/svg+xml'} { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=image/svg+xml' }
] ]
} }
} };

View File

@@ -1,23 +1,24 @@
const path = require('path') const path = require('path');
const exec = require('child_process').exec const exec = require('child_process').exec;
const webpack = require('webpack') const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin') const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin') const ExtractTextPlugin = require('extract-text-webpack-plugin');
const AppCachePlugin = require('appcache-webpack-plugin') const { InjectManifest } = require('workbox-webpack-plugin');
const {BugsnagSourceMapUploaderPlugin} = require('webpack-bugsnag-plugins')
const pkgJson = require('./package')
function CopyDirPlugin (source, destination) { const { BugsnagSourceMapUploaderPlugin } = require('webpack-bugsnag-plugins');
this.source = source const pkgJson = require('./package');
this.destination = destination const buildDate = new Date();
function CopyDirPlugin(source, destination) {
this.source = source;
this.destination = destination;
} }
CopyDirPlugin.prototype.apply = function (compiler) { CopyDirPlugin.prototype.apply = function(compiler) {
compiler.plugin('done', () => { compiler.plugin('done', () => {
console.log(compiler.outputPath, this.destination) console.log(compiler.outputPath, this.destination);
exec('cp -r ' + this.source + ' ' + path.join(compiler.outputPath, this.destination)) exec('cp -r ' + this.source + ' ' + path.join(compiler.outputPath, this.destination));
}) });
} };
module.exports = { module.exports = {
cache: true, cache: true,
@@ -40,10 +41,10 @@ module.exports = {
'screw-ie8': true, 'screw-ie8': true,
sourceMap: true sourceMap: true
}), }),
//new webpack.optimize.CommonsChunkPlugin({ // new webpack.optimize.CommonsChunkPlugin({
// name: 'lib', // name: 'lib',
// filename: 'lib.[chunkhash:6].js' // filename: 'lib.[chunkhash:6].js'
//}), // }),
new HtmlWebpackPlugin({ new HtmlWebpackPlugin({
inject: false, inject: false,
appCache: 'coriolis.appcache', appCache: 'coriolis.appcache',
@@ -60,6 +61,7 @@ module.exports = {
template: path.join(__dirname, 'src/index.ejs'), template: path.join(__dirname, 'src/index.ejs'),
uaTracking: process.env.CORIOLIS_UA_TRACKING || '', uaTracking: process.env.CORIOLIS_UA_TRACKING || '',
gapiKey: process.env.CORIOLIS_GAPI_KEY || '', gapiKey: process.env.CORIOLIS_GAPI_KEY || '',
date: buildDate,
version: pkgJson.version version: pkgJson.version
}), }),
new ExtractTextPlugin({ new ExtractTextPlugin({
@@ -69,28 +71,27 @@ module.exports = {
}), }),
new BugsnagSourceMapUploaderPlugin({ new BugsnagSourceMapUploaderPlugin({
apiKey: 'ba9fae819372850fb660755341fa6ef5', apiKey: 'ba9fae819372850fb660755341fa6ef5',
appVersion: pkgJson.version appVersion: `${pkgJson.version}-${buildDate.toISOString()}`
}), }),
new CopyDirPlugin(path.join(__dirname, 'src/schemas'), 'schemas'), new CopyDirPlugin(path.join(__dirname, 'src/schemas'), 'schemas'),
new CopyDirPlugin(path.join(__dirname, 'src/images/logo/*'), ''), new CopyDirPlugin(path.join(__dirname, 'src/images/logo/*'), ''),
new CopyDirPlugin(path.join(__dirname, 'src/.htaccess'), ''), new CopyDirPlugin(path.join(__dirname, 'src/.htaccess'), ''),
new AppCachePlugin({ new InjectManifest({
network: ['*'], swSrc: './src/sw.js',
settings: ['prefer-online'], importWorkboxFrom: 'cdn',
exclude: ['index.html', /.*\.map$/], swDest: 'service-worker.js'
output: 'coriolis.appcache' }),
})
], ],
module: { module: {
rules: [ rules: [
{test: /\.css$/, loader: ExtractTextPlugin.extract({fallback: 'style-loader', use: 'css-loader'})}, { test: /\.css$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader' }) },
{test: /\.less$/, loader: ExtractTextPlugin.extract({fallback: 'style-loader', use: 'css-loader!less-loader'})}, { test: /\.less$/, loader: ExtractTextPlugin.extract({ fallback: 'style-loader', use: 'css-loader!less-loader' }) },
{test: /\.(js|jsx)$/, loader: 'babel-loader?cacheDirectory=true', include: path.join(__dirname, 'src')}, { test: /\.(js|jsx)$/, loader: 'babel-loader?cacheDirectory=true', include: path.join(__dirname, 'src') },
{test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff'}, { test: /\.woff(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' },
{test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff'}, { test: /\.woff2(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/font-woff' },
{test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream'}, { test: /\.ttf(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=application/octet-stream' },
{test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader'}, { test: /\.eot(\?v=\d+\.\d+\.\d+)?$/, loader: 'file-loader' },
{test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=image/svg+xml'} { test: /\.svg(\?v=\d+\.\d+\.\d+)?$/, loader: 'url-loader?limit=10000&mimetype=image/svg+xml' }
] ]
} }
} };