Compare commits

...

506 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
08c5d2256a 2.9.15 2018-05-30 07:43:09 +10:00
willyb321
ed6ee4341f Fix crash when applying blueprint
thanks bugsnag
2018-05-30 07:42:33 +10:00
willyb321
157c1148fb Implement temporary fix for #280 2018-05-30 07:37:20 +10:00
Pat Nellesen
507ea9e09e Fix to account for cases where there is only one module available for… (#291)
* Fix to account for cases where there is only one module available for a given slot (e.g. Sidewinder fuel tank)

* Fix for issue #289 - set focus to active module section if the slot has an active module

* Added activeSlot ref to set focus to current module when module list is opened

* Comment/console cleanup for PR #291
2018-05-30 07:25:35 +10:00
willyb321
af68cba7be add "view release changes" button below the update available banner 2018-05-29 07:31:58 +10:00
willyb321
224fbe0e8f add a "commits since last release button"
makes it easier for people to see activity
2018-05-29 07:23:20 +10:00
willyb321
49a6c5f2c4 Merge branch 'release/2.9.14' 2018-05-29 07:03:18 +10:00
willyb321
07c936897c Merge branch 'release/2.9.14' into develop 2018-05-29 07:03:18 +10:00
willyb321
3786fb907c 2.9.14 2018-05-29 07:03:11 +10:00
Pat Nellesen
752d9f0c68 Feature/#271 keyboard nav (#288)
* Initial stab at Tab/Enter key handlers for Slots - Added tabIndex="0" attribute for the Slot sections and for the <li> elements inside.

* Set up refs for slot <li> elements to allow focus manipulation.

* Added initial keyDown handlers for Enter and Tab keys.

* Additional update for focus handling inside slot selection <ul>

* Added Tab/Enter/Shift-Tab key handlers for Outfitting section. Needs some fine-tuning but is usable in current state

* Added keydown handler for Empty module selection. ToDo: handlers for engineering sections

* Cleanup

* Update to add focus on Modifications icon and Enter key handler to open modifications menu

* Updates to add keydown handlers for modifications menu, and tab/shift-tab/enter key handlers while mod menus are open.

* Additional updates for keyDown handlers

* Update to add tabindex=0 and Enter Key handling for specials menus. ToDo: keep focus inside specials menu until item is selected with Enter key.

* Further updates for keyDown handler in Modifications menus.

* Added keyDown handlers to full mod menu (when mod menu is opened and a mod has been previously selected)

* Update to add shift-tab and tab focus handling for number editor fields

* Additional tab/shift-tab handlers. Fixed bug with exp. effects being recaculated for every onBlur event.

* Added check to bypass focus reset if change in component was due to change in modification value.

* Additional updates to tab/shift-tab handlers for modifications menu.

* more updates

* "Final" updates and bug fixes - added code for cases where there were no specials.

* Final updates to set focus to appropriate element when slot/mod menus are closed.
2018-05-29 07:00:22 +10:00
Pat Nellesen
baf59aafcb Feature/#248 slider keyboard (#257)
* Added tabIndex=0 to <svg> in Slider to allow keyboard focus

* further tests for mobile keyboard fix

* Proof of concept of way to get mobile keyboard to open for Slider component

* Added CSS to slider input box to make it invisible but still usable

* Update to hide text input and move it up to same position as slider

* Change slider text field type to 'tel" to force numeric-only keyboard on mobile

* Added focus/blur handlers and initial styling to show/hide text field component

* Update to insure text field values are updated when slider moves, and vice versa.

* Added timeout handler for Outfitting sliders to raise keyboard after 1500ms. Doesn't work 100% on iPhone.

* Changed touch/mouse down event handlers to change the display value on the parent div, then use componentDidUpdate to set focus on the text box.

* Added tap/hold mousedown/hold keyboard display - works on desktop, iOS, and Android
2018-05-04 09:01:55 +10:00
willyb321
8787303d2a add a column with what type of shield, update tooltips too 2018-05-04 08:59:57 +10:00
willyb321
298eaa8b4b Merge remote-tracking branch 'origin/develop' 2018-05-03 10:58:46 +10:00
William Blythe
118c80af27 Fix kinetic and thermal res swapped
Closes #265
2018-05-03 10:54:55 +10:00
willyb321
7b87038a8c Merge branch 'develop' 2018-05-03 07:39:08 +10:00
willyb321
d103939e45 add experimentals to core internals 2018-05-03 07:38:54 +10:00
willyb321
fdc9171c69 Merge branch 'release/2.9.13' 2018-05-03 07:14:53 +10:00
willyb321
2a6ade3cab Merge branch 'release/2.9.13' into develop 2018-05-03 07:14:52 +10:00
willyb321
c8c42689f9 2.9.13 2018-05-03 07:14:45 +10:00
willyb321
da2f472f4d hopefully fix #262 2018-05-01 07:13:58 +10:00
William Blythe
3ba878237b fix super cap, nan with no shield 2018-04-30 10:57:42 +10:00
willyb321
7577fb53a2 biweaves are purple 2018-04-28 13:47:46 +10:00
willyb321
f2509f89ee change colour depending on shield 2018-04-28 13:39:17 +10:00
willyb321
9dd1f78330 make shield summary bar blue, change "damage from x" to "x hp" 2018-04-28 13:39:17 +10:00
willyb321
ebb6c2c420 more work on summary 2018-04-28 13:39:17 +10:00
willyb321
9d23dc1763 use set pips to calc shield 2018-04-28 13:39:17 +10:00
willyb321
5fa3f8703e pass pips to summary table 2018-04-28 13:39:17 +10:00
willyb321
797885faea more wip table 2018-04-28 13:39:17 +10:00
willyb321
68e7e9f5b7 fix crash when removing shield 2018-04-28 13:39:17 +10:00
willyb321
d1d165ad51 WIP summary table 2018-04-28 13:39:17 +10:00
willyb321
f43bd100e6 Merge branch 'release/2.9.12' 2018-04-28 12:16:21 +10:00
willyb321
0ebb247666 Merge branch 'release/2.9.12' into develop 2018-04-28 12:16:21 +10:00
willyb321
aa73bc2809 2.9.12 2018-04-28 12:16:11 +10:00
willyb321
c3fcdb918f prep for recon limpet controllers 2018-04-28 12:12:38 +10:00
willyb321
5e3722bcfd hopefully fix a crash 2018-04-28 11:37:24 +10:00
willyb321
6a4fca2eb1 fix .editorconfig 2018-04-26 13:39:52 +10:00
willyb321
fbba0e3ea5 Merge branch 'release/2.9.11' 2018-04-26 09:21:01 +10:00
willyb321
6922cfd047 2.9.11 2018-04-26 09:19:57 +10:00
willyb321
d93fc1d2d0 just update everything when applying a special 2018-04-26 09:06:18 +10:00
willyb321
b15695128f Merge branch 'release/release/v2.9.10' 2018-04-25 20:00:47 +10:00
willyb321
48290b2e75 2.9.10 2018-04-25 20:00:36 +10:00
willyb321
b62abef618 2.9.9 2018-04-25 19:55:05 +10:00
willyb321
e0c0778d82 Lowercase in import 2018-04-25 18:45:24 +10:00
willyb321
b4a82ae7c2 fix crash with not having modifications key 2018-04-25 18:00:01 +10:00
willyb321
fc73102b30 Merge remote-tracking branch 'origin/develop' into develop 2018-04-25 15:07:23 +10:00
willyb321
b14e7473f3 add guardian powerplant, gauss cannon and also plasma charger 2018-04-25 15:07:13 +10:00
willyb321
f4cc9fc722 fix typo 2018-04-25 14:20:45 +10:00
William
8d19ef7783 Merge pull request #256 from ExitCode/develop
Fixed bp preset highlight when specials alter on the same values
2018-04-24 14:49:39 +10:00
willyb321
32be186ec5 remove unused var 2018-04-23 18:15:44 +10:00
willyb321
abb0c7f90d remove debug logs 2018-04-23 18:15:24 +10:00
willyb321
71ddbdfe75 add specials tooltip 2018-04-23 14:51:45 +10:00
ExitCode
38463ad9a6 Fixed bp preset highlight when specials alter on the same values 2018-04-23 01:16:13 +02:00
willyb321
0870b90443 Merge branch 'release/2.9.8' 2018-04-23 07:38:34 +10:00
willyb321
70375f94c8 bump 2018-04-23 07:38:24 +10:00
William
2d4336116a Merge pull request #254 from ExitCode/develop
Value highlights for bp presets
2018-04-23 07:36:12 +10:00
willyb321
f52880765e fix legacy mods showing up 2018-04-23 07:34:52 +10:00
ExitCode
dbfe68decb Implemented active selected blueprint value highlight; updated look and feel 2018-04-22 05:36:19 +02:00
ExitCode
659f337de9 Added a spacer between the mod values 2018-04-22 05:34:25 +02:00
ExitCode
1d36d41da1 Tuned visibility of components in mod menu 2018-04-22 05:33:47 +02:00
ExitCode
0f90efaa54 Implemented a func to determine the current percentage level of a mod 2018-04-22 05:31:16 +02:00
willyb321
48ccab152b Merge branch 'release/2.9.7' 2018-04-22 12:27:20 +10:00
willyb321
4442930a82 bump 2018-04-22 12:26:06 +10:00
William
912a775088 Merge pull request #253 from ExitCode/develop
blue print workflow (easier editing of modules)
2018-04-22 12:08:01 +10:00
ExitCode
24a229d818 Implemented workflow on bp assignment; applied some styles 2018-04-22 03:43:57 +02:00
ExitCode
66afb61494 added new style for inline menu buttons 2018-04-22 03:41:21 +02:00
William
e7511cc05b Merge pull request #252 from ExitCode/develop
(optional) changed roll presets to 0-50-100
2018-04-22 09:09:37 +10:00
ExitCode
926f19a936 added default init 100% on bp selection as the new system makes it obvious to reach 100% 2018-04-22 00:59:44 +02:00
ExitCode
9e6f86b963 (optional) changed roll presets to 0-50-100 2018-04-22 00:28:04 +02:00
William
059de43a9a Merge pull request #251 from ExitCode/develop
reused random functionality as it worked better on percentage rolls between min max
2018-04-22 08:10:30 +10:00
ExitCode
e246b737b2 reused random functionality as it worked better on percentage rolls between min/max 2018-04-21 23:53:02 +02:00
willyb321
f3bc900f16 Merge branch 'release/2.9.6' 2018-04-22 07:00:47 +10:00
willyb321
38842417b0 bump 2018-04-22 06:59:08 +10:00
willyb321
82d485a98e fix preset rolls locked to low value 2018-04-22 06:49:43 +10:00
willyb321
fac71feea7 Lowercase armour check
Fixes #231
2018-04-21 12:56:51 +10:00
willyb321
ec148847a9 Merge branch 'develop' 2018-04-21 11:39:19 +10:00
willyb321
445c63878b add guard for tooltips crashing the import 2018-04-21 11:39:02 +10:00
willyb321
0474af912a Merge branch 'release/2.9.5' 2018-04-21 11:05:29 +10:00
willyb321
7df5953824 bump 2018-04-21 11:05:19 +10:00
willyb321
e391b563fb change preset rolls to 100%, 75%, 50% and random 2018-04-21 11:03:23 +10:00
willyb321
53f62f96d0 Merge branch 'master' into develop 2018-04-21 07:17:11 +10:00
willyb321
18745979a0 update about page 2018-04-20 19:20:47 +10:00
willyb321
c1966a38ff Merge branch 'master' into develop 2018-04-20 12:49:10 +10:00
willyb321
d86973f3b1 specify appVersion for bugsnag 2018-04-20 12:48:46 +10:00
willyb321
56b8d19649 Merge branch 'release/2.9.4' 2018-04-19 07:17:21 +10:00
willyb321
7cb037e0bc bump 2018-04-19 07:17:06 +10:00
willyb321
56e1b3f9e9 Revert "prep for guardian"
This reverts commit 7e67bd80dd.
2018-04-19 07:14:24 +10:00
willyb321
c792323a8a misc bits 2018-04-19 07:14:13 +10:00
willyb321
4d2865de13 fix resistance blueprint values 2018-04-19 07:14:02 +10:00
willyb321
0d360bc367 bugsnag update 2018-04-19 07:13:20 +10:00
willyb321
91c9b46b91 clean up webpack configs 2018-04-19 06:44:02 +10:00
willyb321
7e67bd80dd prep for guardian 2018-04-19 06:26:53 +10:00
willyb321
2f775ea09b Merge branch 'release/2.9.3' 2018-04-18 18:04:42 +10:00
willyb321
0603c55089 bump 2018-04-18 18:04:28 +10:00
willyb321
41f25a44e9 Merge branch 'master' into develop 2018-04-18 10:33:17 +10:00
willyb321
35440b7273 allow specials in internal and standard slots 2018-04-18 10:33:11 +10:00
willyb321
7d68b91018 Merge branch 'release/2.9.2' 2018-04-17 17:33:45 +10:00
willyb321
deaa61b848 bump version 2018-04-17 17:32:56 +10:00
willyb321
2d00cbc41b fix more values (hopefully done)
also, Fixes #247 by changing passengers to pax
2018-04-17 17:31:11 +10:00
willyb321
57c1e83c67 apply new experimentals structure 2018-04-17 15:01:09 +10:00
William
b7079dbd4e Merge pull request #246 from pnellesen/feature/bugfix_245
Fix for issue 245
2018-04-17 12:38:21 +10:00
willyb321
be642a5373 Merge branch 'master' into develop 2018-04-17 07:59:42 +10:00
willyb321
ffc691c1a2 Merge branch 'release/2.9.1' 2018-04-17 07:57:44 +10:00
willyb321
6122d99369 version bump 2018-04-17 07:57:13 +10:00
willyb321
b73a8bcdab hopefully fix some values and crashes
resistances are buggered on shields and armour by the looks of things
2018-04-17 07:55:17 +10:00
willyb321
dd624537b7 fix hardpoints / internals having the wrong mod applied 2018-04-17 07:50:57 +10:00
willyb321
50a67f73fd get rid of some redundant code 2018-04-17 07:50:13 +10:00
willyb321
2262a980d4 Merge remote-tracking branch 'origin/develop' into develop 2018-04-17 07:46:35 +10:00
willyb321
84964ceb5f add source maps to assist with debugging.
also upload them to bugsnag
2018-04-17 07:46:26 +10:00
William Blythe
c4bdb7a66e Merge remote-tracking branch 'origin/master' into develop 2018-04-16 13:11:44 +10:00
Pat
21309e129f Fix for issue 245 - check that the passenger property is defined for a slot before calculating total passenger capacity 2018-04-14 08:50:48 -05:00
willyb321
928e02c718 Merge branch 'release/2.9.0' 2018-04-14 16:53:08 +10:00
willyb321
7f494dd200 bump 2018-04-14 16:52:05 +10:00
William
f38e743e59 Merge pull request #244 from EDCD/feature/beyond-coriolis
Engineer mods.
2018-04-14 15:14:13 +10:00
Willyb321
58b55eb3da Engineers. Are. Back. Probably. 2018-04-14 14:50:37 +10:00
willyb321
8375ad95b3 Merge branch 'master' into feature/beyond-coriolis 2018-04-14 10:16:20 +10:00
willyb321
506d027a2d Merge branch 'develop' 2018-04-14 09:52:03 +10:00
willyb321
fbeb6237cf fix webpack 2018-04-14 09:51:46 +10:00
willyb321
bd95e2c5a5 Merge branch 'master' into develop 2018-04-14 09:48:03 +10:00
willyb321
f3276e557a Merge branch 'release/2.5.3' 2018-04-14 09:47:08 +10:00
willyb321
8b813e0e7f bump 2018-04-14 09:46:41 +10:00
willyb321
d660d2959f more roles fixing
use a smaller shield, add boosters, d rate some internals
2018-04-14 09:44:29 +10:00
willyb321
014ebda7d2 start fixing suggested roles. Thanks to rinzler + others 2018-04-14 08:06:44 +10:00
William
607398d364 Merge pull request #241 from Blackth0rn/fix/#240_incorrect_passenger_counts
Fix/#240 incorrect passenger counts
2018-04-13 16:20:52 +10:00
willyb321
97d141ce2b update lockfile version 2018-04-13 16:17:59 +10:00
willyb321
9f492db9c6 source map 2018-04-13 16:16:42 +10:00
willyb321
6ed82b366c update bugsnag 2018-04-13 16:16:01 +10:00
willyb321
a7ca037f48 loadout event prep 2018-04-13 14:38:22 +10:00
willyb321
6e21d0e74a Work on engineering import 2018-04-13 13:39:33 +10:00
willyb321
e6c75bf2af Merge branch 'develop' into feature/beyond-coriolis 2018-04-13 09:16:23 +10:00
willyb321
270c2f386e Merge branch 'release/2.5.2' 2018-04-13 07:56:38 +10:00
willyb321
83f7880c58 bump version 2018-04-13 07:51:07 +10:00
William
51d29aee38 Merge pull request #235 from pnellesen/feature/fueltank_column
Added fuel tank column to Core Module Classes on Shipyard
2018-04-13 07:38:07 +10:00
William
c7754c0365 Merge pull request #234 from joelgarboden/feature/remote_testing
Disable 'localhost' host header requirement
2018-04-13 07:34:59 +10:00
Greg Matthews
94037cea38 Fix #240: Added passenger count to individual ship page 2018-04-11 21:09:57 -07:00
Greg Matthews
5c1a9d9eea Fix #240: There is no size 7 passenger cabin so use the new findMaxInternal function to get the biggest one that fits in the slot 2018-04-11 21:07:00 -07:00
Greg Matthews
3835c73ffd Add findMaxInternal function to find the biggest module of a given group, rating, slotsize 2018-04-11 21:06:07 -07:00
Pat
b8c1effecb Update for issue #183 - use first letter of bulkhead instead of "Max Rating" value 2018-04-09 23:10:28 -05:00
Pat
dd07241dd9 Added fuel tank column to Core Module Classes on Shipyard 2018-04-07 18:57:54 -05:00
Joel Garboden
5ad828a613 Disable 'localhost' limit
Disale host header so webpack allows more than localhost access
2018-04-07 22:34:50 +00:00
Unknown
9856df5527 Portiguese 2018-03-22 22:00:19 +00:00
Unknown
7b249900ec Portuguese 2018-03-22 21:59:50 +00:00
Unknown
e3b9267c3f Merge remote-tracking branch 'origin/feature/beyond-coriolis' into develop 2018-03-22 21:59:37 +00:00
willyb321
a996b8135a inital engineerless import working 2018-03-23 08:51:48 +11:00
willyb321
5e1237390b Initial work on 3.0 imports.
Decided to split journal imports to a separate file.
2018-03-22 16:22:59 +11:00
Willyb321
83571b4bef add .editorconfig
Makes contributing and not messing up the codestyle easier
2018-03-22 12:07:19 +11:00
Willyb321
c2a0dad9a8 Export textarea select
Fixes #146
2018-03-22 12:06:36 +11:00
Unknown
f6f057689c Merge branch 'feature/chieftain' into develop 2018-01-27 11:47:17 +00:00
willyb321
4378f0020e add chieftain to map
also remove console logs that i forgot to remove
2018-01-27 10:55:37 +11:00
Unknown
2e9e7c4fc6 Merge branch 'release/2.5.1' 2018-01-24 09:18:58 +00:00
Unknown
b7b8ee5580 Merge branch 'release/2.5.1' into develop 2018-01-24 09:18:36 +00:00
Unknown
c630dbbed0 Version Bump 2018-01-24 09:18:21 +00:00
Unknown
fa6703a3b8 Merge remote-tracking branch 'Blackth0rn/feature/#186-passenger-totals' into develop 2018-01-24 08:45:54 +00:00
Unknown
bdcb64c9d1 Merge remote-tracking branch 'willyb321/feature/fix-engineer-values' into develop 2018-01-24 08:45:48 +00:00
Unknown
a1afc869a7 Merge remote-tracking branch 'Ghnuberath/feature/ax-modules' into develop 2018-01-24 08:45:44 +00:00
Unknown
d8ce26c7cf Merge remote-tracking branch 'origin/master' into develop 2018-01-24 08:44:45 +00:00
Sean McIntyre
95c474dc05 Recategorizing experimental modules into an "experimental" category 2018-01-22 16:48:10 -05:00
Sean McIntyre
56ca73b4ad Forgot Remote Release Flak Launcher 2018-01-22 16:43:29 -05:00
Sean McIntyre
d82cd6a89e Removing unnecessary change 2018-01-22 15:21:59 -05:00
Sean McIntyre
bf20d32364 Adding AX modules 2018-01-22 15:20:41 -05:00
willyb321
e968e62fca Fix clip size (hopefully) 2018-01-16 08:14:21 +11:00
Unknown
b9c9ca9fa1 Merge branch 'release/2.5.0' 2018-01-11 21:59:31 +00:00
Unknown
6e965e2e98 Version Bump 2018-01-11 21:59:20 +00:00
Unknown
7d569f9036 Merge branch 'feature/fix-engineers' into develop 2018-01-11 21:51:35 +00:00
Unknown
df5a77199d Merge branch 'develop' 2018-01-11 21:50:28 +00:00
Greg Matthews
3c8dfebfdc Issue #186. Added passenger totals to shipyard screen 2018-01-10 21:20:27 -08:00
Unknown
3cc422596f Something something consistant naming 2017-12-24 09:47:49 +00:00
Unknown
4ed167de22 Add T10 2017-12-24 09:47:33 +00:00
Unknown
b420647501 Something something consistant naming 2017-12-24 09:46:49 +00:00
Unknown
0c318b5e68 Add T10 2017-12-23 12:03:21 +00:00
willyb321
1c627297b8 shorten the terrible _addModifications call 2017-12-19 16:39:42 +11:00
willyb321
f41e2d0552 fix indent and hopefully fix ammo clip
dont know if it was broken, if someone has it DM it to me thanks
2017-12-19 16:28:05 +11:00
willyb321
1a1d539c60 fix engagement range slider and armour integrity
hopefully
2017-12-19 16:18:20 +11:00
willyb321
ba2e46f88f fix rate of fire
probably
2017-12-19 14:49:04 +11:00
willyb321
c2f1fa81af remove console logs 2017-12-17 18:48:24 +11:00
willyb321
752e03fa0f possible armour fix
need more data to confirm however
2017-12-17 18:24:34 +11:00
willyb321
1da69664d7 shields should be accurate now
dont now what the issue with hull is
if you see it let me know
2017-12-17 14:49:51 +11:00
willyb321
9aa986a133 thanks webstorm 2017-12-17 12:48:13 +11:00
willyb321
bdbfb28c4a missed a spot 2017-12-17 12:47:02 +11:00
willyb321
162156bb2b fix random weird indents 2017-12-17 12:46:18 +11:00
willyb321
64c5d542e9 more work on mods 2017-12-17 12:27:04 +11:00
willyb321
31dc789f6e initial (re)implementation of engineer mods
still some kinds to work out.
2017-12-17 11:16:50 +11:00
Unknown
b0e2cfd7db Merge branch 'feature/#112_ship_table_highlights' into develop 2017-12-04 09:41:54 +00:00
Greg Matthews
54ddb0d014 Rename sortValue and tmpSortValue to be more meaningful 2017-11-11 13:29:59 -08:00
Greg Matthews
e19688e96f Add alternate row highlighting for sorted value 2017-11-11 13:25:17 -08:00
Greg Matthews
4f53d75999 Fix whitespace in html 2017-11-11 13:24:57 -08:00
Unknown
7277460060 Merge branch 'release/2.4.2' 2017-11-09 17:23:46 +00:00
Unknown
93c4f6f3c0 Merge branch 'release/2.4.2' into develop 2017-11-09 17:23:32 +00:00
Unknown
c6919a7518 Bumps and changelog 2017-11-09 17:23:15 +00:00
Unknown
ca428e67dc Package Lock and D3 2017-11-09 17:18:55 +00:00
Unknown
c6726cf020 Merge remote-tracking branch 'BenJuan26/feature/fix-tests' into develop 2017-11-09 17:15:06 +00:00
Unknown
56ae1378da Merge branch 'feature/slot-commas' into develop 2017-11-09 17:14:49 +00:00
Unknown
e982ab1a3b Merge branch 'feature/ship-purchase-requirements' into develop 2017-11-09 17:11:32 +00:00
Unknown
0d6aa87e89 Update coriolis to use directory basis 2017-11-09 17:04:23 +00:00
Joshua Campbell
94d06e4025 Show ship purchase requirements on outfitting page 2017-11-07 01:46:58 -05:00
Benjamin Schubert
cee5b297ac Add create-react-class 2017-11-02 12:02:15 -04:00
Benjamin Schubert
c549213ce0 Switch to babel-preset-env 2017-11-02 11:44:53 -04:00
Benjamin Schubert
f3f9112767 Change jest version 2017-11-02 11:25:32 -04:00
Benjamin Schubert
fb325ea3e2 Add commas to slot cost 2017-11-01 15:56:51 -04:00
Unknown
3773f6f7ec Merge branch 'release/2.4.1' into develop 2017-10-31 16:52:51 +00:00
Unknown
b90ab6fe48 Merge branch 'release/2.4.1' 2017-10-31 16:52:34 +00:00
Unknown
cc2e56dc8a bump 2017-10-31 16:52:13 +00:00
William
ae65af7bbf prevent crashing when no modifications are in the data (#160) 2017-10-12 08:33:36 +01:00
William
c8fb513cd1 Fix repair limpet grouping (#161)
* Group repair limpets with the rest

* editor decided to add a random whitespace

* also it was set to tabs
2017-10-12 08:33:22 +01:00
Unknown
7fc3855af4 Merge branch 'release/2.4.0' into develop 2017-10-10 16:47:21 +01:00
Unknown
bcebb26d4a Merge branch 'release/2.4.0' 2017-10-10 16:46:57 +01:00
Unknown
1f23e4cfcc Version Bump 2017-10-10 16:46:34 +01:00
William
65998778fe fix zbuf error (#154)
Add an optional extended description…
2017-10-10 11:59:44 +01:00
Unknown
e53c04a07f Repair Limpet 2017-10-03 20:25:01 +01:00
Unknown
e5a8e106c1 Merge branch 'feature/languages' into develop 2017-09-01 20:56:34 +01:00
Unknown
11d1d80a53 Other languages JSON import 2017-09-01 20:55:11 +01:00
Unknown
c8a3d86a45 JSON Import 2017-09-01 20:52:45 +01:00
Unknown
4484ca226a Polish , Spanish, Italian JSON 2017-09-01 19:50:52 +01:00
Unknown
496c9ba35c Full bugsnag 2017-08-30 20:29:46 +01:00
Unknown
dae2fc9192 Bugsnag 2017-08-30 20:29:29 +01:00
Unknown
07b00e6230 Full bugsnag 2017-08-30 20:28:59 +01:00
Unknown
36526f0824 Merge branch 'feature/bugsnag' into develop 2017-08-30 19:47:52 +01:00
Unknown
f4691939ba Bugsnag 2017-08-30 19:46:36 +01:00
Unknown
534f735b63 Translation Advice 2017-08-30 19:33:42 +01:00
Unknown
bc2c8406a2 Russian JSON Generated 2017-08-30 19:30:27 +01:00
Unknown
032c44f39a German JSON Generated 2017-08-30 19:15:17 +01:00
Unknown
2b43c8e91a French JSON Generated 2017-08-30 19:04:57 +01:00
Unknown
e91b3df31a English JSON Generated 2017-08-30 19:04:49 +01:00
Unknown
3e0597023a Merge remote-tracking branch 'origin/master' 2017-08-29 20:06:30 +01:00
Unknown
1dd2edf742 Merge branch 'master' into develop 2017-08-29 20:05:51 +01:00
Unknown
97f3bece33 Merge branch 'release/2.3.7' 2017-08-29 20:05:28 +01:00
Unknown
be02444487 Changelog
Bump Version
2017-08-29 20:04:52 +01:00
Kamen Litchev
fc012fe68a Fix stroke-width handling bug in Safari 11 (#138)
* Fix stroke-width handling bug in Safari 11

* Further fix Safari 11 SVG stroke-width issues
2017-08-29 13:29:47 +01:00
strideynet
7ac16d6d22 Update Tracking 2017-08-22 21:03:23 +01:00
strideynet
4ee92b1f3e Update Tracking 2017-08-22 20:59:39 +01:00
strideynet
d82128690b Outdated URLs in tests don't match actual URLs 2017-08-17 19:47:13 +01:00
strideynet
9e57eb4262 Remove unwanted console.log and spacing fix 2017-08-17 15:00:13 +01:00
strideynet
6e0d45f419 ESLint issue
ESLint is picking a fault with the JSDoc despite it being correct. Will investigate further but for now this resolves the issue.
2017-08-17 14:42:44 +01:00
strideynet
645e86714e attempted fix for jsdoc detection 2017-08-17 14:31:00 +01:00
strideynet
00afd1cd6a Attempt to fix JSDoc 2017-08-17 14:12:25 +01:00
strideynet
77ae126a51 Compliance
As per #132
2017-08-17 14:05:22 +01:00
strideynet
9c82c7caed remove redundant LoC 2017-08-17 14:01:12 +01:00
strideynet
d9a92e7a78 Remove redundant LoC 2017-08-17 13:59:23 +01:00
strideynet
d8a87029a6 Compliance
As per #132
2017-08-17 13:42:12 +01:00
strideynet
f407d0f92a Compliance
Issue #132
2017-08-17 13:41:41 +01:00
strideynet
4ac42e62e6 Bumping NodeJS Version
See commit to travis.yml
2017-08-17 13:26:09 +01:00
strideynet
748c63fa0b Update NodeJS Version
Over the new few days I will test the newer major versions, for now just bumping to the latest V4 LTS
2017-08-17 13:24:21 +01:00
strideynet
78ca756cef Update package.json 2017-08-17 13:13:24 +01:00
strideynet
a8554b51c2 Uglify JS
Required for the build process for D3
2017-08-17 13:11:37 +01:00
sascha-dev
e54e4da289 Updated german translation (#127)
PR #127 merged to EDCD:Develop
2017-08-16 19:29:22 +01:00
Cmdr McDonald
f97cb5f5a7 Merge branch 'release/2.3.6' into develop 2017-06-24 09:34:41 +01:00
Cmdr McDonald
318e8077d0 Merge branch 'release/2.3.6' 2017-06-24 09:34:37 +01:00
Cmdr McDonald
c8355a532d Bump version 2017-06-24 09:33:21 +01:00
Cmdr McDonald
3686ccd4ed Update Russian translation 2017-06-24 09:24:38 +01:00
Cmdr McDonald
f02db0120a Fix for Spanish translation of Chaff Launcher 2017-06-24 09:18:27 +01:00
Cmdr McDonald
d9ad93d3cd Fix typo causing long range blueprint to not modify shot speed in some circumstances 2017-06-06 11:45:59 +01:00
Cmdr McDonald
77018cc1ad Fix issue where torpedo special effects were not showing 2017-05-30 19:44:35 +01:00
Cmdr McDonald
30a8a29ce3 Update miner role with better defaults 2017-05-30 19:44:11 +01:00
Cmdr McDonald
7b1aa646ac Fix 2017-05-17 20:56:13 +01:00
Cmdr McDonald
49e4409862 Merge branch 'release/2.3.5' into develop 2017-05-17 07:49:06 +01:00
Cmdr McDonald
ab87ddc649 Merge branch 'release/2.3.5' 2017-05-17 07:49:00 +01:00
Cmdr McDonald
57784329ac Bump version 2017-05-17 07:31:46 +01:00
Cmdr McDonald
1db613c56d Grey out modules that are powered off to provide a clearer visual indication 2017-05-16 20:31:56 +01:00
Cmdr McDonald
464cd4165f Added translation 2017-05-16 16:39:15 +01:00
Cmdr McDonald
420ebe4cc7 Merge branch 'feature/fixes' into develop 2017-05-11 13:14:27 +01:00
Cmdr McDonald
15ead440d6 Updated German translation; fix for #100 2017-05-11 13:14:19 +01:00
Cmdr McDonald
ef0614a62f Handle display when summary values show thrusters disabled but current mass keeps them enabled 2017-05-11 13:09:18 +01:00
Cmdr McDonald
2fd2af3e31 Ensure that hidden blueprint effects are applied when a blueprint is selected 2017-05-11 12:45:57 +01:00
Cmdr McDonald
f30a904a02 Remove logging 2017-05-03 19:18:50 +01:00
Cmdr McDonald
e00680113f Do not add hidden features 2017-05-03 19:15:07 +01:00
Cmdr McDonald
93e6a29057 Merge branch 'release/2.3.4' into develop 2017-05-03 17:12:16 +01:00
102 changed files with 7725 additions and 4256 deletions

View File

@@ -1,3 +1,3 @@
{
"presets": ["es2015", "react", "stage-0"]
}
"presets": ["env", "react", "stage-0"]
}

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

21
.editorconfig Normal file
View File

@@ -0,0 +1,21 @@
root = true
[*]
# change these settings to your own preference
indent_style = space
indent_size = 2
# we recommend you to keep these unchanged
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
[{package,bower}.json]
indent_style = space
indent_size = 2

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,
"classes": true,
"modules": true
},
}
},
"env": {
"browser": true,
"node": true
"node": true,
"es6": true
},
"plugins": [
"react"
@@ -33,7 +34,6 @@
"ClassDeclaration": true
}
}],
"no-console": 2,
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
"comma-style": [2, "last"],
"indent": [2, 2, { "SwitchCase": 1, "VariableDeclarator": 2 }],

4
.gitignore vendored
View File

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

1
.npmrc Normal file
View File

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

View File

@@ -3,13 +3,14 @@ notifications:
email: false
sudo: false
node_js:
- "4.2.6"
- "4.8.1"
cache:
directories:
- node_modules
before_script:
before_install:
- git clone https://github.com/EDCD/coriolis-data.git ../coriolis-data
script:
- npm run lint
- npm test
- npm test

View File

@@ -1,3 +1,59 @@
#2.5.1
* Passenger count on main page
* AX Modules
* Engineering fixes
* Use coriolis-data 2.5.1
#2.5.0
* willyb321 and myself have conquered engineering. Mainly him though...
* Use coriolis-data 2.5.0
#2.4.2
Lots of kind people have helped out for this release! Check out the PR history!
* Uses coriolis-data update:
* Fixes issues with repair limpets
* Adds requirement data
* Adds requirements panel
* Adds comma formatting to tooltip numbers
#2.4.1
* Small patches and changes
#2.4.0
* Changed compression library to Pako
* Use coriolis-data 2.4.0
* Repair Limpets added
#2.3.7
* Fixed Travis test issues
* Bumped NodeJS version to provide better compatability and support
* Added updated German Translation
* Fixed issues with Safari
* Use coriolis-data 2.3.7
* Fixed Orca mass-lock
#2.3.6
* Update miner role to provide better defaults
* Fix issue where torpedo special effects were not showing
* Fix typo causing long range blueprint to not modify shot speed in some circumstances
* Fix for Spanish translation of Chaff Launcher (thanks to DamonFstr)
* Update for Russian translation (thanks to LeeNTien)
* Use coriolis-data 2.3.6:
* Add shotspeed modifier to cannon/multi-cannon/fragment cannon
#2.3.5
* Ensure that hidden blueprint effects are applied when a blueprint is selected
* Handle display when summary values show thrusters disabled but current mass keeps them enabled
* Added updated German translations (thanks to @sweisgerber-dev)
* Power state (enabled and priority) now follows modules when they are swapped or copied
* Grey out modules that are powered off to provide a clearer visual indication
* Use coriolis-data 2.3.5:
* Fix list of available blueprints for Point Defence
* Fix integrity values for class 6 power plants
* Add shot speed for long range weapon
* Fix components for dirty drive grade 3
* Update values for Cytoscrambler
#2.3.4
* Fix crash when removing the special effect from a module
* Ensure comparisons with saved stock ships work correctly

View File

@@ -10,6 +10,11 @@ Coriolis was created using assets and imagery from Elite: Dangerous, with the pe
Please [submit issues](https://github.com/EDCD/coriolis/issues), or better yet [pull requests](https://github.com/EDCD/coriolis/pulls) for any corrections or additions to the database or the code.
### Translations
Please use the OneSky translation site to suggest new translations: http://edcd-coriolis.oneskyapp.com
These will be merged regularly by the project manager.
### Feature Requests, Suggestions & Bugs
Chat to us on [Discord](https://discord.gg/0uwCh6R62aPRjk9w)!
@@ -18,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.
Also see [the documentation site.](https://coriolis.willb.info/)
### Ship and Module Database

View File

@@ -169,7 +169,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.AwRj4zNLaA%3D%3D.CwBhCYzBGW9qCTSqq5xA.H4sIAAAAAAAAA2MUe8HMwPD%2FPwMcAABTINwTEgAAAA%3D%3D&bn=Test%20My%20Ship');
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.AwRj4zNLaA%3D%3D.CwBhCYzBGW9qCTSqq5xA.H4sIAAAAAAAAA2MUe8HMwPD%2FPwMAAGvB0AkAAAA%3D&bn=Test%20My%20Ship');
});
});
@@ -186,7 +186,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/asp?code=A0pftiFflfddsnf5------020202033c044002v62f2i.AwRj4yvI.CwRgDBldHnJA.H4sIAAAAAAAAA2P858DAwPCXEUhwHPvx%2F78YG5AltB7I%2F8%2F0TwImJboDSPJ%2F%2B%2Ff%2Fv%2FKlX%2F%2F%2Fi3AwMTBIfARK%2FGf%2BJwVSxArStVAYqOjvz%2F%2F%2FJVo5GRhE2IBc4SKQSSz%2FDGEmCa398P8%2F%2F2%2BgTf%2F%2FAwDFxwtofAAAAA%3D%3D&bn=Multi-purpose%20Asp%20Explorer');
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/asp?code=A0pftiFflfddsnf5------020202033c044002v62f2i.AwRj4yvI.CwRgDBldHnJA.H4sIAAAAAAAAA2P858DAwPCXEUhwHPvx%2F78YG5AltB7I%2F8%2F0TwImJboDSPJ%2F%2B%2Ff%2Fv%2FKlX%2F%2F%2Fi3AwMTBIfARK%2FGf%2BJwVSxArStVAYqOjvz%2F%2F%2FJVo5GRhE2IBc4SKQSSz%2FDGEmCa398P8%2F%2F2%2BgTf%2F%2FA7kMAExxqlSAAAAA&bn=Multi-purpose%20Asp%20Explorer');
});
it('imports a valid v4 build with modifications', function() {
@@ -198,7 +198,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/imperial_courier?code=A0patzF5l0das8f31a1a270202000e402t0101-2f.AwRj4zKA.CwRgDBldLiQ%3D.H4sIAAAAAAAAA12OP0tCYRjFj9fuVbvF1du9ekkT8s%2FkIg4NElyIBBd321yaGvwUQTS3N7UFfYygIT9EoyQUJA36ns47XJCWA%2B%2Fz%2Bz3Pe3ImBbDNKaqNPSBoGrL4ngfomKpFGiJ%2BLgHteR1IPjxJT5pF11uSeXNsJVcRfgdC92syWUuK0iMdKZqrjJ%2F0aoA71lJ5oKf38knWcCiptCPdhJIerdS00vlK0qktlqoj983UmqqHjQ33VsW8eazFmaTyULP2hQ4lX8LBme6g%2F6v0TTdbxJ2KhdEIaCw15MF%2FNB0L%2BS2hwEwyFM8KgP%2BqEpWWA3Qu9Z3z9kPWHzakt7Dt%2BAeD7ghSTgEAAA%3D%3D&bn=Multi-purpose%20Imperial%20Courier');
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/imperial_courier?code=A0patzF5l0das8f31a1a270202000e402t0101-2f.AwRj4zKA.CwRgDBldLiQ%3D.H4sIAAAAAAAAA12OPUvDYBSFT1OTfkRJjUkbbC3Yj8mlODgUISAtdOlety5ODv0Vgji7O7kJ%2FgzBQX%2BEY7Gg0NKhfY%2FnHQLFDBdynufe9%2BRMCmCb06g29oCgacjiRx6gY6oWKUT8UgLaszqQfHmSnpVFN1uSeXNsJVcj%2FA2EHlZkspIUpUc6UjTXGT85qwHuSEuVc%2F16r99kDQeSSjvSbSjpyUpNK10uJJ3aYqk6smwm1lQ9bOxw71TMm8VanEqq9JW1r3Qo%2BREOLnQHvbWmb7rZIu5VLIyGQGOukPv%2F0WQk5LeEAjPOUDwtAP6bShy2HKAz0HPO%2B5KsP25I79O2I7LvD%2Bz4Il1XAQAA&bn=Multi-purpose%20Imperial%20Courier');
});
});
@@ -240,7 +240,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/federal_corvette?code=A2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifr--v66g2f.AwRj4zNaqA%3D%3D.CwRgDBldUExuBiIlUA%3D%3D.H4sIAAAAAAAAA02SPy9DURjG3%2F65vW1v47TXVbeqqF7EQtIIBomRJswsYmISH8BgkFhqFZ9AwlALMYitkXQyEF2k4SMYJNK0dV7PK7nc5ck55%2Fm9z%2FnznpBeJqLvECQbM4hUjZnjO5hyWGfFikAGGjGiku0QuddhQCNdZmdWM9snsDmih4REOdlnNvz9DrPrJIicPdSwoZf8pAnTIpq8x7DYADS%2Bi5DERY85%2BYqpmkc6x%2FWGf6beKCR3YBIZFZCxCgrtczjuOmo4qTf94F4KYuxhz5jjEhXmUJNexFrpIUo02ALN1j9u1JMgD%2FMga1GfbMNRd9iHUwGy%2BpspZF3IBSGvMFJluS%2FuR24FJ2KlV%2Fxju6sQq4lhRsQTUVUJTgegLtS6EUjEE1HPAmUC0KdAjwKJeCKqD8zoURx72gHyDW9nvQhJGHkyUscS1x%2BAZnAlqwU%2FI%2BKJKEvextXrf93eQrR1KUlS5HWwGC61mfOn0oN3IM4OHoBzuuIHj33hS5jT8KeamIYa0sjhgH%2BLfplP4kcwD5Xl3xR1wfeHtqWzBHHX8I9SH9Je%2FgGvXxeungIAAA%3D%3D&bn=Imported%20Federal%20Corvette');
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/federal_corvette?code=A2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifr--v66g2f.AwRj4zNaqA%3D%3D.CwRgDBldUExuBiIlUA%3D%3D.H4sIAAAAAAAAA12STy8DURTFb1szU53Ga8dg2qqqDmJDIoKFxJImumYjVrVqfAALC4lNbcUnkLCoDbEQu0bSlQVhI8JHsJBIQ73rXMkwMYuT9%2Bb87nl%2F7ovoRSL6ikD6TYNINZg5XsWUo7pfrBikr2USlRyXyDuLAhr6ZHanNLOzD5tjOiskysk5dOBvfTB7bjeRW0MNG3ohSBq1bKKxKwyLLUAjmwjpPu4wJx4xVbNI57heDfbUKUAy2xaRUQZpllHoHMHxKqjhhF4LgjtJiFHDmqbrEeVnUJOax7%2FSdRfRwBNotv9wo5kAuZMD2egKyDYcdYl1OBki6z%2BZQjaFnBPyFCM1LefF%2BcgrY0es9FKwbW8ZYj9gmBbxRVRdglMh6BNqnwsk4ouoO4HSIehNoBuBRHwR1QOmsBvHmk6IfMbd2fdCEka%2BjNSexPWGoEkcyX6CnxbxRZQtd%2BPpym%2B31xFtn0iSFPkf%2BBkttZlzB9KDFyBuFRfAGV0Ogoff8SSsCfjjD5hGWtLIwZB%2FgX5Zt%2BLHMI9My7sp6nzgZzekswTxVvCOkq%2FSXqb%2F3zfLxh6HrwIAAA%3D%3D&bn=Imported%20Federal%20Corvette');
});
it('imports a valid companion API build', function() {
@@ -252,7 +252,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/beluga?code=A0pktsFplCdpsnf70t0t2727270004040404043c4fmimlmm04mc0iv62i2f.AwRj4yukg%3D%3D%3D.CwRgDBldHi8IUA%3D%3D.H4sIAAAAAAAAA2P8Z8%2FAwPCXEUiIKTMxMPCv%2F%2Ff%2FP8cFIPGf6Z8YTEr0GjMDg%2FJWICERBOTzn%2Fn7%2F7%2FIO5Ai5n9SIEWsQEIoSxAolfbt%2F3%2BJPk4GBhE7YQYGYVmgcuVnf4Aq%2FwMAIrEcGGsAAAA%3D&bn=Imported%20Beluga%20Liner');
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/beluga?code=A0pktsFplCdpsnf70t0t2727270004040404043c4fmimlmm04mc0iv62i2f.AwRj4yukg%3D%3D%3D.CwRgDBldHi8IUA%3D%3D.H4sIAAAAAAAAA2P8Z8%2FAwPCXEUiIKTMxMPCv%2F%2Ff%2FP8cFIPGf6Z8YTEr0GjMDg%2FJWICERBOTzn%2Fn7%2F7%2FIO5Ai5n9SIEWsQEIoSxAolfbt%2F3%2BJPk4GBhE7YQYGYVmgcuVnf4Aq%2FwOVAAAyiFctbgAAAA%3D%3D&bn=Imported%20Beluga%20Liner');
});
it('imports a valid companion API build', function() {
@@ -276,7 +276,7 @@ describe('Import Modal', function() {
expect(modal.state.singleBuild).toBe(true);
clickProceed();
expect(MockRouter.go.mock.calls.length).toBe(1);
expect(MockRouter.go.mock.calls[0][0]).toBe('outfit/cobra_mk_iii?code=A0p0tdFaldd3sdf4------34---2f2i.AwRj4yKA.CwRgDMYExre1Rcg%3D..EweloBhBGA2EoFMCGBzANokMK6A%3D');
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/cobra_mk_iii?code=A0p0tdFaldd3sdf4------34---2f2i.AwRj4yKA.CwRgDMYExrezBUg%3D.&bn=Imported%20Cobra%20Mk%20III');
});
});

878
d3.js vendored
View File

@@ -1340,6 +1340,12 @@ Selection.prototype = selection.prototype = {
dispatch: selection_dispatch
};
var select = function(selector) {
return typeof selector === "string"
? new Selection([[document.querySelector(selector)]], [document.documentElement])
: new Selection([[selector]], root);
};
var define = function(constructor, factory, prototype) {
constructor.prototype = factory.prototype = prototype;
prototype.constructor = constructor;
@@ -6947,861 +6953,7 @@ var cool = cubehelixLong(cubehelix(260, 0.75, 0.35), cubehelix(80, 1.50, 0.8));
var rainbow = cubehelix();
var xhtml$1 = "http://www.w3.org/1999/xhtml";
var namespaces$1 = {
svg: "http://www.w3.org/2000/svg",
xhtml: xhtml$1,
xlink: "http://www.w3.org/1999/xlink",
xml: "http://www.w3.org/XML/1998/namespace",
xmlns: "http://www.w3.org/2000/xmlns/"
};
var namespace$1 = function(name) {
var prefix = name += "", i = prefix.indexOf(":");
if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1);
return namespaces$1.hasOwnProperty(prefix) ? {space: namespaces$1[prefix], local: name} : name;
};
function creatorInherit$1(name) {
return function() {
var document = this.ownerDocument,
uri = this.namespaceURI;
return uri === xhtml$1 && document.documentElement.namespaceURI === xhtml$1
? document.createElement(name)
: document.createElementNS(uri, name);
};
}
function creatorFixed$1(fullname) {
return function() {
return this.ownerDocument.createElementNS(fullname.space, fullname.local);
};
}
var creator$1 = function(name) {
var fullname = namespace$1(name);
return (fullname.local
? creatorFixed$1
: creatorInherit$1)(fullname);
};
var matcher$2 = function(selector) {
return function() {
return this.matches(selector);
};
};
if (typeof document !== "undefined") {
var element$2 = document.documentElement;
if (!element$2.matches) {
var vendorMatches$1 = element$2.webkitMatchesSelector
|| element$2.msMatchesSelector
|| element$2.mozMatchesSelector
|| element$2.oMatchesSelector;
matcher$2 = function(selector) {
return function() {
return vendorMatches$1.call(this, selector);
};
};
}
}
var matcher$3 = matcher$2;
var filterEvents$1 = {};
var event$1 = null;
if (typeof document !== "undefined") {
var element$3 = document.documentElement;
if (!("onmouseenter" in element$3)) {
filterEvents$1 = {mouseenter: "mouseover", mouseleave: "mouseout"};
}
}
function filterContextListener$1(listener, index, group) {
listener = contextListener$1(listener, index, group);
return function(event) {
var related = event.relatedTarget;
if (!related || (related !== this && !(related.compareDocumentPosition(this) & 8))) {
listener.call(this, event);
}
};
}
function contextListener$1(listener, index, group) {
return function(event1) {
var event0 = event$1; // Events can be reentrant (e.g., focus).
event$1 = event1;
try {
listener.call(this, this.__data__, index, group);
} finally {
event$1 = event0;
}
};
}
function parseTypenames$2(typenames) {
return typenames.trim().split(/^|\s+/).map(function(t) {
var name = "", i = t.indexOf(".");
if (i >= 0) name = t.slice(i + 1), t = t.slice(0, i);
return {type: t, name: name};
});
}
function onRemove$1(typename) {
return function() {
var on = this.__on;
if (!on) return;
for (var j = 0, i = -1, m = on.length, o; j < m; ++j) {
if (o = on[j], (!typename.type || o.type === typename.type) && o.name === typename.name) {
this.removeEventListener(o.type, o.listener, o.capture);
} else {
on[++i] = o;
}
}
if (++i) on.length = i;
else delete this.__on;
};
}
function onAdd$1(typename, value, capture) {
var wrap = filterEvents$1.hasOwnProperty(typename.type) ? filterContextListener$1 : contextListener$1;
return function(d, i, group) {
var on = this.__on, o, listener = wrap(value, i, group);
if (on) for (var j = 0, m = on.length; j < m; ++j) {
if ((o = on[j]).type === typename.type && o.name === typename.name) {
this.removeEventListener(o.type, o.listener, o.capture);
this.addEventListener(o.type, o.listener = listener, o.capture = capture);
o.value = value;
return;
}
}
this.addEventListener(typename.type, listener, capture);
o = {type: typename.type, name: typename.name, value: value, listener: listener, capture: capture};
if (!on) this.__on = [o];
else on.push(o);
};
}
var selection_on$1 = function(typename, value, capture) {
var typenames = parseTypenames$2(typename + ""), i, n = typenames.length, t;
if (arguments.length < 2) {
var on = this.node().__on;
if (on) for (var j = 0, m = on.length, o; j < m; ++j) {
for (i = 0, o = on[j]; i < n; ++i) {
if ((t = typenames[i]).type === o.type && t.name === o.name) {
return o.value;
}
}
}
return;
}
on = value ? onAdd$1 : onRemove$1;
if (capture == null) capture = false;
for (i = 0; i < n; ++i) this.each(on(typenames[i], value, capture));
return this;
};
function none$1() {}
var selector$1 = function(selector) {
return selector == null ? none$1 : function() {
return this.querySelector(selector);
};
};
var selection_select$1 = function(select) {
if (typeof select !== "function") select = selector$1(select);
for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) {
for (var group = groups[j], n = group.length, subgroup = subgroups[j] = new Array(n), node, subnode, i = 0; i < n; ++i) {
if ((node = group[i]) && (subnode = select.call(node, node.__data__, i, group))) {
if ("__data__" in node) subnode.__data__ = node.__data__;
subgroup[i] = subnode;
}
}
}
return new Selection$2(subgroups, this._parents);
};
function empty$2() {
return [];
}
var selectorAll$1 = function(selector) {
return selector == null ? empty$2 : function() {
return this.querySelectorAll(selector);
};
};
var selection_selectAll$1 = function(select) {
if (typeof select !== "function") select = selectorAll$1(select);
for (var groups = this._groups, m = groups.length, subgroups = [], parents = [], j = 0; j < m; ++j) {
for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) {
if (node = group[i]) {
subgroups.push(select.call(node, node.__data__, i, group));
parents.push(node);
}
}
}
return new Selection$2(subgroups, parents);
};
var selection_filter$1 = function(match) {
if (typeof match !== "function") match = matcher$3(match);
for (var groups = this._groups, m = groups.length, subgroups = new Array(m), j = 0; j < m; ++j) {
for (var group = groups[j], n = group.length, subgroup = subgroups[j] = [], node, i = 0; i < n; ++i) {
if ((node = group[i]) && match.call(node, node.__data__, i, group)) {
subgroup.push(node);
}
}
}
return new Selection$2(subgroups, this._parents);
};
var sparse$1 = function(update) {
return new Array(update.length);
};
var selection_enter$1 = function() {
return new Selection$2(this._enter || this._groups.map(sparse$1), this._parents);
};
function EnterNode$1(parent, datum) {
this.ownerDocument = parent.ownerDocument;
this.namespaceURI = parent.namespaceURI;
this._next = null;
this._parent = parent;
this.__data__ = datum;
}
EnterNode$1.prototype = {
constructor: EnterNode$1,
appendChild: function(child) { return this._parent.insertBefore(child, this._next); },
insertBefore: function(child, next) { return this._parent.insertBefore(child, next); },
querySelector: function(selector) { return this._parent.querySelector(selector); },
querySelectorAll: function(selector) { return this._parent.querySelectorAll(selector); }
};
var constant$10 = function(x) {
return function() {
return x;
};
};
var keyPrefix$2 = "$"; // Protect against keys like “__proto__”.
function bindIndex$1(parent, group, enter, update, exit, data) {
var i = 0,
node,
groupLength = group.length,
dataLength = data.length;
// Put any non-null nodes that fit into update.
// Put any null nodes into enter.
// Put any remaining data into enter.
for (; i < dataLength; ++i) {
if (node = group[i]) {
node.__data__ = data[i];
update[i] = node;
} else {
enter[i] = new EnterNode$1(parent, data[i]);
}
}
// Put any non-null nodes that dont fit into exit.
for (; i < groupLength; ++i) {
if (node = group[i]) {
exit[i] = node;
}
}
}
function bindKey$1(parent, group, enter, update, exit, data, key) {
var i,
node,
nodeByKeyValue = {},
groupLength = group.length,
dataLength = data.length,
keyValues = new Array(groupLength),
keyValue;
// Compute the key for each node.
// If multiple nodes have the same key, the duplicates are added to exit.
for (i = 0; i < groupLength; ++i) {
if (node = group[i]) {
keyValues[i] = keyValue = keyPrefix$2 + key.call(node, node.__data__, i, group);
if (keyValue in nodeByKeyValue) {
exit[i] = node;
} else {
nodeByKeyValue[keyValue] = node;
}
}
}
// Compute the key for each datum.
// If there a node associated with this key, join and add it to update.
// If there is not (or the key is a duplicate), add it to enter.
for (i = 0; i < dataLength; ++i) {
keyValue = keyPrefix$2 + key.call(parent, data[i], i, data);
if (node = nodeByKeyValue[keyValue]) {
update[i] = node;
node.__data__ = data[i];
nodeByKeyValue[keyValue] = null;
} else {
enter[i] = new EnterNode$1(parent, data[i]);
}
}
// Add any remaining nodes that were not bound to data to exit.
for (i = 0; i < groupLength; ++i) {
if ((node = group[i]) && (nodeByKeyValue[keyValues[i]] === node)) {
exit[i] = node;
}
}
}
var selection_data$1 = function(value, key) {
if (!value) {
data = new Array(this.size()), j = -1;
this.each(function(d) { data[++j] = d; });
return data;
}
var bind = key ? bindKey$1 : bindIndex$1,
parents = this._parents,
groups = this._groups;
if (typeof value !== "function") value = constant$10(value);
for (var m = groups.length, update = new Array(m), enter = new Array(m), exit = new Array(m), j = 0; j < m; ++j) {
var parent = parents[j],
group = groups[j],
groupLength = group.length,
data = value.call(parent, parent && parent.__data__, j, parents),
dataLength = data.length,
enterGroup = enter[j] = new Array(dataLength),
updateGroup = update[j] = new Array(dataLength),
exitGroup = exit[j] = new Array(groupLength);
bind(parent, group, enterGroup, updateGroup, exitGroup, data, key);
// Now connect the enter nodes to their following update node, such that
// appendChild can insert the materialized enter node before this node,
// rather than at the end of the parent node.
for (var i0 = 0, i1 = 0, previous, next; i0 < dataLength; ++i0) {
if (previous = enterGroup[i0]) {
if (i0 >= i1) i1 = i0 + 1;
while (!(next = updateGroup[i1]) && ++i1 < dataLength);
previous._next = next || null;
}
}
}
update = new Selection$2(update, parents);
update._enter = enter;
update._exit = exit;
return update;
};
var selection_exit$1 = function() {
return new Selection$2(this._exit || this._groups.map(sparse$1), this._parents);
};
var selection_merge$1 = function(selection) {
for (var groups0 = this._groups, groups1 = selection._groups, m0 = groups0.length, m1 = groups1.length, m = Math.min(m0, m1), merges = new Array(m0), j = 0; j < m; ++j) {
for (var group0 = groups0[j], group1 = groups1[j], n = group0.length, merge = merges[j] = new Array(n), node, i = 0; i < n; ++i) {
if (node = group0[i] || group1[i]) {
merge[i] = node;
}
}
}
for (; j < m0; ++j) {
merges[j] = groups0[j];
}
return new Selection$2(merges, this._parents);
};
var selection_order$1 = function() {
for (var groups = this._groups, j = -1, m = groups.length; ++j < m;) {
for (var group = groups[j], i = group.length - 1, next = group[i], node; --i >= 0;) {
if (node = group[i]) {
if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next);
next = node;
}
}
}
return this;
};
var selection_sort$1 = function(compare) {
if (!compare) compare = ascending$2;
function compareNode(a, b) {
return a && b ? compare(a.__data__, b.__data__) : !a - !b;
}
for (var groups = this._groups, m = groups.length, sortgroups = new Array(m), j = 0; j < m; ++j) {
for (var group = groups[j], n = group.length, sortgroup = sortgroups[j] = new Array(n), node, i = 0; i < n; ++i) {
if (node = group[i]) {
sortgroup[i] = node;
}
}
sortgroup.sort(compareNode);
}
return new Selection$2(sortgroups, this._parents).order();
};
function ascending$2(a, b) {
return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
}
var selection_call$1 = function() {
var callback = arguments[0];
arguments[0] = this;
callback.apply(null, arguments);
return this;
};
var selection_nodes$1 = function() {
var nodes = new Array(this.size()), i = -1;
this.each(function() { nodes[++i] = this; });
return nodes;
};
var selection_node$1 = function() {
for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
for (var group = groups[j], i = 0, n = group.length; i < n; ++i) {
var node = group[i];
if (node) return node;
}
}
return null;
};
var selection_size$1 = function() {
var size = 0;
this.each(function() { ++size; });
return size;
};
var selection_empty$1 = function() {
return !this.node();
};
var selection_each$1 = function(callback) {
for (var groups = this._groups, j = 0, m = groups.length; j < m; ++j) {
for (var group = groups[j], i = 0, n = group.length, node; i < n; ++i) {
if (node = group[i]) callback.call(node, node.__data__, i, group);
}
}
return this;
};
function attrRemove$2(name) {
return function() {
this.removeAttribute(name);
};
}
function attrRemoveNS$2(fullname) {
return function() {
this.removeAttributeNS(fullname.space, fullname.local);
};
}
function attrConstant$2(name, value) {
return function() {
this.setAttribute(name, value);
};
}
function attrConstantNS$2(fullname, value) {
return function() {
this.setAttributeNS(fullname.space, fullname.local, value);
};
}
function attrFunction$2(name, value) {
return function() {
var v = value.apply(this, arguments);
if (v == null) this.removeAttribute(name);
else this.setAttribute(name, v);
};
}
function attrFunctionNS$2(fullname, value) {
return function() {
var v = value.apply(this, arguments);
if (v == null) this.removeAttributeNS(fullname.space, fullname.local);
else this.setAttributeNS(fullname.space, fullname.local, v);
};
}
var selection_attr$1 = function(name, value) {
var fullname = namespace$1(name);
if (arguments.length < 2) {
var node = this.node();
return fullname.local
? node.getAttributeNS(fullname.space, fullname.local)
: node.getAttribute(fullname);
}
return this.each((value == null
? (fullname.local ? attrRemoveNS$2 : attrRemove$2) : (typeof value === "function"
? (fullname.local ? attrFunctionNS$2 : attrFunction$2)
: (fullname.local ? attrConstantNS$2 : attrConstant$2)))(fullname, value));
};
var defaultView = function(node) {
return (node.ownerDocument && node.ownerDocument.defaultView) // node is a Node
|| (node.document && node) // node is a Window
|| node.defaultView; // node is a Document
};
function styleRemove$2(name) {
return function() {
this.style.removeProperty(name);
};
}
function styleConstant$2(name, value, priority) {
return function() {
this.style.setProperty(name, value, priority);
};
}
function styleFunction$2(name, value, priority) {
return function() {
var v = value.apply(this, arguments);
if (v == null) this.style.removeProperty(name);
else this.style.setProperty(name, v, priority);
};
}
var selection_style$1 = function(name, value, priority) {
var node;
return arguments.length > 1
? this.each((value == null
? styleRemove$2 : typeof value === "function"
? styleFunction$2
: styleConstant$2)(name, value, priority == null ? "" : priority))
: defaultView(node = this.node())
.getComputedStyle(node, null)
.getPropertyValue(name);
};
function propertyRemove$1(name) {
return function() {
delete this[name];
};
}
function propertyConstant$1(name, value) {
return function() {
this[name] = value;
};
}
function propertyFunction$1(name, value) {
return function() {
var v = value.apply(this, arguments);
if (v == null) delete this[name];
else this[name] = v;
};
}
var selection_property$1 = function(name, value) {
return arguments.length > 1
? this.each((value == null
? propertyRemove$1 : typeof value === "function"
? propertyFunction$1
: propertyConstant$1)(name, value))
: this.node()[name];
};
function classArray$1(string) {
return string.trim().split(/^|\s+/);
}
function classList$1(node) {
return node.classList || new ClassList$1(node);
}
function ClassList$1(node) {
this._node = node;
this._names = classArray$1(node.getAttribute("class") || "");
}
ClassList$1.prototype = {
add: function(name) {
var i = this._names.indexOf(name);
if (i < 0) {
this._names.push(name);
this._node.setAttribute("class", this._names.join(" "));
}
},
remove: function(name) {
var i = this._names.indexOf(name);
if (i >= 0) {
this._names.splice(i, 1);
this._node.setAttribute("class", this._names.join(" "));
}
},
contains: function(name) {
return this._names.indexOf(name) >= 0;
}
};
function classedAdd$1(node, names) {
var list = classList$1(node), i = -1, n = names.length;
while (++i < n) list.add(names[i]);
}
function classedRemove$1(node, names) {
var list = classList$1(node), i = -1, n = names.length;
while (++i < n) list.remove(names[i]);
}
function classedTrue$1(names) {
return function() {
classedAdd$1(this, names);
};
}
function classedFalse$1(names) {
return function() {
classedRemove$1(this, names);
};
}
function classedFunction$1(names, value) {
return function() {
(value.apply(this, arguments) ? classedAdd$1 : classedRemove$1)(this, names);
};
}
var selection_classed$1 = function(name, value) {
var names = classArray$1(name + "");
if (arguments.length < 2) {
var list = classList$1(this.node()), i = -1, n = names.length;
while (++i < n) if (!list.contains(names[i])) return false;
return true;
}
return this.each((typeof value === "function"
? classedFunction$1 : value
? classedTrue$1
: classedFalse$1)(names, value));
};
function textRemove$1() {
this.textContent = "";
}
function textConstant$2(value) {
return function() {
this.textContent = value;
};
}
function textFunction$2(value) {
return function() {
var v = value.apply(this, arguments);
this.textContent = v == null ? "" : v;
};
}
var selection_text$1 = function(value) {
return arguments.length
? this.each(value == null
? textRemove$1 : (typeof value === "function"
? textFunction$2
: textConstant$2)(value))
: this.node().textContent;
};
function htmlRemove$1() {
this.innerHTML = "";
}
function htmlConstant$1(value) {
return function() {
this.innerHTML = value;
};
}
function htmlFunction$1(value) {
return function() {
var v = value.apply(this, arguments);
this.innerHTML = v == null ? "" : v;
};
}
var selection_html$1 = function(value) {
return arguments.length
? this.each(value == null
? htmlRemove$1 : (typeof value === "function"
? htmlFunction$1
: htmlConstant$1)(value))
: this.node().innerHTML;
};
function raise$2() {
if (this.nextSibling) this.parentNode.appendChild(this);
}
var selection_raise$1 = function() {
return this.each(raise$2);
};
function lower$1() {
if (this.previousSibling) this.parentNode.insertBefore(this, this.parentNode.firstChild);
}
var selection_lower$1 = function() {
return this.each(lower$1);
};
var selection_append$1 = function(name) {
var create = typeof name === "function" ? name : creator$1(name);
return this.select(function() {
return this.appendChild(create.apply(this, arguments));
});
};
function constantNull$1() {
return null;
}
var selection_insert$1 = function(name, before) {
var create = typeof name === "function" ? name : creator$1(name),
select = before == null ? constantNull$1 : typeof before === "function" ? before : selector$1(before);
return this.select(function() {
return this.insertBefore(create.apply(this, arguments), select.apply(this, arguments) || null);
});
};
function remove$1() {
var parent = this.parentNode;
if (parent) parent.removeChild(this);
}
var selection_remove$1 = function() {
return this.each(remove$1);
};
var selection_datum$1 = function(value) {
return arguments.length
? this.property("__data__", value)
: this.node().__data__;
};
function dispatchEvent$1(node, type, params) {
var window = defaultView(node),
event = window.CustomEvent;
if (event) {
event = new event(type, params);
} else {
event = window.document.createEvent("Event");
if (params) event.initEvent(type, params.bubbles, params.cancelable), event.detail = params.detail;
else event.initEvent(type, false, false);
}
node.dispatchEvent(event);
}
function dispatchConstant$1(type, params) {
return function() {
return dispatchEvent$1(this, type, params);
};
}
function dispatchFunction$1(type, params) {
return function() {
return dispatchEvent$1(this, type, params.apply(this, arguments));
};
}
var selection_dispatch$1 = function(type, params) {
return this.each((typeof params === "function"
? dispatchFunction$1
: dispatchConstant$1)(type, params));
};
var root$2 = [null];
function Selection$2(groups, parents) {
this._groups = groups;
this._parents = parents;
}
function selection$2() {
return new Selection$2([[document.documentElement]], root$2);
}
Selection$2.prototype = selection$2.prototype = {
constructor: Selection$2,
select: selection_select$1,
selectAll: selection_selectAll$1,
filter: selection_filter$1,
data: selection_data$1,
enter: selection_enter$1,
exit: selection_exit$1,
merge: selection_merge$1,
order: selection_order$1,
sort: selection_sort$1,
call: selection_call$1,
nodes: selection_nodes$1,
node: selection_node$1,
size: selection_size$1,
empty: selection_empty$1,
each: selection_each$1,
attr: selection_attr$1,
style: selection_style$1,
property: selection_property$1,
classed: selection_classed$1,
text: selection_text$1,
html: selection_html$1,
raise: selection_raise$1,
lower: selection_lower$1,
append: selection_append$1,
insert: selection_insert$1,
remove: selection_remove$1,
datum: selection_datum$1,
on: selection_on$1,
dispatch: selection_dispatch$1
};
var select$1 = function(selector) {
return typeof selector === "string"
? new Selection$2([[document.querySelector(selector)]], [document.documentElement])
: new Selection$2([[selector]], root$2);
};
var constant$11 = function(x) {
return function constant() {
return x;
};
@@ -7850,7 +7002,7 @@ function y$3(p) {
var line = function() {
var x$$1 = x$3,
y$$1 = y$3,
defined = constant$11(true),
defined = constant$10(true),
context = null,
curve = curveLinear,
output = null;
@@ -7876,15 +7028,15 @@ var line = function() {
}
line.x = function(_) {
return arguments.length ? (x$$1 = typeof _ === "function" ? _ : constant$11(+_), line) : x$$1;
return arguments.length ? (x$$1 = typeof _ === "function" ? _ : constant$10(+_), line) : x$$1;
};
line.y = function(_) {
return arguments.length ? (y$$1 = typeof _ === "function" ? _ : constant$11(+_), line) : y$$1;
return arguments.length ? (y$$1 = typeof _ === "function" ? _ : constant$10(+_), line) : y$$1;
};
line.defined = function(_) {
return arguments.length ? (defined = typeof _ === "function" ? _ : constant$11(!!_), line) : defined;
return arguments.length ? (defined = typeof _ === "function" ? _ : constant$10(!!_), line) : defined;
};
line.curve = function(_) {
@@ -7924,7 +7076,7 @@ function slope2(that, t) {
// According to https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Representations
// "you can express cubic Hermite interpolation in terms of cubic Bézier curves
// with respect to the four values p0, p0 + m0 / 3, p1 - m1 / 3, p1".
function point$6(that, t0, t1) {
function point$5(that, t0, t1) {
var x0 = that._x0,
y0 = that._y0,
x1 = that._x1,
@@ -7953,7 +7105,7 @@ MonotoneX.prototype = {
lineEnd: function() {
switch (this._point) {
case 2: this._context.lineTo(this._x1, this._y1); break;
case 3: point$6(this, this._t0, slope2(this, this._t0)); break;
case 3: point$5(this, this._t0, slope2(this, this._t0)); break;
}
if (this._line || (this._line !== 0 && this._point === 1)) this._context.closePath();
this._line = 1 - this._line;
@@ -7966,8 +7118,8 @@ MonotoneX.prototype = {
switch (this._point) {
case 0: this._point = 1; this._line ? this._context.lineTo(x, y) : this._context.moveTo(x, y); break;
case 1: this._point = 2; break;
case 2: this._point = 3; point$6(this, slope2(this, t1 = slope3(this, x, y)), t1); break;
default: point$6(this, this._t0, t1 = slope3(this, x, y)); break;
case 2: this._point = 3; point$5(this, slope2(this, t1 = slope3(this, x, y)), t1); break;
default: point$5(this, this._t0, t1 = slope3(this, x, y)); break;
}
this._x0 = this._x1, this._x1 = x;
@@ -8153,7 +7305,7 @@ exports.line = line;
exports.scaleBand = band;
exports.scaleLinear = linear$2;
exports.scaleOrdinal = ordinal;
exports.select = select$1;
exports.select = select;
Object.defineProperty(exports, '__esModule', { value: true });

6
d3.min.js vendored

File diff suppressed because one or more lines are too long

1
devServer.js Normal file → Executable file
View File

@@ -5,6 +5,7 @@ var config = require('./webpack.config.dev');
new WebpackDevServer(webpack(config), {
publicPath: config.output.publicPath,
hot: true,
disableHostCheck: true,
headers: { "Access-Control-Allow-Origin": "*" },
historyApiFallback: {
rewrites: [

View File

@@ -1,106 +1,125 @@
{
"name": "coriolis_shipyard",
"version": "2.3.4",
"repository": {
"type": "git",
"url": "https://github.com/EDCD/coriolis"
},
"homepage": "https://coriolis.edcd.io",
"bugs": "https://github.com/EDCD/coriolis/issues",
"private": true,
"engine": "node >= 4.0.0",
"license": "MIT",
"scripts": {
"prepublish": "rollup -c && uglifyjs d3.js -c -m -o d3.min.js",
"extract-translations": "grep -hroE \"(translate\\('[^']+'\\))|(tip.bind\\(null, '[^']+')\" src/* | grep -oE \"'[^']+'\" | grep -oE \"[^']+\" | sort -u -f",
"clean": "rimraf build",
"start": "node devServer.js",
"lint": "eslint --ext .js,.jsx src",
"test": "jest",
"prod-serve": "nginx -p $(pwd) -c nginx.conf",
"prod-stop": "kill -QUIT $(cat nginx.pid)",
"build": "npm run clean && NODE_ENV=production webpack -p --config webpack.config.prod.js",
"rsync": "rsync -ae \"ssh -i $CORIOLIS_PEM\" --delete build/ $CORIOLIS_USER@$CORIOLIS_HOST:~/wwws",
"deploy": "npm run lint && npm test && npm run build && npm run rsync"
},
"jest": {
"transform": {".*": "<rootDir>/node_modules/babel-jest"},
"testRegex": "(/__tests__/test-.*|\\.(test|spec))\\.js$",
"moduleFileExtensions": [
"js",
"json",
"jsx"
],
"automock": true,
"bail": false,
"unmockedModulePathPatterns": [
"<rootDir>/node_modules/lodash",
"<rootDir>/node_modules/react",
"<rootDir>/node_modules/react-dom",
"<rootDir>/node_modules/react-testutils-additions",
"<rootDir>/node_modules/fbjs",
"<rootDir>/node_modules/fbemitter",
"<rootDir>/node_modules/classnames",
"<rootDir>/node_modules/d3",
"<rootDir>/node_modules/lz-string",
"<rootDir>/node_modules/jsen",
"coriolis-data",
"<rootDir>/src/app/shipyard",
"<rootDir>/src/app/i18n",
"<rootDir>/src/app/utils",
"<rootDir>/src/schemas",
"<rootDir>/__tests__"
]
},
"devDependencies": {
"appcache-webpack-plugin": "^1.3.0",
"babel-core": "*",
"babel-eslint": "*",
"babel-jest": "*",
"babel-loader": "*",
"babel-preset-es2015": "*",
"babel-preset-react": "*",
"babel-preset-stage-0": "*",
"css-loader": "^0.28.0",
"d3-selection": "1",
"eslint": "3.19.0",
"eslint-plugin-react": "^6.10.3",
"expose-loader": "^0.7.3",
"express": "^4.15.2",
"extract-text-webpack-plugin": "2.1.0",
"file-loader": "^0.11.1",
"html-webpack-plugin": "^2.28.0",
"jest-cli": "^19.0.2",
"jsen": "^0.6.4",
"json-loader": "^0.5.4",
"less": "^2.7.2",
"less-loader": "^4.0.3",
"react-addons-perf": "^15.4.2",
"react-measure": "^1.4.7",
"react-testutils-additions": "^15.2.0",
"rimraf": "^2.6.1",
"rollup": "0.41",
"rollup-plugin-node-resolve": "3",
"style-loader": "^0.16.1",
"url-loader": "^0.5.8",
"webpack": "^2.4.1",
"webpack-dev-server": "^2.4.4"
},
"dependencies": {
"babel-polyfill": "*",
"browserify-zlib-next": "^1.0.1",
"classnames": "^2.2.5",
"coriolis-data": "EDCD/coriolis-data",
"d3": "4.8.0",
"detect-browser": "^1.7.0",
"fbemitter": "^2.1.1",
"lodash": "^4.17.4",
"lz-string": "^1.4.4",
"prop-types": "^15.5.8",
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react-number-editor": "Athanasius/react-number-editor.git#miggy",
"recharts": "^0.22.3",
"superagent": "^3.5.2"
}
}
{
"name": "coriolis_shipyard",
"version": "3.0.0",
"repository": {
"type": "git",
"url": "https://github.com/EDCD/coriolis"
},
"homepage": "https://coriolis.edcd.io",
"bugs": "https://github.com/EDCD/coriolis/issues",
"private": true,
"engine": "node >= 4.8.1",
"license": "MIT",
"scripts": {
"prepublish": "rollup -c && uglifyjs d3.js -c -m -o d3.min.js",
"extract-translations": "grep -hroE \"(translate\\('[^']+'\\))|(tip.bind\\(null, '[^']+')\" src/* | grep -oE \"'[^']+'\" | grep -oE \"[^']+\" | sort -u -f",
"clean": "rimraf build",
"start": "node devServer.js",
"lint": "eslint --ext .js,.jsx src",
"test": "jest",
"prod-serve": "nginx -p $(pwd) -c nginx.conf",
"prod-stop": "kill -QUIT $(cat nginx.pid)",
"build": "npm run clean && cross-env NODE_ENV=production webpack -p --config webpack.config.prod.js",
"rsync": "rsync -ae \"ssh -i $CORIOLIS_PEM\" --delete build/ $CORIOLIS_USER@$CORIOLIS_HOST:~/wwws",
"deploy": "npm run lint && npm test && npm run build && npm run rsync"
},
"jest": {
"transform": {
".*": "<rootDir>/node_modules/babel-jest"
},
"testRegex": "(/__tests__/test-.*|\\.(test|spec))\\.js$",
"moduleFileExtensions": [
"js",
"json",
"jsx"
],
"automock": true,
"bail": false,
"unmockedModulePathPatterns": [
"<rootDir>/node_modules/lodash",
"<rootDir>/node_modules/react",
"<rootDir>/node_modules/react-dom",
"<rootDir>/node_modules/react-transition-group",
"<rootDir>/node_modules/react-testutils-additions",
"<rootDir>/node_modules/fbjs",
"<rootDir>/node_modules/fbemitter",
"<rootDir>/node_modules/classnames",
"<rootDir>/node_modules/d3",
"<rootDir>/node_modules/lz-string",
"<rootDir>/node_modules/jsen",
"coriolis-data",
"<rootDir>/src/app/shipyard",
"<rootDir>/src/app/i18n",
"<rootDir>/src/app/utils",
"<rootDir>/src/schemas",
"<rootDir>/__tests__"
]
},
"devDependencies": {
"appcache-webpack-plugin": "^1.3.0",
"babel-core": "*",
"babel-eslint": "*",
"babel-jest": "*",
"babel-loader": "*",
"babel-preset-env": "*",
"babel-preset-react": "*",
"babel-preset-stage-0": "*",
"create-react-class": "^15.6.2",
"cross-env": "^5.1.4",
"css-loader": "^0.28.0",
"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-plugin-react": "^6.10.3",
"expose-loader": "^0.7.3",
"express": "^4.15.2",
"extract-text-webpack-plugin": "2.1.0",
"file-loader": "^0.11.1",
"html-webpack-plugin": "^2.28.0",
"jest-cli": "^21.2.1",
"jsen": "^0.6.4",
"json-loader": "^0.5.4",
"less": "^2.7.2",
"less-loader": "^4.0.3",
"react-addons-perf": "^15.4.2",
"react-container-dimensions": "^1.4.1",
"react-testutils-additions": "^15.2.0",
"react-transition-group": "^1.1.2",
"rimraf": "^2.6.1",
"rollup": "0.41",
"rollup-plugin-node-resolve": "3",
"style-loader": "^0.16.1",
"uglify-js": "^2.4.11",
"url-loader": "^0.5.8",
"webpack": "^2.4.1",
"webpack-bugsnag-plugins": "^1.1.1",
"webpack-dev-server": "^2.4.4",
"webpack-notifier": "^1.6.0",
"workbox-webpack-plugin": "^3.4.1"
},
"dependencies": {
"babel-polyfill": "*",
"browserify-zlib-next": "^1.0.1",
"classnames": "^2.2.5",
"coriolis-data": "../coriolis-data",
"d3": "4.8.0",
"detect-browser": "^1.7.0",
"fbemitter": "^2.1.1",
"lodash": "^4.17.10",
"lz-string": "^1.4.4",
"pako": "^1.0.6",
"prop-types": "^15.5.8",
"react": "^15.5.4",
"react-dom": "^15.5.4",
"react-ga": "^2.5.3",
"react-number-editor": "Athanasius/react-number-editor.git#miggy",
"recharts": "^0.22.3",
"superagent": "^3.5.2"
}
}

View File

@@ -12,6 +12,7 @@ import ModalHelp from './components/ModalHelp';
import ModalImport from './components/ModalImport';
import ModalPermalink from './components/ModalPermalink';
import * as CompanionApiUtils from './utils/CompanionApiUtils';
import * as JournalUtils from './utils/JournalUtils';
import AboutPage from './pages/AboutPage';
import NotFoundPage from './pages/NotFoundPage';
@@ -20,7 +21,7 @@ import ComparisonPage from './pages/ComparisonPage';
import ShipyardPage from './pages/ShipyardPage';
import ErrorDetails from './pages/ErrorDetails';
const zlib = require('zlib');
const zlib = require('pako');
/**
* Coriolis App
@@ -90,9 +91,16 @@ export default class Coriolis extends React.Component {
_importBuild(r) {
try {
// Need to decode and gunzip the data, then build the ship
const data = zlib.gunzipSync(new Buffer(r.params.data, 'base64'));
const data = zlib.inflate(new Buffer(r.params.data, 'base64'), { to: 'string' });
const json = JSON.parse(data);
const ship = CompanionApiUtils.shipFromJson(json);
console.info('Ship import data: ');
console.info(json);
let ship;
if (json && json.modules) {
ship = CompanionApiUtils.shipFromJson(json);
} else if (json && json.Modules) {
ship = JournalUtils.shipFromLoadoutJSON(json);
}
r.params.ship = ship.id;
r.params.code = ship.toString();
this._setPage(OutfittingPage, r);
@@ -124,6 +132,13 @@ export default class Coriolis extends React.Component {
*/
_onError(msg, scriptUrl, line, col, errObj) {
console && console.error && console.error(arguments); // eslint-disable-line no-console
if (errObj) {
if (errObj instanceof Error) {
bugsnagClient.notify(errObj); // eslint-disable-line
} else if (errObj instanceof String) {
bugsnagClient.notify(msg, errObj); // eslint-disable-line
}
}
this.setState({
error: <ErrorDetails error={{ message: msg, details: { scriptUrl, line, col, error: JSON.stringify(errObj) } }}/>,
page: null,
@@ -165,13 +180,13 @@ export default class Coriolis extends React.Component {
case 72: // 'h'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + h
e.preventDefault();
this._showModal(<ModalHelp />);
this._showModal(<ModalHelp/>);
}
break;
case 73: // 'i'
if (e.ctrlKey || e.metaKey) { // CTRL/CMD + i
e.preventDefault();
this._showModal(<ModalImport />);
this._showModal(<ModalImport/>);
}
break;
case 79: // 'o'
@@ -193,7 +208,7 @@ export default class Coriolis extends React.Component {
* @param {React.Component} content Modal 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 });
}
@@ -271,7 +286,7 @@ export default class Coriolis extends React.Component {
return this.emitter.addListener('windowResize', listener);
}
/**
/**
* Add a listener to global commands such as save,
* @param {Function} listener Listener callback
* @return {Object} Subscription token
@@ -307,14 +322,60 @@ export default class Coriolis extends React.Component {
*/
componentWillMount() {
// Listen for appcache updated event, present refresh to update view
if (window.applicationCache) {
window.applicationCache.addEventListener('updateready', () => {
if (window.applicationCache.status == window.applicationCache.UPDATEREADY) {
this.setState({ appCacheUpdate: true }); // Browser downloaded a new app cache.
}
// Check that service workers are registered
if (navigator.storage && navigator.storage.persist) {
window.addEventListener('load', () => {
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.addEventListener('resize', () => this.emitter.emit('windowResize'));
document.getElementById('coriolis').addEventListener('scroll', () => this._tooltip());
@@ -332,14 +393,22 @@ export default class Coriolis extends React.Component {
render() {
let currentMenu = this.state.currentMenu;
return <div style={{ minHeight: '100%' }} onClick={this._closeMenu} className={ this.state.noTouch ? 'no-touch' : null }>
<Header appCacheUpdate={this.state.appCacheUpdate} currentMenu={currentMenu} />
{ this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) : <NotFoundPage/> }
{ this.state.modal }
{ this.state.tooltip }
return <div style={{ minHeight: '100%' }} onClick={this._closeMenu}
className={this.state.noTouch ? 'no-touch' : null}>
<Header appCacheUpdate={this.state.appCacheUpdate} currentMenu={currentMenu}/>
{this.state.error ? this.state.error : this.state.page ? React.createElement(this.state.page, { currentMenu }) :
<NotFoundPage/>}
{this.state.modal}
{this.state.tooltip}
<footer>
<div className="right cap">
<a href="https://github.com/EDCD/coriolis" target="_blank" title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>
<a href="https://github.com/EDCD/coriolis" target="_blank"
title="Coriolis Github Project">{window.CORIOLIS_VERSION} - {window.CORIOLIS_DATE}</a>
<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>
</div>
</footer>
</div>;

View File

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

@@ -20,6 +20,7 @@ const GRPCAT = {
'fx': 'limpet controllers',
'hb': 'limpet controllers',
'pc': 'limpet controllers',
'rpl': 'limpet controllers',
'pce': 'passenger cabins',
'pci': 'passenger cabins',
'pcm': 'passenger cabins',
@@ -36,10 +37,20 @@ const GRPCAT = {
'ml': 'lasers',
'c': 'projectiles',
'mc': 'projectiles',
'axmc': 'experimental',
'fc': 'projectiles',
'rfl': 'experimental',
'pa': 'projectiles',
'rg': 'projectiles',
'mr': 'ordnance',
'axmr': 'experimental',
'rcpl': 'experimental',
'dtl': 'experimental',
'tbsc': 'experimental',
'tbem': 'experimental',
'tbrfl': 'experimental',
'mahr': 'experimental',
'rsl': 'experimental',
'tp': 'ordnance',
'nl': 'ordnance',
'sc': 'scanners',
@@ -48,9 +59,20 @@ const GRPCAT = {
'cs': 'scanners',
'kw': 'scanners',
'ws': 'scanners',
'xs': 'scanners',
'ch': 'defence',
'po': 'defence',
'ec': 'defence',
'sfn': 'defence',
// Guardian
'gpp': 'guardian',
'gpc': '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
const CATEGORIES = {
@@ -60,7 +82,7 @@ const CATEGORIES = {
'fi': ['fi'],
'fuel': ['ft', 'fs'],
'hangars': ['fh', 'pv'],
'limpet controllers': ['cc', 'fx', 'hb', 'pc'],
'limpet controllers': ['cc', 'fx', 'hb', 'pc', 'rpl'],
'passenger cabins': ['pce', 'pci', 'pcm', 'pcq'],
'rf': ['rf'],
'shields': ['sg', 'bsg', 'psg', 'scb'],
@@ -75,6 +97,11 @@ const CATEGORIES = {
'hs': ['hs'],
'defence': ['ch', 'po', 'ec'],
'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners
// Experimental
'experimental': ['axmc', 'axmr', 'rfl', 'tbrfl', 'tbsc', 'tbem', 'xs', 'sfn', 'rcpl', 'dtl', 'rsl', 'mahr', ],
// Guardian
'guardian': ['gpp', 'gpd', 'gpc', 'ggc', 'gsrp', 'gfsb', 'ghrp', 'gmrp', 'gsc']
};
/**
@@ -88,7 +115,11 @@ export default class AvailableModulesMenu extends TranslatedComponent {
diffDetails: PropTypes.func,
m: PropTypes.object,
shipMass: PropTypes.number,
warning: PropTypes.func
warning: PropTypes.func,
firstSlotId: PropTypes.string,
lastSlotId: PropTypes.string,
activeSlotId: PropTypes.string,
slotDiv: PropTypes.object
};
static defaultProps = {
@@ -104,6 +135,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
super(props);
this._hideDiff = this._hideDiff.bind(this);
this.state = this._initState(props, context);
this.slotItems = [];// Array to hold <li> refs.
}
/**
@@ -114,8 +146,9 @@ export default class AvailableModulesMenu extends TranslatedComponent {
*/
_initState(props, context) {
let translate = context.language.translate;
let { m, warning, shipMass, onSelect, modules } = props;
let { m, warning, shipMass, onSelect, modules, firstSlotId, lastSlotId } = props;
let list, currentGroup;
let buildGroup = this._buildGroup.bind(
this,
translate,
@@ -126,7 +159,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
this._hideDiff(event);
onSelect(m);
}
);
);
if (modules instanceof Array) {
list = buildGroup(modules[0].grp, modules);
@@ -134,7 +167,10 @@ export default class AvailableModulesMenu extends TranslatedComponent {
list = [];
// At present time slots with grouped options (Hardpoints and Internal) can be empty
if (m) {
list.push(<div className='empty-c upp' key='empty' onClick={onSelect.bind(null, null)} >{translate('empty')}</div>);
let emptyId = 'empty';
if(this.firstSlotId == null) this.firstSlotId = emptyId;
let keyDown = this._keyDown.bind(this, onSelect);
list.push(<div className='empty-c upp' key={emptyId} data-id={emptyId} onClick={onSelect.bind(null, null)} onKeyDown={keyDown} tabIndex="0" ref={slotItem => this.slotItems[emptyId] = slotItem} >{translate('empty')}</div>);
}
// Need to regroup the modules by our own categorisation
@@ -148,7 +184,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
const existing = catmodules[moduleCategory] || [];
catmodules[moduleCategory] = existing.concat(modules[g]);
}
for (let category in catmodules) {
let categoryHeader = false;
// Order through CATEGORIES if present
@@ -184,37 +220,40 @@ export default class AvailableModulesMenu extends TranslatedComponent {
}
}
}
return { list, currentGroup };
let trackingFocus = false;
return { list, currentGroup, trackingFocus };
}
/**
* Generate React Components for Module Group
* @param {Function} translate Translate function
* @param {Objecy} mountedModule Mounted Module
* @param {Funciton} warningFunc Warning function
* @param {Object} mountedModule Mounted Module
* @param {Function} warningFunc Warning function
* @param {number} mass Mass
* @param {function} onSelect Select/Mount callback
* @param {string} grp Group name
* @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
*/
_buildGroup(translate, mountedModule, warningFunc, mass, onSelect, grp, modules) {
let prevClass = null, prevRating = null;
_buildGroup(translate, mountedModule, warningFunc, mass, onSelect, grp, modules, firstSlotId, lastSlotId) {
let prevClass = null, prevRating = null, prevName;
let elems = [];
const sortedModules = modules.sort(this._moduleOrder);
// Calculate the number of items per class. Used so we don't have long lists with only a few items in each row
const tmp = sortedModules.map((v, i) => v['class']).reduce((count, cls) => { count[cls] = ++count[cls] || 1; return count; }, {});
const itemsPerClass = Math.max.apply(null, Object.keys(tmp).map(key => tmp[key]));
let itemsOnThisRow = 0;
for (let i = 0; i < sortedModules.length; i++) {
let m = sortedModules[i];
let mount = null;
let disabled = false;
prevName = m.name;
if (ModuleUtils.isShieldGenerator(m.grp)) {
// Shield generators care about maximum hull mass
disabled = mass > m.maxmass;
@@ -230,9 +269,21 @@ export default class AvailableModulesMenu extends TranslatedComponent {
});
let eventHandlers;
if (disabled || active) {
eventHandlers = {};
if (disabled) {
eventHandlers = {
onKeyDown: this._keyDown.bind(this, null),
onKeyUp: this._keyUp.bind(this, null)
};
} else {
/**
* Get the ids of the first and last <li> elements in the <ul> that are focusable (i.e. are not active or disabled)
* Will be used to keep focus inside the <ul> on Tab and Shift-Tab while it is visible
*/
if (this.firstSlotId == null) this.firstSlotId = sortedModules[i].id;
if (active) this.activeSlotId = sortedModules[i].id;
this.lastSlotId = sortedModules[i].id;
let showDiff = this._showDiff.bind(this, mountedModule, m);
let select = onSelect.bind(null, m);
@@ -241,7 +292,9 @@ export default class AvailableModulesMenu extends TranslatedComponent {
onTouchStart: this._touchStart.bind(this, showDiff),
onTouchEnd: this._touchEnd.bind(this, select),
onMouseLeave: this._hideDiff,
onClick: select
onClick: select,
onKeyDown: this._keyDown.bind(this, select),
onKeyUp: this._keyUp.bind(this, select)
};
}
@@ -250,24 +303,28 @@ export default class AvailableModulesMenu extends TranslatedComponent {
case 'G': mount = <MountGimballed className={'lg'}/>; break;
case 'T': mount = <MountTurret className={'lg'}/>; break;
}
if (m.name && m.name === prevName) {
// elems.push(<br key={'b' + m.grp + i} />);
itemsOnThisRow = 0;
}
if (itemsOnThisRow == 6 || i > 0 && sortedModules.length > 3 && itemsPerClass > 2 && m.class != prevClass && (m.rating != prevRating || m.mount)) {
elems.push(<br key={'b' + m.grp + i} />);
itemsOnThisRow = 0;
}
let tbIdx = (classes.indexOf('disabled') < 0) ? 0 : undefined;
elems.push(
<li key={m.id} className={classes} {...eventHandlers}>
<li key={m.id} data-id={m.id} className={classes} {...eventHandlers} tabIndex={tbIdx} ref={slotItem => this.slotItems[m.id] = slotItem}>
{mount}
{(mount ? ' ' : '') + m.class + m.rating + (m.missile ? '/' + m.missile : '') + (m.name ? ' ' + translate(m.name) : '')}
</li>
);
itemsOnThisRow++;
prevClass = m.class;
prevRating = m.rating;
prevName = m.name;
}
return <ul key={'modules' + grp} >{elems}</ul>;
return <ul key={'modules' + grp}>{elems}</ul>;
}
/**
@@ -318,6 +375,41 @@ export default class AvailableModulesMenu extends TranslatedComponent {
this._hideDiff();
}
/**
* 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
*/
_keyDown(select, event) {
let className = event.currentTarget.attributes['class'].value;
if (event.key == 'Enter' && className.indexOf('disabled') < 0 && className.indexOf('active') < 0) {
select();
return;
}
let elemId = event.currentTarget.attributes['data-id'].value;
if (className.indexOf('disabled') < 0 && event.key == 'Tab') {
if (event.shiftKey && elemId == this.firstSlotId) {
event.preventDefault();
this.slotItems[this.lastSlotId].focus();
return;
}
if (!event.shiftKey && elemId == this.lastSlotId) {
event.preventDefault();
this.slotItems[this.firstSlotId].focus();
return;
}
}
}
/**
* Key Up
* @param {Function} select Select module callback
* @param {SytheticEvent} event Event
*/
_keyUp(select,event) {
// nothing here yet
}
/**
* Hide diff tooltip
* @param {SyntheticEvent} event Event
@@ -375,6 +467,23 @@ export default class AvailableModulesMenu extends TranslatedComponent {
if (this.groupElem) { // Scroll to currently selected group
this.node.scrollTop = this.groupElem.offsetTop;
}
/**
* Set focus on active or first slot element, if applicable.
*/
if (this.slotItems[this.activeSlotId]) {
this.slotItems[this.activeSlotId].focus();
} else if (this.slotItems[this.firstSlotId]) {
this.slotItems[this.firstSlotId].focus();
}
}
/**
* Handle focus if the component updates
*
*/
componentWillUnmount() {
if(this.props.slotDiv) {
this.props.slotDiv.focus();
}
}
/**

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

View File

@@ -25,7 +25,7 @@ export default class EngagementRange extends TranslatedComponent {
const { ship } = props;
const maxRange = this._calcMaxRange(ship);
const maxRange = Math.round(this._calcMaxRange(ship));
this.state = {
maxRange

View File

@@ -57,7 +57,7 @@ export default class FSDProfile extends TranslatedComponent {
*/
_calcMaxRange(ship, fuel, mass) {
// 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 mass = ship.unladenMass + fuel + cargo;
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
const mark = Math.min(mass, maxMass);

View File

@@ -1,4 +1,5 @@
import React from 'react';
import cn from 'classnames';
import Slot from './Slot';
import Persist from '../stores/Persist';
import { DamageAbsolute, DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret, ListModifications, Modified } from './SvgIcons';
@@ -32,12 +33,13 @@ export default class HardpointSlot extends Slot {
/**
* Generate the slot contents
* @param {Object} m Mounted Module
* @param {Boolean} enabled Slot enabled
* @param {Function} translate Translate function
* @param {Object} formats Localized Formats map
* @param {Object} u Localized Units Map
* @return {React.Component} Slot contents
*/
_getSlotDetails(m, translate, formats, u) {
_getSlotDetails(m, enabled, translate, formats, u) {
if (m) {
let classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
let { drag, drop } = this.props;
@@ -60,7 +62,8 @@ export default class HardpointSlot extends Slot {
);
}
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
const className = cn('details', enabled ? '' : 'disabled');
return <div className={className} draggable='true' onDragStart={drag} onDragEnd={drop}>
<div className={'cb'}>
<div className={'l'}>
{m.mount && m.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed /></span> : ''}
@@ -76,7 +79,7 @@ export default class HardpointSlot extends Slot {
<div className={'r'}>{formats.round(m.getMass())}{u.T}</div>
</div>
<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.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 }
@@ -94,11 +97,12 @@ export default class HardpointSlot extends Slot {
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
{ m && validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
{ m && validMods.length > 0 ? <div className='r' tabIndex="0" ref={ modButton => this.modButton = modButton }><button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
</div>
</div>;
} else {
return <div className={'empty'}>{translate('empty')}</div>;
}
}
}

View File

@@ -17,14 +17,25 @@ export default class HardpointSlotSection extends SlotSection {
*/
constructor(props, context) {
super(props, context, 'hardpoints', 'hardpoints');
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() {
this.selectedRefId = 'emptyall';
this.props.ship.emptyWeapons();
this.props.onChange();
this._close();
@@ -37,6 +48,7 @@ export default class HardpointSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event
*/
_fill(group, mount, event) {
this.selectedRefId = group + '-' + mount;
this.props.ship.useWeapon(group, mount, null, event.getModifierState('Alt'));
this.props.onChange();
this._close();
@@ -77,6 +89,7 @@ export default class HardpointSlotSection extends SlotSection {
dropClass={this._dropClass(h, originSlot, targetSlot)}
ship={ship}
m={h.m}
enabled={h.enabled ? true : false}
/>);
}
}
@@ -94,52 +107,52 @@ export default class HardpointSlotSection extends SlotSection {
return <div className='select hardpoint' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li>
<li className='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>
</ul>
<div className='select-group cap'>{translate('pl')}</div>
<ul>
<li className='c' onClick={_fill.bind(this, 'pl', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'pl', 'G')}><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', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pl-F'] = smRef}><MountFixed 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' tabIndex='0' onClick={_fill.bind(this, 'pl', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pl-T'] = smRef}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('ul')}</div>
<ul>
<li className='c' onClick={_fill.bind(this, 'ul', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'ul', 'G')}><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', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ul-F'] = smRef}><MountFixed 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' tabIndex='0' onClick={_fill.bind(this, 'ul', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ul-T'] = smRef}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('bl')}</div>
<ul>
<li className='c' onClick={_fill.bind(this, 'bl', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'bl', 'G')}><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', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['bl-F'] = smRef}><MountFixed 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' tabIndex='0' onClick={_fill.bind(this, 'bl', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['bl-T'] = smRef}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('mc')}</div>
<ul>
<li className='c' onClick={_fill.bind(this, 'mc', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'mc', 'G')}><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', 'F')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['mc-F'] = smRef}><MountFixed 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' tabIndex='0' onClick={_fill.bind(this, 'mc', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['mc-T'] = smRef}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('c')}</div>
<ul>
<li className='c' onClick={_fill.bind(this, 'c', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'c', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'c', 'T')}><MountTurret className='lg'/></li>
<li className='c' 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' 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' tabIndex='0' onClick={_fill.bind(this, 'c', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['c-T'] = smRef}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('fc')}</div>
<ul>
<li className='c' onClick={_fill.bind(this, 'fc', 'F')}><MountFixed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'fc', 'G')}><MountGimballed className='lg'/></li>
<li className='c' onClick={_fill.bind(this, 'fc', 'T')}><MountTurret className='lg'/></li>
<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' 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' tabIndex='0' onClick={_fill.bind(this, 'fc', 'T')} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['fc-T'] = smRef}><MountTurret className='lg'/></li>
</ul>
<div className='select-group cap'>{translate('pa')}</div>
<ul>
<li className='lc' onClick={_fill.bind(this, 'pa', 'F')}>{translate('pa')}</li>
<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>
<div className='select-group cap'>{translate('nl')}</div>
<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>
</div>;
}

View File

@@ -9,6 +9,8 @@ import { Cogs, CoriolisLogo, Hammer, Help, Rocket, StatsBars } from './SvgIcons'
import { Ships } from 'coriolis-data/dist';
import Persist from '../stores/Persist';
import { toDetailedExport } from '../shipyard/Serializer';
import Ship from '../shipyard/Ship';
import ModalBatchOrbis from './ModalBatchOrbis';
import ModalDeleteAll from './ModalDeleteAll';
import ModalExport from './ModalExport';
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
* @param {SyntheticEvent} e Event
@@ -430,6 +469,7 @@ export default class Header extends TranslatedComponent {
{translate('builds')} & {translate('comparisons')}
<li><Link href="#" className='block' onClick={this._showBackup.bind(this)}>{translate('backup')}</Link></li>
<li><Link href="#" className='block' onClick={this._showDetailedExport.bind(this)}>{translate('detailed export')}</Link></li>
<li><Link href="#" className='block' onClick={this._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._showDeleteAll.bind(this)}>{translate('delete all')}</Link></li>
</ul>
@@ -505,6 +545,9 @@ export default class Header extends TranslatedComponent {
return (
<header>
{this.props.appCacheUpdate && <div id="app-update" onClick={() => window.location.reload() }>{translate('PHRASE_UPDATE_RDY')}</div>}
{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'}
</a> : null}
<Link className='l' href='/' style={{ marginRight: '1em' }} title='Home'><CoriolisLogo className='icon xl' /></Link>
<div className='l menu'>
@@ -528,6 +571,16 @@ export default class Header extends TranslatedComponent {
{openedMenu == 'comp' ? this._getComparisonsMenu() : null}
</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={cn('menu-header', { selected: openedMenu == 'settings' })} onClick={this._openSettings}>
<Cogs className='xl warning'/><span className='menu-item-label'>{translate('settings')}</span>

View File

@@ -1,4 +1,5 @@
import React from 'react';
import cn from 'classnames';
import Slot from './Slot';
import Persist from '../stores/Persist';
import { ListModifications, Modified } from './SvgIcons';
@@ -14,23 +15,27 @@ export default class InternalSlot extends Slot {
/**
* Generate the slot contents
* @param {Object} m Mounted Module
* @param {Boolean} enabled Slot enabled
* @param {Function} translate Translate function
* @param {Object} formats Localized Formats map
* @param {Object} u Localized Units Map
* @return {React.Component} Slot contents
*/
_getSlotDetails(m, translate, formats, u) {
_getSlotDetails(m, enabled, translate, formats, u) {
if (m) {
let classRating = m.class + m.rating;
let { drag, drop, ship } = this.props;
let { termtip, tooltip } = this.context;
let validMods = Modifications.modules[m.grp].modifications || [];
let validMods = (Modifications.modules[m.grp] ? Modifications.modules[m.grp].modifications : []);
let showModuleResistances = Persist.showModuleResistances();
// Modifications tooltip shows blueprint and grade, if available
let modTT = translate('modified');
if (m && m.blueprint && m.blueprint.name) {
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
if (m.blueprint.special && m.blueprint.special.id >= 0) {
modTT += ', ' + translate(m.blueprint.special.name);
}
modTT = (
<div>
<div>{modTT}</div>
@@ -40,7 +45,9 @@ export default class InternalSlot extends Slot {
}
let mass = m.getMass() || m.cargo || m.fuel || 0;
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
const className = cn('details', enabled ? '' : 'disabled');
return <div className={className} draggable='true' onDragStart={drag} onDragEnd={drop}>
<div className={'cb'}>
<div className={'l'}>{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : ''}</div>
<div className={'r'}>{formats.round(mass)}{u.T}</div>
@@ -56,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.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 === '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('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 }
@@ -63,6 +73,7 @@ export default class InternalSlot extends Slot {
{ m.getRange() ? <div className={'l'}>{translate('range')} {formats.f2(m.getRange())}{u.km}</div> : null }
{ m.getRangeT() ? <div className={'l'}>{translate('ranget')} {formats.f1(m.getRangeT())}{u.s}</div> : null }
{ m.getTime() ? <div className={'l'}>{translate('time')}: {formats.time(m.getTime())}</div> : null }
{ m.getHackTime() ? <div className={'l'}>{translate('hacktime')}: {formats.time(m.getHackTime())}</div> : null }
{ m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
{ m.rangeLS ? <div className={'l'}>{translate('range')}: {m.rangeLS}{u.Ls}</div> : null }
{ m.rangeLS === null ? <div className={'l'}>{u.Ls}</div> : null }
@@ -74,10 +85,11 @@ export default class InternalSlot extends Slot {
{ showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null }
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
{ 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.getProtection() ? <div className='l'>{translate('protection')}: {formats.rPct(m.getProtection())}</div> : null }
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
{ m && validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
{ m && validMods.length > 0 ? <div className='r' tabIndex="0" ref={ modButton => this.modButton = modButton }><button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
</div>
</div>;
} else {

View File

@@ -18,7 +18,6 @@ export default class InternalSlotSection extends SlotSection {
*/
constructor(props, context) {
super(props, context, 'internal', 'optional internal');
this._empty = this._empty.bind(this);
this._fillWithCargo = this._fillWithCargo.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._fillWithBusinessClassCabins = this._fillWithBusinessClassCabins.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() {
this.selectedRefId = 'emptyall';
this.props.ship.emptyInternal();
this.props.onChange();
this._close();
@@ -45,6 +56,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event
*/
_fillWithCargo(event) {
this.selectedRefId = 'cargo';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
@@ -61,6 +73,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event
*/
_fillWithFuelTanks(event) {
this.selectedRefId = 'ft';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
@@ -77,6 +90,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event
*/
_fillWithLuxuryCabins(event) {
this.selectedRefId = 'pcq';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
@@ -93,6 +107,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event
*/
_fillWithFirstClassCabins(event) {
this.selectedRefId = 'pcm';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
@@ -109,6 +124,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event
*/
_fillWithBusinessClassCabins(event) {
this.selectedRefId = 'pci';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
@@ -125,6 +141,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event
*/
_fillWithEconomyClassCabins(event) {
this.selectedRefId = 'pce';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
@@ -141,6 +158,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event
*/
_fillWithCells(event) {
this.selectedRefId = 'scb';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
let chargeCap = 0; // Capacity of single activation
@@ -160,6 +178,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event
*/
_fillWithArmor(event) {
this.selectedRefId = 'hr';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
@@ -176,6 +195,7 @@ export default class InternalSlotSection extends SlotSection {
* @param {SyntheticEvent} event Event
*/
_fillWithModuleReinforcementPackages(event) {
this.selectedRefId = 'mrp';
let clobber = event.getModifierState('Alt');
let ship = this.props.ship;
ship.internal.forEach((slot) => {
@@ -216,7 +236,6 @@ export default class InternalSlotSection extends SlotSection {
onChange={this.props.onChange}
onSelect={this._selectModule.bind(this, s)}
selected={currentMenu == s}
enabled={s.enabled}
eligible={s.eligible}
m={s.m}
drag={this._drag.bind(this, s)}
@@ -225,6 +244,7 @@ export default class InternalSlotSection extends SlotSection {
dropClass={this._dropClass(s, originSlot, targetSlot)}
fuel={fuelCapacity}
ship={ship}
enabled={s.enabled ? true : false}
/>);
}
@@ -240,16 +260,16 @@ export default class InternalSlotSection extends SlotSection {
_getSectionMenu(translate, ship) {
return <div className='select' onClick={e => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li>
<li className='lc' onClick={this._fillWithCargo}>{translate('cargo')}</li>
<li className='lc' onClick={this._fillWithCells}>{translate('scb')}</li>
<li className='lc' onClick={this._fillWithArmor}>{translate('hr')}</li>
<li className='lc' onClick={this._fillWithModuleReinforcementPackages}>{translate('mrp')}</li>
<li className='lc' onClick={this._fillWithFuelTanks}>{translate('ft')}</li>
<li className='lc' onClick={this._fillWithEconomyClassCabins}>{translate('pce')}</li>
<li className='lc' onClick={this._fillWithBusinessClassCabins}>{translate('pci')}</li>
<li className='lc' onClick={this._fillWithFirstClassCabins}>{translate('pcm')}</li>
{ ship.luxuryCabins ? <li className='lc' onClick={this._fillWithLuxuryCabins}>{translate('pcq')}</li> : ''}
<li className='lc' tabIndex='0' onClick={this._empty} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['emptyall'] = smRef}>{translate('empty all')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithCargo} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['cargo'] = smRef}>{translate('cargo')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithCells} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['scb'] = smRef}>{translate('scb')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithArmor} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['hr'] = smRef}>{translate('hr')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithModuleReinforcementPackages} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['mrp'] = smRef}>{translate('mrp')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithFuelTanks} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['ft'] = smRef}>{translate('ft')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithEconomyClassCabins} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pce'] = smRef}>{translate('pce')}</li>
<li className='lc' tabIndex='0' onClick={this._fillWithBusinessClassCabins} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['pci'] = smRef}>{translate('pci')}</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' 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>
</ul>
</div>;

View File

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

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') {
for (let shipId in importData.builds) {
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 });

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>
<input value={this.state.shortenedUrl} readOnly size={25} onFocus={ (e) => e.target.select() }/>
<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>
</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 cn from 'classnames';
import NumberEditor from 'react-number-editor';
import { isValueBeneficial } from '../utils/BlueprintFunctions';
/**
* Modification
@@ -14,7 +15,10 @@ export default class Modification extends TranslatedComponent {
m: PropTypes.object.isRequired,
name: PropTypes.string.isRequired,
value: PropTypes.number.isRequired,
onChange: PropTypes.func.isRequired
onChange: PropTypes.func.isRequired,
onKeyDown: PropTypes.func.isRequired,
modItems: PropTypes.array.isRequired,
handleModChange: PropTypes.func.isRequired
};
/**
@@ -35,31 +39,23 @@ export default class Modification extends TranslatedComponent {
* in a value by hand
*/
_updateValue(value) {
const name = this.props.name;
let scaledValue = Math.round(Number(value) * 100);
// Limit to +1000% / -99.99%
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);
let { m, name, ship } = this.props;
value = Math.max(Math.min(value, 50000), -50000);
ship.setModification(m, name, value, true, true);
this.setState({ value });
}
/**
* 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
* with each onBlur event, even when no change has actually been made to the field.
*/
_updateFinished() {
this.props.onChange();
if (this.props.value != this.state.value) {
this.props.handleModChange(true);
this.props.onChange();
}
}
/**
@@ -67,28 +63,50 @@ export default class Modification extends TranslatedComponent {
* @return {React.Component} modification
*/
render() {
let translate = this.context.language.translate;
let { translate, formats, units } = this.context.language;
let { m, name } = this.props;
let modValue = m.getChange(name);
if (name === 'damagedist') {
// We don't show damage distribution
return null;
}
let symbol;
if (name === 'jitter') {
symbol = '°';
} else if (name !== 'burst' && name != 'burstrof') {
symbol = '%';
}
if (symbol) {
symbol = ' (' + symbol + ')';
}
return (
<div onBlur={this._updateFinished.bind(this)} className={'cb'} key={name}>
<div className={'cb'}>{translate(name, m.grp)}{symbol}</div>
<NumberEditor className={'cb'} style={{ width: '90%', textAlign: 'center' }} step={0.01} stepModifier={1} decimals={2} value={this.state.value} onValueChange={this._updateValue.bind(this)} />
<div onBlur={this._updateFinished.bind(this)} key={name}
className={cn('cb', 'modification-container')}
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>
);
}

View File

@@ -6,7 +6,18 @@ import { isEmpty, stopCtxPropagation } from '../utils/UtilityFunctions';
import cn from 'classnames';
import { Modifications } from 'coriolis-data/dist';
import Modification from './Modification';
import { getBlueprint, blueprintTooltip, setWorst, setBest, setExtreme, setRandom } from '../utils/BlueprintFunctions';
import {
getBlueprint,
blueprintTooltip,
setPercent,
getPercent,
setRandom,
specialToolTip
} from '../utils/BlueprintFunctions';
const MODIFICATIONS_COMPARATOR = (mod1, mod2) => {
return mod1.props.name.localeCompare(mod2.props.name);
};
/**
* Modifications menu
@@ -17,7 +28,8 @@ export default class ModificationsMenu extends TranslatedComponent {
ship: PropTypes.object.isRequired,
m: PropTypes.object.isRequired,
marker: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired
onChange: PropTypes.func.isRequired,
modButton:PropTypes.object
};
/**
@@ -30,14 +42,24 @@ export default class ModificationsMenu extends TranslatedComponent {
this._toggleBlueprintsMenu = this._toggleBlueprintsMenu.bind(this);
this._toggleSpecialsMenu = this._toggleSpecialsMenu.bind(this);
this._rollWorst = this._rollWorst.bind(this);
this._rollFifty = this._rollFifty.bind(this);
this._rollRandom = this._rollRandom.bind(this);
this._rollBest = this._rollBest.bind(this);
this._rollExtreme = this._rollExtreme.bind(this);
this._rollWorst = this._rollWorst.bind(this);
this._reset = this._reset.bind(this);
this._keyDown = this._keyDown.bind(this);
this.modItems = [];// Array to hold various element refs (<li>, <div>, <ul>, etc.)
this.firstModId = null;
this.firstBPLabel = null;// First item in mod menu
this.lastModId = null;
this.selectedModId = null;
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.state = {
blueprintMenuOpened: false,
blueprintMenuOpened: !(props.m.blueprint && props.m.blueprint.name),
specialMenuOpened: false
};
}
@@ -52,7 +74,6 @@ export default class ModificationsMenu extends TranslatedComponent {
const { m } = props;
const { language, tooltip, termtip } = context;
const translate = language.translate;
const blueprints = [];
for (const blueprintName in Modifications.modules[m.grp].blueprints) {
const blueprint = getBlueprint(blueprintName, m);
@@ -61,14 +82,18 @@ export default class ModificationsMenu extends TranslatedComponent {
// Grade is a string in the JSON so make it a number
grade = Number(grade);
const classes = cn('c', {
active: m.blueprint && blueprint.id === m.blueprint.id && grade === m.blueprint.grade
active: m.blueprint && blueprint.id === m.blueprint.id && grade === m.blueprint.grade
});
const close = this._blueprintSelected.bind(this, blueprintName, grade);
const key = blueprintName + ':' + grade;
const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers, m.grp);
blueprintGrades.unshift(<li key={key} className={classes} style={{ width: '2em' }} onMouseOver={termtip.bind(null, tooltipContent)} onMouseOut={tooltip.bind(null, null)} onClick={close}>{grade}</li>);
if (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>);
}
if (blueprintGrades) {
const thisLen = blueprintGrades.length;
if (this.firstModId == null) this.firstModId = blueprintGrades[0].key;
this.lastModId = blueprintGrades[thisLen - 1].key;
blueprints.push(<div key={blueprint.name} className={'select-group cap'}>{translate(blueprint.name)}</div>);
blueprints.push(<ul key={blueprintName}>{blueprintGrades}</ul>);
}
@@ -76,6 +101,64 @@ export default class ModificationsMenu extends TranslatedComponent {
return blueprints;
}
/**
* Key down - select module on Enter key, move to next/previous module on Tab/Shift-Tab, close on Esc
* @param {SyntheticEvent} event Event
*
*/
_keyDown(event) {
let className = null;
let elemId = null;
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.key == 'Enter' && className.indexOf('disabled') < 0 && className.indexOf('active') < 0) {
event.stopPropagation();
if (elemId != null) {
this.modItems[elemId].click();
} else {
event.currentTarget.click();
}
return;
}
if (event.key == 'Tab') {
// Shift-Tab
if(event.shiftKey) {
if (elemId == this.firstModId && elemId != null) {
// Initial modification menu
event.preventDefault();
this.modItems[this.lastModId].focus();
return;
} 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
event.preventDefault();
this.modItems[this.lastNeId].lastChild.focus();
return;
} else if (event.currentTarget.className.indexOf('button-inline-menu') >= 0 && event.currentTarget.previousElementSibling == null) {
// shift-tab on button-inline-menu with no number editor
event.preventDefault();
event.currentTarget.parentElement.lastElementChild.focus();
}
} else {
if (elemId == this.lastModId && elemId != null) {
// Initial modification menu
event.preventDefault();
this.modItems[this.firstModId].focus();
return;
} else if (event.currentTarget.className.indexOf('button-inline-menu') >= 0 && event.currentTarget.nextSibling == null && event.currentTarget.nodeName != 'TD') {
// Experimental menu
event.preventDefault();
event.currentTarget.parentElement.firstElementChild.focus();
return;
} else if (event.currentTarget.className == 'cb' && event.currentTarget.parentElement.nextSibling == null) {
event.preventDefault();
this.modItems[this.firstBPLabel].focus();
}
}
}
}
/**
* Render the specials
* @param {Object} props React component properties
@@ -86,15 +169,34 @@ export default class ModificationsMenu extends TranslatedComponent {
const { m } = props;
const { language, tooltip, termtip } = context;
const translate = language.translate;
const specials = [];
const specialsId = m.missile ? 'specials_' + m.missile : 'specials';
const specialsId = m.missile && Modifications.modules[m.grp]['specials_' + m.missile] ? 'specials_' + m.missile : 'specials';
if (Modifications.modules[m.grp][specialsId] && Modifications.modules[m.grp][specialsId].length > 0) {
const close = this._specialSelected.bind(this, null);
specials.push(<div style={{ cursor: 'pointer' }} key={ 'none' } onClick={ close }>{translate('PHRASE_NO_SPECIAL')}</div>);
specials.push(<div tabIndex="0" style={{ cursor: 'pointer', fontWeight: 'bold' }} className={ 'button-inline-menu warning' } key={ 'none' } data-id={ 'none' } onClick={ close } onKeyDown={this._keyDown} ref={modItem => this.modItems['none'] = modItem}>{translate('PHRASE_NO_SPECIAL')}</div>);
for (const specialName of Modifications.modules[m.grp][specialsId]) {
if (Modifications.specials[specialName].name.search('Legacy') >= 0) {
continue;
}
const classes = cn('button-inline-menu', {
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);
specials.push(<div style={{ cursor: 'pointer' }} key={ specialName } onClick={ close }>{translate(Modifications.specials[specialName].name)}</div>);
if (m.blueprint && m.blueprint.name) {
let tmp = {};
if (m.blueprint.special) {
tmp = m.blueprint.special;
} else {
tmp = undefined;
}
m.blueprint.special = Modifications.specials[specialName];
let specialTt = specialToolTip(translate, m.blueprint.grades[m.blueprint.grade], m.grp, m, specialName);
m.blueprint.special = tmp;
specials.push(<div tabIndex="0" style={{ cursor: 'pointer' }} className={classes} key={ specialName } data-id={ specialName } onMouseOver={termtip.bind(null, specialTt)} onMouseOut={tooltip.bind(null, null)} onClick={ close } onKeyDown={this._keyDown} ref={modItem => this.modItems[specialName] = modItem}>{translate(Modifications.specials[specialName].name)}</div>);
} else {
specials.push(<div tabIndex="0" style={{ cursor: 'pointer' }} className={classes} key={ specialName } data-id={ specialName }onClick={ close } onKeyDown={this._keyDown} ref={modItem => this.modItems[specialName] = modItem}>{translate(Modifications.specials[specialName].name)}</div>);
}
}
}
return specials;
@@ -107,14 +209,26 @@ export default class ModificationsMenu extends TranslatedComponent {
*/
_renderModifications(props) {
const { m, onChange, ship } = props;
const modifiableModifications = [];
const modifications = [];
for (const modName of Modifications.modules[m.grp].modifications) {
if (!Modifications.modifications[modName].hidden) {
const key = modName + (m.getModValue(modName) / 100 || 0);
modifications.push(<Modification key={ key } ship={ ship } m={ m } name={ modName } value={ m.getModValue(modName) / 100 || 0 } onChange={ onChange }/>);
const editable = modName !== 'fallofffromrange' &&
m.blueprint.grades[m.blueprint.grade].features[modName];
this.lastNeId = modName;
(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} />
);
}
}
return modifications;
modifiableModifications.sort(MODIFICATIONS_COMPARATOR);
modifications.sort(MODIFICATIONS_COMPARATOR);
return modifiableModifications.concat(modifications);
}
/**
@@ -136,8 +250,9 @@ export default class ModificationsMenu extends TranslatedComponent {
const blueprint = getBlueprint(fdname, m);
blueprint.grade = grade;
ship.setModuleBlueprint(m, blueprint);
setPercent(ship, m, 100);
this.setState({ blueprintMenuOpened: false });
this.setState({ blueprintMenuOpened: false, specialMenuOpened: true });
this.props.onChange();
}
@@ -168,11 +283,15 @@ export default class ModificationsMenu extends TranslatedComponent {
}
/**
* Provide a 'worst' roll within the information we have
* Provide a '50%' roll within the information we have
*/
_rollWorst() {
_rollFifty() {
const { m, ship } = this.props;
setWorst(ship, m);
setPercent(ship, m, 50);
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
this._handleModChange(true);
this.props.onChange();
}
@@ -182,6 +301,10 @@ export default class ModificationsMenu extends TranslatedComponent {
_rollRandom() {
const { m, ship } = this.props;
setRandom(ship, m);
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
this._handleModChange(true);
this.props.onChange();
}
@@ -190,16 +313,22 @@ export default class ModificationsMenu extends TranslatedComponent {
*/
_rollBest() {
const { m, ship } = this.props;
setBest(ship, m);
setPercent(ship, m, 100);
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
this._handleModChange(true);
this.props.onChange();
}
/**
* Provide an 'extreme' roll within the information we have
* Provide a 'worst' roll within the information we have
*/
_rollExtreme() {
_rollWorst() {
const { m, ship } = this.props;
setExtreme(ship, m);
setPercent(ship, m, 0);
// this will change the values in the modifications. Set modDidChange to true to prevent focus change when component updates
this._handleModChange(true);
this.props.onChange();
}
@@ -210,10 +339,67 @@ export default class ModificationsMenu extends TranslatedComponent {
const { m, ship } = this.props;
ship.clearModifications(m);
ship.clearModuleBlueprint(m);
this.selectedModId = null;
this.selectedSpecialId = null;
this.props.onChange();
}
/**
* set mod did change boolean
* @param {boolean} b Boolean to determine if a change has been made to a module
*/
_handleModChange(b) {
this.modValDidChange = b;
}
/**
* Set focus on first element in modifications menu
* after it first mounts
*/
componentDidMount() {
let firstEleCn = this.modItems['modMainDiv'].children.length > 0 ? this.modItems['modMainDiv'].children[0].className : null;
if (firstEleCn.indexOf('select-group cap') >= 0) {
this.modItems['modMainDiv'].children[1].firstElementChild.focus();
} else {
this.modItems['modMainDiv'].firstElementChild.focus();
}
}
/**
* Set focus on first element in modifications menu
* if component updates, unless update is due to value change
* in a modification
*/
componentDidUpdate() {
if (!this.modValDidChange) {
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;
if (firstEleCn.indexOf('button-inline-menu') >= 0) {
this.modItems['modMainDiv'].firstElementChild.focus();
} else if (firstEleCn.indexOf('select-group cap') >= 0) {
this.modItems['modMainDiv'].children[1].firstElementChild.focus();
}
}
} else {
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() {
if (this.props.modButton) {
this.props.modButton.focus();
}
}
/**
* Render the list
* @return {React.Component} List
@@ -226,66 +412,82 @@ export default class ModificationsMenu extends TranslatedComponent {
const _toggleBlueprintsMenu = this._toggleBlueprintsMenu;
const _toggleSpecialsMenu = this._toggleSpecialsMenu;
const _rollBest = this._rollBest;
const _rollExtreme = this._rollExtreme;
const _rollFull = this._rollBest;
const _rollWorst = this._rollWorst;
const _rollFifty = this._rollFifty;
const _rollRandom = this._rollRandom;
const _reset = this._reset;
let blueprintLabel;
let haveBlueprint = false;
let blueprintTt;
if (m.blueprint && m.blueprint.name) {
let blueprintCv;
// 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]) {
this.props.ship.clearModuleBlueprint(m);
this.props.ship.clearModuleSpecial(m);
}
if (m.blueprint && m.blueprint.name && Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade]) {
blueprintLabel = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
haveBlueprint = true;
blueprintTt = blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], Modifications.modules[m.grp].blueprints[m.blueprint.fdname].grades[m.blueprint.grade].engineers, m.grp);
blueprintCv = getPercent(m);
}
let specialLabel;
let specialTt;
if (m.blueprint && m.blueprint.special) {
specialLabel = m.blueprint.special.name;
specialTt = specialToolTip(translate, m.blueprint.grades[m.blueprint.grade], m.grp, m, m.blueprint.special.edname);
} else {
specialLabel = translate('PHRASE_SELECT_SPECIAL');
}
const specials = this._renderSpecials(this.props, this.context);
/**
* pnellesen - 05/28/2018 - added additional checks for specials.length below to ensure menus
* display correctly in cases where there are no specials (ex: AFMUs.)
*/
const showBlueprintsMenu = blueprintMenuOpened;
const showSpecial = haveBlueprint && specials.length && !blueprintMenuOpened;
const showSpecialsMenu = specialMenuOpened;
const showRolls = haveBlueprint && !blueprintMenuOpened && !specialMenuOpened;
const showReset = !blueprintMenuOpened && !specialMenuOpened;
const showMods = !blueprintMenuOpened && !specialMenuOpened;
const showSpecialsMenu = specialMenuOpened && specials.length;
const showRolls = haveBlueprint && !blueprintMenuOpened && (!specialMenuOpened || !specials.length);
const showReset = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint;
const showMods = !blueprintMenuOpened && (!specialMenuOpened || !specials.length) && haveBlueprint;
if (haveBlueprint) {
this.firstBPLabel = blueprintLabel;
} else {
this.firstBPLabel = 'selectBP';
}
return (
<div
className={cn('select', this.props.className)}
onClick={(e) => e.stopPropagation() }
onContextMenu={stopCtxPropagation}
ref={modItem => this.modItems['modMainDiv'] = modItem}
>
{ showBlueprintsMenu ? '' : haveBlueprint ?
<div className={ cn('section-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={termtip.bind(null, blueprintTt)} onMouseOut={tooltip.bind(null, null)} onClick={_toggleBlueprintsMenu}>{blueprintLabel}</div> :
<div className={ cn('section-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleBlueprintsMenu}>{translate('PHRASE_SELECT_BLUEPRINT')}</div> }
{ showBlueprintsMenu | 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' }} 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 }
{ showSpecial ? <div className={ cn('section-menu', { selected: specialMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleSpecialsMenu}>{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 }
{ showRolls || showReset ?
{ 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 ?
<table style={{ width: '100%', backgroundColor: 'transparent' }}>
<tbody>
{ showRolls ?
<tr>
<td> { translate('roll') }: </td>
<td style={{ cursor: 'pointer' }} onClick={_rollWorst} onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')} onMouseOut={tooltip.bind(null, null)}> { translate('worst') } </td>
<td style={{ cursor: 'pointer' }} onClick={_rollBest}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')} onMouseOut={tooltip.bind(null, null)}> { translate('best') } </td>
<td style={{ cursor: 'pointer' }} onClick={_rollExtreme}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_EXTREME')} onMouseOut={tooltip.bind(null, null)}> { translate('extreme') } </td>
<td style={{ cursor: 'pointer' }} onClick={_rollRandom} onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')} onMouseOut={tooltip.bind(null, null)}> { translate('random') } </td>
</tr> : null }
{ showReset ?
<tr>
<td colSpan={'5'} style={{ cursor: 'pointer' }} onClick={_reset}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')} onMouseOut={tooltip.bind(null, null)}> { translate('reset') } </td>
<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 === 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 === 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>
</tr> : null }
</tbody>
</table> : null }
{ showMods ? <hr /> : null }
{ showMods ?
<span onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')} onMouseOut={tooltip.bind(null, null)} >
{ this._renderModifications(this.props) }

View File

@@ -58,13 +58,13 @@ export default class Movement extends TranslatedComponent {
<path d="M342 450l.4 1.5-16.2 10.7-.4-.2-3.5-13 .3-.3L342 450zm-14.3 7.6l7.7-5-9.2-.6 1.5 5.6z"/>
// Speed
<text x="470" y="30" strokeWidth='0'>{formats.int(ship.calcSpeed(eng, fuel, cargo, boost))}m/s</text>
<text x="470" y="30" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcSpeed(eng, fuel, cargo, boost)) + 'm/s' : '-'}</text>
// Pitch
<text x="355" y="410" strokeWidth='0'>{formats.int(ship.calcPitch(eng, fuel, cargo, boost))}°/s</text>
<text x="355" y="410" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcPitch(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
// Roll
<text x="450" y="110" strokeWidth='0'>{formats.int(ship.calcRoll(eng, fuel, cargo, boost))}°/s</text>
<text x="450" y="110" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcRoll(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
// Yaw
<text x="160" y="430" strokeWidth='0'>{formats.int(ship.calcYaw(eng, fuel, cargo, boost))}°/s</text>
<text x="160" y="430" strokeWidth='0'>{ship.canThrust(cargo, fuel) ? formats.int(ship.calcYaw(eng, fuel, cargo, boost)) + '°/s' : '-'}</text>
</svg>
</span>);
}

View File

@@ -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 consists of four panels:
@@ -70,7 +127,7 @@ export default class Offence extends TranslatedComponent {
this._sort = this._sort.bind(this);
const damage = Calc.offenceMetrics(props.ship, props.opponent, props.wep, props.opponentSys, props.engagementrange);
this.state = {
this.state = {
predicate: 'n',
desc: true,
damage
@@ -144,51 +201,36 @@ export default class Offence extends TranslatedComponent {
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 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 = [];
for (let i = 0; i < damage.length; i++) {
const weapon = damage[i];
totalSEps += weapon.seps;
absoluteShieldsSDps += weapon.sdps.shields.absolute;
explosiveShieldsSDps += weapon.sdps.shields.explosive;
kineticShieldsSDps += weapon.sdps.shields.kinetic;
thermalShieldsSDps += weapon.sdps.shields.thermal;
absoluteArmourSDps += weapon.sdps.armour.absolute;
explosiveArmourSDps += weapon.sdps.armour.explosive;
kineticArmourSDps += weapon.sdps.armour.kinetic;
thermalArmourSDps += weapon.sdps.armour.thermal;
addSDps(totalSDpsObject, weapon.sdps.base);
addSDps(shieldsSDpsObject, weapon.sdps.shields);
addSDps(armourSDpsObject, weapon.sdps.armour);
const baseSDpsTooltipDetails = getSDpsTooltip(translate, formats, weapon.sdps.base);
const effectivenessShieldsTooltipDetails = [];
effectivenessShieldsTooltipDetails.push(<div key='range'>{translate('range') + ' ' + formats.pct1(weapon.effectiveness.shields.range)}</div>);
effectivenessShieldsTooltipDetails.push(<div key='resistance'>{translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.shields.resistance)}</div>);
effectivenessShieldsTooltipDetails.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(weapon.effectiveness.shields.sys)}</div>);
const effectiveShieldsSDpsTooltipDetails = [];
if (weapon.sdps.shields.absolute) effectiveShieldsSDpsTooltipDetails.push(<div key='absolute'>{translate('absolute') + ' ' + formats.f1(weapon.sdps.shields.absolute)}</div>);
if (weapon.sdps.shields.explosive) effectiveShieldsSDpsTooltipDetails.push(<div key='explosive'>{translate('explosive') + ' ' + formats.f1(weapon.sdps.shields.explosive)}</div>);
if (weapon.sdps.shields.kinetic) effectiveShieldsSDpsTooltipDetails.push(<div key='kinetic'>{translate('kinetic') + ' ' + formats.f1(weapon.sdps.shields.kinetic)}</div>);
if (weapon.sdps.shields.thermal) effectiveShieldsSDpsTooltipDetails.push(<div key='thermal'>{translate('thermal') + ' ' + formats.f1(weapon.sdps.shields.thermal)}</div>);
const effectiveShieldsSDpsTooltipDetails = getSDpsTooltip(translate, formats, weapon.sdps.armour);
const effectivenessArmourTooltipDetails = [];
effectivenessArmourTooltipDetails.push(<div key='range'>{translate('range') + ' ' + formats.pct1(weapon.effectiveness.armour.range)}</div>);
effectivenessArmourTooltipDetails.push(<div key='resistance'>{translate('resistance') + ' ' + formats.pct1(weapon.effectiveness.armour.resistance)}</div>);
effectivenessArmourTooltipDetails.push(<div key='hardness'>{translate('hardness') + ' ' + formats.pct1(weapon.effectiveness.armour.hardness)}</div>);
const effectiveArmourSDpsTooltipDetails = [];
if (weapon.sdps.armour.absolute) effectiveArmourSDpsTooltipDetails.push(<div key='absolute'>{translate('absolute') + ' ' + formats.f1(weapon.sdps.armour.absolute)}</div>);
if (weapon.sdps.armour.explosive) effectiveArmourSDpsTooltipDetails.push(<div key='explosive'>{translate('explosive') + ' ' + formats.f1(weapon.sdps.armour.explosive)}</div>);
if (weapon.sdps.armour.kinetic) effectiveArmourSDpsTooltipDetails.push(<div key='kinetic'>{translate('kinetic') + ' ' + formats.f1(weapon.sdps.armour.kinetic)}</div>);
if (weapon.sdps.armour.thermal) effectiveArmourSDpsTooltipDetails.push(<div key='thermal'>{translate('thermal') + ' ' + formats.f1(weapon.sdps.armour.thermal)}</div>);
const effectiveArmourSDpsTooltipDetails = getSDpsTooltip(translate, formats, weapon.sdps.armour);
rows.push(
<tr key={weapon.id}>
@@ -199,27 +241,25 @@ export default class Offence extends TranslatedComponent {
{weapon.classRating} {translate(weapon.name)}
{weapon.engineering ? ' (' + weapon.engineering + ')' : null }
</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, effectivenessShieldsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(weapon.effectiveness.shields.total)}</span></td>
<td className='ri'><span onMouseOver={termtip.bind(null, effectiveArmourSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.armour.total)}</span></td>
<td className='ri'><span onMouseOver={termtip.bind(null, effectivenessArmourTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(weapon.effectiveness.armour.total)}</span></td>
</tr>);
}
}
const totalShieldsSDps = absoluteShieldsSDps + explosiveShieldsSDps + kineticShieldsSDps + thermalShieldsSDps;
const totalArmourSDps = absoluteArmourSDps + explosiveArmourSDps + kineticArmourSDps + thermalArmourSDps;
const totalSDps = sumSDps(totalSDpsObject);
const totalSDpsTooltipDetails = getSDpsTooltip(translate, formats, totalSDpsObject);
const totalSDpsData = getSDpsData(translate, totalSDpsObject);
const shieldsSDpsData = [];
shieldsSDpsData.push({ value: Math.round(absoluteShieldsSDps), label: translate('absolute') });
shieldsSDpsData.push({ value: Math.round(explosiveShieldsSDps), label: translate('explosive') });
shieldsSDpsData.push({ value: Math.round(kineticShieldsSDps), label: translate('kinetic') });
shieldsSDpsData.push({ value: Math.round(thermalShieldsSDps), label: translate('thermal') });
const totalShieldsSDps = sumSDps(shieldsSDpsObject);
const totalShieldsSDpsTooltipDetails = getSDpsTooltip(translate, formats, shieldsSDpsObject);
const shieldsSDpsData = getSDpsData(translate, shieldsSDpsObject);
const armourSDpsData = [];
armourSDpsData.push({ value: Math.round(absoluteArmourSDps), label: translate('absolute') });
armourSDpsData.push({ value: Math.round(explosiveArmourSDps), label: translate('explosive') });
armourSDpsData.push({ value: Math.round(kineticArmourSDps), label: translate('kinetic') });
armourSDpsData.push({ value: Math.round(thermalArmourSDps), label: translate('thermal') });
const totalArmourSDps = sumSDps(armourSDpsObject);
const totalArmourSDpsTooltipDetails = getSDpsTooltip(translate, formats, armourSDpsObject);
const armourSDpsData = getSDpsData(translate, armourSDpsObject);
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));
@@ -231,19 +271,31 @@ export default class Offence extends TranslatedComponent {
<thead>
<tr className='main'>
<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 armour')}</th>
</tr>
<tr>
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')} onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'esdpss')}>{'sdps'}</th>
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')} onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'esdpss')}>{'sdps'}</th>
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_SHIELDS')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'es')}>{'eft'}</th>
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'esdpsh')}>{'sdps'}</th>
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'eh')}>{'eft'}</th>
</tr>
</thead>
<tbody>
{rows}
</tbody>
<tbody>
{rows}
{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>
</div>
<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_TIME_TO_REMOVE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_REMOVE_ARMOUR')}<br/>{timeToDepleteArmour === Infinity ? translate('never') : formats.time(timeToDepleteArmour)}</h2>
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_OVERALL_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('overall damage')}</h2>
<PieChart data={totalSDpsData} />
</div>
<div className='group quarter'>
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield damage sources')}</h2>
<PieChart data={shieldsSDpsData} />

View File

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

View File

@@ -23,11 +23,11 @@ export default class ShipPicker extends TranslatedComponent {
}
/**
* Constructor
* @param {Object} props React Component properties
* @param {Object} context React Component context
* constructor
* @param {object} props Properties react
* @param {object} context react context
*/
constructor(props, context) {
constructor(props, context) { // eslint-disable-line
super(props);
this.shipOrder = Object.keys(Ships).sort();
@@ -39,8 +39,8 @@ export default class ShipPicker extends TranslatedComponent {
/**
* Update ship
* @param {object} ship the ship
* @param {string} build the build, if present
* @param {object} ship the ship
* @param {string} build the build, if present
*/
_shipChange(ship, build) {
this._closeMenu();

View File

@@ -15,87 +15,198 @@ export default class ShipSummaryTable extends TranslatedComponent {
cargo: PropTypes.number.isRequired,
fuel: PropTypes.number.isRequired,
marker: PropTypes.string.isRequired,
pips: PropTypes.object.isRequired
};
/**
* The ShipSummaryTable constructor
* @param {Object} props The props
*/
constructor(props) {
super(props);
this.didContextChange = this.didContextChange.bind(this);
this.state = {
shieldColour: 'blue'
};
}
/**
* Render the table
* @return {React.Component} Summary table
*/
render() {
const { ship, cargo, fuel } = this.props;
const { ship, cargo, fuel, pips } = this.props;
let { language, tooltip, termtip } = this.context;
let translate = language.translate;
let u = language.units;
let formats = language.formats;
let { time, int, round, f1, f2 } = formats;
let hide = tooltip.bind(null, null);
const shieldGenerator = ship.findInternalByGroup('sg');
const shieldGenerator = ship.findInternalByGroup('sg') || ship.findInternalByGroup('psg');
const sgClassNames = cn({ warning: shieldGenerator && !ship.shield, muted: !shieldGenerator });
const sgTooltip = shieldGenerator ? 'TT_SUMMARY_SHIELDS' : 'TT_SUMMARY_SHIELDS_NONFUNCTIONAL';
const timeToDrain = Calc.timeToDrainWep(ship, 4);
const canThrust = ship.canThrust(cargo, fuel);
const canThrust = ship.canThrust(cargo, ship.fuelCapacity);
const speedTooltip = canThrust ? 'TT_SUMMARY_SPEED' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
const canBoost = ship.canBoost(cargo, fuel);
const canBoost = ship.canBoost(cargo, ship.fuelCapacity);
const boostTooltip = canBoost ? 'TT_SUMMARY_BOOST' : canThrust ? 'TT_SUMMARY_BOOST_NONFUNCTIONAL' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
const sgMetrics = Calc.shieldMetrics(ship, pips.sys);
const shipBoost = canBoost ? Calc.calcBoost(ship) : 'No Boost';
const armourMetrics = Calc.armourMetrics(ship);
let shieldColour = 'blue';
if (shieldGenerator && shieldGenerator.m.grp === 'psg') {
shieldColour = 'green';
} else if (shieldGenerator && shieldGenerator.m.grp === 'bsg') {
shieldColour = 'purple';
}
this.state = {
shieldColour
};
return <div id='summary'>
<table id='summaryTable'>
<thead>
<tr className='main'>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canThrust }) }>{translate('speed')}</th>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canBoost }) }>{translate('boost')}</th>
<th colSpan={5}>{translate('jump range')}</th>
<th rowSpan={2}>{translate('shield')}</th>
<th rowSpan={2}>{translate('integrity')}</th>
<th rowSpan={2}>{translate('DPS')}</th>
<th rowSpan={2}>{translate('EPS')}</th>
<th rowSpan={2}>{translate('TTD')}</th>
{/* <th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th> */}
<th rowSpan={2}>{translate('cargo')}</th>
<th rowSpan={2}>{translate('fuel')}</th>
<th colSpan={3}>{translate('mass')}</th>
<th onMouseEnter={termtip.bind(null, 'hull hardness', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('hrd')}</th>
<th rowSpan={2}>{translate('crew')}</th>
<th onMouseEnter={termtip.bind(null, 'mass lock factor', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th>
</tr>
<tr>
<th className='lft'>{translate('max')}</th>
<th>{translate('unladen')}</th>
<th>{translate('laden')}</th>
<th>{translate('total unladen')}</th>
<th>{translate('total laden')}</th>
<th className='lft'>{translate('hull')}</th>
<th>{translate('unladen')}</th>
<th>{translate('laden')}</th>
</tr>
</thead>
<tbody>
<tr>
<td onMouseEnter={termtip.bind(null, speedTooltip, { cap: 0 })} onMouseLeave={hide}>{ canThrust ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, false))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td onMouseEnter={termtip.bind(null, boostTooltip, { cap: 0 })} onMouseLeave={hide}>{ canBoost ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, true))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_MAX_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.standard[2].m.getMaxFuelPerJump(), ship.standard[2].m, ship.standard[2].m.getMaxFuelPerJump()))}{u.LY}</span></td>
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</span></td>
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</span></td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity))}{u.LY}</td>
<td className={sgClassNames} onMouseEnter={termtip.bind(null, sgTooltip, { cap: 0 })} onMouseLeave={hide}>{int(ship.shield)}{u.MJ}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_INTEGRITY', { cap: 0 })} onMouseLeave={hide}>{int(ship.armour)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_DPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalDps)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_EPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalEps)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_TTD', { cap: 0 })} onMouseLeave={hide}>{timeToDrain === Infinity ? '∞' : time(timeToDrain)}</td>
{/* <td>{f1(ship.totalHps)}</td> */}
<td>{round(ship.cargoCapacity)}{u.T}</td>
<td>{round(ship.fuelCapacity)}{u.T}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_HULL_MASS', { cap: 0 })} onMouseLeave={hide}>{ship.hullMass}{u.T}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.unladenMass)}{u.T}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.ladenMass)}{u.T}</td>
<td>{int(ship.hardness)}</td>
<td>{ship.crew}</td>
<td>{ship.masslock}</td>
</tr>
</tbody>
</table>
<div style={{display: "table", width: "100%"}}>
<div style={{display: "table-row"}}>
<table className={'summaryTable'}>
<thead>
<tr className='main'>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canThrust }) }>{translate('speed')}</th>
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canBoost }) }>{translate('boost')}</th>
<th colSpan={5}>{translate('jump range')}</th>
<th rowSpan={2}>{translate('shield')}</th>
<th rowSpan={2}>{translate('integrity')}</th>
<th rowSpan={2}>{translate('DPS')}</th>
<th rowSpan={2}>{translate('EPS')}</th>
<th rowSpan={2}>{translate('TTD')}</th>
{/* <th onMouseEnter={termtip.bind(null, 'heat per second')} onMouseLeave={hide} rowSpan={2}>{translate('HPS')}</th> */}
<th rowSpan={2}>{translate('cargo')}</th>
<th rowSpan={2}>{translate('pax')}</th>
<th rowSpan={2}>{translate('fuel')}</th>
<th colSpan={3}>{translate('mass')}</th>
<th onMouseEnter={termtip.bind(null, 'hull hardness', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('hrd')}</th>
<th rowSpan={2}>{translate('crew')}</th>
<th onMouseEnter={termtip.bind(null, 'mass lock factor', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('MLF')}</th>
<th onMouseEnter={termtip.bind(null, 'TT_SUMMARY_BOOST_TIME', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('boost time')}</th>
</tr>
<tr>
<th className='lft'>{translate('max')}</th>
<th>{translate('unladen')}</th>
<th>{translate('laden')}</th>
<th>{translate('total unladen')}</th>
<th>{translate('total laden')}</th>
<th className='lft'>{translate('hull')}</th>
<th>{translate('unladen')}</th>
<th>{translate('laden')}</th>
</tr>
</thead>
<tbody>
<tr>
<td onMouseEnter={termtip.bind(null, speedTooltip, { cap: 0 })} onMouseLeave={hide}>{ canThrust ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, false))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td onMouseEnter={termtip.bind(null, boostTooltip, { cap: 0 })} onMouseLeave={hide}>{ canBoost ? <span>{int(ship.calcSpeed(4, ship.fuelCapacity, 0, true))}{u['m/s']}</span> : <span className='warning'>0 <Warning/></span> }</td>
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_MAX_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.standard[2].m.getMaxFuelPerJump(), ship.standard[2].m, ship.standard[2].m.getMaxFuelPerJump(), ship))}{u.LY}</span></td>
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</span></td>
<td><span onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_SINGLE_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.jumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</span></td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_TOTAL_JUMP', { cap: 0 })} onMouseLeave={hide}>{f2(Calc.totalJumpRange(ship.unladenMass + ship.fuelCapacity + ship.cargoCapacity, ship.standard[2].m, ship.fuelCapacity, ship))}{u.LY}</td>
<td className={sgClassNames} onMouseEnter={termtip.bind(null, sgTooltip, { cap: 0 })} onMouseLeave={hide}>{int(ship.shield)}{u.MJ}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_INTEGRITY', { cap: 0 })} onMouseLeave={hide}>{int(ship.armour)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_DPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalDps)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_EPS', { cap: 0 })} onMouseLeave={hide}>{f1(ship.totalEps)}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_TTD', { cap: 0 })} onMouseLeave={hide}>{timeToDrain === Infinity ? '∞' : time(timeToDrain)}</td>
{/* <td>{f1(ship.totalHps)}</td> */}
<td>{round(ship.cargoCapacity)}{u.T}</td>
<td>{ship.passengerCapacity}</td>
<td>{round(ship.fuelCapacity)}{u.T}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_HULL_MASS', { cap: 0 })} onMouseLeave={hide}>{ship.hullMass}{u.T}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.unladenMass)}{u.T}</td>
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_MASS', { cap: 0 })} onMouseLeave={hide}>{int(ship.ladenMass)}{u.T}</td>
<td>{int(ship.hardness)}</td>
<td>{ship.crew}</td>
<td>{ship.masslock}</td>
<td>{shipBoost !== 'No Boost' ? formats.time(shipBoost) : 'No Boost'}</td>
</tr>
</tbody>
</table>
<table className={'summaryTable'}>
<thead className={this.state.shieldColour}>
<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 colSpan={5} onMouseEnter={termtip.bind(null, 'TT_SUMMARY_SHIELDS_SCB', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('HP')}`}</th>
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECOVER', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('recovery')}</th>
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'PHRASE_SG_RECHARGE', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('recharge')}</th>
</tr>
<tr>
<th>{`${translate('explosive')}`}</th>
<th>{`${translate('kinetic')}`}</th>
<th>{`${translate('thermal')}`}</th>
<th></th>
<th className={'bordered'}>{`${translate('absolute')}`}</th>
<th>{`${translate('explosive')}`}</th>
<th>{`${translate('kinetic')}`}</th>
<th>{`${translate('thermal')}`}</th>
<th></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>
<th colSpan={5} onMouseEnter={termtip.bind(null, 'PHRASE_EFFECTIVE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{`${translate('HP')}`}</th>
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'TT_MODULE_ARMOUR', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('raw module armour')}</th>
<th rowSpan={2} onMouseEnter={termtip.bind(null, 'TT_MODULE_PROTECTION_INTERNAL', { cap: 0 })} onMouseLeave={hide} className='lft'>{translate('internal protection')}</th>
</tr>
<tr>
<th>{`${translate('explosive')}`}</th>
<th>{`${translate('kinetic')}`}</th>
<th>{`${translate('thermal')}`}</th>
<th>{`${translate('caustic')}`}</th>
<th className={'bordered'}>{`${translate('absolute')}`}</th>
<th>{`${translate('explosive')}`}</th>
<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>;
}
}

View File

@@ -1,166 +1,386 @@
import React from 'react';
import PropTypes from 'prop-types';
const MARGIN_LR = 8; // Left/ Right margin
/**
* Horizontal Slider
*/
export default class Slider extends React.Component {
static defaultProps = {
axis: false,
min: 0,
max: 1,
scale: 1 // SVG render scale
};
static propTypes = {
axis: PropTypes.bool,
axisUnit: PropTypes.string,
max: PropTypes.number,
min: PropTypes.number,
onChange: PropTypes.func.isRequired,
onResize: PropTypes.func,
percent: PropTypes.number.isRequired,
scale: PropTypes.number
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this._down = this._down.bind(this);
this._move = this._move.bind(this);
this._up = this._up.bind(this);
this._updatePercent = this._updatePercent.bind(this);
this._updateDimensions = this._updateDimensions.bind(this);
this.state = { width: 0 };
}
/**
* On Mouse/Touch down handler
* @param {SyntheticEvent} event Event
*/
_down(event) {
let rect = event.currentTarget.getBoundingClientRect();
this.left = rect.left;
this.width = rect.width;
this._move(event);
}
/**
* Update the slider percentage on move
* @param {SyntheticEvent} event Event
*/
_move(event) {
if(this.width !== null && this.left != null) {
let clientX = event.touches ? event.touches[0].clientX : event.clientX;
event.preventDefault();
this._updatePercent(clientX - this.left, this.width);
}
}
/**
* On Mouse/Touch up handler
* @param {Event} event DOM Event
*/
_up(event) {
event.preventDefault();
this.left = null;
this.width = null;
}
/**
* Determine if the user is still dragging
* @param {SyntheticEvent} event Event
*/
_enter(event) {
if(event.buttons !== 1) {
this.left = null;
this.width = null;
}
}
/**
* Update the slider percentage
* @param {number} pos Slider drag position
* @param {number} width Slider width
* @param {Event} event DOM Event
*/
_updatePercent(pos, width) {
this.props.onChange(Math.min(Math.max(pos / width, 0), 1));
}
/**
* Update dimenions from rendered DOM
*/
_updateDimensions() {
this.setState({
outerWidth: this.node.getBoundingClientRect().width
});
}
/**
* Add listeners when about to mount
*/
componentWillMount() {
if (this.props.onResize) {
this.resizeListener = this.props.onResize(this._updateDimensions);
}
}
/**
* Trigger DOM updates on mount
*/
componentDidMount() {
this._updateDimensions();
}
/**
* Remove listeners on unmount
*/
componentWillUnmount() {
if (this.resizeListener) {
this.resizeListener.remove();
}
}
/**
* Render the slider
* @return {React.Component} The slider
*/
render() {
let outerWidth = this.state.outerWidth;
let { axis, axisUnit, min, max, scale } = this.props;
let style = {
width: '100%',
height: axis ? '2.5em' : '1.5em',
boxSizing: 'border-box'
};
if (!outerWidth) {
return <svg style={style} ref={node => this.node = node} />;
}
let margin = MARGIN_LR * scale;
let width = outerWidth - (margin * 2);
let pctPos = width * this.props.percent;
return <svg onMouseUp={this._up} onMouseEnter={this._enter.bind(this)} onMouseMove={this._move} onTouchEnd={this._up} style={style} ref={node => this.node = node}>
<rect className='primary' style={{ opacity: 0.3 }} x={margin} y='0.25em' rx='0.3em' ry='0.3em' width={width} height='0.7em' />
<rect className='primary-disabled' x={margin} y='0.45em' rx='0.15em' ry='0.15em' width={pctPos} height='0.3em' />
<circle className='primary' r={margin} cy='0.6em' cx={pctPos + margin} />
<rect x={margin} width={width} height='100%' fillOpacity='0' style={{ cursor: 'col-resize' }} onMouseDown={this._down} onTouchMove={this._move} onTouchStart={this._down} />
{axis && <g style={{ fontSize: '.7em' }}>
<text className='primary-disabled' y='3em' x={margin} style={{ textAnchor: 'middle' }}>{min + axisUnit}</text>
<text className='primary-disabled' y='3em' x='50%' style={{ textAnchor: 'middle' }}>{(min + max / 2) + axisUnit}</text>
<text className='primary-disabled' y='3em' x='100%' style={{ textAnchor: 'end' }}>{max + axisUnit}</text>
</g>}
</svg>;
}
}
import React from 'react';
import PropTypes from 'prop-types';
const MARGIN_LR = 8; // Left/ Right margin
/**
* Horizontal Slider
*/
export default class Slider extends React.Component {
static defaultProps = {
axis: false,
min: 0,
max: 1,
scale: 1 // SVG render scale
};
static propTypes = {
axis: PropTypes.bool,
axisUnit: PropTypes.string,// units (T, M, etc.)
max: PropTypes.number,
min: PropTypes.number,
onChange: PropTypes.func.isRequired,// function which determins percent value
onResize: PropTypes.func,
percent: PropTypes.number.isRequired,// value of slider
scale: PropTypes.number
};
/**
* Constructor
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this._down = this._down.bind(this);
this._move = this._move.bind(this);
this._up = this._up.bind(this);
this._keyup = this._keyup.bind(this);
this._keydown = this._keydown.bind(this);
this._touchstart = this._touchstart.bind(this);
this._touchend = this._touchend.bind(this);
this._updatePercent = this._updatePercent.bind(this);
this._updateDimensions = this._updateDimensions.bind(this);
this.state = { width: 0 };
}
/**
* On Mouse/Touch down handler
* @param {SyntheticEvent} event Event
*/
_down(event) {
let rect = event.currentTarget.getBoundingClientRect();
this.left = rect.left;
this.width = rect.width;
this._move(event);
this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500);
}
/**
* Update the slider percentage on move
* @param {SyntheticEvent} event Event
*/
_move(event) {
if(this.width !== null && this.left != null) {
let clientX = event.touches ? event.touches[0].clientX : event.clientX;
event.preventDefault();
this._updatePercent(clientX - this.left, this.width);
}
}
/**
* On Mouse/Touch up handler
* @param {Event} event DOM Event
*/
_up(event) {
this.sliderInputBox.sliderVal.focus();
clearTimeout(this.touchStartTimer);
event.preventDefault();
this.left = null;
this.width = null;
}
/**
* Key up handler for keyboard.
* display the number field then set focus to it
* when "Enter" key is pressed
* @param {Event} event Keyboard event
*/
_keyup(event) {
switch (event.key) {
case 'Enter':
event.preventDefault();
this.sliderInputBox._setDisplay('block');
return;
default:
return;
}
}
/**
* Key down handler
* increment slider position by +/- 1 when right/left arrow key is pressed or held
* @param {Event} event Keyboard even
*/
_keydown(event) {
let newVal = this.props.percent * this.props.max;
switch (event.key) {
case 'ArrowRight':
newVal += 1;
if (newVal <= this.props.max) this.props.onChange(newVal / this.props.max);
return;
case 'ArrowLeft':
newVal -= 1;
if (newVal >= 0) this.props.onChange(newVal / this.props.max);
return;
default:
return;
}
}
/**
* Touch start handler
* @param {Event} event DOM Event
*
*/
_touchstart(event) {
this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500);
}
/**
* Touch end handler
* @param {Event} event DOM Event
*
*/
_touchend(event) {
this.sliderInputBox.sliderVal.focus();
clearTimeout(this.touchStartTimer);
}
/**
* Determine if the user is still dragging
* @param {SyntheticEvent} event Event
*/
_enter(event) {
if(event.buttons !== 1) {
this.left = null;
this.width = null;
}
}
/**
* Update the slider percentage
* @param {number} pos Slider drag position
* @param {number} width Slider width
* @param {Event} event DOM Event
*/
_updatePercent(pos, width) {
this.props.onChange(Math.min(Math.max(pos / width, 0), 1));
}
/**
* Update dimenions from rendered DOM
*/
_updateDimensions() {
this.setState({
outerWidth: this.node.getBoundingClientRect().width
});
}
/**
* Add listeners when about to mount
*/
componentWillMount() {
if (this.props.onResize) {
this.resizeListener = this.props.onResize(this._updateDimensions);
}
}
/**
* Trigger DOM updates on mount
*/
componentDidMount() {
this._updateDimensions();
}
/**
* Remove listeners on unmount
*/
componentWillUnmount() {
if (this.resizeListener) {
this.resizeListener.remove();
}
}
/**
* Render the slider
* @return {React.Component} The slider
*/
render() {
let outerWidth = this.state.outerWidth;
let { axis, axisUnit, min, max, scale } = this.props;
let style = {
width: '100%',
height: axis ? '2.5em' : '1.5em',
boxSizing: 'border-box'
};
if (!outerWidth) {
return <svg style={style} ref={node => this.node = node} />;
}
let margin = MARGIN_LR * scale;
let width = outerWidth - (margin * 2);
let pctPos = width * this.props.percent;
return <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">
<rect className='primary' style={{ opacity: 0.3 }} x={margin} y='0.25em' rx='0.3em' ry='0.3em' width={width} height='0.7em' />
<rect className='primary-disabled' x={margin} y='0.45em' rx='0.15em' ry='0.15em' width={pctPos} height='0.3em' />
<circle className='primary' r={margin} cy='0.6em' cx={pctPos + margin} />
<rect x={margin} width={width} height='100%' fillOpacity='0' style={{ cursor: 'col-resize' }} onMouseDown={this._down} onTouchMove={this._move} onTouchStart={this._down} onTouchEnd={this._touchend} />
{axis && <g style={{ fontSize: '.7em' }}>
<text className='primary-disabled' y='3em' x={margin} style={{ textAnchor: 'middle' }}>{min + axisUnit}</text>
<text className='primary-disabled' y='3em' x='50%' style={{ textAnchor: 'middle' }}>{(min + max / 2) + axisUnit}</text>
<text className='primary-disabled' y='3em' x='100%' style={{ textAnchor: 'end' }}>{max + axisUnit}</text>
</g>}
</svg>
<TextInputBox ref={(tb) => this.sliderInputBox = tb}
onChange={this.props.onChange}
percent={this.props.percent}
axisUnit={this.props.axisUnit}
scale={this.props.scale}
max={this.props.max}
/>
</div>;
}
}
/**
* New component to add keyboard support for sliders - works on all devices (desktop, iOS, Android)
**/
class TextInputBox extends React.Component {
static propTypes = {
axisUnit: PropTypes.string,// units (T, M, etc.)
max: PropTypes.number,
onChange: PropTypes.func.isRequired,// function which determins percent value
percent: PropTypes.number.isRequired,// value of slider
scale: PropTypes.number
};
/**
* Determine if the user is still dragging
* @param {Object} props React Component properties
*/
constructor(props) {
super(props);
this._handleFocus = this._handleFocus.bind(this);
this._handleBlur = this._handleBlur.bind(this);
this._handleChange = this._handleChange.bind(this);
this._keyup = this._keyup.bind(this);
this.state = this._getInitialState();
}
/**
* Update input value if slider changes will change props/state
* @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 });
}
}
/**
* Update slider textbox visibility/values if changes are made to slider
* @param {Object} prevProps React Component properites
* @param {Object} prevState React Component state values
*/
componentDidUpdate(prevProps, prevState) {
if (prevState.divStyle.display == 'none' && this.state.divStyle.display == 'block') {
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);
}
}
/**
* 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({
inputValue: this.props.percent * this.props.max
});
}
}
/**
* Get the value in the text box
* @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 });
}
}
/**
* Key up handler for input field.
* If user hits Enter key, blur/close the input field
* @param {Event} event Keyboard event
*/
_keyup(event) {
switch (event.key) {
case 'Enter':
this.sliderVal.blur();
return;
default:
return;
}
}
/**
* Get the value in the text box
* @return {React.Component} Text Input component for Slider
*/
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

@@ -20,6 +20,7 @@ export default class Slot extends TranslatedComponent {
maxClass: PropTypes.number.isRequired,
selected: PropTypes.bool,
m: PropTypes.object,
enabled: PropTypes.bool.isRequired,
ship: PropTypes.object.isRequired,
eligible: PropTypes.object,
warning: PropTypes.func,
@@ -39,6 +40,8 @@ export default class Slot extends TranslatedComponent {
this._contextMenu = wrapCtxMenu(this._contextMenu.bind(this));
this._getMaxClassLabel = this._getMaxClassLabel.bind(this);
this._keyDown = this._keyDown.bind(this);
this.slotDiv = null;
}
// Must be implemented by subclasses:
@@ -72,6 +75,22 @@ export default class Slot extends TranslatedComponent {
this.props.onSelect(null,null);
}
/** Key Down handler
* @param {SyntheticEvent} event Event
* ToDo: see if this can be moved up
* we do more or less the same thing
* in every section when Enter key is pressed
* on a focusable item
*
*/
_keyDown(event) {
if (event.key == 'Enter') {
if(event.target.className == 'r') {
this._toggleModifications();
}
this.props.onOpen(event);
}
}
/**
* Render the slot
* @return {React.Component} The slot
@@ -79,7 +98,7 @@ export default class Slot extends TranslatedComponent {
render() {
let language = this.context.language;
let translate = language.translate;
let { ship, m, dropClass, dragOver, onOpen, onChange, selected, eligible, onSelect, warning, availableModules } = this.props;
let { ship, m, enabled, dropClass, dragOver, onOpen, onChange, selected, eligible, onSelect, warning, availableModules } = this.props;
let slotDetails, modificationsMarker, menu;
if (!selected) {
@@ -88,7 +107,7 @@ export default class Slot extends TranslatedComponent {
}
if (m) {
slotDetails = this._getSlotDetails(m, translate, language.formats, language.units); // Must be implemented by sub classes
slotDetails = this._getSlotDetails(m, enabled, translate, language.formats, language.units); // Must be implemented by sub classes
modificationsMarker = JSON.stringify(m);
} else {
slotDetails = <div className={'empty'}>{translate(eligible ? 'emptyrestricted' : 'empty')}</div>;
@@ -103,6 +122,7 @@ export default class Slot extends TranslatedComponent {
ship={ship}
m={m}
marker={modificationsMarker}
modButton = {this.modButton}
/>;
} else {
menu = <AvailableModulesMenu
@@ -113,6 +133,7 @@ export default class Slot extends TranslatedComponent {
onSelect={onSelect}
warning={warning}
diffDetails={diffDetails.bind(ship, this.context.language)}
slotDiv = {this.slotDiv}
/>;
}
}
@@ -120,7 +141,7 @@ export default class Slot extends TranslatedComponent {
// TODO: implement touch dragging
return (
<div className={cn('slot', dropClass, { selected })} onClick={onOpen} onContextMenu={this._contextMenu} onDragOver={dragOver}>
<div className={cn('slot', dropClass, { selected })} onClick={onOpen} onKeyDown={this._keyDown} onContextMenu={this._contextMenu} onDragOver={dragOver} tabIndex="0" ref={slotDiv => this.slotDiv = slotDiv}>
<div className='details-container'>
<div className='sz'>{this._getMaxClassLabel(translate)}</div>
{slotDetails}
@@ -130,6 +151,7 @@ export default class Slot extends TranslatedComponent {
);
}
/**
* Toggle the modifications flag when selecting the modifications icon
*/

View File

@@ -15,8 +15,11 @@ export default class SlotSection extends TranslatedComponent {
static propTypes = {
ship: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
onCargoChange: PropTypes.func.isRequired,
onFuelChange: PropTypes.func.isRequired,
code: PropTypes.string.isRequired,
togglePwr: PropTypes.func
togglePwr: PropTypes.func,
sectionMenuRefs: PropTypes.object
};
/**
@@ -30,7 +33,10 @@ export default class SlotSection extends TranslatedComponent {
super(props);
this.sectionId = sectionId;
this.sectionName = sectionName;
this.ssHeadRef = null;
this.sectionRefArr = this.props.sectionMenuRefs[this.sectionId] = [];
this.sectionRefArr['selectedRef'] = null;
this._getSlots = this._getSlots.bind(this);
this._selectModule = this._selectModule.bind(this);
this._getSectionMenu = this._getSectionMenu.bind(this);
@@ -38,6 +44,8 @@ export default class SlotSection extends TranslatedComponent {
this._drop = this._drop.bind(this);
this._dragOverNone = this._dragOverNone.bind(this);
this._close = this._close.bind(this);
this._keyDown = this._keyDown.bind(this);
this._handleSectionFocus = this._handleSectionFocus.bind(this);
this.state = {};
}
@@ -45,7 +53,59 @@ export default class SlotSection extends TranslatedComponent {
// _getSlots()
// _getSectionMenu()
// _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
* @param {string} menu Menu name
@@ -129,24 +189,58 @@ export default class SlotSection extends TranslatedComponent {
let { originSlot, targetSlot, copy } = this.state;
let m = originSlot.m;
if (copy) {
// We want to copy the module in to the target slot
if (targetSlot && canMount(this.props.ship, targetSlot, m.grp, m.class)) {
const mCopy = m.clone();
this.props.ship.use(targetSlot, mCopy, false);
this.props.onChange();
}
} else {
// We want to move the module in to the target slot, and swap back any module that was originally in the target slot
if (targetSlot && m && canMount(this.props.ship, targetSlot, m.grp, m.class)) {
// Swap modules if possible
if (targetSlot.m && canMount(this.props.ship, originSlot, targetSlot.m.grp, targetSlot.m.class)) {
this.props.ship.use(originSlot, targetSlot.m, true);
} else { // Otherwise empty the origin slot
this.props.ship.use(originSlot, null, true); // Empty but prevent summary update
if (targetSlot && originSlot != targetSlot) {
if (copy) {
// We want to copy the module in to the target slot
if (targetSlot && canMount(this.props.ship, targetSlot, m.grp, m.class)) {
const mCopy = m.clone();
this.props.ship.use(targetSlot, mCopy, false);
// Copy power info
targetSlot.enabled = originSlot.enabled;
targetSlot.priority = originSlot.priority;
this.props.onChange();
}
} else {
// Store power info
const originEnabled = targetSlot.enabled;
const originPriority = targetSlot.priority;
const targetEnabled = originSlot.enabled;
const targetPriority = originSlot.priority;
// We want to move the module in to the target slot, and swap back any module that was originally in the target slot
if (targetSlot && m && canMount(this.props.ship, targetSlot, m.grp, m.class)) {
// Swap modules if possible
if (targetSlot.m && canMount(this.props.ship, originSlot, targetSlot.m.grp, targetSlot.m.class)) {
this.props.ship.use(originSlot, targetSlot.m, true);
this.props.ship.use(targetSlot, m);
// Swap power
originSlot.enabled = originEnabled;
originSlot.priority = originPriority;
targetSlot.enabled = targetEnabled;
targetSlot.priority = targetPriority;
} else { // Otherwise empty the origin slot
// Store power
const targetEnabled = originSlot.enabled;
this.props.ship.use(originSlot, null, true); // Empty but prevent summary update
this.props.ship.use(targetSlot, m);
originSlot.enabled = 0;
originSlot.priority = 0;
targetSlot.enabled = targetEnabled;
targetSlot.priority = targetPriority;
}
this.props.onChange();
this.props.ship
.updatePowerGenerated()
.updatePowerUsed()
.recalculateMass()
.updateJumpStats()
.recalculateShield()
.recalculateShieldCells()
.recalculateArmour()
.recalculateDps()
.recalculateEps()
.recalculateHps()
.updateMovement();
}
this.props.ship.use(targetSlot, m); // update target slot
this.props.onChange();
}
}
this.setState({ originSlot: null, targetSlot: null, copy: null });
@@ -201,7 +295,7 @@ export default class SlotSection extends TranslatedComponent {
return (
<div id={this.sectionId} className={'group'} onDragLeave={this._dragOverNone}>
<div className={cn('section-menu', { selected: sectionMenuOpened })} onClick={open} onContextMenu={ctx}>
<h1>{translate(this.sectionName)} <Equalizer/></h1>
<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 }
</div>
{this._getSlots()}

View File

@@ -35,6 +35,21 @@ export default class StandardSlot extends TranslatedComponent {
constructor(props) {
super(props);
this._modificationsSelected = false;
this._keyDown = this._keyDown.bind(this);
this.modButton = null;
this.slotDiv = null;
}
/**
* Handle Enter key
* @param {SyntheticEvent} event KeyDown event
*/
_keyDown(event) {
if (event.key == 'Enter') {
if(event.target.className == 'r') {
this._toggleModifications();
}
this.props.onOpen(event);
}
}
/**
@@ -48,7 +63,13 @@ export default class StandardSlot extends TranslatedComponent {
let m = slot.m;
let classRating = m.class + m.rating;
let menu;
let validMods = m == null ? [] : (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 mass = m.getMass() || m.cargo || m.fuel || 0;
@@ -56,6 +77,9 @@ export default class StandardSlot extends TranslatedComponent {
let modTT = translate('modified');
if (m && m.blueprint && m.blueprint.name) {
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
if (m.blueprint.special && m.blueprint.special.id >= 0) {
modTT += ', ' + translate(m.blueprint.special.name);
}
modTT = (
<div>
<div>{modTT}</div>
@@ -79,6 +103,7 @@ export default class StandardSlot extends TranslatedComponent {
ship={ship}
m={m}
marker={modificationsMarker}
modButton = {this.modButton}
/>;
} else {
menu = <AvailableModulesMenu
@@ -89,14 +114,15 @@ export default class StandardSlot extends TranslatedComponent {
onSelect={onSelect}
warning={warning}
diffDetails={diffDetails.bind(ship, this.context.language)}
slotDiv = {this.slotDiv}
/>;
}
}
return (
<div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen} onContextMenu={stopCtxPropagation}>
<div className={cn('details-container', { warning: warning && warning(slot.m) })}>
<div className={'sz'}>{slot.maxClass}</div>
<div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen} onKeyDown={this._keyDown} onContextMenu={stopCtxPropagation} tabIndex="0" ref={ slotDiv => this.slotDiv = slotDiv }>
<div className={cn('details-container', { warning: warning && warning(slot.m), disabled: m.grp !== 'bh' && !slot.enabled })}>
<div className={'sz'}>{m.grp == 'bh' ? m.name.charAt(0) : slot.maxClass}</div>
<div>
<div className={'l'}>{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span className='r' onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : null }</div>
<div className={'r'}>{formats.round(mass)}{units.T}</div>
@@ -118,7 +144,7 @@ export default class StandardSlot extends TranslatedComponent {
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
{ validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
{ validMods.length > 0 ? <div className='r' tabIndex="0" ref={ modButton => this.modButton = modButton }><button tabIndex="-1" onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
</div>
</div>
</div>

View File

@@ -20,14 +20,27 @@ export default class StandardSlotSection extends SlotSection {
super(props, context, 'standard', 'core internal');
this._optimizeStandard = this._optimizeStandard.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
*/
_optimizeStandard() {
this.selectedRefId = 'maxjump';
this.props.ship.useLightestStandard();
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
@@ -37,8 +50,12 @@ export default class StandardSlotSection extends SlotSection {
* @param {integer} bulkheadIndex Bulkhead to use see Constants.BulkheadNames
*/
_multiPurpose(shielded, bulkheadIndex) {
this.selectedRefId = 'multipurpose';
if (bulkheadIndex === 2) this.selectedRefId = 'combat';
ShipRoles.multiPurpose(this.props.ship, shielded, bulkheadIndex);
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
@@ -47,8 +64,11 @@ export default class StandardSlotSection extends SlotSection {
* @param {Boolean} shielded True if shield generator should be included
*/
_optimizeCargo(shielded) {
this.selectedRefId = 'trader';
ShipRoles.trader(this.props.ship, shielded);
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
@@ -57,8 +77,11 @@ export default class StandardSlotSection extends SlotSection {
* @param {Boolean} shielded True if shield generator should be included
*/
_optimizeMiner(shielded) {
this.selectedRefId = 'miner';
ShipRoles.miner(this.props.ship, shielded);
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
@@ -67,8 +90,12 @@ export default class StandardSlotSection extends SlotSection {
* @param {Boolean} planetary True if Planetary Vehicle Hangar (PVH) should be included
*/
_optimizeExplorer(planetary) {
this.selectedRefId = 'explorer';
if (planetary) this.selectedRefId = 'planetary';
ShipRoles.explorer(this.props.ship, planetary);
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
@@ -76,8 +103,11 @@ export default class StandardSlotSection extends SlotSection {
* Racer role
*/
_optimizeRacer() {
this.selectedRefId = 'racer';
ShipRoles.racer(this.props.ship);
this.props.onChange();
this.props.onCargoChange(this.props.ship.cargoCapacity);
this.props.onFuelChange(this.props.ship.fuelCapacity);
this._close();
}
@@ -217,19 +247,17 @@ export default class StandardSlotSection extends SlotSection {
let planetaryDisabled = this.props.ship.internal.length < 4;
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul>
<li className='lc' onClick={this._optimizeStandard}>{translate('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>
<div className='select-group cap'>{translate('roles')}</div>
<ul>
<li className='lc' onClick={this._multiPurpose.bind(this, false, 0)}>{translate('Multi-purpose')}</li>
<li className='lc' onClick={this._multiPurpose.bind(this, true, 2)}>{translate('Combat')}</li>
<li className='lc' onClick={this._optimizeCargo.bind(this, false)}>{translate('Trader')}</li>
<li className='lc' onClick={this._optimizeCargo.bind(this, true)}>{translate('Shielded Trader')}</li>
<li className='lc' onClick={this._optimizeExplorer.bind(this, false)}>{translate('Explorer')}</li>
<li className={cn('lc', { disabled: planetaryDisabled })} onClick={!planetaryDisabled && this._optimizeExplorer.bind(this, true)}>{translate('Planetary Explorer')}</li>
<li className='lc' onClick={this._optimizeMiner.bind(this, false)}>{translate('Miner')}</li>
<li className='lc' onClick={this._optimizeMiner.bind(this, true)}>{translate('Shielded Miner')}</li>
<li className='lc' onClick={this._optimizeRacer.bind(this)}>{translate('Racer')}</li>
<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' tabIndex="0" onClick={this._multiPurpose.bind(this, true, 2)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['combat'] = smRef}>{translate('Combat')}</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' 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 })} 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' tabIndex="0" onClick={this._optimizeMiner.bind(this, true)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['miner'] = smRef}>{translate('Miner')}</li>
<li className='lc' tabIndex="0" onClick={this._optimizeRacer.bind(this)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['racer'] = smRef}>{translate('Racer')}</li>
</ul>
</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)
*/

View File

@@ -17,12 +17,23 @@ export default class UtilitySlotSection extends SlotSection {
constructor(props, context) {
super(props, context, 'utility', 'utility mounts');
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() {
this.selectedRefId = this.firstRefId;
this.props.ship.emptyUtility();
this.props.onChange();
this._close();
@@ -36,6 +47,9 @@ export default class UtilitySlotSection extends SlotSection {
* @param {Synthetic} event 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.onChange();
this._close();
@@ -74,9 +88,9 @@ export default class UtilitySlotSection extends SlotSection {
dragOver={this._dragOverSlot.bind(this, h)}
drop={this._drop}
dropClass={this._dropClass(h, originSlot, targetSlot)}
enabled={h.enabled}
ship={ship}
m={h.m}
enabled={h.enabled ? true : false}
/>);
}
}
@@ -94,28 +108,28 @@ export default class UtilitySlotSection extends SlotSection {
return <div className='select' onClick={(e) => e.stopPropagation()} onContextMenu={stopCtxPropagation}>
<ul>
<li className='lc' onClick={this._empty}>{translate('empty all')}</li>
<li className='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>
</ul>
<div className='select-group cap'>{translate('sb')}</div>
<ul>
<li className='c' onClick={_use.bind(this, 'sb', 'A', null)}>A</li>
<li className='c' onClick={_use.bind(this, 'sb', 'B', null)}>B</li>
<li className='c' onClick={_use.bind(this, 'sb', 'C', null)}>C</li>
<li className='c' onClick={_use.bind(this, 'sb', 'D', null)}>D</li>
<li className='c' onClick={_use.bind(this, 'sb', 'E', null)}>E</li>
<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' tabIndex='0' onClick={_use.bind(this, 'sb', 'B', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-B'] = smRef}>B</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' tabIndex='0' onClick={_use.bind(this, 'sb', 'D', null)} onKeyDown={this._keyDown} ref={smRef => this.sectionRefArr['sb-D'] = smRef}>D</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>
<div className='select-group cap'>{translate('hs')}</div>
<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>
<div className='select-group cap'>{translate('ch')}</div>
<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>
<div className='select-group cap'>{translate('po')}</div>
<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>
</div>;
}

View File

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

View File

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

View File

@@ -6,6 +6,7 @@ import * as FR from './fr';
import * as IT from './it';
import * as RU from './ru';
import * as PL from './pl';
import * as PT from './pt';
import * as d3 from 'd3';
let fallbackTerms = EN.terms;
@@ -25,6 +26,7 @@ export function getLanguage(langCode) {
case 'it': lang = IT; break;
case 'ru': lang = RU; break;
case 'pl': lang = PL; break;
case 'pt': lang = PT; break;
default:
lang = EN;
}
@@ -58,17 +60,20 @@ export function getLanguage(langCode) {
},
translate,
units: {
ang: '°', // Angle
CR: <u>{translate('CR')}</u>, // Credits
kg: <u>{translate('kg')}</u>, // Kilograms
kgs: <u>{translate('kg/s')}</u>, // Kilograms per second
km: <u>{translate('km')}</u>, // Kilometers
Ls: <u>{translate('Ls')}</u>, // Light Seconds
LY: <u>{translate('LY')}</u>, // Light Years
m: <u>{translate('m')}</u>, // Meters
MJ: <u>{translate('MJ')}</u>, // Mega Joules
'm/s': <u>{translate('m/s')}</u>, // Meters per second
'°/s': <u>{translate('°/s')}</u>, // Degrees per second
MW: <u>{translate('MW')}</u>, // Mega Watts (same as Mega Joules per second)
mps: <u>{translate('m/s')}</u>, // Metres per second
pct: '%', // Percent
ps: <u>{translate('/s')}</u>, // per second
pm: <u>{translate('/min')}</u>, // per minute
s: <u>{translate('secs')}</u>, // Seconds
@@ -88,5 +93,6 @@ export const Languages = {
es: 'Español',
fr: 'Français',
ru: 'ру́сский',
pl: 'polski'
pl: 'polski',
pt: 'português'
};

View File

@@ -1,166 +1,16 @@
export const formats = {
decimal: ',',
thousands: '.',
grouping: [3],
currency: ['', ' €'],
dateTime: '%A, der %e. %B %Y, %X',
date: '%d.%m.%Y',
time: '%H:%M:%S',
periods: ['AM', 'PM'], // unused
days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
shortDays: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
shortMonths: ['Jan', 'Feb', 'Mrz', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez']
};
export const terms = {
// Phrases
PHRASE_BACKUP_DESC: 'Export aller Coriolis-Daten, um sie zu sichern oder um sie zu einem anderen Browser/Gerät zu übertragen.', // Backup of all Coriolis data to save or transfer to another browser/device
PHRASE_CONFIRMATION: 'Sind Sie sicher?', // Are You Sure?
PHRASE_EXPORT_DESC: 'Ein detaillierter JSON-Export Ihrer Konfiguration für die Verwendung in anderen Websites und Tools', // A detailed JSON export of your build for use in other sites and tools
PHRASE_FASTEST_RANGE: 'aufeinanderfolgende maximale Reichweite/Sprünge', // Consecutive max range jumps
PHRASE_IMPORT: 'JSON hier einfügen oder importieren', // Paste JSON or import here
PHRASE_LADEN: 'Schiffsmasse + Treibstoff + Fracht', // Ship Mass + Fuel + Cargo
PHRASE_NO_BUILDS: 'Keine Konfigurationen zum Vergleich ausgewählt!', // No builds added to comparison!
PHRASE_NO_RETROCH: 'Keine Umrüständerungen', // No Retrofitting changes
PHRASE_SELECT_BUILDS: 'Ausstattung zum Vergleich auswählen', // Select Builds to Compare
PHRASE_SG_RECHARGE: 'Zeit von 50% bis 100% der Ladung', // Time from 50% to 100% Charge
PHRASE_SG_RECOVER: 'Erneuerung (zu 50%) nach Zusammenbruch', // Recovery (to 50%) after collapse
PHRASE_UNLADEN: 'Schiffsmasse ohne Treibstoff und Fracht', // Ship Mass excluding Fuel and Cargo
PHRASE_UPDATE_RDY: 'Update verfügbar! Klicken zum Aktualisieren', // Update Available! Click to Refresh
// Units / Metrics
LY: 'Lj', // Light Years
T: 't', // Tons (Metric Ton - 1000kg)
// Sizes
S: 'K', // Small Hardpoint (single Character)
L: 'G', // Large Hardpoint size (single character)
H: 'R', // Huge Hardpoint size (single character)
U: 'W', // Utility Hardpoint size (single character) - Kill warrant scanner, etc
small: 'klein', // Small ship size
medium: 'mittel', // Medium ship size
large: 'groß', // Large Ship Size
// Terms
about: 'über', // Link to about page / about Coriolis.io
action: 'Aktion',
added: 'hinzugefügt',
ammo: 'Munition', // Ammunition
armour: 'Panzerung',
available: 'verfügbar', // Available options
backup: 'Sicherungsdatei',
base: 'Basis', // Base speed, boost, etc - Base ship stats
bays: 'Lagerraum',
bins: 'Behälter', // Number of Mining Refinery bins
build: 'Ausstattung', // Shorthand for the build/configuration/design name
'build name': 'Ausstattungsname', // Ship build/configuration/design name
builds: 'Ausstattungen', // Ship build/configuration/design names
buy: 'kaufen',
cancel: 'abbrechen',
cargo: 'Fracht',
cells: 'Zellen', // Number of cells in a shield cell bank
close: 'schließen',
compare: 'vergleichen',
'compare all': 'alles vergleichen',
comparison: 'Vergleich',
comparisons: 'Vergleiche',
cost: 'Preis', // Cost / price of a module or price of a ship
costs: 'Kosten', // Costs / prices of a modules or prices of ships
create: 'erstellen',
'create new': 'neu erstellen',
credits: 'Credits',
damage: 'Schaden',
'damage per second': 'Schaden pro Sekunde',
delete: 'löschen',
'delete all': 'alles löschen',
dep: 'ausg', // Weapons/Hardpoints Deployed abbreviation
deployed: 'ausgefahren', // Weapons/Hardpoints Deployed
'detailed export': 'detailierter Export',
disabled: 'deaktiviert',
discount: 'Rabatt',
'edit data': 'bearbeiten',
efficiency: 'Effizienz', // Power Plant efficiency
empty: 'leer',
'empty all': 'alles entfernen',
ENG: 'ANT', // Abbreviation - Engine recharge rate for power distributor
'Enter Name': 'Namen eingeben',
Explorer: 'Forscher',
export: 'Export',
'fastest range': 'maximale Reichweite', // Fastet totaljump range - sum of succesive jumps
forum: 'Forum',
fuel: 'Treibstoff',
'fuel level': 'Tankfüllstand', // Percent of fuel (T) in the tank
'full tank': 'Tank voll',
hardpoints: 'Waffenaufhängungen',
hull: 'Rumpf', // Ships hull
import: 'importieren',
insurance: 'Versicherung',
'internal compartments': 'Innenbereich',
jump: 'Sprung', // Single jump range
'jump range': 'Sprungreichweite',
jumps: 'Sprünge',
laden: 'beladen',
language: 'Sprache',
maneuverability: 'Manövrierbarkeit',
manufacturer: 'Hersteller',
mass: 'Masse',
'mass lock factor': 'Massensperrefaktor',
'max mass': 'maximale Masse',
MLF: 'MSF', // Mass Lock Factor Abbreviation
module: 'Modul',
modules: 'Module',
'net cost': 'Nettokosten',
no: 'Nein',
'none created': 'nicht erstellt',
ok: 'OK',
'optimal mass': 'optimale Masse', // Lowest weight / best weight for jump distance, etc
optimize: 'optimieren',
pen: 'Durchdr.', // Armour peneration abbreviation
permalink: 'Permanentlink',
power: 'Energie', // Power = Energy / second. Power generated by the power plant, or power consumed (MW / Mega Watts). Used in the power plant section
pri: 'Prio', // Priority abbreviation for power management
proceed: 'fortfahren',
qty: 'Menge', // Quantity abbreviation
range: 'Reichweite',
rate: 'Rate',
recharge: 'aufladen', // Shield Recharge time from 50% -> 100%
recovery: 'Erneuerung', // Shield recovery time (after losing shields/turning on -> 50%)
'refuel time': 'Auftankzeit', // Time to refuel the tank when scooping
reload: 'aktualisieren', // Reload weapon/hardpoint
'reload costs': 'Nachladekosten',
rename: 'umbenennen',
repair: 'reparieren',
reset: 'zurücksetzen',
ret: 'eing', // Retracted abbreviation
retracted: 'eingefahren', // Weapons/Hardpoints retracted
'retrofit costs': 'Änderungskosten', // The cost difference when upgrading / downgrading a component
'retrofit from': 'nachrüsten von', // Retrofit from Build A against build B
ROF: 'Kad', // Rate of Fire abbreviation
roles: 'Rollen', // Commander/Ship build roles - e.g. Trader, Bounty-Hunter, Explorer, etc
save: 'speichern',
sell: 'verkaufen',
settings: 'Einstellungen', // Coriolis application settings
shields: 'Schilde',
ship: 'Schiff',
ships: 'Schiffe',
shortened: 'gekürzt', // Standard/Stock build of a ship when purchased new
size: 'Größe',
skip: 'überspringen', // Skip past something / ignore it
speed: 'Geschwindigkeit',
standard: 'Grundausstattung', // Standard / Common modules (FSD, power plant, life support, etc)
Stock: 'Standard', // Thermal-load abbreviation
strength: 'Stärke', // Strength in reference to Shield Strength
subtotal: 'Zwischensumme',
time: 'Dauer', // time it takes to complete something
tooltips: 'Tooltips', // Tooltips setting - show/hide
total: 'gesamt',
'total range': 'Gesamtbereich',
Trader: 'Händler', // Trader role
type: 'Typ',
'unit cost': 'Kosten pro Einheit',
unladen: 'unbeladen', // No cargo or fuel
'utility mounts': 'Werkzeug-Steckplätze',
WEP: 'WAF', // Abbreviation - Weapon recharge rate for power distributor
yes: 'ja'
};
export const formats = {
decimal: ',',
thousands: '.',
grouping: [3],
currency: ['', ' €'],
dateTime: '%A, der %e. %B %Y, %X',
date: '%d.%m.%Y',
time: '%H:%M:%S',
periods: ['AM', 'PM'], // unused
days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'],
shortDays: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
shortMonths: ['Jan', 'Feb', 'Mrz', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez']
};
export { default as terms } from './de.json';

358
src/app/i18n/de.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -13,458 +13,4 @@ export const formats = {
shortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
};
export const terms = {
PHRASE_ALT_ALL: 'Alt + Click to fill all slots',
PHRASE_BACKUP_DESC: 'Backup of all Coriolis data to save or transfer to another browser/device',
PHRASE_CONFIRMATION: 'Are you sure?',
PHRASE_EXPORT_DESC: 'A detailed JSON export of your build for use in other sites and tools',
PHRASE_FASTEST_RANGE: 'Consecutive max range jumps',
PHRASE_IMPORT: 'Paste JSON or import here',
PHRASE_LADEN: 'Ship mass + fuel + cargo',
PHRASE_NO_BUILDS: 'No builds added to comparison!',
PHRASE_NO_RETROCH: 'No Retrofitting changes',
PHRASE_SELECT_BUILDS: 'Select builds to compare',
PHRASE_SG_RECHARGE: 'Time from 50% to 100% charge, assuming full SYS capacitor to start with',
PHRASE_SG_RECOVER: 'Time from 0% to 50% charge, assuming full SYS capacitor to start with',
PHRASE_UNLADEN: 'Ship mass excluding fuel and cargo',
PHRASE_UPDATE_RDY: 'Update Available! Click to refresh',
PHRASE_ENGAGEMENT_RANGE: 'The distance between your ship and its target',
PHRASE_SELECT_BLUEPRINT: 'Click to select a blueprint',
PHRASE_BLUEPRINT_WORST: 'Worst primary values for this blueprint',
PHRASE_BLUEPRINT_RANDOM: 'Random selection between worst and best primary values for this blueprint',
PHRASE_BLUEPRINT_BEST: 'Best primary values for this blueprint',
PHRASE_BLUEPRINT_EXTREME: 'Best beneficial and worst detrimental primary values for this blueprint',
PHRASE_BLUEPRINT_RESET: 'Remove all modifications and blueprint',
PHRASE_SELECT_SPECIAL: 'Click to select an experimental effect',
PHRASE_NO_SPECIAL: 'No experimental effect',
PHRASE_SHOPPING_LIST: 'Stations that sell this build',
PHRASE_REFIT_SHOPPING_LIST: 'Stations that sell required modules',
PHRASE_TOTAL_EFFECTIVE_SHIELD: 'Total amount of damage that can be taken from each damage type, if using all shield cells',
PHRASE_TIME_TO_LOSE_SHIELDS: 'Shields will hold for',
PHRASE_TIME_TO_RECOVER_SHIELDS: 'Shields will recover in',
PHRASE_TIME_TO_RECHARGE_SHIELDS: 'Shields will recharge in',
PHRASE_SHIELD_SOURCES: 'Breakdown of the supply of shield energy',
PHRASE_EFFECTIVE_SHIELD: 'Effective shield strength against different damage types',
PHRASE_ARMOUR_SOURCES: 'Breakdown of the supply of armour',
PHRASE_EFFECTIVE_ARMOUR: 'Effective armour strength against different damage types',
PHRASE_DAMAGE_TAKEN: '% of raw damage taken for different damage types',
PHRASE_TIME_TO_LOSE_ARMOUR: 'Armour will hold for',
PHRASE_MODULE_PROTECTION_EXTERNAL: 'Protection for hardpoints',
PHRASE_MODULE_PROTECTION_INTERNAL: 'Protection for all other modules',
PHRASE_SHIELD_DAMAGE: 'Breakdown of sources for sustained DPS against shields',
PHRASE_ARMOUR_DAMAGE: 'Breakdown of sources for sustained DPS against armour',
PHRASE_TIME_TO_REMOVE_SHIELDS: 'Will remove shields in',
TT_TIME_TO_REMOVE_SHIELDS: 'With sustained fire by all weapons',
PHRASE_TIME_TO_REMOVE_ARMOUR: 'Will remove armour in',
TT_TIME_TO_REMOVE_ARMOUR: 'With sustained fire by all weapons',
PHRASE_TIME_TO_DRAIN_WEP: 'Will drain WEP in',
TT_TIME_TO_DRAIN_WEP: 'Time to drain WEP capacitor with all weapons firing',
TT_TIME_TO_LOSE_SHIELDS: 'Against sustained fire from all opponent\'s weapons',
TT_TIME_TO_LOSE_ARMOUR: 'Against sustained fire from all opponent\'s weapons',
TT_MODULE_ARMOUR: 'Armour protecting against module damage',
TT_MODULE_PROTECTION_EXTERNAL: 'Percentage of damage diverted from hardpoints to module reinforcement packages',
TT_MODULE_PROTECTION_INTERNAL: 'Percentage of damage diverted from non-hardpoint modules to module reinforcement packages',
TT_EFFECTIVE_SDPS_SHIELDS: 'Actual sustained DPS whilst WEP capacitor is not empty',
TT_EFFECTIVENESS_SHIELDS: 'Effectivness compared to hitting a 0-resistance target with 0 pips to SYS at 0m',
TT_EFFECTIVE_SDPS_ARMOUR: 'Actual sustained DPS whilst WEP capacitor is not empty',
TT_EFFECTIVENESS_ARMOUR: 'Effectivness compared to hitting a 0-resistance target at 0m',
PHRASE_EFFECTIVE_SDPS_SHIELDS: 'SDPS against shields',
PHRASE_EFFECTIVE_SDPS_ARMOUR: 'SDPS against armour',
TT_SUMMARY_SPEED: 'With full fuel tank and 4 pips to ENG',
TT_SUMMARY_SPEED_NONFUNCTIONAL: 'Thrusters powered off or over maximum mass',
TT_SUMMARY_BOOST: 'With full fuel tank and 4 pips to ENG',
TT_SUMMARY_BOOST_NONFUNCTIONAL: 'Power distributor not able to supply enough power to boost',
TT_SUMMARY_SHIELDS: 'Raw shield strength, including boosters',
TT_SUMMARY_SHIELDS_NONFUNCTIONAL: 'No shield generator or shield generator powered off',
TT_SUMMARY_INTEGRITY: 'Ship integrity, including bulkheads and hull reinforcement packages',
TT_SUMMARY_HULL_MASS: 'Mass of the hull prior to any modules being installed',
TT_SUMMARY_UNLADEN_MASS: 'Mass of the hull and modules prior to any fuel or cargo',
TT_SUMMARY_LADEN_MASS: 'Mass of the hull and modules with full fuel and cargo',
TT_SUMMARY_DPS: 'Damage per second with all weapons firing',
TT_SUMMARY_EPS: 'WEP capacitor consumed per second with all weapons firing',
TT_SUMMARY_TTD: 'Time to drain WEP capacitor with all weapons firing and 4 pips to WEP',
TT_SUMMARY_MAX_SINGLE_JUMP: 'Farthest possible jump range with no cargo and only enough fuel for the jump itself',
TT_SUMMARY_UNLADEN_SINGLE_JUMP: 'Farthest possible jump range with no cargo and a full fuel tank',
TT_SUMMARY_LADEN_SINGLE_JUMP: 'Farthest possible jump range with full cargo and a full fuel tank',
TT_SUMMARY_UNLADEN_TOTAL_JUMP: 'Farthest possible range with no cargo, a full fuel tank, and jumping as far as possible each time',
TT_SUMMARY_LADEN_TOTAL_JUMP: 'Farthest possible range with full cargo, a full fuel tank, and jumping as far as possible each time',
HELP_MODIFICATIONS_MENU: 'Click on a number to enter a new value, or drag along the bar for small changes',
// Other languages fallback to these values
// Only Translate to other languages if the name is different in-game
am: 'Auto Field-Maintenance Unit',
bh: 'Bulkheads',
bl: 'Beam Laser',
bsg: 'Bi-Weave Shield Generator',
c: 'Cannon',
cc: 'Collector Limpet Controller',
ch: 'Chaff Launcher',
cr: 'Cargo Rack',
cs: 'Manifest Scanner',
dc: 'Docking Computer',
ec: 'Electronic Countermeasure',
fc: 'Fragment Cannon',
fh: 'Fighter Hangar',
fi: 'FSD Interdictor',
fs: 'Fuel Scoop',
fsd: 'Frame Shift Drive',
ft: 'Fuel Tank',
fx: 'Fuel Transfer Limpet Controller',
hb: 'Hatch Breaker Limpet Controller',
hr: 'Hull Reinforcement Package',
hs: 'Heat Sink Launcher',
kw: 'Kill Warrant Scanner',
ls: 'Life Support',
mc: 'Multi-cannon',
ml: 'Mining Laser',
mr: 'Missile Rack',
mrp: 'Module Reinforcement Package',
nl: 'Mine Launcher',
pa: 'Plasma Accelerator',
pas: 'Planetary Approach Suite',
pc: 'Prospector Limpet Controller',
pce: 'Economy Class Passenger Cabin',
pci: 'Business Class Passenger Cabin',
pcm: 'First Class Passenger Cabin',
pcq: 'Luxury Passenger Cabin',
pd: 'power distributor',
pl: 'Pulse Laser',
po: 'Point Defence',
pp: 'Power Plant',
psg: 'Prismatic Shield Generator',
pv: 'Planetary Vehicle Hangar',
rf: 'Refinery',
rg: 'Rail Gun',
s: 'Sensors',
sb: 'Shield Booster',
sc: 'Stellar Scanners',
scb: 'Shield Cell Bank',
sg: 'Shield Generator',
ss: 'Surface Scanners',
t: 'thrusters',
tp: 'Torpedo Pylon',
ul: 'Burst Laser',
ws: 'Frame Shift Wake Scanner',
// Items on the outfitting page
// Notification of restricted slot
emptyrestricted: 'empty (restricted)',
'damage dealt to': 'Damage dealt to',
'damage received from': 'Damage received from',
'against shields': 'Against shields',
'against hull': 'Against hull',
'total effective shield': 'Total effective shield',
// 'ammo' was overloaded for outfitting page and modul info, so changed to ammunition for outfitting page
ammunition: 'Ammo',
// Unit for seconds
secs: 's',
rebuildsperbay: 'Rebuilds per bay',
// Blueprint rolls
worst: 'Worst',
average: 'Average',
random: 'Random',
best: 'Best',
extreme: 'Extreme',
reset: 'Reset',
// Weapon, offence, defence and movement
dpe: 'Damage per MJ of energy',
dps: 'Damage per second',
sdps: 'Sustained damage per second',
dpssdps: 'Damage per second (sustained damage per second)',
eps: 'Energy per second',
epsseps: 'Energy per second (sustained energy per second)',
hps: 'Heat per second',
hpsshps: 'Heat per second (sustained heat per second)',
'damage by': 'Damage by',
'damage from': 'Damage from',
'shield cells': 'Shield cells',
'recovery': 'Recovery',
'recharge': 'Recharge',
'engine pips': 'Engine Pips',
'4b': '4 pips and boost',
'speed': 'Speed',
'pitch': 'Pitch',
'roll': 'Roll',
'yaw': 'Yaw',
'internal protection': 'Internal protection',
'external protection': 'External protection',
'engagement range': 'Engagement range',
'total': 'Total',
// Modifications
ammo: 'Ammunition maximum',
boot: 'Boot time',
brokenregen: 'Broken regeneration rate',
burst: 'Burst',
burstrof: 'Burst rate of fire',
clip: 'Ammunition clip',
damage: 'Damage',
distdraw: 'Distributor draw',
duration: 'Duration',
eff: 'Efficiency',
engcap: 'Engines capacity',
engrate: 'Engines recharge rate',
explres: 'Explosive resistance',
facinglimit: 'Facing limit',
hullboost: 'Hull boost',
hullreinforcement: 'Hull reinforcement',
integrity: 'Integrity',
jitter: 'Jitter',
kinres: 'Kinetic resistance',
maxfuel: 'Maximum fuel per jump',
mass: 'Mass',
optmass: 'Optimal mass',
optmul: 'Optimal multiplier',
pgen: 'Power generation',
piercing: 'Piercing',
power: 'Power draw',
protection: 'Protection',
range: 'Range',
ranget: 'Range', // Range in time (for FSD interdictor)
regen: 'Regeneration rate',
reload: 'Reload',
rof: 'Rate of fire',
angle: 'Scan angle',
scanrate: 'Scan rate',
scantime: 'Scan time',
shield: 'Shield',
shieldboost: 'Shield boost',
shieldreinforcement: 'Shield reinforcement',
shotspeed: 'Shot speed',
spinup: 'Spin up time',
syscap: 'Systems capacity',
sysrate: 'Systems recharge rate',
thermload: 'Thermal load',
thermres: 'Thermal resistance',
wepcap: 'Weapons capacity',
weprate: 'Weapons recharge rate',
// Shield generators use a different terminology
minmass_sg: 'Minimum hull mass',
optmass_sg: 'Optimal hull mass',
maxmass_sg: 'Maximum hull mass',
minmul_sg: 'Minimum strength',
optmul_sg: 'Optimal strength',
maxmul_sg: 'Minimum strength',
minmass_psg: 'Minimum hull mass',
optmass_psg: 'Optimal hull mass',
maxmass_psg: 'Maximum hull mass',
minmul_psg: 'Minimum strength',
optmul_psg: 'Optimal strength',
maxmul_psg: 'Minimum strength',
minmass_bsg: 'Minimum hull mass',
optmass_bsg: 'Optimal hull mass',
maxmass_bsg: 'Maximum hull mass',
minmul_bsg: 'Minimum strength',
optmul_bsg: 'Optimal strength',
maxmul_bsg: 'Minimum strength',
range_s: 'Typical emission range',
// Damage types
absolute: 'Absolute',
explosive: 'Explosive',
kinetic: 'Kinetic',
thermal: 'Thermal',
// Shield sources
generator: 'Generator',
boosters: 'Boosters',
cells: 'Cells',
// Armour sources
bulkheads: 'Bulkheads',
reinforcement: 'Reinforcement',
// Help text
HELP_TEXT: `
<h1>Introduction</h1>
Coriolis is a ship builder for Elite: Dangerous. This help file provides you with the information you need to use Coriolis.
<h1>Importing Your Ship Into Coriolis</h1>
Often, you will want to start with your existing ship in Coriolis and see how particular changes might affect it, for example upgrading your FSD. There are a number of tools that can be used to import your ship without you having to create it manually. This has the added benefit of copying over any engineering modifications that have taken place as well. </p>
<h2>Importing Your Ship From EDDI</h2>
To import your ship from EDDI first ensure that your connection to the Frontier servers&apos; companion API is working. To do this check the &apos;Companion App&apos; tab where you should see "Your connection to the companion app is operational". If not then follow the instructions in the companion app tab in EDDI to connect to the Frontier servers.</p>
Once you have a working companion API connection go to the &apos;Shipyard&apos; tab. At the right-hand side of each ship is an &apos;Export to Coriolis&apos; button that will open your default web browser in Coriolis with the ship&apos;s build. </p>
Note that Internet Explorer and Edge might not import correctly, due to their internal restrictions on URL length. If you find that this is the case then please change your default browser to Chrome. </p>
Also, the imported information does not provide any data on the power priority or enabled status of your cargo hatch. Coriolis sets this item to have a power priority of "5" and to be disabled by default. You can change this after import in the Power Management section. </p>
<h2>Importing Your Ship From EDMC</h2>
To import your ship from EDMC once your connection to the Frontier servers&apos; companion API is working go to &apos;Settings -&gt;Configuration&apos; and set the &apos;Preferred Shipyard&apos; to &apos;Coriolis&apos;. Once this is set up clicking on your ship in the main window will open your default web browser in Coriolis with the ship&apos;s build.</p>
Note that Internet Explorer and Edge might not import correctly, due to their internal restrictions on URL length. If you find that this is the case then please change your default browser to Chrome. </p>
<h1>Understanding And Using The Outfitting Panels</h1>
The outfitting page is where you will spend most of your time, and contains the information for your ship. Information on each of the panels is provided below. </p>
<h2>Key Values</h2>
Along the top of the screen are some of the key values for your build. This is a handy reference for the values, but more information is provided for the values in the further panels. </p>
Here, along with most places in Coriolis, acronyms will have tooltips explaining what they mean. Hover over the acronym to obtain more detail, or look in the glossary at the end of this help.</p>
All values are the highest possible, assuming that you an optimal setup for that particular value (maximum pips in ENG for speed, minimum fuel for jump range, etc.). This means that these values will not be affected by changes to pip settings. Details of the specific setup for each value are listed in the associated tootip.</p>
<h2>Modules</h2>
The next set of panels laid out horizontally across the screen contain the modules you have put in your build. From left to right these are the core modules, the internal modules, the hardpoints and the utility mounts. These represent the available slots in your ship and cannot be altered. Each slot has a class, or size, and in general any module up to a given size can fit in a given slot (exceptions being bulkheads, life support and sensors in core modules and restricted internal slots, which can only take a subset of module depending on their restrictions). </p>
To add a module to a slot left-click on the slot and select the required module. Only the modules capable of fitting in the selected slot will be shown. </p>
To remove a module from a slot right-click on the module. </p>
To move a module from one slot to another drag it. If you instead want to copy the module drag it whilst holding down the &apos;Alt&apos; key. </p>
Clicking on the headings for each set of modules gives you the ability to either select an overall role for your ship (when clicking the core internal header) or a specific module with which you want to fill all applicable slots (when clicking the other headers). </p>
<h2>Ship Controls</h2>
The ship controls allow you to set your pips, boost, and amount of fuel and cargo that your build carries. The changes made here will effect the information supplied in the subsequent panels, giving you a clearer view of what effect different changing these items will have. </p>
Ship control settings are saved as part of a build. </p>
<h2>Opponent</h2>
The opponet selection allows you to choose your opponent. The opponent can be either a stock build of a ship or one of your own saved builds. You can also set the engagement range between you and your opponent. Your selection here will effect the information supplied in the subsequent panels, specifically the Offence and Defence panels. </p>
Opponent settings are saved as part of a build. </p>
<h2>Power and Costs Sub-panels</h2>
<h3>Power</h3>
The power management panel provides information about power usage and priorities. It allows you to enable and disable individual modules, as well as set power priorities for each module. Disabled modules will not be included in the build&apos;s statistics, with the exception of Shield Cell Banks as they are usually disabled when not in use and only enabled when required. </p>
<h3>Costs</h3>
The costs panel provides information about the costs for each of your modules, and the total cost and insurance for your build. By default Coriolis uses the standard costs, however discounts for your ship, modules and insurance can be altered in the &apos;Settings&apos; at the top-right of the page.</p>
The retrofit costs provides information about the costs of changing the base build for your ship, or your saved build, to the current build.</p>
The reload costs provides information about the costs of reloading your current build.</p>
<h2>Profiles</h2>
Profiles provide graphs that show the general performance of modules in your build
<h3>Engine Profile</h3>
The engine profile panel provides information about the capabilities of your current thrusters. The graph shows you how the maximum speed alters with the overall mass of your build. The vertical dashed line on the graph shows your current mass. Your engine profile can be altered by obtaining different thrusters or engineering your existing thrusters, and you can increase your maximum speed by adding pips to the ENG capacitor as well as reducing the amount of fuel and cargo you are carrying as well as reducing the overall weight of the build. You can also temporarily increase your speed by hitting the boost button. </p>
<h3>FSD Profile</h3>
The FSD profile panel provides information about the capabilities of your current frame shift drive. The graph shows you how the maximum jump range alters with the overall mass of your build. The vertical dashed line on the graph shows your current maximum single jump range. Your FSD profile can be altered by obtaining a different FSD or engineering your existing FSD, and you can increase your maximum jump range by reducing the amount of fuel and cargo you are carrying as well as reducing the overall weight of the build, </p>
<h3>Movement Profile</h3>
The movement profile panel provides information about the capabilities of your current thrusters with your current overall mass and ENG pips settings. The diagram shows your ability to move and rotate in the different axes:
<dl>
<dt>Speed</dt><dd>The fastest the ship can move, in metres per second</dd>
<dt>Pitch</dt><dd>The fastest the ship can raise or lower its nose, in degrees per second</dd>
<dt>Roll</dt><dd>The fastest the ship can roll its body, in degrees per second</dd>
<dt>Yaw</dt><dd>The fastest the ship can turn its nose left or right, in degrees per second</dd>
</dl>
Your movement profile can be altered by obtaining different thrusters or engineering your existing thrusters, and you can increase your movement values by adding pips to the ENG capacitor as well as reducing the amount of fuel and cargo you are carrying as well as reducing the overall weight of the build. You can also temporarily increase your movement profile by hitting the boost button. </p>
<h3>Damage Profile</h3>
The damage profile provides two graphs showing how the the build&apos;s damage to the opponent&apos;s shields and hull change with engagement range. The vertical dashed line on the graph shows your current engagement range. This combines information about the build&apos;s weapons with the opponent&apos;s shields and hull to provide an accurate picture of sustained damage that can be inflicted on the opponent. </p>
<h2>Offence</h2>
<h3>Summary</h3>
The offence summary provides per-weapon information about sustained damage per second inflicted to shields and hull, along with a measure of effectiveness of that weapon. The effectiveness value has a tooltip that provides a breakdown of the effectiveness, and can include reductions or increases due to range, resistance, and either power distributor (for shields) or hardness (for hull). The final effectiveness value is calculated by multiplying these percentages together. </p>
<h3>Offence Metrics</h3>
The offence metrics panel provides information about your offence. </p>
Time to drain is a measure of how quickly your WEP capacitor will drain when firing all weapons. It is affected by the number of pips you have in your WEP capacitor, with more pips resulting in a higher WEP recharge rate and hence a longer time to drain. </p>
The next value is the time it will take you to remove your opponent&apos;s shields. This assumes that you have 100% time on target and that your engagement range stays constant. Note that if your time to remove shields is longer than your time to drain this assumes that you continue firing throughout, inflicting lower damage due to the reduced energy in your WEP capacitor. </p>
The next value is the time it will take you to remove your opponent&apos;s armour. This follows the same logic as the time to remove shields. </p>
<h3>Shield Damage Sources</h3>
The shield damage sources provides information about the sources of damage to your opponent by damage type. For each applicable type of damage (absolute explosive, kinetic, thermal) a sustained damage per second value is provided. </p>
<h3>Hull Damage Sources</h3>
The hull damage sources provides information about the sources of damage to your opponent by damage type. For each applicable type of damage (absolute explosive, kinetic, thermal) a sustained damage per second value is provided. </p>
<h2>Defence</h2>
<h3>Shield Metrics</h3>
The shield metrics provides information about your shield defence. </p>
Raw shield strength is the sum of the shield from your generator, boosters and shield cell banks. A tooltip provides a breakdown of these values. </p>
The time the shields will hold for is the time it will take your opponent&apos; to remove your shields. This assumes that they have 100% time on target and that the engagement range stays constant. It also assumes that you fire all of your shield cell banks prior to your shields being lost. </p>
The time the shields will recover in is the time it will take your shields to go from collapsed (0%) to recovered (50%). This is affected by the number of pips you have in your SYS capacitor. </p>
The time the shields will recharge in is the time it will take your shields to go from recovered (50%) to full (100%). This is affected by the number of pips you have in your SYS capacitor. </p>
</h3>Shield Sources</h3>
This chart provides information about the sources of your shields. For each applicable source of shields (generator, boosters, shield cell banks) a value is provided. </p>
</h3>Damage Taken</h3>
This graph shows how the initial damage from the weapons of each type are reduced before their damage is applied to the shields. For each type of damage (absolute, explosive, kinetic, thermal) a percentage of the initial damage is provided. A tooltip provides a breakdown of these values. </p>
</h3>Effective Shield</h3>
This graph shows the effective shield for each damage type, found by dividing the raw shield value by the damage taken for that type. </p>
<h3>Amour Metrics</h3>
The armour metrics provides information about your armour defence. </p>
Raw armour strength is the sum of the armour from your bulkheads and hull reinforcement packages. A tooltip provides a breakdown of these values. </p>
The time the armour will hold for is the time it will take your opponent&apos; to take your armour to 0. This assumes that they have 100% time on target, the engagement range stays constant, and that all damage is dealt to the armour rather than modules. </p>
Raw module armour is the sum of the protection from your module reinforcement packages. </p>
Protection for hardpoints is the amount of protection that your module reinforcement packages provide to hardpoints. This percentage of damage to the hardpoints will be diverted to the module reinforcement packages. </p>
Protection for all other modules is the amount of protection that your module reinforcement packages provide to everything other than hardpoints. This percentage of damage to the modules will be diverted to the module reinforcement packages. </p>
</h3>Armour Sources</h3>
This chart provides information about the sources of your armour. For each applicable source of shields (bulkheads, hull reinforcement packages) a value is provided. </p>
</h3>Damage Taken</h3>
This graph shows how the initial damage from the weapons of each type are reduced before their damage is applied to the armour. For each type of damage (absolute, explosive, kinetic, thermal) a percentage of the initial damage is provided. A tooltip provides a breakdown of these values. </p>
</h3>Effective Armour</h3>
This graph shows the effective armour for each damage type, found by dividing the raw armour value by the damage taken for that type. </p>
<h1>Keyboard Shortcuts</h1>
<dl>
<dt>Ctrl-b</dt><dd>toggle boost</dd>
<dt>Ctrl-e</dt><dd>open export dialogue (outfitting page only)</dd>
<dt>Ctrl-h</dt><dd>open help dialogue</dd>
<dt>Ctrl-i</dt><dd>open import dialogue</dd>
<dt>Ctrl-o</dt><dd>open shortlink dialogue</dd>
<dt>Ctrl-left-arrow</dt><dd>increase SYS capacitor</dd>
<dt>Ctrl-up-arrow</dt><dd>increase ENG capacitor</dd>
<dt>Ctrl-right-arrow</dt><dd>increase WEP capacitor</dd>
<dt>Ctrl-down-arrow</dt><dd>reset power distributor</dd>
<dt>Esc</dt><dd>close any open dialogue</dd>
</dl>
<h1>Glossary</h1>
<dl>
<dt>Absolute damage</dt><dd>A type of damage, without any protection. Absolute damage is always dealt at 100% regardless of if the damage is to shields, hull or modules, and irrespective of resistances</dd>
<dt>DPS</dt><dd>Damage per second; the amount of damage that a weapon or a ship can deal per second to a target under optimum conditions</dd>
<dt>EPS</dt><dd>Energy per second; the amount of energy that a weapon or a ship drains from the weapons capacitor per second when firing</dd>
<dt>HPS</dt><dd>Heat per second; the amount of heat that a weapon or a ship generates per second when firing</dd>
<dt>Effectivness</dt><dd>A comparison of the maximum DPS of a given weapon to the actual DPS of the given weapon in a specific situation. DPS can be reduced by range to the target, the target&apos;s hull and shield resistances, and the target&apos;s hardness</dd>
<dt>Explosive damage</dt><dd>A type of damage, protected against by explosive resistance</dd>
<dt>Hardness</dt><dd>The inherent resistance to damage of a ship&apos;s hull. Hardness is defined on a per-ship basis and there is currently nothing that can be done to change it. Hardness of a ship&apos;s hull is compared to the piercing of weapons: if piercing is higher than hardness the weapon does 100% damage, otherwise it does a fraction of its damage calculated as piercing/hardness</dd>
<dt>Falloff</dt><dd>The distance at which a weapons starts to do less damage than its stated DPS</dd>
<dt>Kinetic damage</dt><dd>A type of damage, protected against by kinetic resistance</dd>
<dt>SDPS</dt><dd>Sustained damage per second; the amount of damage that a weapon or a ship can deal per second to a target, taking in to account ammunition reload</dd>
<dt>SEPS</dt><dd>Sustained energy per second; the amount of energy that a weapon or a ship drains from the weapons capacitor per second when firing, taking in to account ammunition reload</dd>
<dt>SHPS</dt><dd>Sustained heat per second; the amount of heat that a weapon or a ship generates per second when firing, taking in to account ammunition reload</dd>
<dt>Thermal damage</dt><dd>A type of damage, protected against by thermal resistance</dd>
</dl>
`,
};
export { default as terms } from './en.json';

353
src/app/i18n/en.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -13,196 +13,4 @@ export const formats = {
shortMonths: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic']
};
export const terms = {
'PHRASE_EXPORT_DESC': 'Una detallada exportaci\u00f3n JSON de tu construcci\u00f3n para usarlo en otros sitios web y herramientas',
'A-Rated': 'Calidad-A',
'about': 'Acerca',
'action': 'Acci\u00f3n',
'added': 'A\u00f1adido',
'Advanced Discovery Scanner': 'Esc\u00e1ner de exploraci\u00f3n avanzado',
maneuverability: 'Maniobrabilidad',
'alpha': 'Alfa',
'ammo': 'Munici\u00f3n',
'PHRASE_CONFIRMATION': '\u00bfEst\u00e1s seguro?',
'armour': 'Blindaje',
'am': 'Unidad de auto-reparaciones',
'available': 'Disponible',
'backup': 'Reserva',
'Basic Discovery Scanner': 'Esc\u00e1ner de exploraci\u00f3n b\u00e1sico',
'bl': 'L\u00e1ser de haz',
'bins': 'contenedores',
'boost': 'incrementar',
'build': 'Construcci\u00f3n',
'build name': 'Nombre de la construcci\u00f3n',
'builds': 'Construcciones',
'bh': 'mamparos',
'ul': 'Laser de r\u00e1fagas',
'buy': 'Comprar',
'cancel': 'Cancelar',
'c': 'Ca\u00f1\u00f3n',
'capital': 'capital',
'cargo': 'Carga',
'Cargo Hatch': 'Compuerta de carga',
'cr': 'Compartimento de carga',
'cs': 'Esc\u00e1ner de carga',
'cells': 'celdas',
'Chaff Launcher': 'Lanzador de birutas',
'close': 'Cerrar',
'cc': 'Controlador de Drones de Recogida',
'compare': 'Comparar',
'compare all': 'comparar todas',
'comparison': 'Comparativa',
'comparisons': 'Comparativas',
'component': 'Componente',
'cost': 'Coste',
'costs': 'Costes',
'cm': 'Contramedidas',
'create': 'Crear',
'create new': 'Crear nuevo',
'credits': 'Cr\u00e9ditos',
'damage': 'Da\u00f1o',
'delete': 'Borrar',
'delete all': 'Borrar todo',
'dep': 'desp',
'deployed': 'Desplegado',
'detailed export': 'Exportacion detallada',
'Detailed Surface Scanner': 'Escaner de exploraci\u00f3n detallada',
'disabled': 'Desactivado',
'discount': 'Descuento',
'dc': 'Ordenador de aterrizaje',
'done': 'Hecho',
'DPS': 'DPS (Da\u00f1o Por Segundo)',
'edit data': 'Editar datos',
'efficiency': 'Eficiencia',
'Electronic Countermeasure': 'Contramedidas electr\u00f3nicas',
'empty': 'Vac\u00edo',
'ENG': 'MOT',
'enter name': 'Introduce el nombre',
'export': 'exportar',
'fixed': 'fijo',
'forum': 'Foro',
'fc': 'Ca\u00f1\u00f3n de fragmentaci\u00f3n',
'fsd': 'Motor de salto',
'ws': 'Esc\u00e1ner de Salto',
'fi': 'Interdictor FSD',
'fuel': 'Combustible',
'fs': 'Recolector de Combustible',
'ft': 'Tanque de combustible',
'fx': 'Sistema de Transferencia de Combustilble',
'full tank': 'Tanque lleno',
'Gimballed': 'Card\u00e1n',
'H': 'E',
'hardpoints': 'Montura de armas',
'hb': 'Controlador de Apertura de Bah\u00eda de Carga',
'Heat Sink Launcher': 'Eyector de Acumulador de Calor',
'huge': 'enorme',
'hull': 'Casco',
'hr': 'Sistema de Casco Reforzado',
'import': 'Importar',
'import all': 'Importar todo',
'insurance': 'Seguro',
'Intermediate Discovery Scanner': 'Esc\u00e1ner de exploraci\u00f3n media',
'internal compartments': 'Compartimentos internos',
'jump range': 'Rango de salto',
'jumps': 'Saltos',
'kw': 'Esc\u00e1ner Detector de Recompensas',
'L': 'G',
'laden': 'Cargada',
'language': 'Idioma',
'large': 'Grande',
'ls': 'Soporte vital',
'Lightweight Alloy': 'Aleaci\u00f3n ligera',
'limpets': 'Drones',
'lock factor': 'factor de bloqueo',
'mass': 'Masa',
'max': 'm\u00e1x',
'max mass': 'Masa m\u00e1xima',
'medium': 'medio',
'Military Grade Composite': 'Blindaje Militar',
'nl': 'Lanzaminas',
'Mining Lance': 'Lanza de miner\u00eda',
'ml': 'L\u00e1ser de miner\u00eda',
'Mirrored Surface Composite': 'Blindaje Reflectante',
'mr': 'Bah\u00eda de Misiles',
'mc': 'Ca\u00f1\u00f3n m\u00faltiple',
'net cost': 'Coste neto',
'PHRASE_NO_BUILDS': '\u00a1No se a\u00f1adieron plantillas para comparaci\u00f3n!',
'PHRASE_NO_RETROCH': 'No hay cambios en los ajutes',
'none': 'Nada',
'none created': 'Nada creado',
'off': 'apagado',
'on': 'encendido',
'optimal': '\u00f3ptimo',
'optimal mass': 'masa \u00f3ptima',
'optimize mass': 'optimizar masa',
'overwrite': 'Sobreescribir',
'PHRASE_IMPORT': 'Pega el JSON o imp\u00f3rtalo aqu\u00ed',
'penetration': 'penetraci\u00f3n',
'permalink': 'enlace permanente',
'pa': 'Acelerador de Plasma',
'Point Defence': 'Punto de Defensa',
'power': 'energ\u00eda',
'pd': 'distribuidor de energ\u00eda',
'pp': 'Planta de Energ\u00eda',
'priority': 'prioridad',
'proceed': 'Proceder',
'pc': 'Controlador de drones de prospecci\u00f3n',
'pl': 'L\u00e1ser de Pulso',
'PWR': 'POT',
'rg': 'Ca\u00f1\u00f3n de Riel',
'range': 'rango',
'rate': 'ratio',
'Reactive Surface Composite': 'Blindaje Reactivo',
'recharge': 'recargar',
'rf': 'Refineria',
'refuel time': 'Tiempo para repostar',
'Reinforced Alloy': 'Armadura reforzada',
'reload': 'Recargar',
'rename': 'Renombrar',
'repair': 'Reparar',
'reset': 'Reiniciar',
'ret': 'PLE',
'retracted': 'plegadas',
'retrofit costs': 'costes de equipamiento',
'retrofit from': 'equipamiento desde',
'ROF': 'RDF',
'S': 'P',
'save': 'guardar',
'sc': 'sc\u00e1ner',
'PHRASE_SELECT_BUILDS': 'Selecciona equipamientos para comparar',
'sell': 'Vender',
's': 'Sensores',
'settings': 'Configuraci\u00f3n',
'sb': 'Potenciador de Escudos',
'scb': 'C\u00e9lula de Energ\u00eda de Escudos',
'sg': 'Generador de escudos',
'shields': 'Escudos',
'ship': 'Nave ',
'ships': 'Naves',
'shortened': 'Abreviado',
'size': 'Tama\u00f1o',
'skip': 'omitir',
'small': 'Peque\u00f1o',
'speed': 'velocidad',
'standard': 'est\u00e1ndar',
'Standard Docking Computer': 'Computador de Atraque Est\u00e1ndar',
'Stock': 'De serie',
'SYS': 'SIS',
'T_LOAD': 'c-t\u00e9rmica',
't': 'Propulsores',
'time': 'Tiempo',
'tp': 'Anclaje de torpedo',
'total': 'Total',
'total range': 'Rango total',
'turret': 'torreta',
'type': 'Tipo',
'unladen': 'Sin carga',
'PHRASE_UPDATE_RDY': 'Actualizacion disponible! Haz click para recargar',
'URL': 'Enlace',
'utility': 'utilidad',
'utility mounts': 'monturas de utilidad',
'version': 'Versi\u00f3n',
'WEP': 'ARM',
'yes': 'si',
'PHRASE_BACKUP_DESC': 'Copia de seguridad de todos los datos de Coriolis para guardarlos o transferirlos a otro navegador\/dispositivo'
};
export { default as terms } from './es.json';

193
src/app/i18n/es.json Normal file
View File

@@ -0,0 +1,193 @@
{
"PHRASE_EXPORT_DESC": "Una detallada exportación JSON de tu construcción para usarlo en otros sitios web y herramientas",
"A-Rated": "Calidad-A",
"about": "Acerca",
"action": "Acción",
"added": "Añadido",
"Advanced Discovery Scanner": "Escáner de exploración avanzado",
"maneuverability": "Maniobrabilidad",
"alpha": "Alfa",
"ammo": "Munición",
"PHRASE_CONFIRMATION": "¿Estás seguro?",
"armour": "Blindaje",
"am": "Unidad de auto-reparaciones",
"available": "Disponible",
"backup": "Reserva",
"Basic Discovery Scanner": "Escáner de exploración básico",
"bl": "Láser de haz",
"bins": "contenedores",
"boost": "incrementar",
"build": "Construcción",
"build name": "Nombre de la construcción",
"builds": "Construcciones",
"bh": "mamparos",
"ul": "Laser de ráfagas",
"buy": "Comprar",
"cancel": "Cancelar",
"c": "Cañón",
"capital": "capital",
"cargo": "Carga",
"Cargo Hatch": "Compuerta de carga",
"cr": "Compartimento de carga",
"cs": "Escáner de carga",
"cells": "celdas",
"Chaff Launcher": "Lanzador de virutas",
"close": "Cerrar",
"cc": "Controlador de Drones de Recogida",
"compare": "Comparar",
"compare all": "comparar todas",
"comparison": "Comparativa",
"comparisons": "Comparativas",
"component": "Componente",
"cost": "Coste",
"costs": "Costes",
"cm": "Contramedidas",
"create": "Crear",
"create new": "Crear nuevo",
"credits": "Créditos",
"damage": "Daño",
"delete": "Borrar",
"delete all": "Borrar todo",
"dep": "desp",
"deployed": "Desplegado",
"detailed export": "Exportacion detallada",
"Detailed Surface Scanner": "Escaner de exploración detallada",
"disabled": "Desactivado",
"discount": "Descuento",
"dc": "Ordenador de aterrizaje",
"done": "Hecho",
"DPS": "DPS (Daño Por Segundo)",
"edit data": "Editar datos",
"efficiency": "Eficiencia",
"Electronic Countermeasure": "Contramedidas electrónicas",
"empty": "Vacío",
"ENG": "MOT",
"enter name": "Introduce el nombre",
"export": "exportar",
"fixed": "fijo",
"forum": "Foro",
"fc": "Cañón de fragmentación",
"fsd": "Motor de salto",
"ws": "Escáner de Salto",
"fi": "Interdictor FSD",
"fuel": "Combustible",
"fs": "Recolector de Combustible",
"ft": "Tanque de combustible",
"fx": "Sistema de Transferencia de Combustilble",
"full tank": "Tanque lleno",
"Gimballed": "Cardán",
"H": "E",
"hardpoints": "Montura de armas",
"hb": "Controlador de Apertura de Bahía de Carga",
"Heat Sink Launcher": "Eyector de Acumulador de Calor",
"huge": "enorme",
"hull": "Casco",
"hr": "Sistema de Casco Reforzado",
"import": "Importar",
"import all": "Importar todo",
"insurance": "Seguro",
"Intermediate Discovery Scanner": "Escáner de exploración media",
"internal compartments": "Compartimentos internos",
"jump range": "Rango de salto",
"jumps": "Saltos",
"kw": "Escáner Detector de Recompensas",
"L": "G",
"laden": "Cargada",
"language": "Idioma",
"large": "Grande",
"ls": "Soporte vital",
"Lightweight Alloy": "Aleación ligera",
"limpets": "Drones",
"lock factor": "factor de bloqueo",
"mass": "Masa",
"max": "máx",
"max mass": "Masa máxima",
"medium": "medio",
"Military Grade Composite": "Blindaje Militar",
"nl": "Lanzaminas",
"Mining Lance": "Lanza de minería",
"ml": "Láser de minería",
"Mirrored Surface Composite": "Blindaje Reflectante",
"mr": "Bahía de Misiles",
"mc": "Cañón múltiple",
"net cost": "Coste neto",
"PHRASE_NO_BUILDS": "¡No se añadieron plantillas para comparación!",
"PHRASE_NO_RETROCH": "No hay cambios en los ajutes",
"none": "Nada",
"none created": "Nada creado",
"off": "apagado",
"on": "encendido",
"optimal": "óptimo",
"optimal mass": "masa óptima",
"optimize mass": "optimizar masa",
"overwrite": "Sobreescribir",
"PHRASE_IMPORT": "Pega el JSON o impórtalo aquí",
"penetration": "penetración",
"permalink": "enlace permanente",
"pa": "Acelerador de Plasma",
"Point Defence": "Punto de Defensa",
"power": "energía",
"pd": "distribuidor de energía",
"pp": "Planta de Energía",
"priority": "prioridad",
"proceed": "Proceder",
"pc": "Controlador de drones de prospección",
"pl": "Láser de Pulso",
"PWR": "POT",
"rg": "Cañón de Riel",
"range": "rango",
"rate": "ratio",
"Reactive Surface Composite": "Blindaje Reactivo",
"recharge": "recargar",
"rf": "Refineria",
"refuel time": "Tiempo para repostar",
"Reinforced Alloy": "Armadura reforzada",
"reload": "Recargar",
"rename": "Renombrar",
"repair": "Reparar",
"reset": "Reiniciar",
"ret": "PLE",
"retracted": "plegadas",
"retrofit costs": "costes de equipamiento",
"retrofit from": "equipamiento desde",
"ROF": "RDF",
"S": "P",
"save": "guardar",
"sc": "scáner",
"PHRASE_SELECT_BUILDS": "Selecciona equipamientos para comparar",
"sell": "Vender",
"s": "Sensores",
"settings": "Configuración",
"sb": "Potenciador de Escudos",
"scb": "Célula de Energía de Escudos",
"sg": "Generador de escudos",
"shields": "Escudos",
"ship": "Nave ",
"ships": "Naves",
"shortened": "Abreviado",
"size": "Tamaño",
"skip": "omitir",
"small": "Pequeño",
"speed": "velocidad",
"standard": "estándar",
"Standard Docking Computer": "Computador de Atraque Estándar",
"Stock": "De serie",
"SYS": "SIS",
"T_LOAD": "c-térmica",
"t": "Propulsores",
"time": "Tiempo",
"tp": "Anclaje de torpedo",
"total": "Total",
"total range": "Rango total",
"turret": "torreta",
"type": "Tipo",
"unladen": "Sin carga",
"PHRASE_UPDATE_RDY": "Actualizacion disponible! Haz click para recargar",
"URL": "Enlace",
"utility": "utilidad",
"utility mounts": "monturas de utilidad",
"version": "Versión",
"WEP": "ARM",
"yes": "si",
"PHRASE_BACKUP_DESC": "Copia de seguridad de todos los datos de Coriolis para guardarlos o transferirlos a otro navegador/dispositivo"
}

View File

@@ -13,143 +13,4 @@ export const formats = {
shortMonths: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.']
};
export const terms = {
// Phrases
PHRASE_BACKUP_DESC: 'Copie des données Coriolis pour l\'utilisation dans d\'autres sites et outils', // Backup of all Coriolis data to save or transfer to another browser/device
PHRASE_CONFIRMATION: 'Êtes-vous sûr?', // Are You Sure?
PHRASE_EXPORT_DESC: 'Un export détaillé en JSON de votre configuration pour l\'utilisation dans d\'autres sites et outils', // A detailed JSON export of your build for use in other sites and tools
PHRASE_FASTEST_RANGE: 'Portée maximale en cas de sauts successifs', // Consecutive max range jumps
PHRASE_IMPORT: 'Coller JSON ou importer ici', // Paste JSON or import here
PHRASE_LADEN: 'Masse du Vaisseau + Carburant + Cargo', // Ship Mass + Fuel + Cargo
PHRASE_NO_BUILDS: 'Aucune configuration ajoutée pour la comparaison', // No builds added to comparison!
PHRASE_NO_RETROCH: 'Aucun changement de configuration', // No Retrofitting changes
PHRASE_SELECT_BUILDS: 'Sélectionner les configurations à comparer', // Select Builds to Compare
PHRASE_SG_RECHARGE: 'Temps de charge de 50% à 100 %', // Time from 50% to 100% Charge
PHRASE_SG_RECOVER: 'Temps de redémarrage après perte du bouclier', // Recovery (to 50%) after collapse
PHRASE_UNLADEN: 'Masse du Vaisseau hors Carburant et Cargo', // Ship Mass excluding Fuel and Cargo
PHRASE_UPDATE_RDY: 'Mise à jour disponible ! Cliquez pour rafraichir', // Update Available! Click to Refresh
// Units / Metrics
Ls: 'SL', // Light seconds
LY: 'AL', // Light Years
// Sizes
L: 'G', // Large Hardpoint size (single character)
large: 'grand', // Large Ship Size
medium: 'moyen', // Medium ship size
S: 'P', // Small Hardpoint (single Character)
small: 'petit', // Small ship size
// Terms
about: 'à propos', // Link to about page / about Coriolis.io
added: 'ajouté',
ammo: 'munition', // Ammunition
armour: 'Armure',
available: 'Disponibilité', // Available options
backup: 'sauvegarde',
bays: 'baies',
bins: 'bacs', // Number of Mining Refinery bins
build: 'Configuration', // Shorthand for the build/configuration/design name
'build name': 'Nom de la configuration', // Ship build/configuration/design name
builds: 'Configurations', // Ship build/configuration/design names
buy: 'Acheter',
cancel: 'Annuler',
cargo: 'Soute',
cells: 'Cellule', // Number of cells in a shield cell bank
close: 'fermer',
compare: 'comparer',
'compare all': 'tout comparer',
comparison: 'comparaison',
comparisons: 'comparaisons',
cost: 'coût', // Cost / price of a module or price of a ship
costs: 'coûts', // Costs / prices of a modules or prices of ships
create: 'Créer',
'create new': 'Créer nouveau',
credits: 'crédits',
damage: 'dégât',
'damage per second': 'dégât par seconde',
delete: 'supprimer',
'delete all': 'tout supprimer',
dep: 'depl', // Weapons/Hardpoints Deployed abbreviation
deployed: 'déployé', // Weapons/Hardpoints Deployed
'detailed export': 'export détaillé',
disabled: 'désactivé',
discount: 'ristourne',
'edit data': 'Editer donnée',
efficiency: 'rendement', // Power Plant efficiency
empty: 'Vide',
'empty all': 'vide tout',
'Enter Name': 'Entrer nom',
Explorer: 'explorateur',
'fastest range': 'gamme la plus rapide', // Fastet totaljump range - sum of succesive jumps
fuel: 'carburant',
'fuel level': 'niveau de carburant', // Percent of fuel (T) in the tank
'full tank': 'Réservoir plein',
hardpoints: 'Points d\'emport',
hull: 'Coque', // Ships hull
import: 'Importer',
insurance: 'Assurance',
'internal compartments': 'compartiments internes',
jump: 'saut', // Single jump range
'jump range': 'Distance de saut',
jumps: 'Sauts',
laden: 'chargé',
language: 'Langage',
maneuverability: 'maniabilité',
manufacturer: 'fabricant',
mass: 'Masse',
'mass lock factor': 'facteur inhibition de masse',
'max mass': 'masse max',
MLF: 'FIM', // Mass Lock Factor Abbreviation
'net cost': 'coûts nets',
no: 'non',
'none created': 'Rien de créé',
ok: 'D\'accord',
'optimal mass': 'masse optimale', // Lowest weight / best weight for jump distance, etc
optimize: 'optimiser',
pen: 'pén.', // Armour peneration abbreviation
permalink: 'lien durable',
power: 'énergie', // Power = Energy / second. Power generated by the power plant, or power consumed (MW / Mega Watts). Used in the power plant section
proceed: 'continuer',
PWR: 'P', // Power Abbreviation. See Power
qty: 'quantité', // Quantity abbreviation
range: 'portée',
rate: 'cadence',
recharge: 'recharger', // Shield Recharge time from 50% -> 100%
recovery: 'récupération', // Shield recovery time (after losing shields/turning on -> 50%)
'refuel time': 'Temps de remplissage', // Time to refuel the tank when scooping
reload: 'recharger', // Reload weapon/hardpoint
'reload costs': 'recharger coûts',
rename: 'renommer',
repair: 'réparer',
reset: 'Réinitialisation',
ret: 'esc', // Retracted abbreviation
retracted: 'escamoté', // Weapons/Hardpoints retracted
'retrofit costs': 'Valeur de rachat', // The cost difference when upgrading / downgrading a component
'retrofit from': 'Racheter de', // Retrofit from Build A against build B
ROF: 'cadence', // Rate of Fire abbreviation
roles: 'rôles', // Commander/Ship build roles - e.g. Trader, Bounty-Hunter, Explorer, etc
save: 'sauvegarder',
sell: 'vendre',
settings: 'paramètres', // Coriolis application settings
shields: 'boucliers',
ship: 'vaisseau',
ships: 'vaisseaux',
shortened: 'raccourci', // Standard/Stock build of a ship when purchased new
size: 'taille',
skip: 'Suivant', // Skip past something / ignore it
speed: 'vitesse',
Stock: 'de base', // Thermal-load abbreviation
strength: 'force', // Strength in reference to Shield Strength
subtotal: 'Sous-Total',
'T-Load': 'degrés', // Thermal load abbreviation
time: 'temps', // time it takes to complete something
tooltips: 'infobulles', // Tooltips setting - show/hide
'total range': 'plage totale',
Trader: 'commerçant', // Trader role
'unit cost': 'coût unitaire',
unladen: 'Non chargé', // No cargo or fuel
'utility mounts': 'Support utilitaire',
WEP: 'ARM', // Abbreviation - Weapon recharge rate for power distributor
yes: 'oui'
};
export { default as terms } from './fr.json';

133
src/app/i18n/fr.json Normal file
View File

@@ -0,0 +1,133 @@
{
"PHRASE_BACKUP_DESC": "Copie des données Coriolis pour l'utilisation dans d'autres sites et outils",
"PHRASE_CONFIRMATION": "Êtes-vous sûr?",
"PHRASE_EXPORT_DESC": "Un export détaillé en JSON de votre configuration pour l'utilisation dans d'autres sites et outils",
"PHRASE_FASTEST_RANGE": "Portée maximale en cas de sauts successifs",
"PHRASE_IMPORT": "Coller JSON ou importer ici",
"PHRASE_LADEN": "Masse du Vaisseau + Carburant + Cargo",
"PHRASE_NO_BUILDS": "Aucune configuration ajoutée pour la comparaison",
"PHRASE_NO_RETROCH": "Aucun changement de configuration",
"PHRASE_SELECT_BUILDS": "Sélectionner les configurations à comparer",
"PHRASE_SG_RECHARGE": "Temps de charge de 50% à 100 %",
"PHRASE_SG_RECOVER": "Temps de redémarrage après perte du bouclier",
"PHRASE_UNLADEN": "Masse du Vaisseau hors Carburant et Cargo",
"PHRASE_UPDATE_RDY": "Mise à jour disponible ! Cliquez pour rafraichir",
"Ls": "SL",
"LY": "AL",
"L": "G",
"large": "grand",
"medium": "moyen",
"S": "P",
"small": "petit",
"about": "à propos",
"added": "ajouté",
"ammo": "munition",
"armour": "Armure",
"available": "Disponibilité",
"backup": "sauvegarde",
"bays": "baies",
"bins": "bacs",
"build": "Configuration",
"build name": "Nom de la configuration",
"builds": "Configurations",
"buy": "Acheter",
"cancel": "Annuler",
"cargo": "Soute",
"cells": "Cellule",
"close": "fermer",
"compare": "comparer",
"compare all": "tout comparer",
"comparison": "comparaison",
"comparisons": "comparaisons",
"cost": "coût",
"costs": "coûts",
"create": "Créer",
"create new": "Créer nouveau",
"credits": "crédits",
"damage": "dégât",
"damage per second": "dégât par seconde",
"delete": "supprimer",
"delete all": "tout supprimer",
"dep": "depl",
"deployed": "déployé",
"detailed export": "export détaillé",
"disabled": "désactivé",
"discount": "ristourne",
"edit data": "Editer donnée",
"efficiency": "rendement",
"empty": "Vide",
"empty all": "vide tout",
"Enter Name": "Entrer nom",
"Explorer": "explorateur",
"fastest range": "gamme la plus rapide",
"fuel": "carburant",
"fuel level": "niveau de carburant",
"full tank": "Réservoir plein",
"hardpoints": "Points d'emport",
"hull": "Coque",
"import": "Importer",
"insurance": "Assurance",
"internal compartments": "compartiments internes",
"jump": "saut",
"jump range": "Distance de saut",
"jumps": "Sauts",
"laden": "chargé",
"language": "Langage",
"maneuverability": "maniabilité",
"manufacturer": "fabricant",
"mass": "Masse",
"mass lock factor": "facteur inhibition de masse",
"max mass": "masse max",
"MLF": "FIM",
"net cost": "coûts nets",
"no": "non",
"none created": "Rien de créé",
"ok": "D'accord",
"optimal mass": "masse optimale",
"optimize": "optimiser",
"pen": "pén.",
"permalink": "lien durable",
"power": "énergie",
"proceed": "continuer",
"PWR": "P",
"qty": "quantité",
"range": "portée",
"rate": "cadence",
"recharge": "recharger",
"recovery": "récupération",
"refuel time": "Temps de remplissage",
"reload": "recharger",
"reload costs": "recharger coûts",
"rename": "renommer",
"repair": "réparer",
"reset": "Réinitialisation",
"ret": "esc",
"retracted": "escamoté",
"retrofit costs": "Valeur de rachat",
"retrofit from": "Racheter de",
"ROF": "cadence",
"roles": "rôles",
"save": "sauvegarder",
"sell": "vendre",
"settings": "paramètres",
"shields": "boucliers",
"ship": "vaisseau",
"ships": "vaisseaux",
"shortened": "raccourci",
"size": "taille",
"skip": "Suivant",
"speed": "vitesse",
"Stock": "de base",
"strength": "force",
"subtotal": "Sous-Total",
"T-Load": "degrés",
"time": "temps",
"tooltips": "infobulles",
"total range": "plage totale",
"Trader": "commerçant",
"unit cost": "coût unitaire",
"unladen": "Non chargé",
"utility mounts": "Support utilitaire",
"WEP": "ARM",
"yes": "oui"
}

View File

@@ -13,117 +13,4 @@ export const formats = {
shortMonths: ['Gen', 'Feb', 'Mar', 'Apr', 'Mag', 'Giu', 'Lug', 'Ago', 'Set', 'Ott', 'Nov', 'Dic']
};
export const terms = {
PHRASE_EXPORT_DESC: 'Un export dettagliato in formato JSON della tua configurazione per essere usato in altri siti o tools',
'A-Rated': 'Classe A',
about: 'Info su Coriolis',
action: 'azione',
added: 'aggiunto',
Advanced: 'Avanzato',
maneuverability: 'agilità',
ammo: 'munizioni',
PHRASE_CONFIRMATION: 'Sei sicuro ?',
armour: 'armatura',
available: 'disponibile',
bins: 'contenitore',
build: 'configurazione',
'build name': 'Nome Configurazione',
builds: 'configurazioni',
buy: 'compra',
cancel: 'cancella',
cells: 'celle',
close: 'chiudi',
compare: 'confronta',
'compare all': 'confronta tutti',
comparison: 'comparazione',
comparisons: 'comparazioni',
component: 'componente',
cost: 'costo',
costs: 'costi',
cm: 'Contromisure',
create: 'crea',
'create new': 'crea nuovo',
credits: 'crediti',
damage: 'danno',
delete: 'elimina',
'delete all': 'elimina tutto',
dep: 'dep',
deployed: 'deployed',
'detailed export': 'esportazione dettagliata',
disabled: 'disabilita',
discount: 'sconto',
done: 'fatto',
'edit data': 'modifica i dati',
efficiency: 'efficenza',
empty: 'vuoto',
Enforcer: 'Rinforzatore',
'enter name': 'Inserisci un nome',
export: 'esporta',
fixed: 'fissi',
fuel: 'carburante',
'full tank': 'Serbatoio Pieno',
huge: 'enorme',
hull: 'corazza',
import: 'importa',
'import all': 'importa tutto',
insurance: 'assicurazione',
'internal compartments': 'compartimenti interni',
'jump range': 'distanza di salto',
jumps: 'salti',
laden: 'carico',
language: 'lingua',
large: 'largo',
mass: 'massa',
max: 'massimo',
'max mass': 'massa massimale',
medium: 'medio',
'net cost': 'costo netto',
PHRASE_NO_BUILDS: 'nessuna configurazione è stata aggiunta per la comparazione!',
PHRASE_NO_RETROCH: 'Nessun cambiamento di Retrofitting',
none: 'nessuno',
'none created': 'nessuno creato',
optimal: 'ottimale',
'optimal mass': 'massa ottimale',
'optimize mass': 'ottimizza la massa',
overwrite: 'sovrasscrivi',
PHRASE_IMPORT: 'Incolla un JSON o importalo qua',
penetration: 'penetrazione',
power: 'potenza',
priority: 'priorità',
proceed: 'procedi',
range: 'distanza',
rate: 'rateo',
recharge: 'ricarica',
reload: 'ricarica',
rename: 'rinomina',
repair: 'ripara',
reset: 'resetta',
retracted: 'retratti',
'retrofit costs': 'costi di retrofit',
'retrofit from': 'retrofit da',
save: 'salva',
sell: 'vendi',
settings: 'impostazioni',
shields: 'scudi',
ship: 'nave',
ships: 'navi',
shortened: 'accorciato',
size: 'grandezza',
skip: 'salta',
small: 'piccolo',
speed: 'velocità',
Stock: 'appena comprata',
t: 'thrusters',
time: 'tempo',
total: 'totale',
'total range': 'distanza totale',
turret: 'torrette',
type: 'tipo',
unladen: 'scarico',
PHRASE_UPDATE_RDY: 'Aggiornamenti disponibili ! Clicca per Aggiornare',
utility: 'supporti',
'utility mounts': 'supporti di utilità',
version: 'versione',
yes: 'sì',
PHRASE_BACKUP_DESC: 'Esportazione di tutti i dati su Coriolis per salvarli o trasferirli in un altro Browser/dispositivo'
};
export { default as terms } from './it.json';

114
src/app/i18n/it.json Normal file
View File

@@ -0,0 +1,114 @@
{
"PHRASE_EXPORT_DESC": "Un export dettagliato in formato JSON della tua configurazione per essere usato in altri siti o tools",
"A-Rated": "Classe A",
"about": "Info su Coriolis",
"action": "azione",
"added": "aggiunto",
"Advanced": "Avanzato",
"maneuverability": "agilità",
"ammo": "munizioni",
"PHRASE_CONFIRMATION": "Sei sicuro ?",
"armour": "armatura",
"available": "disponibile",
"bins": "contenitore",
"build": "configurazione",
"build name": "Nome Configurazione",
"builds": "configurazioni",
"buy": "compra",
"cancel": "cancella",
"cells": "celle",
"close": "chiudi",
"compare": "confronta",
"compare all": "confronta tutti",
"comparison": "comparazione",
"comparisons": "comparazioni",
"component": "componente",
"cost": "costo",
"costs": "costi",
"cm": "Contromisure",
"create": "crea",
"create new": "crea nuovo",
"credits": "crediti",
"damage": "danno",
"delete": "elimina",
"delete all": "elimina tutto",
"dep": "dep",
"deployed": "deployed",
"detailed export": "esportazione dettagliata",
"disabled": "disabilita",
"discount": "sconto",
"done": "fatto",
"edit data": "modifica i dati",
"efficiency": "efficenza",
"empty": "vuoto",
"Enforcer": "Rinforzatore",
"enter name": "Inserisci un nome",
"export": "esporta",
"fixed": "fissi",
"fuel": "carburante",
"full tank": "Serbatoio Pieno",
"huge": "enorme",
"hull": "corazza",
"import": "importa",
"import all": "importa tutto",
"insurance": "assicurazione",
"internal compartments": "compartimenti interni",
"jump range": "distanza di salto",
"jumps": "salti",
"laden": "carico",
"language": "lingua",
"large": "largo",
"mass": "massa",
"max": "massimo",
"max mass": "massa massimale",
"medium": "medio",
"net cost": "costo netto",
"PHRASE_NO_BUILDS": "nessuna configurazione è stata aggiunta per la comparazione!",
"PHRASE_NO_RETROCH": "Nessun cambiamento di Retrofitting",
"none": "nessuno",
"none created": "nessuno creato",
"optimal": "ottimale",
"optimal mass": "massa ottimale",
"optimize mass": "ottimizza la massa",
"overwrite": "sovrasscrivi",
"PHRASE_IMPORT": "Incolla un JSON o importalo qua",
"penetration": "penetrazione",
"power": "potenza",
"priority": "priorità",
"proceed": "procedi",
"range": "distanza",
"rate": "rateo",
"recharge": "ricarica",
"reload": "ricarica",
"rename": "rinomina",
"repair": "ripara",
"reset": "resetta",
"retracted": "retratti",
"retrofit costs": "costi di retrofit",
"retrofit from": "retrofit da",
"save": "salva",
"sell": "vendi",
"settings": "impostazioni",
"shields": "scudi",
"ship": "nave",
"ships": "navi",
"shortened": "accorciato",
"size": "grandezza",
"skip": "salta",
"small": "piccolo",
"speed": "velocità",
"Stock": "appena comprata",
"t": "thrusters",
"time": "tempo",
"total": "totale",
"total range": "distanza totale",
"turret": "torrette",
"type": "tipo",
"unladen": "scarico",
"PHRASE_UPDATE_RDY": "Aggiornamenti disponibili ! Clicca per Aggiornare",
"utility": "supporti",
"utility mounts": "supporti di utilità",
"version": "versione",
"yes": "sì",
"PHRASE_BACKUP_DESC": "Esportazione di tutti i dati su Coriolis per salvarli o trasferirli in un altro Browser/dispositivo"
}

View File

@@ -13,65 +13,4 @@ export const formats = {
shortMonths: ['Sty', 'Lut', 'Mar', 'Kwi', 'Maj', 'Cze', 'Lip', 'Sie', 'Wrz', 'Paź', 'Lis', 'Gru']
};
export const terms = {
PHRASE_ALT_ALL: 'Alt + kliknięcie by wypełnić wszystkie sloty',
PHRASE_BACKUP_DESC: 'Kopia zapasowa wszystkich danych Coriolis w celu zapisu lub przeniesienia na inne urządzenie/przeglądarkę',
PHRASE_CONFIRMATION: 'Czy jesteś pewien?',
PHRASE_EXPORT_DESC: 'Szczegółowy eksport schematu w formacie JSON w celu użycia na innych stronach i narzędziach',
PHRASE_FASTEST_RANGE: 'Maksymalna ilość skoków na najwyższym zasięgu',
PHRASE_IMPORT: 'Wklej tu JSON lub importuj',
PHRASE_LADEN: 'Masa statku + paliwo + ładunek',
PHRASE_NO_BUILDS: 'Nie dodano schematu do porównania!',
PHRASE_NO_RETROCH: 'Brak zmian retrofit',
PHRASE_SELECT_BUILDS: 'Wybierz schematy do porównania',
PHRASE_SG_RECHARGE: 'Czas od 50% do 100% naładowania',
PHRASE_SG_RECOVER: 'Odnowienie (do 50%) po upadku',
PHRASE_UNLADEN: 'Masa statku z wyłączeniem paliwa i ładunku',
PHRASE_UPDATE_RDY: 'Dostępna aktualizacja! Naciśnij by odświeżyć',
// Other languages fallback to these values
// Only Translate to other languages if the name is different in-game
am: 'Auto Field-Maintenance Unit',
bh: 'Bulkheads',
bl: 'Beam Laser',
bsg: 'Bi-Weave Shield Generator',
c: 'Cannon',
cc: 'Collector Limpet Controller',
cm: 'Countermeasure',
cr: 'Cargo Rack',
cs: 'Cargo Scanner',
dc: 'Docking Computer',
fc: 'Fragment Cannon',
fi: 'FSD Interdictor',
fs: 'Fuel Scoop',
fsd: 'Frame Shift Drive',
ft: 'Fuel Tank',
fx: 'Fuel Transfer Limpet Controller',
hb: 'Hatch Breaker Limpet Controller',
hr: 'Hull Reinforcement Package',
kw: 'Kill Warrant Scanner',
ls: 'Life Support',
mc: 'Multi-cannon',
ml: 'Mining Laser',
mr: 'Missile Rack',
nl: 'Mine Launcher',
pa: 'Plasma Accelerator',
pas: 'Planetary Approach Suite',
pc: 'Prospector Limpet Controller',
pd: 'power distributor',
pl: 'Pulse Laser',
pp: 'Power Plant',
psg: 'Prismatic Shield Generator',
pv: 'Planetary Vehicle Hangar',
rf: 'Refinery',
rg: 'Rail Gun',
s: 'Sensors',
sb: 'Shield Booster',
sc: 'Scanner',
scb: 'Shield Cell Bank',
sg: 'Shield Generator',
t: 'thrusters',
tp: 'Torpedo Pylon',
ul: 'Burst Laser',
ws: 'Frame Shift Wake Scanner'
};
export { default as terms } from './pl.json';

59
src/app/i18n/pl.json Normal file
View File

@@ -0,0 +1,59 @@
{
"PHRASE_ALT_ALL": "Alt + kliknięcie by wypełnić wszystkie sloty",
"PHRASE_BACKUP_DESC": "Kopia zapasowa wszystkich danych Coriolis w celu zapisu lub przeniesienia na inne urządzenie/przeglądarkę",
"PHRASE_CONFIRMATION": "Czy jesteś pewien?",
"PHRASE_EXPORT_DESC": "Szczegółowy eksport schematu w formacie JSON w celu użycia na innych stronach i narzędziach",
"PHRASE_FASTEST_RANGE": "Maksymalna ilość skoków na najwyższym zasięgu",
"PHRASE_IMPORT": "Wklej tu JSON lub importuj",
"PHRASE_LADEN": "Masa statku + paliwo + ładunek",
"PHRASE_NO_BUILDS": "Nie dodano schematu do porównania!",
"PHRASE_NO_RETROCH": "Brak zmian retrofit",
"PHRASE_SELECT_BUILDS": "Wybierz schematy do porównania",
"PHRASE_SG_RECHARGE": "Czas od 50% do 100% naładowania",
"PHRASE_SG_RECOVER": "Odnowienie (do 50%) po upadku",
"PHRASE_UNLADEN": "Masa statku z wyłączeniem paliwa i ładunku",
"PHRASE_UPDATE_RDY": "Dostępna aktualizacja! Naciśnij by odświeżyć",
"am": "Auto Field-Maintenance Unit",
"bh": "Bulkheads",
"bl": "Beam Laser",
"bsg": "Bi-Weave Shield Generator",
"c": "Cannon",
"cc": "Collector Limpet Controller",
"cm": "Countermeasure",
"cr": "Cargo Rack",
"cs": "Cargo Scanner",
"dc": "Docking Computer",
"fc": "Fragment Cannon",
"fi": "FSD Interdictor",
"fs": "Fuel Scoop",
"fsd": "Frame Shift Drive",
"ft": "Fuel Tank",
"fx": "Fuel Transfer Limpet Controller",
"hb": "Hatch Breaker Limpet Controller",
"hr": "Hull Reinforcement Package",
"kw": "Kill Warrant Scanner",
"ls": "Life Support",
"mc": "Multi-cannon",
"ml": "Mining Laser",
"mr": "Missile Rack",
"nl": "Mine Launcher",
"pa": "Plasma Accelerator",
"pas": "Planetary Approach Suite",
"pc": "Prospector Limpet Controller",
"pd": "power distributor",
"pl": "Pulse Laser",
"pp": "Power Plant",
"psg": "Prismatic Shield Generator",
"pv": "Planetary Vehicle Hangar",
"rf": "Refinery",
"rg": "Rail Gun",
"s": "Sensors",
"sb": "Shield Booster",
"sc": "Scanner",
"scb": "Shield Cell Bank",
"sg": "Shield Generator",
"t": "thrusters",
"tp": "Torpedo Pylon",
"ul": "Burst Laser",
"ws": "Frame Shift Wake Scanner"
}

16
src/app/i18n/pt.js Normal file
View File

@@ -0,0 +1,16 @@
export const formats = {
decimal: ',',
thousands: '.',
grouping: [3],
currency: ['', ' €'],
dateTime: '%A, %e de %B de %Y, %X',
date: '%d/%m/%Y',
time: '%H:%M:%S',
periods: ['AM', 'PM'],
days: ['domingo', 'lunes', 'martes', 'miércoles', 'jueves', 'viernes', 'sábado'],
shortDays: ['dom', 'lun', 'mar', 'mié', 'jue', 'vie', 'sáb'],
months: ['enero', 'febrero', 'marzo', 'abril', 'mayo', 'junio', 'julio', 'agosto', 'septiembre', 'octubre', 'noviembre', 'diciembre'],
shortMonths: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic']
};
export { default as terms } from './pt.json';

278
src/app/i18n/pt.json Normal file

File diff suppressed because one or more lines are too long

View File

@@ -13,152 +13,4 @@ export const formats = {
shortMonths: ['янв', 'фев', 'мар', 'апр', 'май', 'июн', 'июл', 'авг', 'сен', 'окт', 'ноя', 'дек']
};
export const terms = {
// Phrases
PHRASE_BACKUP_DESC: 'Сохраните все данные перед переносом в другой браузер или устройство', // Backup of all Coriolis data to save or transfer to another browser/device
PHRASE_CONFIRMATION: 'Вы уверены?', // Are You Sure?
PHRASE_EXPORT_DESC: 'Детальный JSON-экспорт вашей сборки для использования в других местах и инструментах', // A detailed JSON export of your build for use in other sites and tools
PHRASE_FASTEST_RANGE: 'Последовательные прыжки максимальной дальности', // Consecutive max range jumps
PHRASE_IMPORT: 'Для импорта вставьте код в эту форму', // Paste JSON or import here
PHRASE_LADEN: 'Масса корабля с учётом топлива и грузов', // Ship Mass + Fuel + Cargo
PHRASE_NO_BUILDS: 'Нечего сравнивать', // No builds added to comparison!
PHRASE_NO_RETROCH: 'нет ранних версий сборки\\конфигурации', // No Retrofitting changes
PHRASE_SELECT_BUILDS: 'Выберите конфигурацию для сравнения', // Select Builds to Compare
PHRASE_SG_RECHARGE: 'восстановление с 60% до 100% объема щита', // Time from 50% to 100% Charge
PHRASE_SG_RECOVER: 'восстановление [до 60%] после снятия щита', // Recovery (to 50%) after collapse
PHRASE_UNLADEN: 'Масса корабля без учета топлива и грузов', // Ship Mass excluding Fuel and Cargo
PHRASE_UPDATE_RDY: 'Доступно обновление. Нажмите для обновления.', // Update Available! Click to Refresh
// Units / Metrics
'/s': '/с', // Per second
'm/s': 'м/с', // Meters / Second
Ls: 'Св.сек', // Light seconds
LY: 'Св.лет', // Light Years
CR: 'кр.', // Credits abbreviation
// Sizes
S: 'M', // Small Hardpoint (single Character)
M: 'С', // Medium Hardpoint size (single character)
L: 'б', // Large Hardpoint size (single character)
H: 'O', // Huge Hardpoint size (single character)
U: 'B', // Utility Hardpoint size (single character) - Kill warrant scanner, etc
small: 'Малый', // Small ship size
medium: 'Средний', // Medium ship size
large: 'большой', // Large Ship Size
// Insurance
alpha: 'Альфа', // Alpha backer insurance level
beta: 'Бета', // Beta back insurance level
standard: 'Стандартный', // Standard insurance level
// Terms
'build name': 'название сборки', // Ship build/configuration/design name
'compare all': 'сравнить все',
'create new': 'Создать новый',
'damage per second': 'урон в секунду',
'delete all': 'Удалить все',
'detailed export': 'Подробный экспорт',
'edit data': 'Редактирование',
'empty all': 'пусто все',
'Enter Name': 'Введите имя',
'fastest range': 'быстрый диапазон', // Fastet totaljump range - sum of succesive jumps
'fuel level': 'уровень топлива', // Percent of fuel (T) in the tank
'full tank': 'Полный бак',
'internal compartments': 'внутренние отсеки',
'jump range': 'Дальность прыжка',
'mass lock factor': 'Масс. блок',
'max mass': 'Максимальная масса',
'net cost': 'разница в цене',
'none created': 'не создано',
'refuel time': 'Время дозаправки', // Time to refuel the tank when scooping
'retrofit costs': 'цена модификации', // The cost difference when upgrading / downgrading a component
'retrofit from': 'модификация от', // Retrofit from Build A against build B
'T-Load': 'Тепл.', // Thermal load abbreviation
'utility mounts': 'Вспомогательное оборудование',
about: 'О ...', // Link to about page / about Coriolis.io
action: 'Действие',
added: 'Добавлено',
ammo: 'Боекомплект', // Ammunition
armour: 'Броня',
available: 'доступно', // Available options
backup: 'Резервная копия',
bins: 'контейнеры', // Number of Mining Refinery bins
boost: 'форсаж',
build: 'cборка', // Shorthand for the build/configuration/design name
builds: 'cборки', // Ship build/configuration/design names
buy: 'купить',
cancel: 'отменить',
cargo: 'Груз',
cells: 'Ячейки', // Number of cells in a shield cell bank
close: 'закрыть',
compare: 'сравнить ',
comparison: 'сравнение',
comparisons: 'сравнения',
cost: 'Стоимость', // Cost / price of a module or price of a ship
costs: 'Расходы', // Costs / prices of a modules or prices of ships
create: 'создать',
credits: 'Кредиты',
damage: 'Урон',
delete: 'Удалить',
dep: 'Вып', // Weapons/Hardpoints Deployed abbreviation
deployed: 'Открыты', // Weapons/Hardpoints Deployed
disabled: 'Отключено',
discount: 'Скидка',
DPS: 'УВС', // Damage per second abbreviation
efficiency: 'Эффективность', // Power Plant efficiency
empty: 'пусто',
ENG: 'ДВГ', // Abbreviation - Engine recharge rate for power distributor
export: 'Экспорт',
forum: 'Форум',
fuel: 'Топливо',
hardpoints: 'Орудийные порты',
hull: 'Корпус', // Ships hull
import: 'импортировать ',
insurance: 'Страховка',
jumps: 'Прыжков',
laden: 'Груженый',
language: 'Язык',
maneuverability: 'Маневренность',
mass: 'Масса',
max: 'Макс',
no: 'Нет',
pen: 'ПБ', // Armour peneration abbreviation
permalink: 'Постоянная ссылка',
power: 'Мощность', // Power = Energy / second. Power generated by the power plant, or power consumed (MW / Mega Watts). Used in the power plant section
pri: 'Осн', // Priority abbreviation for power management
proceed: 'продолжить',
PWR: 'Эн', // Power Abbreviation. See Power
range: 'Дальность',
rate: 'скорость',
recharge: 'перезарядка', // Shield Recharge time from 50% -> 100%
recovery: 'включение', // Shield recovery time (after losing shields/turning on -> 50%)
reload: 'Перезагрузить', // Reload weapon/hardpoint
rename: 'Переименовать',
repair: 'Починка',
reset: 'Сброс',
ret: 'Убр.', // Retracted abbreviation
retracted: 'Убрано', // Weapons/Hardpoints retracted
ROF: 'В/сек', // Rate of Fire abbreviation
save: 'Сохранить',
sell: 'Продать',
settings: 'Настройки', // Coriolis application settings
shields: 'Щиты',
ship: 'Корабль',
ships: 'Корабли',
shortened: 'Укороченный', // Standard/Stock build of a ship when purchased new
size: 'размер',
skip: 'пропустить', // Skip past something / ignore it
speed: 'скорость',
standard: 'Стандартный', // Standard / Common modules (FSD, power plant, life support, etc)
Stock: 'Стандартная комплектация', // Thermal-load abbreviation
SYS: 'СИСТЕМЫ', // Abbreviation - System recharge rate for power distributor
time: 'Время', // time it takes to complete something
total: 'Всего',
type: 'Тип',
unladen: 'Пустой', // No cargo or fuel
URL: 'Ссылка', // Link, Uniform Resource Locator
WEP: 'ОРУДИЯ', // Abbreviation - Weapon recharge rate for power distributor
yes: 'Да'
};
export { default as terms } from './ru.json';

384
src/app/i18n/ru.json Normal file
View File

@@ -0,0 +1,384 @@
{
"PHRASE_ALT_ALL": "Alt + Нажатие для заполнения всех слотов",
"PHRASE_BACKUP_DESC": "Сохраните все данные перед переносом в другой браузер или устройство",
"PHRASE_CONFIRMATION": "Вы уверены?",
"PHRASE_EXPORT_DESC": "Детальный JSON-экспорт вашей сборки для использования в других местах и инструментах",
"PHRASE_FASTEST_RANGE": "Последовательные прыжки максимальной дальности",
"PHRASE_IMPORT": "Для импорта вставьте код в эту форму",
"PHRASE_LADEN": "Масса корабля с учётом топлива и грузов",
"PHRASE_NO_BUILDS": "Нечего сравнивать",
"PHRASE_NO_RETROCH": "Нет ранних версий сборки",
"PHRASE_SELECT_BUILDS": "Выберите конфигурацию для сравнения",
"PHRASE_SG_RECHARGE": "Восстановление с 50% до 100% объема щита, учитывая полный аккумулятор СИС в начале",
"PHRASE_SG_RECOVER": "Восстановление с 0% до 50% объема щита, учитывая полный аккумулятор СИС в начале",
"PHRASE_UNLADEN": "Масса корабля без учета топлива и грузов",
"PHRASE_UPDATE_RDY": "Доступна новая версия. Нажмите для обновления.",
"PHRASE_ENGAGEMENT_RANGE": "Дистанция между кораблём и целью",
"PHRASE_SELECT_BLUEPRINT": "Нажмите чтобы выбрать чертёж",
"PHRASE_BLUEPRINT_WORST": "Худшие основные значения для чертежа",
"PHRASE_BLUEPRINT_RANDOM": "Случайный выбор между худшими и лучшими значениями для этого чертежа",
"PHRASE_BLUEPRINT_BEST": "Лучшие основные значения для чертежа",
"PHRASE_BLUEPRINT_EXTREME": "Лучшие положительные и худшие отрицательные основные значения для чертежа",
"PHRASE_BLUEPRINT_RESET": "Убрать все изменения и чертёж",
"PHRASE_SELECT_SPECIAL": "Нажмите, чтобы выбрать экспериментальный эффект",
"PHRASE_NO_SPECIAL": "Без экспериментального эффекта",
"PHRASE_SHOPPING_LIST": "Станции, что продают эту сборку",
"PHRASE_REFIT_SHOPPING_LIST": "Станции, что продают необходимые модули",
"PHRASE_TOTAL_EFFECTIVE_SHIELD": "Общий урон, что может быть нанесён в каждым типе, если используются все щитонакопители",
"PHRASE_TIME_TO_LOSE_SHIELDS": "Щиты продержатся",
"PHRASE_TIME_TO_RECOVER_SHIELDS": "Щиты восстановятся за",
"PHRASE_TIME_TO_RECHARGE_SHIELDS": "Щиты будут заряжены за",
"PHRASE_SHIELD_SOURCES": "Подробности энергии щита",
"PHRASE_EFFECTIVE_SHIELD": "Эффективная сила щита против разных типов урона",
"PHRASE_ARMOUR_SOURCES": "Подробности состава брони",
"PHRASE_EFFECTIVE_ARMOUR": "Эффективная сила брони против разных типов урона",
"PHRASE_DAMAGE_TAKEN": "% общих повреждений полученных в разных типах урона",
"PHRASE_TIME_TO_LOSE_ARMOUR": "Броня продержится",
"PHRASE_MODULE_PROTECTION_EXTERNAL": "Защита гнёзд",
"PHRASE_MODULE_PROTECTION_INTERNAL": "Защита всех остальных модулей",
"PHRASE_SHIELD_DAMAGE": "Подробности источников поддерживаемого ДПС против щитов",
"PHRASE_ARMOUR_DAMAGE": "Подробности источников поддерживаемого ДПС против брони",
"PHRASE_TIME_TO_REMOVE_SHIELDS": "Снимет щиты за",
"TT_TIME_TO_REMOVE_SHIELDS": "Непрерывным огнём из всех орудий",
"PHRASE_TIME_TO_REMOVE_ARMOUR": "Снимет броню за",
"TT_TIME_TO_REMOVE_ARMOUR": "Непрерывным огнём из всех орудий",
"PHRASE_TIME_TO_DRAIN_WEP": "Опустошит ОРУЖ за",
"TT_TIME_TO_DRAIN_WEP": "Время, за которое опустошится аккумулятор ОРУЖ при стрельбе из всех орудий",
"TT_TIME_TO_LOSE_SHIELDS": "Против поддерживаемой стрельбы из всех орудий противника",
"TT_TIME_TO_LOSE_ARMOUR": "Против поддерживаемой стрельбы из всех орудий противника",
"TT_MODULE_ARMOUR": "Броня, защищающая модули от урона",
"TT_MODULE_PROTECTION_EXTERNAL": "Процент урона, перенаправленного от гнёзд на наборы для усиления модулей",
"TT_MODULE_PROTECTION_INTERNAL": "Процент урона, перенаправленного от модулей вне гнёзд на наборы для усиления модулей",
"TT_EFFECTIVE_SDPS_SHIELDS": "Реальный поддерживаемый ДПС пока аккумулятор ОРУЖ не пуст",
"TT_EFFECTIVENESS_SHIELDS": "Эффективность в сравнении с попаданием по цели с 0-сопротивляемостью без пунктов в СИС на 0 метрах",
"TT_EFFECTIVE_SDPS_ARMOUR": "Реальный поддерживаемый ДПС пока аккумулятор ОРУЖ не пуст",
"TT_EFFECTIVENESS_ARMOUR": "Эффективность в сравнении с попаданием по цели с 0-сопротивляемостью на 0 метрах",
"PHRASE_EFFECTIVE_SDPS_SHIELDS": "ПДПС против щитов",
"PHRASE_EFFECTIVE_SDPS_ARMOUR": "ПДПС против брони",
"TT_SUMMARY_SPEED": "С полным топливным баком и 4 пунктами в ДВИ",
"TT_SUMMARY_SPEED_NONFUNCTIONAL": "Маневровые двигатели выключены или превышена максимальная масса с топливом и грузом",
"TT_SUMMARY_BOOST": "С полным топливным баком и 4 пунктами в ДВИ",
"TT_SUMMARY_BOOST_NONFUNCTIONAL": "Распределитель питания не может обеспечить достаточно энергии для форсажа",
"TT_SUMMARY_SHIELDS": "Чистая сила щита, включая усилители",
"TT_SUMMARY_SHIELDS_NONFUNCTIONAL": "Шитогенератор отсутствует или выключен",
"TT_SUMMARY_INTEGRITY": "Целостность корабля, включая переборки и наборы для усиления корпуса",
"TT_SUMMARY_HULL_MASS": "Масса корпуса без каких-либо модулей",
"TT_SUMMARY_UNLADEN_MASS": "Масса корпуса и модулей без топлива и груза",
"TT_SUMMARY_LADEN_MASS": "Масса корпуса и модулей с топливом и грузом",
"TT_SUMMARY_DPS": "Урон в секунду при стрельбе из всех орудий",
"TT_SUMMARY_EPS": "Расход аккумулятора ОРУЖ в секунду при стрельбе из всех орудий",
"TT_SUMMARY_TTD": "Время расхода аккумулятора ОРУЖ при стрельбе из всех орудий и с 4 пунктами в ОРУЖ",
"TT_SUMMARY_MAX_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с топливом, достаточным только на сам прыжок",
"TT_SUMMARY_UNLADEN_SINGLE_JUMP": "Самый дальний возможный прыжок без груза и с полным топливным баком",
"TT_SUMMARY_LADEN_SINGLE_JUMP": "Самый дальний возможный прыжок с полным грузовым отсеком и с полным топливным баком",
"TT_SUMMARY_UNLADEN_TOTAL_JUMP": "Самая дальняя общая дистанция без груза, с полным топливным баком и при прыжках на максимальное расстояние",
"TT_SUMMARY_LADEN_TOTAL_JUMP": "Самая дальняя общая дистанция с полным грузовым отсеком, с полным топливным баком и при прыжках на максимальное расстояние",
"HELP_MODIFICATIONS_MENU": "Нажмите на номер, чтобы ввести новое значение, или потяните вдоль полосы для малых изменений",
"am": "Блок Автом. Полевого Ремонта",
"bh": "Переборки",
"bl": "Пучковый лазер",
"bsg": "Двухпоточный щитогенератор",
"c": "Орудие",
"cc": "Контроллер магнитного снаряда для сбора",
"ch": "Разбрасыватель дипольных отражателей",
"cr": "Грузовой стеллаж",
"cs": "Сканер содержимого",
"dc": "Стыковочный компьютер",
"ec": "Электр. противодействие",
"fc": "Залповое орудие",
"fh": "Ангар для истребителя",
"fi": "FSD-перехватчик",
"fs": "Топливозаборник",
"fsd": "Рамочно-сместительный двигатель",
"ft": "Топливный бак",
"fx": "Контроллер магнитного снаряда для топлива",
"hb": "Контроллер магнитного снаряда для взлома трюма",
"hr": "Набор для усиления корпуса",
"hs": "Теплоотводная катапульта",
"kw": "Сканер преступников",
"ls": "Система жизнеобеспечения",
"mc": "Многоствольное орудие",
"ml": "Проходочный лазер",
"mr": "Ракетный лоток",
"mrp": "Набор для усиления модуля",
"nl": "Мины",
"pa": "Ускоритель плазмы",
"pas": "Комплект для сближения с планетой",
"pc": "Контроллер магнитного снаряда для геологоразведки",
"pce": "Каюта пассажира эконом-класса",
"pci": "Каюта пассажира бизнес-класса",
"pcm": "Каюта пассажира первого класса",
"pcq": "Каюта пассажира класса люкс",
"pd": "Распределитель питания",
"pl": "Импульсный лазер",
"po": "Точечная оборона",
"pp": "Силовая установка",
"psg": "Призматический щитогенератор",
"pv": "Гараж для планетарного транспорта",
"rf": "Устройство переработки",
"rg": "Электромагнитная пушка",
"s": "Сенсоры",
"sb": "Усилитель щита",
"sc": "Сканер обнаружения",
"scb": "Щитонакопитель",
"sg": "Щитогенератор",
"ss": "Сканер поверхностей",
"t": "Маневровые двигатели",
"tp": "Торпедная стойка",
"ul": "Пульсирующие лазеры",
"ws": "Сканер следа FSD",
"emptyrestricted": "пусто (ограниченно)",
"damage dealt to": "Урон нанесён",
"damage received from": "Урон получен от",
"against shields": "Против щитов",
"against hull": "Против корпуса",
"total effective shield": "Общие эффективные щиты",
"ammunition": "Припасы",
"secs": "с",
"rebuildsperbay": "Построек за полосу",
"worst": "Худшее",
"average": "Среднее",
"random": "Случайное",
"best": "Лучшее",
"extreme": "Экстремальное",
"reset": "Сброс",
"dpe": "Урон на МДж энергии",
"dps": "Урон в Секунду",
"sdps": "Поддерживаемый урон в секунду",
"dpssdps": "Урон в секунду (поддерживаемый урон в секунду)",
"eps": "Энергия в секунду",
"epsseps": "Энергия в секунду (поддерживаемая энергия в секунду)",
"hps": "Нагрев в секунду",
"hpsshps": "Нагрев в секунду (поддерживаемый нагрев в секунду)",
"damage by": "Урон",
"damage from": "Урон от",
"shield cells": "Щитонакопители",
"recovery": "включение",
"recharge": "перезарядка",
"engine pips": "Пункты в двигателе",
"4b": "4 пункта и Форсаж",
"speed": "скорость",
"pitch": "Тангаж",
"roll": "Крен",
"yaw": "Рыскание",
"internal protection": "Внутренняя защита",
"external protection": "Внешняя защита",
"engagement range": "Боевое расстояние",
"total": "Всего",
"ammo": "Боекомплект",
"boot": "Время загрузки",
"brokenregen": "Скорость восстановления при пробое",
"burst": "Длина очереди",
"burstrof": "Скорострельность очереди",
"clip": "Боекомплект",
"damage": "Урон",
"distdraw": "Тяга распределителя",
"duration": "Продолжительность",
"eff": "Эффективность",
"engcap": "Ресурс двигателей",
"engrate": "Перезарядка двигателей",
"explres": "Сопротивление взрывам",
"facinglimit": "Ограничение по направлению",
"hullboost": "Увеличение корпуса",
"hullreinforcement": "Укрепление корпуса",
"integrity": "Целостность",
"jitter": "Дрожание",
"kinres": "Сопротивление кинетическому урону",
"maxfuel": "Макс. топлива на прыжок",
"mass": "Масса",
"optmass": "Оптимизированная масса",
"optmul": "Оптимальный усилитель",
"pgen": "Мощность",
"piercing": "Бронебойность",
"power": "Мощность",
"protection": "Защита от повреждений",
"range": "Дальность",
"ranget": "Дальность",
"regen": "Скорость восстановления",
"reload": "Перезагрузить",
"rof": "Скорострельность",
"angle": "Угол сканера",
"scanrate": "Скорость сканера",
"scantime": "Время сканирования",
"shield": "Щит",
"shieldboost": "Усиление щитов",
"shieldreinforcement": "Усилитель щита",
"shotspeed": "Скорость выстрела",
"spinup": "Время раскрутки",
"syscap": "Ресурс систем",
"sysrate": "Перезарядка систем",
"thermload": "Тепловая нагрузка",
"thermres": "Сопротивление термическому урону",
"wepcap": "Орудийный ресурс",
"weprate": "Перезарядка оружия",
"minmass_sg": "Мин. масса корпуса",
"optmass_sg": "Опт. масса корпуса",
"maxmass_sg": "Макс. масса корпуса",
"minmul_sg": "Минимальная прочность",
"optmul_sg": "Оптимальная прочность",
"maxmul_sg": "Максимальная прочность",
"minmass_psg": "Мин. масса корпуса",
"optmass_psg": "Опт. масса корпуса",
"maxmass_psg": "Макс. масса корпуса",
"minmul_psg": "Минимальная прочность",
"optmul_psg": "Оптимальная прочность",
"maxmul_psg": "Максимальная прочность",
"minmass_bsg": "Мин. масса корпуса",
"optmass_bsg": "Опт. масса корпуса",
"maxmass_bsg": "Макс. масса корпуса",
"minmul_bsg": "Минимальная прочность",
"optmul_bsg": "Оптимальная прочность",
"maxmul_bsg": "Максимальная прочность",
"range_s": "Типовой диапозон выброса",
"absolute": "Общий",
"explosive": "Взрывч.",
"kinetic": "Механич.",
"thermal": "Тепл.",
"generator": "Генератор",
"boosters": "Усилители",
"cells": "Ячейки",
"bulkheads": "Переборки",
"reinforcement": "Усилители",
"power and costs": "Энергия и стоимость",
"costs": "Расходы",
"retrofit costs": "цена модификации",
"reload costs": "Стоимость перезарядки",
"profiles": "Графики",
"engine profile": "Двигатели",
"fsd profile": "FSD",
"movement profile": "Движение",
"damage to opponent's shields": "Урон щиту противника",
"damage to opponent's hull": "Урон корпусу противника",
"offence": "Нападение",
"defence": "Оборона",
"shield metrics": "Данные щита",
"raw shield strength": "Чистая мощность щита",
"shield sources": "Ресурсы щита",
"damage taken": "Полученный урон",
"effective shield": "Эффективный щит",
"armour metrics": "Данные брони",
"raw armour strength": "Чистая мощность брони",
"armour sources": "Ресурсы брони",
"raw module armour": "Чистая броня модулей",
"effective armour": "Эффективная броня",
"offence metrics": "Данные нападения",
"defence metrics": "Данные обороны",
"fuel carried": "Топливо на борту",
"cargo carried": "Груз на борту",
"ship control": "Управление кораблём",
"opponent": "Противник",
"opponent's shields": "Щит противника",
"opponent's armour": "Броня противника",
"shield damage sources": "источники урона по щиту",
"armour damage sources": "источники урона по броне",
"never": "Никогда",
"stock": "базовый",
"boost": "форсаж",
"/s": "/с",
"m/s": "м/с",
"Ls": "Св.сек",
"LY": "Св.лет",
"CR": "кр.",
"S": "M",
"M": "С",
"L": "б",
"H": "O",
"U": "B",
"small": "Малый",
"medium": "Средний",
"large": "большой",
"alpha": "Альфа",
"beta": "Бета",
"standard": "Стандартный",
"build name": "название сборки",
"compare all": "сравнить все",
"create new": "Создать новый",
"damage per second": "урон в секунду",
"delete all": "Удалить все",
"detailed export": "Подробный экспорт",
"edit data": "Редактирование",
"empty all": "пусто все",
"Enter Name": "Введите имя",
"fastest range": "быстрый диапазон",
"fuel level": "уровень топлива",
"full tank": "Полный бак",
"internal compartments": "внутренние отсеки",
"jump range": "Дальность прыжка",
"mass lock factor": "Масс. блок",
"max mass": "Максимальная масса",
"net cost": "разница в цене",
"none created": "не создано",
"refuel time": "Время дозаправки",
"retrofit from": "модификация от",
"T-Load": "Тепл.",
"utility mounts": "Вспомогательное оборудование",
"about": "О ...",
"action": "Действие",
"added": "Добавлено",
"armour": "Броня",
"available": "доступно",
"backup": "Резервная копия",
"bins": "контейнеры",
"build": "cборка",
"builds": "cборки",
"buy": "купить",
"cancel": "отменить",
"cargo": "Груз",
"close": "закрыть",
"compare": "сравнить ",
"comparison": "сравнение",
"comparisons": "сравнения",
"cost": "Стоимость",
"create": "создать",
"credits": "Кредиты",
"delete": "Удалить",
"dep": "Вып",
"deployed": "Открыты",
"disabled": "Отключено",
"discount": "Скидка",
"DPS": "УВС",
"efficiency": "Эффективность",
"empty": "пусто",
"ENG": "ДВИ",
"export": "Экспорт",
"forum": "Форум",
"fuel": "Топливо",
"hardpoints": "Орудийные порты",
"hull": "Корпус",
"import": "импортировать ",
"insurance": "Страховка",
"jumps": "Прыжков",
"laden": "Груженый",
"language": "Язык",
"maneuverability": "Маневренность",
"max": "Макс",
"no": "Нет",
"pen": "ПБ",
"permalink": "Постоянная ссылка",
"pri": "Осн",
"proceed": "продолжить",
"PWR": "Эн",
"rate": "скорость",
"rename": "Переименовать",
"repair": "Починка",
"ret": "Убр.",
"retracted": "Убрано",
"ROF": "В/сек",
"save": "Сохранить",
"sell": "Продать",
"settings": "Настройки",
"shields": "Щиты",
"ship": "Корабль",
"ships": "Корабли",
"shortened": "Укороченный",
"size": "размер",
"skip": "пропустить",
"Stock": "Стандартная комплектация",
"SYS": "СИС",
"time": "Время",
"type": "Тип",
"unladen": "Пустой",
"URL": "Ссылка",
"WEP": "ОРУЖ",
"yes": "Да"
}

View File

@@ -41,6 +41,15 @@ export default class AboutPage extends Page {
<h3>Chat</h3>
<p>You can chat to us on our <a href='https://discord.gg/0uwCh6R62aPRjk9w' target='_blank'>EDCD Discord server</a>.</p>
<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>
<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>;
}
}

View File

@@ -7,9 +7,20 @@ import Router from '../Router';
import Persist from '../stores/Persist';
import * as Utils from '../utils/UtilityFunctions';
import Ship from '../shipyard/Ship';
import * as _ from 'lodash';
import { toDetailedBuild } from '../shipyard/Serializer';
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 ShipSummaryTable from '../components/ShipSummaryTable';
import StandardSlotSection from '../components/StandardSlotSection';
@@ -25,6 +36,8 @@ import EngagementRange from '../components/EngagementRange';
import OutfittingSubpages from '../components/OutfittingSubpages';
import ModalExport from '../components/ModalExport';
import ModalPermalink from '../components/ModalPermalink';
import ModalShoppingList from '../components/ModalShoppingList';
import ModalOrbis from '../components/ModalOrbis';
/**
* Document Title Generator
@@ -58,6 +71,7 @@ export default class OutfittingPage extends Page {
this._fuelUpdated = this._fuelUpdated.bind(this);
this._opponentUpdated = this._opponentUpdated.bind(this);
this._engagementRangeUpdated = this._engagementRangeUpdated.bind(this);
this._sectionMenuRefs = {};
}
/**
@@ -73,13 +87,10 @@ export default class OutfittingPage extends Page {
let buildName = params.bn;
let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults
let savedCode = Persist.getBuild(shipId, buildName);
if (!data) {
return { error: { message: 'Ship not found: ' + shipId } };
}
let ship = new Ship(shipId, data.properties, data.slots); // Create a new Ship instance
if (code) {
ship.buildFrom(code); // Populate modules from serialized 'code' URL param
} else {
@@ -446,7 +457,10 @@ export default class OutfittingPage extends Page {
fuel = ship.fuelCapacity;
}
const code = this._fullCode(ship, fuel, cargo);
this.setState({ code, cargo, fuel }, () => this._updateRoute(shipId, buildName, code));
// Only update the state if this really has been updated
if (this.state.code != code || this.state.cargo != cargo || this.state.fuel != fuel) {
this.setState({ code, cargo, fuel }, () => this._updateRoute(shipId, buildName, code));
}
}
/**
@@ -491,6 +505,23 @@ export default class OutfittingPage extends Page {
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
*/
@@ -505,6 +536,13 @@ export default class OutfittingPage extends Page {
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
* @param {Event} e Keyboard Event
@@ -551,12 +589,36 @@ export default class OutfittingPage extends Page {
const internalSlotMarker = `${ship.name}${_iStr}${_pStr}${_mStr}`;
const hardpointsSlotMarker = `${ship.name}${_hStr}${_pStr}${_mStr}`;
const boostMarker = `${ship.canBoost(cargo, fuel)}`;
const shipSummaryMarker = `${ship.name}${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}${ship.ladenMass}${ship.cargo}${ship.fuel}`;
const shipSummaryMarker = `${ship.name}${_sStr}${_iStr}${_hStr}${_pStr}${_mStr}${ship.ladenMass}${cargo}${fuel}`;
const requirements = Ships[ship.id].requirements;
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) {
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) {
requirements.federationRank && renderRequirement('federation', 'federation rank ' + requirements.federationRank, 'federation rank required');
requirements.empireRank && renderRequirement('empire', 'empire rank ' + requirements.empireRank, 'empire rank required');
requirements.horizons && renderRequirement('horizons', 'horizons', 'horizons required');
requirements.horizonsEarlyAdoption && renderRequirement('horizons', 'horizons early adoption', 'horizons early adoption required');
}
return (
<div id='outfit' className={'page'} style={{ fontSize: (sizeRatio * 0.9) + 'em' }}>
<div id='overview'>
<h1>{ship.name}</h1>
<div id='requirements'>{requirementElements}</div>
<div id='build'>
<input value={newBuildName || ''} onChange={this._buildNameChange} placeholder={translate('Enter Name')} maxLength={50} />
<button onClick={canSave && this._saveBuild} disabled={!canSave} onMouseOver={termtip.bind(null, 'save')} onMouseOut={hide}>
@@ -583,15 +645,21 @@ export default class OutfittingPage extends Page {
<button onClick={this._genShortlink} onMouseOver={termtip.bind(null, 'shortlink')} onMouseOut={hide}>
<LinkIcon className='lg' />
</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>
{/* Main tables */}
<ShipSummaryTable ship={ship} fuel={fuel} cargo={cargo} marker={shipSummaryMarker} />
<StandardSlotSection ship={ship} fuel={fuel} cargo={cargo} code={standardSlotMarker} onChange={shipUpdated} currentMenu={menu} />
<InternalSlotSection ship={ship} code={internalSlotMarker} onChange={shipUpdated} currentMenu={menu} />
<HardpointSlotSection ship={ship} code={hardpointsSlotMarker} onChange={shipUpdated} currentMenu={menu} />
<UtilitySlotSection ship={ship} code={hardpointsSlotMarker} onChange={shipUpdated} currentMenu={menu} />
<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} sectionMenuRefs={this._sectionMenuRefs}/>
<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} sectionMenuRefs={this._sectionMenuRefs}/>
<UtilitySlotSection ship={ship} code={hardpointsSlotMarker} onChange={shipUpdated} onCargoChange={this._cargoUpdated} onFuelChange={this._fuelUpdated} currentMenu={menu} sectionMenuRefs={this._sectionMenuRefs}/>
{/* Control of ship and opponent */}
<div className='group quarter'>

View File

@@ -25,6 +25,25 @@ function countInt(slot) {
this.int[slot.maxClass - 1]++; // Subtract 1 since there is no Class 0 Internal compartment
this.intCount++;
this.maxCargo += crEligible ? ModuleUtils.findInternal('cr', slot.maxClass, 'E').cargo : 0;
// if no eligiblity, then assume pce
let passSlotType = null;
let passSlotRating = null;
if (!slot.eligible || slot.eligible.pce) {
passSlotType = 'pce';
passSlotRating = 'E';
} else if (slot.eligible.pci) {
passSlotType = 'pci';
passSlotRating = 'D';
} else if (slot.eligible.pcm) {
passSlotType = 'pcm';
passSlotRating = 'C';
} else if (slot.eligible.pcq) {
passSlotType = 'pcq';
passSlotRating = 'B';
}
let passengerBay = passSlotType ? ModuleUtils.findMaxInternal(passSlotType, slot.maxClass, passSlotRating) : null;
this.maxPassengers += passengerBay ? passengerBay.passengers : 0;
}
/**
@@ -39,6 +58,7 @@ function shipSummary(shipId, shipData) {
hpCount: 0,
intCount: 0,
maxCargo: 0,
maxPassengers: 0,
hp: [0, 0, 0, 0, 0], // Utility, Small, Medium, Large, Huge
int: [0, 0, 0, 0, 0, 0, 0, 0], // Sizes 1 - 8
standard: shipData.slots.standard,
@@ -139,15 +159,16 @@ export default class ShipyardPage extends Page {
* @param {Object} u Localized unit map
* @param {Function} fInt Localized integer formatter
* @param {Function} fRound Localized round formatter
* @param {Boolean} highlight Should this row be highlighted
* @return {React.Component} Table Row
*/
_shipRowElement(s, translate, u, fInt, fRound) {
_shipRowElement(s, translate, u, fInt, fRound, highlight) {
let noTouch = this.context.noTouch;
return <tr
key={s.id}
style={{ height: '1.5em' }}
className={cn({ highlighted: noTouch && this.state.shipId === s.id })}
className={cn({ highlighted: noTouch && this.state.shipId === s.id, alt: highlight })}
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
>
<td className='ri'>{s.manufacturer}</td>
@@ -166,12 +187,14 @@ export default class ShipyardPage extends Page {
<td className='ri'>{fInt(s.topBoost)}</td>
<td className='ri'>{fRound(s.maxJumpRange)}</td>
<td className='ri'>{fInt(s.maxCargo)}</td>
<td className='ri'>{fInt(s.maxPassengers)}</td>
<td className='cn'>{s.standard[0]}</td>
<td className='cn'>{s.standard[1]}</td>
<td className='cn'>{s.standard[2]}</td>
<td className='cn'>{s.standard[3]}</td>
<td className='cn'>{s.standard[4]}</td>
<td className='cn'>{s.standard[5]}</td>
<td className='cn'>{s.standard[6]}</td>
<td className={cn({ disabled: !s.hp[1] })}>{s.hp[1]}</td>
<td className={cn({ disabled: !s.hp[2] })}>{s.hp[2]}</td>
<td className={cn({ disabled: !s.hp[3] })}>{s.hp[3]}</td>
@@ -246,13 +269,26 @@ export default class ShipyardPage extends Page {
let shipRows = new Array(shipSummaries.length);
let detailRows = new Array(shipSummaries.length);
let lastShipSortValue = null;
let backgroundHighlight = false;
for (let s of shipSummaries) {
detailRows[i] = this._shipRowElement(s, translate, units, fInt, formats.f1);
let shipSortValue = s[shipPredicate];
if(shipPredicateIndex != undefined) {
shipSortValue = shipSortValue[shipPredicateIndex];
}
if(shipSortValue != lastShipSortValue) {
backgroundHighlight = !backgroundHighlight;
lastShipSortValue = shipSortValue;
}
detailRows[i] = this._shipRowElement(s, translate, units, fInt, formats.f1, backgroundHighlight);
shipRows[i] = (
<tr
key={i}
style={{ height: '1.5em' }}
className={cn({ highlighted: noTouch && this.state.shipId === s.id })}
className={cn({ highlighted: noTouch && this.state.shipId === s.id, alt: backgroundHighlight })}
onMouseEnter={noTouch && this._highlightShip.bind(this, s.id)}
>
<td className='le'><Link href={'/outfit/' + s.id}>{s.name}</Link></td>
@@ -293,8 +329,8 @@ export default class ShipyardPage extends Page {
<th rowSpan={3} className='sortable' onMouseEnter={termtip.bind(null, 'hardness')} onMouseLeave={hide} onClick={sortShips('hardness')}>{translate('hrd')}</th>
<th>&nbsp;</th>
<th colSpan={4}>{translate('base')}</th>
<th colSpan={4}>{translate('max')}</th>
<th className='lft' colSpan={6}></th>
<th colSpan={5}>{translate('max')}</th>
<th className='lft' colSpan={7}></th>
<th className='lft' colSpan={5}></th>
<th className='lft' colSpan={8}></th>
</tr>
@@ -310,29 +346,31 @@ export default class ShipyardPage extends Page {
<th className='sortable' onClick={sortShips('topBoost')}>{translate('boost')}</th>
<th className='sortable' onClick={sortShips('maxJumpRange')}>{translate('jump')}</th>
<th className='sortable' onClick={sortShips('maxCargo')}>{translate('cargo')}</th>
<th className='sortable' onClick={sortShips('maxPassengers')}>{translate('pax')}</th>
<th className='lft' colSpan={6}>{translate('core module classes')}</th>
<th className='lft' colSpan={7}>{translate('core module classes')}</th>
<th colSpan={5} className='sortable lft' onClick={sortShips('hpCount')}>{translate('hardpoints')}</th>
<th colSpan={8} className='sortable lft' onClick={sortShips('intCount')}>{translate('internal compartments')}</th>
</tr>
<tr>
<th className='sortable lft' onClick={sortShips('retailCost')}>{units.CR}</th>
<th className='sortable lft' onClick={sortShips('hullMass')}>{units.T}</th>
<th className='sortable lft' onClick={sortShips('speed')}>{units['m/s']}</th>
<th className='sortable' onClick={sortShips('boost')}>{units['m/s']}</th>
<th>&nbsp;</th>
<th className='sortable' onClick={sortShips('baseShieldStrength')}>{units.MJ}</th>
<th className='sortable lft' onClick={sortShips('topSpeed')}>{units['m/s']}</th>
<th className='sortable' onClick={sortShips('topBoost')}>{units['m/s']}</th>
<th className='sortable' onClick={sortShips('maxJumpRange')}>{units.LY}</th>
<th className='sortable' onClick={sortShips('maxCargo')}>{units.T}</th>
<th className='sortable lft' onClick={sortShips('retailCost')}>{units.CR}</th>
<th className='sortable lft' onClick={sortShips('hullMass')}>{units.T}</th>
<th className='sortable lft' onClick={sortShips('speed')}>{units['m/s']}</th>
<th className='sortable' onClick={sortShips('boost')}>{units['m/s']}</th>
<th>&nbsp;</th>
<th className='sortable' onClick={sortShips('baseShieldStrength')}>{units.MJ}</th>
<th className='sortable lft' onClick={sortShips('topSpeed')}>{units['m/s']}</th>
<th className='sortable' onClick={sortShips('topBoost')}>{units['m/s']}</th>
<th className='sortable' onClick={sortShips('maxJumpRange')}>{units.LY}</th>
<th className='sortable' onClick={sortShips('maxCargo')}>{units.T}</th>
<th>&nbsp;</th>
<th className='sortable lft' onMouseEnter={termtip.bind(null, 'power plant')} onMouseLeave={hide} onClick={sortShips('standard', 0)}>{'pp'}</th>
<th className='sortable' onMouseEnter={termtip.bind(null, 'thrusters')} onMouseLeave={hide} onClick={sortShips('standard', 1)}>{'th'}</th>
<th className='sortable' onMouseEnter={termtip.bind(null, 'frame shift drive')} onMouseLeave={hide} onClick={sortShips('standard', 2)}>{'fsd'}</th>
<th className='sortable' onMouseEnter={termtip.bind(null, 'life support')} onMouseLeave={hide} onClick={sortShips('standard', 3)}>{'ls'}</th>
<th className='sortable' onMouseEnter={termtip.bind(null, 'power distriubtor')} onMouseLeave={hide} onClick={sortShips('standard', 4)}>{'pd'}</th>
<th className='sortable' onMouseEnter={termtip.bind(null, 'sensors')} onMouseLeave={hide} onClick={sortShips('standard', 5)}>{'s'}</th>
<th className='sortable' onMouseEnter={termtip.bind(null, 'fuel tank')} onMouseLeave={hide} onClick={sortShips('standard', 6)}>{'ft'}</th>
<th className='sortable lft' onClick={sortShips('hp',1)}>{translate('S')}</th>
<th className='sortable' onClick={sortShips('hp', 2)}>{translate('M')}</th>
<th className='sortable' onClick={sortShips('hp', 3)}>{translate('L')}</th>

View File

@@ -7,11 +7,20 @@ import Module from './Module';
* @param {object} fsd The FDS object/component with maxfuel, fuelmul, fuelpower, optmass
* @param {number} fuel Optional - The fuel consumed during the jump
* @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 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 {number} fuel The total fuel available
* @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 fsdOptimalMass = fsd instanceof Module ? fsd.getOptMass() : fsd.optmass;
@@ -30,7 +40,7 @@ export function totalJumpRange(mass, fsd, fuel) {
let totalRange = 0;
while (fuelRemaining > 0) {
const fuelForThisJump = Math.min(fuelRemaining, fsdMaxFuelPerJump);
totalRange += this.jumpRange(mass, fsd, fuelForThisJump);
totalRange += this.jumpRange(mass, fsd, fuelForThisJump, ship);
// Mass is reduced
mass -= fuelForThisJump;
fuelRemaining -= fuelForThisJump;
@@ -45,7 +55,7 @@ export function totalJumpRange(mass, fsd, fuel) {
* @param {number} baseShield Base Shield strength MJ for ship
* @param {object} sg The shield generator used
* @param {number} multiplier Shield multiplier for ship (1 + shield boosters if any)
* @return {number} Approximate shield strengh in MJ
* @return {number} Approximate shield strengh in MJ
*/
export function shieldStrength(mass, baseShield, sg, multiplier) {
// sg might be a module or a template; handle either here
@@ -55,13 +65,12 @@ export function shieldStrength(mass, baseShield, sg, multiplier) {
let minMul = sg instanceof Module ? sg.getMinMul() : sg.minmul;
let optMul = sg instanceof Module ? sg.getOptMul() : sg.optmul;
let maxMul = sg instanceof Module ? sg.getMaxMul() : sg.maxmul;
let xnorm = Math.min(1, (maxMass - mass) / (maxMass - minMass));
let exponent = Math.log((optMul - minMul) / (maxMul - minMul)) / Math.log(Math.min(1, (maxMass - optMass) / (maxMass - minMass)));
let ynorm = Math.pow(xnorm, exponent);
let mul = minMul + ynorm * (maxMul - minMul);
return baseShield * mul * multiplier;
return (baseShield * mul * multiplier);
}
/**
@@ -87,6 +96,16 @@ export function speed(mass, baseSpeed, thrusters, engpip) {
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
* @param {number} mass the mass of the ship
@@ -202,7 +221,7 @@ function calcValue(minMass, optMass, maxMass, minMul, optMul, maxMul, mass, base
* Calculate speed for a given setup
* @param {number} mass the mass of the ship
* @param {number} baseSpeed the base speed of the ship
* @param {ojbect} thrusters the thrusters of the ship
* @param {object} thrusters the thrusters of the ship
* @param {number} engpip the multiplier per pip to engines
* @param {number} eng the pips to engines
* @param {number} boostFactor the boost factor for ths ship
@@ -230,7 +249,7 @@ export function calcSpeed(mass, baseSpeed, thrusters, engpip, eng, boostFactor,
* Calculate pitch for a given setup
* @param {number} mass the mass of the ship
* @param {number} basePitch the base pitch of the ship
* @param {ojbect} thrusters the thrusters of the ship
* @param {object} thrusters the thrusters of the ship
* @param {number} engpip the multiplier per pip to engines
* @param {number} eng the pips to engines
* @param {number} boostFactor the boost factor for ths ship
@@ -321,41 +340,76 @@ export function shieldMetrics(ship, sys) {
const maxSysResistance = this.sysResistance(4);
let shield = {};
const dimReturnLine = (res) => 1 - (1 - res) * 0.7;
const shieldGeneratorSlot = ship.findInternalByGroup('sg');
if (shieldGeneratorSlot && shieldGeneratorSlot.enabled && shieldGeneratorSlot.m) {
const shieldGenerator = shieldGeneratorSlot.m;
let res = {
kin: shieldGenerator.kinres,
therm: shieldGenerator.thermres,
expl: shieldGenerator.explres
};
// Boosters
let boost = 1;
let boosterExplDmg = 1;
let boosterKinDmg = 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) {
if (slot.enabled && slot.m && slot.m.grp == 'sb') {
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());
boosterKinDmg = boosterKinDmg * (1 - slot.m.getKineticResistance());
boosterThermDmg = boosterThermDmg * (1 - slot.m.getThermalResistance());
}
}
if (slot.m && slot.m.grp == 'gsrp') {
}
}
// Calculate diminishing returns for boosters
// Diminishing returns not currently in-game
// boost = Math.min(boost, (1 - Math.pow(Math.E, -0.7 * boost)) * 2.5);
// Remove base shield generator strength
boost -= 1;
// Apply diminishing returns
boosterExplDmg = boosterExplDmg > 0.7 ? boosterExplDmg : 0.7 - (0.7 - boosterExplDmg) / 2;
boosterKinDmg = boosterKinDmg > 0.7 ? boosterKinDmg : 0.7 - (0.7 - boosterKinDmg) / 2;
boosterThermDmg = boosterThermDmg > 0.7 ? boosterThermDmg : 0.7 - (0.7 - boosterThermDmg) / 2;
const generatorStrength = this.shieldStrength(ship.hullMass, ship.baseShieldStrength, shieldGenerator, 1);
// 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;
// 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 sysRechargeRate = this.sysRechargeRate(powerDistributor, sys);
@@ -374,7 +428,7 @@ export function shieldMetrics(ship, sys) {
const remainingShieldToRecover = shieldToRecover - capacitorLifetime * shieldGenerator.getBrokenRegenerationRate();
if (sys === 0) {
// No system pips so will never recover shields
recover = Math.Inf;
recover = Math.Infinity;
} else {
// Recover remaining shields at the rate of the power distributor's recharge
recover += remainingShieldToRecover / (sysRechargeRate / 0.6);
@@ -382,7 +436,7 @@ export function shieldMetrics(ship, sys) {
}
// 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
// 0.6 is a magic number from FD: each 0.6 MW of energy from the power distributor recharges 1 MJ/s of regeneration
@@ -409,8 +463,9 @@ export function shieldMetrics(ship, sys) {
shield = {
generator: generatorStrength,
boosters: boostersStrength,
addition: shieldAddition,
cells: ship.shieldCells,
total: generatorStrength + boostersStrength + ship.shieldCells,
total: generatorStrength + boostersStrength + ship.shieldCells + shieldAddition,
recover,
recharge,
};
@@ -425,34 +480,74 @@ export function shieldMetrics(ship, sys) {
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 = {
generator: 1 - shieldGenerator.getExplosiveResistance(),
boosters: boosterExplDmg,
generator: sgExplosiveDmg,
boosters: sgSbExplosiveDmg / sgExplosiveDmg,
sys: (1 - sysResistance),
total: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - sysResistance),
max: (1 - shieldGenerator.getExplosiveResistance()) * boosterExplDmg * (1 - maxSysResistance)
base: sgSbExplosiveDmg,
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 = {
generator: 1 - shieldGenerator.getKineticResistance(),
boosters: boosterKinDmg,
generator: sgKineticDmg,
boosters: sgSbKineticDmg / sgKineticDmg,
sys: (1 - sysResistance),
total: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - sysResistance),
max: (1 - shieldGenerator.getKineticResistance()) * boosterKinDmg * (1 - maxSysResistance)
base: sgSbKineticDmg,
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 = {
generator: 1 - shieldGenerator.getThermalResistance(),
boosters: boosterThermDmg,
generator: sgThermalDmg,
boosters: sgSbThermalDmg / sgThermalDmg,
sys: (1 - sysResistance),
total: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - sysResistance),
max: (1 - shieldGenerator.getThermalResistance()) * boosterThermDmg * (1 - maxSysResistance)
base: sgSbThermalDmg,
total: sgSbThermalDmg * (1 - sysResistance),
max: sgSbThermalDmg * (1 - maxSysResistance),
};
}
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
* @param {Object} ship The ship
@@ -465,33 +560,58 @@ export function armourMetrics(ship) {
let moduleArmour = 0;
let moduleProtection = 1;
const bulkheads = ship.bulkheads.m;
let hullExplDmg = 1;
let hullKinDmg = 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
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();
// Hull boost for HRPs is applied against the ship's base armour
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());
hullKinDmg = hullKinDmg * (1 - slot.m.getKineticResistance());
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();
moduleProtection = moduleProtection * (1 - slot.m.getProtection());
}
}
moduleProtection = 1 - moduleProtection;
// Apply diminishing returns
hullExplDmg = hullExplDmg > 0.7 ? hullExplDmg : 0.7 - (0.7 - hullExplDmg) / 2;
hullKinDmg = hullKinDmg > 0.7 ? hullKinDmg : 0.7 - (0.7 - hullKinDmg) / 2;
hullThermDmg = hullThermDmg > 0.7 ? hullThermDmg : 0.7 - (0.7 - hullThermDmg) / 2;
// const explDim = dimReturnLine(bulkheads.explres);
// const thermDim = dimReturnLine(bulkheads.thermres);
// const kinDim = dimReturnLine(bulkheads.kinres);
// 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 = {
bulkheads: armourBulkheads,
@@ -509,24 +629,41 @@ export function armourMetrics(ship) {
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 = {
bulkheads: 1 - ship.bulkheads.m.getExplosiveResistance(),
reinforcement: hullExplDmg,
total: (1 - ship.bulkheads.m.getExplosiveResistance()) * hullExplDmg
bulkheads: armourExplDmg,
reinforcement: armourReinforcedExplDmg / armourExplDmg,
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 = {
bulkheads: 1 - ship.bulkheads.m.getKineticResistance(),
reinforcement: hullKinDmg,
total: (1 - ship.bulkheads.m.getKineticResistance()) * hullKinDmg
bulkheads: armourKinDmg,
reinforcement: armourReinforcedKinDmg / armourKinDmg,
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 = {
bulkheads: 1 - ship.bulkheads.m.getThermalResistance(),
reinforcement: hullThermDmg,
total: (1 - ship.bulkheads.m.getThermalResistance()) * hullThermDmg
bulkheads: armourThermDmg,
reinforcement: armourReinforcedThermDmg / armourThermDmg,
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;
}
@@ -698,20 +835,55 @@ export function _sustainedDps(ship, opponent, opponentShields, opponentArmour, e
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
* @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} opponentArmour The opponent's armour resistances
* @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) {
const opponentHasShields = opponentShields.generator ? true : false;
const weapon = {
eps: 0,
damage: {
base: {
absolute: 0,
explosive: 0,
kinetic: 0,
thermal: 0,
total: 0,
},
shields: {
absolute: 0,
explosive: 0,
@@ -745,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();
// 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
const falloff = m.getFalloff();
@@ -756,6 +928,12 @@ export function _weaponSustainedDps(m, opponent, opponentShields, opponentArmour
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)
const armourMultiple = m.getPiercing() >= opponent.hardness ? 1 : m.getPiercing() / opponent.hardness;
weapon.effectiveness.armour.hardness = armourMultiple;
@@ -853,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

@@ -9,12 +9,16 @@ export const StandardArray = [
'pd', // Power Distributor
's', // Sensors
'ft', // Fuel Tank
'gpp', // Guardian Hybrid Power Plant
'gpd' // Guardian Hybrid Power Distributor
];
// Map to lookup group labels/names for component grp, used for JSON Serialization
export const ModuleGroupToName = {
// Standard
pp: 'Power Plant',
gpp: 'Guardian Hybrid Power Plant',
gpd: 'Guardian Power Distributor',
t: 'Thrusters',
fsd: 'Frame Shift Drive',
ls: 'Life Support',
@@ -48,6 +52,11 @@ export const ModuleGroupToName = {
pcq: 'Luxury Passenger Cabin',
cc: 'Collector Limpet Controller',
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
bl: 'Beam Laser',
@@ -58,19 +67,33 @@ export const ModuleGroupToName = {
cm: 'Countermeasure',
ec: 'Electronic Countermeasure',
fc: 'Fragment Cannon',
rfl: 'Remote Release Flak Launcher',
hs: 'Heat Sink Launcher',
ws: 'Frame Shift Wake Scanner',
kw: 'Kill Warrant Scanner',
nl: 'Mine Launcher',
ml: 'Mining Laser',
mr: 'Missile Rack',
axmr: 'AX Missile Rack',
pa: 'Plasma Accelerator',
po: 'Point Defence',
mc: 'Multi-cannon',
axmc: 'AX Multi-cannon',
pl: 'Pulse Laser',
rg: 'Rail Gun',
sb: 'Shield Booster',
tp: 'Torpedo Pylon'
tp: 'Torpedo Pylon',
sfn: 'Shutdown Field Neutraliser',
xs: 'Xeno Scanner',
rcpl: 'Recon Limpet Controller',
rsl: 'Research Limpet Controller',
dtl: 'Decontamination Limpet Controller',
gpc: 'Guardian Plasma Charger',
ggc: 'Guardian Gauss Cannon',
tbsc: 'Shock Cannon',
gsc: 'Guardian Shard Cannon',
tbem: 'Enzyme Missile Rack',
tbrfl: 'Remote Release Flechette Launcher',
};
let GrpNameToCodeMap = {};

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -63,7 +63,6 @@ export function standard(type, id) {
if (!isNaN(type)) {
type = StandardArray[type];
}
let s = Modules.standard[type].find(e => e.id == id || (e.class == id.charAt(0) && e.rating == id.charAt(1)));
if (s) {
s = new Module({ template: s });
@@ -196,6 +195,29 @@ export function findInternal(groupName, clss, rating, name) {
return null;
}
/**
* Finds an internal module based on Class, Rating, Group and/or name.
* At least one of Group name or unique module name must be provided.
* will start searching at specified class and proceed lower until a
* module is found or 0 is hit
* Uses findInternal internally
*
* @param {String} groupName [Optional] Full name or abbreviated name for module group
* @param {integer} clss module Class
* @param {String} rating module Rating
* @param {String} name [Optional] Long/unique name for module -e.g. 'Advanced Discover Scanner'
* @return {Object} The module if found, null if not found
*/
export function findMaxInternal(groupName, clss, rating, name) {
let foundModule = null;
let currentClss = clss;
while (currentClss > 0 && foundModule == null) {
foundModule = findInternal(groupName, currentClss, rating, name);
currentClss = currentClss - 1;
}
return foundModule;
}
/**
* Finds an internal Module ID based on Class, Rating, Group and/or name.
* At least one ofGroup name or unique module name must be provided

View File

@@ -7,9 +7,10 @@ import LZString from 'lz-string';
import * as _ from 'lodash';
import isEqual from 'lodash/lang';
import { Ships, Modifications } from 'coriolis-data/dist';
import { chain } from 'lodash';
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
const SLOT_ID_DONE = -1;
@@ -151,7 +152,7 @@ export default class Ship {
* @return {Number} Jump range in Light Years
*/
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) {
fsd = fsd || this.standard[2].m;
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);
}
/**
@@ -198,7 +199,7 @@ export default class Ship {
* @return {Number} Pitch
*/
calcPitch(eng, fuel, cargo, boost) {
return Calc.calcPitch(this.unladenMass + fuel + cargo, this.pitch, this.standard[1].m, this.pipSpeed, eng, this.topBoost / this.topSpeed, boost);
return Calc.calcPitch(this.unladenMass + fuel + cargo, this.pitch, this.standard[1].m, this.pipSpeed, eng, this.boost / this.speed, boost);
}
/**
@@ -210,7 +211,7 @@ export default class Ship {
* @return {Number} Roll
*/
calcRoll(eng, fuel, cargo, boost) {
return Calc.calcRoll(this.unladenMass + fuel + cargo, this.roll, this.standard[1].m, this.pipSpeed, eng, this.topBoost / this.topSpeed, boost);
return Calc.calcRoll(this.unladenMass + fuel + cargo, this.roll, this.standard[1].m, this.pipSpeed, eng, this.boost / this.speed, boost);
}
/**
@@ -222,7 +223,7 @@ export default class Ship {
* @return {Number} Yaw
*/
calcYaw(eng, fuel, cargo, boost) {
return Calc.calcYaw(this.unladenMass + fuel + cargo, this.yaw, this.standard[1].m, this.pipSpeed, eng, this.topBoost / this.topSpeed, boost);
return Calc.calcYaw(this.unladenMass + fuel + cargo, this.yaw, this.standard[1].m, this.pipSpeed, eng, this.boost / this.speed, boost);
}
/**
@@ -239,7 +240,6 @@ export default class Ship {
}
sg = sgSlot.m;
}
// TODO Not accurate if the ship has modified shield boosters
return Calc.shieldStrength(this.hullMass, this.baseShieldStrength, sg, 1 + (multiplierDelta || 0));
}
@@ -435,6 +435,17 @@ export default class Ship {
*/
setModuleBlueprint(m, bp) {
m.blueprint = bp;
this.clearModifications(m);
// Set any hidden items for the blueprint now
if (m.blueprint.grades[m.blueprint.grade] && m.blueprint.grades[m.blueprint.grade].features) {
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
if (Modifications.modifications[featureName].hidden) {
this.setModification(m, featureName, bp.grades[bp.grade].features[featureName][0]);
}
}
}
this.updateModificationsString();
}
@@ -456,7 +467,17 @@ export default class Ship {
if (m.blueprint) {
m.blueprint.special = special;
}
this.recalculateDps().recalculateHps().recalculateEps();
this.updatePowerGenerated()
.updatePowerUsed()
.recalculateMass()
.updateJumpStats()
.recalculateShield()
.recalculateShieldCells()
.recalculateArmour()
.recalculateDps()
.recalculateEps()
.recalculateHps()
.updateMovement();
}
/**
@@ -464,10 +485,7 @@ export default class Ship {
* @param {Object} m The module for which to clear the blueprint
*/
clearModuleSpecial(m) {
if (m.blueprint) {
m.blueprint.special = null;
}
this.recalculateDps().recalculateHps().recalculateEps();
this.setModuleSpecial(m, null);
}
/**
@@ -476,69 +494,62 @@ export default class Ship {
* @param {Object} name The name of the modification to change
* @param {Number} value The new value of the modification. The value of the modification is scaled to provide two decimal places of precision in an integer. For example 1.23% is stored as 123
* @param {bool} sentfromui True if this update was sent from the UI
* @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)) {
// Value passed is invalid; reset it to 0
value = 0;
}
if (isAbsolute) {
m.setPretty(name, value, sentfromui);
} else {
m.setModValue(name, value, sentfromui);
}
// Handle special cases
if (name === 'pgen') {
// Power generation
m.setModValue(name, value, sentfromui);
this.updatePowerGenerated();
} else if (name === 'power') {
// Power usage
m.setModValue(name, value, sentfromui);
this.updatePowerUsed();
} else if (name === 'mass') {
// Mass
m.setModValue(name, value, sentfromui);
this.recalculateMass();
this.updateMovement();
this.updateJumpStats();
} else if (name === 'maxfuel') {
m.setModValue(name, value, sentfromui);
this.updateJumpStats();
} else if (name === 'optmass') {
m.setModValue(name, value, sentfromui);
// Could be for any of thrusters, FSD or shield
this.updateMovement();
this.updateJumpStats();
this.recalculateShield();
} else if (name === 'optmul') {
m.setModValue(name, value, sentfromui);
// Could be for any of thrusters, FSD or shield
this.updateMovement();
this.updateJumpStats();
this.recalculateShield();
} else if (name === 'shieldboost') {
m.setModValue(name, value, sentfromui);
this.recalculateShield();
} else if (name === 'hullboost' || name === 'hullreinforcement' || name === 'modulereinforcement') {
m.setModValue(name, value, sentfromui);
this.recalculateArmour();
} else if (name === 'shieldreinforcement') {
m.setModValue(name, value, sentfromui);
this.recalculateShieldCells();
} else if (name === 'burst' || name == 'burstrof' || name === 'clip' || name === 'damage' || name === 'distdraw' || name === 'jitter' || name === 'piercing' || name === 'range' || name === 'reload' || name === 'rof' || name === 'thermload') {
m.setModValue(name, value, sentfromui);
this.recalculateDps();
this.recalculateHps();
this.recalculateEps();
} else if (name === 'explres' || name === 'kinres' || name === 'thermres') {
m.setModValue(name, value, sentfromui);
// Could be for shields or armour
this.recalculateArmour();
this.recalculateShield();
} else if (name === 'engcap') {
m.setModValue(name, value, sentfromui);
// Might have resulted in a change in boostability
this.updateMovement();
} else {
// Generic
m.setModValue(name, value, sentfromui);
}
}
@@ -561,6 +572,7 @@ export default class Ship {
// Reset Cumulative stats
this.fuelCapacity = 0;
this.cargoCapacity = 0;
this.passengerCapacity = 0;
this.ladenMass = 0;
this.armour = this.baseArmour;
this.shield = this.baseShieldStrength;
@@ -608,7 +620,7 @@ export default class Ship {
standard[i].cat = 0;
standard[i].priority = priorities && priorities[i + 1] ? priorities[i + 1] * 1 : 0;
standard[i].type = 'SYS';
standard[i].m = null; // Resetting 'old' modul if there was one
standard[i].m = null; // Resetting 'old' module if there was one
standard[i].discountedCost = 0;
if (comps) {
let module = ModuleUtils.standard(i, comps.standard[i]);
@@ -762,7 +774,7 @@ export default class Ship {
// Alter as required due to changes in the (build) code from one version to the next
this.upgradeInternals(internal, 1 + this.standard.length + this.hardpoints.length, priorities, enabled, modifications, blueprints, version);
}
return this.buildWith(
{
bulkheads: code.charAt(0) * 1,
@@ -920,9 +932,14 @@ export default class Ship {
let epsChanged = n && n.getEps() || old && old.getEps();
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');
@@ -979,25 +996,6 @@ export default class Ship {
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
* @return {this} The ship instance (for chaining operations)
@@ -1180,42 +1178,45 @@ export default class Ship {
let unladenMass = this.hullMass;
let cargoCapacity = 0;
let fuelCapacity = 0;
let passengerCapacity = 0;
unladenMass += this.bulkheads.m.getMass();
for (let slotNum in this.standard) {
const slot = this.standard[slotNum];
if (slot.m) {
unladenMass += slot.m.getMass();
if (slot.m.grp === 'ft') {
fuelCapacity += slot.m.fuel;
}
}
}
let slots = this.standard.concat(this.internal, this.hardpoints);
// TODO: create class for slot and also add slot.get
// handle unladen mass
unladenMass += chain(slots)
.map(slot => slot.m ? slot.m.get('mass') : null)
.filter()
.reduce((sum, mass) => sum + mass)
.value();
for (let slotNum in this.internal) {
const slot = this.internal[slotNum];
if (slot.m) {
unladenMass += slot.m.getMass();
if (slot.m.grp === 'ft') {
fuelCapacity += slot.m.fuel;
} else if (slot.m.grp === 'cr') {
cargoCapacity += slot.m.cargo;
}
}
}
// handle fuel capacity
fuelCapacity += chain(slots)
.map(slot => slot.m ? slot.m.get('fuel') : null)
.filter()
.reduce((sum, fuel) => sum + fuel)
.value();
for (let slotNum in this.hardpoints) {
const slot = this.hardpoints[slotNum];
if (slot.m) {
unladenMass += slot.m.getMass();
}
}
// handle cargo capacity
cargoCapacity += chain(slots)
.map(slot => slot.m ? slot.m.get('cargo') : null)
.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
this.unladenMass = unladenMass;
this.cargoCapacity = cargoCapacity;
this.fuelCapacity = fuelCapacity;
this.passengerCapacity = passengerCapacity;
this.ladenMass = unladenMass + fuelCapacity + cargoCapacity;
return this;
@@ -1250,7 +1251,7 @@ export default class Ship {
// Obtain shield metrics with 0 pips to sys (parts affected by SYS aren't used here)
const metrics = Calc.shieldMetrics(this, 0);
this.shield = metrics.generator ? metrics.generator + metrics.boosters : 0;
this.shield = metrics.generator ? metrics.generator + metrics.boosters + metrics.addition : 0;
this.shieldExplRes = this.shield > 0 ? 1 - metrics.explosive.total : null;
this.shieldKinRes = this.shield > 0 ? 1 - metrics.kinetic.total : null;
this.shieldThermRes = this.shield > 0 ? 1 - metrics.thermal.total : null;
@@ -1283,45 +1284,15 @@ export default class Ship {
*/
recalculateArmour() {
// Armour from bulkheads
let bulkhead = this.bulkheads.m;
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);
let metrics = Calc.armourMetrics(this);
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;
}
@@ -1333,10 +1304,10 @@ export default class Ship {
let fsd = this.standard[2].m; // Frame Shift Drive;
let { unladenMass, fuelCapacity } = this;
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.unladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity, fsd, fuelCapacity);
this.ladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity + this.cargoCapacity, fsd, fuelCapacity);
this.unladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity, fsd, fuelCapacity, this);
this.ladenFastestRange = Calc.totalJumpRange(unladenMass + this.fuelCapacity + this.cargoCapacity, fsd, fuelCapacity, this);
this.maxJumpCount = Math.ceil(fuelCapacity / fsd.getMaxFuelPerJump());
return this;
}

View File

@@ -9,9 +9,9 @@ import { canMount } from '../utils/SlotFunctions';
*/
export function multiPurpose(ship, shielded, bulkheadIndex) {
ship.useStandard('A')
.use(ship.standard[3], ModuleUtils.standard(3, ship.standard[3].maxClass + 'D')) // D Life Support
.use(ship.standard[5], ModuleUtils.standard(5, ship.standard[5].maxClass + 'D')) // D Sensors
.useBulkhead(bulkheadIndex);
.use(ship.standard[3], ModuleUtils.standard(3, ship.standard[3].maxClass + 'D')) // D Life Support
.use(ship.standard[5], ModuleUtils.standard(5, ship.standard[5].maxClass + 'D')) // D Sensors
.useBulkhead(bulkheadIndex);
if (shielded) {
ship.internal.some(function(slot) {
@@ -31,24 +31,31 @@ export function multiPurpose(ship, shielded, bulkheadIndex) {
* @param {Object} standardOpts [Optional] Standard module optional overrides
*/
export function trader(ship, shielded, standardOpts) {
let usedSlots = [],
sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
// Shield generator if required
if (shielded) {
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class)
.sort((a,b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
for (let i = 0; i < shieldInternals.length; i++) {
if (canMount(ship, shieldInternals[i], 'sg')) {
ship.use(shieldInternals[i], sg);
usedSlots.push(shieldInternals[i]);
break;
let usedSlots = [];
let bstCount = 2;
let sg = ship.getAvailableModules().lightestShieldGenerator(ship.hullMass);
ship.useStandard('A')
.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[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
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class)
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
shieldInternals.some(function(slot) {
if (canMount(ship, slot, 'sg')) { // Assuming largest slot can hold an eligible shield
const shield = ModuleUtils.findInternal('sg', slot.maxClass, 'A');
if (shield && shield.maxmass > ship.hullMass) {
ship.use(slot, shield);
ship.setSlotEnabled(slot, true);
usedSlots.push(slot);
return true;
}
}
}
});
// Fill the empty internals with cargo racks
for (let i = ship.internal.length; i--;) {
@@ -62,8 +69,15 @@ export function trader(ship, shielded, standardOpts) {
for (let s of ship.hardpoints) {
ship.use(s, null);
}
ship.useLightestStandard(standardOpts);
for (let s of ship.hardpoints) {
if (s.maxClass == 0 && bstCount) { // Mount up to 2 boosters
ship.use(s, ModuleUtils.hardpoints('04'));
bstCount--;
} else {
ship.use(s, null);
}
}
// ship.useLightestStandard(standardOpts);
}
/**
@@ -89,8 +103,8 @@ export function explorer(ship, planetary) {
// Advanced Discovery Scanner - class 1 or higher
const adsOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const adsInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sc)
.sort((a,b) => adsOrder.indexOf(a.maxClass) - adsOrder.indexOf(b.maxClass));
.filter(a => (!a.eligible) || a.eligible.sc)
.sort((a, b) => adsOrder.indexOf(a.maxClass) - adsOrder.indexOf(b.maxClass));
for (let i = 0; i < adsInternals.length; i++) {
if (canMount(ship, adsInternals[i], 'sc')) {
ship.use(adsInternals[i], ModuleUtils.internal('2f'));
@@ -103,8 +117,8 @@ export function explorer(ship, planetary) {
// Planetary Vehicle Hangar - class 2 or higher
const pvhOrder = [2, 3, 4, 5, 6, 7, 8, 1];
const pvhInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.pv)
.sort((a,b) => pvhOrder.indexOf(a.maxClass) - pvhOrder.indexOf(b.maxClass));
.filter(a => (!a.eligible) || a.eligible.pv)
.sort((a, b) => pvhOrder.indexOf(a.maxClass) - pvhOrder.indexOf(b.maxClass));
for (let i = 0; i < pvhInternals.length; i++) {
if (canMount(ship, pvhInternals[i], 'pv')) {
// Planetary Vehical Hangar only has even classes
@@ -120,9 +134,9 @@ export function explorer(ship, planetary) {
// Shield generator
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class)
.sort((a,b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
.filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class)
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
for (let i = 0; i < shieldInternals.length; i++) {
if (canMount(ship, shieldInternals[i], 'sg')) {
ship.use(shieldInternals[i], sg);
@@ -135,8 +149,8 @@ export function explorer(ship, planetary) {
// Detailed Surface Scanner
const dssOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const dssInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sc)
.sort((a,b) => dssOrder.indexOf(a.maxClass) - dssOrder.indexOf(b.maxClass));
.filter(a => (!a.eligible) || a.eligible.sc)
.sort((a, b) => dssOrder.indexOf(a.maxClass) - dssOrder.indexOf(b.maxClass));
for (let i = 0; i < dssInternals.length; i++) {
if (canMount(ship, dssInternals[i], 'sc')) {
ship.use(dssInternals[i], ModuleUtils.internal('2i'));
@@ -148,8 +162,8 @@ export function explorer(ship, planetary) {
// Fuel scoop - best possible
const fuelScoopOrder = [8, 7, 6, 5, 4, 3, 2, 1];
const fuelScoopInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.fs)
.sort((a,b) => fuelScoopOrder.indexOf(a.maxClass) - fuelScoopOrder.indexOf(b.maxClass));
.filter(a => (!a.eligible) || a.eligible.fs)
.sort((a, b) => fuelScoopOrder.indexOf(a.maxClass) - fuelScoopOrder.indexOf(b.maxClass));
for (let i = 0; i < fuelScoopInternals.length; i++) {
if (canMount(ship, fuelScoopInternals[i], 'fs')) {
ship.use(fuelScoopInternals[i], ModuleUtils.findInternal('fs', fuelScoopInternals[i].maxClass, 'A'));
@@ -162,8 +176,8 @@ export function explorer(ship, planetary) {
// AFMUs - fill as they are 0-weight
const afmuOrder = [8, 7, 6, 5, 4, 3, 2, 1];
const afmuInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.pc)
.sort((a,b) => afmuOrder.indexOf(a.maxClass) - afmuOrder.indexOf(b.maxClass));
.filter(a => (!a.eligible) || a.eligible.pc)
.sort((a, b) => afmuOrder.indexOf(a.maxClass) - afmuOrder.indexOf(b.maxClass));
for (let i = 0; i < afmuInternals.length; i++) {
if (canMount(ship, afmuInternals[i], 'am')) {
ship.use(afmuInternals[i], ModuleUtils.findInternal('am', afmuInternals[i].maxClass, 'A'));
@@ -200,6 +214,7 @@ export function explorer(ship, planetary) {
* @param {Boolean} shielded True if shield generator should be included
*/
export function miner(ship, shielded) {
shielded = true;
let standardOpts = { ppRating: 'A' },
miningLaserCount = 2,
usedSlots = [],
@@ -211,8 +226,8 @@ export function miner(ship, shielded) {
// Largest possible refinery
const refineryOrder = [4, 5, 6, 7, 8, 3, 2, 1];
const refineryInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.rf)
.sort((a,b) => refineryOrder.indexOf(a.maxClass) - refineryOrder.indexOf(b.maxClass));
.filter(a => (!a.eligible) || a.eligible.rf)
.sort((a, b) => refineryOrder.indexOf(a.maxClass) - refineryOrder.indexOf(b.maxClass));
for (let i = 0; i < refineryInternals.length; i++) {
if (canMount(ship, refineryInternals[i], 'rf')) {
ship.use(refineryInternals[i], ModuleUtils.findInternal('rf', Math.min(refineryInternals[i].maxClass, 4), 'A'));
@@ -224,8 +239,8 @@ export function miner(ship, shielded) {
// Prospector limpet controller - 3A if possible
const prospectorOrder = [3, 4, 5, 6, 7, 8, 2, 1];
const prospectorInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.pc)
.sort((a,b) => prospectorOrder.indexOf(a.maxClass) - prospectorOrder.indexOf(b.maxClass));
.filter(a => (!a.eligible) || a.eligible.pc)
.sort((a, b) => prospectorOrder.indexOf(a.maxClass) - prospectorOrder.indexOf(b.maxClass));
for (let i = 0; i < prospectorInternals.length; i++) {
if (canMount(ship, prospectorInternals[i], 'pc')) {
// Prospector only has odd classes
@@ -240,9 +255,9 @@ export function miner(ship, shielded) {
if (shielded) {
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class)
.sort((a,b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
.filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class)
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
for (let i = 0; i < shieldInternals.length; i++) {
if (canMount(ship, shieldInternals[i], 'sg')) {
ship.use(shieldInternals[i], sg);
@@ -252,27 +267,9 @@ export function miner(ship, shielded) {
}
}
// Collector limpet controller if there are enough internals left
let collectorLimpetsRequired = Math.max(ship.internal.filter(a => (!a.eligible) || a.eligible.cr).length - 6, 0);
if (collectorLimpetsRequired > 0) {
const collectorOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const collectorInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.cc)
.sort((a,b) => collectorOrder.indexOf(a.maxClass) - collectorOrder.indexOf(b.maxClass));
for (let i = 0; i < collectorInternals.length && collectorLimpetsRequired > 0; i++) {
if (canMount(ship, collectorInternals[i], 'cc')) {
// Collector only has odd classes
const collectorClass = collectorInternals[i].maxClass % 2 === 0 ? collectorInternals[i].maxClass - 1 : collectorInternals[i].maxClass;
ship.use(collectorInternals[i], ModuleUtils.findInternal('cc', collectorClass, 'A'));
usedSlots.push(collectorInternals[i]);
collectorLimpetsRequired -= collectorInternals[i].m.maximum;
}
}
}
// Dual mining lasers of highest possible class; remove anything else
const miningLaserOrder = [2, 3, 4, 1, 0];
const miningLaserHardpoints = ship.hardpoints.concat().sort(function(a,b) {
const miningLaserHardpoints = ship.hardpoints.concat().sort(function(a, b) {
return miningLaserOrder.indexOf(a.maxClass) - miningLaserOrder.indexOf(b.maxClass);
});
for (let s of miningLaserHardpoints) {
@@ -284,6 +281,44 @@ export function miner(ship, shielded) {
}
}
// Number of collector limpets required to be active is a function of the size of the ship and the power of the lasers
const miningLaserDps = ship.hardpoints.filter(h => h.m != null)
.reduce(function(a, b) {
return a + b.m.getDps();
}, 0);
// Find out how many internal slots we have, and their potential cargo size
const potentialCargo = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.cr)
.map(b => Math.pow(2, b.maxClass));
// One collector for each 1.25 DPS, multiply by 1.25 for medium ships and 1.5 for large ships as they have further to travel
// 0 if we only have 1 cargo slot, otherwise minium of 1 and maximum of 6 (excluding size modifier)
const sizeModifier = ship.class == 2 ? 1.2 : ship.class == 3 ? 1.5 : 1;
let collectorLimpetsRequired = potentialCargo.length == 1 ? 0 : Math.ceil(sizeModifier * Math.min(6, Math.floor(miningLaserDps / 1.25)));
if (collectorLimpetsRequired > 0) {
const collectorOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const collectorInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.cc)
.sort((a, b) => collectorOrder.indexOf(a.maxClass) - collectorOrder.indexOf(b.maxClass));
// Always keep at least 2 slots free for cargo racks (1 for shielded)
for (let i = 0; i < collectorInternals.length - (shielded ? 1 : 2) && collectorLimpetsRequired > 0; i++) {
if (canMount(ship, collectorInternals[i], 'cc')) {
// Collector only has odd classes
const collectorClass = collectorInternals[i].maxClass % 2 === 0 ? collectorInternals[i].maxClass - 1 : collectorInternals[i].maxClass;
ship.use(collectorInternals[i], ModuleUtils.findInternal('cc', collectorClass, 'D'));
usedSlots.push(collectorInternals[i]);
collectorLimpetsRequired -= collectorInternals[i].m.maximum;
}
}
}
// Power distributor to power the mining lasers indefinitely
const wepRateRequired = ship.hardpoints.filter(h => h.m != null)
.reduce(function(a, b) {
return a + b.m.getEps();
}, 0);
standardOpts.pd = ship.getAvailableModules().matchingPowerDist({ weprate: wepRateRequired }).id;
// Fill the empty internals with cargo racks
for (let i = ship.internal.length; i--;) {
let slot = ship.internal[i];
@@ -311,9 +346,9 @@ export function racer(ship) {
// Shield generator
const shieldOrder = [1, 2, 3, 4, 5, 6, 7, 8];
const shieldInternals = ship.internal.filter(a => usedSlots.indexOf(a) == -1)
.filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class)
.sort((a,b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
.filter(a => (!a.eligible) || a.eligible.sg)
.filter(a => a.maxClass >= sg.class)
.sort((a, b) => shieldOrder.indexOf(a.maxClass) - shieldOrder.indexOf(b.maxClass));
for (let i = 0; i < shieldInternals.length; i++) {
if (canMount(ship, shieldInternals[i], 'sg')) {
ship.use(shieldInternals[i], sg);
@@ -378,4 +413,3 @@ export function racer(ship) {
// ship.standard[5].m.blueprint.grade = 5;
// setBest(ship, ship.standard[5].m);
}

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_LANG = 'NG_TRANSLATE_LANG_KEY';
const LS_KEY_COST_TAB = 'costTab';
const LS_KEY_CMDR_NAME = 'cmdrName';
const LS_KEY_OUTFITTING_TAB = 'outfittingTab';
const LS_KEY_INSURANCE = 'insurance';
const LS_KEY_SHIP_DISCOUNT = 'shipDiscount';
@@ -13,6 +14,8 @@ const LS_KEY_STATE = 'state';
const LS_KEY_SIZE_RATIO = 'sizeRatio';
const LS_KEY_TOOLTIPS = 'tooltips';
const LS_KEY_MODULE_RESISTANCES = 'moduleResistances';
const LS_KEY_ROLLS = 'matsPerGrade';
const LS_KEY_ORBIS = 'orbis';
let LS;
@@ -84,6 +87,8 @@ export class Persist extends EventEmitter {
}
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 insurance = _getString(LS_KEY_INSURANCE);
let shipDiscount = _get(LS_KEY_SHIP_DISCOUNT);
@@ -91,6 +96,7 @@ export class Persist extends EventEmitter {
let buildJson = _get(LS_KEY_BUILDS);
let comparisonJson = _get(LS_KEY_COMPARISONS);
this.orbisCreds = _get(LS_KEY_ORBIS) || { email: '', password: '' };
this.onStorageChange = this.onStorageChange.bind(this);
this.langCode = _getString(LS_KEY_LANG) || 'en';
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.state = _get(LS_KEY_STATE);
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.moduleResistancesEnabled = moduleResistances === null ? true : moduleResistances;
@@ -152,6 +166,14 @@ export class Persist extends EventEmitter {
this.moduleResistancesEnabled = !!newValue && newValue.toLowerCase() == 'true';
this.emit('moduleresistances', this.moduleResistancesEnabled);
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) {
// On JSON.Parse Error - don't sync or do anything
@@ -177,6 +199,24 @@ export class Persist extends EventEmitter {
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
* @param {boolean} show Optional - update setting
@@ -457,6 +497,31 @@ export class Persist extends EventEmitter {
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
* @param {number} tabName Cost tab name
@@ -466,6 +531,16 @@ export class Persist extends EventEmitter {
_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
* @return {number} val Discount value/amount

View File

@@ -1,366 +1,417 @@
import React from 'react';
import { Modifications } from 'coriolis-data/dist';
/**
* Generate a tooltip with details of a blueprint's effects
* @param {Object} translate The translate object
* @param {Object} blueprint The blueprint at the required grade
* @param {Array} engineers The engineers supplying this blueprint
* @param {string} grp The group of the module
* @param {Object} m The module to compare with
* @returns {Object} The react components
*/
export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
const effects = [];
for (const feature in blueprint.features) {
const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]);
const featureDef = Modifications.modifications[feature];
if (!featureDef.hidden) {
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let lowerBound = blueprint.features[feature][0];
let upperBound = blueprint.features[feature][1];
if (featureDef.type === 'percentage') {
lowerBound = Math.round(lowerBound * 1000) / 10;
upperBound = Math.round(upperBound * 1000) / 10;
}
const lowerIsBeneficial = isValueBeneficial(feature, lowerBound);
const upperIsBeneficial = isValueBeneficial(feature, upperBound);
if (m) {
// We have a module - add in the current value
let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
</tr>
);
} else {
// We do not have a module, no value
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
</tr>
);
}
}
}
if (m) {
// Because we have a module add in any benefits that aren't part of the primary blueprint
for (const feature in m.mods) {
if (!blueprint.features[feature]) {
const featureDef = Modifications.modifications[feature];
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td>
</tr>
);
}
}
// We also add in any benefits from specials that aren't covered above
if (m.blueprint && m.blueprint.special) {
for (const feature in Modifications.modifierActions[m.blueprint.special.edname]) {
if (!blueprint.features[feature] && !m.mods.feature) {
const featureDef = Modifications.modifications[feature];
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td>
</tr>
);
}
}
}
}
let components;
if (!m) {
components = [];
for (const component in blueprint.components) {
components.push(
<tr key={component}>
<td style={{ textAlign: 'left' }}>{translate(component)}</td>
<td style={{ textAlign: 'right' }}>{blueprint.components[component]}</td>
</tr>
);
}
}
let engineersList;
if (engineers) {
engineersList = [];
for (const engineer of engineers) {
engineersList.push(
<tr key={engineer}>
<td style={{ textAlign: 'left' }}>{engineer}</td>
</tr>
);
}
}
return (
<div>
<table width='100%'>
<thead>
<tr>
<td>{translate('feature')}</td>
<td>{translate('worst')}</td>
{m ? <td>{translate('current')}</td> : null }
<td>{translate('best')}</td>
</tr>
</thead>
<tbody>
{effects}
</tbody>
</table>
{ components ? <table width='100%'>
<thead>
<tr>
<td>{translate('component')}</td>
<td>{translate('amount')}</td>
</tr>
</thead>
<tbody>
{components}
</tbody>
</table> : null }
{ engineersList ? <table width='100%'>
<thead>
<tr>
<td>{translate('engineers')}</td>
</tr>
</thead>
<tbody>
{engineersList}
</tbody>
</table> : null }
</div>
);
}
/**
* Is this blueprint feature beneficial?
* @param {string} feature The name of the feature
* @param {array} values The value of the feature
* @returns {boolean} True if this feature is beneficial
*/
export function isBeneficial(feature, values) {
const fact = (values[0] < 0 || (values[0] === 0 && values[1] < 0));
if (Modifications.modifications[feature].higherbetter) {
return !fact;
} else {
return fact;
}
}
/**
* Is this feature value beneficial?
* @param {string} feature The name of the feature
* @param {number} value The value of the feature
* @returns {boolean} True if this value is beneficial
*/
export function isValueBeneficial(feature, value) {
if (Modifications.modifications[feature].higherbetter) {
return value > 0;
} else {
return value < 0;
}
}
/**
* Get a blueprint with a given name and an optional module
* @param {string} name The name of the blueprint
* @param {Object} module The module for which to obtain this blueprint
* @returns {Object} The matching blueprint
*/
export function getBlueprint(name, module) {
// Start with a copy of the blueprint
const blueprint = JSON.parse(JSON.stringify(Modifications.blueprints[name]));
if (module) {
if (module.grp === 'bh' || module.grp === 'hr' || module.grp === 'sg' || module.grp === 'psg' || module.grp === 'bsg') {
// Bulkheads, hull reinforcements and shield generators need to have their resistances altered by the base values
for (const grade in blueprint.grades) {
for (const feature in blueprint.grades[grade].features) {
if (feature === 'explres') {
blueprint.grades[grade].features[feature][0] *= (1 - module.explres);
blueprint.grades[grade].features[feature][1] *= (1 - module.explres);
}
if (feature === 'kinres') {
blueprint.grades[grade].features[feature][0] *= (1 - module.kinres);
blueprint.grades[grade].features[feature][1] *= (1 - module.kinres);
}
if (feature === 'thermres') {
blueprint.grades[grade].features[feature][0] *= (1 - module.thermres);
blueprint.grades[grade].features[feature][1] *= (1 - module.thermres);
}
}
}
}
if (module.grp === 'sb') {
// Shield boosters are treated internally as straight modifiers, so rather than (for example)
// being a 4% boost they are a 104% multiplier. We need to fix the values here so that they look
// accurate as per the information in Elite
for (const grade in blueprint.grades) {
for (const feature in blueprint.grades[grade].features) {
if (feature === 'shieldboost') {
blueprint.grades[grade].features[feature][0] = ((1 + blueprint.grades[grade].features[feature][0]) * (1 + module.shieldboost) - 1) / module.shieldboost - 1;
blueprint.grades[grade].features[feature][1] = ((1 + blueprint.grades[grade].features[feature][1]) * (1 + module.shieldboost) - 1) / module.shieldboost - 1;
}
}
}
}
}
return blueprint;
}
/**
* Provide 'worst' primary modifications
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
*/
export function setWorst(ship, m) {
ship.clearModifications(m);
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
const value = features[featureName][0];
_setValue(ship, m, featureName, value);
}
}
/**
* Provide 'best' primary modifications
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
*/
export function setBest(ship, m) {
ship.clearModifications(m);
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
const value = features[featureName][1];
_setValue(ship, m, featureName, value);
}
}
/**
* Provide 'extreme' primary modifications
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
*/
export function setExtreme(ship, m) {
ship.clearModifications(m);
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
let value;
if (Modifications.modifications[featureName].higherbetter) {
// Higher is better, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
value = features[featureName][0];
} else {
value = features[featureName][1];
}
} else {
// Higher is worse, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
value = features[featureName][1];
} else {
value = features[featureName][0];
}
}
_setValue(ship, m, featureName, value);
}
}
/**
* Provide 'random' primary modifications
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
*/
export function setRandom(ship, m) {
ship.clearModifications(m);
// Pick a single value for our randomness
const mult = Math.random();
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
let value;
if (Modifications.modifications[featureName].higherbetter) {
// Higher is better, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
} else {
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
}
} else {
// Higher is worse, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
} else {
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
}
}
_setValue(ship, m, featureName, value);
}
}
/**
* Set a modification feature value
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
* @param {string} featureName The feature being set
* @param {number} value The value being set for the feature
*/
function _setValue(ship, m, featureName, value) {
if (Modifications.modifications[featureName].type == 'percentage') {
ship.setModification(m, featureName, value * 10000);
} else if (Modifications.modifications[featureName].type == 'numeric') {
ship.setModification(m, featureName, value * 100);
} else {
ship.setModification(m, featureName, value);
}
}
import React from 'react';
import { Modifications } from 'coriolis-data/dist';
/**
* Generate a tooltip with details of a blueprint's specials
* @param {Object} translate The translate object
* @param {Object} blueprint The blueprint at the required grade
* @param {string} grp The group of the module
* @param {Object} m The module to compare with
* @param {string} specialName The name of the special
* @returns {Object} The react components
*/
export function specialToolTip(translate, blueprint, grp, m, specialName) {
const effects = [];
if (!blueprint || !blueprint.features) {
return undefined;
}
if (m) {
// We also add in any benefits from specials that aren't covered above
if (m.blueprint) {
for (const feature in Modifications.modifierActions[specialName]) {
// if (!blueprint.features[feature] && !m.mods.feature) {
const featureDef = Modifications.modifications[feature];
if (featureDef && !featureDef.hidden) {
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let current = m.getModValue(feature) - m.getModValue(feature, true);
if (featureDef.type === 'percentage') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature + '_specialTT'}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'}
style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td>
</tr>
);
}
}
}
}
return (
<div>
<table width='100%'>
<tbody>
{effects}
</tbody>
</table>
</div>
);
}
/**
* Generate a tooltip with details of a blueprint's effects
* @param {Object} translate The translate object
* @param {Object} blueprint The blueprint at the required grade
* @param {Array} engineers The engineers supplying this blueprint
* @param {string} grp The group of the module
* @param {Object} m The module to compare with
* @returns {Object} The react components
*/
export function blueprintTooltip(translate, blueprint, engineers, grp, m) {
const effects = [];
if (!blueprint || !blueprint.features) {
return undefined;
}
for (const feature in blueprint.features) {
const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]);
const featureDef = Modifications.modifications[feature];
if (!featureDef.hidden) {
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let lowerBound = blueprint.features[feature][0];
let upperBound = blueprint.features[feature][1];
if (featureDef.type === 'percentage') {
lowerBound = Math.round(lowerBound * 1000) / 10;
upperBound = Math.round(upperBound * 1000) / 10;
}
const lowerIsBeneficial = isValueBeneficial(feature, lowerBound);
const upperIsBeneficial = isValueBeneficial(feature, upperBound);
if (m) {
// We have a module - add in the current value
let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
</tr>
);
} else {
// We do not have a module, no value
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td className={lowerBound === 0 ? '' : lowerIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{lowerBound}{symbol}</td>
<td className={upperBound === 0 ? '' : upperIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{upperBound}{symbol}</td>
</tr>
);
}
}
}
if (m) {
// Because we have a module add in any benefits that aren't part of the primary blueprint
for (const feature in m.mods) {
if (!blueprint.features[feature]) {
const featureDef = Modifications.modifications[feature];
if (featureDef && !featureDef.hidden) {
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td>
</tr>
);
}
}
}
// We also add in any benefits from specials that aren't covered above
if (m.blueprint && m.blueprint.special) {
for (const feature in Modifications.modifierActions[m.blueprint.special.edname]) {
if (!blueprint.features[feature] && !m.mods.feature) {
const featureDef = Modifications.modifications[feature];
if (featureDef && !featureDef.hidden) {
let symbol = '';
if (feature === 'jitter') {
symbol = '°';
} else if (featureDef.type === 'percentage') {
symbol = '%';
}
let current = m.getModValue(feature);
if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') {
current = Math.round(current / 10) / 10;
} else if (featureDef.type === 'numeric') {
current /= 100;
}
const currentIsBeneficial = isValueBeneficial(feature, current);
effects.push(
<tr key={feature}>
<td style={{ textAlign: 'left' }}>{translate(feature, grp)}</td>
<td>&nbsp;</td>
<td className={current === 0 ? '' : currentIsBeneficial ? 'secondary' : 'warning'} style={{ textAlign: 'right' }}>{current}{symbol}</td>
<td>&nbsp;</td>
</tr>
);
}
}
}
}
}
let components;
if (!m) {
components = [];
for (const component in blueprint.components) {
components.push(
<tr key={component}>
<td style={{ textAlign: 'left' }}>{translate(component)}</td>
<td style={{ textAlign: 'right' }}>{blueprint.components[component]}</td>
</tr>
);
}
}
let engineersList;
if (engineers) {
engineersList = [];
for (const engineer of engineers) {
engineersList.push(
<tr key={engineer}>
<td style={{ textAlign: 'left' }}>{engineer}</td>
</tr>
);
}
}
return (
<div>
<table width='100%'>
<thead>
<tr>
<td>{translate('feature')}</td>
<td>{translate('worst')}</td>
{m ? <td>{translate('current')}</td> : null }
<td>{translate('best')}</td>
</tr>
</thead>
<tbody>
{effects}
</tbody>
</table>
{ components ? <table width='100%'>
<thead>
<tr>
<td>{translate('component')}</td>
<td>{translate('amount')}</td>
</tr>
</thead>
<tbody>
{components}
</tbody>
</table> : null }
{ engineersList ? <table width='100%'>
<thead>
<tr>
<td>{translate('engineers')}</td>
</tr>
</thead>
<tbody>
{engineersList}
</tbody>
</table> : null }
</div>
);
}
/**
* Is this blueprint feature beneficial?
* @param {string} feature The name of the feature
* @param {array} values The value of the feature
* @returns {boolean} True if this feature is beneficial
*/
export function isBeneficial(feature, values) {
const fact = (values[0] < 0 || (values[0] === 0 && values[1] < 0));
if (Modifications.modifications[feature].higherbetter) {
return !fact;
} else {
return fact;
}
}
/**
* Is this feature value beneficial?
* @param {string} feature The name of the feature
* @param {number} value The value of the feature
* @returns {boolean} True if this value is beneficial
*/
export function isValueBeneficial(feature, value) {
if (Modifications.modifications[feature].higherbetter) {
return value > 0;
} else {
return value < 0;
}
}
/**
* Get a blueprint with a given name and an optional module
* @param {string} name The name of the blueprint
* @param {Object} module The module for which to obtain this blueprint
* @returns {Object} The matching blueprint
*/
export function getBlueprint(name, module) {
// Start with a copy of the blueprint
const 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)];
if (!found || !found.fdname) {
return {};
}
const blueprint = JSON.parse(JSON.stringify(found));
return blueprint;
}
/**
* Provide 'percent' primary modifications
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
* @param {Number} percent The percent to set values to of full.
*/
export function setPercent(ship, m, percent) {
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;
if (Modifications.modifications[featureName].higherbetter) {
// Higher is better, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
} else {
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
}
} else {
// Higher is worse, but is this making it better or worse?
if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) {
value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * mult);
} else {
value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * mult);
}
}
_setValue(ship, m, featureName, value);
}
}
/**
* Provide 'random' primary modifications
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
*/
export function setRandom(ship, m) {
// Pick a single value for our randomness
setPercent(ship, m, Math.random() * 100);
}
/**
* Set a modification feature value
* @param {Object} ship The ship for which to perform the modifications
* @param {Object} m The module for which to perform the modifications
* @param {string} featureName The feature being set
* @param {number} value The value being set for the feature
*/
function _setValue(ship, m, featureName, value) {
if (Modifications.modifications[featureName].type == 'percentage') {
ship.setModification(m, featureName, value * 10000);
} else if (Modifications.modifications[featureName].type == 'numeric') {
ship.setModification(m, featureName, value * 100);
} else {
ship.setModification(m, featureName, value);
}
}
/**
* Provide 'percent' primary query
* @param {Object} m The module for which to perform the query
* @returns {Number} percent The percentage indicator of current applied values.
*/
export function getPercent(m) {
let result = null;
const features = m.blueprint.grades[m.blueprint.grade].features;
for (const featureName in features) {
if (features[featureName][0] === features[featureName][1]) {
continue;
}
let value = _getValue(m, featureName);
let mult;
if (featureName == 'shieldboost') {
mult = ((1 + value) * (1 + m.shieldboost)) - 1 - m.shieldboost;
} else 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)) {
mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100);
} else {
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?
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);
}
}
if (result && result != mult) {
return null;
} else if (result != mult) {
result = mult;
}
}
return result;
}
/**
* 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 %
*/
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';
// 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',
'Anaconda': 'anaconda',
'Asp': 'asp',
@@ -29,19 +29,24 @@ const SHIP_FD_NAME_TO_CORIOLIS_NAME = {
'FerDeLance': 'fer_de_lance',
'Hauler': 'hauler',
'Independant_Trader': 'keelback',
'Krait_MkII': 'krait_mkii',
'Orca': 'orca',
'Python': 'python',
'SideWinder': 'sidewinder',
'Type6': 'type_6_transporter',
'Type7': 'type_7_transport',
'Type9': 'type_9_heavy',
'Type9_Military': 'type_10_defender',
'TypeX': 'alliance_chieftain',
'TypeX_2': 'alliance_crusader',
'TypeX_3': 'alliance_challenger',
'Viper': 'viper',
'Viper_MkIV': 'viper_mk_iv',
'Vulture': 'vulture'
};
// Mapping from hardpoint class to name in companion API
const HARDPOINT_NUM_TO_CLASS = {
export const HARDPOINT_NUM_TO_CLASS = {
0: 'Tiny',
1: 'Small',
2: 'Medium',
@@ -104,7 +109,7 @@ function _moduleFromEdId(edId) {
* @return {string} the Coriolis model of the ship
*/
function _shipModelFromEDName(edName) {
return SHIP_FD_NAME_TO_CORIOLIS_NAME[edName];
return SHIP_FD_NAME_TO_CORIOLIS_NAME[Object.keys(SHIP_FD_NAME_TO_CORIOLIS_NAME).find(elem => elem.toLowerCase() === edName.toLowerCase())];
}
/**
@@ -113,7 +118,7 @@ function _shipModelFromEDName(edName) {
* @return {string} the Coriolis model of the ship
*/
export function shipModelFromJson(json) {
return _shipModelFromEDName(json.name);
return _shipModelFromEDName(json.name || json.Ship);
}
/**
@@ -141,30 +146,34 @@ export function shipFromJson(json) {
ship.cargoHatch.enabled = false;
ship.cargoHatch.priority = 4;
}
let rootModule;
// Add the bulkheads
const armourJson = json.modules.Armour.module;
if (armourJson.name.endsWith('_Armour_Grade1')) {
if (armourJson.name.toLowerCase().endsWith('_armour_grade1')) {
ship.useBulkhead(0, true);
} else if (armourJson.name.endsWith('_Armour_Grade2')) {
} else if (armourJson.name.toLowerCase().endsWith('_armour_grade2')) {
ship.useBulkhead(1, true);
} else if (armourJson.name.endsWith('_Armour_Grade3')) {
} else if (armourJson.name.toLowerCase().endsWith('_armour_grade3')) {
ship.useBulkhead(2, true);
} else if (armourJson.name.endsWith('_Armour_Mirrored')) {
} else if (armourJson.name.toLowerCase().endsWith('_armour_mirrored')) {
ship.useBulkhead(3, true);
} else if (armourJson.name.endsWith('_Armour_Reactive')) {
} else if (armourJson.name.toLowerCase().endsWith('_armour_reactive')) {
ship.useBulkhead(4, true);
} else {
throw 'Unknown bulkheads "' + armourJson.name + '"';
}
ship.bulkheads.enabled = true;
if (armourJson.modifiers) _addModifications(ship.bulkheads.m, armourJson.modifiers, armourJson.recipeName, armourJson.recipeLevel);
rootModule = json.modules.Armour;
if (rootModule.WorkInProgress_modifications) _addModifications(ship.bulkheads.m, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel);
// Add the standard modules
// Power plant
const powerplantJson = json.modules.PowerPlant.module;
const powerplant = _moduleFromEdId(powerplantJson.id);
if (powerplantJson.modifiers) _addModifications(powerplant, powerplantJson.modifiers, powerplantJson.recipeName, powerplantJson.recipeLevel);
rootModule = json.modules.PowerPlant;
if (rootModule.WorkInProgress_modifications) _addModifications(powerplant, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel);
ship.use(ship.standard[0], powerplant, true);
ship.standard[0].enabled = powerplantJson.on === true;
ship.standard[0].priority = powerplantJson.priority;
@@ -172,7 +181,8 @@ export function shipFromJson(json) {
// Thrusters
const thrustersJson = json.modules.MainEngines.module;
const thrusters = _moduleFromEdId(thrustersJson.id);
if (thrustersJson.modifiers) _addModifications(thrusters, thrustersJson.modifiers, thrustersJson.recipeName, thrustersJson.recipeLevel);
rootModule = json.modules.MainEngines;
if (rootModule.WorkInProgress_modifications) _addModifications(thrusters, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel);
ship.use(ship.standard[1], thrusters, true);
ship.standard[1].enabled = thrustersJson.on === true;
ship.standard[1].priority = thrustersJson.priority;
@@ -180,7 +190,8 @@ export function shipFromJson(json) {
// FSD
const frameshiftdriveJson = json.modules.FrameShiftDrive.module;
const frameshiftdrive = _moduleFromEdId(frameshiftdriveJson.id);
if (frameshiftdriveJson.modifiers) _addModifications(frameshiftdrive, frameshiftdriveJson.modifiers, frameshiftdriveJson.recipeName, frameshiftdriveJson.recipeLevel);
rootModule = json.modules.FrameShiftDrive;
if (rootModule.WorkInProgress_modifications) _addModifications(frameshiftdrive, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel);
ship.use(ship.standard[2], frameshiftdrive, true);
ship.standard[2].enabled = frameshiftdriveJson.on === true;
ship.standard[2].priority = frameshiftdriveJson.priority;
@@ -188,7 +199,8 @@ export function shipFromJson(json) {
// Life support
const lifesupportJson = json.modules.LifeSupport.module;
const lifesupport = _moduleFromEdId(lifesupportJson.id);
if (lifesupportJson.modifiers)_addModifications(lifesupport, lifesupportJson.modifiers, lifesupportJson.recipeName, lifesupportJson.recipeLevel);
rootModule = json.modules.LifeSupport;
if (rootModule.WorkInProgress_modifications) _addModifications(lifesupport, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel);
ship.use(ship.standard[3], lifesupport, true);
ship.standard[3].enabled = lifesupportJson.on === true;
ship.standard[3].priority = lifesupportJson.priority;
@@ -196,7 +208,8 @@ export function shipFromJson(json) {
// Power distributor
const powerdistributorJson = json.modules.PowerDistributor.module;
const powerdistributor = _moduleFromEdId(powerdistributorJson.id);
if (powerdistributorJson.modifiers) _addModifications(powerdistributor, powerdistributorJson.modifiers, powerdistributorJson.recipeName, powerdistributorJson.recipeLevel);
rootModule = json.modules.PowerDistributor;
if (rootModule.WorkInProgress_modifications) _addModifications(powerdistributor, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel);
ship.use(ship.standard[4], powerdistributor, true);
ship.standard[4].enabled = powerdistributorJson.on === true;
ship.standard[4].priority = powerdistributorJson.priority;
@@ -204,7 +217,8 @@ export function shipFromJson(json) {
// Sensors
const sensorsJson = json.modules.Radar.module;
const sensors = _moduleFromEdId(sensorsJson.id);
if (sensorsJson.modifiers) _addModifications(sensors, sensorsJson.modifiers, sensorsJson.recipeName, sensorsJson.recipeLevel);
rootModule = json.modules.Radar;
if (rootModule.WorkInProgress_modifications) _addModifications(sensors, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel);
ship.use(ship.standard[5], sensors, true);
ship.standard[5].enabled = sensorsJson.on === true;
ship.standard[5].priority = sensorsJson.priority;
@@ -240,7 +254,8 @@ export function shipFromJson(json) {
} else {
const hardpointJson = hardpointSlot.module;
const hardpoint = _moduleFromEdId(hardpointJson.id);
if (hardpointJson.modifiers) _addModifications(hardpoint, hardpointJson.modifiers, hardpointJson.recipeName, hardpointJson.recipeLevel);
rootModule = hardpointSlot;
if (rootModule.WorkInProgress_modifications) _addModifications(hardpoint, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel, rootModule.specialModifications);
ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true);
ship.hardpoints[hardpointArrayNum].enabled = hardpointJson.on === true;
ship.hardpoints[hardpointArrayNum].priority = hardpointJson.priority;
@@ -282,7 +297,8 @@ export function shipFromJson(json) {
} else {
const internalJson = internalSlot.module;
const internal = _moduleFromEdId(internalJson.id);
if (internalJson.modifiers) _addModifications(internal, internalJson.modifiers, internalJson.recipeName, internalJson.recipeLevel);
rootModule = internalSlot;
if (rootModule.WorkInProgress_modifications) _addModifications(internal, rootModule.WorkInProgress_modifications, rootModule.engineer.recipeName, rootModule.engineer.recipeLevel);
ship.use(ship.internal[i], internal, true);
ship.internal[i].enabled = internalJson.on === true;
ship.internal[i].priority = internalJson.priority;
@@ -299,35 +315,48 @@ export function shipFromJson(json) {
* @param {Object} modifiers the modifiers
* @param {Object} blueprint the blueprint of the modification
* @param {Object} grade the grade of the modification
* @param {Object} specialModifications special modification
*/
function _addModifications(module, modifiers, blueprint, grade) {
if (!modifiers || !modifiers.modifiers) return;
function _addModifications(module, modifiers, blueprint, grade, specialModifications) {
if (!modifiers) return;
let special;
for (const i in modifiers.modifiers) {
if (specialModifications) {
special = Modifications.specials[Object.keys(specialModifications)[0]];
}
for (const i in modifiers) {
// Some special modifications
if (modifiers.modifiers[i].name === 'mod_weapon_clip_size_override') {
if (modifiers[i].name === 'mod_weapon_clip_size_override') {
// This is a numeric addition to the clip size, but we need to work it out in terms of being a percentage so
// that it works the same as other modifications
const origClip = module.clip || 1;
module.setModValue('clip', ((modifiers.modifiers[i].value - origClip) / origClip) * 10000);
} else if (modifiers.modifiers[i].name === 'mod_weapon_burst_size') {
module.setModValue('clip', ((modifiers[i].value - origClip) / origClip) * 10000);
} else if (modifiers[i].name === 'mod_weapon_burst_size') {
// This is an absolute number that acts as an override
module.setModValue('burst', modifiers.modifiers[i].value * 100);
} else if (modifiers.modifiers[i].name === 'mod_weapon_burst_rof') {
module.setModValue('burst', modifiers[i].value * 100);
} else if (modifiers[i].name === 'mod_weapon_burst_rof') {
// This is an absolute number that acts as an override
module.setModValue('burstrof', modifiers.modifiers[i].value * 100);
} else if (modifiers.modifiers[i].name === 'mod_weapon_falloffrange_from_range') {
module.setModValue('burstrof', modifiers[i].value * 100);
} else if (modifiers[i].name === 'mod_weapon_falloffrange_from_range') {
// Obtain the falloff value directly from the range
module.setModValue('fallofffromrange', 1);
} else if (modifiers.modifiers[i].name && modifiers.modifiers[i].name.startsWith('special_')) {
} else if (modifiers[i].name && modifiers[i].name.startsWith('special_')) {
// We don't add special effects directly, but keep a note of them so they can be added when fetching values
special = Modifications.specials[modifiers.modifiers[i].name];
special = Modifications.specials[modifiers[i].name];
} else {
// Look up the modifiers to find what we need to do
const modifierActions = Modifications.modifierActions[modifiers.modifiers[i].name];
const value = modifiers.modifiers[i].value;
const modifierActions = Modifications.modifierActions[i];
let value;
if (i === 'OutfittingFieldType_DefenceModifierShieldMultiplier') {
value = modifiers[i].value - 1;
} else if (i === 'OutfittingFieldType_DefenceModifierHealthMultiplier' && blueprint.startsWith('Armour_')) {
value = (modifiers[i].value - module.hullboost) / module.hullboost;
} else if (i === 'OutfittingFieldType_DefenceModifierHealthMultiplier') {
value = modifiers[i].value / module.hullboost;
} else if (i === 'OutfittingFieldType_RateOfFire') {
value = (1 / Math.abs(modifiers[i].value));
} else {
value = modifiers[i].value - 1;
}
// Carry out the required changes
for (const action in modifierActions) {
if (isNaN(modifierActions[action])) {
@@ -354,7 +383,7 @@ function _addModifications(module, modifiers, blueprint, grade) {
module.blueprint.special = special;
}
}
// Need to fix up a few items
// Shield boosters are treated internally as straight modifiers, so rather than (for example)
@@ -377,7 +406,7 @@ function _addModifications(module, modifiers, blueprint, grade) {
module.setModValue('thermres', ((module.getModValue('thermres') / 10000) * -1) * 10000);
}
}
// Shield generator resistance is actually a damage modifier, so needs to be inverted.
// In addition, the modification is based off the inherent resistance of the module
if (ModuleUtils.isShieldGenerator(module.grp)) {
@@ -405,7 +434,7 @@ function _addModifications(module, modifiers, blueprint, grade) {
module.setModValue('thermres', ((1 - (1 - module.thermres) * (1 + module.getModValue('thermres') / 10000)) - module.thermres) * 10000);
}
}
// Bulkhead resistance is actually a damage modifier, so needs to be inverted.
// In addition, the modification is based off the inherent resistance of the module
if (module.grp == 'bh') {
@@ -423,7 +452,7 @@ function _addModifications(module, modifiers, blueprint, grade) {
// Bulkhead boost is based off the inherent boost of the module
if (module.grp == 'bh') {
const alteredBoost = (1 + module.hullboost) * (1 + module.getModValue('hullboost') / 10000) - 1;
module.setModValue('hullboost', (alteredBoost / module.hullboost - 1) * 10000);
module.setModValue('hullboost', (alteredBoost / module.hullboost - 1) * 1000);
}
// Jitter is an absolute number, so we need to divide it by 100
@@ -431,11 +460,6 @@ function _addModifications(module, modifiers, blueprint, grade) {
module.setModValue('jitter', module.getModValue('jitter') / 100);
}
// FD uses interval between bursts internally, so we need to translate this to a real rate of fire
if (module.getModValue('rof')) {
module.setModValue('rof', ((1 / (1 + module.getModValue('rof') / 10000)) - 1) * 10000);
}
// Clip size is rounded up so that the result is a whole number
if (module.getModValue('clip')) {
const individual = 1 / (module.clip || 1);

View File

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

@@ -220,7 +220,7 @@ export function diffDetails(language, m, mm) {
let mCost = m.cost || 0;
let mmCost = mm ? mm.cost : 0;
if (mCost != mmCost) propDiffs.push(<div key='cost'>{translate('cost')}: <span className={diffClass(mCost, mmCost, true) }>{mCost ? Math.round(mCost * (1 - Persist.getModuleDiscount())) : 0}{units.CR}</span></div>);
if (mCost != mmCost) propDiffs.push(<div key='cost'>{translate('cost')}: <span className={diffClass(mCost, mmCost, true) }>{formats.int(mCost ? Math.round(mCost * (1 - Persist.getModuleDiscount())) : 0)}{units.CR}</span></div>);
let mMass = m.mass || 0;
let mmMass = mm ? mm.getMass() : 0;

6
src/images/Empire.svg Normal file
View File

@@ -0,0 +1,6 @@
<svg width="1718.2px" height="999.8px" viewBox="0 0 1718.2 999.8" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs/>
<g>
<path stroke="none" fill="#FF8C0D" d="M1717.55 -0.05 L1717.65 -0.25 1718.2 -0.1 1718.6 0.35 1719.2 1.55 1719.3 2.25 1718.95 2.85 1709.75 11.35 1010.8 687.7 1008.05 690.5 1005.45 693.4 1003 696.35 1000.75 699.45 998.65 702.65 996.7 705.95 994.9 709.4 993.25 713.05 934.9 850.7 876.4 988.25 871.55 998.95 871.3 999.3 870.9 999.5 870.6 999.55 870 999.4 869.5 999.05 868.3 997.95 867.35 996.7 866.6 995.25 864.7 989.85 762.3 730.55 754.8 711.85 751.1 702.45 746.6 690.65 745.5 688.4 744.25 686.3 742.9 684.35 741.4 682.45 739.75 680.6 737.95 678.85 736 677.15 8.1 61.2 5.75 59.15 3.65 57.1 1.6 54.6 1.1 53.7 1.1 53.25 1.25 52.9 1.5 52.65 1.8 52.45 2.15 52.4 2.55 52.5 9.75 55.7 713.05 398.5 780.95 431.7 848.75 464.95 851.7 466.25 854.4 467.15 856.95 467.6 859.45 467.65 861.9 467.25 864.4 466.45 866.95 465.2 869.6 463.5 1416.95 79.15 1419.7 77.35 1422.45 75.7 1425.25 74.2 1428.1 72.85 1431 71.6 1434 70.5 1437.05 69.55 1440.2 68.7 1574.85 35.15 1709.5 1.5 1713.75 0.6 1717.55 -0.05 M903.9 129.8 L904 129.65 930.9 129.75 934.3 129.85 937.55 130.15 940.65 130.65 943.6 131.35 946.4 132.2 949.2 133.3 951.95 134.6 954.55 136.05 957 137.7 959.35 139.5 961.6 141.5 963.75 143.65 965.8 145.95 967.75 148.4 969.6 151 971.35 153.8 974.25 159 976.8 164.3 979.05 169.7 981 175.2 982.7 180.8 984.2 186.45 985.55 192.15 986.75 197.9 987.85 203.75 988.8 209.65 990.4 221.45 991.6 233.25 992.55 245.1 995 280.65 996 292.5 996.2 295.1 996.25 297.55 996.15 299.85 995.9 302 995.5 304.1 994.95 306.1 994.25 308 993.45 309.7 992.4 311.5 991.25 313.15 989.95 314.75 988.45 316.3 986.8 317.8 985 319.25 980.85 322.15 946.35 344.45 912.05 367.15 843.7 413.05 841.45 414.45 839.25 415.55 836.9 416.35 834.85 416.75 832.6 416.85 830.15 416.5 827.75 415.8 825.25 414.7 812.45 408.55 799.4 402.55 773.35 390.75 773 390.5 772.8 390.15 772.75 389.75 772.85 389.35 794.5 343.45 801.35 328.15 811 305.8 820.45 283.4 839 238.45 841 233 842.7 227.35 844.1 221.65 845.25 215.95 845.7 213.05 845.8 210.65 845.55 208.55 845.3 207.75 844.9 206.9 843.85 205.55 842.3 204.4 840.2 203.4 837.55 202.55 830.2 200.55 822.85 198.85 819.15 198.15 815.5 197.6 811.85 197.2 808.2 196.95 804.55 196.9 800.9 197.05 797.25 197.4 793.6 198 789.9 198.9 786.25 200.05 782.6 201.55 778.55 203.5 777 203.65 776.65 203.6 776.1 203.15 775.95 202.8 775.5 200.55 775.25 197.65 775.3 194.75 775.65 191.9 776.25 189.05 777.05 186.3 778 183.6 779.05 180.9 780.5 177.65 782.1 174.5 783.8 171.5 785.65 168.6 787.6 165.85 789.7 163.2 791.9 160.7 794.25 158.3 796.7 156 799.3 153.85 802 151.8 804.8 149.9 807.7 148.1 810.75 146.4 813.9 144.85 817.15 143.4 824.4 140.6 831.75 138.2 839.15 136.15 846.65 134.45 854.2 133.05 861.8 131.95 869.45 131.1 877.1 130.5 883.8 130.15 890.5 129.95 903.9 129.8"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 30 KiB

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="631.072px" height="713.591px" viewBox="0 0 631.072 713.591" enable-background="new 0 0 631.072 713.591"
xml:space="preserve">
<g id="Layer_2">
<g>
<rect x="460.344" y="668.509" transform="matrix(0.7399 0.6727 -0.6727 0.7399 586.8318 -155.8918)" fill="#FF8C0D" width="69.376" height="24.999"/>
</g>
<g>
<rect x="499.816" y="630.511" transform="matrix(0.8742 0.4856 -0.4856 0.8742 379.9255 -180.1477)" fill="#FF8C0D" width="75.501" height="24.999"/>
</g>
<g>
<rect x="537.078" y="590.363" transform="matrix(0.9477 0.319 -0.319 0.9477 222.2531 -151.1744)" fill="#FF8C0D" width="71.047" height="25"/>
</g>
<g>
<rect x="561.364" y="544.916" transform="matrix(0.9741 0.2263 -0.2263 0.9741 141.5892 -120.2454)" fill="#FF8C0D" width="67.758" height="25"/>
</g>
<g>
<rect x="113.454" y="633.977" transform="matrix(0.6727 0.7399 -0.7399 0.6727 536.0002 125.674)" fill="#FF8C0D" width="24.999" height="69.375"/>
</g>
<g>
<rect x="72.787" y="594.581" transform="matrix(0.4857 0.8741 -0.8741 0.4857 596.6155 250.6765)" fill="#FF8C0D" width="25" height="75.501"/>
</g>
<g>
<rect x="43.454" y="554.475" transform="matrix(0.319 0.9477 -0.9477 0.319 597.2715 348.7429)" fill="#FF8C0D" width="25" height="71.046"/>
</g>
<g>
<rect x="23.329" y="507.12" transform="matrix(0.2263 0.9741 -0.9741 0.2263 554.6862 383.6775)" fill="#FF8C0D" width="25" height="67.758"/>
</g>
<path fill="#FF8C0D" d="M556.439,425.748c-13.466-24.188-32.583-45.79-56.821-64.205c-30.562-23.22-67.327-39.855-107.331-48.986
v25.737c93.31,23.32,160.681,90.053,160.681,168.752c0,98.096-104.668,177.619-233.785,177.619
c-129.115,0-233.784-79.523-233.784-177.619c0-75.918,62.696-140.699,150.889-166.118v-25.964
c-36.209,9.508-69.489,25.266-97.539,46.578c-24.238,18.415-43.355,40.017-56.821,64.205
c-14.286,25.66-21.529,53.014-21.529,81.299c0,28.284,7.243,55.637,21.529,81.298c13.465,24.188,32.583,45.789,56.821,64.204
c48.479,36.832,112.559,57.117,180.435,57.117c67.876,0,131.956-20.285,180.436-57.117c24.238-18.415,43.355-40.017,56.821-64.204
c14.285-25.661,21.528-53.014,21.528-81.298C577.968,478.761,570.725,451.408,556.439,425.748z"/>
<path fill="#FF8C0D" d="M473.97,427.082c-8.958-16.091-21.621-30.42-37.64-42.591c-13.129-9.975-27.996-18.079-44.043-24.152
v27.053c42.591,19.056,71.178,54.248,71.178,94.519c0,60.541-64.597,109.619-144.282,109.619
c-79.684,0-144.281-49.078-144.281-109.619c0-37.101,24.263-69.892,61.386-89.727v-27.819c-12.323,5.48-23.835,12.212-34.25,20.125
c-16.019,12.171-28.683,26.5-37.64,42.591c-9.618,17.276-14.495,35.724-14.495,54.829c0,19.104,4.877,37.552,14.495,54.829
c8.958,16.091,21.622,30.42,37.64,42.59c31.574,23.989,73.178,37.2,117.146,37.2c43.969,0,85.572-13.211,117.147-37.2
c16.019-12.17,28.682-26.499,37.64-42.59c9.618-17.277,14.495-35.725,14.495-54.829
C488.465,462.805,483.588,444.358,473.97,427.082z"/>
</g>
<g id="Layer_3">
<path fill="#FF8C0D" d="M253.551,232.333c-0.157-0.981-0.264-1.978-0.264-3c0-10.844,9.173-19.667,20.448-19.667
s20.448,8.822,20.448,19.667c0,1.022-0.106,2.019-0.264,3h25.15c0.067-0.993,0.113-1.991,0.113-3
c0-24.668-20.348-44.667-45.448-44.667s-45.448,19.998-45.448,44.667c0,1.009,0.046,2.007,0.113,3H253.551z"/>
<path fill="#FF8C0D" d="M302.831,48c-0.136-0.669-0.21-1.337-0.21-2c0-9.913,15.539-21,36.333-21s36.333,11.087,36.333,21
c0,0.663-0.074,1.331-0.21,2h25.143c0.038-0.664,0.067-1.329,0.067-2c0-25.405-27.46-46-61.333-46
c-33.874,0-61.333,20.595-61.333,46c0,0.671,0.03,1.336,0.067,2H302.831z"/>
<ellipse fill="#FF8C0D" cx="338.954" cy="68.499" rx="36.333" ry="6.75"/>
<path fill="#FF8C0D" d="M375.287,46c0,2.208-16.267,3.999-36.333,3.999S302.621,48.208,302.621,46h-25v158.499h25v-136h72.667
V473.51c-4.165,3.092-16.463,8.535-36.333,8.535c-7.763,0-14.353-0.836-19.771-2.033l-10.749,23.051
c8.99,2.529,19.405,3.982,30.52,3.982c33.873,0,61.333-13.432,61.333-30V46H375.287z"/>
<ellipse fill="#FF8C0D" cx="273.735" cy="253.132" rx="20.448" ry="3.916"/>
<path fill="#FF8C0D" d="M294.184,229.333c0,1.151-9.155,2.084-20.448,2.084s-20.448-0.933-20.448-2.084h-25v257.666h0.128
c1.667,14.529,21.313,26,45.32,26s43.653-11.471,45.32-26h0.128V229.333H294.184z M294.184,254.333v228.201
c-2.473,2.008-9.464,5.465-20.448,5.465s-17.975-3.456-20.448-5.465V253.132h40.896V254.333z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html <%= htmlWebpackPlugin.options.appCache ? 'manifest=/' + htmlWebpackPlugin.options.appCache : '' %> >
<html manifest="/">
<head>
<title>Coriolis EDCD Edition</title>
<meta charset="UTF-8">
@@ -26,7 +26,8 @@
<script>
window.CORIOLIS_GAPI_KEY = '<%- htmlWebpackPlugin.options.gapiKey %>';
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>
<% if (htmlWebpackPlugin.options.uaTracking) { %>
<script>
@@ -36,6 +37,31 @@
</script>
<script async src='https://www.google-analytics.com/analytics.js'></script>
<% } %>
<!-- Piwik -->
<!-- <script type="text/javascript">
var _paq = _paq || [];
/* tracker methods like "setCustomDimension" should be called before "trackPageView" */
_paq.push(["setCookieDomain", "*.coriolis.edcd.io"]);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);
(function() {
var u="//stats.isadankme.me/";
_paq.push(['setTrackerUrl', u+'piwik.php']);
_paq.push(['setSiteId', '4']);
var d=document, g=d.createElement('script'), s=d.getElementsByTagName('script')[0];
g.type='text/javascript'; g.async=true; g.defer=true; g.src=u+'piwik.js'; s.parentNode.insertBefore(g,s);
})();
</script>-->
<!-- End Piwik Code -->
<!-- Bugsnag -->
<script src="//d2wy8f7a9ursnm.cloudfront.net/v4/bugsnag.min.js"></script>
<script src="//d2wy8f7a9ursnm.cloudfront.net/bugsnag-plugins/v1/bugsnag-react.min.js"></script>
<script>
window.bugsnagClient = bugsnag('ba9fae819372850fb660755341fa6ef5', {appVersion: window.BUGSNAG_VERSION || undefined})
window.Bugsnag = window.bugsnagClient
</script>
</head>
<body style="background-color:#000;">
<section id="coriolis"></section>

View File

@@ -31,6 +31,50 @@ button {
}
}
.button-inline-menu {
white-space: nowrap;
line-height: 1.5em;
text-align: center;
margin: 0.5em 0;
padding-left: 5px;
border-top: 1px solid @primary-disabled;
border-bottom: 1px solid @primary-disabled;
overflow: hidden;
text-overflow: ellipsis;
background: @primary-bg;
&.warning {
border-color: @warning-disabled;
color: @warning-disabled;
stroke: @warning-disabled;
.no-touch &:hover {
border-color: @warning;
color: @warning;
stroke: @warning;
}
}
&.disabled, &.disabled:hover {
cursor: not-allowed;
border-color: @disabled;
color: @disabled;
stroke: @disabled;
}
&.active {
border-color: @secondary;
color: @secondary;
stroke: @secondary;
}
&:hover {
border-color: @primary;
color: @primary;
stroke: @primary;
}
}
.button-lbl {
margin-left: 0.5em;

View File

@@ -10,6 +10,8 @@
@secondary: #1FB0FF; // Light blue
@warning: #FF3B00; // Dark Orange
@disabled: #555; // Light grey
@success: #71a052; // Green
@purple: #800080; // Purple
@primary-disabled: darken(@primary, @disabledDarken);
@secondary-disabled: darken(@secondary, @disabledDarken);
@warning-disabled: darken(@warning, @disabledDarken);
@@ -18,57 +20,68 @@
@bg: rgba(30,30,30,1);
@bgBlack: #000;
@primary-bg: fadeout(darken(@primary, 47%), 15%);
@alt-primary-bg: fadeout(darken(@primary, 42%), 15%); // Lighter brown background
@secondary-bg: fadeout(darken(@secondary, @bgDarken), @bgTransparency); // Brown background
@warning-bg: fadeout(darken(@warning, @bgDarken), @bgTransparency); // Dark Red
.fg {
color: @fg;
stroke: @fg;
fill: @fg;
}
.muted {
color: @muted;
stroke: @muted;
fill: @muted;
}
.disabled {
color: @disabled;
stroke: @disabled;
fill: @disabled;
}
.primary {
color: @primary;
stroke: @primary;
fill: @primary;
}
.primary-bg {
color: @primary-bg;
stroke: @primary-bg;
fill: @primary-bg;
}
.primary-disabled {
color: @primary-disabled;
stroke: @primary-disabled;
fill: @primary-disabled;
}
.secondary {
color: @secondary;
stroke: @secondary;
fill: @secondary;
}
.secondary-disabled {
color: @secondary-disabled;
stroke: @secondary-disabled;
fill: @secondary-disabled;
}
.warning {
color: @warning;
stroke: @warning;
fill: @warning;
}
.warning-disabled {
color: @warning-disabled;
stroke: @warning-disabled;
fill: @warning-disabled;
}

View File

@@ -12,6 +12,20 @@
cursor: pointer;
}
.view-changes {
position: fixed;
top: 3em;
left: 0;
right: 0;
height: 3em;
z-index: 3;
line-height: 3em;
text-align: center;
background-color: @bg;
color: @warning;
cursor: pointer;
}
header {
background-color: @bg;
margin: 0;

View File

@@ -27,13 +27,12 @@
}
}
// Modifiction icons - hard-code stroke/fill
// Modifiction icons - hard-code fill
.modicon {
display: inline-block;
vertical-align: middle;
width: 1.1em;
height: 1em;
stoke: @fg;
stroke-width: 20;
fill: transparent;
@@ -61,6 +60,9 @@
.summary {
stroke: @fg;
stroke-width: 10;
fill: @fg;
svg {
stroke-width: 10;
}
}

View File

@@ -54,6 +54,7 @@ textarea {
width:100%;
min-height: 10em;
resize: vertical;
user-select: auto;
margin:2em 0;
}
}

View File

@@ -21,13 +21,47 @@
#overview {
h1 {
margin: 0;
margin: 0 1em 0.2em 0;
float: left;
}
overflow: hidden;
margin: 0.5em;
}
#requirements {
margin: 0;
float: left;
text-align: left;
div {
display: inline-block;
margin: 0 1em 0.5em 0;
height: 1.6em;
line-height: 1.6em;
text-align: left;
vertical-align: middle;
cursor: default;
background-repeat: no-repeat;
background-position: left center;
background-size: contain;
}
.federation {
background-image: url('../images/Federation.svg');
padding-left: 1.985em + 0.5em;
}
.empire {
background-image: url('../images/Empire.svg');
padding-left: 2.750em + 0.5em;
}
.horizons {
background-image: url('../images/surface_port_pm.svg');
padding-left: 1.415em + 0.5em;
}
}
#summary {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
@@ -36,10 +70,32 @@
padding: 0.5em 0.2em;
font-size: 0.9em;
#summaryTable {
.summaryTable {
.user-select-none();
width: 100%;
border-collapse: collapse;
& > thead.blue {
background-color: @secondary;
border-left: 1px solid @primary-bg;
color: @primary-bg;
}
& > thead.green {
background-color: @success;
border-left: 1px solid @primary-bg;
color: @primary-bg;
}
& > thead.purple {
background-color: @purple;
border-left: 1px solid @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 {
color: @primary-disabled;
position: absolute;
@@ -88,7 +93,6 @@ select {
cursor: pointer;
line-height:@optionSpacing;
color: @primary-disabled;
stroke-width: 0.5em;
stroke: @primary-disabled;
.no-touch &:hover {
@@ -96,6 +100,10 @@ select {
color: @primary;
stroke: @primary;
}
svg {
stroke-width: 0.5em;
}
}
.lc, .c {

View File

@@ -13,7 +13,6 @@
border: 1px solid @primary-disabled;
color: @fg;
stroke: @fg;
stroke-width: 20;
fill: @fg;
.details-container {
@@ -38,10 +37,48 @@
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 {
overflow: hidden;
}
input.cb:focus {
border-color:#fff;
}
input:disabled {
border-color: #888;
color: #888;
}
.l {
text-transform: capitalize;
margin-right: 0.8em;
@@ -57,11 +94,13 @@
width: 1.2em;
color: @primary-disabled;
stroke: @primary-disabled;
stroke-width: 20;
border-right: 1px solid @primary-disabled;
box-sizing: border-box;
padding-top: 0.2em;
padding-left: 0.05em;
svg {
stroke-width: 20;
}
}
.empty {
@@ -69,16 +108,17 @@
font-size: 1.3em;
color: lighten(@primary-bg, 12%);
stroke: lighten(@primary-bg, 12%);
stroke-width: 20;
text-align: center;
letter-spacing: 0.1em;
line-height: 1.7em;
svg {
stroke-width: 20;
}
}
&.selected {
color: @primary-bg;
stroke: @primary-bg;
stroke-width: 20;
fill: @primary-bg;
background-color: @primary;
border: 1px solid @primary;
@@ -86,13 +126,18 @@
.sz {
color: @primary;
stroke: @primary;
stroke-width: 20;
background-color: @primary-bg;
border-right: 1px solid @primary;
svg {
stroke-width: 20;
}
}
.details {
background-color: transparent;
}
svg {
stroke-width: 20;
}
}
&.eligible {
@@ -144,4 +189,8 @@
background-color: transparent;
}
}
svg {
stroke-width: 20;
}
}

View File

@@ -54,6 +54,10 @@ tbody tr {
.no-touch &.highlight:hover, .no-touch &.highlighted {
background-color: @warning-bg;
}
&.alt {
background-color: @alt-primary-bg;
}
}
td {

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 😬');
}

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