mirror of
https://github.com/EDCD/coriolis.git
synced 2025-12-08 22:33:24 +00:00
Compare commits
887 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25c30e46a6 | ||
|
|
870c033c64 | ||
|
|
46b0200b28 | ||
|
|
e8bcb18f12 | ||
|
|
68ca24ef96 | ||
|
|
7ca2754934 | ||
|
|
e82ab0aec0 | ||
|
|
d406018f0b | ||
|
|
2438aa1f48 | ||
|
|
c5bbeacc6a | ||
|
|
d9763f2db7 | ||
|
|
5692cc6fe3 | ||
|
|
22cdfcdecf | ||
|
|
3db3b09c22 | ||
|
|
23c264e321 | ||
|
|
d2c3787165 | ||
|
|
1e18d6e463 | ||
|
|
044fea2d33 | ||
|
|
bc865f534b | ||
|
|
796694a4e0 | ||
|
|
829815a40b | ||
|
|
7a2c849ece | ||
|
|
873dfaa305 | ||
|
|
7050356bce | ||
|
|
88c9bb0254 | ||
|
|
45f1dd2da9 | ||
|
|
46bcc2313f | ||
|
|
9e012c1490 | ||
|
|
50f9c0faa1 | ||
|
|
74829a09c0 | ||
|
|
45508ba2d4 | ||
|
|
624adf2b64 | ||
|
|
821daefeb8 | ||
|
|
86b95981f1 | ||
|
|
688eebb9ea | ||
|
|
d719da2cde | ||
|
|
19c1851e14 | ||
|
|
414bf4cb20 | ||
|
|
c674459376 | ||
|
|
fd009fe567 | ||
|
|
e28eccb6fb | ||
|
|
301c97db58 | ||
|
|
2eed1bc85b | ||
|
|
3469af10b6 | ||
|
|
629ba35bc5 | ||
|
|
c19ca6648d | ||
|
|
e453ff73b7 | ||
|
|
cfdb92ecc6 | ||
|
|
de5ca7b5e6 | ||
|
|
c73ce1c234 | ||
|
|
8676deba7d | ||
|
|
d3ce8d4f7c | ||
|
|
00d3a93b91 | ||
|
|
d4e612cb61 | ||
|
|
0c3de95025 | ||
|
|
83f1f9aa2e | ||
|
|
dee14a5dee | ||
|
|
db13da95db | ||
|
|
cb08b10a63 | ||
|
|
189eb2b726 | ||
|
|
b9abf784f4 | ||
|
|
39287bc5d7 | ||
|
|
c07cfc6e70 | ||
|
|
c4c6d32a5d | ||
|
|
a65bb06754 | ||
|
|
23548e7c5c | ||
|
|
74e6f54e19 | ||
|
|
a46f8f97f6 | ||
|
|
44dbdb1703 | ||
|
|
bcdd0c6044 | ||
|
|
f70455ce26 | ||
|
|
888f807a7b | ||
|
|
5040141096 | ||
|
|
46ba782911 | ||
|
|
524e204e01 | ||
|
|
a9753828c1 | ||
|
|
6d30a54925 | ||
|
|
7c58eb1cde | ||
|
|
4001e1e9ac | ||
|
|
0da00d38a4 | ||
|
|
1db6fe1a34 | ||
|
|
10b8bb95a9 | ||
|
|
8d9581900f | ||
|
|
187c5dae4a | ||
|
|
07d324a3fa | ||
|
|
5c63afd96c | ||
|
|
53c40ac9c4 | ||
|
|
a180cbfdd4 | ||
|
|
2bb55d2c36 | ||
|
|
fc1524a943 | ||
|
|
c3747e4e5e | ||
|
|
ab153981c9 | ||
|
|
a970e052c1 | ||
|
|
df7e264a02 | ||
|
|
cdcda004f3 | ||
|
|
49c827b2c8 | ||
|
|
20e448fc0a | ||
|
|
436e626c42 | ||
|
|
3dd4675a0b | ||
|
|
d3766d9e17 | ||
|
|
d987c08ac8 | ||
|
|
cf50537e3d | ||
|
|
f865ef6c6c | ||
|
|
f2b7daac82 | ||
|
|
832bc488b6 | ||
|
|
f513166d6c | ||
|
|
61fd0eb991 | ||
|
|
1e51e7d4a6 | ||
|
|
4943d36bb8 | ||
|
|
9271d1fa09 | ||
|
|
8a09d94dfa | ||
|
|
c44925dd62 | ||
|
|
d006bbcb0f | ||
|
|
14453f6b80 | ||
|
|
804466f88a | ||
|
|
bded793374 | ||
|
|
fc918d893c | ||
|
|
5fe13b26a4 | ||
|
|
be1393994e | ||
|
|
dc6db31d43 | ||
|
|
8c267150a9 | ||
|
|
ed60a78be0 | ||
|
|
82142b0cb1 | ||
|
|
d8949fedb2 | ||
|
|
cf72bd11a8 | ||
|
|
840ce9f3e4 | ||
|
|
9674aa2367 | ||
|
|
d19b6b107f | ||
|
|
1b96c18ecb | ||
|
|
0f43c4d7eb | ||
|
|
4c70806a5a | ||
|
|
7de304bdbe | ||
|
|
34f9f28c16 | ||
|
|
16ef7ea389 | ||
|
|
ff455e349e | ||
|
|
ba9e7f1a32 | ||
|
|
904498b20c | ||
|
|
13ec027772 | ||
|
|
409be7374c | ||
|
|
00c525e6ab | ||
|
|
3966f92454 | ||
|
|
38f72438dd | ||
|
|
0179382379 | ||
|
|
7f5c652f49 | ||
|
|
1f9b1e5d27 | ||
|
|
ebf4491901 | ||
|
|
d322a47592 | ||
|
|
06a58d22cb | ||
|
|
25d4520eee | ||
|
|
0087062468 | ||
|
|
14bb49a2bc | ||
|
|
ab671b0af5 | ||
|
|
304ddf9ea8 | ||
|
|
b3f320e69f | ||
|
|
3a63e08f80 | ||
|
|
a3feb42fd7 | ||
|
|
a77d991cf9 | ||
|
|
9ebe5dc786 | ||
|
|
baace95f83 | ||
|
|
fc5db94f9a | ||
|
|
c3b0e8d949 | ||
|
|
1b8c460876 | ||
|
|
a2f52c03a1 | ||
|
|
037df6b166 | ||
|
|
90ab5b4b0a | ||
|
|
7bbfa8c43f | ||
|
|
2fbcd158cc | ||
|
|
33c201800e | ||
|
|
9797a8d781 | ||
|
|
67409a613b | ||
|
|
e4a826592f | ||
|
|
cee4c32551 | ||
|
|
081d8fb86a | ||
|
|
3dfd563d90 | ||
|
|
fd08cd219c | ||
|
|
6a15326d31 | ||
|
|
608ecc51b7 | ||
|
|
fcef26ebbb | ||
|
|
ba6d758ed5 | ||
|
|
43aa3e4e79 | ||
|
|
18f0e060a7 | ||
|
|
c7547e8baf | ||
|
|
ffff242abe | ||
|
|
b44c66b986 | ||
|
|
ae77ec6256 | ||
|
|
4f1e32b154 | ||
|
|
af37c2bfc5 | ||
|
|
5d4ab6f2ad | ||
|
|
0c9db53057 | ||
|
|
b689605ac2 | ||
|
|
baab91e371 | ||
|
|
70e69c7099 | ||
|
|
f4534fd3eb | ||
|
|
93594e1a65 | ||
|
|
b5e449ea54 | ||
|
|
0ff4b849aa | ||
|
|
b99e38043f | ||
|
|
28e3a59473 | ||
|
|
b20290fb60 | ||
|
|
2734beb6f8 | ||
|
|
345eec528c | ||
|
|
7a17e18a76 | ||
|
|
4697677457 | ||
|
|
7d8a5a1368 | ||
|
|
dd7402bd0e | ||
|
|
65592b0fc6 | ||
|
|
0ab66023a6 | ||
|
|
d6fad098ee | ||
|
|
1b5730d337 | ||
|
|
439b615b1b | ||
|
|
a8b30594dc | ||
|
|
9b6b1d328c | ||
|
|
ac2e2e4d69 | ||
|
|
3a5fb31860 | ||
|
|
c610eb8627 | ||
|
|
94980270c4 | ||
|
|
c685e002e3 | ||
|
|
1f665eed9e | ||
|
|
0c4fc1fd9a | ||
|
|
0fc033363e | ||
|
|
fb6e9538bc | ||
|
|
95b98fc4ed | ||
|
|
1840dafed0 | ||
|
|
1ad82b116c | ||
|
|
7bdd17504b | ||
|
|
2d820bb5d5 | ||
|
|
2e14512ed8 | ||
|
|
48ed583c6d | ||
|
|
dd444a17f3 | ||
|
|
2ea63c711e | ||
|
|
6d6d31db25 | ||
|
|
e9273dcb9b | ||
|
|
2bdc4562c6 | ||
|
|
9e8a5323e9 | ||
|
|
8e001063b3 | ||
|
|
dc88fab4c5 | ||
|
|
dfca917e50 | ||
|
|
ef7dfd6ca1 | ||
|
|
435c1b6d45 | ||
|
|
c5c9abe588 | ||
|
|
363735d36b | ||
|
|
2741e7701b | ||
|
|
3be442ea60 | ||
|
|
34cbeca201 | ||
|
|
b37e73ead6 | ||
|
|
ee775521d6 | ||
|
|
5f84aaef1b | ||
|
|
99ac58d999 | ||
|
|
f128a1e87d | ||
|
|
8c0768b451 | ||
|
|
319307136c | ||
|
|
a498452943 | ||
|
|
4b854b8305 | ||
|
|
b400db8216 | ||
|
|
fb811faf5e | ||
|
|
deeb525433 | ||
|
|
1cb88115f6 | ||
|
|
a181791500 | ||
|
|
94eec120da | ||
|
|
48092d4395 | ||
|
|
2457c30b94 | ||
|
|
593f069806 | ||
|
|
a073692632 | ||
|
|
7752d5c9db | ||
|
|
544e5acaef | ||
|
|
9ab35bbaf9 | ||
|
|
98782da200 | ||
|
|
2936364934 | ||
|
|
01e1609a9f | ||
|
|
f85a03a9ae | ||
|
|
2703c2aa23 | ||
|
|
954921c231 | ||
|
|
8bed35a8ba | ||
|
|
9f4ae60577 | ||
|
|
ee3c50e27d | ||
|
|
03020743b3 | ||
|
|
001fed67b7 | ||
|
|
3894915740 | ||
|
|
68fd13e8dc | ||
|
|
fdf16cd959 | ||
|
|
d916c67fe0 | ||
|
|
d8a8e224f4 | ||
|
|
e1c115747c | ||
|
|
e9b6d71606 | ||
|
|
e03e249d2f | ||
|
|
0cfb0b6878 | ||
|
|
600df162aa | ||
|
|
94141aa3c5 | ||
|
|
aca90d7077 | ||
|
|
a66fa8e83f | ||
|
|
194db07057 | ||
|
|
307886d4ae | ||
|
|
bbba048129 | ||
|
|
222173b388 | ||
|
|
ec0d05e081 | ||
|
|
b3be0bd639 | ||
|
|
529d80682c | ||
|
|
934de01803 | ||
|
|
3367580d78 | ||
|
|
fbf59219d0 | ||
|
|
77401a3b3f | ||
|
|
7c587c29aa | ||
|
|
2295dccd82 | ||
|
|
cc4ad6d132 | ||
|
|
8a2d27290a | ||
|
|
eda61a8e06 | ||
|
|
3987c4e681 | ||
|
|
71b90eb6f4 | ||
|
|
4e891f382c | ||
|
|
3d3f9e44b5 | ||
|
|
dd7a133caa | ||
|
|
ae247c4812 | ||
|
|
6c932f96a6 | ||
|
|
0ea25692d3 | ||
|
|
e129e1da39 | ||
|
|
8acd32b0fc | ||
|
|
8e5dd9fb8d | ||
|
|
97ce2828e0 | ||
|
|
f8f99a5aaa | ||
|
|
70cfa58896 | ||
|
|
6e79ced51e | ||
|
|
56571f9c1f | ||
|
|
0b10cac85c | ||
|
|
34c04a6354 | ||
|
|
4e337c4ca1 | ||
|
|
5048b7e094 | ||
|
|
27fbc1ad66 | ||
|
|
4ab376d9ed | ||
|
|
dfffc3a268 | ||
|
|
b59fa15e00 | ||
|
|
12bca4c44e | ||
|
|
2858ef3e93 | ||
|
|
7d99471f89 | ||
|
|
a2ab708ac9 | ||
|
|
a34a9c355f | ||
|
|
3215b3942d | ||
|
|
557c0afd9b | ||
|
|
d52365a204 | ||
|
|
14b2a14e58 | ||
|
|
7f24904f77 | ||
|
|
da07790594 | ||
|
|
5008c7cd74 | ||
|
|
a778b1b6e1 | ||
|
|
bd9771f9ba | ||
|
|
600c244f9b | ||
|
|
a599b1a076 | ||
|
|
3e0a5e22b1 | ||
|
|
3a6ac818c2 | ||
|
|
6f077d4c41 | ||
|
|
9c767c928c | ||
|
|
515f4ad3da | ||
|
|
4fcf074595 | ||
|
|
e5f8153a34 | ||
|
|
571854a11c | ||
|
|
1f22f249a1 | ||
|
|
718ac0a514 | ||
|
|
8f089cb1ee | ||
|
|
d19a7276dd | ||
|
|
10fffe67fc | ||
|
|
f0bf8e8ce2 | ||
|
|
598cf8d677 | ||
|
|
90f03de3fe | ||
|
|
e0766f4424 | ||
|
|
28a90768e4 | ||
|
|
f3d917ccbe | ||
|
|
7e5d52385d | ||
|
|
4368015dc0 | ||
|
|
1201da1811 | ||
|
|
d195b568b0 | ||
|
|
c9866c146b | ||
|
|
5d52809d0d | ||
|
|
8f0cca4fd9 | ||
|
|
e46bb425fe | ||
|
|
06dc110025 | ||
|
|
e9c34c636a | ||
|
|
59d38cbd33 | ||
|
|
51f5188efc | ||
|
|
be8934da80 | ||
|
|
18d78b3089 | ||
|
|
b1ff4e84f7 | ||
|
|
bed2ede701 | ||
|
|
124bd62d2c | ||
|
|
975846f4ab | ||
|
|
3f73f9be10 | ||
|
|
d218101708 | ||
|
|
608ce12156 | ||
|
|
80d653483a | ||
|
|
f9a1e5afd9 | ||
|
|
7f5181a9c7 | ||
|
|
93d8f15f26 | ||
|
|
06d8930777 | ||
|
|
23f4024e09 | ||
|
|
a5e9a71037 | ||
|
|
423d42bc8a | ||
|
|
4075bab3d0 | ||
|
|
469f7a0a48 | ||
|
|
25d9f8ec61 | ||
|
|
1151bd1614 | ||
|
|
2456ce330b | ||
|
|
f86ce62c27 | ||
|
|
c295a9f4e4 | ||
|
|
c4186faa4a | ||
|
|
00f4889c93 | ||
|
|
b6c83f4aba | ||
|
|
3fa040c210 | ||
|
|
8c4957c21e | ||
|
|
a178c59aa3 | ||
|
|
0d9fcd97d7 | ||
|
|
b02ca117be | ||
|
|
ca2e2a22dd | ||
|
|
035e217124 | ||
|
|
6424ab283a | ||
|
|
f30f10c904 | ||
|
|
8ce09a607f | ||
|
|
5b48e30798 | ||
|
|
0d7e6af2e6 | ||
|
|
be3ca23aee | ||
|
|
28f464ba5a | ||
|
|
8cedd1b8bc | ||
|
|
56cbc49d04 | ||
|
|
5d1ccd2a20 | ||
|
|
f898fbc55e | ||
|
|
63acad3aeb | ||
|
|
5b4b0b8dff | ||
|
|
8492dd74f9 | ||
|
|
77e652cd34 | ||
|
|
5069d7e464 | ||
|
|
8b3e9c0f63 | ||
|
|
99e76e480b | ||
|
|
c161ed2298 | ||
|
|
464770f096 | ||
|
|
9fba1be814 | ||
|
|
b419b8b104 | ||
|
|
0cae955ca2 | ||
|
|
ec70ad5d29 | ||
|
|
8a386c4ece | ||
|
|
013460ada4 | ||
|
|
d46ad89dc5 | ||
|
|
86c53c8e46 | ||
|
|
df14786e79 | ||
|
|
24f206ad82 | ||
|
|
242c3efe45 | ||
|
|
07a848b906 | ||
|
|
1b5402fd2d | ||
|
|
903d791549 | ||
|
|
f03c164f1c | ||
|
|
107bab0192 | ||
|
|
41f5ebb2f1 | ||
|
|
266090dc2a | ||
|
|
83f29f3d62 | ||
|
|
3e6137ca96 | ||
|
|
58510bbd22 | ||
|
|
2f7be75bcf | ||
|
|
0aecbbf892 | ||
|
|
9ef2f4179b | ||
|
|
afbfe3ea12 | ||
|
|
b145a2da85 | ||
|
|
a679f7dd98 | ||
|
|
f343366ea1 | ||
|
|
1bbea7dda0 | ||
|
|
da097e0955 | ||
|
|
1307474755 | ||
|
|
80f5a95297 | ||
|
|
e619966679 | ||
|
|
3f94382925 | ||
|
|
ea7a6f8872 | ||
|
|
4e87b3a0b8 | ||
|
|
8463dd46f7 | ||
|
|
3febe465f6 | ||
|
|
45b834c424 | ||
|
|
1ca041097b | ||
|
|
587f7acd5b | ||
|
|
9e64e4a26b | ||
|
|
96e09ab36c | ||
|
|
4268d3f07b | ||
|
|
1767a2aed5 | ||
|
|
9f061506bb | ||
|
|
0eb9a2048d | ||
|
|
faeafb2402 | ||
|
|
2a0acfa6cb | ||
|
|
0ac44ac267 | ||
|
|
b9ae3a4d5a | ||
|
|
b1aefb0003 | ||
|
|
f6a41ec55c | ||
|
|
39650cc584 | ||
|
|
d837287da7 | ||
|
|
6e718a39d1 | ||
|
|
2e7db02238 | ||
|
|
dbfd108819 | ||
|
|
82c0f6fc0f | ||
|
|
9e6bbaa67d | ||
|
|
012a203c4a | ||
|
|
c7ba129ed7 | ||
|
|
fbe57d00db | ||
|
|
dc4fdf215c | ||
|
|
2166ac1584 | ||
|
|
504756de09 | ||
|
|
90f04dba94 | ||
|
|
bfd7645fb7 | ||
|
|
c37c6983c3 | ||
|
|
736c700aa0 | ||
|
|
de1cb901fc | ||
|
|
3d9d6397f6 | ||
|
|
dbe836729f | ||
|
|
82fa93e676 | ||
|
|
90a3392b80 | ||
|
|
b7f715bd5e | ||
|
|
5f88f46770 | ||
|
|
2244c91a64 | ||
|
|
78134404c3 | ||
|
|
c9cd6b175d | ||
|
|
5f70d283e0 | ||
|
|
cd1e27fd11 | ||
|
|
0fd1c19514 | ||
|
|
a82dffd77d | ||
|
|
d3eed87077 | ||
|
|
d99f8ad7e7 | ||
|
|
e933305407 | ||
|
|
690bc5a64a | ||
|
|
d2380a5c9c | ||
|
|
c2d0a6e9e0 | ||
|
|
a8c44fddca | ||
|
|
0e6b306bdc | ||
|
|
9b2a47ba0c | ||
|
|
3038ebc1c7 | ||
|
|
5a48d5b400 | ||
|
|
f36edfe98d | ||
|
|
3e80d6e13b | ||
|
|
c8f0a1ccc6 | ||
|
|
79325a3129 | ||
|
|
1ca0fffe5e | ||
|
|
f5b9842b2e | ||
|
|
72ea3f125e | ||
|
|
f7a2408e44 | ||
|
|
c05de9a085 | ||
|
|
fc657f3b2d | ||
|
|
e388de0364 | ||
|
|
26e55afb29 | ||
|
|
9b8e1039f6 | ||
|
|
082d14ba50 | ||
|
|
87ead595c6 | ||
|
|
fb073acdc3 | ||
|
|
3dda4e24bf | ||
|
|
c966bd08ed | ||
|
|
692c73a6d0 | ||
|
|
849b484b4d | ||
|
|
9489baccda | ||
|
|
62cac15222 | ||
|
|
0ee9ce2958 | ||
|
|
a83007fc6c | ||
|
|
901f266dad | ||
|
|
6009426f04 | ||
|
|
6fb89df4cd | ||
|
|
48b99425a3 | ||
|
|
9569f9b09b | ||
|
|
7421896902 | ||
|
|
b837c8d66c | ||
|
|
e180f96eab | ||
|
|
de1f1f790e | ||
|
|
fefa74ce0f | ||
|
|
a2ceb70aa4 | ||
|
|
634103f144 | ||
|
|
f3379de81e | ||
|
|
064ee74c1a | ||
|
|
0e0c15fe3b | ||
|
|
3c855c608d | ||
|
|
414516289c | ||
|
|
d79fa71337 | ||
|
|
39c2ca94c8 | ||
|
|
acf1c5ce04 | ||
|
|
fd7923155f | ||
|
|
2aa6a6daa2 | ||
|
|
9662621980 | ||
|
|
37d007d9ab | ||
|
|
a4e48c359a | ||
|
|
8ef0101a6e | ||
|
|
e21f109026 | ||
|
|
737837eebd | ||
|
|
ba09b54409 | ||
|
|
417091c648 | ||
|
|
5a02368298 | ||
|
|
da7c739497 | ||
|
|
0842281466 | ||
|
|
b480f879b1 | ||
|
|
f8a215d790 | ||
|
|
da69f3b2c8 | ||
|
|
fc442c1a42 | ||
|
|
cf59a6b9fd | ||
|
|
34afcd511a | ||
|
|
e2444a2e4e | ||
|
|
692516de9b | ||
|
|
d51009c823 | ||
|
|
2f121bef5e | ||
|
|
61f7d376d2 | ||
|
|
9e253012e6 | ||
|
|
e4e5b1327b | ||
|
|
6ef3b227b8 | ||
|
|
bf657a0945 | ||
|
|
c3dd1886c9 | ||
|
|
32498bb8a7 | ||
|
|
69bb90a0e4 | ||
|
|
8e2988edf0 | ||
|
|
876a352cfd | ||
|
|
84e44cabfa | ||
|
|
36a838d565 | ||
|
|
9ee8693f40 | ||
|
|
6f02965149 | ||
|
|
27ce82de3b | ||
|
|
3d5a9ef220 | ||
|
|
9b81f6efd2 | ||
|
|
3e77e23d71 | ||
|
|
120c032c82 | ||
|
|
46e15b8ecd | ||
|
|
d71d87041b | ||
|
|
124bd58b9f | ||
|
|
257b9b0562 | ||
|
|
b8e15f691d | ||
|
|
2255e3bfc4 | ||
|
|
8797d84605 | ||
|
|
719179a923 | ||
|
|
1d544099f6 | ||
|
|
9b131a762a | ||
|
|
08c5d2256a | ||
|
|
ed6ee4341f | ||
|
|
157c1148fb | ||
|
|
507ea9e09e | ||
|
|
af68cba7be | ||
|
|
224fbe0e8f | ||
|
|
49a6c5f2c4 | ||
|
|
07c936897c | ||
|
|
3786fb907c | ||
|
|
752d9f0c68 | ||
|
|
baf59aafcb | ||
|
|
8787303d2a | ||
|
|
298eaa8b4b | ||
|
|
118c80af27 | ||
|
|
7b87038a8c | ||
|
|
d103939e45 | ||
|
|
fdc9171c69 | ||
|
|
2a6ade3cab | ||
|
|
c8c42689f9 | ||
|
|
da2f472f4d | ||
|
|
3ba878237b | ||
|
|
7577fb53a2 | ||
|
|
f2509f89ee | ||
|
|
9dd1f78330 | ||
|
|
ebb6c2c420 | ||
|
|
9d23dc1763 | ||
|
|
5fa3f8703e | ||
|
|
797885faea | ||
|
|
68e7e9f5b7 | ||
|
|
d1d165ad51 | ||
|
|
f43bd100e6 | ||
|
|
0ebb247666 | ||
|
|
aa73bc2809 | ||
|
|
c3fcdb918f | ||
|
|
5e3722bcfd | ||
|
|
6a4fca2eb1 | ||
|
|
fbba0e3ea5 | ||
|
|
6922cfd047 | ||
|
|
d93fc1d2d0 | ||
|
|
b15695128f | ||
|
|
48290b2e75 | ||
|
|
b62abef618 | ||
|
|
e0c0778d82 | ||
|
|
b4a82ae7c2 | ||
|
|
fc73102b30 | ||
|
|
b14e7473f3 | ||
|
|
f4cc9fc722 | ||
|
|
8d19ef7783 | ||
|
|
32be186ec5 | ||
|
|
abb0c7f90d | ||
|
|
71ddbdfe75 | ||
|
|
38463ad9a6 | ||
|
|
0870b90443 | ||
|
|
70375f94c8 | ||
|
|
2d4336116a | ||
|
|
f52880765e | ||
|
|
dbfe68decb | ||
|
|
659f337de9 | ||
|
|
1d36d41da1 | ||
|
|
0f90efaa54 | ||
|
|
48ccab152b | ||
|
|
4442930a82 | ||
|
|
912a775088 | ||
|
|
24a229d818 | ||
|
|
66afb61494 | ||
|
|
e7511cc05b | ||
|
|
926f19a936 | ||
|
|
9e6f86b963 | ||
|
|
059de43a9a | ||
|
|
e246b737b2 | ||
|
|
f3bc900f16 | ||
|
|
38842417b0 | ||
|
|
82d485a98e | ||
|
|
fac71feea7 | ||
|
|
ec148847a9 | ||
|
|
445c63878b | ||
|
|
0474af912a | ||
|
|
7df5953824 | ||
|
|
e391b563fb | ||
|
|
53f62f96d0 | ||
|
|
18745979a0 | ||
|
|
c1966a38ff | ||
|
|
d86973f3b1 | ||
|
|
56b8d19649 | ||
|
|
7cb037e0bc | ||
|
|
56e1b3f9e9 | ||
|
|
c792323a8a | ||
|
|
4d2865de13 | ||
|
|
0d360bc367 | ||
|
|
91c9b46b91 | ||
|
|
7e67bd80dd | ||
|
|
2f775ea09b | ||
|
|
0603c55089 | ||
|
|
41f25a44e9 | ||
|
|
35440b7273 | ||
|
|
7d68b91018 | ||
|
|
deaa61b848 | ||
|
|
2d00cbc41b | ||
|
|
57c1e83c67 | ||
|
|
b7079dbd4e | ||
|
|
be642a5373 | ||
|
|
ffc691c1a2 | ||
|
|
6122d99369 | ||
|
|
b73a8bcdab | ||
|
|
dd624537b7 | ||
|
|
50a67f73fd | ||
|
|
2262a980d4 | ||
|
|
84964ceb5f | ||
|
|
c4bdb7a66e | ||
|
|
21309e129f | ||
|
|
928e02c718 | ||
|
|
7f494dd200 | ||
|
|
f38e743e59 | ||
|
|
58b55eb3da | ||
|
|
8375ad95b3 | ||
|
|
506d027a2d | ||
|
|
fbeb6237cf | ||
|
|
bd95e2c5a5 | ||
|
|
f3276e557a | ||
|
|
8b813e0e7f | ||
|
|
d660d2959f | ||
|
|
014ebda7d2 | ||
|
|
607398d364 | ||
|
|
97d141ce2b | ||
|
|
9f492db9c6 | ||
|
|
6ed82b366c | ||
|
|
a7ca037f48 | ||
|
|
6e21d0e74a | ||
|
|
e6c75bf2af | ||
|
|
270c2f386e | ||
|
|
83f7880c58 | ||
|
|
51d29aee38 | ||
|
|
c7754c0365 | ||
|
|
94037cea38 | ||
|
|
5c1a9d9eea | ||
|
|
3835c73ffd | ||
|
|
b8c1effecb | ||
|
|
dd07241dd9 | ||
|
|
5ad828a613 | ||
|
|
9856df5527 | ||
|
|
7b249900ec | ||
|
|
e3b9267c3f | ||
|
|
a996b8135a | ||
|
|
5e1237390b | ||
|
|
83571b4bef | ||
|
|
c2a0dad9a8 | ||
|
|
f6f057689c | ||
|
|
4378f0020e | ||
|
|
2e9e7c4fc6 | ||
|
|
b7b8ee5580 | ||
|
|
c630dbbed0 | ||
|
|
fa6703a3b8 | ||
|
|
bdcb64c9d1 | ||
|
|
a1afc869a7 | ||
|
|
d8ce26c7cf | ||
|
|
95c474dc05 | ||
|
|
56ca73b4ad | ||
|
|
d82cd6a89e | ||
|
|
bf20d32364 | ||
|
|
e968e62fca | ||
|
|
b9c9ca9fa1 | ||
|
|
6e965e2e98 | ||
|
|
7d569f9036 | ||
|
|
df5a77199d | ||
|
|
3c8dfebfdc | ||
|
|
3cc422596f | ||
|
|
4ed167de22 | ||
|
|
b420647501 | ||
|
|
0c318b5e68 | ||
|
|
1c627297b8 | ||
|
|
f41e2d0552 | ||
|
|
1a1d539c60 | ||
|
|
ba2e46f88f | ||
|
|
c2f1fa81af | ||
|
|
752e03fa0f | ||
|
|
1da69664d7 | ||
|
|
9aa986a133 | ||
|
|
bdbfb28c4a | ||
|
|
162156bb2b | ||
|
|
64c5d542e9 | ||
|
|
31dc789f6e | ||
|
|
b0e2cfd7db | ||
|
|
54ddb0d014 | ||
|
|
e19688e96f | ||
|
|
4f53d75999 | ||
|
|
7277460060 | ||
|
|
93c4f6f3c0 | ||
|
|
c6919a7518 | ||
|
|
ca428e67dc | ||
|
|
c6726cf020 | ||
|
|
56ae1378da | ||
|
|
e982ab1a3b | ||
|
|
0d6aa87e89 | ||
|
|
94d06e4025 | ||
|
|
cee5b297ac | ||
|
|
c549213ce0 | ||
|
|
f3f9112767 | ||
|
|
fb325ea3e2 | ||
|
|
3773f6f7ec | ||
|
|
b90ab6fe48 | ||
|
|
cc2e56dc8a | ||
|
|
ae65af7bbf | ||
|
|
c8fb513cd1 | ||
|
|
7fc3855af4 | ||
|
|
bcebb26d4a | ||
|
|
1f23e4cfcc | ||
|
|
65998778fe | ||
|
|
e53c04a07f | ||
|
|
e5a8e106c1 | ||
|
|
11d1d80a53 | ||
|
|
c8a3d86a45 | ||
|
|
4484ca226a | ||
|
|
496c9ba35c | ||
|
|
dae2fc9192 | ||
|
|
07b00e6230 | ||
|
|
36526f0824 | ||
|
|
f4691939ba | ||
|
|
534f735b63 | ||
|
|
bc2c8406a2 | ||
|
|
032c44f39a | ||
|
|
2b43c8e91a | ||
|
|
e91b3df31a | ||
|
|
3e0597023a | ||
|
|
1dd2edf742 | ||
|
|
97f3bece33 | ||
|
|
be02444487 | ||
|
|
fc012fe68a | ||
|
|
7ac16d6d22 | ||
|
|
4ee92b1f3e | ||
|
|
d82128690b | ||
|
|
9e57eb4262 | ||
|
|
6e0d45f419 | ||
|
|
645e86714e | ||
|
|
00afd1cd6a | ||
|
|
77ae126a51 | ||
|
|
9c82c7caed | ||
|
|
d9a92e7a78 | ||
|
|
d8a87029a6 | ||
|
|
f407d0f92a | ||
|
|
4ac42e62e6 | ||
|
|
748c63fa0b | ||
|
|
78ca756cef | ||
|
|
a8554b51c2 | ||
|
|
e54e4da289 | ||
|
|
f97cb5f5a7 | ||
|
|
318e8077d0 | ||
|
|
c8355a532d | ||
|
|
3686ccd4ed | ||
|
|
f02db0120a | ||
|
|
d9ad93d3cd | ||
|
|
77018cc1ad | ||
|
|
30a8a29ce3 | ||
|
|
7b1aa646ac | ||
|
|
49e4409862 | ||
|
|
ab87ddc649 | ||
|
|
57784329ac | ||
|
|
1db613c56d | ||
|
|
464cd4165f | ||
|
|
420ebe4cc7 | ||
|
|
15ead440d6 | ||
|
|
ef0614a62f | ||
|
|
2fd2af3e31 | ||
|
|
f30a904a02 | ||
|
|
e00680113f | ||
|
|
93e6a29057 |
35
.babelrc
35
.babelrc
@@ -1,3 +1,34 @@
|
||||
{
|
||||
"presets": ["es2015", "react", "stage-0"]
|
||||
}
|
||||
"presets": [
|
||||
["@babel/preset-env", {"modules": "commonjs"}],
|
||||
"@babel/preset-react"
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-syntax-dynamic-import",
|
||||
"@babel/plugin-syntax-import-meta",
|
||||
["@babel/plugin-proposal-class-properties", { "loose": true }],
|
||||
"@babel/plugin-proposal-json-strings",
|
||||
[
|
||||
"@babel/plugin-proposal-decorators",
|
||||
{
|
||||
"legacy": true
|
||||
}
|
||||
],
|
||||
"@babel/plugin-proposal-function-sent",
|
||||
"@babel/plugin-proposal-export-namespace-from",
|
||||
"@babel/plugin-proposal-numeric-separator",
|
||||
"@babel/plugin-proposal-throw-expressions",
|
||||
"@babel/plugin-proposal-export-default-from",
|
||||
"@babel/plugin-proposal-logical-assignment-operators",
|
||||
"@babel/plugin-proposal-optional-chaining",
|
||||
[
|
||||
"@babel/plugin-proposal-pipeline-operator",
|
||||
{
|
||||
"proposal": "minimal"
|
||||
}
|
||||
],
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator",
|
||||
"@babel/plugin-proposal-do-expressions",
|
||||
"@babel/plugin-proposal-function-bind"
|
||||
]
|
||||
}
|
||||
|
||||
21
.editorconfig
Normal file
21
.editorconfig
Normal 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
103
.esdoc.json
Normal 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.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.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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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 }],
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -7,3 +7,6 @@ nginx.pid
|
||||
/bin
|
||||
env
|
||||
*.swp
|
||||
.project
|
||||
.vscode/
|
||||
docs/
|
||||
|
||||
15
.travis.yml
15
.travis.yml
@@ -1,15 +0,0 @@
|
||||
language: node_js
|
||||
notifications:
|
||||
email: false
|
||||
sudo: false
|
||||
node_js:
|
||||
- "4.2.6"
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
|
||||
before_script:
|
||||
|
||||
script:
|
||||
- npm run lint
|
||||
- npm test
|
||||
56
ChangeLog.md
56
ChangeLog.md
@@ -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
|
||||
|
||||
24
LICENSE.md
Normal file
24
LICENSE.md
Normal file
@@ -0,0 +1,24 @@
|
||||
All Data and [associated JSON](https://github.com/EDCD/coriolis-data) files are intellectual property and copyright of Frontier Developments plc ('Frontier', 'Frontier Developments') and are subject to their
|
||||
[terms and conditions](https://www.frontierstore.net/terms-and-conditions/).
|
||||
|
||||
The code (Javascript, CSS, HTML, and SVG files only) specificially for Coriolis.io is released under the MIT License.
|
||||
|
||||
Copyright (c) 2015 Coriolis.io, Colin McLeod
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software (Javascript, CSS, HTML, and SVG files only), and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
56
README.md
56
README.md
@@ -1,4 +1,4 @@
|
||||
 [](https://travis-ci.org/EDCD/coriolis) [](https://discord.gg/0uwCh6R62aPRjk9w)
|
||||
[](https://discord.gg/0uwCh6R62aPRjk9w)
|
||||
|
||||
## About
|
||||
|
||||
@@ -8,45 +8,41 @@ Coriolis was created using assets and imagery from Elite: Dangerous, with the pe
|
||||
|
||||
## Contributing
|
||||
|
||||
Please [submit issues](https://github.com/EDCD/coriolis/issues), or better yet [pull requests](https://github.com/EDCD/coriolis/pulls) for any corrections or additions to the database or the code.
|
||||
|
||||
### Feature Requests, Suggestions & Bugs
|
||||
|
||||
Chat to us on [Discord](https://discord.gg/0uwCh6R62aPRjk9w)!
|
||||
- [Submit issues](https://github.com/EDCD/coriolis/issues)
|
||||
- [Submit pull requests](https://github.com/EDCD/coriolis/pulls) targetting `develop` branch
|
||||
- Chat to us on [Discord](https://discord.gg/0uwCh6R62aPRjk9w)!
|
||||
|
||||
## Development
|
||||
|
||||
See the [Developer's Guide](https://github.com/EDCD/coriolis/wiki/Developing-for-Coriolis) in the wiki.
|
||||
To get a local instance of coriolis running, perform the following steps in a shell:
|
||||
```sh
|
||||
> git clone https://github.com/EDCD/coriolis.git
|
||||
> git clone https://github.com/EDCD/coriolis-data.git
|
||||
> cd ./coriolis-data
|
||||
> npm install
|
||||
> cd ../coriolis
|
||||
> npm install
|
||||
> npm start
|
||||
```
|
||||
|
||||
You will then have a development server running on `localhost:3300`.
|
||||
|
||||
### Ship and Module Database
|
||||
|
||||
See the [Data wiki](https://github.com/cmmcleod/coriolis-data/wiki) for details on structure, etc.
|
||||
|
||||
## Deployment
|
||||
|
||||
## License
|
||||
Follow the steps for [Development](#development) as above, but instead
|
||||
of `npm start` you'll want to:
|
||||
|
||||
All Data and [associated JSON](https://github.com/EDCD/coriolis-data) files are intellectual property and copyright of Frontier Developments plc ('Frontier', 'Frontier Developments') and are subject to their
|
||||
[terms and conditions](https://www.frontierstore.net/terms-and-conditions/).
|
||||
```sh
|
||||
> npm run build
|
||||
```
|
||||
|
||||
The code (Javascript, CSS, HTML, and SVG files only) specificially for Coriolis.io is released under the MIT License.
|
||||
this will result in a `build/` directory being created containing all the necessary files.
|
||||
|
||||
Copyright (c) 2015 Coriolis.io, Colin McLeod
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software (Javascript, CSS, HTML, and SVG files only), and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
After this you need to serve the files in some manner.
|
||||
Either configure your webserver to make the actual `build/` directory
|
||||
visible on the web, or alternatively copy it to somewhere to serve it
|
||||
from.
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"adder": {
|
||||
"t3": {"speed": 205, "boost": 298, "pitch": 35.37, "roll": 93.09, "yaw": 13.03},
|
||||
"t2": {"speed": 209, "boost": 304, "pitch": 36.06, "roll": 94.90, "yaw": 13.29},
|
||||
"t1": {"speed": 213, "boost": 310, "pitch": 36.80, "roll": 96.84, "yaw": 13.56},
|
||||
"t0": {"speed": 218, "boost": 317, "pitch": 37.70, "roll": 99.20, "yaw": 13.89},
|
||||
"t9": {"speed": 220, "boost": 321, "pitch": 38.08, "roll": 100.21, "yaw": 14.03},
|
||||
"t8": {"speed": 225, "boost": 327, "pitch": 38.86, "roll": 102.26, "yaw": 14.32},
|
||||
"t7": {"speed": 230, "boost": 334, "pitch": 39.69, "roll": 104.44, "yaw": 14.62},
|
||||
"t6": {"speed": 234, "boost": 340, "pitch": 40.41, "roll": 106.34, "yaw": 14.89},
|
||||
"t5": {"speed": 242, "boost": 351, "pitch": 41.71, "roll": 109.78, "yaw": 15.37}
|
||||
},
|
||||
"eagle": {
|
||||
"t2": {"speed": 223, "boost": 325, "pitch": 46.45, "roll": 111.48, "yaw": 16.72},
|
||||
"t1": {"speed": 229, "boost": 334, "pitch": 47.69, "roll": 114.46, "yaw": 17.17},
|
||||
"t0": {"speed": 235, "boost": 343, "pitch": 49.00, "roll": 117.60, "yaw": 17.64},
|
||||
"t9": {"speed": 239, "boost": 349, "pitch": 49.80, "roll": 119.53, "yaw": 17.93},
|
||||
"t8": {"speed": 243, "boost": 355, "pitch": 50.70, "roll": 121.69, "yaw": 18.25},
|
||||
"t7": {"speed": 248, "boost": 361, "pitch": 51.62, "roll": 123.89, "yaw": 18.58},
|
||||
"t6": {"speed": 252, "boost": 367, "pitch": 52.46, "roll": 125.91, "yaw": 18.89},
|
||||
"t5": {"speed": 259, "boost": 378, "pitch": 53.99, "roll": 129.56, "yaw": 19.43}
|
||||
},
|
||||
"hauler": {
|
||||
"t4": {"speed": 203, "boost": 305, "pitch": 36.61, "roll": 101.71, "yaw": 14.24},
|
||||
"t3": {"speed": 209, "boost": 314, "pitch": 37.63, "roll": 104.54, "yaw": 14.64},
|
||||
"t2": {"speed": 216, "boost": 324, "pitch": 38.89, "roll": 108.03, "yaw": 15.12},
|
||||
"t1": {"speed": 222, "boost": 333, "pitch": 39.97, "roll": 111.02, "yaw": 15.54},
|
||||
"t0": {"speed": 232, "boost": 348, "pitch": 41.76, "roll": 116.00, "yaw": 16.24}
|
||||
}
|
||||
}
|
||||
@@ -1,289 +0,0 @@
|
||||
{
|
||||
"$schema": "http://cdn.coriolis.io/schemas/ship-loadout/3.json#",
|
||||
"name": "Test My Ship",
|
||||
"ship": "Anaconda",
|
||||
"references": [
|
||||
{
|
||||
"name": "Coriolis.io",
|
||||
"url": "http://localhost:3300/outfit/anaconda/48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA?bn=Test%20My%20Ship",
|
||||
"old-code": "48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA",
|
||||
"code": "4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA",
|
||||
"shipId": "anaconda"
|
||||
}
|
||||
],
|
||||
"components": {
|
||||
"standard": {
|
||||
"bulkheads": "Reactive Surface Composite",
|
||||
"cargoHatch": {
|
||||
"enabled": false,
|
||||
"priority": 5
|
||||
},
|
||||
"powerPlant": {
|
||||
"class": 8,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
},
|
||||
"thrusters": {
|
||||
"class": 6,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
},
|
||||
"frameShiftDrive": {
|
||||
"class": 6,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 3
|
||||
},
|
||||
"lifeSupport": {
|
||||
"class": 5,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
},
|
||||
"powerDistributor": {
|
||||
"class": 8,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
},
|
||||
"sensors": {
|
||||
"class": 8,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
},
|
||||
"fuelTank": {
|
||||
"class": 5,
|
||||
"rating": "C",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
}
|
||||
},
|
||||
"hardpoints": [
|
||||
{
|
||||
"class": 4,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Plasma Accelerator",
|
||||
"mount": "Fixed"
|
||||
},
|
||||
{
|
||||
"class": 3,
|
||||
"rating": "D",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Beam Laser",
|
||||
"mount": "Turret"
|
||||
},
|
||||
{
|
||||
"class": 3,
|
||||
"rating": "D",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Beam Laser",
|
||||
"mount": "Turret"
|
||||
},
|
||||
{
|
||||
"class": 3,
|
||||
"rating": "D",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Beam Laser",
|
||||
"mount": "Turret"
|
||||
},
|
||||
{
|
||||
"class": 2,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Cannon",
|
||||
"mount": "Turret"
|
||||
},
|
||||
{
|
||||
"class": 2,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Cannon",
|
||||
"mount": "Turret"
|
||||
},
|
||||
{
|
||||
"class": 1,
|
||||
"rating": "F",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Beam Laser",
|
||||
"mount": "Turret"
|
||||
},
|
||||
{
|
||||
"class": 1,
|
||||
"rating": "F",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Beam Laser",
|
||||
"mount": "Turret"
|
||||
}
|
||||
],
|
||||
"utility": [
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Shield Booster"
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Shield Booster"
|
||||
},
|
||||
null,
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "C",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Kill Warrant Scanner"
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "C",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Cargo Scanner"
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "F",
|
||||
"enabled": false,
|
||||
"priority": 1,
|
||||
"group": "Countermeasure",
|
||||
"name": "Electronic Countermeasure"
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "I",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Countermeasure",
|
||||
"name": "Chaff Launcher"
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "I",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Countermeasure",
|
||||
"name": "Point Defence"
|
||||
}
|
||||
],
|
||||
"internal": [
|
||||
{
|
||||
"class": 7,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Shield Generator"
|
||||
},
|
||||
{
|
||||
"class": 6,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Shield Cell Bank"
|
||||
},
|
||||
{
|
||||
"class": 6,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Cargo Rack"
|
||||
},
|
||||
{
|
||||
"class": 5,
|
||||
"rating": "D",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Hull Reinforcement Package"
|
||||
},
|
||||
{
|
||||
"class": 5,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Cargo Rack"
|
||||
},
|
||||
null,
|
||||
null,
|
||||
{
|
||||
"class": 4,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Cargo Rack"
|
||||
},
|
||||
{
|
||||
"class": 4,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Cargo Rack"
|
||||
},
|
||||
{
|
||||
"class": 4,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 3,
|
||||
"group": "Fuel Scoop"
|
||||
},
|
||||
{
|
||||
"class": 2,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 3,
|
||||
"group": "Frame Shift Drive Interdictor"
|
||||
}
|
||||
]
|
||||
},
|
||||
"stats": {
|
||||
"class": 3,
|
||||
"hullCost": 141889930,
|
||||
"speed": 180,
|
||||
"topSpeed": 186.5,
|
||||
"boost": 240,
|
||||
"boostEnergy": 29,
|
||||
"topBoost": 248.66,
|
||||
"agility": 2,
|
||||
"baseShieldStrength": 350,
|
||||
"baseArmour": 945,
|
||||
"hullMass": 400,
|
||||
"masslock": 23,
|
||||
"pipSpeed": 0.14,
|
||||
"moduleCostMultiplier": 1,
|
||||
"fuelCapacity": 32,
|
||||
"cargoCapacity": 128,
|
||||
"ladenMass": 1339.2,
|
||||
"armour": 2228,
|
||||
"armourAdded": 390,
|
||||
"armourMultiplier": 1.95,
|
||||
"shieldMultiplier": 1.4,
|
||||
"totalCost": 882362060,
|
||||
"unladenMass": 1179.2,
|
||||
"totalDps": 29,
|
||||
"powerAvailable": 36,
|
||||
"powerRetracted": 23.33,
|
||||
"powerDeployed": 34.76,
|
||||
"unladenRange": 18.49,
|
||||
"fullTankRange": 18.12,
|
||||
"ladenRange": 16.39,
|
||||
"unladenFastestRange": 73.21,
|
||||
"ladenFastestRange": 66.15,
|
||||
"maxJumpCount": 4,
|
||||
"shieldStrength": 833
|
||||
}
|
||||
}
|
||||
@@ -1,325 +0,0 @@
|
||||
{
|
||||
"$schema": "http://cdn.coriolis.io/schemas/ship-loadout/4.json#",
|
||||
"name": "Test My Ship",
|
||||
"ship": "Anaconda",
|
||||
"references": [
|
||||
{
|
||||
"name": "Coriolis.io",
|
||||
"url": "http://localhost:3300/outfit/anaconda/48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA.H4sIAAAAAAAAA2MUe8HMwPD-PwDDhxeuCAAAAA==?bn=Test%20My%20Ship",
|
||||
"old-code": "48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA.H4sIAAAAAAAAA2MUe8HMwPD-PwDDhxeuCAAAAA==",
|
||||
"code": "4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04--0303326b.AwRj4zNKqA==.CwBhCYzBGW9qCTSqs5xA.H4sIAAAAAAAAA2MUe8HMwPD-PwDDhxeuCAAAAA==",
|
||||
"shipId": "anaconda"
|
||||
}
|
||||
],
|
||||
"components": {
|
||||
"standard": {
|
||||
"bulkheads": "Reactive Surface Composite",
|
||||
"cargoHatch": {
|
||||
"enabled": false,
|
||||
"priority": 5
|
||||
},
|
||||
"powerPlant": {
|
||||
"class": 8,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"modifications": {
|
||||
"pgen": 1000
|
||||
}
|
||||
},
|
||||
"thrusters": {
|
||||
"class": 6,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
},
|
||||
"frameShiftDrive": {
|
||||
"class": 6,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 3
|
||||
},
|
||||
"lifeSupport": {
|
||||
"class": 5,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
},
|
||||
"powerDistributor": {
|
||||
"class": 8,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
},
|
||||
"sensors": {
|
||||
"class": 8,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
},
|
||||
"fuelTank": {
|
||||
"class": 5,
|
||||
"rating": "C",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
}
|
||||
},
|
||||
"hardpoints": [
|
||||
{
|
||||
"class": 4,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Plasma Accelerator",
|
||||
"mount": "Fixed"
|
||||
},
|
||||
{
|
||||
"class": 3,
|
||||
"rating": "D",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Beam Laser",
|
||||
"mount": "Turret"
|
||||
},
|
||||
{
|
||||
"class": 3,
|
||||
"rating": "D",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Beam Laser",
|
||||
"mount": "Turret"
|
||||
},
|
||||
{
|
||||
"class": 3,
|
||||
"rating": "D",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Beam Laser",
|
||||
"mount": "Turret"
|
||||
},
|
||||
{
|
||||
"class": 2,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Cannon",
|
||||
"mount": "Turret"
|
||||
},
|
||||
{
|
||||
"class": 2,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Cannon",
|
||||
"mount": "Turret"
|
||||
},
|
||||
{
|
||||
"class": 1,
|
||||
"rating": "F",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Beam Laser",
|
||||
"mount": "Turret"
|
||||
},
|
||||
{
|
||||
"class": 1,
|
||||
"rating": "F",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Beam Laser",
|
||||
"mount": "Turret"
|
||||
}
|
||||
],
|
||||
"utility": [
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Shield Booster"
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Shield Booster"
|
||||
},
|
||||
null,
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "C",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Kill Warrant Scanner"
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "C",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Cargo Scanner"
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "F",
|
||||
"enabled": false,
|
||||
"priority": 1,
|
||||
"group": "Electronic Countermeasure",
|
||||
"name": "Electronic Countermeasure"
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "I",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Chaff Launcher",
|
||||
"name": "Chaff Launcher"
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "I",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Point Defence",
|
||||
"name": "Point Defence"
|
||||
}
|
||||
],
|
||||
"internal": [
|
||||
{
|
||||
"class": 7,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Shield Generator"
|
||||
},
|
||||
{
|
||||
"class": 6,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Shield Cell Bank"
|
||||
},
|
||||
{
|
||||
"class": 6,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Cargo Rack"
|
||||
},
|
||||
{
|
||||
"class": 5,
|
||||
"rating": "D",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Hull Reinforcement Package"
|
||||
},
|
||||
{
|
||||
"class": 5,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Cargo Rack"
|
||||
},
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
{
|
||||
"class": 4,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Cargo Rack"
|
||||
},
|
||||
{
|
||||
"class": 4,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Cargo Rack"
|
||||
},
|
||||
{
|
||||
"class": 4,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 3,
|
||||
"group": "Fuel Scoop"
|
||||
},
|
||||
{
|
||||
"class": 2,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 3,
|
||||
"group": "Frame Shift Drive Interdictor"
|
||||
}
|
||||
]
|
||||
},
|
||||
"stats": {
|
||||
"class": 3,
|
||||
"fighterHangars": 1,
|
||||
"hullCost": 141889930,
|
||||
"speed": 180,
|
||||
"topSpeed": 186.5,
|
||||
"boost": 240,
|
||||
"boostEnergy": 27,
|
||||
"topBoost": 249.34,
|
||||
"topPitch": 25.97,
|
||||
"topRoll": 62.34,
|
||||
"topYaw": 10.39,
|
||||
"topSpeed": 187.01,
|
||||
"totalCost": 882362058,
|
||||
"totalDpe": 142.68,
|
||||
"totalDps": 101.13,
|
||||
"totalEps": 18.71,
|
||||
"totalExplDpe": 0,
|
||||
"totalExplDps": 0,
|
||||
"totalExplSDps": 0,
|
||||
"totalAbsDpe": 3.57,
|
||||
"totalAbsDps": 18.78,
|
||||
"totalAbsSDps": 14.45,
|
||||
"totalHps": 28.28,
|
||||
"totalKinDpe": 117.48,
|
||||
"totalKinDps": 22.27,
|
||||
"totalKinSDps": 16.91,
|
||||
"totalSDps": 89.99,
|
||||
"totalThermDpe": 21.63,
|
||||
"totalThermDps": 60.08,
|
||||
"totalThermSDps": 58.64,
|
||||
"baseShieldStrength": 350,
|
||||
"baseArmour": 945,
|
||||
"hullExplRes": 0.22,
|
||||
"hullKinRes": 0.27,
|
||||
"hullMass": 400,
|
||||
"hullThermRes": -0.36,
|
||||
"masslock": 23,
|
||||
"pipSpeed": 0.14,
|
||||
"pitch": 25,
|
||||
"moduleCostMultiplier": 1,
|
||||
"modulearmour": 0,
|
||||
"moduleprotection": 0,
|
||||
"fuelCapacity": 32,
|
||||
"cargoCapacity": 128,
|
||||
"ladenMass": 1323.2,
|
||||
"armour": 2227.5,
|
||||
"baseArmour": 525,
|
||||
"unladenMass": 1163.2,
|
||||
"powerAvailable": 39.6,
|
||||
"powerRetracted": 23.33,
|
||||
"powerDeployed": 34.13,
|
||||
"roll": 60,
|
||||
"unladenRange": 18.74,
|
||||
"yaw": 10,
|
||||
"fullTankRange": 18.36,
|
||||
"hardness": 65,
|
||||
"ladenRange": 16.59,
|
||||
"unladenFastestRange": 74.2,
|
||||
"ladenFastestRange": 66.96,
|
||||
"maxJumpCount": 4,
|
||||
"shield": 833,
|
||||
"shieldCells": 1840,
|
||||
"shieldExplRes": 0.5,
|
||||
"shieldKinRes": 0.4,
|
||||
"shieldThermRes": -0.2,
|
||||
"crew": 3
|
||||
}
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
{
|
||||
"$schema": "http://cdn.coriolis.io/schemas/ship-loadout/4.json#",
|
||||
"name": "Multi-purpose Asp Explorer",
|
||||
"ship": "Asp Explorer",
|
||||
"references": [
|
||||
{
|
||||
"name": "Coriolis.io",
|
||||
"url": "https://coriolis.edcd.io/outfit/asp?code=0pftiFflfddsnf5------020202033c044002v62f2i.AwRj4yvI.CwRgDBldHnJA.H4sIAAAAAAAAA2P858DAwPCXEUhwHPvx%2F78YG5AltB7I%2F8%2F0TwImJboDSPJ%2F%2B%2Ff%2Fv%2FKlX%2F%2F%2Fi3AwMTBIfARK%2FGf%2BJwVSxArStVAYqOjvz%2F%2F%2FJVo5GRhE2IBc4SKQSSz%2FDGEmCa398P8%2F%2F2%2BgTf%2F%2FAwDFxwtofAAAAA%3D%3D&bn=Multi-purpose%20Asp%20Explorer",
|
||||
"code": "0pftiFflfddsnf5------020202033c044002v62f2i.AwRj4yvI.CwRgDBldHnJA.H4sIAAAAAAAAA2P858DAwPCXEUhwHPvx/78YG5AltB7I/8/0TwImJboDSPJ/+/f/v/KlX///i3AwMTBIfARK/Gf+JwVSxArStVAYqOjvz///JVo5GRhE2IBc4SKQSSz/DGEmCa398P8//2+gTf//AwDFxwtofAAAAA==",
|
||||
"shipId": "asp"
|
||||
}
|
||||
],
|
||||
"components": {
|
||||
"standard": {
|
||||
"bulkheads": "Lightweight Alloy",
|
||||
"cargoHatch": {
|
||||
"enabled": false,
|
||||
"priority": 5
|
||||
},
|
||||
"powerPlant": {
|
||||
"class": 5,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"modifications": {
|
||||
"eff": -1850,
|
||||
"pgen": 6,
|
||||
"mass": 431
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 64,
|
||||
"name": "Low emissions",
|
||||
"grade": 1
|
||||
}
|
||||
},
|
||||
"thrusters": {
|
||||
"class": 5,
|
||||
"rating": "D",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"modifications": {
|
||||
"optmul": 440,
|
||||
"integrity": -266,
|
||||
"thermload": -1326,
|
||||
"optmass": 520,
|
||||
"power": 241
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 24,
|
||||
"name": "Clean",
|
||||
"grade": 1
|
||||
}
|
||||
},
|
||||
"frameShiftDrive": {
|
||||
"class": 5,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"modifications": {
|
||||
"mass": 5025,
|
||||
"integrity": -1539,
|
||||
"power": 2437,
|
||||
"optmass": 4870,
|
||||
"maxfuel": 370
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 26,
|
||||
"name": "Increased range",
|
||||
"grade": 5
|
||||
}
|
||||
},
|
||||
"lifeSupport": {
|
||||
"class": 4,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"modifications": {
|
||||
"mass": -3923,
|
||||
"integrity": -1797
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 49,
|
||||
"name": "Lightweight",
|
||||
"grade": 1
|
||||
}
|
||||
},
|
||||
"powerDistributor": {
|
||||
"class": 3,
|
||||
"rating": "D",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
},
|
||||
"sensors": {
|
||||
"class": 5,
|
||||
"rating": "D",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
},
|
||||
"fuelTank": {
|
||||
"class": 5,
|
||||
"rating": "C",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
}
|
||||
},
|
||||
"hardpoints": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
],
|
||||
"utility": [
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "I",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Heat Sink Launcher",
|
||||
"name": "Heat Sink Launcher"
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "I",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Heat Sink Launcher",
|
||||
"name": "Heat Sink Launcher"
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "I",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Heat Sink Launcher",
|
||||
"name": "Heat Sink Launcher"
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "I",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Point Defence",
|
||||
"name": "Point Defence"
|
||||
}
|
||||
],
|
||||
"internal": [
|
||||
{
|
||||
"class": 6,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Fuel Scoop"
|
||||
},
|
||||
{
|
||||
"class": 5,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Cargo Rack"
|
||||
},
|
||||
{
|
||||
"class": 3,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Shield Generator"
|
||||
},
|
||||
{
|
||||
"class": 3,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Cargo Rack"
|
||||
},
|
||||
{
|
||||
"class": 2,
|
||||
"rating": "G",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Planetary Vehicle Hangar"
|
||||
},
|
||||
{
|
||||
"class": 1,
|
||||
"rating": "C",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Scanner",
|
||||
"name": "Advanced Discovery Scanner"
|
||||
},
|
||||
{
|
||||
"class": 1,
|
||||
"rating": "C",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Scanner",
|
||||
"name": "Detailed Surface Scanner"
|
||||
}
|
||||
]
|
||||
},
|
||||
"stats": {
|
||||
"class": 2,
|
||||
"hullCost": 6135660,
|
||||
"speed": 250,
|
||||
"boost": 340,
|
||||
"boostEnergy": 13,
|
||||
"agility": 6,
|
||||
"baseShieldStrength": 140,
|
||||
"baseArmour": 210,
|
||||
"hullMass": 280,
|
||||
"masslock": 11,
|
||||
"pipSpeed": 0.13,
|
||||
"moduleCostMultiplier": 1,
|
||||
"fuelCapacity": 32,
|
||||
"cargoCapacity": 40,
|
||||
"ladenMass": 435.26,
|
||||
"armour": 378,
|
||||
"shield": 113.43,
|
||||
"shieldCells": 0,
|
||||
"totalCost": 48402550,
|
||||
"unladenMass": 363.26,
|
||||
"totalDpe": 0,
|
||||
"totalExplDpe": 0,
|
||||
"totalKinDpe": 0,
|
||||
"totalThermDpe": 0,
|
||||
"totalDps": 0,
|
||||
"totalExplDps": 0,
|
||||
"totalKinDps": 0,
|
||||
"totalThermDps": 0,
|
||||
"totalSDps": 0,
|
||||
"totalExplSDps": 0,
|
||||
"totalKinSDps": 0,
|
||||
"totalThermSDps": 0,
|
||||
"totalEps": 1.2,
|
||||
"totalHps": 1,
|
||||
"shieldExplRes": 0.5,
|
||||
"shieldKinRes": 0.6,
|
||||
"shieldThermRes": 1.2,
|
||||
"hullExplRes": 1.4,
|
||||
"hullKinRes": 1.2,
|
||||
"hullThermRes": 1,
|
||||
"powerAvailable": 20.41,
|
||||
"powerRetracted": 11.91,
|
||||
"powerDeployed": 11.91,
|
||||
"unladenRange": 50.45,
|
||||
"fullTankRange": 47.03,
|
||||
"ladenRange": 42.71,
|
||||
"unladenFastestRange": 317.24,
|
||||
"ladenFastestRange": 287.02,
|
||||
"maxJumpCount": 7,
|
||||
"topSpeed": 274.01,
|
||||
"topBoost": 372.65
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,552 +0,0 @@
|
||||
{
|
||||
"cargo": {
|
||||
"capacity": 32
|
||||
},
|
||||
"free": false,
|
||||
"fuel": {
|
||||
"main": {
|
||||
"capacity": 128
|
||||
},
|
||||
"reserve": {
|
||||
"capacity": 0.81
|
||||
}
|
||||
},
|
||||
"id": 31,
|
||||
"modules": {
|
||||
"Armour": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128049346,
|
||||
"name": "BelugaLiner_Armour_Grade1",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"Bobble01": [],
|
||||
"Bobble02": [],
|
||||
"Bobble03": [],
|
||||
"Bobble04": [],
|
||||
"Bobble05": [],
|
||||
"Bobble06": [],
|
||||
"Bobble07": [],
|
||||
"Bobble08": [],
|
||||
"Bobble09": [],
|
||||
"Bobble10": [],
|
||||
"Decal1": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128667757,
|
||||
"name": "Decal_Explorer_Ranger",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"Decal2": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128667742,
|
||||
"name": "Decal_Combat_Deadly",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"Decal3": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128667750,
|
||||
"name": "Decal_Trade_Tycoon",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"EngineColour": [],
|
||||
"FrameShiftDrive": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064132,
|
||||
"modifiers": {
|
||||
"engineerID": 300100,
|
||||
"id": 175,
|
||||
"modifiers": [
|
||||
{
|
||||
"name": "mod_mass",
|
||||
"type": 1,
|
||||
"value": 0.4457540512085
|
||||
},
|
||||
{
|
||||
"name": "mod_health",
|
||||
"type": 1,
|
||||
"value": -0.24584779143333
|
||||
},
|
||||
{
|
||||
"name": "mod_passive_power",
|
||||
"type": 1,
|
||||
"value": 0.24457727372646
|
||||
},
|
||||
{
|
||||
"name": "mod_fsd_optimised_mass",
|
||||
"type": 1,
|
||||
"value": 0.49257898330688
|
||||
},
|
||||
{
|
||||
"name": "mod_fsd_max_fuel_per_jump",
|
||||
"type": 2,
|
||||
"value": 0.028505677357316
|
||||
},
|
||||
{
|
||||
"name": "mod_fsd_heat_rate",
|
||||
"type": 2,
|
||||
"value": -0.079360365867615
|
||||
}
|
||||
],
|
||||
"moduleTags": [
|
||||
16
|
||||
],
|
||||
"recipeID": 128673694,
|
||||
"slotIndex": 53
|
||||
},
|
||||
"name": "Int_Hyperdrive_Size7_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"recipeLevel": 5,
|
||||
"recipeName": "FSD_LongRange",
|
||||
"recipeValue": 0,
|
||||
"unloaned": 0,
|
||||
"value": 46160201
|
||||
}
|
||||
},
|
||||
"FuelTank": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064352,
|
||||
"name": "Int_FuelTank_Size7_Class3",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 1602822,
|
||||
"value": 1602822
|
||||
}
|
||||
},
|
||||
"LifeSupport": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064174,
|
||||
"name": "Int_LifeSupport_Size8_Class2",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 1569565
|
||||
}
|
||||
},
|
||||
"MainEngines": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064094,
|
||||
"modifiers": {
|
||||
"engineerID": 300100,
|
||||
"id": 253,
|
||||
"modifiers": [
|
||||
{
|
||||
"name": "mod_engine_mass_curve_multiplier",
|
||||
"type": 1,
|
||||
"value": 0.098235413432121
|
||||
},
|
||||
{
|
||||
"name": "mod_engine_heat",
|
||||
"type": 1,
|
||||
"value": 0.18069696426392
|
||||
},
|
||||
{
|
||||
"name": "mod_passive_power",
|
||||
"type": 1,
|
||||
"value": 0.033788848668337
|
||||
},
|
||||
{
|
||||
"name": "mod_health",
|
||||
"type": 1,
|
||||
"value": -0.056404989212751
|
||||
},
|
||||
{
|
||||
"name": "mod_engine_mass_curve",
|
||||
"type": 1,
|
||||
"value": -0.027384582906961
|
||||
},
|
||||
{
|
||||
"name": "mod_engine_heat",
|
||||
"type": 2,
|
||||
"value": -0.072683908045292
|
||||
}
|
||||
],
|
||||
"moduleTags": [
|
||||
17
|
||||
],
|
||||
"recipeID": 128673655,
|
||||
"slotIndex": 52
|
||||
},
|
||||
"name": "Int_Engine_Size7_Class2",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"recipeLevel": 1,
|
||||
"recipeName": "Engine_Dirty",
|
||||
"recipeValue": 0,
|
||||
"unloaned": 0,
|
||||
"value": 1709638
|
||||
}
|
||||
},
|
||||
"MediumHardpoint1": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128049436,
|
||||
"name": "Hpt_BeamLaser_Turret_Medium",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 1889910
|
||||
}
|
||||
},
|
||||
"MediumHardpoint2": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128049436,
|
||||
"name": "Hpt_BeamLaser_Turret_Medium",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 1889910
|
||||
}
|
||||
},
|
||||
"MediumHardpoint3": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128049460,
|
||||
"name": "Hpt_MultiCannon_Gimbal_Medium",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 51300
|
||||
}
|
||||
},
|
||||
"MediumHardpoint4": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128049460,
|
||||
"name": "Hpt_MultiCannon_Gimbal_Medium",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 51300
|
||||
}
|
||||
},
|
||||
"MediumHardpoint5": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128049460,
|
||||
"name": "Hpt_MultiCannon_Gimbal_Medium",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 51300
|
||||
}
|
||||
},
|
||||
"PaintJob": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128732290,
|
||||
"name": "PaintJob_BelugaLiner_Tactical_White",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"PlanetaryApproachSuite": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128672317,
|
||||
"name": "Int_PlanetApproachSuite",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 450,
|
||||
"value": 450
|
||||
}
|
||||
},
|
||||
"PowerDistributor": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064207,
|
||||
"name": "Int_PowerDistributor_Size6_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 3128120
|
||||
}
|
||||
},
|
||||
"PowerPlant": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064057,
|
||||
"modifiers": {
|
||||
"engineerID": 300100,
|
||||
"id": 277,
|
||||
"modifiers": [
|
||||
{
|
||||
"name": "mod_powerplant_power",
|
||||
"type": 1,
|
||||
"value": 0.054692290723324
|
||||
},
|
||||
{
|
||||
"name": "mod_health",
|
||||
"type": 1,
|
||||
"value": -0.033690698444843
|
||||
},
|
||||
{
|
||||
"name": "mod_powerplant_heat",
|
||||
"type": 1,
|
||||
"value": 0.027470717206597
|
||||
},
|
||||
{
|
||||
"name": "mod_powerplant_heat",
|
||||
"type": 2,
|
||||
"value": -0.056317910552025
|
||||
}
|
||||
],
|
||||
"moduleTags": [
|
||||
18
|
||||
],
|
||||
"recipeID": 128673765,
|
||||
"slotIndex": 51
|
||||
},
|
||||
"name": "Int_Powerplant_Size6_Class5",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"recipeLevel": 1,
|
||||
"recipeName": "PowerPlant_Boosted",
|
||||
"recipeValue": 0,
|
||||
"unloaned": 0,
|
||||
"value": 14561578
|
||||
}
|
||||
},
|
||||
"Radar": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064239,
|
||||
"name": "Int_Sensors_Size5_Class2",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 71500
|
||||
}
|
||||
},
|
||||
"Slot01_Size6": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128666681,
|
||||
"name": "Int_FuelScoop_Size6_Class5",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 25887249
|
||||
}
|
||||
},
|
||||
"Slot02_Size6": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064287,
|
||||
"name": "Int_ShieldGenerator_Size6_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 14561578
|
||||
}
|
||||
},
|
||||
"Slot03_Size6": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128727927,
|
||||
"name": "Int_PassengerCabin_Size6_Class2",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 165808,
|
||||
"value": 165808
|
||||
}
|
||||
},
|
||||
"Slot04_Size6": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128727928,
|
||||
"name": "Int_PassengerCabin_Size6_Class3",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 497429
|
||||
}
|
||||
},
|
||||
"Slot05_Size5": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128727925,
|
||||
"name": "Int_PassengerCabin_Size5_Class4",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 1492286
|
||||
}
|
||||
},
|
||||
"Slot06_Size5": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064342,
|
||||
"name": "Int_CargoRack_Size5_Class1",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 100409,
|
||||
"value": 100409
|
||||
}
|
||||
},
|
||||
"Slot07_Size4": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128727922,
|
||||
"name": "Int_PassengerCabin_Size4_Class1",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 17059
|
||||
}
|
||||
},
|
||||
"Slot08_Size3": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128667632,
|
||||
"name": "Int_Repairer_Size3_Class5",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 2361960
|
||||
}
|
||||
},
|
||||
"Slot09_Size3": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128672289,
|
||||
"name": "Int_BuggyBay_Size2_Class2",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 19440
|
||||
}
|
||||
},
|
||||
"Slot10_Size3": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128666634,
|
||||
"name": "Int_DetailedSurfaceScanner_Tiny",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 225000
|
||||
}
|
||||
},
|
||||
"Slot11_Size3": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128663561,
|
||||
"name": "Int_StellarBodyDiscoveryScanner_Advanced",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 1390500
|
||||
}
|
||||
},
|
||||
"TinyHardpoint1": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128049513,
|
||||
"name": "Hpt_ChaffLauncher_Tiny",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 7650
|
||||
}
|
||||
},
|
||||
"TinyHardpoint2": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128668536,
|
||||
"name": "Hpt_ShieldBooster_Size0_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 252900
|
||||
}
|
||||
},
|
||||
"TinyHardpoint3": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128668536,
|
||||
"name": "Hpt_ShieldBooster_Size0_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 252900
|
||||
}
|
||||
},
|
||||
"TinyHardpoint4": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128668536,
|
||||
"name": "Hpt_ShieldBooster_Size0_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 281000
|
||||
}
|
||||
},
|
||||
"TinyHardpoint5": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128668536,
|
||||
"name": "Hpt_ShieldBooster_Size0_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 281000
|
||||
}
|
||||
},
|
||||
"TinyHardpoint6": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128668536,
|
||||
"name": "Hpt_ShieldBooster_Size0_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 281000
|
||||
}
|
||||
},
|
||||
"WeaponColour": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128732194,
|
||||
"name": "WeaponCustomisation_Purple",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "BelugaLiner",
|
||||
"value": {
|
||||
"hull": 71688743,
|
||||
"modules": 120812762,
|
||||
"unloaned": 1869489
|
||||
}
|
||||
}
|
||||
@@ -1,314 +0,0 @@
|
||||
{
|
||||
"cargo": {
|
||||
"capacity": 264
|
||||
},
|
||||
"free": false,
|
||||
"fuel": {
|
||||
"main": {
|
||||
"capacity": 32
|
||||
},
|
||||
"reserve": {
|
||||
"capacity": 0.52
|
||||
}
|
||||
},
|
||||
"id": 4,
|
||||
"modules": {
|
||||
"Armour": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128049298,
|
||||
"name": "Type7_Armour_Grade1",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"Bobble01": [],
|
||||
"Bobble02": [],
|
||||
"Bobble03": [],
|
||||
"Bobble04": [],
|
||||
"Bobble05": [],
|
||||
"Bobble06": [],
|
||||
"Bobble07": [],
|
||||
"Bobble08": [],
|
||||
"Bobble09": [],
|
||||
"Bobble10": [],
|
||||
"Decal1": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128667746,
|
||||
"name": "Decal_Trade_Dealer",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"Decal2": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128667738,
|
||||
"name": "Decal_Combat_Competent",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"Decal3": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128667753,
|
||||
"name": "Decal_Explorer_Scout",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"EngineColour": [],
|
||||
"FrameShiftDrive": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064122,
|
||||
"name": "Int_Hyperdrive_Size5_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 5103953
|
||||
}
|
||||
},
|
||||
"FuelTank": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064350,
|
||||
"name": "Int_FuelTank_Size5_Class3",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 97754,
|
||||
"value": 97754
|
||||
}
|
||||
},
|
||||
"LifeSupport": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064154,
|
||||
"name": "Int_LifeSupport_Size4_Class2",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 28373
|
||||
}
|
||||
},
|
||||
"MainEngines": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064087,
|
||||
"name": "Int_Engine_Size5_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 5103953
|
||||
}
|
||||
},
|
||||
"PaintJob": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128671422,
|
||||
"name": "PaintJob_Type7_Tactical_White",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"PlanetaryApproachSuite": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128672317,
|
||||
"name": "Int_PlanetApproachSuite",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 500,
|
||||
"value": 500
|
||||
}
|
||||
},
|
||||
"PowerDistributor": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064192,
|
||||
"name": "Int_PowerDistributor_Size3_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 158331
|
||||
}
|
||||
},
|
||||
"PowerPlant": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064047,
|
||||
"name": "Int_Powerplant_Size4_Class5",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 1610080
|
||||
}
|
||||
},
|
||||
"Radar": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064229,
|
||||
"name": "Int_Sensors_Size3_Class2",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 10133
|
||||
}
|
||||
},
|
||||
"Slot01_Size6": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064343,
|
||||
"name": "Int_CargoRack_Size6_Class1",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 362591
|
||||
}
|
||||
},
|
||||
"Slot02_Size6": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064343,
|
||||
"name": "Int_CargoRack_Size6_Class1",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 362591
|
||||
}
|
||||
},
|
||||
"Slot03_Size5": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064343,
|
||||
"name": "Int_CargoRack_Size6_Class1",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 362591
|
||||
}
|
||||
},
|
||||
"Slot04_Size5": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064342,
|
||||
"name": "Int_CargoRack_Size5_Class1",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 111566,
|
||||
"value": 111566
|
||||
}
|
||||
},
|
||||
"Slot05_Size4": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064342,
|
||||
"name": "Int_CargoRack_Size5_Class1",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 111566,
|
||||
"value": 111566
|
||||
}
|
||||
},
|
||||
"Slot06_Size4": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064279,
|
||||
"name": "Int_ShieldGenerator_Size5_Class2",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 189035
|
||||
}
|
||||
},
|
||||
"Slot07_Size2": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128049549,
|
||||
"name": "Int_DockingComputer_Standard",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 4500
|
||||
}
|
||||
},
|
||||
"Slot08_Size2": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064340,
|
||||
"name": "Int_CargoRack_Size3_Class1",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"unloaned": 0,
|
||||
"value": 10563
|
||||
}
|
||||
},
|
||||
"SmallHardpoint1": [],
|
||||
"SmallHardpoint2": [],
|
||||
"SmallHardpoint3": [],
|
||||
"SmallHardpoint4": [],
|
||||
"TinyHardpoint1": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128668536,
|
||||
"name": "Hpt_ShieldBooster_Size0_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 281000
|
||||
}
|
||||
},
|
||||
"TinyHardpoint2": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128668536,
|
||||
"name": "Hpt_ShieldBooster_Size0_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 281000
|
||||
}
|
||||
},
|
||||
"TinyHardpoint3": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128668536,
|
||||
"name": "Hpt_ShieldBooster_Size0_Class5",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 281000
|
||||
}
|
||||
},
|
||||
"TinyHardpoint4": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128049513,
|
||||
"name": "Hpt_ChaffLauncher_Tiny",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"unloaned": 0,
|
||||
"value": 8500
|
||||
}
|
||||
},
|
||||
"WeaponColour": []
|
||||
},
|
||||
"name": "Type7",
|
||||
"value": {
|
||||
"hull": 16780009,
|
||||
"modules": 14479580,
|
||||
"unloaned": 321386
|
||||
}
|
||||
}
|
||||
@@ -1,225 +0,0 @@
|
||||
{
|
||||
"free": false,
|
||||
"id": 2,
|
||||
"modules": {
|
||||
"Armour": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128049280,
|
||||
"name": "CobraMkIII_Armour_Grade1",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"FrameShiftDrive": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064117,
|
||||
"name": "Int_Hyperdrive_Size4_Class5",
|
||||
"on": true,
|
||||
"priority": 4,
|
||||
"value": 1610080
|
||||
}
|
||||
},
|
||||
"FuelTank": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064349,
|
||||
"name": "Int_FuelTank_Size4_Class3",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"value": 24734
|
||||
}
|
||||
},
|
||||
"LifeSupport": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064149,
|
||||
"name": "Int_LifeSupport_Size3_Class2",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"value": 10133
|
||||
}
|
||||
},
|
||||
"MainEngines": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064079,
|
||||
"name": "Int_Engine_Size4_Class2",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"value": 59633
|
||||
}
|
||||
},
|
||||
"PaintJob": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128741033,
|
||||
"name": "PaintJob_CobraMKIII_Corrosive_05",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"PlanetaryApproachSuite": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128672317,
|
||||
"name": "Int_PlanetApproachSuite",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"value": 500
|
||||
}
|
||||
},
|
||||
"PowerDistributor": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064179,
|
||||
"name": "Int_PowerDistributor_Size1_Class2",
|
||||
"on": true,
|
||||
"priority": 2,
|
||||
"value": 1293
|
||||
}
|
||||
},
|
||||
"PowerPlant": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064037,
|
||||
"name": "Int_Powerplant_Size2_Class5",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"value": 160224
|
||||
}
|
||||
},
|
||||
"Radar": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128064229,
|
||||
"name": "Int_Sensors_Size3_Class2",
|
||||
"on": true,
|
||||
"priority": 0,
|
||||
"value": 10133
|
||||
}
|
||||
},
|
||||
"ShipID0": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128758976,
|
||||
"name": "Nameplate_ShipID_Black",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"ShipID1": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128758976,
|
||||
"name": "Nameplate_ShipID_Black",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"ShipKitBumper": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128740698,
|
||||
"name": "CobraMkIII_ShipkitRaider1_Bumper1",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"ShipKitSpoiler": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128740701,
|
||||
"name": "CobraMkIII_ShipkitRaider1_Spoiler1",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"ShipKitTail": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128740705,
|
||||
"name": "CobraMkIII_ShipkitRaider1_Tail2",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"ShipKitWings": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128740707,
|
||||
"name": "CobraMkIII_ShipkitRaider1_Wings1",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"ShipName0": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128758944,
|
||||
"name": "Nameplate_Explorer01_Black",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"ShipName1": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128758944,
|
||||
"name": "Nameplate_Explorer01_Black",
|
||||
"on": true,
|
||||
"priority": 1,
|
||||
"value": 0
|
||||
}
|
||||
},
|
||||
"Slot01_Size4": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128666663,
|
||||
"name": "Int_FuelScoop_Size4_Class3",
|
||||
"on": true,
|
||||
"priority": 2,
|
||||
"value": 178898
|
||||
}
|
||||
},
|
||||
"Slot02_Size4": [],
|
||||
"Slot03_Size4": [],
|
||||
"Slot04_Size2": [],
|
||||
"Slot05_Size2": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128663561,
|
||||
"name": "Int_StellarBodyDiscoveryScanner_Advanced",
|
||||
"on": true,
|
||||
"priority": 2,
|
||||
"value": 1545000
|
||||
}
|
||||
},
|
||||
"Slot06_Size2": {
|
||||
"module": {
|
||||
"free": false,
|
||||
"id": 128666634,
|
||||
"name": "Int_DetailedSurfaceScanner_Tiny",
|
||||
"on": true,
|
||||
"priority": 2,
|
||||
"value": 250000
|
||||
}
|
||||
}
|
||||
},
|
||||
"name": "CobraMkIII",
|
||||
"value": {
|
||||
"hull": 205287,
|
||||
"modules": 3850628,
|
||||
"unloaned": 1751109
|
||||
}
|
||||
}
|
||||
@@ -1,327 +0,0 @@
|
||||
{
|
||||
"$schema": "http://cdn.coriolis.io/schemas/ship-loadout/4.json#",
|
||||
"name": "Multi-purpose Imperial Courier",
|
||||
"ship": "Imperial Courier",
|
||||
"references": [
|
||||
{
|
||||
"name": "Coriolis.io",
|
||||
"url": "https://coriolis.edcd.io/outfit/imperial_courier?code=0patzF5l0das8f31a1a270202000e402t0101-2f.AwRj4zKA.CwRgDBldLiQ%3D.H4sIAAAAAAAAA12OP0tCYRjFj9fuVbvF1du9ekkT8s%2FkIg4NElyIBBd321yaGvwUQTS3N7UFfYygIT9EoyQUJA36ns47XJCWA%2B%2Fz%2Bz3Pe3ImBbDNKaqNPSBoGrL4ngfomKpFGiJ%2BLgHteR1IPjxJT5pF11uSeXNsJVcRfgdC92syWUuK0iMdKZqrjJ%2F0aoA71lJ5oKf38knWcCiptCPdhJIerdS00vlK0qktlqoj983UmqqHjQ33VsW8eazFmaTyULP2hQ4lX8LBme6g%2F6v0TTdbxJ2KhdEIaCw15MF%2FNB0L%2BS2hwEwyFM8KgP%2BqEpWWA3Qu9Z3z9kPWHzakt7Dt%2BAeD7ghSTgEAAA%3D%3D&bn=Multi-purpose%20Imperial%20Courier",
|
||||
"code": "0patzF5l0das8f31a1a270202000e402t0101-2f.AwRj4zKA.CwRgDBldLiQ=.H4sIAAAAAAAAA12OP0tCYRjFj9fuVbvF1du9ekkT8s/kIg4NElyIBBd321yaGvwUQTS3N7UFfYygIT9EoyQUJA36ns47XJCWA+/z+z3Pe3ImBbDNKaqNPSBoGrL4ngfomKpFGiJ+LgHteR1IPjxJT5pF11uSeXNsJVcRfgdC92syWUuK0iMdKZqrjJ/0aoA71lJ5oKf38knWcCiptCPdhJIerdS00vlK0qktlqoj983UmqqHjQ33VsW8eazFmaTyULP2hQ4lX8LBme6g/6v0TTdbxJ2KhdEIaCw15MF/NB0L+S2hwEwyFM8KgP+qEpWWA3Qu9Z3z9kPWHzakt7Dt+AeD7ghSTgEAAA==",
|
||||
"shipId": "imperial_courier"
|
||||
}
|
||||
],
|
||||
"components": {
|
||||
"standard": {
|
||||
"bulkheads": "Lightweight Alloy",
|
||||
"cargoHatch": {
|
||||
"enabled": false,
|
||||
"priority": 5
|
||||
},
|
||||
"powerPlant": {
|
||||
"class": 4,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"modifications": {
|
||||
"pgen": 1052,
|
||||
"integrity": -482,
|
||||
"eff": 974
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 63,
|
||||
"name": "Overcharged",
|
||||
"grade": 1
|
||||
}
|
||||
},
|
||||
"thrusters": {
|
||||
"class": 3,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"name": "Enhanced Performance",
|
||||
"modifications": {
|
||||
"optmul": 2476,
|
||||
"thermload": 7023,
|
||||
"power": 1763,
|
||||
"integrity": 165,
|
||||
"optmass": -667
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 22,
|
||||
"name": "Dirty",
|
||||
"grade": 4
|
||||
}
|
||||
},
|
||||
"frameShiftDrive": {
|
||||
"class": 3,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"modifications": {
|
||||
"mass": 4082,
|
||||
"integrity": -2422,
|
||||
"power": 1782,
|
||||
"optmass": 4927
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 26,
|
||||
"name": "Increased range",
|
||||
"grade": 5
|
||||
}
|
||||
},
|
||||
"lifeSupport": {
|
||||
"class": 1,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
},
|
||||
"powerDistributor": {
|
||||
"class": 3,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
},
|
||||
"sensors": {
|
||||
"class": 2,
|
||||
"rating": "D",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
},
|
||||
"fuelTank": {
|
||||
"class": 3,
|
||||
"rating": "C",
|
||||
"enabled": true,
|
||||
"priority": 1
|
||||
}
|
||||
},
|
||||
"hardpoints": [
|
||||
{
|
||||
"class": 2,
|
||||
"rating": "F",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Pulse Laser",
|
||||
"mount": "Fixed",
|
||||
"modifications": {
|
||||
"rof": 5931,
|
||||
"damage": -184,
|
||||
"jitter": 50,
|
||||
"distdraw": -4689,
|
||||
"piercing": 3328
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 89,
|
||||
"name": "Rapid fire",
|
||||
"grade": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"class": 2,
|
||||
"rating": "F",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Pulse Laser",
|
||||
"mount": "Fixed",
|
||||
"modifications": {
|
||||
"rof": 4715,
|
||||
"damage": -97,
|
||||
"jitter": 30,
|
||||
"distdraw": -4548,
|
||||
"piercing": 1057,
|
||||
"integrity": 319
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 89,
|
||||
"name": "Rapid fire",
|
||||
"grade": 5
|
||||
}
|
||||
},
|
||||
{
|
||||
"class": 2,
|
||||
"rating": "F",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Multi-cannon",
|
||||
"mount": "Gimballed",
|
||||
"modifications": {
|
||||
"damage": 2437,
|
||||
"distdraw": 5487,
|
||||
"rof": 1120,
|
||||
"jitter": 58,
|
||||
"thermload": 1346,
|
||||
"power": 1009,
|
||||
"integrity": -202,
|
||||
"ammo": -2000
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 88,
|
||||
"name": "Overcharged",
|
||||
"grade": 3,
|
||||
"special": {
|
||||
"id": 3,
|
||||
"name": "Corrosive shell"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"utility": [
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "I",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Heat Sink Launcher",
|
||||
"name": "Heat Sink Launcher",
|
||||
"modifications": {
|
||||
"ammo": 5000,
|
||||
"mass": 17684,
|
||||
"reload": 9707
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 37,
|
||||
"name": "Ammo capacity",
|
||||
"grade": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "I",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Heat Sink Launcher",
|
||||
"name": "Heat Sink Launcher",
|
||||
"modifications": {
|
||||
"ammo": 5000,
|
||||
"mass": 18520,
|
||||
"reload": 8715
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 37,
|
||||
"name": "Ammo capacity",
|
||||
"grade": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "I",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Chaff Launcher",
|
||||
"name": "Chaff Launcher"
|
||||
},
|
||||
{
|
||||
"class": 0,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Frame Shift Wake Scanner"
|
||||
}
|
||||
],
|
||||
"internal": [
|
||||
{
|
||||
"class": 3,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Shield Generator",
|
||||
"modifications": {
|
||||
"optmul": 1888,
|
||||
"explres": 455,
|
||||
"kinres": 546,
|
||||
"thermres": 1092,
|
||||
"brokenregen": -2614,
|
||||
"regen": -876,
|
||||
"distdraw": 463
|
||||
},
|
||||
"blueprint": {
|
||||
"id": 77,
|
||||
"name": "Reinforced",
|
||||
"grade": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"class": 3,
|
||||
"rating": "A",
|
||||
"enabled": true,
|
||||
"priority": 1,
|
||||
"group": "Fuel Scoop"
|
||||
},
|
||||
{
|
||||
"class": 2,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Cargo Rack"
|
||||
},
|
||||
{
|
||||
"class": 2,
|
||||
"rating": "E",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Cargo Rack"
|
||||
},
|
||||
null,
|
||||
{
|
||||
"class": 1,
|
||||
"rating": "C",
|
||||
"enabled": true,
|
||||
"priority": 2,
|
||||
"group": "Scanner",
|
||||
"name": "Advanced Discovery Scanner"
|
||||
}
|
||||
]
|
||||
},
|
||||
"stats": {
|
||||
"class": 1,
|
||||
"hullCost": 2481550,
|
||||
"speed": 280,
|
||||
"boost": 380,
|
||||
"boostEnergy": 10,
|
||||
"agility": 6,
|
||||
"baseShieldStrength": 200,
|
||||
"baseArmour": 80,
|
||||
"hullMass": 35,
|
||||
"masslock": 7,
|
||||
"pipSpeed": 0.05,
|
||||
"moduleCostMultiplier": 1,
|
||||
"fuelCapacity": 8,
|
||||
"cargoCapacity": 8,
|
||||
"ladenMass": 104.25,
|
||||
"armour": 144,
|
||||
"shield": 404.19,
|
||||
"shieldCells": 0,
|
||||
"totalCost": 14059860,
|
||||
"unladenMass": 88.25,
|
||||
"totalDpe": 32.25,
|
||||
"totalExplDpe": 0,
|
||||
"totalKinDpe": 9.41,
|
||||
"totalThermDpe": 22.84,
|
||||
"totalDps": 53.8,
|
||||
"totalExplDps": 0,
|
||||
"totalKinDps": 17.44,
|
||||
"totalThermDps": 36.35,
|
||||
"totalSDps": 48.99,
|
||||
"totalExplSDps": 0,
|
||||
"totalKinSDps": 12.64,
|
||||
"totalThermSDps": 36.35,
|
||||
"totalEps": 9.84,
|
||||
"totalHps": 12.28,
|
||||
"shieldExplRes": 0.48,
|
||||
"shieldKinRes": 0.55,
|
||||
"shieldThermRes": 1.09,
|
||||
"hullExplRes": 1.4,
|
||||
"hullKinRes": 1.2,
|
||||
"hullThermRes": 1,
|
||||
"powerAvailable": 17.24,
|
||||
"powerRetracted": 11.3,
|
||||
"powerDeployed": 16.41,
|
||||
"unladenRange": 25.57,
|
||||
"fullTankRange": 23.92,
|
||||
"ladenRange": 22.09,
|
||||
"unladenFastestRange": 116.23,
|
||||
"ladenFastestRange": 107,
|
||||
"maxJumpCount": 5,
|
||||
"topSpeed": 386.56,
|
||||
"topBoost": 524.62
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
[
|
||||
{
|
||||
"buildText": "[Imaginary Ship]\nbla bla",
|
||||
"errorMsg": "No such ship found: \"Imaginary Ship\""
|
||||
},
|
||||
{
|
||||
"buildText": "[Viper]\nS: 1F/F Pulse Laser\nsome un-parseable nonsense\nS: 1F/F Pulse Laser\n",
|
||||
"errorMsg": "Error parsing: \"some un-parseable nonsense\""
|
||||
},
|
||||
{
|
||||
"buildText": "[Sidewinder]\nS: 2F/F Pulse Laser\nS: 1F/F Pulse Laser\n",
|
||||
"errorMsg": "2F Pulse Laser exceeds slot size: \"S: 2F/F Pulse Laser\""
|
||||
},
|
||||
{
|
||||
"buildText": "[Sidewinder]\nL: 2F/F Pulse Laser\nS: 1F/F Pulse Laser\n",
|
||||
"errorMsg": "No hardpoint slot available for: \"L: 2F/F Pulse Laser\""
|
||||
},
|
||||
{
|
||||
"buildText": "[Sidewinder]\nS: 1F/F Magic Thing\nS: 1F/F Pulse Laser\n",
|
||||
"errorMsg": "Unknown component: \"S: 1F/F Magic Thing\""
|
||||
}
|
||||
]
|
||||
@@ -1,32 +0,0 @@
|
||||
[
|
||||
{
|
||||
"shipId": "anaconda",
|
||||
"buildName": "Imported Anaconda",
|
||||
"buildCode": "0pyttFolodDsyf5------1717--------05044j-03--2h--00.Iw18ZlA=.Aw18ZlA=.",
|
||||
"buildText": "[Anaconda]\nS: 1F/F Pulse Laser\nS: 1F/F Pulse Laser\n\nBH: 1I Lightweight Alloy\nRB: 8E Power Plant\nTM: 7E Thrusters\nFH: 6E Frame Shift Drive\nEC: 5E Life Support\nPC: 8E Power Distributor\nSS: 8E Sensors\nFS: 5C Fuel Tank (Capacity: 32)\n\n7: 6E Cargo Rack (Capacity: 64)\n6: 5E Cargo Rack (Capacity: 32)\n6: 6E Shield Generator\n5: 4E Cargo Rack (Capacity: 16)\n4: 1E Basic Discovery Scanner\n2: 1E Cargo Rack (Capacity: 2)\n"
|
||||
},
|
||||
{
|
||||
"shipId": "anaconda",
|
||||
"buildName": "Imported Anaconda",
|
||||
"buildCode": "0pyttFolodDsyf5------1717--------05044j-03--2h--00.Iw18ZlA=.Aw18ZlA=.",
|
||||
"buildText": "\n\n \t[Anaconda]\nS: 1F/F Pulse Laser\nS: 1F/F Pulse Laser\n\nBH: 1I Lightweight Alloy\nRB: 8E Power Plant\nTM: 7E Thrusters\nFH: 6E Frame Shift Drive\nEC: 5E Life Support\nPC: 8E Power Distributor\nSS: 8E Sensors\nFS: 5C Fuel Tank (Capacity: 32)\n\n7: 6E Cargo Rack (Capacity: 64)\n6: 5E Cargo Rack (Capacity: 32)\n6: 6E Shield Generator\n5: 4E Cargo Rack (Capacity: 16)\n4: 1E Basic Discovery Scanner\n2: 1E Cargo Rack (Capacity: 2)\n"
|
||||
},
|
||||
{
|
||||
"shipId": "cobra_mk_iii",
|
||||
"buildName": "Imported Cobra Mk III",
|
||||
"buildCode": "0patcFeldd5sdf41712222503040202490f242h.Iw1-kA==.Aw1-kA==.",
|
||||
"buildText": "[Cobra Mk III]\nM: 1F/F Pulse Laser\nM: 1G/G Burst Laser\nS: 1E/T Fragment Cannon\nS: 1G/T Multi-cannon\nU: 0I Point Defence\nU: 0A Shield Booster\n\nBH: 1I Lightweight Alloy\nRB: 4A Power Plant\nTM: 4C Thrusters\nFH: 4E Frame Shift Drive\nEC: 3D Life Support\nPC: 2A Power Distributor\nSS: 3D Sensors\nFS: 4C Fuel Tank (Capacity: 16)\n\n4: 3E Cargo Rack (Capacity: 8)\n4: 3E Cargo Rack (Capacity: 8)\n4: 4E Shield Generator\n2: 2C Auto Field-Maintenance Unit\n2: 1E Standard Docking Computer\n2: 1E Basic Discovery Scanner\n---\nShield: 112.29 MJ\nPower : 10.45 MW retracted (67%)\n 12.16 MW deployed (78%)\n 15.60 MW available\nCargo : 16 T\nFuel : 16 T\nMass : 235.5 T empty\n 267.5 T full\nRange : 10.69 LY unladen\n 10.05 LY laden\nPrice : 2,929,040 CR\nRe-Buy: 146,452 CR @ 95% insurance\n"
|
||||
},
|
||||
{
|
||||
"shipId": "type_9_heavy",
|
||||
"buildName": "Imported Type-9 Heavy",
|
||||
"buildCode": "3pftsFklkdisif57e2k2f2h110001020306054j03022f01242i.Iw18eQ==.Aw18eQ==.",
|
||||
"buildText": "[Type-9 Heavy]\nM: 2D/G Fragment Cannon\nM: 2I/F Mine Launcher\nM: 2B/FD Missile Rack\nS: 1I/FS Torpedo Pylon\nS: 1F/F Burst Laser\nU: 0I Chaff Launcher\nU: 0F Electronic Countermeasure\nU: 0I Heat Sink Launcher\nU: 0I Point Defence\n\nBH: 1I Mirrored Surface Composite\nRB: 5A Power Plant\nTM: 7D Thrusters\nFH: 6A Frame Shift Drive\nEC: 5A Life Support\nPC: 4D Power Distributor\nSS: 4D Sensors\nFS: 5C Fuel Tank (Capacity: 32)\n\n8: 7E Cargo Rack (Capacity: 128)\n7: 6E Cargo Rack (Capacity: 64)\n6: 6E Shield Generator\n5: 4E Cargo Rack (Capacity: 16)\n4: 3E Cargo Rack (Capacity: 8)\n4: 1C Advanced Discovery Scanner\n3: 2E Cargo Rack (Capacity: 4)\n3: 1E Standard Docking Computer\n2: 1C Detailed Surface Scanner\n"
|
||||
},
|
||||
{
|
||||
"shipId": "vulture",
|
||||
"buildName": "Imported Vulture",
|
||||
"buildCode": "4patfFalddksif31e1e0e0j04044a0n532jf1.Iw19kA==.Aw19kA==.",
|
||||
"buildText": "[Vulture]\nL: 3E/G Pulse Laser\nL: 3E/G Pulse Laser\nU: 0A Frame Shift Wake Scanner\nU: 0A Kill Warrant Scanner\nU: 0A Shield Booster\nU: 0A Shield Booster\n\nBH: 1I Reactive Surface Composite\nRB: 4A Power Plant\nTM: 5A Thrusters\nFH: 4A Frame Shift Drive\nEC: 3D Life Support\nPC: 5A Power Distributor\nSS: 4D Sensors\nFS: 3C Fuel Tank (Capacity: 8)\n\n5: 5A Shield Generator\n4: 4A Auto Field-Maintenance Unit\n2: 2A Shield Cell Bank\n1: 1A Fuel Scoop\n1: 1C Fuel Tank (Capacity: 2)"
|
||||
}
|
||||
]
|
||||
@@ -1,50 +0,0 @@
|
||||
{
|
||||
"type_6_transporter": {
|
||||
"Cargo": "A0p0tdFal8d8s8f4-----04040303430101.Iw1/kA==.Aw1/kA==.",
|
||||
"Miner": "A0p5tdFal8d8s8f42l2l---040403451q0101.Iw1/kA==.Aw1/kA==.",
|
||||
"Hopper": "A0p0tdFal8d0s8f41717---030302024300-.Iw1/kA==.Aw1/kA==."
|
||||
},
|
||||
"type_7_transport": {
|
||||
"Cargo": "A0p0tiFfliddsdf5--------0505040403480101.Iw18aQ==.Aw18aQ==.",
|
||||
"Miner": "A0pdtiFflid8sdf5--2l2l----0505041v03450000.Iw18aQ==.Aw18aQ==."
|
||||
},
|
||||
"federal_dropship": {
|
||||
"Cargo": "A0pdtiFflnddsif4-1717------05040448--020201.Iw18eQ==.Aw18eQ==."
|
||||
},
|
||||
"asp": {
|
||||
"Miner": "A2pftfFflidfskf50s0s24242l2l---04054a1q02022o27.Iw18WQ==.Aw18WQ==."
|
||||
},
|
||||
"imperial_clipper": {
|
||||
"Cargo": "A0p5tiFflndisnf4--0s0s----0605450302020101.Iw18aQ==.Aw18aQ==.",
|
||||
"Dream": "A2pktkFflndpskf40v0v0s0s0404040n4k5n5d2b29292o-.AwRj4yWU1I==.CwBhCYy6YRigzLIA.",
|
||||
"Current": "A0patkFflndfskf4----------------.AwRj4yWU1I==.CwBhCYy6YRigzLIA."
|
||||
},
|
||||
"type_9_heavy": {
|
||||
"Current": "A0patsFklndnsif6---------0706054a0303020224.AwRj4yoo.EwBhEYy6dsg=."
|
||||
},
|
||||
"python": {
|
||||
"Cargo": "A0patnFflidsssf5---------050505040448020201.Iw18eQ==.Aw18eQ==.",
|
||||
"Miner": "A0pktkFflidpspf50v0v0v2m2m0404--050505Ce4a1v02022o.Iw18eQ==.IwBhBYy6dkCYg===.",
|
||||
"Dream": "A2pptkFfliduspf50v0v0v27270404040m5n5n4f2d2d032t0201.Iw1+gDBxA===.EwBhEYy6e0WEA===.",
|
||||
"Missile": "A0pttoFjljdystf52f2g2d2ePh----04044j03---002h.Iw18eQ==.Aw18eQ==."
|
||||
},
|
||||
"anaconda": {
|
||||
"Dream": "A4putpFklndzsuf52c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d04-0303326b.AwRj4yo5dyg=.MwBhCYy6duvARiA=.",
|
||||
"Cargo": "A0patnFklndnsxf5----------------06050505040404-45030301.Iw18ZVA=.Aw18ZVA=.",
|
||||
"Current": "A0patnFklndksxf5----------------06050505040404-03034524.Iw18ZVA=.Aw18ZVA=.",
|
||||
"Explorer": "A0patnFklndksxf5--------0202------f7050505040s37-2f2i4524.AwRj4yVKJ9hA.AwhMIyumQRhEA===.",
|
||||
"Test": "A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.Iw18ZVA=.Aw18ZVA=."
|
||||
},
|
||||
"diamondback_explorer": {
|
||||
"Explorer": "A0p0tdFfldddsdf5---0202--320p432i2f-.AwRj4zTYg===.AwiMIyoo."
|
||||
},
|
||||
"vulture": {
|
||||
"Bounty Hunter": "A3patcFalddksff31e1e0404-0l4a-5d27662j.AwRj4z2I.MwBhBYy6oJmAjLIA."
|
||||
},
|
||||
"fer_de_lance": {
|
||||
"Attack": "A2pfthFalidpsff31r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.CwBhrSu8EZyA."
|
||||
},
|
||||
"eagle": {
|
||||
"Figther": "A4p0t5F5l3d5s5f20p0p24-4053-2j-.Iw18kA==.Aw18kA==."
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
{
|
||||
"builds": {
|
||||
"type_6_transporter": {
|
||||
"Cargo": "02A4D4A2D2D2D4C-----04040303430101",
|
||||
"Miner": "03A4D4A2D2D2D4C2l2l---040403451q0101",
|
||||
"Hopper": "02A4D4A2D1A2D4C1717---030302024300-"
|
||||
},
|
||||
"type_7_transport": {
|
||||
"Cargo": "02A5D5A4D3D3D5C--------0505040403480101",
|
||||
"Miner": "04D5D5A4D2D3D5C--2l2l----0505041v03450000"
|
||||
},
|
||||
"federal_dropship": {
|
||||
"Cargo": "04D5D5A5D3D4D4C-1717------05040448020201"
|
||||
},
|
||||
"asp": {
|
||||
"Miner": "25A5A5A4D4A5A5C0s0s24242l2l---04054a1q02022o27"
|
||||
},
|
||||
"imperial_clipper": {
|
||||
"Cargo": "03A5D5A5D4D5D4C--0s0s----0605450302020101",
|
||||
"Dream": "26A6A5A5D6A5A4C0v0v0s0s0404040n4k5n5d2b29292o-.AwRj4yWU1I==.CwBhCYy6YRigzLIA",
|
||||
"Current": "04A6A5A5D4A5A4C----------------.AwRj4yWU1I==.CwBhCYy6YRigzLIA"
|
||||
},
|
||||
"type_9_heavy": {
|
||||
"Current": "04A7D6A5D5D4D6C---------0706054a0303020224.AwRj4yoo.EwBhEYy6dsg="
|
||||
},
|
||||
"python": {
|
||||
"Cargo": "04A6D5A4D6D6D5C---------050505040448020201.Iw18eQ==.Aw18eQ==",
|
||||
"Miner": "06A6A5A4D6A6A5C0v0v0v2m2m0404--050505Ce4a1v02022o.Iw18eQ==.IwBhBYy6dkCYg===",
|
||||
"Dream": "27A6A5A4D7A6A5C0v0v0v27270404040m5n5n4f2d2d032t0201.Iw1+gDBxA===.EwBhEYy6e0WEA==="
|
||||
},
|
||||
"anaconda": {
|
||||
"Dream": "48A7A6A5D8A8A5C2c0o0o0o1m1m0q0q0404040l0b0100004k5n5n112d2d040303326b.AwRj4yo5dig=.MwBhCYy6du3ARiA=",
|
||||
"Cargo": "04A6D6A5D5D8D5C----------------0605050504040445030301.Iw18ZlA=.Aw18ZlA=",
|
||||
"Current": "04A6D6A5D5A8D5C----------------0605050504040403034524.Iw18ZlA=.Aw18ZlA=",
|
||||
"Explorer": "04A6D6A5D5A8D5C--------0202------f7050505040s372f2i4524.AwRj4yVKJthA.AwhMIyungRhEA==="
|
||||
},
|
||||
"diamondback_explorer": {
|
||||
"Explorer": "02A4D5A3D3D3D5C---0202--320p432i2f.AwRj4zTI.AwiMIypI"
|
||||
},
|
||||
"vulture": {
|
||||
"Bounty Hunter": "34A4C4A3D5A4A3C1e1e0404-0l4a5d27662j.AwRj4y2I.MwBhBYy6wJmAjLIA"
|
||||
},
|
||||
"fer_de_lance": {
|
||||
"Attack": "25A5C4A4D6A4A3C1r0s0s0s0s000404-04-4a-5d27-.Iw18aQ==.CwBhrSu8EZyA"
|
||||
},
|
||||
"eagle": {
|
||||
"Figther": "42A3A3A1D2A2A2C0p0p24-40532j.AwRj49iA.AwgsIkEZigmIA==="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
{
|
||||
"builds": {
|
||||
"type_6_transporter": {
|
||||
"Cargo": "02A4D4A2D2D2D4C-----04040303430101",
|
||||
"Miner": "03A4D4A2D2D2D4C2l2l---040403451q0101",
|
||||
"Hopper": "02A4D4A2D1A2D4C1717---030302024300-"
|
||||
},
|
||||
"type_7_transport": {
|
||||
"Cargo": "02A5D5A4D3D3D5C--------0505040403480101",
|
||||
"Miner": "04D5D5A4D2D3D5C--2l2l----0505041v03450000"
|
||||
},
|
||||
"federal_dropship": {
|
||||
"Cargo": "04D5D5A5D3D4D4C-1717------05040448020201"
|
||||
},
|
||||
"asp": {
|
||||
"Miner": "25A5A5A4D4A5A5C0s0s24242l2l---04054a1q02022o27"
|
||||
},
|
||||
"cobra_mk_iii": {
|
||||
"Example": "24A4A4A3D3A3A4C0s0s2d2d0m0445032b2o2753.AwRj4yKA.CwBhEYyrKhmMQ==="
|
||||
},
|
||||
"imperial_clipper": {
|
||||
"Cargo": "03A5D5A5D4D5D4C--0s0s----0605450302020101",
|
||||
"Multi-purpose": "26A4A5A5D6A5A4C0v0v272704090j0h064f2c0302020101",
|
||||
"Current": "05A6D5A5D6A5A4C0v0v27270404050n4m05035d29292o01.AwRj4yrI.AwhMIyuBGNiA",
|
||||
"Dream": "26A6A5A5D6A5A4C0v0v0s0s04040c0n064f5d2b02022o0d.AwRj49UlmI==.AwiMIyuo"
|
||||
},
|
||||
"type_9_heavy": {
|
||||
"Cargo": "04A6D6A5D4D4D5C---------07064f040303010201.AwRj4yoo.EwBhEYy6dsg="
|
||||
},
|
||||
"python": {
|
||||
"Cargo": "04A6D5A4D6D6D5C---------050505044a03020201",
|
||||
"Miner": "04A6D5A4D6D6D5C---2m2m----050505044d1v02022o"
|
||||
},
|
||||
"anaconda": {
|
||||
"Dream": "48A6A6A5A8A8A5C2c0o0o0o1m1m0q0q0404040l0b0100034k5n05050404040303326b.AwRj4yo5dig=.MwBhEYy6duwEziA=",
|
||||
"Cargo": "03A7D6A5D4D8D5C----------------060505054d040403030301.AwRj4yuqg===.Aw18ZlA=",
|
||||
"Modified": "0pyttFolodDsyf5------1717--------05044j-03----2h00.Iw18ZlA=.Aw18ZlA=.H4sIAAAAAAAAA2MUe8HMwPD-PwDDhxeuCAAAAA=="
|
||||
},
|
||||
"diamondback_explorer": {
|
||||
"Explorer": "02A4D5A3D3D3D5C-------320p432i2f.AwRj4zTI.AwiMIypI"
|
||||
}
|
||||
},
|
||||
"comparisons": {
|
||||
"Test": {
|
||||
"facets": [ 9, 6, 4, 1, 3, 2 ],
|
||||
"builds": [
|
||||
{
|
||||
"shipId": "anaconda",
|
||||
"buildName": "Dream"
|
||||
},
|
||||
{
|
||||
"shipId": "asp",
|
||||
"buildName": "Miner"
|
||||
},
|
||||
{
|
||||
"shipId": "diamondback_explorer",
|
||||
"buildName": "Explorer"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"insurance": "Beta",
|
||||
"discounts": [
|
||||
1,
|
||||
1
|
||||
]
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,90 +0,0 @@
|
||||
import Ship from '../src/app/shipyard/Ship';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import * as ModuleUtils from '../src/app/shipyard/ModuleUtils';
|
||||
|
||||
describe("Agility", function() {
|
||||
|
||||
it("correctly calculates speed", function() {
|
||||
let agilityData = require('./fixtures/agility-data');
|
||||
|
||||
for (let shipId in agilityData) {
|
||||
for (let thrusterId in agilityData[shipId]) {
|
||||
const thrusterData = agilityData[shipId][thrusterId];
|
||||
let shipData = Ships[shipId];
|
||||
let ship = new Ship(shipId, shipData.properties, shipData.slots);
|
||||
ship.buildWith(shipData.defaults);
|
||||
ship.use(ship.standard[1], ModuleUtils.findModule('t', thrusterId));
|
||||
|
||||
expect(Math.round(ship.topSpeed)).toBe(thrusterData.speed);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("correctly calculates boost", function() {
|
||||
let agilityData = require('./fixtures/agility-data');
|
||||
|
||||
for (let shipId in agilityData) {
|
||||
for (let thrusterId in agilityData[shipId]) {
|
||||
const thrusterData = agilityData[shipId][thrusterId];
|
||||
let shipData = Ships[shipId];
|
||||
let ship = new Ship(shipId, shipData.properties, shipData.slots);
|
||||
ship.buildWith(shipData.defaults);
|
||||
// Turn off internals to ensure we have enough power to boost
|
||||
for (let internal in ship.internal) {
|
||||
ship.internal[internal].enabled = 0;
|
||||
}
|
||||
ship.use(ship.standard[1], ModuleUtils.findModule('t', thrusterId));
|
||||
|
||||
expect(Math.round(ship.topBoost)).toBe(thrusterData.boost);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("correctly calculates pitch", function() {
|
||||
let agilityData = require('./fixtures/agility-data');
|
||||
|
||||
for (let shipId in agilityData) {
|
||||
for (let thrusterId in agilityData[shipId]) {
|
||||
const thrusterData = agilityData[shipId][thrusterId];
|
||||
let shipData = Ships[shipId];
|
||||
let ship = new Ship(shipId, shipData.properties, shipData.slots);
|
||||
ship.buildWith(shipData.defaults);
|
||||
ship.use(ship.standard[1], ModuleUtils.findModule('t', thrusterId));
|
||||
|
||||
expect(Math.round(ship.pitches[4] * 100) / 100).toBeCloseTo(thrusterData.pitch, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("correctly calculates roll", function() {
|
||||
let agilityData = require('./fixtures/agility-data');
|
||||
|
||||
for (let shipId in agilityData) {
|
||||
for (let thrusterId in agilityData[shipId]) {
|
||||
const thrusterData = agilityData[shipId][thrusterId];
|
||||
let shipData = Ships[shipId];
|
||||
let ship = new Ship(shipId, shipData.properties, shipData.slots);
|
||||
ship.buildWith(shipData.defaults);
|
||||
ship.use(ship.standard[1], ModuleUtils.findModule('t', thrusterId));
|
||||
|
||||
expect(Math.round(ship.rolls[4] * 100) / 100).toBeCloseTo(thrusterData.roll, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("correctly calculates yaw", function() {
|
||||
let agilityData = require('./fixtures/agility-data');
|
||||
|
||||
for (let shipId in agilityData) {
|
||||
for (let thrusterId in agilityData[shipId]) {
|
||||
const thrusterData = agilityData[shipId][thrusterId];
|
||||
let shipData = Ships[shipId];
|
||||
let ship = new Ship(shipId, shipData.properties, shipData.slots);
|
||||
ship.buildWith(shipData.defaults);
|
||||
ship.use(ship.standard[1], ModuleUtils.findModule('t', thrusterId));
|
||||
|
||||
expect(Math.round(ship.yaws[4] * 100) / 100).toBeCloseTo(thrusterData.yaw, 1);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -1,327 +0,0 @@
|
||||
jest.unmock('../src/app/stores/Persist');
|
||||
jest.unmock('../src/app/components/TranslatedComponent');
|
||||
jest.unmock('../src/app/components/ModalImport');
|
||||
jest.unmock('prop-types');
|
||||
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import ReactDOM from 'react-dom';
|
||||
import TU from 'react-testutils-additions';
|
||||
import Utils from './testUtils';
|
||||
import { getLanguage } from '../src/app/i18n/Language';
|
||||
|
||||
describe('Import Modal', function() {
|
||||
|
||||
let MockRouter = require('../src/app/Router').default;
|
||||
const Persist = require('../src/app/stores/Persist').default;
|
||||
const ModalImport = require('../src/app/components/ModalImport').default;
|
||||
const mockContext = {
|
||||
language: getLanguage('en'),
|
||||
sizeRatio: 1,
|
||||
openMenu: jest.genMockFunction(),
|
||||
closeMenu: jest.genMockFunction(),
|
||||
showModal: jest.genMockFunction(),
|
||||
hideModal: jest.genMockFunction(),
|
||||
tooltip: jest.genMockFunction(),
|
||||
termtip: jest.genMockFunction(),
|
||||
onWindowResize: jest.genMockFunction()
|
||||
};
|
||||
|
||||
let modal, render, ContextProvider = Utils.createContextProvider(mockContext);
|
||||
|
||||
/**
|
||||
* Clear saved builds, and reset React DOM
|
||||
*/
|
||||
function reset() {
|
||||
MockRouter.go.mockClear();
|
||||
Persist.deleteAll();
|
||||
render = TU.renderIntoDocument(<ContextProvider><ModalImport /></ContextProvider>);
|
||||
modal = TU.findRenderedComponentWithType(render, ModalImport);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate user import text entry / paste
|
||||
* @param {string} text Import text / raw data
|
||||
*/
|
||||
function pasteText(text) {
|
||||
let textarea = TU.findRenderedDOMComponentWithTag(render, 'textarea');
|
||||
TU.Simulate.change(textarea, { target: { value: text } });
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate click on Proceed button
|
||||
*/
|
||||
function clickProceed() {
|
||||
let proceedButton = TU.findRenderedDOMComponentWithId(render, 'proceed');
|
||||
TU.Simulate.click(proceedButton);
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate click on Import button
|
||||
*/
|
||||
function clickImport() {
|
||||
let importButton = TU.findRenderedDOMComponentWithId(render, 'import');
|
||||
TU.Simulate.click(importButton);
|
||||
}
|
||||
|
||||
describe('Import Backup', function() {
|
||||
|
||||
beforeEach(reset);
|
||||
|
||||
it('imports a valid backup', function() {
|
||||
let importData = require('./fixtures/valid-backup');
|
||||
let importString = JSON.stringify(importData);
|
||||
|
||||
expect(modal.state.importValid).toEqual(false);
|
||||
expect(modal.state.errorMsg).toEqual(null);
|
||||
pasteText(importString);
|
||||
expect(modal.state.importValid).toBe(true);
|
||||
expect(modal.state.errorMsg).toEqual(null);
|
||||
expect(modal.state.builds).toEqual(importData.builds);
|
||||
expect(modal.state.comparisons).toEqual(importData.comparisons);
|
||||
expect(modal.state.shipDiscount).toEqual(importData.discounts[0]);
|
||||
expect(modal.state.moduleDiscount).toEqual(importData.discounts[1]);
|
||||
expect(modal.state.insurance).toBe(importData.insurance.toLowerCase());
|
||||
clickProceed();
|
||||
expect(modal.state.processed).toBe(true);
|
||||
expect(modal.state.errorMsg).toEqual(null);
|
||||
clickImport();
|
||||
expect(Persist.getBuilds()).toEqual(importData.builds);
|
||||
expect(Persist.getComparisons()).toEqual(importData.comparisons);
|
||||
expect(Persist.getInsurance()).toEqual(importData.insurance.toLowerCase());
|
||||
expect(Persist.getShipDiscount()).toEqual(importData.discounts[0]);
|
||||
expect(Persist.getModuleDiscount()).toEqual(importData.discounts[1]);
|
||||
});
|
||||
|
||||
it('imports an old valid backup', function() {
|
||||
const importData = require('./fixtures/old-valid-export');
|
||||
const importStr = JSON.stringify(importData);
|
||||
|
||||
pasteText(importStr);
|
||||
expect(modal.state.builds).toEqual(importData.builds);
|
||||
expect(modal.state.importValid).toBe(true);
|
||||
expect(modal.state.errorMsg).toEqual(null);
|
||||
clickProceed();
|
||||
expect(modal.state.processed).toBeTruthy();
|
||||
clickImport();
|
||||
expect(Persist.getBuilds()).toEqual(importData.builds);
|
||||
});
|
||||
|
||||
it('catches an invalid backup', function() {
|
||||
const importData = require('./fixtures/valid-backup');
|
||||
let invalidImportData = Object.assign({}, importData);
|
||||
//invalidImportData.builds.asp = null; // Remove Asp Miner build used in comparison
|
||||
delete(invalidImportData.builds.asp);
|
||||
|
||||
pasteText('"this is not valid"');
|
||||
expect(modal.state.importValid).toBeFalsy();
|
||||
expect(modal.state.errorMsg).toEqual('Must be an object or array!');
|
||||
pasteText('{ "builds": "Should not be a string" }');
|
||||
expect(modal.state.importValid).toBeFalsy();
|
||||
expect(modal.state.errorMsg).toEqual('builds must be an object!');
|
||||
pasteText(JSON.stringify(importData).replace('anaconda', 'invalid_ship'));
|
||||
expect(modal.state.importValid).toBeFalsy();
|
||||
expect(modal.state.errorMsg).toEqual('"invalid_ship" is not a valid Ship Id!');
|
||||
pasteText(JSON.stringify(importData).replace('Dream', ''));
|
||||
expect(modal.state.importValid).toBeFalsy();
|
||||
expect(modal.state.errorMsg).toEqual('Imperial Clipper build "" must be a string at least 1 character long!');
|
||||
pasteText(JSON.stringify(invalidImportData));
|
||||
expect(modal.state.importValid).toBeFalsy();
|
||||
expect(modal.state.errorMsg).toEqual('asp build "Miner" data is missing!');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Import Detailed V3 Build', function() {
|
||||
|
||||
beforeEach(reset);
|
||||
|
||||
it('imports a valid v3 build', function() {
|
||||
const importData = require('./fixtures/anaconda-test-detailed-export-v3');
|
||||
pasteText(JSON.stringify(importData));
|
||||
|
||||
expect(modal.state.importValid).toBeTruthy();
|
||||
expect(modal.state.errorMsg).toEqual(null);
|
||||
expect(modal.state.singleBuild).toBe(true);
|
||||
clickProceed();
|
||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.AwRj4zNLaA%3D%3D.CwBhCYzBGW9qCTSqq5xA.&bn=Test%20My%20Ship');
|
||||
});
|
||||
|
||||
it('catches an invalid build', function() {
|
||||
const importData = require('./fixtures/anaconda-test-detailed-export-v3');
|
||||
pasteText(JSON.stringify(importData).replace('references', 'refs'));
|
||||
|
||||
expect(modal.state.importValid).toBeFalsy();
|
||||
expect(modal.state.errorMsg).toEqual('Anaconda Build "Test My Ship": Invalid data');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Import Detailed V4 Build', function() {
|
||||
|
||||
beforeEach(reset);
|
||||
|
||||
it('imports a valid v4 build', function() {
|
||||
const importData = require('./fixtures/anaconda-test-detailed-export-v4');
|
||||
pasteText(JSON.stringify(importData));
|
||||
|
||||
expect(modal.state.importValid).toBeTruthy();
|
||||
expect(modal.state.errorMsg).toEqual(null);
|
||||
expect(modal.state.singleBuild).toBe(true);
|
||||
clickProceed();
|
||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/anaconda?code=A4putkFklkdzsuf52c0o0o0o1m1m0q0q0404-0l0b0100034k5n052d04---0303326b.AwRj4zNLaA%3D%3D.CwBhCYzBGW9qCTSqq5xA.H4sIAAAAAAAAA2MUe8HMwPD%2FPwMcAABTINwTEgAAAA%3D%3D&bn=Test%20My%20Ship');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Import Detailed Engineered V4 Build', function() {
|
||||
|
||||
beforeEach(reset);
|
||||
|
||||
it('imports a valid v4 build', function() {
|
||||
const importData = require('./fixtures/asp-test-detailed-export-v4');
|
||||
pasteText(JSON.stringify(importData));
|
||||
|
||||
expect(modal.state.importValid).toBeTruthy();
|
||||
expect(modal.state.errorMsg).toEqual(null);
|
||||
expect(modal.state.singleBuild).toBe(true);
|
||||
clickProceed();
|
||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/asp?code=A0pftiFflfddsnf5------020202033c044002v62f2i.AwRj4yvI.CwRgDBldHnJA.H4sIAAAAAAAAA2P858DAwPCXEUhwHPvx%2F78YG5AltB7I%2F8%2F0TwImJboDSPJ%2F%2B%2Ff%2Fv%2FKlX%2F%2F%2Fi3AwMTBIfARK%2FGf%2BJwVSxArStVAYqOjvz%2F%2F%2FJVo5GRhE2IBc4SKQSSz%2FDGEmCa398P8%2F%2F2%2BgTf%2F%2FAwDFxwtofAAAAA%3D%3D&bn=Multi-purpose%20Asp%20Explorer');
|
||||
});
|
||||
|
||||
it('imports a valid v4 build with modifications', function() {
|
||||
const importData = require('./fixtures/courier-test-detailed-export-v4');
|
||||
pasteText(JSON.stringify(importData));
|
||||
|
||||
expect(modal.state.importValid).toBeTruthy();
|
||||
expect(modal.state.errorMsg).toEqual(null);
|
||||
expect(modal.state.singleBuild).toBe(true);
|
||||
clickProceed();
|
||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/imperial_courier?code=A0patzF5l0das8f31a1a270202000e402t0101-2f.AwRj4zKA.CwRgDBldLiQ%3D.H4sIAAAAAAAAA12OP0tCYRjFj9fuVbvF1du9ekkT8s%2FkIg4NElyIBBd321yaGvwUQTS3N7UFfYygIT9EoyQUJA36ns47XJCWA%2B%2Fz%2Bz3Pe3ImBbDNKaqNPSBoGrL4ngfomKpFGiJ%2BLgHteR1IPjxJT5pF11uSeXNsJVcRfgdC92syWUuK0iMdKZqrjJ%2F0aoA71lJ5oKf38knWcCiptCPdhJIerdS00vlK0qktlqoj983UmqqHjQ33VsW8eazFmaTyULP2hQ4lX8LBme6g%2F6v0TTdbxJ2KhdEIaCw15MF%2FNB0L%2BS2hwEwyFM8KgP%2BqEpWWA3Qu9Z3z9kPWHzakt7Dt%2BAeD7ghSTgEAAA%3D%3D&bn=Multi-purpose%20Imperial%20Courier');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Import Detaild Builds Array', function() {
|
||||
|
||||
beforeEach(reset);
|
||||
|
||||
it('imports all builds', function() {
|
||||
const importData = require('./fixtures/valid-detailed-export');
|
||||
const expectedBuilds = require('./fixtures/expected-builds');
|
||||
|
||||
pasteText(JSON.stringify(importData));
|
||||
expect(modal.state.importValid).toBeTruthy();
|
||||
expect(modal.state.errorMsg).toEqual(null);
|
||||
clickProceed();
|
||||
expect(modal.state.processed).toBeTruthy();
|
||||
clickImport();
|
||||
|
||||
let builds = Persist.getBuilds();
|
||||
|
||||
for (let s in builds) {
|
||||
for (let b in builds[s]) {
|
||||
expect(builds[s][b]).toEqual(expectedBuilds[s][b]);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Import Companion API Build', function() {
|
||||
|
||||
beforeEach(reset);
|
||||
|
||||
it('imports a valid companion API build', function() {
|
||||
const importData = require('./fixtures/companion-api-import-1');
|
||||
pasteText(JSON.stringify(importData));
|
||||
|
||||
expect(modal.state.importValid).toBeTruthy();
|
||||
expect(modal.state.errorMsg).toEqual(null);
|
||||
expect(modal.state.singleBuild).toBe(true);
|
||||
clickProceed();
|
||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/federal_corvette?code=A2putsFklndzsxf50x0x7l28281919040404040402020l06p05sf63c5ifr--v66g2f.AwRj4zNaqA%3D%3D.CwRgDBldUExuBiIlUA%3D%3D.H4sIAAAAAAAAA02SPy9DURjG3%2F65vW1v47TXVbeqqF7EQtIIBomRJswsYmISH8BgkFhqFZ9AwlALMYitkXQyEF2k4SMYJNK0dV7PK7nc5ck55%2Fm9z%2FnznpBeJqLvECQbM4hUjZnjO5hyWGfFikAGGjGiku0QuddhQCNdZmdWM9snsDmih4REOdlnNvz9DrPrJIicPdSwoZf8pAnTIpq8x7DYADS%2Bi5DERY85%2BYqpmkc6x%2FWGf6beKCR3YBIZFZCxCgrtczjuOmo4qTf94F4KYuxhz5jjEhXmUJNexFrpIUo02ALN1j9u1JMgD%2FMga1GfbMNRd9iHUwGy%2BpspZF3IBSGvMFJluS%2FuR24FJ2KlV%2Fxju6sQq4lhRsQTUVUJTgegLtS6EUjEE1HPAmUC0KdAjwKJeCKqD8zoURx72gHyDW9nvQhJGHkyUscS1x%2BAZnAlqwU%2FI%2BKJKEvextXrf93eQrR1KUlS5HWwGC61mfOn0oN3IM4OHoBzuuIHj33hS5jT8KeamIYa0sjhgH%2BLfplP4kcwD5Xl3xR1wfeHtqWzBHHX8I9SH9Je%2FgGvXxeungIAAA%3D%3D&bn=Imported%20Federal%20Corvette');
|
||||
});
|
||||
|
||||
it('imports a valid companion API build', function() {
|
||||
const importData = require('./fixtures/companion-api-import-2');
|
||||
pasteText(JSON.stringify(importData));
|
||||
|
||||
expect(modal.state.importValid).toBeTruthy();
|
||||
expect(modal.state.errorMsg).toEqual(null);
|
||||
expect(modal.state.singleBuild).toBe(true);
|
||||
clickProceed();
|
||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/beluga?code=A0pktsFplCdpsnf70t0t2727270004040404043c4fmimlmm04mc0iv62i2f.AwRj4yukg%3D%3D%3D.CwRgDBldHi8IUA%3D%3D.H4sIAAAAAAAAA2P8Z8%2FAwPCXEUiIKTMxMPCv%2F%2Ff%2FP8cFIPGf6Z8YTEr0GjMDg%2FJWICERBOTzn%2Fn7%2F7%2FIO5Ai5n9SIEWsQEIoSxAolfbt%2F3%2BJPk4GBhE7YQYGYVmgcuVnf4Aq%2FwMAIrEcGGsAAAA%3D&bn=Imported%20Beluga%20Liner');
|
||||
});
|
||||
|
||||
it('imports a valid companion API build', function() {
|
||||
const importData = require('./fixtures/companion-api-import-3');
|
||||
pasteText(JSON.stringify(importData));
|
||||
|
||||
expect(modal.state.importValid).toBeTruthy();
|
||||
expect(modal.state.errorMsg).toEqual(null);
|
||||
expect(modal.state.singleBuild).toBe(true);
|
||||
clickProceed();
|
||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/type_7_transport?code=A0patfFflidasdf5----0404040005050504044d2402.AwRj4yrI.CwRgDBlVK7EiA%3D%3D%3D.&bn=Imported%20Type-7%20Transporter');
|
||||
});
|
||||
|
||||
it('imports a valid companion API build', function() {
|
||||
const importData = require('./fixtures/companion-api-import-4');
|
||||
pasteText(JSON.stringify(importData));
|
||||
|
||||
expect(modal.state.importValid).toBeTruthy();
|
||||
expect(modal.state.errorMsg).toEqual(null);
|
||||
expect(modal.state.singleBuild).toBe(true);
|
||||
clickProceed();
|
||||
expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
expect(MockRouter.go.mock.calls[0][0]).toBe('outfit/cobra_mk_iii?code=A0p0tdFaldd3sdf4------34---2f2i.AwRj4yKA.CwRgDMYExre1Rcg%3D..EweloBhBGA2EoFMCGBzANokMK6A%3D');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Import E:D Shipyard Builds', function() {
|
||||
|
||||
// it('imports a valid build', function() {
|
||||
// const imports = require('./fixtures/ed-shipyard-import-valid');
|
||||
//
|
||||
// for (let i = 0; i < imports.length; i++ ) {
|
||||
// reset();
|
||||
// let fixture = imports[i];
|
||||
// pasteText(fixture.buildText);
|
||||
// expect(modal.state.importValid).toBeTruthy();
|
||||
// expect(modal.state.errorMsg).toEqual(null);
|
||||
// clickProceed();
|
||||
// expect(MockRouter.go.mock.calls.length).toBe(1);
|
||||
// expect(MockRouter.go.mock.calls[0][0]).toBe('/outfit/' + fixture.shipId + '?code=' + encodeURIComponent(fixture.buildCode) + '&bn=' + encodeURIComponent(fixture.buildName));
|
||||
// }
|
||||
// });
|
||||
|
||||
it('catches invalid builds', function() {
|
||||
const imports = require('./fixtures/ed-shipyard-import-invalid');
|
||||
|
||||
for (let i = 0; i < imports.length; i++ ) {
|
||||
reset();
|
||||
pasteText(imports[i].buildText);
|
||||
expect(modal.state.importValid).toBeFalsy();
|
||||
expect(modal.state.errorMsg).toEqual(imports[i].errorMsg);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Imports from a Comparison', function() {
|
||||
|
||||
it('imports a valid comparison', function() {
|
||||
const importBuilds = require('./fixtures/valid-backup').builds;
|
||||
Persist.deleteAll();
|
||||
render = TU.renderIntoDocument(<ContextProvider><ModalImport builds={importBuilds} /></ContextProvider>);
|
||||
modal = TU.findRenderedComponentWithType(render, ModalImport);
|
||||
|
||||
expect(modal.state.processed).toBe(true);
|
||||
expect(modal.state.errorMsg).toEqual(null);
|
||||
clickImport();
|
||||
expect(Persist.getBuilds()).toEqual(importBuilds);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,143 +0,0 @@
|
||||
jest.unmock('../src/app/stores/Persist');
|
||||
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import TU from 'react-testutils-additions';
|
||||
|
||||
let origAddEventListener = window.addEventListener;
|
||||
let storageListener;
|
||||
let ls = {};
|
||||
|
||||
// Implment mock localStorage
|
||||
let localStorage = {
|
||||
getItem: function(key) {
|
||||
return ls[key];
|
||||
},
|
||||
setItem: function(key, value) {
|
||||
ls[key] = value;
|
||||
},
|
||||
removeItem: function(key) {
|
||||
delete ls[key];
|
||||
},
|
||||
clear: function() {
|
||||
ls = {};
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener = function(eventName, listener) {
|
||||
|
||||
if(eventName == 'storage') {
|
||||
storageListener = listener; // Keep track of latest storage listener
|
||||
} else {
|
||||
origAddEventListener.apply(arguments);
|
||||
}
|
||||
}
|
||||
|
||||
describe('Persist', function() {
|
||||
|
||||
const Persist = require('../src/app/stores/Persist').Persist;
|
||||
|
||||
describe('Multi tab/window', function() {
|
||||
it("syncs builds", function() {
|
||||
window.localStorage = localStorage;
|
||||
ls = {};
|
||||
let p = new Persist();
|
||||
let newBuilds = {
|
||||
anaconda: { test: '1234' }
|
||||
};
|
||||
|
||||
storageListener({ key: 'builds', newValue: JSON.stringify(newBuilds) });
|
||||
expect(p.getBuild('anaconda', 'test')).toBe('1234');
|
||||
});
|
||||
});
|
||||
|
||||
describe('General and Settings', function() {
|
||||
it("has defaults", function() {
|
||||
window.localStorage = localStorage;
|
||||
ls = {};
|
||||
let p = new Persist();
|
||||
expect(p.getLangCode()).toBe('en');
|
||||
expect(p.showTooltips()).toBe(true);
|
||||
expect(p.getInsurance()).toBe('standard');
|
||||
expect(p.getShipDiscount()).toBe(0);
|
||||
expect(p.getModuleDiscount()).toBe(0);
|
||||
expect(p.getSizeRatio()).toBe(1);
|
||||
});
|
||||
|
||||
it("loads from localStorage correctly", function() {
|
||||
window.localStorage = localStorage;
|
||||
let savedData = require('./fixtures/valid-backup');
|
||||
ls = {};
|
||||
ls.builds = JSON.stringify(savedData.builds);
|
||||
ls.NG_TRANSLATE_LANG_KEY = 'de';
|
||||
ls.insurance = 'Standard';
|
||||
ls.shipDiscount = 0.25;
|
||||
ls.moduleDiscount = 0.15;
|
||||
let p = new Persist();
|
||||
|
||||
expect(p.getInsurance()).toBe('standard');
|
||||
expect(p.getShipDiscount()).toBe(0.25);
|
||||
expect(p.getModuleDiscount()).toBe(0.15);
|
||||
expect(p.getLangCode()).toEqual('de');
|
||||
expect(p.getBuilds('anaconda')).toEqual(savedData.builds.anaconda);
|
||||
expect(p.getBuilds('python')).toEqual(savedData.builds.python);
|
||||
expect(p.getBuildsNamesFor('imperial_clipper')).toEqual(['Cargo', 'Current', 'Dream', 'Multi-purpose']);
|
||||
expect(p.getBuild('type_7_transport', 'Cargo')).toEqual('02A5D5A4D3D3D5C--------0505040403480101');
|
||||
});
|
||||
|
||||
it("uses defaults from a corrupted localStorage", function() {
|
||||
window.localStorage = localStorage;
|
||||
ls = {};
|
||||
ls.builds = "not valid json";
|
||||
ls.comparisons = "1, 3, 4";
|
||||
ls.insurance = 'this insurance does not exist';
|
||||
ls.shipDiscount = 'this is not a number';
|
||||
ls.moduleDiscount = 10; // Way to big
|
||||
|
||||
let p = new Persist();
|
||||
expect(p.getLangCode()).toBe('en');
|
||||
expect(p.showTooltips()).toBe(true);
|
||||
expect(p.getInsurance()).toBe('standard');
|
||||
expect(p.getShipDiscount()).toBe(0);
|
||||
expect(p.getModuleDiscount()).toBe(0);
|
||||
expect(p.getBuilds()).toEqual({});
|
||||
expect(p.getComparisons()).toEqual({});
|
||||
});
|
||||
|
||||
it("works without localStorage", function() {
|
||||
window.localStorage = null;
|
||||
let p = new Persist();
|
||||
expect(p.getLangCode()).toBe('en');
|
||||
expect(p.showTooltips()).toBe(true);
|
||||
expect(p.getInsurance()).toBe('standard');
|
||||
expect(p.getShipDiscount()).toBe(0);
|
||||
expect(p.getModuleDiscount()).toBe(0);
|
||||
expect(p.getSizeRatio()).toBe(1);
|
||||
|
||||
p.saveBuild('anaconda', 'test', '12345');
|
||||
expect(p.getBuild('anaconda', 'test')).toBe('12345');
|
||||
|
||||
p.deleteBuild('anaconda', 'test');
|
||||
expect(p.hasBuilds()).toBe(false);
|
||||
});
|
||||
|
||||
it("generates the backup", function() {
|
||||
window.localStorage = localStorage;
|
||||
let savedData = require('./fixtures/valid-backup');
|
||||
ls = {};
|
||||
ls.builds = JSON.stringify(savedData.builds);
|
||||
ls.insurance = 'Beta';
|
||||
ls.shipDiscount = 0.25;
|
||||
ls.moduleDiscount = 0.15;
|
||||
|
||||
let p = new Persist();
|
||||
let backup = p.getAll();
|
||||
|
||||
expect(backup.insurance).toBe('beta');
|
||||
expect(backup.shipDiscount).toBe(0.25);
|
||||
expect(backup.moduleDiscount).toBe(0.15);
|
||||
expect(backup.builds).toEqual(savedData.builds);
|
||||
expect(backup.comparisons).toEqual({});
|
||||
});
|
||||
});
|
||||
})
|
||||
@@ -1,63 +0,0 @@
|
||||
import Ship from '../src/app/shipyard/Ship';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import * as Serializer from '../src/app/shipyard/Serializer';
|
||||
import jsen from 'jsen';
|
||||
|
||||
describe("Serializer", function() {
|
||||
const anacondaTestExport = require.requireActual('./fixtures/anaconda-test-detailed-export-v4');
|
||||
const code = anacondaTestExport.references[0].code;
|
||||
const anaconda = Ships.anaconda;
|
||||
const validate = jsen(require('../src/schemas/ship-loadout/4'));
|
||||
|
||||
describe("To Detailed Build", function() {
|
||||
let testBuild = new Ship('anaconda', anaconda.properties, anaconda.slots).buildFrom(code);
|
||||
let exportData = Serializer.toDetailedBuild('Test My Ship', testBuild);
|
||||
|
||||
it("conforms to the v4 ship-loadout schema", function() {
|
||||
expect(validate(exportData)).toBe(true);
|
||||
});
|
||||
|
||||
it("contains the correct components and stats", function() {
|
||||
expect(exportData.components).toEqual(anacondaTestExport.components);
|
||||
expect(exportData.stats).toEqual(anacondaTestExport.stats);
|
||||
expect(exportData.ship).toEqual(anacondaTestExport.ship);
|
||||
expect(exportData.name).toEqual(anacondaTestExport.name);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("Export Detailed Builds", function() {
|
||||
const expectedExport = require('./fixtures/valid-detailed-export');
|
||||
const builds = require('./fixtures/expected-builds');
|
||||
const exportData = Serializer.toDetailedExport(builds);
|
||||
|
||||
it("conforms to the v4 ship-loadout schema", function() {
|
||||
expect(exportData instanceof Array).toBe(true);
|
||||
|
||||
for (let detailedBuild of exportData) {
|
||||
expect(validate(detailedBuild)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe("From Detailed Build", function() {
|
||||
|
||||
it("builds the ship correctly", function() {
|
||||
let testBuildA = new Ship('anaconda', anaconda.properties, anaconda.slots);
|
||||
testBuildA.buildFrom(code);
|
||||
let testBuildB = Serializer.fromDetailedBuild(anacondaTestExport);
|
||||
|
||||
for(var p in testBuildB) {
|
||||
if (p == 'availCS') {
|
||||
continue;
|
||||
}
|
||||
expect(testBuildB[p]).toEqual(testBuildA[p], p + ' does not match');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
@@ -1,156 +0,0 @@
|
||||
import Ship from '../src/app/shipyard/Ship';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import * as ModuleUtils from '../src/app/shipyard/ModuleUtils';
|
||||
|
||||
describe("Ship", function() {
|
||||
|
||||
it("can build all ships", function() {
|
||||
for (let s in Ships) {
|
||||
let shipData = Ships[s];
|
||||
let ship = new Ship(s, shipData.properties, shipData.slots);
|
||||
|
||||
for (let p in shipData.properties) {
|
||||
expect(ship[p]).toEqual(shipData.properties[p], s + ' property [' + p + '] does not match when built');
|
||||
}
|
||||
|
||||
ship.buildWith(shipData.defaults);
|
||||
|
||||
expect(ship.totalCost).toEqual(shipData.retailCost, s + ' retail cost does not match default build cost');
|
||||
expect(ship.cargoCapacity).toBeDefined();
|
||||
expect(ship.priorityBands[0].retracted).toBeGreaterThan(0, s + ' priorityBands');
|
||||
expect(ship.powerAvailable).toBeGreaterThan(0, s + ' powerAvailable');
|
||||
expect(ship.unladenRange).toBeGreaterThan(0, s + ' unladenRange');
|
||||
expect(ship.ladenRange).toBeGreaterThan(0, s + ' ladenRange');
|
||||
expect(ship.fuelCapacity).toBeGreaterThan(0, s + ' fuelCapacity');
|
||||
expect(ship.unladenFastestRange).toBeGreaterThan(0, s + ' unladenFastestRange');
|
||||
expect(ship.ladenFastestRange).toBeGreaterThan(0, s + ' ladenFastestRange');
|
||||
expect(ship.shield).toBeGreaterThan(0, s + ' shield');
|
||||
expect(ship.armour).toBeGreaterThan(0, s + ' armour');
|
||||
expect(ship.topSpeed).toBeGreaterThan(0, s + ' topSpeed');
|
||||
}
|
||||
});
|
||||
|
||||
it("resets and rebuilds properly", function() {
|
||||
var id = 'cobra_mk_iii';
|
||||
var cobra = Ships[id];
|
||||
var shipA = new Ship(id, cobra.properties, cobra.slots);
|
||||
var shipB = new Ship(id, cobra.properties, cobra.slots);
|
||||
var testShip = new Ship(id, cobra.properties, cobra.slots);
|
||||
|
||||
var buildA = cobra.defaults;
|
||||
var buildB = {
|
||||
standard:['4A', '4A', '4A', '3D', '3A', '3A', '4C'],
|
||||
hardpoints: ['0s', '0s', '2d', '2d', 0, '04'],
|
||||
internal: ['45', '03', '2b', '2o', '27', '53']
|
||||
};
|
||||
|
||||
shipA.buildWith(buildA); // Build A
|
||||
shipB.buildWith(buildB);// Build B
|
||||
testShip.buildWith(buildA);
|
||||
|
||||
for(var p in testShip) {
|
||||
if (p == 'availCS') {
|
||||
continue;
|
||||
}
|
||||
expect(testShip[p]).toEqual(shipA[p], p + ' does not match');
|
||||
}
|
||||
|
||||
testShip.buildWith(buildB);
|
||||
|
||||
for(var p in testShip) {
|
||||
if (p == 'availCS') {
|
||||
continue;
|
||||
}
|
||||
expect(testShip[p]).toEqual(shipB[p], p + ' does not match');
|
||||
}
|
||||
|
||||
testShip.buildWith(buildA);
|
||||
|
||||
for(var p in testShip) {
|
||||
if (p == 'availCS') {
|
||||
continue;
|
||||
}
|
||||
expect(testShip[p]).toEqual(shipA[p], p + ' does not match');
|
||||
}
|
||||
});
|
||||
|
||||
it("discounts hull and components properly", function() {
|
||||
var id = 'cobra_mk_iii';
|
||||
var cobra = Ships[id];
|
||||
var testShip = new Ship(id, cobra.properties, cobra.slots);
|
||||
testShip.buildWith(cobra.defaults);
|
||||
|
||||
var originalHullCost = testShip.hullCost;
|
||||
var originalTotalCost = testShip.totalCost;
|
||||
var discount = 0.1;
|
||||
|
||||
expect(testShip.m.discountedCost).toEqual(originalHullCost, 'Hull cost does not match');
|
||||
|
||||
testShip.applyDiscounts(discount, discount);
|
||||
|
||||
// Floating point errors cause miniscule decimal places which are handled in the app by rounding/formatting
|
||||
|
||||
expect(Math.floor(testShip.m.discountedCost)).toEqual(Math.floor(originalHullCost * (1 - discount)), 'Discounted Hull cost does not match');
|
||||
expect(Math.floor(testShip.totalCost)).toEqual(Math.floor(originalTotalCost * (1 - discount)), 'Discounted Total cost does not match');
|
||||
|
||||
testShip.applyDiscounts(0, 0); // No discount, 100% of cost
|
||||
|
||||
expect(testShip.m.discountedCost).toEqual(originalHullCost, 'Hull cost does not match');
|
||||
expect(testShip.totalCost).toEqual(originalTotalCost, 'Total cost does not match');
|
||||
|
||||
testShip.applyDiscounts(discount, 0); // Only discount hull
|
||||
|
||||
expect(Math.floor(testShip.m.discountedCost)).toEqual(Math.round(originalHullCost * (1 - discount)), 'Discounted Hull cost does not match');
|
||||
expect(testShip.totalCost).toEqual(originalTotalCost - originalHullCost + testShip.m.discountedCost, 'Total cost does not match');
|
||||
|
||||
});
|
||||
|
||||
it("enforces a single shield generator", function() {
|
||||
var id = 'anaconda';
|
||||
var anacondaData = Ships[id];
|
||||
var anaconda = new Ship(id, anacondaData.properties, anacondaData.slots);
|
||||
anaconda.buildWith(anacondaData.defaults);
|
||||
|
||||
expect(anaconda.internal[2].m.grp).toEqual('sg', 'Anaconda default shield generator slot');
|
||||
|
||||
anaconda.use(anaconda.internal[1], ModuleUtils.internal('4j')); // 6E Shield Generator
|
||||
|
||||
expect(anaconda.internal[2].m).toEqual(null, 'Anaconda default shield generator slot is empty');
|
||||
expect(anaconda.internal[1].m.id).toEqual('4j', 'Slot 1 should have SG 4j in it');
|
||||
expect(anaconda.internal[1].m.grp).toEqual('sg','Slot 1 should have SG 4j in it');
|
||||
|
||||
});
|
||||
|
||||
it("enforces a single shield fuel scoop", function() {
|
||||
var id = 'anaconda';
|
||||
var anacondaData = Ships[id];
|
||||
var anaconda = new Ship(id, anacondaData.properties, anacondaData.slots);
|
||||
anaconda.buildWith(anacondaData.defaults);
|
||||
|
||||
anaconda.use(anaconda.internal[4], ModuleUtils.internal('32')); // 4A Fuel Scoop
|
||||
expect(anaconda.internal[4].m.grp).toEqual('fs', 'Anaconda fuel scoop slot');
|
||||
|
||||
anaconda.use(anaconda.internal[3], ModuleUtils.internal('32'));
|
||||
|
||||
expect(anaconda.internal[4].m).toEqual(null, 'Anaconda original fuel scoop slot is empty');
|
||||
expect(anaconda.internal[3].m.id).toEqual('32', 'Slot 1 should have FS 32 in it');
|
||||
expect(anaconda.internal[3].m.grp).toEqual('fs','Slot 1 should have FS 32 in it');
|
||||
});
|
||||
|
||||
it("enforces a single refinery", function() {
|
||||
var id = 'anaconda';
|
||||
var anacondaData = Ships[id];
|
||||
var anaconda = new Ship(id, anacondaData.properties, anacondaData.slots);
|
||||
anaconda.buildWith(anacondaData.defaults);
|
||||
|
||||
anaconda.use(anaconda.internal[4], ModuleUtils.internal('23')); // 4E Refinery
|
||||
expect(anaconda.internal[4].m.grp).toEqual('rf', 'Anaconda refinery slot');
|
||||
|
||||
anaconda.use(anaconda.internal[3], ModuleUtils.internal('23'));
|
||||
|
||||
expect(anaconda.internal[4].m).toEqual(null, 'Anaconda original refinery slot is empty');
|
||||
expect(anaconda.internal[3].m.id).toEqual('23', 'Slot 1 should have RF 23 in it');
|
||||
expect(anaconda.internal[3].m.grp).toEqual('rf','Slot 1 should have RF 23 in it');
|
||||
});
|
||||
|
||||
});
|
||||
@@ -1,25 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const TestUtils = {
|
||||
createContextProvider: function(context) {
|
||||
var _contextTypes = {};
|
||||
|
||||
Object.keys(context).forEach(function(key) {
|
||||
_contextTypes[key] = PropTypes.any;
|
||||
});
|
||||
|
||||
return React.createClass({
|
||||
displayName: 'ContextProvider',
|
||||
childContextTypes: _contextTypes,
|
||||
getChildContext() { return context; },
|
||||
|
||||
render() {
|
||||
return React.Children.only(this.props.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export default TestUtils;
|
||||
11
d3-funcs.js
vendored
11
d3-funcs.js
vendored
@@ -1,11 +0,0 @@
|
||||
export {
|
||||
axisBottom,
|
||||
axisLeft,
|
||||
axisTop,
|
||||
formatLocale,
|
||||
line,
|
||||
scaleBand,
|
||||
scaleLinear,
|
||||
scaleOrdinal,
|
||||
select
|
||||
} from 'd3';
|
||||
1
devServer.js
Normal file → Executable file
1
devServer.js
Normal file → Executable 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: [
|
||||
|
||||
59
nginx.conf
59
nginx.conf
@@ -1,59 +0,0 @@
|
||||
worker_processes 2;
|
||||
error_log ./nginx.error.log;
|
||||
worker_rlimit_nofile 8192;
|
||||
pid nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
multi_accept on;
|
||||
}
|
||||
|
||||
http {
|
||||
|
||||
access_log off;
|
||||
charset UTF-8;
|
||||
|
||||
types {
|
||||
text/html html htm shtml;
|
||||
text/css css;
|
||||
text/xml xml rss;
|
||||
image/gif gif;
|
||||
image/jpeg jpeg jpg;
|
||||
application/x-javascript js;
|
||||
text/plain txt;
|
||||
image/png png;
|
||||
image/svg+xml svg;
|
||||
image/x-icon ico;
|
||||
application/pdf pdf;
|
||||
text/cache-manifest appcache;
|
||||
}
|
||||
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_proxied any;
|
||||
gzip_comp_level 6;
|
||||
gzip_buffers 16 8k;
|
||||
gzip_http_version 1.1;
|
||||
gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
|
||||
|
||||
server {
|
||||
listen 3301;
|
||||
server_name localhost;
|
||||
root ./build/;
|
||||
index index.html;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
30544
package-lock.json
generated
Normal file
30544
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
152
package.json
152
package.json
@@ -1,106 +1,112 @@
|
||||
{
|
||||
"name": "coriolis_shipyard",
|
||||
"version": "2.3.4",
|
||||
"version": "3.0.0",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/EDCD/coriolis"
|
||||
},
|
||||
"homepage": "https://coriolis.edcd.io",
|
||||
"homepage": "https://coriolis.io",
|
||||
"bugs": "https://github.com/EDCD/coriolis/issues",
|
||||
"private": true,
|
||||
"engine": "node >= 4.0.0",
|
||||
"engine": "node >= 4.8.1",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"prepublish": "rollup -c && uglifyjs d3.js -c -m -o d3.min.js",
|
||||
"extract-translations": "grep -hroE \"(translate\\('[^']+'\\))|(tip.bind\\(null, '[^']+')\" src/* | grep -oE \"'[^']+'\" | grep -oE \"[^']+\" | sort -u -f",
|
||||
"clean": "rimraf build",
|
||||
"start": "node devServer.js",
|
||||
"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",
|
||||
"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-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",
|
||||
"@babel/core": "^7.0.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.0.0",
|
||||
"@babel/plugin-proposal-decorators": "^7.0.0",
|
||||
"@babel/plugin-proposal-do-expressions": "^7.0.0",
|
||||
"@babel/plugin-proposal-export-default-from": "^7.0.0",
|
||||
"@babel/plugin-proposal-export-namespace-from": "^7.0.0",
|
||||
"@babel/plugin-proposal-function-bind": "^7.0.0",
|
||||
"@babel/plugin-proposal-function-sent": "^7.0.0",
|
||||
"@babel/plugin-proposal-json-strings": "^7.0.0",
|
||||
"@babel/plugin-proposal-logical-assignment-operators": "^7.0.0",
|
||||
"@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0",
|
||||
"@babel/plugin-proposal-numeric-separator": "^7.0.0",
|
||||
"@babel/plugin-proposal-optional-chaining": "^7.0.0",
|
||||
"@babel/plugin-proposal-pipeline-operator": "^7.0.0",
|
||||
"@babel/plugin-proposal-throw-expressions": "^7.0.0",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.0.0",
|
||||
"@babel/plugin-syntax-import-meta": "^7.0.0",
|
||||
"@babel/preset-env": "^7.0.0",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"appcache-webpack-plugin": "^1.4.0",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-loader": "^8.0.0",
|
||||
"copy-webpack-plugin": "^4.5.2",
|
||||
"create-react-class": "^15.6.3",
|
||||
"cross-env": "^5.2.0",
|
||||
"css-loader": "^1.0.0",
|
||||
"d3-selection": "^1.3.2",
|
||||
"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": "^5.6.0",
|
||||
"eslint-plugin-react": "^7.11.1",
|
||||
"expose-loader": "^0.7.5",
|
||||
"express": "^4.16.3",
|
||||
"extract-text-webpack-plugin": "^4.0.0-beta.0",
|
||||
"file-loader": "^2.0.0",
|
||||
"html-webpack-plugin": "^3.0.7",
|
||||
"jsen": "^0.6.4",
|
||||
"json-loader": "^0.5.4",
|
||||
"less": "^2.7.2",
|
||||
"less-loader": "^4.0.3",
|
||||
"less": "^3.8.1",
|
||||
"less-loader": "^4.1.0",
|
||||
"react-addons-perf": "^15.4.2",
|
||||
"react-measure": "^1.4.7",
|
||||
"react-testutils-additions": "^15.2.0",
|
||||
"react-container-dimensions": "^1.4.1",
|
||||
"react-testutils-additions": "^16.0.0",
|
||||
"react-transition-group": "^2.5.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"
|
||||
"rollup": "^0.66.2",
|
||||
"rollup-plugin-node-resolve": "^3.4.0",
|
||||
"style-loader": "^0.23.0",
|
||||
"uglify-js": "^3.4.9",
|
||||
"url-loader": "^1.1.1",
|
||||
"webpack": "^4.20.2",
|
||||
"webpack-bugsnag-plugins": "^1.2.2",
|
||||
"webpack-cli": "^3.1.1",
|
||||
"webpack-dev-server": "^3.1.9",
|
||||
"webpack-notifier": "^1.6.0",
|
||||
"workbox-webpack-plugin": "^3.6.1"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"dependencies": {
|
||||
"babel-polyfill": "*",
|
||||
"@babel/polyfill": "^7.0.0",
|
||||
"auto-bind": "^2.1.1",
|
||||
"browserify-zlib-next": "^1.0.1",
|
||||
"classnames": "^2.2.5",
|
||||
"coriolis-data": "EDCD/coriolis-data",
|
||||
"d3": "4.8.0",
|
||||
"detect-browser": "^1.7.0",
|
||||
"classnames": "^2.2.6",
|
||||
"d3": "^5.7.0",
|
||||
"detect-browser": "^3.0.1",
|
||||
"ed-forge": "../ed-forge",
|
||||
"fbemitter": "^2.1.1",
|
||||
"lodash": "^4.17.4",
|
||||
"lodash": "^4.17.11",
|
||||
"lz-string": "^1.4.4",
|
||||
"prop-types": "^15.5.8",
|
||||
"pako": "^1.0.6",
|
||||
"prop-types": "^15.6.2",
|
||||
"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"
|
||||
"react-extras": "^0.7.1",
|
||||
"react-fuzzy": "^0.5.2",
|
||||
"react-ga": "^2.5.3",
|
||||
"react-number-editor": "^4.0.3",
|
||||
"recharts": "^1.2.0",
|
||||
"register-service-worker": "^1.5.2",
|
||||
"superagent": "^3.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,28 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Router from './Router';
|
||||
import { register } from 'register-service-worker';
|
||||
import { EventEmitter } from 'fbemitter';
|
||||
import { getLanguage } from './i18n/Language';
|
||||
import Persist from './stores/Persist';
|
||||
import { Ship } from 'ed-forge';
|
||||
|
||||
import Announcement from './components/Announcement';
|
||||
import Header from './components/Header';
|
||||
import Tooltip from './components/Tooltip';
|
||||
import ModalExport from './components/ModalExport';
|
||||
import ModalHelp from './components/ModalHelp';
|
||||
import ModalImport from './components/ModalImport';
|
||||
import ModalPermalink from './components/ModalPermalink';
|
||||
import * as CompanionApiUtils from './utils/CompanionApiUtils';
|
||||
|
||||
import AboutPage from './pages/AboutPage';
|
||||
import NotFoundPage from './pages/NotFoundPage';
|
||||
import OutfittingPage from './pages/OutfittingPage';
|
||||
import ComparisonPage from './pages/ComparisonPage';
|
||||
import ShipyardPage from './pages/ShipyardPage';
|
||||
import ErrorDetails from './pages/ErrorDetails';
|
||||
|
||||
const zlib = require('zlib');
|
||||
|
||||
/**
|
||||
* Coriolis App
|
||||
*/
|
||||
export default class Coriolis extends React.Component {
|
||||
|
||||
static childContextTypes = {
|
||||
closeMenu: PropTypes.func.isRequired,
|
||||
hideModal: PropTypes.func.isRequired,
|
||||
@@ -59,26 +55,24 @@ export default class Coriolis extends React.Component {
|
||||
this._onLanguageChange = this._onLanguageChange.bind(this);
|
||||
this._onSizeRatioChange = this._onSizeRatioChange.bind(this);
|
||||
this._keyDown = this._keyDown.bind(this);
|
||||
this._importBuild = this._importBuild.bind(this);
|
||||
|
||||
this.emitter = new EventEmitter();
|
||||
this.state = {
|
||||
noTouch: !('ontouchstart' in window || navigator.msMaxTouchPoints || navigator.maxTouchPoints),
|
||||
page: null,
|
||||
announcements: [],
|
||||
language: getLanguage(Persist.getLangCode()),
|
||||
route: {},
|
||||
sizeRatio: Persist.getSizeRatio()
|
||||
};
|
||||
|
||||
// TODO: New mechanism for announcements
|
||||
// this._getAnnouncements();
|
||||
Router('', (r) => this._setPage(ShipyardPage, r));
|
||||
Router('/import?', (r) => this._importBuild(r));
|
||||
Router('/import/:data', (r) => this._importBuild(r));
|
||||
Router('/outfit/?', (r) => this._setPage(OutfittingPage, r));
|
||||
Router('/outfit/:ship/?', (r) => this._setPage(OutfittingPage, r));
|
||||
Router('/outfit/:ship/:code?', (r) => this._setPage(OutfittingPage, r));
|
||||
Router('/compare/:name?', (r) => this._setPage(ComparisonPage, r));
|
||||
Router('/comparison?', (r) => this._setPage(ComparisonPage, r));
|
||||
Router('/comparison/:code', (r) => this._setPage(ComparisonPage, r));
|
||||
Router('/about', (r) => this._setPage(AboutPage, r));
|
||||
Router('*', (r) => this._setPage(null, r));
|
||||
}
|
||||
@@ -90,17 +84,25 @@ 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 json = JSON.parse(data);
|
||||
const ship = CompanionApiUtils.shipFromJson(json);
|
||||
r.params.ship = ship.id;
|
||||
r.params.code = ship.toString();
|
||||
let ship = new Ship(r.params.data);
|
||||
r.params.ship = ship.getShipType();
|
||||
r.params.code = ship.compress();
|
||||
this._setPage(OutfittingPage, r);
|
||||
} catch (err) {
|
||||
this._onError('Failed to import ship', r.path, 0, 0, err);
|
||||
}
|
||||
}
|
||||
|
||||
// async _getAnnouncements() {
|
||||
// try {
|
||||
// const announces = await request.get('https://orbis.zone/api/announcement')
|
||||
// .query({ showInCoriolis: true });
|
||||
// this.setState({ announcements: announces.body });
|
||||
// } catch (err) {
|
||||
// console.error(err)
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* Updates / Sets the page and route context
|
||||
* @param {[type]} page The page to be shown
|
||||
@@ -124,6 +126,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 +174,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 +202,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 +280,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 +316,50 @@ 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) {
|
||||
// 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;
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
register('/service-worker.js', {
|
||||
ready(registration) {
|
||||
console.log('Service worker is active.');
|
||||
},
|
||||
registered(registration) {
|
||||
console.log('Service worker has been registered.');
|
||||
},
|
||||
cached(registration) {
|
||||
console.log('Content has been cached for offline use.');
|
||||
},
|
||||
updatefound(registration) {
|
||||
console.log('New content is downloading.');
|
||||
},
|
||||
updated(registration) {
|
||||
self.setState({ appCacheUpdate: true });
|
||||
console.log('New content is available; please refresh.');
|
||||
},
|
||||
offline() {
|
||||
console.log('No internet connection found. App is running in offline mode.');
|
||||
},
|
||||
error(error) {
|
||||
console.error('Error during service worker registration:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
window.onerror = this._onError.bind(this);
|
||||
window.addEventListener('resize', () => this.emitter.emit('windowResize'));
|
||||
document.getElementById('coriolis').addEventListener('scroll', () => this._tooltip());
|
||||
@@ -331,15 +376,28 @@ 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 announcements={this.state.announcements} appCacheUpdate={this.state.appCacheUpdate}
|
||||
currentMenu={currentMenu}/>
|
||||
{/* <div className="announcement-container">{this.state.announcements.map(a => <Announcement
|
||||
text={a.message}/>)}</div> */}
|
||||
{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" rel="noopener noreferrer"
|
||||
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" rel="noopener noreferrer" title={'Coriolis Commits since' + window.CORIOLIS_DATE}>Commits
|
||||
since last release
|
||||
({window.CORIOLIS_DATE})</a>
|
||||
</div>
|
||||
</footer>
|
||||
</div>;
|
||||
|
||||
@@ -72,6 +72,7 @@ Router.go = function(path, state) {
|
||||
gaTrack(path);
|
||||
let ctx = new Context(path, state);
|
||||
Router.dispatch(ctx);
|
||||
|
||||
if (!ctx.unhandled) {
|
||||
if (isStandAlone()) {
|
||||
Persist.setState(ctx);
|
||||
@@ -257,9 +258,8 @@ Route.prototype.match = function(path, params) {
|
||||
* @param {string} path Path to track
|
||||
*/
|
||||
function gaTrack(path) {
|
||||
if (window.ga) {
|
||||
window.ga('send', 'pageview', path);
|
||||
}
|
||||
const _paq = window._paq || [];
|
||||
_paq.push(['trackPageView']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,6 @@ function isActive(href) {
|
||||
* Active Link - Highlighted when URL matches window location
|
||||
*/
|
||||
export default class ActiveLink extends Link {
|
||||
|
||||
/**
|
||||
* Renders the component
|
||||
* @return {React.Component} The active link
|
||||
@@ -29,5 +28,4 @@ export default class ActiveLink extends Link {
|
||||
|
||||
return <a {...this.props} className={className} onClick={this.handler}>{this.props.children}</a>;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
29
src/app/components/Announcement.jsx
Normal file
29
src/app/components/Announcement.jsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { autoBind } from 'react-extras';
|
||||
|
||||
/**
|
||||
* Announcement component
|
||||
*/
|
||||
export default class Announcement extends React.Component {
|
||||
static propTypes = {
|
||||
text: PropTypes.string
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
autoBind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the announcement
|
||||
* @return {React.Component} A href element
|
||||
*/
|
||||
render() {
|
||||
return <div className="announcement" >{this.props.text}</div>;
|
||||
}
|
||||
}
|
||||
@@ -1,98 +1,34 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
import cn from 'classnames';
|
||||
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
|
||||
import FuzzySearch from 'react-fuzzy';
|
||||
import { getModuleInfo } from 'ed-forge/lib/src/data/items';
|
||||
import { get, groupBy, mapValues, sortBy, zip, zipWith } from 'lodash';
|
||||
import autoBind from 'auto-bind';
|
||||
import MODULE_STATS from 'ed-forge/lib/src/module-stats';
|
||||
import { SHOW } from '../shipyard/StatsMapping';
|
||||
|
||||
const PRESS_THRESHOLD = 500; // mouse/touch down threshold
|
||||
|
||||
/*
|
||||
* Categorisation of module groups
|
||||
*/
|
||||
const GRPCAT = {
|
||||
'sg': 'shields',
|
||||
'bsg': 'shields',
|
||||
'psg': 'shields',
|
||||
'scb': 'shields',
|
||||
'cc': 'limpet controllers',
|
||||
'fx': 'limpet controllers',
|
||||
'hb': 'limpet controllers',
|
||||
'pc': 'limpet controllers',
|
||||
'pce': 'passenger cabins',
|
||||
'pci': 'passenger cabins',
|
||||
'pcm': 'passenger cabins',
|
||||
'pcq': 'passenger cabins',
|
||||
'fh': 'hangars',
|
||||
'pv': 'hangars',
|
||||
'fs': 'fuel',
|
||||
'ft': 'fuel',
|
||||
'hr': 'structural reinforcement',
|
||||
'mrp': 'structural reinforcement',
|
||||
'bl': 'lasers',
|
||||
'pl': 'lasers',
|
||||
'ul': 'lasers',
|
||||
'ml': 'lasers',
|
||||
'c': 'projectiles',
|
||||
'mc': 'projectiles',
|
||||
'fc': 'projectiles',
|
||||
'pa': 'projectiles',
|
||||
'rg': 'projectiles',
|
||||
'mr': 'ordnance',
|
||||
'tp': 'ordnance',
|
||||
'nl': 'ordnance',
|
||||
'sc': 'scanners',
|
||||
'ss': 'scanners',
|
||||
// Utilities
|
||||
'cs': 'scanners',
|
||||
'kw': 'scanners',
|
||||
'ws': 'scanners',
|
||||
'ch': 'defence',
|
||||
'po': 'defence',
|
||||
'ec': 'defence',
|
||||
};
|
||||
// Order here is the order in which items will be shown in the modules menu
|
||||
const CATEGORIES = {
|
||||
// Internals
|
||||
'am': ['am'],
|
||||
'cr': ['cr'],
|
||||
'fi': ['fi'],
|
||||
'fuel': ['ft', 'fs'],
|
||||
'hangars': ['fh', 'pv'],
|
||||
'limpet controllers': ['cc', 'fx', 'hb', 'pc'],
|
||||
'passenger cabins': ['pce', 'pci', 'pcm', 'pcq'],
|
||||
'rf': ['rf'],
|
||||
'shields': ['sg', 'bsg', 'psg', 'scb'],
|
||||
'structural reinforcement': ['hr', 'mrp'],
|
||||
'dc': ['dc'],
|
||||
// Hardpoints
|
||||
'lasers': ['pl', 'ul', 'bl', 'ml'],
|
||||
'projectiles': ['mc', 'c', 'fc', 'pa', 'rg'],
|
||||
'ordnance': ['mr', 'tp', 'nl'],
|
||||
// Utilities
|
||||
'sb': ['sb'],
|
||||
'hs': ['hs'],
|
||||
'defence': ['ch', 'po', 'ec'],
|
||||
'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners
|
||||
const MOUNT_MAP = {
|
||||
fixed: <MountFixed className={'lg'} />,
|
||||
gimbal: <MountGimballed className={'lg'} />,
|
||||
turret: <MountTurret className={'lg'} />,
|
||||
};
|
||||
|
||||
/**
|
||||
* Available modules menu
|
||||
*/
|
||||
export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
modules: PropTypes.oneOfType([PropTypes.object, PropTypes.array]).isRequired,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
diffDetails: PropTypes.func,
|
||||
hideSearch: PropTypes.bool,
|
||||
m: PropTypes.object,
|
||||
shipMass: PropTypes.number,
|
||||
warning: PropTypes.func
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
shipMass: 0
|
||||
warning: PropTypes.func,
|
||||
slotDiv: PropTypes.object
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -102,7 +38,7 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
this._hideDiff = this._hideDiff.bind(this);
|
||||
autoBind(this);
|
||||
this.state = this._initState(props, context);
|
||||
}
|
||||
|
||||
@@ -113,175 +49,230 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
* @return {Object} list: Array of React Components, currentGroup Component if any
|
||||
*/
|
||||
_initState(props, context) {
|
||||
let translate = context.language.translate;
|
||||
let { m, warning, shipMass, onSelect, modules } = props;
|
||||
let list, currentGroup;
|
||||
let buildGroup = this._buildGroup.bind(
|
||||
this,
|
||||
translate,
|
||||
m,
|
||||
warning,
|
||||
shipMass - (m && m.mass ? m.mass : 0),
|
||||
(m, event) => {
|
||||
this._hideDiff(event);
|
||||
onSelect(m);
|
||||
}
|
||||
const { translate } = context.language;
|
||||
const { m } = props;
|
||||
const list = [], fuzzy = [];
|
||||
let currentGroup;
|
||||
|
||||
const modules = m.getApplicableItems().map(getModuleInfo);
|
||||
const groups = mapValues(
|
||||
groupBy(modules, (info) => info.meta.group),
|
||||
(infos) => groupBy(infos, (info) => info.meta.type),
|
||||
);
|
||||
|
||||
if (modules instanceof Array) {
|
||||
list = buildGroup(modules[0].grp, modules);
|
||||
} else {
|
||||
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>);
|
||||
// Build categories sorted by translated category name
|
||||
const groupKeys = sortBy(Object.keys(groups), translate);
|
||||
for (const group of groupKeys) {
|
||||
const groupName = translate(group);
|
||||
if (groupKeys.length > 1) {
|
||||
list.push(<div key={`group-${group}`} className="select-category upp">{groupName}</div>);
|
||||
}
|
||||
|
||||
// Need to regroup the modules by our own categorisation
|
||||
let catmodules = {};
|
||||
// Pre-create to preserve ordering
|
||||
for (let cat in CATEGORIES) {
|
||||
catmodules[cat] = [];
|
||||
}
|
||||
for (let g in modules) {
|
||||
const moduleCategory = GRPCAT[g] || g;
|
||||
const existing = catmodules[moduleCategory] || [];
|
||||
catmodules[moduleCategory] = existing.concat(modules[g]);
|
||||
}
|
||||
|
||||
for (let category in catmodules) {
|
||||
let categoryHeader = false;
|
||||
// Order through CATEGORIES if present
|
||||
const categories = CATEGORIES[category] || [category];
|
||||
if (categories && categories.length) {
|
||||
for (let n in categories) {
|
||||
const grp = categories[n];
|
||||
// We now have the group and the category. We might not have any modules, though...
|
||||
if (modules[grp]) {
|
||||
// Decide if we need a category header as well as a group header
|
||||
if (categories.length === 1) {
|
||||
// Show category header instead of group header
|
||||
if (m && grp == m.grp) {
|
||||
list.push(<div ref={(elem) => this.groupElem = elem} key={category} className={'select-category upp'}>{translate(category)}</div>);
|
||||
} else {
|
||||
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
|
||||
}
|
||||
} else {
|
||||
// Show category header as well as group header
|
||||
if (!categoryHeader) {
|
||||
list.push(<div key={category} className={'select-category upp'}>{translate(category)}</div>);
|
||||
categoryHeader = true;
|
||||
}
|
||||
if (m && grp == m.grp) {
|
||||
list.push(<div ref={(elem) => this.groupElem = elem} key={grp} className={'select-group cap'}>{translate(grp)}</div>);
|
||||
} else {
|
||||
list.push(<div key={grp} className={'select-group cap'}>{translate(grp)}</div>);
|
||||
}
|
||||
}
|
||||
list.push(buildGroup(grp, modules[grp]));
|
||||
}
|
||||
}
|
||||
const categories = groups[group];
|
||||
const categoryKeys = sortBy(Object.keys(categories), translate);
|
||||
for (const category of categoryKeys) {
|
||||
const categoryName = translate(category);
|
||||
const infos = categories[category];
|
||||
if (categoryKeys.length > 1) {
|
||||
list.push(<div key={`category-${category}`} className="select-group cap">{categoryName}</div>);
|
||||
}
|
||||
list.push(
|
||||
this._buildGroup(
|
||||
m,
|
||||
category,
|
||||
infos,
|
||||
),
|
||||
);
|
||||
fuzzy.push(
|
||||
...infos.map((info) => {
|
||||
const { meta } = info;
|
||||
const mount = meta.mount ? ' ' + translate(meta.mount) : '';
|
||||
return {
|
||||
grp: groupName,
|
||||
cat: categoryName,
|
||||
m: info.proto.Item,
|
||||
name: `${meta.class}${meta.rating}${mount} ${categoryName}`,
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return { list, currentGroup };
|
||||
return { list, currentGroup, fuzzy, trackingFocus: false };
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate React Components for Module Group
|
||||
* @param {Function} translate Translate function
|
||||
* @param {Objecy} mountedModule Mounted Module
|
||||
* @param {Funciton} warningFunc Warning function
|
||||
* @param {number} mass Mass
|
||||
* @param {function} onSelect Select/Mount callback
|
||||
* @param {string} grp Group name
|
||||
* @param {Object} mountedModule Mounted Module
|
||||
* @param {String} category Category key
|
||||
* @param {Array} modules Available modules
|
||||
* @return {React.Component} Available Module Group contents
|
||||
*/
|
||||
_buildGroup(translate, mountedModule, warningFunc, mass, onSelect, grp, modules) {
|
||||
let prevClass = null, prevRating = null;
|
||||
let elems = [];
|
||||
_buildGroup(mountedModule, category, modules) {
|
||||
const { warning } = this.props;
|
||||
const ship = mountedModule.getShip();
|
||||
const classMapping = groupBy(modules, (info) => info.meta.class);
|
||||
|
||||
const sortedModules = modules.sort(this._moduleOrder);
|
||||
const itemsPerClass = Math.max(
|
||||
...Object.values(classMapping).map((l) => l.length),
|
||||
);
|
||||
const itemsPerRow = itemsPerClass <= 2 ? 6 : itemsPerClass;
|
||||
// Nested array of <li> elements; will be flattened before being rendered.
|
||||
// Each sub-array represents one row in the final view.
|
||||
const elems = [[]];
|
||||
|
||||
// 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]));
|
||||
// Reverse sort for descending order of module class
|
||||
for (const clazz of Object.keys(classMapping).sort().reverse()) {
|
||||
for (let info of sortBy(
|
||||
classMapping[clazz],
|
||||
(info) => info.meta.mount || info.meta.rating,
|
||||
)) {
|
||||
const { meta } = info;
|
||||
const { Item } = info.proto;
|
||||
|
||||
let itemsOnThisRow = 0;
|
||||
// Can only be true if shieldgenmaximalmass is defined, i.e. this
|
||||
// module must be a shield generator
|
||||
let disabled = info.props.shieldgenmaximalmass < ship.readProp('hullmass');
|
||||
if (meta.experimental && !mountedModule.readMeta('experimental')) {
|
||||
disabled =
|
||||
4 <=
|
||||
ship.getHardpoints().filter((m) => m.readMeta('experimental'))
|
||||
.length;
|
||||
}
|
||||
|
||||
for (let i = 0; i < sortedModules.length; i++) {
|
||||
let m = sortedModules[i];
|
||||
let mount = null;
|
||||
let disabled = false;
|
||||
if (ModuleUtils.isShieldGenerator(m.grp)) {
|
||||
// Shield generators care about maximum hull mass
|
||||
disabled = mass > m.maxmass;
|
||||
} else if (m.maxmass) {
|
||||
// Thrusters care about total mass
|
||||
disabled = mass + m.mass > m.maxmass;
|
||||
// Default event handlers for objects that are disabled
|
||||
let eventHandlers = {};
|
||||
if (!disabled) {
|
||||
const showDiff = this._showDiff.bind(this, mountedModule, info);
|
||||
const select = (event) => {
|
||||
this._hideDiff(event);
|
||||
this.props.onSelect(Item);
|
||||
};
|
||||
|
||||
eventHandlers = {
|
||||
onMouseEnter: this._over.bind(this, showDiff),
|
||||
onTouchStart: this._touchStart.bind(this, showDiff),
|
||||
onTouchEnd: this._touchEnd.bind(this, select),
|
||||
onMouseLeave: this._hideDiff,
|
||||
onClick: select,
|
||||
};
|
||||
}
|
||||
|
||||
const mountSymbol = MOUNT_MAP[meta.mount];
|
||||
const li = (
|
||||
<li key={Item} data-id={Item}
|
||||
ref={Item === mountedModule.getItem() ? (ref) => { this.activeSlotRef = ref; } : undefined}
|
||||
className={cn(meta.type === 'armour' ? 'lc' : 'c', {
|
||||
warning: !disabled && warning && warning(info),
|
||||
active: mountedModule.getItem() === Item,
|
||||
disabled,
|
||||
hardpoint: mountSymbol,
|
||||
})}
|
||||
{...eventHandlers}
|
||||
>{mountSymbol}{meta.type === 'armour' ? Item : `${meta.class}${meta.rating}`}</li>
|
||||
);
|
||||
|
||||
const tail = elems.pop();
|
||||
let newTail = [tail];
|
||||
if (tail.length < itemsPerRow) {
|
||||
// If the row has not grown too long, the new <li> element can be
|
||||
// added to the row itself
|
||||
tail.push(li);
|
||||
} else {
|
||||
// Otherwise, the last row gets a line break element added and this
|
||||
// item is put into a new row
|
||||
tail.push(<br key={elems.length}/>);
|
||||
newTail.push([li]);
|
||||
}
|
||||
elems.push(...newTail);
|
||||
}
|
||||
let active = mountedModule && mountedModule.id === m.id;
|
||||
let classes = cn(m.name ? 'lc' : 'c', {
|
||||
warning: !disabled && warningFunc && warningFunc(m),
|
||||
active,
|
||||
disabled
|
||||
});
|
||||
let eventHandlers;
|
||||
|
||||
if (disabled || active) {
|
||||
eventHandlers = {};
|
||||
} else {
|
||||
let showDiff = this._showDiff.bind(this, mountedModule, m);
|
||||
let select = onSelect.bind(null, m);
|
||||
|
||||
eventHandlers = {
|
||||
onMouseEnter: this._over.bind(this, showDiff),
|
||||
onTouchStart: this._touchStart.bind(this, showDiff),
|
||||
onTouchEnd: this._touchEnd.bind(this, select),
|
||||
onMouseLeave: this._hideDiff,
|
||||
onClick: select
|
||||
};
|
||||
}
|
||||
|
||||
switch(m.mount) {
|
||||
case 'F': mount = <MountFixed className={'lg'} />; break;
|
||||
case 'G': mount = <MountGimballed className={'lg'}/>; break;
|
||||
case 'T': mount = <MountTurret className={'lg'}/>; break;
|
||||
}
|
||||
|
||||
if (itemsOnThisRow == 6 || i > 0 && sortedModules.length > 3 && itemsPerClass > 2 && m.class != prevClass && (m.rating != prevRating || m.mount)) {
|
||||
elems.push(<br key={'b' + m.grp + i} />);
|
||||
itemsOnThisRow = 0;
|
||||
}
|
||||
|
||||
elems.push(
|
||||
<li key={m.id} className={classes} {...eventHandlers}>
|
||||
{mount}
|
||||
{(mount ? ' ' : '') + m.class + m.rating + (m.missile ? '/' + m.missile : '') + (m.name ? ' ' + translate(m.name) : '')}
|
||||
</li>
|
||||
);
|
||||
itemsOnThisRow++;
|
||||
prevClass = m.class;
|
||||
prevRating = m.rating;
|
||||
}
|
||||
|
||||
return <ul key={'modules' + grp} >{elems}</ul>;
|
||||
return <ul key={'ul' + category}>{[].concat(...elems)}</ul>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate tooltip content for the difference between the
|
||||
* mounted module and the hovered modules
|
||||
* @param {Object} mm The module mounet currently
|
||||
* @param {Object} m The hovered module
|
||||
* @param {Object} mountedModule The module mounted currently
|
||||
* @param {Object} hoveringModule The hovered module
|
||||
* @param {DOMRect} rect DOMRect for target element
|
||||
*/
|
||||
_showDiff(mm, m, rect) {
|
||||
if (this.props.diffDetails) {
|
||||
this.touchTimeout = null;
|
||||
this.context.tooltip(this.props.diffDetails(m, mm), rect);
|
||||
_showDiff(mountedModule, hoveringModule, rect) {
|
||||
const { tooltip, language } = this.context;
|
||||
const { formats, translate, units } = language;
|
||||
this.touchTimeout = null;
|
||||
const mountedIsEmpty = mountedModule.isEmpty();
|
||||
const props = (
|
||||
mountedIsEmpty ? ['mass'] : Object.keys(hoveringModule.props)
|
||||
).map((prop) => SHOW[prop] ? SHOW[prop].as : prop);
|
||||
const oldProps = mountedIsEmpty ?
|
||||
[{ value: 0 }] :
|
||||
props.map((prop) => mountedModule.getFormatted(prop, false));
|
||||
const newProps = mountedModule.try(() => {
|
||||
mountedModule.setItem(hoveringModule.proto.Item);
|
||||
return props.map((prop) => mountedModule.getFormatted(prop, false));
|
||||
});
|
||||
|
||||
const diffs = zipWith(oldProps, newProps, (oldVal, newVal) => {
|
||||
const { unit, value } = newVal;
|
||||
if (!oldVal.value) {
|
||||
return undefined;
|
||||
}
|
||||
return { value, diff: value - oldVal.value, unit };
|
||||
});
|
||||
const namedDiffs = zip(props, diffs).filter(([_, stat]) => stat !== undefined);
|
||||
namedDiffs.push(['cost', {
|
||||
value: hoveringModule.meta.cost,
|
||||
diff: hoveringModule.meta.cost - (mountedIsEmpty ? 0 : mountedModule.readMeta('cost')),
|
||||
unit: units.CR,
|
||||
}]);
|
||||
|
||||
const tt = <div className='cap' style={{ whiteSpace: 'nowrap' }}>
|
||||
{sortBy(namedDiffs, ([prop, _]) => prop).map(([prop, stats]) => {
|
||||
const { unit, value, diff } = stats;
|
||||
const beneficial = get(MODULE_STATS, [prop, 'higherbetter'], false) === diff > 0;
|
||||
return <div key={prop}>
|
||||
{translate(prop)}: <span className={diff === 0 ? 'disabled' : beneficial ? 'secondary' : 'warning'}>
|
||||
{formats.round(value)} {diff !== 0 && ` (${diff > 0 ? '+' : ''}${formats.round(diff)})`}{unit}
|
||||
</span>
|
||||
</div>;
|
||||
})}
|
||||
</div>;
|
||||
tooltip(tt, rect);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate tooltip content for the difference between the
|
||||
* mounted module and the hovered modules
|
||||
* @returns {React.Component} Search component if available
|
||||
*/
|
||||
_showSearch() {
|
||||
if (this.props.hideSearch) {
|
||||
return;
|
||||
}
|
||||
return (
|
||||
<FuzzySearch
|
||||
list={this.state.fuzzy}
|
||||
keys={['grp', 'name']}
|
||||
tokenize={true}
|
||||
className={'input'}
|
||||
width={'100%'}
|
||||
style={{ padding: 0 }}
|
||||
onSelect={e => this.props.onSelect.bind(null, e.m)()}
|
||||
resultsTemplate={(props, state, styles, clickHandler) => {
|
||||
return state.results.map((val, i) => {
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
className={'lc'}
|
||||
onClick={() => clickHandler(i)}
|
||||
>
|
||||
{val.name}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -328,52 +319,22 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
this.context.tooltip();
|
||||
}
|
||||
|
||||
/**
|
||||
* Order two modules suitably for display in module selection
|
||||
* @param {Object} a the first module
|
||||
* @param {Object} b the second module
|
||||
* @return {int} -1 if the first module should go first, 1 if the second module should go first
|
||||
*/
|
||||
_moduleOrder(a, b) {
|
||||
// Named modules go last
|
||||
if (!a.name && b.name) {
|
||||
return -1;
|
||||
}
|
||||
if (a.name && !b.name) {
|
||||
return 1;
|
||||
}
|
||||
// Class ordered from highest (8) to lowest (1)
|
||||
if (a.class < b.class) {
|
||||
return 1;
|
||||
}
|
||||
if (a.class > b.class) {
|
||||
return -1;
|
||||
}
|
||||
// Mount type, if applicable
|
||||
if (a.mount && b.mount && a.mount !== b.mount) {
|
||||
if (a.mount === 'F' || (a.mount === 'G' && b.mount === 'T')) {
|
||||
return -1;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
// Rating ordered from highest (A) to lowest (E)
|
||||
if (a.rating < b.rating) {
|
||||
return -1;
|
||||
}
|
||||
if (a.rating > b.rating) {
|
||||
return 1;
|
||||
}
|
||||
// Do not attempt to order by name at this point, as that mucks up the order of armour
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scroll to mounted (if it exists) module group on mount
|
||||
*/
|
||||
componentDidMount() {
|
||||
if (this.groupElem) { // Scroll to currently selected group
|
||||
this.node.scrollTop = this.groupElem.offsetTop;
|
||||
if (this.activeSlotRef) {
|
||||
this.activeSlotRef.focus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle focus if the component updates
|
||||
*
|
||||
*/
|
||||
componentWillUnmount() {
|
||||
if (this.props.slotDiv) {
|
||||
this.props.slotDiv.focus();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,14 +354,14 @@ export default class AvailableModulesMenu extends TranslatedComponent {
|
||||
render() {
|
||||
return (
|
||||
<div ref={node => this.node = node}
|
||||
className={cn('select', this.props.className)}
|
||||
onScroll={this._hideDiff}
|
||||
onClick={(e) => e.stopPropagation() }
|
||||
onContextMenu={stopCtxPropagation}
|
||||
className={cn('select', this.props.className)}
|
||||
onScroll={this._hideDiff}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onContextMenu={stopCtxPropagation}
|
||||
>
|
||||
{this._showSearch()}
|
||||
{this.state.list}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,7 +38,6 @@ function insertLinebreaks(d) {
|
||||
* Bar Chart
|
||||
*/
|
||||
export default class BarChart extends TranslatedComponent {
|
||||
|
||||
static defaultProps = {
|
||||
colors: ['#7b6888', '#6b486b', '#3182bd', '#a05d56', '#d0743c'],
|
||||
labels: null,
|
||||
|
||||
@@ -1,13 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import { nameComparator } from '../utils/SlotFunctions';
|
||||
import { Pip } from './SvgIcons';
|
||||
import LineChart from '../components/LineChart';
|
||||
import Slider from '../components/Slider';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import Module from '../shipyard/Module';
|
||||
import autoBind from 'auto-bind';
|
||||
|
||||
/**
|
||||
* Boost displays a boost button that toggles bosot
|
||||
@@ -15,8 +9,6 @@ import Module from '../shipyard/Module';
|
||||
*/
|
||||
export default class Boost extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
marker: PropTypes.string.isRequired,
|
||||
ship: PropTypes.object.isRequired,
|
||||
boost: PropTypes.bool.isRequired,
|
||||
onChange: PropTypes.func.isRequired
|
||||
};
|
||||
@@ -26,12 +18,9 @@ export default class Boost extends TranslatedComponent {
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
const { ship, boost } = props;
|
||||
|
||||
this._keyDown = this._keyDown.bind(this);
|
||||
this._toggleBoost = this._toggleBoost.bind(this);
|
||||
autoBind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,13 +66,12 @@ export default class Boost extends TranslatedComponent {
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { formats, translate, units } = this.context.language;
|
||||
const { ship, boost } = this.props;
|
||||
|
||||
// TODO disable if ship cannot boost
|
||||
const { translate } = this.context.language;
|
||||
return (
|
||||
<span id='boost'>
|
||||
<button id='boost' className={boost ? 'selected' : null} onClick={this._toggleBoost}>{translate('boost')}</button>
|
||||
<button id='boost' className={this.props.boost ? 'selected' : null} onClick={this._toggleBoost}>
|
||||
{translate('boost')}
|
||||
</button>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import Slider from '../components/Slider';
|
||||
import autoBind from 'auto-bind';
|
||||
|
||||
/**
|
||||
* Cargo slider
|
||||
@@ -22,8 +22,7 @@ export default class Cargo extends TranslatedComponent {
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
this._cargoChange = this._cargoChange.bind(this);
|
||||
autoBind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,7 +10,6 @@ import { outfitURL } from '../utils/UrlGenerators';
|
||||
* Comparison Table
|
||||
*/
|
||||
export default class ComparisonTable extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
facets: PropTypes.array.isRequired,
|
||||
builds: PropTypes.array.isRequired,
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import Persist from '../stores/Persist';
|
||||
import Ship from '../shipyard/Ship';
|
||||
import { Factory, Ship } from 'ed-forge';
|
||||
import { Insurance } from '../shipyard/Constants';
|
||||
import { slotName, slotComparator } from '../utils/SlotFunctions';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { ShoppingIcon } from '../components/SvgIcons';
|
||||
import { ShoppingIcon } from './SvgIcons';
|
||||
import autoBind from 'auto-bind';
|
||||
import { assign, differenceBy, sortBy, reverse } from 'lodash';
|
||||
import { FUEL_CAPACITY } from 'ed-forge/lib/src/ship-stats';
|
||||
|
||||
/**
|
||||
* Cost Section
|
||||
*/
|
||||
export default class CostSection extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
ship: PropTypes.object.isRequired,
|
||||
code: PropTypes.string.isRequired,
|
||||
buildName: PropTypes.string
|
||||
buildName: PropTypes.string,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -26,71 +26,34 @@ export default class CostSection extends TranslatedComponent {
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._costsTab = this._costsTab.bind(this);
|
||||
this._sortCost = this._sortCost.bind(this);
|
||||
this._sortAmmo = this._sortAmmo.bind(this);
|
||||
this._sortRetrofit = this._sortRetrofit.bind(this);
|
||||
this._buildRetrofitShip = this._buildRetrofitShip.bind(this);
|
||||
this._onBaseRetrofitChange = this._onBaseRetrofitChange.bind(this);
|
||||
this._defaultRetrofitName = this._defaultRetrofitName.bind(this);
|
||||
this._eddbShoppingList = this._eddbShoppingList.bind(this);
|
||||
|
||||
let data = Ships[props.ship.id]; // Retrieve the basic ship properties, slots and defaults
|
||||
let retrofitName = this._defaultRetrofitName(props.ship.id, props.buildName);
|
||||
let retrofitShip = this._buildRetrofitShip(props.ship.id, retrofitName);
|
||||
let shipDiscount = Persist.getShipDiscount();
|
||||
let moduleDiscount = Persist.getModuleDiscount();
|
||||
|
||||
this.props.ship.applyDiscounts(shipDiscount, moduleDiscount);
|
||||
retrofitShip.applyDiscounts(shipDiscount, moduleDiscount);
|
||||
autoBind(this);
|
||||
|
||||
const { ship, buildName } = props;
|
||||
this.shipType = ship.getShipType();
|
||||
this.state = {
|
||||
retrofitShip,
|
||||
retrofitName,
|
||||
shipDiscount,
|
||||
moduleDiscount,
|
||||
retrofitName: Persist.hasBuild(ship.getShipType(), buildName) ? buildName : null,
|
||||
shipDiscount: Persist.getShipDiscount(),
|
||||
moduleDiscount: Persist.getModuleDiscount(),
|
||||
insurance: Insurance[Persist.getInsurance()],
|
||||
tab: Persist.getCostTab(),
|
||||
buildOptions: Persist.getBuildsNamesFor(props.ship.id),
|
||||
ammoPredicate: 'cr',
|
||||
ammoDesc: true,
|
||||
costPredicate: 'cr',
|
||||
costDesc: true,
|
||||
retroPredicate: 'cr',
|
||||
retroDesc: true
|
||||
buildOptions: Persist.getBuildsNamesFor(ship.getShipType()),
|
||||
predicate: 'cr',
|
||||
desc: true,
|
||||
excluded: {},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a ship instance to base/reference retrofit changes from
|
||||
* @param {string} shipId Ship Id
|
||||
* @param {string} name Build name
|
||||
* @param {Ship} retrofitShip Existing retrofit ship
|
||||
* @return {Ship} Retrofit ship
|
||||
*/
|
||||
_buildRetrofitShip(shipId, name, retrofitShip) {
|
||||
let data = Ships[shipId]; // Retrieve the basic ship properties, slots and defaults
|
||||
|
||||
if (!retrofitShip) { // Don't create a new instance unless needed
|
||||
retrofitShip = new Ship(shipId, data.properties, data.slots); // Create a new Ship for retrofit comparison
|
||||
}
|
||||
|
||||
if (Persist.hasBuild(shipId, name)) {
|
||||
retrofitShip.buildFrom(Persist.getBuild(shipId, name)); // Populate modules from existing build
|
||||
_buildRetrofitShip() {
|
||||
const { retrofitName } = this.state;
|
||||
if (Persist.hasBuild(this.shipType, retrofitName)) {
|
||||
return new Ship(Persist.getBuild(this.shipType, retrofitName));
|
||||
} else {
|
||||
retrofitShip.buildWith(data.defaults); // Populate with default components
|
||||
return Factory.newShip(this.shipType);
|
||||
}
|
||||
return retrofitShip;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default retrofit build name if it exists
|
||||
* @param {string} shipId Ship Id
|
||||
* @param {string} name Build name
|
||||
* @return {string} Build name or null
|
||||
*/
|
||||
_defaultRetrofitName(shipId, name) {
|
||||
return Persist.hasBuild(shipId, name) ? name : null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -108,9 +71,6 @@ export default class CostSection extends TranslatedComponent {
|
||||
_onDiscountChanged() {
|
||||
let shipDiscount = Persist.getShipDiscount();
|
||||
let moduleDiscount = Persist.getModuleDiscount();
|
||||
this.props.ship.applyDiscounts(shipDiscount, moduleDiscount);
|
||||
this.state.retrofitShip.applyDiscounts(shipDiscount, moduleDiscount);
|
||||
this._updateRetrofit(this.props.ship, this.state.retrofitShip);
|
||||
this.setState({ shipDiscount, moduleDiscount });
|
||||
}
|
||||
|
||||
@@ -127,156 +87,33 @@ export default class CostSection extends TranslatedComponent {
|
||||
* @param {SyntheticEvent} event Build name to base the retrofit ship on
|
||||
*/
|
||||
_onBaseRetrofitChange(event) {
|
||||
let retrofitName = event.target.value;
|
||||
let ship = this.props.ship;
|
||||
|
||||
if (retrofitName) {
|
||||
this.state.retrofitShip.buildFrom(Persist.getBuild(ship.id, retrofitName));
|
||||
} else {
|
||||
this.state.retrofitShip.buildWith(Ships[ship.id].defaults); // Retrofit ship becomes stock build
|
||||
}
|
||||
this._updateRetrofit(ship, this.state.retrofitShip);
|
||||
this.setState({ retrofitName });
|
||||
this.setState({ retrofitName: event.target.value });
|
||||
}
|
||||
|
||||
/**
|
||||
* On builds changed check to see if the retrofit ship needs
|
||||
* to be updated
|
||||
* Toggle item cost inclusion
|
||||
* @param {String} key Key of the row to toggle
|
||||
*/
|
||||
_onBuildsChanged() {
|
||||
let update = false;
|
||||
let ship = this.props.ship;
|
||||
let { retrofitName, retrofitShip } = this.state;
|
||||
|
||||
if(!Persist.hasBuild(ship.id, retrofitName)) {
|
||||
retrofitShip.buildWith(Ships[ship.id].defaults); // Retrofit ship becomes stock build
|
||||
this.setState({ retrofitName: null });
|
||||
update = true;
|
||||
} else if (Persist.getBuild(ship.id, retrofitName) != retrofitShip.toString()) {
|
||||
retrofitShip.buildFrom(Persist.getBuild(ship.id, retrofitName)); // Repopulate modules from saved build
|
||||
update = true;
|
||||
}
|
||||
|
||||
if (update) { // Update retrofit comparison
|
||||
this._updateRetrofit(ship, retrofitShip);
|
||||
}
|
||||
// Update list of retrofit base build options
|
||||
this.setState({ buildOptions: Persist.getBuildsNamesFor(ship.id) });
|
||||
_toggleExcluded(key) {
|
||||
let { excluded } = this.state;
|
||||
excluded = assign({}, excluded);
|
||||
const slotExcluded = excluded[key];
|
||||
excluded[key] = (slotExcluded === undefined ? true : !slotExcluded);
|
||||
this.setState({ excluded });
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle item cost inclusion in overall total
|
||||
* @param {Object} item Cost item
|
||||
* Set list sort predicate
|
||||
* @param {string} newPredicate sort predicate
|
||||
*/
|
||||
_toggleCost(item) {
|
||||
this.props.ship.setCostIncluded(item, !item.incCost);
|
||||
this.forceUpdate();
|
||||
}
|
||||
_sortBy(newPredicate) {
|
||||
let { predicate, desc } = this.state;
|
||||
|
||||
/**
|
||||
* Toggle item cost inclusion in retrofit total
|
||||
* @param {Object} item Cost item
|
||||
*/
|
||||
_toggleRetrofitCost(item) {
|
||||
let retrofitTotal = this.state.retrofitTotal;
|
||||
item.retroItem.incCost = !item.retroItem.incCost;
|
||||
retrofitTotal += item.netCost * (item.retroItem.incCost ? 1 : -1);
|
||||
this.setState({ retrofitTotal });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set cost list sort predicate
|
||||
* @param {string} predicate sort predicate
|
||||
*/
|
||||
_sortCostBy(predicate) {
|
||||
let { costPredicate, costDesc } = this.state;
|
||||
|
||||
if (costPredicate == predicate) {
|
||||
costDesc = !costDesc;
|
||||
if (newPredicate == predicate) {
|
||||
desc = !desc;
|
||||
}
|
||||
|
||||
this.setState({ costPredicate: predicate, costDesc });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort cost list
|
||||
* @param {Ship} ship Ship instance
|
||||
* @param {string} predicate Sort predicate
|
||||
* @param {Boolean} desc Sort descending
|
||||
*/
|
||||
_sortCost(ship, predicate, desc) {
|
||||
let costList = ship.costList;
|
||||
let translate = this.context.language.translate;
|
||||
|
||||
if (predicate == 'm') {
|
||||
costList.sort(slotComparator(translate, null, desc));
|
||||
} else {
|
||||
costList.sort(slotComparator(translate, (a, b) => (a.m.cost || 0) - (b.m.cost || 0), desc));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set ammo list sort predicate
|
||||
* @param {string} predicate sort predicate
|
||||
*/
|
||||
_sortAmmoBy(predicate) {
|
||||
let { ammoPredicate, ammoDesc } = this.state;
|
||||
|
||||
if (ammoPredicate == predicate) {
|
||||
ammoDesc = !ammoDesc;
|
||||
}
|
||||
|
||||
this.setState({ ammoPredicate: predicate, ammoDesc });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort ammo cost list
|
||||
* @param {Array} ammoCosts Ammo cost list
|
||||
* @param {string} predicate Sort predicate
|
||||
* @param {Boolean} desc Sort descending
|
||||
*/
|
||||
_sortAmmo(ammoCosts, predicate, desc) {
|
||||
let translate = this.context.language.translate;
|
||||
|
||||
if (predicate == 'm') {
|
||||
ammoCosts.sort(slotComparator(translate, null, desc));
|
||||
} else {
|
||||
ammoCosts.sort(slotComparator(translate, (a, b) => a[predicate] - b[predicate], desc));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set retrofit list sort predicate
|
||||
* @param {string} predicate sort predicate
|
||||
*/
|
||||
_sortRetrofitBy(predicate) {
|
||||
let { retroPredicate, retroDesc } = this.state;
|
||||
|
||||
if (retroPredicate == predicate) {
|
||||
retroDesc = !retroDesc;
|
||||
}
|
||||
|
||||
this.setState({ retroPredicate: predicate, retroDesc });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort retrofit cost list
|
||||
* @param {Array} retrofitCosts Retrofit cost list
|
||||
* @param {string} predicate Sort predicate
|
||||
* @param {Boolean} desc Sort descending
|
||||
*/
|
||||
_sortRetrofit(retrofitCosts, predicate, desc) {
|
||||
let translate = this.context.language.translate;
|
||||
|
||||
if (predicate == 'cr') {
|
||||
retrofitCosts.sort((a, b) => a.netCost - b.netCost);
|
||||
} else {
|
||||
retrofitCosts.sort((a , b) => (a[predicate] ? translate(a[predicate]).toLowerCase() : '').localeCompare(b[predicate] ? translate(b[predicate]).toLowerCase() : ''));
|
||||
}
|
||||
|
||||
if (!desc) {
|
||||
retrofitCosts.reverse();
|
||||
}
|
||||
this.setState({ predicate: newPredicate, desc });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -285,18 +122,34 @@ export default class CostSection extends TranslatedComponent {
|
||||
*/
|
||||
_costsTab() {
|
||||
let { ship } = this.props;
|
||||
let { shipDiscount, moduleDiscount, insurance } = this.state;
|
||||
let {
|
||||
excluded, shipDiscount, moduleDiscount, insurance, desc, predicate
|
||||
} = this.state;
|
||||
let { translate, formats, units } = this.context.language;
|
||||
let rows = [];
|
||||
|
||||
for (let i = 0, l = ship.costList.length; i < l; i++) {
|
||||
let item = ship.costList[i];
|
||||
if (item.m && item.m.cost) {
|
||||
let toggle = this._toggleCost.bind(this, item);
|
||||
rows.push(<tr key={i} className={cn('highlight', { disabled: !item.incCost })}>
|
||||
<td className='ptr' style={{ width: '1em' }} onClick={toggle}>{item.m.class + item.m.rating}</td>
|
||||
<td className='le ptr shorten cap' onClick={toggle}>{slotName(translate, item)}</td>
|
||||
<td className='ri ptr' onClick={toggle}>{formats.int(item.discountedCost)}{units.CR}</td>
|
||||
let modules = sortBy(
|
||||
ship.getModules(),
|
||||
(predicate === 'm' ? (m) => m.getItem() : (m) => m.readMeta('cost'))
|
||||
);
|
||||
if (desc) {
|
||||
reverse(modules);
|
||||
}
|
||||
|
||||
let totalCost = 0;
|
||||
for (let module of modules) {
|
||||
const cost = module.readMeta('cost');
|
||||
const slot = module.getSlot();
|
||||
if (cost) {
|
||||
let toggle = this._toggleExcluded.bind(this, slot);
|
||||
const disabled = excluded[slot];
|
||||
if (!disabled) {
|
||||
totalCost += cost;
|
||||
}
|
||||
rows.push(<tr key={slot} className={cn('highlight', { disabled })}>
|
||||
<td className='ptr' style={{ width: '1em' }} onClick={toggle}>{module.getClassRating()}</td>
|
||||
<td className='le ptr shorten cap' onClick={toggle}>{translate(module.readMeta('type'))}</td>
|
||||
<td className='ri ptr' onClick={toggle}>{formats.int(cost * (1 - moduleDiscount))}{units.CR}</td>
|
||||
</tr>);
|
||||
}
|
||||
}
|
||||
@@ -305,23 +158,23 @@ export default class CostSection extends TranslatedComponent {
|
||||
<table style={{ width: '100%', borderCollapse: 'collapse' }}>
|
||||
<thead>
|
||||
<tr className='main'>
|
||||
<th colSpan='2' className='sortable le' onClick={this._sortCostBy.bind(this,'m')}>
|
||||
<th colSpan='2' className='sortable le' onClick={() => this._sortBy('m')}>
|
||||
{translate('module')}
|
||||
{shipDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} -${formats.pct(shipDiscount)}]`}</u> : null}
|
||||
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
|
||||
{shipDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('ship')} ${formats.pct(-1 * shipDiscount)}]`}</u> : null}
|
||||
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} ${formats.pct(-1 * moduleDiscount)}]`}</u> : null}
|
||||
</th>
|
||||
<th className='sortable le' onClick={this._sortCostBy.bind(this, 'cr')} >{translate('credits')}</th>
|
||||
<th className='sortable le' onClick={() => this._sortBy('cr')} >{translate('credits')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows}
|
||||
<tr className='ri'>
|
||||
<td colSpan='2' className='lbl' >{translate('total')}</td>
|
||||
<td className='val'>{formats.int(ship.totalCost)}{units.CR}</td>
|
||||
<td className='val'>{formats.int(totalCost)}{units.CR}</td>
|
||||
</tr>
|
||||
<tr className='ri'>
|
||||
<td colSpan='2' className='lbl'>{translate('insurance')}</td>
|
||||
<td className='val'>{formats.int(ship.totalCost * insurance)}{units.CR}</td>
|
||||
<td className='val'>{formats.int(totalCost * insurance)}{units.CR}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -332,14 +185,63 @@ export default class CostSection extends TranslatedComponent {
|
||||
* Open up a window for EDDB with a shopping list of our retrofit components
|
||||
*/
|
||||
_eddbShoppingList() {
|
||||
const { retrofitCosts } = this.state;
|
||||
const {} = this.state;
|
||||
const { ship } = this.props;
|
||||
|
||||
// Provide unique list of non-PP module EDDB IDs to buy
|
||||
const modIds = retrofitCosts.filter(item => item.retroItem.incCost && item.buyId && !item.buyPp).map(item => item.buyId).filter((v, i, a) => a.indexOf(v) === i);
|
||||
// const modIds = retrofitCosts.filter(item => item.retroItem.incCost && item.buyId && !item.buyPp).map(item => item.buyId).filter((v, i, a) => a.indexOf(v) === i);
|
||||
|
||||
// Open up the relevant URL
|
||||
window.open('https://eddb.io/station?m=' + modIds.join(','));
|
||||
// TODO:
|
||||
// window.open('https://eddb.io/station?m=' + modIds.join(','));
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
_retrofitInfo() {
|
||||
const { ship } = this.props;
|
||||
const { desc, moduleDiscount, predicate, retrofitName, excluded } = this.state;
|
||||
const retrofitShip = this._buildRetrofitShip();
|
||||
|
||||
const currentModules = ship.getModules();
|
||||
const oldModules = retrofitShip.getModules();
|
||||
const buyModules = differenceBy(currentModules, oldModules, (m) => m.getItem());
|
||||
const sellModules = differenceBy(oldModules, currentModules, (m) => m.getItem());
|
||||
|
||||
let modules = [];
|
||||
let totalCost = 0;
|
||||
const addModule = (m, costFactor) => {
|
||||
const key = `${m.getItem()}@${m.getSlot()}`;
|
||||
const cost = costFactor * m.readMeta('cost') * (1 - moduleDiscount);
|
||||
modules.push({
|
||||
key, cost,
|
||||
rating: m.getClassRating(),
|
||||
item: m.readMeta('type'),
|
||||
});
|
||||
if (!excluded[key]) {
|
||||
totalCost += cost;
|
||||
}
|
||||
};
|
||||
for (let m of buyModules) {
|
||||
addModule(m, 1);
|
||||
}
|
||||
for (let m of sellModules) {
|
||||
addModule(m, -1);
|
||||
}
|
||||
|
||||
let _sortF = undefined;
|
||||
switch (predicate) {
|
||||
case 'cr': _sortF = (o) => o.cost; break;
|
||||
case 'm':
|
||||
default: _sortF = (o) => o.item; break;
|
||||
};
|
||||
|
||||
modules = sortBy(modules, _sortF);
|
||||
if (desc) {
|
||||
reverse(modules);
|
||||
}
|
||||
return [totalCost, modules];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -347,59 +249,52 @@ export default class CostSection extends TranslatedComponent {
|
||||
* @return {React.Component} Tab contents
|
||||
*/
|
||||
_retrofitTab() {
|
||||
let { retrofitTotal, retrofitCosts, moduleDiscount, retrofitName } = this.state;
|
||||
let { buildOptions, excluded, moduleDiscount, retrofitName } = this.state;
|
||||
const { termtip, tooltip } = this.context;
|
||||
let { translate, formats, units } = this.context.language;
|
||||
let int = formats.int;
|
||||
let rows = [], options = [<option key='stock' value=''>{translate('Stock')}</option>];
|
||||
|
||||
for (let opt of this.state.buildOptions) {
|
||||
options.push(<option key={opt} value={opt}>{opt}</option>);
|
||||
}
|
||||
|
||||
if (retrofitCosts.length) {
|
||||
for (let i = 0, l = retrofitCosts.length; i < l; i++) {
|
||||
let item = retrofitCosts[i];
|
||||
rows.push(<tr key={i} className={cn('highlight', { disabled: !item.retroItem.incCost })} onClick={this._toggleRetrofitCost.bind(this, item)}>
|
||||
<td className='ptr' style={{ width: '1em' }}>{item.sellClassRating}</td>
|
||||
<td className='le ptr shorten cap'>{translate(item.sellName)}</td>
|
||||
<td className='ptr' style={{ width: '1em' }}>{item.buyClassRating}</td>
|
||||
<td className='le ptr shorten cap'>{translate(item.buyName)}</td>
|
||||
<td colSpan='2' className={cn('ri ptr', item.retroItem.incCost ? item.netCost > 0 ? 'warning' : 'secondary-disabled' : 'disabled')}>{int(item.netCost)}{units.CR}</td>
|
||||
</tr>);
|
||||
}
|
||||
} else {
|
||||
rows = <tr><td colSpan='7' style={{ padding: '3em 0' }}>{translate('PHRASE_NO_RETROCH')}</td></tr>;
|
||||
}
|
||||
|
||||
const [retrofitTotal, retrofitInfo] = this._retrofitInfo();
|
||||
return <div>
|
||||
<div className='scroll-x'>
|
||||
<table style={{ width: '100%' }}>
|
||||
<thead>
|
||||
<tr className='main'>
|
||||
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'sellName')}>{translate('sell')}</th>
|
||||
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'buyName')}>{translate('buy')}</th>
|
||||
<th colSpan='2' className='sortable le' onClick={this._sortRetrofitBy.bind(this, 'cr')}>
|
||||
<th colSpan='2' className='sortable le' onClick={() => this._sortBy('m')}>{translate('module')}</th>
|
||||
<th colSpan='2' className='sortable le' onClick={() => this._sortBy('cr')}>
|
||||
{translate('net cost')}
|
||||
{moduleDiscount ? <u className='cap optional-hide' style={{ marginLeft: '0.5em' }}>{`[${translate('modules')} -${formats.pct(moduleDiscount)}]`}</u> : null}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows}
|
||||
{retrofitInfo.length ?
|
||||
retrofitInfo.map((info) => (
|
||||
<tr key={info.key} className={cn('highlight', { disabled: excluded[info.key] })}
|
||||
onClick={() => this._toggleExcluded(info.key)}>
|
||||
<td className='ptr' style={{ width: '1em' }}>{info.rating}</td>
|
||||
<td className='le ptr shorten cap'>{translate(info.item)}</td>
|
||||
<td colSpan="2" className={cn('ri ptr', excluded[info.key] ? 'disabled' : (info.cost < 0 ? 'secondary-disabled' : 'warning'))}>
|
||||
{int(info.cost)}{units.CR}
|
||||
</td>
|
||||
</tr>
|
||||
)) : (
|
||||
<tr><td colSpan='7' style={{ padding: '3em 0' }}>{translate('PHRASE_NO_RETROCH')}</td></tr>
|
||||
)}
|
||||
<tr className='ri'>
|
||||
<td className='lbl' ><button onClick={this._eddbShoppingList} onMouseOver={termtip.bind(null, 'PHRASE_REFIT_SHOPPING_LIST')} onMouseOut={tooltip.bind(null, null)}><ShoppingIcon className='lg' style={{ fill: 'black' }}/></button></td>
|
||||
<td colSpan='3' className='lbl' >{translate('cost')}</td>
|
||||
<td className='lbl' >{translate('cost')}</td>
|
||||
<td colSpan='2' className={cn('val', retrofitTotal > 0 ? 'warning' : 'secondary-disabled')} style={{ borderBottom:'none' }}>
|
||||
{int(retrofitTotal)}{units.CR}
|
||||
</td>
|
||||
</tr>
|
||||
<tr className='ri'>
|
||||
<td colSpan='4' className='lbl cap' >{translate('retrofit from')}</td>
|
||||
<td colSpan='2' className='lbl cap' >{translate('retrofit from')}</td>
|
||||
<td className='val cen' style={{ borderRight: 'none', width: '1em' }}><u className='primary-disabled'>▾</u></td>
|
||||
<td className='val' style={{ borderLeft:'none', padding: 0 }}>
|
||||
<select style={{ width: '100%', padding: 0 }} value={retrofitName || translate('Stock')} onChange={this._onBaseRetrofitChange}>
|
||||
{options}
|
||||
<option key='stock' value=''>{translate('Stock')}</option>
|
||||
{buildOptions.map((opt) => <option key={opt} value={opt}>{opt}</option>)}
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -409,63 +304,50 @@ export default class CostSection extends TranslatedComponent {
|
||||
</div>;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Update retrofit costs
|
||||
* @param {Ship} ship Ship instance
|
||||
* @param {Ship} retrofitShip Retrofit Ship instance
|
||||
*
|
||||
* @param {*} modules
|
||||
*/
|
||||
_updateRetrofit(ship, retrofitShip) {
|
||||
let retrofitCosts = [];
|
||||
let retrofitTotal = 0, i, l, item;
|
||||
_ammoInfo() {
|
||||
const { ship } = this.props;
|
||||
const { desc, predicate } = this.state;
|
||||
|
||||
if (ship.bulkheads.m.index != retrofitShip.bulkheads.m.index) {
|
||||
item = {
|
||||
buyClassRating: ship.bulkheads.m.class + ship.bulkheads.m.rating,
|
||||
buyId: ship.bulkheads.m.eddbID,
|
||||
buyPp: ship.bulkheads.m.pp,
|
||||
buyName: ship.bulkheads.m.name,
|
||||
sellClassRating: retrofitShip.bulkheads.m.class + retrofitShip.bulkheads.m.rating,
|
||||
sellName: retrofitShip.bulkheads.m.name,
|
||||
netCost: ship.bulkheads.discountedCost - retrofitShip.bulkheads.discountedCost,
|
||||
retroItem: retrofitShip.bulkheads
|
||||
};
|
||||
retrofitCosts.push(item);
|
||||
if (retrofitShip.bulkheads.incCost) {
|
||||
retrofitTotal += item.netCost;
|
||||
let info = [{
|
||||
key: 'fuel',
|
||||
item: 'Fuel',
|
||||
qty: ship.get(FUEL_CAPACITY),
|
||||
unitCost: 50,
|
||||
cost: 50 * ship.get(FUEL_CAPACITY),
|
||||
}];
|
||||
for (let m of ship.getModules()) {
|
||||
const rebuilds = m.get('bays') * m.get('rebuildsperbay');
|
||||
const ammo = (m.get('ammomaximum') + m.get('ammoclipsize')) || rebuilds;
|
||||
if (ammo) {
|
||||
const unitCost = m.readMeta('ammocost');
|
||||
info.push({
|
||||
key: `restock_${m.getSlot()}`,
|
||||
rating: m.getClassRating(),
|
||||
item: m.readMeta('type'),
|
||||
qty: ammo,
|
||||
unitCost, cost: unitCost * ammo,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (let g in { standard: 1, internal: 1, hardpoints: 1 }) {
|
||||
let retroSlotGroup = retrofitShip[g];
|
||||
let slotGroup = ship[g];
|
||||
for (i = 0, l = slotGroup.length; i < l; i++) {
|
||||
const modId = slotGroup[i].m ? slotGroup[i].m.eddbID : null;
|
||||
const retroModId = retroSlotGroup[i].m ? retroSlotGroup[i].m.eddbID : null;
|
||||
if (modId != retroModId) {
|
||||
item = { netCost: 0, retroItem: retroSlotGroup[i] };
|
||||
if (slotGroup[i].m) {
|
||||
item.buyId = slotGroup[i].m.eddbID,
|
||||
item.buyPp = slotGroup[i].m.pp,
|
||||
item.buyName = slotGroup[i].m.name || slotGroup[i].m.grp;
|
||||
item.buyClassRating = slotGroup[i].m.class + slotGroup[i].m.rating;
|
||||
item.netCost = slotGroup[i].discountedCost;
|
||||
}
|
||||
if (retroSlotGroup[i].m) {
|
||||
item.sellName = retroSlotGroup[i].m.name || retroSlotGroup[i].m.grp;
|
||||
item.sellClassRating = retroSlotGroup[i].m.class + retroSlotGroup[i].m.rating;
|
||||
item.netCost -= retroSlotGroup[i].discountedCost;
|
||||
}
|
||||
retrofitCosts.push(item);
|
||||
if (retroSlotGroup[i].incCost) {
|
||||
retrofitTotal += item.netCost;
|
||||
}
|
||||
}
|
||||
}
|
||||
let _sortF = undefined;
|
||||
switch (predicate) {
|
||||
case 'cr': _sortF = (o) => o.cost; break;
|
||||
case 'qty': _sortF = (o) => o.qty; break;
|
||||
case 'cost': _sortF = (o) => o.unitCost; break;
|
||||
case 'm':
|
||||
default: _sortF = (o) => o.item;
|
||||
}
|
||||
info = sortBy(info, _sortF);
|
||||
if (desc) {
|
||||
reverse(info);
|
||||
}
|
||||
|
||||
this.setState({ retrofitCosts, retrofitTotal });
|
||||
this._sortRetrofit(retrofitCosts, this.state.retroPredicate, this.state.retroDesc);
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -473,20 +355,24 @@ export default class CostSection extends TranslatedComponent {
|
||||
* @return {React.Component} Tab contents
|
||||
*/
|
||||
_ammoTab() {
|
||||
let { ammoTotal, ammoCosts } = this.state;
|
||||
let { translate, formats, units } = this.context.language;
|
||||
let int = formats.int;
|
||||
let rows = [];
|
||||
const { excluded } = this.state;
|
||||
const { translate, formats, units } = this.context.language;
|
||||
const int = formats.int;
|
||||
const rows = [];
|
||||
|
||||
for (let i = 0, l = ammoCosts.length; i < l; i++) {
|
||||
let item = ammoCosts[i];
|
||||
rows.push(<tr key={i} className='highlight'>
|
||||
<td style={{ width: '1em' }}>{item.m.class + item.m.rating}</td>
|
||||
<td className='le shorten cap'>{slotName(translate, item)}</td>
|
||||
<td className='ri'>{int(item.max)}</td>
|
||||
<td className='ri'>{int(item.cost)}{units.CR}</td>
|
||||
<td className='ri'>{int(item.total)}{units.CR}</td>
|
||||
const ammoInfo = this._ammoInfo();
|
||||
let total = 0;
|
||||
for (let i of ammoInfo) {
|
||||
const disabled = excluded[i.key];
|
||||
rows.push(<tr key={i.key} onClick={() => this._toggleExcluded(i.key)}
|
||||
className={cn('highlight', { disabled })}>
|
||||
<td style={{ width: '1em' }}>{i.rating}</td>
|
||||
<td className='le shorten cap'>{translate(i.item)}</td>
|
||||
<td className='ri'>{int(i.qty)}</td>
|
||||
<td className='ri'>{int(i.unitCost)}{units.CR}</td>
|
||||
<td className='ri'>{int(i.cost)}{units.CR}</td>
|
||||
</tr>);
|
||||
total += disabled ? 0 : i.cost;
|
||||
}
|
||||
|
||||
return <div>
|
||||
@@ -494,17 +380,17 @@ export default class CostSection extends TranslatedComponent {
|
||||
<table style={{ width: '100%' }}>
|
||||
<thead>
|
||||
<tr className='main'>
|
||||
<th colSpan='2' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'm')} >{translate('module')}</th>
|
||||
<th colSpan='1' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'max')} >{translate('qty')}</th>
|
||||
<th colSpan='1' className='sortable le' onClick={this._sortAmmoBy.bind(this, 'cost')} >{translate('unit cost')}</th>
|
||||
<th className='sortable le' onClick={this._sortAmmoBy.bind(this, 'total')}>{translate('subtotal')}</th>
|
||||
<th colSpan='2' className='sortable le' onClick={() => this._sortBy('m')}>{translate('module')}</th>
|
||||
<th colSpan='1' className='sortable le' onClick={() => this._sortBy('qty')}>{translate('qty')}</th>
|
||||
<th colSpan='1' className='sortable le' onClick={() => this._sortBy('cost')}>{translate('unit cost')}</th>
|
||||
<th className='sortable le' onClick={() => this._sortBy('cr')}>{translate('subtotal')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows}
|
||||
<tr className='ri'>
|
||||
<td colSpan='4' className='lbl' >{translate('total')}</td>
|
||||
<td className='val'>{int(ammoTotal)}{units.CR}</td>
|
||||
<td className='val'>{int(total)}{units.CR}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -512,103 +398,6 @@ export default class CostSection extends TranslatedComponent {
|
||||
</div>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recalculate all ammo costs
|
||||
* @param {Ship} ship Ship instance
|
||||
*/
|
||||
_updateAmmoCosts(ship) {
|
||||
let ammoCosts = [], ammoTotal = 0, item, q, limpets = 0, srvs = 0, scoop = false;
|
||||
|
||||
for (let g in { standard: 1, internal: 1, hardpoints: 1 }) {
|
||||
let slotGroup = ship[g];
|
||||
for (let i = 0, l = slotGroup.length; i < l; i++) {
|
||||
if (slotGroup[i].m) {
|
||||
// Special cases needed for SCB, AFMU, and limpet controllers since they don't use standard ammo/clip
|
||||
q = 0;
|
||||
switch (slotGroup[i].m.grp) {
|
||||
case 'fs': // Skip fuel calculation if scoop present
|
||||
scoop = true;
|
||||
break;
|
||||
case 'scb':
|
||||
q = slotGroup[i].m.getAmmo() + 1;
|
||||
break;
|
||||
case 'am':
|
||||
q = slotGroup[i].m.getAmmo();
|
||||
break;
|
||||
case 'pv':
|
||||
srvs += slotGroup[i].m.getBays();
|
||||
break;
|
||||
case 'fx': case 'hb': case 'cc': case 'pc':
|
||||
limpets = ship.cargoCapacity;
|
||||
break;
|
||||
default:
|
||||
q = slotGroup[i].m.getClip() + slotGroup[i].m.getAmmo();
|
||||
}
|
||||
// Calculate ammo costs only if a cost is specified
|
||||
if (slotGroup[i].m.ammocost > 0) {
|
||||
item = {
|
||||
m: slotGroup[i].m,
|
||||
max: q,
|
||||
cost: slotGroup[i].m.ammocost,
|
||||
total: q * slotGroup[i].m.ammocost
|
||||
};
|
||||
ammoCosts.push(item);
|
||||
ammoTotal += item.total;
|
||||
}
|
||||
// Add fighters
|
||||
if (slotGroup[i].m.grp === 'fh') {
|
||||
item = {
|
||||
m: slotGroup[i].m,
|
||||
max: slotGroup[i].m.getRebuildsPerBay() * slotGroup[i].m.getBays(),
|
||||
cost: slotGroup[i].m.fightercost,
|
||||
total: slotGroup[i].m.getRebuildsPerBay() * slotGroup[i].m.getBays() * slotGroup[i].m.fightercost
|
||||
};
|
||||
ammoCosts.push(item);
|
||||
ammoTotal += item.total;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Limpets if controllers exist and cargo space available
|
||||
if (limpets > 0) {
|
||||
item = {
|
||||
m: { name: 'limpets', class: '', rating: '' },
|
||||
max: ship.cargoCapacity,
|
||||
cost: 101,
|
||||
total: ship.cargoCapacity * 101
|
||||
};
|
||||
ammoCosts.push(item);
|
||||
ammoTotal += item.total;
|
||||
}
|
||||
|
||||
if (srvs > 0) {
|
||||
item = {
|
||||
m: { name: 'SRVs', class: '', rating: '' },
|
||||
max: srvs,
|
||||
cost: 1030,
|
||||
total: srvs * 1030
|
||||
};
|
||||
ammoCosts.push(item);
|
||||
ammoTotal += item.total;
|
||||
}
|
||||
|
||||
// Calculate refuel costs if no scoop present
|
||||
if (!scoop) {
|
||||
item = {
|
||||
m: { name: 'fuel', class: '', rating: '' },
|
||||
max: ship.fuelCapacity,
|
||||
cost: 50,
|
||||
total: ship.fuelCapacity * 50
|
||||
};
|
||||
ammoCosts.push(item);
|
||||
ammoTotal += item.total;
|
||||
}
|
||||
|
||||
this.setState({ ammoTotal, ammoCosts });
|
||||
this._sortAmmo(ammoCosts, this.state.ammoPredicate, this.state.ammoDesc);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add listeners on mount and update costs
|
||||
*/
|
||||
@@ -616,64 +405,7 @@ export default class CostSection extends TranslatedComponent {
|
||||
this.listeners = [
|
||||
Persist.addListener('discounts', this._onDiscountChanged.bind(this)),
|
||||
Persist.addListener('insurance', this._onInsuranceChanged.bind(this)),
|
||||
Persist.addListener('builds', this._onBuildsChanged.bind(this)),
|
||||
];
|
||||
this._updateAmmoCosts(this.props.ship);
|
||||
this._updateRetrofit(this.props.ship, this.state.retrofitShip);
|
||||
this._sortCost(this.props.ship);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update state based on property and context changes
|
||||
* @param {Object} nextProps Incoming/Next properties
|
||||
* @param {Object} nextContext Incoming/Next context
|
||||
*/
|
||||
componentWillReceiveProps(nextProps, nextContext) {
|
||||
let retrofitShip = this.state.retrofitShip;
|
||||
|
||||
if (nextProps.ship != this.props.ship) { // Ship has changed
|
||||
let nextId = nextProps.ship.id;
|
||||
let retrofitName = this._defaultRetrofitName(nextId, nextProps.buildName);
|
||||
retrofitShip = this._buildRetrofitShip(nextId, retrofitName, nextId == this.props.ship.id ? retrofitShip : null);
|
||||
this.setState({
|
||||
retrofitShip,
|
||||
retrofitName,
|
||||
buildOptions: Persist.getBuildsNamesFor(nextId)
|
||||
});
|
||||
}
|
||||
|
||||
if (nextProps.ship != this.props.ship || nextProps.code != this.props.code) {
|
||||
nextProps.ship.applyDiscounts(Persist.getShipDiscount(), Persist.getModuleDiscount());
|
||||
this._updateAmmoCosts(nextProps.ship);
|
||||
this._updateRetrofit(nextProps.ship, retrofitShip);
|
||||
this._sortCost(nextProps.ship);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort lists before render
|
||||
* @param {Object} nextProps Incoming/Next properties
|
||||
* @param {Object} nextState Incoming/Next state
|
||||
*/
|
||||
componentWillUpdate(nextProps, nextState) {
|
||||
let state = this.state;
|
||||
|
||||
switch (nextState.tab) {
|
||||
case 'ammo':
|
||||
if (state.ammoPredicate != nextState.ammoPredicate || state.ammoDesc != nextState.ammoDesc) {
|
||||
this._sortAmmo(nextState.ammoCosts, nextState.ammoPredicate, nextState.ammoDesc);
|
||||
}
|
||||
break;
|
||||
case 'retrofit':
|
||||
if (state.retroPredicate != nextState.retroPredicate || state.retroDesc != nextState.retroDesc) {
|
||||
this._sortRetrofit(nextState.retrofitCosts, nextState.retroPredicate, nextState.retroDesc);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (state.costPredicate != nextState.costPredicate || state.costDesc != nextState.costDesc) {
|
||||
this._sortCost(nextProps.ship, nextState.costPredicate, nextState.costDesc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import * as Calc from '../shipyard/Calculations';
|
||||
import PieChart from './PieChart';
|
||||
import VerticalBarChart from './VerticalBarChart';
|
||||
import autoBind from 'auto-bind';
|
||||
import { ARMOUR_METRICS, MODULE_PROTECTION_METRICS, SHIELD_METRICS } from 'ed-forge/lib/src/ship-stats';
|
||||
|
||||
/**
|
||||
* Defence information
|
||||
@@ -15,12 +16,10 @@ import VerticalBarChart from './VerticalBarChart';
|
||||
*/
|
||||
export default class Defence extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
marker: PropTypes.string.isRequired,
|
||||
code: PropTypes.string.isRequired,
|
||||
ship: PropTypes.object.isRequired,
|
||||
opponent: PropTypes.object.isRequired,
|
||||
engagementrange: PropTypes.number.isRequired,
|
||||
sys: PropTypes.number.isRequired,
|
||||
opponentWep: PropTypes.number.isRequired
|
||||
engagementRange: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -29,22 +28,7 @@ export default class Defence extends TranslatedComponent {
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const { shield, armour, shielddamage, armourdamage } = Calc.defenceMetrics(props.ship, props.opponent, props.sys, props.opponentWep, props.engagementrange);
|
||||
this.state = { shield, armour, shielddamage, armourdamage };
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state if our properties change
|
||||
* @param {Object} nextProps Incoming/Next properties
|
||||
* @return {boolean} Returns true if the component should be rerendered
|
||||
*/
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.marker != nextProps.marker || this.props.sys != nextProps.sys) {
|
||||
const { shield, armour, shielddamage, armourdamage } = Calc.defenceMetrics(nextProps.ship, nextProps.opponent, nextProps.sys, nextProps.opponentWep, nextProps.engagementrange);
|
||||
this.setState({ shield, armour, shielddamage, armourdamage });
|
||||
}
|
||||
return true;
|
||||
autoBind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,197 +36,126 @@ export default class Defence extends TranslatedComponent {
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { ship, sys, opponentWep } = this.props;
|
||||
const { ship } = this.props;
|
||||
const { language, tooltip, termtip } = this.context;
|
||||
const { formats, translate, units } = language;
|
||||
const { shield, armour, shielddamage, armourdamage } = this.state;
|
||||
|
||||
const pd = ship.standard[4].m;
|
||||
const shields = ship.get(SHIELD_METRICS);
|
||||
|
||||
const shieldSourcesData = [];
|
||||
const effectiveShieldData = [];
|
||||
const shieldDamageTakenData = [];
|
||||
const shieldSourcesTt = [];
|
||||
const shieldDamageTakenAbsoluteTt = [];
|
||||
const shieldDamageTakenExplosiveTt = [];
|
||||
const shieldDamageTakenKineticTt = [];
|
||||
const shieldDamageTakenThermalTt = [];
|
||||
const effectiveShieldAbsoluteTt = [];
|
||||
const effectiveShieldExplosiveTt = [];
|
||||
const effectiveShieldKineticTt = [];
|
||||
const effectiveShieldThermalTt = [];
|
||||
let maxEffectiveShield = 0;
|
||||
if (shield.total) {
|
||||
shieldSourcesData.push({ value: Math.round(shield.generator), label: translate('generator') });
|
||||
shieldSourcesData.push({ value: Math.round(shield.boosters), label: translate('boosters') });
|
||||
shieldSourcesData.push({ value: Math.round(shield.cells), label: translate('cells') });
|
||||
// Data for pie chart (absolute MJ)
|
||||
const shieldSourcesData = [
|
||||
'byBoosters', 'byGenerator', 'byReinforcements', 'bySCBs',
|
||||
].map((key) => { return { label: key, value: Math.round(shields[key]) }; });
|
||||
|
||||
if (shield.generator > 0) {
|
||||
shieldSourcesTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
||||
effectiveShieldAbsoluteTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
||||
effectiveShieldExplosiveTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
||||
effectiveShieldKineticTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
||||
effectiveShieldThermalTt.push(<div key='generator'>{translate('generator') + ' ' + formats.int(shield.generator)}{units.MJ}</div>);
|
||||
if (shield.boosters > 0) {
|
||||
shieldSourcesTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
||||
effectiveShieldAbsoluteTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
||||
effectiveShieldExplosiveTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
||||
effectiveShieldKineticTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
||||
effectiveShieldThermalTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.int(shield.boosters)}{units.MJ}</div>);
|
||||
}
|
||||
// Data for tooltip
|
||||
const shieldSourcesTt = shieldSourcesData.map((o) => {
|
||||
let { label, value } = o;
|
||||
return <div key={label}>
|
||||
{translate(label)} {formats.int(value)}{units.MJ}
|
||||
</div>;
|
||||
});
|
||||
|
||||
if (shield.cells > 0) {
|
||||
shieldSourcesTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
||||
effectiveShieldAbsoluteTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
||||
effectiveShieldExplosiveTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
||||
effectiveShieldKineticTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
||||
effectiveShieldThermalTt.push(<div key='cells'>{translate('cells') + ' ' + formats.int(shield.cells)}{units.MJ}</div>);
|
||||
}
|
||||
// Shield resistances
|
||||
const shieldDamageTakenData = [
|
||||
'absolute', 'explosive', 'kinetic', 'thermal',
|
||||
].map((label) => {
|
||||
const dmgMult = shields[label];
|
||||
const tooltip = ['byBoosters', 'byGenerator', 'bySys'].map(
|
||||
(label) => <div key={label}>
|
||||
{translate(label)} {formats.pct1(dmgMult[label])}
|
||||
</div>
|
||||
);
|
||||
return { label, value: Math.round(100 * dmgMult.withSys), tooltip };
|
||||
});
|
||||
|
||||
// Add effective shield from resistances
|
||||
const rawMj = shield.generator + shield.boosters + shield.cells;
|
||||
const explosiveMj = rawMj / (shield.explosive.generator * shield.explosive.boosters) - rawMj;
|
||||
if (explosiveMj != 0) effectiveShieldExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(explosiveMj)}{units.MJ}</div>);
|
||||
const kineticMj = rawMj / (shield.kinetic.generator * shield.kinetic.boosters) - rawMj;
|
||||
if (kineticMj != 0) effectiveShieldKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(kineticMj)}{units.MJ}</div>);
|
||||
const thermalMj = rawMj / (shield.thermal.generator * shield.thermal.boosters) - rawMj;
|
||||
if (thermalMj != 0) effectiveShieldThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(thermalMj)}{units.MJ}</div>);
|
||||
// Effective MJ
|
||||
const effectiveShieldData = [
|
||||
'absolute', 'explosive', 'kinetic', 'thermal'
|
||||
].map((label) => {
|
||||
const dmgMult = shields[label];
|
||||
const raw = shields.withSCBs;
|
||||
const tooltip = ['byBoosters', 'byGenerator', 'bySys'].map(
|
||||
(label) => <div key={label}>
|
||||
{translate(label)} {formats.int(raw * dmgMult[label])}{units.MJ}
|
||||
</div>
|
||||
);
|
||||
return { label, value: Math.round(dmgMult.withSys * raw), tooltip };
|
||||
});
|
||||
const maxEffectiveShield = Math.max(...effectiveShieldData.map((o) => o.value));
|
||||
|
||||
// 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>);
|
||||
}
|
||||
}
|
||||
const armour = ship.get(ARMOUR_METRICS);
|
||||
const moduleProtection = ship.get(MODULE_PROTECTION_METRICS);
|
||||
|
||||
shieldDamageTakenAbsoluteTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.absolute.generator)}</div>);
|
||||
shieldDamageTakenAbsoluteTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.absolute.boosters)}</div>);
|
||||
shieldDamageTakenAbsoluteTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.absolute.sys)}</div>);
|
||||
// Data for pie chart (absolute HP)
|
||||
const armourSourcesData = ['base', 'byAlloys', 'byHRPs',].map(
|
||||
(key) => { return { label: key, value: Math.round(armour[key]) }; }
|
||||
);
|
||||
|
||||
shieldDamageTakenExplosiveTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.explosive.generator)}</div>);
|
||||
shieldDamageTakenExplosiveTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.explosive.boosters)}</div>);
|
||||
shieldDamageTakenExplosiveTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.explosive.sys)}</div>);
|
||||
// Data for tooltip
|
||||
const armourSourcesTt = armourSourcesData.map((o) => {
|
||||
let { label, value } = o;
|
||||
return <div key={label}>{translate(label)} {formats.int(value)}</div>;
|
||||
});
|
||||
|
||||
shieldDamageTakenKineticTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.kinetic.generator)}</div>);
|
||||
shieldDamageTakenKineticTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.kinetic.boosters)}</div>);
|
||||
shieldDamageTakenKineticTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.kinetic.sys)}</div>);
|
||||
// Armour resistances
|
||||
const armourDamageTakenData = [
|
||||
'absolute', 'explosive', 'kinetic', 'thermal', 'caustic',
|
||||
].map((label) => {
|
||||
const dmgMult = armour[label];
|
||||
const tooltip = ['byAlloys', 'byHRPs'].map(
|
||||
(label) => <div key={label}>
|
||||
{translate(label)} {formats.pct1(dmgMult[label])}
|
||||
</div>
|
||||
);
|
||||
return { label, value: Math.round(100 * dmgMult.damageMultiplier), tooltip };
|
||||
});
|
||||
|
||||
shieldDamageTakenThermalTt.push(<div key='generator'>{translate('generator') + ' ' + formats.pct1(shield.thermal.generator)}</div>);
|
||||
shieldDamageTakenThermalTt.push(<div key='boosters'>{translate('boosters') + ' ' + formats.pct1(shield.thermal.boosters)}</div>);
|
||||
shieldDamageTakenThermalTt.push(<div key='power distributor'>{translate('power distributor') + ' ' + formats.pct1(shield.thermal.sys)}</div>);
|
||||
|
||||
const effectiveAbsoluteShield = shield.total / shield.absolute.total;
|
||||
effectiveShieldData.push({ value: Math.round(effectiveAbsoluteShield), label: translate('absolute'), tooltip: effectiveShieldAbsoluteTt });
|
||||
const effectiveExplosiveShield = shield.total / shield.explosive.total;
|
||||
effectiveShieldData.push({ value: Math.round(effectiveExplosiveShield), label: translate('explosive'), tooltip: effectiveShieldExplosiveTt });
|
||||
const effectiveKineticShield = shield.total / shield.kinetic.total;
|
||||
effectiveShieldData.push({ value: Math.round(effectiveKineticShield), label: translate('kinetic'), tooltip: effectiveShieldKineticTt });
|
||||
const effectiveThermalShield = shield.total / shield.thermal.total;
|
||||
effectiveShieldData.push({ value: Math.round(effectiveThermalShield), label: translate('thermal'), tooltip: effectiveShieldThermalTt });
|
||||
|
||||
shieldDamageTakenData.push({ value: Math.round(shield.absolute.total * 100), label: translate('absolute'), tooltip: shieldDamageTakenAbsoluteTt });
|
||||
shieldDamageTakenData.push({ value: Math.round(shield.explosive.total * 100), label: translate('explosive'), tooltip: shieldDamageTakenExplosiveTt });
|
||||
shieldDamageTakenData.push({ value: Math.round(shield.kinetic.total * 100), label: translate('kinetic'), tooltip: shieldDamageTakenKineticTt });
|
||||
shieldDamageTakenData.push({ value: Math.round(shield.thermal.total * 100), label: translate('thermal'), tooltip: shieldDamageTakenThermalTt });
|
||||
|
||||
maxEffectiveShield = Math.max(shield.total / shield.absolute.max, shield.total / shield.explosive.max, shield.total / shield.kinetic.max, shield.total / shield.thermal.max);
|
||||
}
|
||||
|
||||
const armourSourcesData = [];
|
||||
armourSourcesData.push({ value: Math.round(armour.bulkheads), label: translate('bulkheads') });
|
||||
armourSourcesData.push({ value: Math.round(armour.reinforcement), label: translate('reinforcement') });
|
||||
|
||||
const armourSourcesTt = [];
|
||||
const effectiveArmourAbsoluteTt = [];
|
||||
const effectiveArmourExplosiveTt = [];
|
||||
const effectiveArmourKineticTt = [];
|
||||
const effectiveArmourThermalTt = [];
|
||||
if (armour.bulkheads > 0) {
|
||||
armourSourcesTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
||||
effectiveArmourAbsoluteTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
||||
effectiveArmourExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
||||
effectiveArmourKineticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
||||
effectiveArmourThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.int(armour.bulkheads)}</div>);
|
||||
if (armour.reinforcement > 0) {
|
||||
armourSourcesTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
||||
effectiveArmourAbsoluteTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
||||
effectiveArmourExplosiveTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
||||
effectiveArmourKineticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
||||
effectiveArmourThermalTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.int(armour.reinforcement)}</div>);
|
||||
}
|
||||
}
|
||||
|
||||
const rawArmour = armour.bulkheads + armour.reinforcement;
|
||||
|
||||
const armourDamageTakenTt = [];
|
||||
armourDamageTakenTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.absolute.bulkheads)}</div>);
|
||||
armourDamageTakenTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.absolute.reinforcement)}</div>);
|
||||
|
||||
const armourDamageTakenExplosiveTt = [];
|
||||
armourDamageTakenExplosiveTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.explosive.bulkheads)}</div>);
|
||||
armourDamageTakenExplosiveTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.explosive.reinforcement)}</div>);
|
||||
if (armour.explosive.bulkheads * armour.explosive.reinforcement != 1) effectiveArmourExplosiveTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.explosive.bulkheads * armour.explosive.reinforcement) - rawArmour)}</div>);
|
||||
|
||||
const armourDamageTakenKineticTt = [];
|
||||
armourDamageTakenKineticTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.kinetic.bulkheads)}</div>);
|
||||
armourDamageTakenKineticTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.kinetic.reinforcement)}</div>);
|
||||
if (armour.kinetic.bulkheads * armour.kinetic.reinforcement != 1) effectiveArmourKineticTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.kinetic.bulkheads * armour.kinetic.reinforcement) - rawArmour)}</div>);
|
||||
|
||||
const armourDamageTakenThermalTt = [];
|
||||
armourDamageTakenThermalTt.push(<div key='bulkheads'>{translate('bulkheads') + ' ' + formats.pct1(armour.thermal.bulkheads)}</div>);
|
||||
armourDamageTakenThermalTt.push(<div key='reinforcement'>{translate('reinforcement') + ' ' + formats.pct1(armour.thermal.reinforcement)}</div>);
|
||||
if (armour.thermal.bulkheads * armour.thermal.reinforcement != 1) effectiveArmourThermalTt.push(<div key='resistance'>{translate('resistance') + ' ' + formats.int(rawArmour / (armour.thermal.bulkheads * armour.thermal.reinforcement) - rawArmour)}</div>);
|
||||
|
||||
const effectiveArmourData = [];
|
||||
const effectiveAbsoluteArmour = armour.total / armour.absolute.total;
|
||||
effectiveArmourData.push({ value: Math.round(effectiveAbsoluteArmour), label: translate('absolute'), tooltip: effectiveArmourAbsoluteTt });
|
||||
const effectiveExplosiveArmour = armour.total / armour.explosive.total;
|
||||
effectiveArmourData.push({ value: Math.round(effectiveExplosiveArmour), label: translate('explosive'), tooltip: effectiveArmourExplosiveTt });
|
||||
const effectiveKineticArmour = armour.total / armour.kinetic.total;
|
||||
effectiveArmourData.push({ value: Math.round(effectiveKineticArmour), label: translate('kinetic'), tooltip: effectiveArmourKineticTt });
|
||||
const effectiveThermalArmour = armour.total / armour.thermal.total;
|
||||
effectiveArmourData.push({ value: Math.round(effectiveThermalArmour), label: translate('thermal'), tooltip: effectiveArmourThermalTt });
|
||||
|
||||
const armourDamageTakenData = [];
|
||||
armourDamageTakenData.push({ value: Math.round(armour.absolute.total * 100), label: translate('absolute'), tooltip: armourDamageTakenTt });
|
||||
armourDamageTakenData.push({ value: Math.round(armour.explosive.total * 100), label: translate('explosive'), tooltip: armourDamageTakenExplosiveTt });
|
||||
armourDamageTakenData.push({ value: Math.round(armour.kinetic.total * 100), label: translate('kinetic'), tooltip: armourDamageTakenKineticTt });
|
||||
armourDamageTakenData.push({ value: Math.round(armour.thermal.total * 100), label: translate('thermal'), tooltip: armourDamageTakenThermalTt });
|
||||
// Effective HP
|
||||
const effectiveArmourData = [
|
||||
'absolute', 'explosive', 'kinetic', 'thermal'
|
||||
].map((label) => {
|
||||
const dmgMult = armour[label];
|
||||
const raw = armour.armour;
|
||||
const tooltip = ['byBoosters', 'byGenerator', 'bySys'].map(
|
||||
(label) => <div key={label}>
|
||||
{translate(label)} {formats.int(raw * dmgMult[label])}
|
||||
</div>
|
||||
);
|
||||
return { label, value: Math.round(dmgMult.damageMultiplier * raw), tooltip };
|
||||
});
|
||||
|
||||
return (
|
||||
<span id='defence'>
|
||||
{shield.total ? <span>
|
||||
<div className='group quarter'>
|
||||
<h2>{translate('shield metrics')}</h2>
|
||||
<br/>
|
||||
<h2 onMouseOver={termtip.bind(null, <div>{shieldSourcesTt}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw shield strength')}<br/>{formats.int(shield.total)}{units.MJ}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_SHIELDS')}<br/>{shielddamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(shield.total, shielddamage.totalsdps, shielddamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * opponentWep / 4))}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECOVER'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECOVER_SHIELDS')}<br/>{shield.recover === Math.Inf ? translate('never') : formats.time(shield.recover)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECHARGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECHARGE_SHIELDS')}<br/>{shield.recharge === Math.Inf ? translate('never') : formats.time(shield.recharge)}</h2>
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_SOURCES'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield sources')}</h2>
|
||||
<PieChart data={shieldSourcesData} />
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_DAMAGE_TAKEN'))} onMouseOut={tooltip.bind(null, null)}>{translate('damage taken')}(%)</h2>
|
||||
<VerticalBarChart data={shieldDamageTakenData} yMax={140} />
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_EFFECTIVE_SHIELD'))} onMouseOut={tooltip.bind(null, null)}>{translate('effective shield')}(MJ)</h2>
|
||||
<VerticalBarChart data={effectiveShieldData} yMax={maxEffectiveShield}/>
|
||||
</div>
|
||||
{shields.withSCBs ? <span>
|
||||
<div className='group quarter'>
|
||||
<h2>{translate('shield metrics')}</h2>
|
||||
<br/>
|
||||
<h2 onMouseOver={termtip.bind(null, <div>{shieldSourcesTt}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw shield strength')}<br/>{formats.int(shields.withSCBs)}{units.MJ}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_SHIELDS')}<br/>TODO</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECOVER'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECOVER_SHIELDS')}<br/>{shields.recover ? formats.time(shields.recover) : translate('never')}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SG_RECHARGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_RECHARGE_SHIELDS')}<br/>{shields.recharge ? formats.time(shields.recharge) : translate('never')}</h2>
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_SOURCES'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield sources')}</h2>
|
||||
<PieChart data={shieldSourcesData} />
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_DAMAGE_TAKEN'))} onMouseOut={tooltip.bind(null, null)}>{translate('damage taken')}(%)</h2>
|
||||
<VerticalBarChart data={shieldDamageTakenData} yMax={140} />
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_EFFECTIVE_SHIELD'))} onMouseOut={tooltip.bind(null, null)}>{translate('effective shield')}(MJ)</h2>
|
||||
<VerticalBarChart data={effectiveShieldData} yMax={maxEffectiveShield}/>
|
||||
</div>
|
||||
</span> : null }
|
||||
|
||||
<div className='group quarter'>
|
||||
<h2>{translate('armour metrics')}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, <div>{armourSourcesTt}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw armour strength')}<br/>{formats.int(armour.total)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_ARMOUR')}<br/>{armourdamage.totalsdps == 0 ? translate('ever') : formats.time(Calc.timeToDeplete(armour.total, armourdamage.totalsdps, armourdamage.totalseps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * opponentWep / 4))}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('raw module armour')}<br/>{formats.int(armour.modulearmour)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_PROTECTION_EXTERNAL'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_MODULE_PROTECTION_EXTERNAL')}<br/>{formats.pct1(armour.moduleprotection / 2)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_PROTECTION_INTERNAL'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_MODULE_PROTECTION_INTERNAL')}<br/>{formats.pct1(armour.moduleprotection)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, <div>{armourSourcesTt}</div>)} onMouseOut={tooltip.bind(null, null)} className='summary'>{translate('raw armour strength')}<br/>{formats.int(armour.armour)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_LOSE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_LOSE_ARMOUR')}<br/>TODO</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('raw module armour')}<br/>{formats.int(moduleProtection.moduleArmour)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_PROTECTION_EXTERNAL'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_MODULE_PROTECTION_EXTERNAL')}<br/>{formats.pct1((1 - moduleProtection.moduleProtection) / 2)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_MODULE_PROTECTION_INTERNAL'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_MODULE_PROTECTION_INTERNAL')}<br/>{formats.pct1(1 - moduleProtection.moduleProtection)}</h2>
|
||||
<br/>
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
|
||||
134
src/app/components/EDEngineerButton.jsx
Normal file
134
src/app/components/EDEngineerButton.jsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import React from 'react';
|
||||
import autoBind from 'auto-bind';
|
||||
import Persist from '../stores/Persist';
|
||||
import PropTypes from 'prop-types';
|
||||
import { getBlueprintUuid, getExperimentalUuid } from 'ed-forge/lib/src/data/blueprints';
|
||||
import { Loader, MatIcon } from '../components/SvgIcons';
|
||||
import request from 'superagent';
|
||||
import { chain, entries } from 'lodash';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
|
||||
const STATE = {
|
||||
READY: 0,
|
||||
LOADING: 1,
|
||||
ERROR: 2,
|
||||
DONE: 3,
|
||||
};
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export default class EDEngineerButton extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
ship: PropTypes.object.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
autoBind(this);
|
||||
|
||||
const { ship } = props;
|
||||
const uuids = chain(ship.getModules())
|
||||
.filter((m) => m.getBlueprint())
|
||||
.map((m) => {
|
||||
const uuids = [getBlueprintUuid(m.getBlueprint(), m.getBlueprintGrade())];
|
||||
const exp = m.getExperimental();
|
||||
if (exp) {
|
||||
uuids.push(getExperimentalUuid(exp));
|
||||
}
|
||||
return uuids;
|
||||
})
|
||||
.flatMap()
|
||||
.groupBy()
|
||||
.mapValues((v) => v.length)
|
||||
.value();
|
||||
|
||||
this.state = {
|
||||
status: STATE.READY,
|
||||
uuids,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the shopping list
|
||||
*/
|
||||
_sendToEDEngineer() {
|
||||
const { uuids } = this.state;
|
||||
this.setState({ status: STATE.LOADING });
|
||||
request.get('http://localhost:44405/commanders')
|
||||
.then((data) => {
|
||||
const [cmdr] = JSON.parse(data.text);
|
||||
return Promise.all(
|
||||
entries(uuids).map(
|
||||
(entry) => {
|
||||
const [uuid, n] = entry;
|
||||
return new Promise((resolve, reject) => {
|
||||
request.patch(`http://localhost:44405/${cmdr}/shopping-list`)
|
||||
.field('uuid', uuid)
|
||||
.field('size', n)
|
||||
.end((err, res) => {
|
||||
console.log('request goes out!');
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(res);
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
),
|
||||
);
|
||||
})
|
||||
.then(() => this.setState({ status: STATE.DONE }))
|
||||
.catch((err) => {
|
||||
console.error(err);
|
||||
this.setState({ status: STATE.ERROR });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for browser compatibility of sending to ED Engineer.
|
||||
* @returns {boolean} True if browser is compatible
|
||||
*/
|
||||
_browserIsCompatible() {
|
||||
// !== Firefox 1.0+
|
||||
// TODO: Double check if this really doesn't work in firefox
|
||||
return typeof InstallTrigger === 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns
|
||||
*/
|
||||
render() {
|
||||
const { termtip, tooltip } = this.context;
|
||||
const hide = tooltip.bind(null, null);
|
||||
const { status } = this.state;
|
||||
|
||||
let msg = 'PHRASE_FIREFOX_EDENGINEER';
|
||||
if (this._browserIsCompatible()) {
|
||||
switch (status) {
|
||||
case STATE.READY: msg = 'Send to EDEngineer'; break;
|
||||
case STATE.LOADING: msg = 'Sending...'; break;
|
||||
case STATE.ERROR: msg = 'Error sending to EDEngineer'; break;
|
||||
case STATE.DONE: msg = 'Success! Clicking sends again.'; break;
|
||||
}
|
||||
}
|
||||
|
||||
return (<button
|
||||
disabled={!this._browserIsCompatible()}
|
||||
onClick={status !== STATE.LOADING && this._sendToEDEngineer}
|
||||
onMouseOver={termtip.bind(null, msg)}
|
||||
onMouseOut={hide}
|
||||
>
|
||||
{status === STATE.LOADING ?
|
||||
<Loader className="lg" /> :
|
||||
<MatIcon className="lg" />
|
||||
}
|
||||
</button>);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import Slider from '../components/Slider';
|
||||
import { moduleReduce } from 'ed-forge/lib/src/helper';
|
||||
|
||||
/**
|
||||
* Engagement range slider
|
||||
@@ -22,35 +22,18 @@ export default class EngagementRange extends TranslatedComponent {
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
const { ship } = props;
|
||||
|
||||
const maxRange = this._calcMaxRange(ship);
|
||||
|
||||
this.state = {
|
||||
maxRange
|
||||
maxRange: moduleReduce(
|
||||
this.props.ship.getHardpoints(),
|
||||
'maximumrange',
|
||||
true,
|
||||
// Don't use plain `Math.max` because callback will be passed four args
|
||||
(a, v) => Math.max(a, v),
|
||||
1000,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the maximum range of a ship's weapons
|
||||
* @param {Object} ship The ship
|
||||
* @returns {int} The maximum range, in metres
|
||||
*/
|
||||
_calcMaxRange(ship) {
|
||||
let maxRange = 1000;
|
||||
for (let i = 0; i < ship.hardpoints.length; i++) {
|
||||
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
|
||||
const thisRange = ship.hardpoints[i].m.getRange();
|
||||
if (thisRange > maxRange) {
|
||||
maxRange = thisRange;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return maxRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update range
|
||||
* @param {number} rangeLevel percentage level from 0 to 1
|
||||
@@ -62,7 +45,9 @@ export default class EngagementRange extends TranslatedComponent {
|
||||
const range = Math.round(rangeLevel * maxRange);
|
||||
|
||||
if (range !== this.props.engagementRange) {
|
||||
this.props.onChange(range);
|
||||
const { onChange, ship } = this.props;
|
||||
ship.setEngagementRange(range);
|
||||
onChange(range);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,8 +56,8 @@ export default class EngagementRange extends TranslatedComponent {
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
||||
const { formats, translate, units } = language;
|
||||
const { language, onWindowResize, sizeRatio } = this.context;
|
||||
const { formats, translate } = language;
|
||||
const { engagementRange } = this.props;
|
||||
const { maxRange } = this.state;
|
||||
|
||||
|
||||
@@ -1,104 +1,59 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import { nameComparator } from '../utils/SlotFunctions';
|
||||
import LineChart from '../components/LineChart';
|
||||
import Slider from '../components/Slider';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import Module from '../shipyard/Module';
|
||||
import * as Calc from '../shipyard/Calculations';
|
||||
import { getBoostMultiplier, getSpeedMultipliers } from 'ed-forge/lib/src/stats/SpeedProfile';
|
||||
import { ShipProps } from 'ed-forge';
|
||||
const { LADEN_MASS } = ShipProps;
|
||||
|
||||
/**
|
||||
* Engine profile for a given ship
|
||||
*/
|
||||
export default class EngineProfile extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
code: PropTypes.string.isRequired,
|
||||
ship: PropTypes.object.isRequired,
|
||||
cargo: PropTypes.number.isRequired,
|
||||
fuel: PropTypes.number.isRequired,
|
||||
eng: PropTypes.number.isRequired,
|
||||
pips: PropTypes.number.isRequired,
|
||||
boost: PropTypes.bool.isRequired,
|
||||
marker: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
const ship = this.props.ship;
|
||||
|
||||
this.state = {
|
||||
calcMaxSpeedFunc: this.calcMaxSpeed.bind(this, ship, this.props.eng, this.props.boost)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state if our ship changes
|
||||
* @param {Object} nextProps Incoming/Next properties
|
||||
* @param {Object} nextContext Incoming/Next conext
|
||||
* @return {boolean} Returns true if the component should be rerendered
|
||||
*/
|
||||
componentWillReceiveProps(nextProps, nextContext) {
|
||||
if (nextProps.marker != this.props.marker) {
|
||||
this.setState({ calcMaxSpeedFunc: this.calcMaxSpeed.bind(this, nextProps.ship, nextProps.eng, nextProps.boost) });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the top speed for this ship given thrusters, mass and pips to ENG
|
||||
* @param {Object} ship The ship
|
||||
* @param {Object} eng The number of pips to ENG
|
||||
* @param {Object} boost If boost is enabled
|
||||
* @param {Object} mass The mass at which to calculate the top speed
|
||||
* @return {number} The maximum speed
|
||||
*/
|
||||
calcMaxSpeed(ship, eng, boost, mass) {
|
||||
// Obtain the top speed
|
||||
return Calc.calcSpeed(mass, ship.speed, ship.standard[1].m, ship.pipSpeed, eng, ship.boost / ship.speed, boost);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render engine profile
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
||||
const { formats, translate, units } = language;
|
||||
const { ship, cargo, eng, fuel, boost } = this.props;
|
||||
const { language } = this.context;
|
||||
const { translate } = language;
|
||||
const { code, ship, pips, boost } = this.props;
|
||||
|
||||
// Calculate bounds for our line chart
|
||||
const thrusters = ship.standard[1].m;
|
||||
const minMass = ship.calcLowestPossibleMass({ th: thrusters });
|
||||
const maxMass = thrusters.getMaxMass();
|
||||
const mass = ship.unladenMass + fuel + cargo;
|
||||
const minSpeed = Calc.calcSpeed(maxMass, ship.speed, thrusters, ship.pipSpeed, 0, ship.boost / ship.speed, false);
|
||||
const maxSpeed = Calc.calcSpeed(minMass, ship.speed, thrusters, ship.pipSpeed, 4, ship.boost / ship.speed, true);
|
||||
// Add a mark at our current mass
|
||||
const mark = Math.min(mass, maxMass);
|
||||
|
||||
const code = `${ship.toString()}:${cargo}:${fuel}:${eng}:${boost}`;
|
||||
|
||||
const minMass = ship.readProp('hullmass');
|
||||
const maxMass = ship.getThrusters().get('enginemaximalmass');
|
||||
const baseSpeed = ship.readProp('speed');
|
||||
const baseBoost = getBoostMultiplier(ship);
|
||||
const cb = (eng, boost, mass) => {
|
||||
const mult = getSpeedMultipliers(ship, mass)[(boost ? 4 : eng) / 0.5];
|
||||
return baseSpeed * (boost ? baseBoost : 1) * mult;
|
||||
};
|
||||
// This graph can have a precipitous fall-off so we use lots of points to make it look a little smoother
|
||||
return (
|
||||
<LineChart
|
||||
xMin={minMass}
|
||||
xMax={maxMass}
|
||||
yMin={minSpeed}
|
||||
yMax={maxSpeed}
|
||||
xMark={mark}
|
||||
yMin={cb(0, false, maxMass)}
|
||||
yMax={cb(4, true, minMass)}
|
||||
// Add a mark at our current mass
|
||||
xMark={Math.min(ship.get(LADEN_MASS), maxMass)}
|
||||
xLabel={translate('mass')}
|
||||
xUnit={translate('T')}
|
||||
yLabel={translate('maximum speed')}
|
||||
yUnit={translate('m/s')}
|
||||
func={this.state.calcMaxSpeedFunc}
|
||||
func={cb.bind(this, pips.Eng.base + pips.Eng.mc, boost)}
|
||||
points={1000}
|
||||
code={code}
|
||||
// Encode boost in code to re-render on state change
|
||||
code={`${pips.Eng.base + pips.Eng.mc}:${Number(boost)}:${code}`}
|
||||
aspect={0.7}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,100 +1,48 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import { nameComparator } from '../utils/SlotFunctions';
|
||||
import LineChart from '../components/LineChart';
|
||||
import Slider from '../components/Slider';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import Module from '../shipyard/Module';
|
||||
import * as Calc from '../shipyard/Calculations';
|
||||
import { calculateJumpRange } from 'ed-forge/lib/src/stats/JumpRangeProfile';
|
||||
import { ShipProps } from 'ed-forge';
|
||||
const { LADEN_MASS } = ShipProps;
|
||||
|
||||
/**
|
||||
* FSD profile for a given ship
|
||||
*/
|
||||
export default class FSDProfile extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
code: PropTypes.string.isRequired,
|
||||
ship: PropTypes.object.isRequired,
|
||||
cargo: PropTypes.number.isRequired,
|
||||
fuel: PropTypes.number.isRequired,
|
||||
marker: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
const ship = this.props.ship;
|
||||
|
||||
this.state = {
|
||||
calcMaxRangeFunc: this._calcMaxRange.bind(this, ship, this.props.fuel)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state if our ship changes
|
||||
* @param {Object} nextProps Incoming/Next properties
|
||||
* @param {Object} nextContext Incoming/Next conext
|
||||
* @return {boolean} Returns true if the component should be rerendered
|
||||
*/
|
||||
componentWillReceiveProps(nextProps, nextContext) {
|
||||
if (nextProps.marker != this.props.marker) {
|
||||
this.setState({ calcMaxRangeFunc: this._calcMaxRange.bind(this, nextProps.ship, nextProps.fuel) });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the maximum range for this ship across its applicable mass
|
||||
* @param {Object} ship The ship
|
||||
* @param {Object} fuel The fuel on the ship
|
||||
* @param {Object} mass The mass at which to calculate the maximum range
|
||||
* @return {number} The maximum range
|
||||
*/
|
||||
_calcMaxRange(ship, fuel, mass) {
|
||||
// Obtain the maximum range
|
||||
return Calc.jumpRange(mass, ship.standard[2].m, Math.min(fuel, ship.standard[2].m.getMaxFuelPerJump()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Render FSD profile
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
||||
const { formats, translate, units } = language;
|
||||
const { ship, cargo, fuel } = this.props;
|
||||
|
||||
|
||||
// Calculate bounds for our line chart - use thruster info for X
|
||||
const thrusters = ship.standard[1].m;
|
||||
const fsd = ship.standard[2].m;
|
||||
const minMass = ship.calcLowestPossibleMass({ th: thrusters });
|
||||
const maxMass = thrusters.getMaxMass();
|
||||
const mass = ship.unladenMass + fuel + cargo;
|
||||
const minRange = 0;
|
||||
const maxRange = Calc.jumpRange(minMass + fsd.getMaxFuelPerJump(), fsd, fsd.getMaxFuelPerJump());
|
||||
// Add a mark at our current mass
|
||||
const mark = Math.min(mass, maxMass);
|
||||
|
||||
const code = ship.name + ship.toString() + '.' + fuel;
|
||||
const { language } = this.context;
|
||||
const { translate } = language;
|
||||
const { code, ship } = this.props;
|
||||
|
||||
const minMass = ship.readProp('hullmass');
|
||||
const maxMass = ship.getThrusters().get('enginemaximalmass');
|
||||
const mass = ship.get(LADEN_MASS);
|
||||
const cb = (mass) => calculateJumpRange(ship.getFSD(), 0, mass, Infinity, true);
|
||||
return (
|
||||
<LineChart
|
||||
xMin={minMass}
|
||||
xMax={maxMass}
|
||||
yMin={minRange}
|
||||
yMax={maxRange}
|
||||
xMark={mark}
|
||||
yMin={0}
|
||||
yMax={cb(minMass)}
|
||||
// Add a mark at our current mass
|
||||
xMark={Math.min(mass, maxMass)}
|
||||
xLabel={translate('mass')}
|
||||
xUnit={translate('T')}
|
||||
yLabel={translate('maximum range')}
|
||||
yUnit={translate('LY')}
|
||||
func={this.state.calcMaxRangeFunc}
|
||||
func={cb}
|
||||
points={200}
|
||||
code={code}
|
||||
aspect={0.7}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import Slider from '../components/Slider';
|
||||
import autoBind from 'auto-bind';
|
||||
|
||||
/**
|
||||
* Fuel slider
|
||||
@@ -22,8 +22,7 @@ export default class Fuel extends TranslatedComponent {
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
this._fuelChange = this._fuelChange.bind(this);
|
||||
autoBind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
import React from 'react';
|
||||
import Slot from './Slot';
|
||||
import Persist from '../stores/Persist';
|
||||
import { DamageAbsolute, DamageKinetic, DamageThermal, DamageExplosive, MountFixed, MountGimballed, MountTurret, ListModifications, Modified } from './SvgIcons';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
import { blueprintTooltip } from '../utils/BlueprintFunctions';
|
||||
|
||||
|
||||
/**
|
||||
* Hardpoint / Utility Slot
|
||||
*/
|
||||
export default class HardpointSlot extends Slot {
|
||||
|
||||
/**
|
||||
* Get the CSS class name for the slot.
|
||||
* @return {string} CSS Class name
|
||||
*/
|
||||
_getClassNames() {
|
||||
return this.props.maxClass > 0 ? 'hardpoint' : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the label for the slot
|
||||
* @param {Function} translate Translate function
|
||||
* @return {string} Label
|
||||
*/
|
||||
_getMaxClassLabel(translate) {
|
||||
return translate(['U','S','M','L','H'][this.props.maxClass]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the slot contents
|
||||
* @param {Object} m Mounted Module
|
||||
* @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) {
|
||||
if (m) {
|
||||
let classRating = `${m.class}${m.rating}${m.missile ? '/' + m.missile : ''}`;
|
||||
let { drag, drop } = this.props;
|
||||
let { termtip, tooltip } = this.context;
|
||||
let validMods = Modifications.modules[m.grp].modifications || [];
|
||||
let showModuleResistances = Persist.showModuleResistances();
|
||||
|
||||
// Modifications tooltip shows blueprint and grade, if available
|
||||
let modTT = translate('modified');
|
||||
if (m && m.blueprint && m.blueprint.name) {
|
||||
modTT = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
|
||||
if (m.blueprint.special && m.blueprint.special.id >= 0) {
|
||||
modTT += ', ' + translate(m.blueprint.special.name);
|
||||
}
|
||||
modTT = (
|
||||
<div>
|
||||
<div>{modTT}</div>
|
||||
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
|
||||
<div className={'cb'}>
|
||||
<div className={'l'}>
|
||||
{m.mount && m.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed /></span> : ''}
|
||||
{m.mount && m.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : ''}
|
||||
{m.mount && m.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : ''}
|
||||
{m.getDamageDist() && m.getDamageDist().K ? <span onMouseOver={termtip.bind(null, 'kinetic')} onMouseOut={tooltip.bind(null, null)}><DamageKinetic /></span> : ''}
|
||||
{m.getDamageDist() && m.getDamageDist().T ? <span onMouseOver={termtip.bind(null, 'thermal')} onMouseOut={tooltip.bind(null, null)}><DamageThermal /></span> : ''}
|
||||
{m.getDamageDist() && m.getDamageDist().E ? <span onMouseOver={termtip.bind(null, 'explosive')} onMouseOut={tooltip.bind(null, null)}><DamageExplosive /></span> : ''}
|
||||
{m.getDamageDist() && m.getDamageDist().A ? <span onMouseOver={termtip.bind(null, 'absolute')} onMouseOut={tooltip.bind(null, null)}><DamageAbsolute /></span> : ''}
|
||||
{classRating} {translate(m.name || m.grp)}{ m.mods && Object.keys(m.mods).length > 0 ? <span className='r' onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : null }
|
||||
</div>
|
||||
|
||||
<div className={'r'}>{formats.round(m.getMass())}{u.T}</div>
|
||||
</div>
|
||||
<div className={'cb'}>
|
||||
{ m.getDps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'dpssdps' : 'dps')} onMouseOut={tooltip.bind(null, null)}>{translate('DPS')}: {formats.round1(m.getDps())} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getDps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) })</span> : null }</div> : null }
|
||||
{ m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'epsseps' : 'eps')} onMouseOut={tooltip.bind(null, null)}>{translate('EPS')}: {formats.round1(m.getEps())}{u.MW} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getEps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) }{u.MW})</span> : null }</div> : null }
|
||||
{ m.getHps() ? <div className={'l'} onMouseOver={termtip.bind(null, m.getClip() ? 'hpsshps' : 'hps')} onMouseOut={tooltip.bind(null, null)}>{translate('HPS')}: {formats.round1(m.getHps())} { m.getClip() ? <span>({formats.round1((m.getClip() * m.getHps() / m.getRoF()) / ((m.getClip() / m.getRoF()) + m.getReload())) })</span> : null }</div> : null }
|
||||
{ m.getDps() && m.getEps() ? <div className={'l'} onMouseOver={termtip.bind(null, 'dpe')} onMouseOut={tooltip.bind(null, null)}>{translate('DPE')}: {formats.f1(m.getDps() / m.getEps())}</div> : null }
|
||||
{ m.getRoF() ? <div className={'l'} onMouseOver={termtip.bind(null, 'rof')} onMouseOut={tooltip.bind(null, null)}>{translate('ROF')}: {formats.f1(m.getRoF())}{u.ps}</div> : null }
|
||||
{ m.getRange() ? <div className={'l'}>{translate('range', m.grp)} {formats.f1(m.getRange() / 1000)}{u.km}</div> : null }
|
||||
{ m.getScanTime() ? <div className={'l'}>{translate('scantime')} {formats.f1(m.getScanTime())}{u.s}</div> : null }
|
||||
{ m.getFalloff() ? <div className={'l'}>{translate('falloff')} {formats.round(m.getFalloff() / 1000)}{u.km}</div> : null }
|
||||
{ m.getShieldBoost() ? <div className={'l'}>+{formats.pct1(m.getShieldBoost())}</div> : null }
|
||||
{ m.getAmmo() ? <div className={'l'}>{translate('ammunition')}: {formats.int(m.getClip())}/{formats.int(m.getAmmo())}</div> : null }
|
||||
{ m.getReload() ? <div className={'l'}>{translate('reload')}: {formats.round(m.getReload())}{u.s}</div> : null }
|
||||
{ m.getShotSpeed() ? <div className={'l'}>{translate('shotspeed')}: {formats.int(m.getShotSpeed())}{u.mps}</div> : null }
|
||||
{ m.getPiercing() ? <div className={'l'}>{translate('piercing')}: {formats.int(m.getPiercing())}</div> : null }
|
||||
{ m.getJitter() ? <div className={'l'}>{translate('jitter')}: {formats.f2(m.getJitter())}°</div> : null }
|
||||
{ showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null }
|
||||
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
|
||||
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
|
||||
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
|
||||
{ m && validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
|
||||
</div>
|
||||
</div>;
|
||||
} else {
|
||||
return <div className={'empty'}>{translate('empty')}</div>;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,84 +1,82 @@
|
||||
import React from 'react';
|
||||
import SlotSection from './SlotSection';
|
||||
import HardpointSlot from './HardpointSlot';
|
||||
import cn from 'classnames';
|
||||
import Slot from './Slot';
|
||||
import { MountFixed, MountGimballed, MountTurret } from '../components/SvgIcons';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
import autoBind from 'auto-bind';
|
||||
|
||||
const SIZE_ORDER = ['huge', 'large', 'medium', 'small'];
|
||||
|
||||
/**
|
||||
* Hardpoint slot section
|
||||
*/
|
||||
export default class HardpointSlotSection extends SlotSection {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props, context, 'hardpoints', 'hardpoints');
|
||||
|
||||
this._empty = this._empty.bind(this);
|
||||
constructor(props) {
|
||||
super(props, 'hardpoints');
|
||||
autoBind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty all slots
|
||||
*/
|
||||
_empty() {
|
||||
this.props.ship.emptyWeapons();
|
||||
this.props.onChange();
|
||||
this.props.ship.getHardpoints(undefined, true).forEach((slot) => slot.reset());
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill slots with specified module
|
||||
* @param {string} group Group name
|
||||
* @param {string} mount Mount Type - F, G, T
|
||||
* @param {SyntheticEvent} event Event
|
||||
* @param {string} type Type of item
|
||||
* @param {string} rating Mount Type - (fixed, gimbal, turret)
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fill(group, mount, event) {
|
||||
this.props.ship.useWeapon(group, mount, null, event.getModifierState('Alt'));
|
||||
this.props.onChange();
|
||||
_fill(type, rating, event) {
|
||||
const fillAll = event.getModifierState('Alt');
|
||||
this.props.ship.getHardpoints(undefined, true).forEach((slot) => {
|
||||
if (slot.isEmpty() || fillAll) {
|
||||
const slotSize = slot.getSize();
|
||||
const fittingSizes = SIZE_ORDER.slice(SIZE_ORDER.findIndex((e) => e === slotSize));
|
||||
for (const size of fittingSizes) {
|
||||
try {
|
||||
slot.setItem(type, size, rating);
|
||||
} catch (err) {
|
||||
// Try next item if this doesn't fit/exist
|
||||
continue;
|
||||
}
|
||||
// If still here, we were able to apply the module
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty all on section header right click
|
||||
*/
|
||||
_contextMenu() {
|
||||
this._empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the slot React Components
|
||||
* @return {Array} Array of Slots
|
||||
*/
|
||||
_getSlots() {
|
||||
let { ship, currentMenu } = this.props;
|
||||
let { ship, currentMenu, propsToShow, onPropToggle } = this.props;
|
||||
let { originSlot, targetSlot } = this.state;
|
||||
let slots = [];
|
||||
let hardpoints = ship.hardpoints;
|
||||
let availableModules = ship.getAvailableModules();
|
||||
|
||||
for (let i = 0, l = hardpoints.length; i < l; i++) {
|
||||
let h = hardpoints[i];
|
||||
if (h.maxClass) {
|
||||
slots.push(<HardpointSlot
|
||||
key={i}
|
||||
maxClass={h.maxClass}
|
||||
availableModules={() => availableModules.getHps(h.maxClass)}
|
||||
onOpen={this._openMenu.bind(this, h)}
|
||||
onSelect={this._selectModule.bind(this, h)}
|
||||
onChange={this.props.onChange}
|
||||
selected={currentMenu == h}
|
||||
drag={this._drag.bind(this, h)}
|
||||
dragOver={this._dragOverSlot.bind(this, h)}
|
||||
drop={this._drop}
|
||||
dropClass={this._dropClass(h, originSlot, targetSlot)}
|
||||
ship={ship}
|
||||
m={h.m}
|
||||
/>);
|
||||
}
|
||||
for (let h of ship.getHardpoints(undefined, true)) {
|
||||
slots.push(<Slot
|
||||
key={h.object.Slot}
|
||||
currentMenu={currentMenu}
|
||||
drag={this._drag.bind(this, h)}
|
||||
dragOver={this._dragOverSlot.bind(this, h)}
|
||||
drop={this._drop}
|
||||
dropClass={this._dropClass(h, originSlot, targetSlot)}
|
||||
m={h}
|
||||
enabled={h.enabled ? true : false}
|
||||
propsToShow={propsToShow}
|
||||
onPropToggle={onPropToggle}
|
||||
/>);
|
||||
}
|
||||
|
||||
return slots;
|
||||
@@ -89,59 +87,68 @@ export default class HardpointSlotSection extends SlotSection {
|
||||
* @param {Function} translate Translate function
|
||||
* @return {React.Component} Section menu
|
||||
*/
|
||||
_getSectionMenu(translate) {
|
||||
_getSectionMenu() {
|
||||
const { translate } = this.context.language;
|
||||
let _fill = this._fill;
|
||||
|
||||
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}>{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>
|
||||
<div className='select-group cap'>{translate('pulselaser')}</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 hardpoint" tabIndex="0" onClick={_fill.bind(this, 'pulselaser', 'fixed')}><MountFixed className='lg'/></li>
|
||||
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'pulselaser', 'gimbal')}><MountGimballed className='lg'/></li>
|
||||
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'pulselaser', 'turret')}><MountTurret className='lg'/></li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('ul')}</div>
|
||||
<div className='select-group cap'>{translate('burstlaser')}</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 hardpoint" tabIndex="0" onClick={_fill.bind(this, 'burstlaser', 'fixed')}><MountFixed className='lg'/></li>
|
||||
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'burstlaser', 'gimbal')}><MountGimballed className='lg'/></li>
|
||||
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'burstlaser', 'turret')}><MountTurret className='lg'/></li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('bl')}</div>
|
||||
<div className='select-group cap'>{translate('beamlaser')}</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 hardpoint" tabIndex="0" onClick={_fill.bind(this, 'beamlaser', 'fixed')}><MountFixed className='lg'/></li>
|
||||
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'beamlaser', 'gimbal')}><MountGimballed className='lg'/></li>
|
||||
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'beamlaser', 'turret')}><MountTurret className='lg'/></li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('mc')}</div>
|
||||
<div className='select-group cap'>{translate('multicannon')}</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 hardpoint" tabIndex="0" onClick={_fill.bind(this, 'multicannon', 'fixed')}><MountFixed className='lg'/></li>
|
||||
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'multicannon', 'gimbal')}><MountGimballed className='lg'/></li>
|
||||
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'multicannon', 'turret')}><MountTurret className='lg'/></li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('c')}</div>
|
||||
<div className='select-group cap'>{translate('cannon')}</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 hardpoint" tabIndex="0" onClick={_fill.bind(this, 'cannon', 'fixed')}><MountFixed className='lg'/></li>
|
||||
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'cannon', 'gimbal')}><MountGimballed className='lg'/></li>
|
||||
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'cannon', 'turret')}><MountTurret className='lg'/></li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('fc')}</div>
|
||||
<div className='select-group cap'>{translate('fragcannon')}</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 hardpoint" tabIndex="0" onClick={_fill.bind(this, 'fragcannon', 'fixed')}><MountFixed className='lg'/></li>
|
||||
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'fragcannon', 'gimbal')}><MountGimballed className='lg'/></li>
|
||||
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'fragcannon', 'turret')}><MountTurret className='lg'/></li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('pa')}</div>
|
||||
<div className='select-group cap'>{translate('plasmaacc')}</div>
|
||||
<ul>
|
||||
<li className='lc' onClick={_fill.bind(this, 'pa', 'F')}>{translate('pa')}</li>
|
||||
<li className='lc' tabIndex="0" onClick={_fill.bind(this, 'plasmaacc', 'fixed')}>{translate('pa')}</li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('nl')}</div>
|
||||
<div className='select-group cap'>{translate('railgun')}</div>
|
||||
<ul>
|
||||
<li className='lc' onClick={_fill.bind(this, 'nl', 'F')}>{translate('nl')}</li>
|
||||
<li className='lc' tabIndex="0" onClick={_fill.bind(this, 'railgun', 'fixed')}>{translate('rg')}</li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('minelauncher')}</div>
|
||||
<ul>
|
||||
<li className='lc' tabIndex="0" onClick={_fill.bind(this, 'minelauncher', 'fixed')}>{translate('nl')}</li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('flaklauncher')}</div>
|
||||
<ul>
|
||||
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'flaklauncher', 'fixed')}><MountFixed className='lg'/></li>
|
||||
<li className="c hardpoint" tabIndex="0" onClick={_fill.bind(this, 'flaklauncher', 'turret')}><MountTurret className='lg'/></li>
|
||||
</ul>
|
||||
</div>;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,15 +6,17 @@ import Link from './Link';
|
||||
import ActiveLink from './ActiveLink';
|
||||
import cn from 'classnames';
|
||||
import { Cogs, CoriolisLogo, Hammer, Help, Rocket, StatsBars } from './SvgIcons';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import Persist from '../stores/Persist';
|
||||
import { toDetailedExport } from '../shipyard/Serializer';
|
||||
import ModalDeleteAll from './ModalDeleteAll';
|
||||
import ModalExport from './ModalExport';
|
||||
import ModalHelp from './ModalHelp';
|
||||
import ModalImport from './ModalImport';
|
||||
import Slider from './Slider';
|
||||
import Announcement from './Announcement';
|
||||
import { outfitURL } from '../utils/UrlGenerators';
|
||||
import autoBind from 'auto-bind';
|
||||
import { Factory, Ship } from 'ed-forge';
|
||||
import { chain, entries } from 'lodash';
|
||||
|
||||
const SIZE_MIN = 0.65;
|
||||
const SIZE_RANGE = 0.55;
|
||||
@@ -53,29 +55,21 @@ function selectAll(e) {
|
||||
* Coriolis App Header section / menus
|
||||
*/
|
||||
export default class Header extends TranslatedComponent {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
this.shipOrder = Object.keys(Ships).sort();
|
||||
autoBind(this);
|
||||
this.ships = Factory.getAllShipTypes().sort();
|
||||
|
||||
this._setLanguage = this._setLanguage.bind(this);
|
||||
this._setInsurance = this._setInsurance.bind(this);
|
||||
this._setShipDiscount = this._setShipDiscount.bind(this);
|
||||
this._changeShipDiscount = this._changeShipDiscount.bind(this);
|
||||
this._kpShipDiscount = this._kpShipDiscount.bind(this);
|
||||
this._setModuleDiscount = this._setModuleDiscount.bind(this);
|
||||
this._changeModuleDiscount = this._changeModuleDiscount.bind(this);
|
||||
this._kpModuleDiscount = this._kpModuleDiscount.bind(this);
|
||||
this._openShips = this._openMenu.bind(this, 's');
|
||||
this._openBuilds = this._openMenu.bind(this, 'b');
|
||||
this._openComp = this._openMenu.bind(this, 'comp');
|
||||
this._openAnnounce = this._openMenu.bind(this, 'announce');
|
||||
this._openSettings = this._openMenu.bind(this, 'settings');
|
||||
this._showHelp = this._showHelp.bind(this);
|
||||
this.languageOptions = [];
|
||||
this.insuranceOptions = [];
|
||||
this.state = {
|
||||
@@ -205,13 +199,6 @@ export default class Header extends TranslatedComponent {
|
||||
Persist.showTooltips(!Persist.showTooltips());
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle module resistances setting
|
||||
*/
|
||||
_toggleModuleResistances() {
|
||||
Persist.showModuleResistances(!Persist.showModuleResistances());
|
||||
}
|
||||
|
||||
/**
|
||||
* Show delete all modal
|
||||
* @param {SyntheticEvent} e Event
|
||||
@@ -221,20 +208,6 @@ export default class Header extends TranslatedComponent {
|
||||
this.context.showModal(<ModalDeleteAll />);
|
||||
};
|
||||
|
||||
/**
|
||||
* Show export modal with backup data
|
||||
* @param {SyntheticEvent} e Event
|
||||
*/
|
||||
_showBackup(e) {
|
||||
let translate = this.context.language.translate;
|
||||
e.preventDefault();
|
||||
this.context.showModal(<ModalExport
|
||||
title={translate('backup')}
|
||||
description={translate('PHRASE_BACKUP_DESC')}
|
||||
data={Persist.getAll()}
|
||||
/>);
|
||||
};
|
||||
|
||||
/**
|
||||
* Show export modal with detailed export
|
||||
* @param {SyntheticEvent} e Event
|
||||
@@ -243,10 +216,22 @@ export default class Header extends TranslatedComponent {
|
||||
let translate = this.context.language.translate;
|
||||
e.preventDefault();
|
||||
|
||||
const builds = chain(Persist.getBuilds())
|
||||
.values()
|
||||
.map((builds) => Object.values(builds))
|
||||
.flatMap()
|
||||
.map((code) => new Ship(code))
|
||||
.value();
|
||||
|
||||
this.context.showModal(<ModalExport
|
||||
title={translate('detailed export')}
|
||||
description={translate('PHRASE_EXPORT_DESC')}
|
||||
data={toDetailedExport(Persist.getBuilds())}
|
||||
data={JSON.stringify(builds.map((build) => {
|
||||
return {
|
||||
header: { appName: 'Inara', 'appVersion': '1.0' },
|
||||
data: build.toJSON(),
|
||||
};
|
||||
}))}
|
||||
/>);
|
||||
}
|
||||
|
||||
@@ -304,15 +289,10 @@ export default class Header extends TranslatedComponent {
|
||||
* @return {React.Component} Menu
|
||||
*/
|
||||
_getShipsMenu() {
|
||||
let shipList = [];
|
||||
|
||||
for (let s in Ships) {
|
||||
shipList.push(<ActiveLink key={s} href={outfitURL(s)} className='block'>{Ships[s].properties.name}</ActiveLink>);
|
||||
}
|
||||
|
||||
const { translate } = this.context.language;
|
||||
return (
|
||||
<div className='menu-list dbl no-wrap' onClick={ (e) => e.stopPropagation() }>
|
||||
{shipList}
|
||||
{this.ships.map((s) => <ActiveLink key={s} href={outfitURL(s)} className='block'>{translate(s)}</ActiveLink>)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -322,9 +302,10 @@ export default class Header extends TranslatedComponent {
|
||||
* @return {React.Component} Menu
|
||||
*/
|
||||
_getBuildsMenu() {
|
||||
const { translate } = this.context.language;
|
||||
let builds = Persist.getBuilds();
|
||||
let buildList = [];
|
||||
for (let shipId of this.shipOrder) {
|
||||
for (let shipId of this.ships) {
|
||||
if (builds[shipId]) {
|
||||
let shipBuilds = [];
|
||||
let buildNameOrder = Object.keys(builds[shipId]).sort();
|
||||
@@ -332,7 +313,7 @@ export default class Header extends TranslatedComponent {
|
||||
let href = outfitURL(shipId, builds[shipId][buildName], buildName);
|
||||
shipBuilds.push(<li key={shipId + '-' + buildName} ><ActiveLink href={href} className='block'>{buildName}</ActiveLink></li>);
|
||||
}
|
||||
buildList.push(<ul key={shipId}>{Ships[shipId].properties.name}{shipBuilds}</ul>);
|
||||
buildList.push(<ul key={shipId}>{translate(shipId)}{shipBuilds}</ul>);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,6 +353,32 @@ export default class Header extends TranslatedComponent {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the announcement menu
|
||||
* @return {React.Component} Menu
|
||||
*/
|
||||
_getAnnouncementsMenu() {
|
||||
let announcements;
|
||||
let translate = this.context.language.translate;
|
||||
|
||||
if (this.props.announcements) {
|
||||
announcements = [];
|
||||
for (let announce of this.props.announcements) {
|
||||
if (announce.expiry < Date.now()) {
|
||||
continue;
|
||||
}
|
||||
announcements.push(<Announcement text={announce.text} />);
|
||||
announcements.push(<hr/>);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className='menu-list' onClick={ (e) => e.stopPropagation() } style={{ whiteSpace: 'nowrap' }}>
|
||||
{announcements}
|
||||
<hr />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the settings menu
|
||||
* @return {React.Component} Menu
|
||||
@@ -379,7 +386,6 @@ export default class Header extends TranslatedComponent {
|
||||
_getSettingsMenu() {
|
||||
let translate = this.context.language.translate;
|
||||
let tips = Persist.showTooltips();
|
||||
let moduleResistances = Persist.showModuleResistances();
|
||||
|
||||
return (
|
||||
<div className='menu-list no-wrap cap' onClick={ (e) => e.stopPropagation() }>
|
||||
@@ -397,10 +403,6 @@ export default class Header extends TranslatedComponent {
|
||||
<td>{translate('tooltips')}</td>
|
||||
<td className={cn('ri', { disabled: !tips, 'primary-disabled': tips })}>{(tips ? '✓' : '✗')}</td>
|
||||
</tr>
|
||||
<tr className='cap ptr' onClick={this._toggleModuleResistances} >
|
||||
<td>{translate('module resistances')}</td>
|
||||
<td className={cn('ri', { disabled: !moduleResistances, 'primary-disabled': moduleResistances })}>{(moduleResistances ? '✓' : '✗')}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{translate('insurance')}</td>
|
||||
<td className='ri'>
|
||||
@@ -428,10 +430,9 @@ export default class Header extends TranslatedComponent {
|
||||
<hr />
|
||||
<ul style={{ width: '100%' }}>
|
||||
{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._showImport.bind(this)}>{translate('import')}</Link></li>
|
||||
<li><Link href="#" className='block' onClick={this._showDeleteAll.bind(this)}>{translate('delete all')}</Link></li>
|
||||
<li><Link href="#" className='block' onClick={this._showDetailedExport}>{translate('detailed export')}</Link></li>
|
||||
<li><Link href="#" className='block' onClick={this._showImport}>{translate('import')}</Link></li>
|
||||
<li><Link href="#" className='block' onClick={this._showDeleteAll}>{translate('delete all')}</Link></li>
|
||||
</ul>
|
||||
<hr />
|
||||
<table style={{ width: 300, backgroundColor: 'transparent' }}>
|
||||
@@ -442,7 +443,7 @@ export default class Header extends TranslatedComponent {
|
||||
<td style={{ width: 20 }}><span style={{ fontSize: 30 }}>A</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colSpan='3' style={{ textAlign: 'center', cursor: 'pointer' }} className='primary-disabled cap' onClick={this._resetTextSize.bind(this)}>{translate('reset')}</td>
|
||||
<td colSpan='3' style={{ textAlign: 'center', cursor: 'pointer' }} className='primary-disabled cap' onClick={this._resetTextSize.bind(this)}>{translate('reset')}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -463,7 +464,6 @@ export default class Header extends TranslatedComponent {
|
||||
Persist.addListener('deletedAll', update);
|
||||
Persist.addListener('builds', update);
|
||||
Persist.addListener('tooltips', update);
|
||||
Persist.addListener('moduleresistances', update);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -494,6 +494,15 @@ export default class Header extends TranslatedComponent {
|
||||
}
|
||||
}
|
||||
|
||||
async update() {
|
||||
const reg = await navigator.serviceWorker.getRegistration();
|
||||
if (!reg || !reg.waiting) {
|
||||
return window.location.reload();
|
||||
}
|
||||
reg.waiting.postMessage('skipWaiting');
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the header
|
||||
* @return {React.Component} Header
|
||||
@@ -504,7 +513,10 @@ export default class Header extends TranslatedComponent {
|
||||
let hasBuilds = Persist.hasBuilds();
|
||||
return (
|
||||
<header>
|
||||
{this.props.appCacheUpdate && <div id="app-update" onClick={() => window.location.reload() }>{translate('PHRASE_UPDATE_RDY')}</div>}
|
||||
{this.props.appCacheUpdate && <div id="app-update" onClick={this.update}>{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'>
|
||||
@@ -521,12 +533,23 @@ export default class Header extends TranslatedComponent {
|
||||
{openedMenu == 'b' ? this._getBuildsMenu() : null}
|
||||
</div>
|
||||
|
||||
<div className='l menu'>
|
||||
<div className={cn('menu-header', { selected: openedMenu == 'comp', disabled: !hasBuilds })} onClick={hasBuilds && this._openComp}>
|
||||
<StatsBars className={cn('warning', { 'warning-disabled': !hasBuilds })} /><span className='menu-item-label'>{translate('compare')}</span>
|
||||
{/* TODO: Enable */}
|
||||
{/* <div className='l menu'>
|
||||
<div className={cn('menu-header', { selected: openedMenu == 'announce', disabled: this.props.announcements.length === 0 })} onClick={this.props.announcements.length !== 0 && this._openAnnounce}>
|
||||
<span className='menu-item-label'>{translate('announcements')}</span>
|
||||
</div>
|
||||
{openedMenu == 'comp' ? this._getComparisonsMenu() : null}
|
||||
</div>
|
||||
{openedMenu == 'announce' ? this._getAnnouncementsMenu() : 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}>
|
||||
@@ -543,5 +566,4 @@ export default class Header extends TranslatedComponent {
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
import React from 'react';
|
||||
import Slot from './Slot';
|
||||
import Persist from '../stores/Persist';
|
||||
import { ListModifications, Modified } from './SvgIcons';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
import { blueprintTooltip } from '../utils/BlueprintFunctions';
|
||||
|
||||
/**
|
||||
* Internal Slot
|
||||
*/
|
||||
export default class InternalSlot extends Slot {
|
||||
|
||||
/**
|
||||
* Generate the slot contents
|
||||
* @param {Object} m Mounted Module
|
||||
* @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) {
|
||||
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 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;
|
||||
modTT = (
|
||||
<div>
|
||||
<div>{modTT}</div>
|
||||
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let mass = m.getMass() || m.cargo || m.fuel || 0;
|
||||
return <div className='details' draggable='true' onDragStart={drag} onDragEnd={drop}>
|
||||
<div className={'cb'}>
|
||||
<div className={'l'}>{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : ''}</div>
|
||||
<div className={'r'}>{formats.round(mass)}{u.T}</div>
|
||||
</div>
|
||||
<div className={'cb'}>
|
||||
{ m.getOptMass() ? <div className={'l'}>{translate('optmass', 'sg')}: {formats.int(m.getOptMass())}{u.T}</div> : null }
|
||||
{ m.getMaxMass() ? <div className={'l'}>{translate('maxmass', 'sg')}: {formats.int(m.getMaxMass())}{u.T}</div> : null }
|
||||
{ m.bins ? <div className={'l'}>{m.bins} <u>{translate('bins')}</u></div> : null }
|
||||
{ m.bays ? <div className={'l'}>{translate('bays')}: {m.bays}</div> : null }
|
||||
{ m.rebuildsperbay ? <div className={'l'}>{translate('rebuildsperbay')}: {m.rebuildsperbay}</div> : null }
|
||||
{ m.rate ? <div className={'l'}>{translate('rate')}: {m.rate}{u.kgs} {translate('refuel time')}: {formats.time(this.props.fuel * 1000 / m.rate)}</div> : null }
|
||||
{ m.getAmmo() && m.grp !== 'scb' ? <div className={'l'}>{translate('ammunition')}: {formats.gen(m.getAmmo())}</div> : null }
|
||||
{ m.getSpinup() ? <div className={'l'}>{translate('spinup')}: {formats.f1(m.getSpinup())}{u.s}</div> : null }
|
||||
{ m.getDuration() ? <div className={'l'}>{translate('duration')}: {formats.f1(m.getDuration())}{u.s}</div> : null }
|
||||
{ m.grp === 'scb' ? <div className={'l'}>{translate('cells')}: {formats.int(m.getAmmo() + 1)}</div> : null }
|
||||
{ m.getShieldReinforcement() ? <div className={'l'}>{translate('shieldreinforcement')}: {formats.f1(m.getDuration() * m.getShieldReinforcement())}{u.MJ}</div> : null }
|
||||
{ m.getShieldReinforcement() ? <div className={'l'}>{translate('total')}: {formats.int((m.getAmmo() + 1) * (m.getDuration() * m.getShieldReinforcement()))}{u.MJ}</div> : null }
|
||||
{ m.repair ? <div className={'l'}>{translate('repair')}: {m.repair}</div> : null }
|
||||
{ m.getFacingLimit() ? <div className={'l'}>{translate('facinglimit')} {formats.f1(m.getFacingLimit())}°</div> : null }
|
||||
{ m.getRange() ? <div className={'l'}>{translate('range')} {formats.f2(m.getRange())}{u.km}</div> : null }
|
||||
{ m.getRangeT() ? <div className={'l'}>{translate('ranget')} {formats.f1(m.getRangeT())}{u.s}</div> : null }
|
||||
{ m.getTime() ? <div className={'l'}>{translate('time')}: {formats.time(m.getTime())}</div> : null }
|
||||
{ m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
|
||||
{ m.rangeLS ? <div className={'l'}>{translate('range')}: {m.rangeLS}{u.Ls}</div> : null }
|
||||
{ m.rangeLS === null ? <div className={'l'}>∞{u.Ls}</div> : null }
|
||||
{ m.rangeRating ? <div className={'l'}>{translate('range')}: {m.rangeRating}</div> : null }
|
||||
{ m.maximum ? <div className={'l'}>{translate('max')}: {(m.maximum)}</div> : null }
|
||||
{ m.passengers ? <div className={'l'}>{translate('passengers')}: {m.passengers}</div> : null }
|
||||
{ m.getRegenerationRate() ? <div className='l'>{translate('regen')}: {formats.round1(m.getRegenerationRate())}{u.ps}</div> : null }
|
||||
{ m.getBrokenRegenerationRate() ? <div className='l'>{translate('brokenregen')}: {formats.round1(m.getBrokenRegenerationRate())}{u.ps}</div> : null }
|
||||
{ showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null }
|
||||
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
|
||||
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
|
||||
{ m.getHullReinforcement() ? <div className='l'>{translate('armour')}: {formats.int(m.getHullReinforcement() + ship.baseArmour * m.getModValue('hullboost') / 10000)}</div> : null }
|
||||
{ m.getProtection() ? <div className='l'>{translate('protection')}: {formats.rPct(m.getProtection())}</div> : null }
|
||||
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
|
||||
{ m && validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
|
||||
</div>
|
||||
</div>;
|
||||
} else {
|
||||
return <div className={'empty'}>{translate('empty')}</div>;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +1,46 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import SlotSection from './SlotSection';
|
||||
import InternalSlot from './InternalSlot';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import Slot from './Slot';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
import { canMount } from '../utils/SlotFunctions';
|
||||
import autoBind from 'auto-bind';
|
||||
import { TYPES } from 'ed-forge/lib/src/data/slots';
|
||||
|
||||
/**
|
||||
* Sets all empty slots of a ship to a item of the given size.
|
||||
* @param {Ship} ship Ship to set items for
|
||||
* @param {boolean} fillAll True to also fill occupied
|
||||
* @param {string} type Item type
|
||||
* @param {string} rating Item rating
|
||||
*/
|
||||
function setAllEmpty(ship, fillAll, type, rating = '') {
|
||||
ship.getModules(TYPES.ANY_INTERNAL, undefined, true).forEach((slot) => {
|
||||
if (slot.isEmpty() || fillAll) {
|
||||
try {
|
||||
// Maybe the item does not exist. Simply catch this error.
|
||||
slot.setItem(type, slot.getSize(), rating);
|
||||
} catch (e) {}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Internal slot section
|
||||
*/
|
||||
export default class InternalSlotSection extends SlotSection {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
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);
|
||||
this._fillWithArmor = this._fillWithArmor.bind(this);
|
||||
this._fillWithModuleReinforcementPackages = this._fillWithModuleReinforcementPackages.bind(this);
|
||||
this._fillWithFuelTanks = this._fillWithFuelTanks.bind(this);
|
||||
this._fillWithLuxuryCabins = this._fillWithLuxuryCabins.bind(this);
|
||||
this._fillWithFirstClassCabins = this._fillWithFirstClassCabins.bind(this);
|
||||
this._fillWithBusinessClassCabins = this._fillWithBusinessClassCabins.bind(this);
|
||||
this._fillWithEconomyClassCabins = this._fillWithEconomyClassCabins.bind(this);
|
||||
constructor(props) {
|
||||
super(props, 'optional internal');
|
||||
autoBind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty all slots
|
||||
*/
|
||||
_empty() {
|
||||
this.props.ship.emptyInternal();
|
||||
this.props.onChange();
|
||||
this.props.ship.getModules(TYPES.ANY_INTERNAL).forEach((slot) => slot.reset());
|
||||
this._close();
|
||||
}
|
||||
|
||||
@@ -45,14 +49,8 @@ export default class InternalSlotSection extends SlotSection {
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithCargo(event) {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if ((clobber || !slot.m) && canMount(ship, slot, 'cr')) {
|
||||
ship.use(slot, ModuleUtils.findInternal('cr', slot.maxClass, 'E'));
|
||||
}
|
||||
});
|
||||
this.props.onChange();
|
||||
const fillAll = event.getModifierState('Alt');
|
||||
setAllEmpty(this.props.ship, fillAll, 'cargorack');
|
||||
this._close();
|
||||
}
|
||||
|
||||
@@ -61,14 +59,8 @@ export default class InternalSlotSection extends SlotSection {
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithFuelTanks(event) {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if ((clobber || !slot.m) && canMount(ship, slot, 'ft')) {
|
||||
ship.use(slot, ModuleUtils.findInternal('ft', slot.maxClass, 'C'));
|
||||
}
|
||||
});
|
||||
this.props.onChange();
|
||||
const fillAll = event.getModifierState('Alt');
|
||||
setAllEmpty(this.props.ship, fillAll, 'fueltank', '3');
|
||||
this._close();
|
||||
}
|
||||
|
||||
@@ -77,14 +69,8 @@ export default class InternalSlotSection extends SlotSection {
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithLuxuryCabins(event) {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if ((clobber || !slot.m) && canMount(ship, slot, 'pcq')) {
|
||||
ship.use(slot, ModuleUtils.findInternal('pcq', Math.min(slot.maxClass, 6), 'B')); // Passenger cabins top out at 6
|
||||
}
|
||||
});
|
||||
this.props.onChange();
|
||||
const fillAll = event.getModifierState('Alt');
|
||||
setAllEmpty(this.props.ship, fillAll, 'passengercabins', '4');
|
||||
this._close();
|
||||
}
|
||||
|
||||
@@ -93,14 +79,8 @@ export default class InternalSlotSection extends SlotSection {
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithFirstClassCabins(event) {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if ((clobber || !slot.m) && canMount(ship, slot, 'pcm')) {
|
||||
ship.use(slot, ModuleUtils.findInternal('pcm', Math.min(slot.maxClass, 6), 'C')); // Passenger cabins top out at 6
|
||||
}
|
||||
});
|
||||
this.props.onChange();
|
||||
const fillAll = event.getModifierState('Alt');
|
||||
setAllEmpty(this.props.ship, fillAll, 'passengercabins', '3');
|
||||
this._close();
|
||||
}
|
||||
|
||||
@@ -109,14 +89,8 @@ export default class InternalSlotSection extends SlotSection {
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithBusinessClassCabins(event) {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if ((clobber || !slot.m) && canMount(ship, slot, 'pci')) {
|
||||
ship.use(slot, ModuleUtils.findInternal('pci', Math.min(slot.maxClass, 6), 'D')); // Passenger cabins top out at 6
|
||||
}
|
||||
});
|
||||
this.props.onChange();
|
||||
const fillAll = event.getModifierState('Alt');
|
||||
setAllEmpty(this.props.ship, fillAll, 'passengercabins', '2');
|
||||
this._close();
|
||||
}
|
||||
|
||||
@@ -125,14 +99,8 @@ export default class InternalSlotSection extends SlotSection {
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithEconomyClassCabins(event) {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if ((clobber || !slot.m) && canMount(ship, slot, 'pce')) {
|
||||
ship.use(slot, ModuleUtils.findInternal('pce', Math.min(slot.maxClass, 6), 'E')); // Passenger cabins top out at 6
|
||||
}
|
||||
});
|
||||
this.props.onChange();
|
||||
const fillAll = event.getModifierState('Alt');
|
||||
setAllEmpty(this.props.ship, fillAll, 'passengercabins', '1');
|
||||
this._close();
|
||||
}
|
||||
|
||||
@@ -141,17 +109,8 @@ export default class InternalSlotSection extends SlotSection {
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithCells(event) {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
let chargeCap = 0; // Capacity of single activation
|
||||
ship.internal.forEach(function(slot) {
|
||||
if ((clobber && !(slot.m && ModuleUtils.isShieldGenerator(slot.m.grp)) || !slot.m) && canMount(ship, slot, 'scb')) {
|
||||
ship.use(slot, ModuleUtils.findInternal('scb', slot.maxClass, 'A'));
|
||||
ship.setSlotEnabled(slot, chargeCap <= ship.shieldStrength); // Don't waste cell capacity on overcharge
|
||||
chargeCap += slot.m.recharge;
|
||||
}
|
||||
});
|
||||
this.props.onChange();
|
||||
const fillAll = event.getModifierState('Alt');
|
||||
setAllEmpty(this.props.ship, fillAll, 'scb', '5');
|
||||
this._close();
|
||||
}
|
||||
|
||||
@@ -160,14 +119,8 @@ export default class InternalSlotSection extends SlotSection {
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithArmor(event) {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if ((clobber || !slot.m) && canMount(ship, slot, 'hr')) {
|
||||
ship.use(slot, ModuleUtils.findInternal('hr', Math.min(slot.maxClass, 5), 'D')); // Hull reinforcements top out at 5D
|
||||
}
|
||||
});
|
||||
this.props.onChange();
|
||||
const fillAll = event.getModifierState('Alt');
|
||||
setAllEmpty(this.props.ship, fillAll, 'hrp', '2');
|
||||
this._close();
|
||||
}
|
||||
|
||||
@@ -176,55 +129,31 @@ export default class InternalSlotSection extends SlotSection {
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_fillWithModuleReinforcementPackages(event) {
|
||||
let clobber = event.getModifierState('Alt');
|
||||
let ship = this.props.ship;
|
||||
ship.internal.forEach((slot) => {
|
||||
if ((clobber || !slot.m) && canMount(ship, slot, 'mrp')) {
|
||||
ship.use(slot, ModuleUtils.findInternal('mrp', Math.min(slot.maxClass, 5), 'D')); // Module reinforcements top out at 5D
|
||||
}
|
||||
});
|
||||
this.props.onChange();
|
||||
const fillAll = event.getModifierState('Alt');
|
||||
setAllEmpty(this.props.ship, fillAll, 'mrp', '2');
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty all on section header right click
|
||||
*/
|
||||
_contextMenu() {
|
||||
this._empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the slot React Components
|
||||
* @return {Array} Array of Slots
|
||||
*/
|
||||
_getSlots() {
|
||||
let slots = [];
|
||||
let { currentMenu, ship } = this.props;
|
||||
let { currentMenu, ship, propsToShow, onPropToggle } = this.props;
|
||||
let { originSlot, targetSlot } = this.state;
|
||||
let { internal, fuelCapacity } = ship;
|
||||
let availableModules = ship.getAvailableModules();
|
||||
|
||||
for (let i = 0, l = internal.length; i < l; i++) {
|
||||
let s = internal[i];
|
||||
|
||||
slots.push(<InternalSlot
|
||||
key={i}
|
||||
maxClass={s.maxClass}
|
||||
availableModules={() => availableModules.getInts(ship, s.maxClass, s.eligible)}
|
||||
onOpen={this._openMenu.bind(this,s)}
|
||||
onChange={this.props.onChange}
|
||||
onSelect={this._selectModule.bind(this, s)}
|
||||
selected={currentMenu == s}
|
||||
enabled={s.enabled}
|
||||
eligible={s.eligible}
|
||||
m={s.m}
|
||||
drag={this._drag.bind(this, s)}
|
||||
dragOver={this._dragOverSlot.bind(this, s)}
|
||||
for (const m of ship.getInternals(undefined, true)) {
|
||||
slots.push(<Slot
|
||||
key={m.object.Slot}
|
||||
currentMenu={currentMenu}
|
||||
m={m}
|
||||
drag={this._drag.bind(this, m)}
|
||||
dragOver={this._dragOverSlot.bind(this, m)}
|
||||
drop={this._drop}
|
||||
dropClass={this._dropClass(s, originSlot, targetSlot)}
|
||||
fuel={fuelCapacity}
|
||||
ship={ship}
|
||||
dropClass={this._dropClass(m, originSlot, targetSlot)}
|
||||
propsToShow={propsToShow}
|
||||
onPropToggle={onPropToggle}
|
||||
/>);
|
||||
}
|
||||
|
||||
@@ -237,22 +166,23 @@ export default class InternalSlotSection extends SlotSection {
|
||||
* @param {Function} ship The ship
|
||||
* @return {React.Component} Section menu
|
||||
*/
|
||||
_getSectionMenu(translate, ship) {
|
||||
_getSectionMenu() {
|
||||
const { ship } = this.props;
|
||||
const { translate } = this.context.language;
|
||||
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}>{translate('empty all')}</li>
|
||||
<li className='lc' tabIndex='0' onClick={this._fillWithCargo}>{translate('cargo')}</li>
|
||||
<li className='lc' tabIndex='0' onClick={this._fillWithCells}>{translate('scb')}</li>
|
||||
<li className='lc' tabIndex='0' onClick={this._fillWithArmor}>{translate('hr')}</li>
|
||||
<li className='lc' tabIndex='0' onClick={this._fillWithModuleReinforcementPackages}>{translate('mrp')}</li>
|
||||
<li className='lc' tabIndex='0' onClick={this._fillWithFuelTanks}>{translate('ft')}</li>
|
||||
<li className='lc' tabIndex='0' onClick={this._fillWithEconomyClassCabins}>{translate('pce')}</li>
|
||||
<li className='lc' tabIndex='0' onClick={this._fillWithBusinessClassCabins}>{translate('pci')}</li>
|
||||
<li className='lc' tabIndex='0' onClick={this._fillWithFirstClassCabins} onKeyDown={ship.luxuryCabins ? '' : this._keyDown}>{translate('pcm')}</li>
|
||||
{ ship.readMeta('luxuryCabins') ? <li className='lc' tabIndex='0' onClick={this._fillWithLuxuryCabins}>{translate('pcq')}</li> : ''}
|
||||
<li className='optional-hide' style={{ textAlign: 'center', marginTop: '1em' }}>{translate('PHRASE_ALT_ALL')}</li>
|
||||
</ul>
|
||||
</div>;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import { nameComparator } from '../utils/SlotFunctions';
|
||||
import LineChart from '../components/LineChart';
|
||||
import Slider from '../components/Slider';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import Module from '../shipyard/Module';
|
||||
import * as Calc from '../shipyard/Calculations';
|
||||
|
||||
/**
|
||||
* Jump range for a given ship
|
||||
*/
|
||||
export default class JumpRange extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
ship: PropTypes.object.isRequired,
|
||||
code: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
|
||||
const ship = this.props.ship;
|
||||
|
||||
this.state = {
|
||||
fuelLevel: 1,
|
||||
calcJumpRangeFunc: this._calcJumpRange.bind(this, ship)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state if our ship changes
|
||||
* @param {Object} nextProps Incoming/Next properties
|
||||
* @param {Object} nextContext Incoming/Next conext
|
||||
* @return {boolean} Returns true if the component should be rerendered
|
||||
*/
|
||||
componentWillReceiveProps(nextProps, nextContext) {
|
||||
if (nextProps.code != this.props.code) {
|
||||
this.setState({ fuelLevel: 1,
|
||||
calcJumpRangeFunc: this._calcJumpRange.bind(this, nextProps.ship) });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the jump range this ship at a given cargo
|
||||
* @param {Object} ship The ship
|
||||
* @param {Object} cargo The cargo
|
||||
* @return {number} The jump range
|
||||
*/
|
||||
_calcJumpRange(ship, cargo) {
|
||||
// Obtain the FSD for this ship
|
||||
const fsd = ship.standard[2].m;
|
||||
|
||||
const fuel = this.state.fuelLevel * ship.fuelCapacity;
|
||||
|
||||
// Obtain the jump range
|
||||
return Calc.jumpRange(ship.unladenMass + fuel + cargo, fsd, fuel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update fuel level
|
||||
* @param {number} fuelLevel Fuel level 0 - 1
|
||||
*/
|
||||
_fuelChange(fuelLevel) {
|
||||
this.setState({
|
||||
fuelLevel,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Render engine profile
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
||||
const { formats, translate, units } = language;
|
||||
const { ship } = this.props;
|
||||
const { fuelLevel } = this.state;
|
||||
|
||||
const code = ship.toString() + '.' + ship.getModificationsString() + '.' + fuelLevel;
|
||||
|
||||
return (
|
||||
<span>
|
||||
<h1>{translate('jump range')}</h1>
|
||||
<LineChart
|
||||
xMax={ship.cargoCapacity}
|
||||
yMax={ship.unladenRange}
|
||||
xLabel={translate('cargo')}
|
||||
xUnit={translate('T')}
|
||||
yLabel={translate('jump range')}
|
||||
yUnit={translate('LY')}
|
||||
func={this.state.calcJumpRangeFunc}
|
||||
points={200}
|
||||
code={code}
|
||||
/>
|
||||
<h3>{translate('fuel carried')}: {formats.f2(fuelLevel * ship.fuelCapacity)}{units.T}</h3>
|
||||
<table style={{ width: '100%', lineHeight: '1em', backgroundColor: 'transparent' }}>
|
||||
<tbody >
|
||||
<tr>
|
||||
<td>
|
||||
<Slider
|
||||
axis={true}
|
||||
onChange={this._fuelChange.bind(this)}
|
||||
axisUnit={translate('T')}
|
||||
percent={fuelLevel}
|
||||
max={ship.fuelCapacity}
|
||||
scale={sizeRatio}
|
||||
onResize={onWindowResize}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,283 +1,275 @@
|
||||
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';
|
||||
import autoBind from 'auto-bind';
|
||||
|
||||
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);
|
||||
autoBind(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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { shallowEqual } from '../utils/UtilityFunctions';
|
||||
* Link wrapper component
|
||||
*/
|
||||
export default class Link extends React.Component {
|
||||
|
||||
static propTypes = {
|
||||
children: PropTypes.any,
|
||||
href: PropTypes.string.isRequired,
|
||||
@@ -56,5 +55,4 @@ export default class Link extends React.Component {
|
||||
render() {
|
||||
return <a {...this.props} onClick={this.handler}>{this.props.children}</a>;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ function buildComparator(a, b) {
|
||||
* Compare builds modal
|
||||
*/
|
||||
export default class ModalCompare extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
builds: PropTypes.array
|
||||
@@ -105,8 +104,8 @@ export default class ModalCompare extends TranslatedComponent {
|
||||
|
||||
let selectedBuilds = usedBuilds.map((build, i) =>
|
||||
<tr key={i} onClick={this._removeBuild.bind(this, i)}>
|
||||
<td className='tl'>{build.name}</td><
|
||||
td className='tl'>{build.buildName}</td>
|
||||
<td className='tl'>{build.name}</td>
|
||||
<td className='tl'>{build.buildName}</td>
|
||||
</tr>
|
||||
);
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import Persist from '../stores/Persist';
|
||||
* Delete All saved data modal
|
||||
*/
|
||||
export default class ModalDeleteAll extends TranslatedComponent {
|
||||
|
||||
/**
|
||||
* Delete everything and hide the modal
|
||||
*/
|
||||
|
||||
@@ -6,7 +6,6 @@ import TranslatedComponent from './TranslatedComponent';
|
||||
* Export Modal
|
||||
*/
|
||||
export default class ModalExport extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
title: PropTypes.string,
|
||||
generator: PropTypes.func,
|
||||
|
||||
@@ -7,19 +7,10 @@ import TranslatedComponent from './TranslatedComponent';
|
||||
* Help Modal
|
||||
*/
|
||||
export default class ModalHelp extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
title: PropTypes.string
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the modal
|
||||
* @return {React.Component} Modal Content
|
||||
|
||||
@@ -2,90 +2,23 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import Router from '../Router';
|
||||
import Persist from '../stores/Persist';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import Ship from '../shipyard/Ship';
|
||||
import { ModuleNameToGroup, Insurance } from '../shipyard/Constants';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import { fromDetailedBuild } from '../shipyard/Serializer';
|
||||
import { Download } from './SvgIcons';
|
||||
import { outfitURL } from '../utils/UrlGenerators';
|
||||
import * as CompanionApiUtils from '../utils/CompanionApiUtils';
|
||||
import autoBind from 'auto-bind';
|
||||
import { isArray } from 'lodash';
|
||||
import { Ship } from 'ed-forge';
|
||||
|
||||
const textBuildRegex = new RegExp('^\\[([\\w \\-]+)\\]\n');
|
||||
const lineRegex = new RegExp('^([\\dA-Z]{1,2}): (\\d)([A-I])[/]?([FGT])?([SD])? ([\\w\\- ]+)');
|
||||
const mountMap = { 'H': 4, 'L': 3, 'M': 2, 'S': 1, 'U': 0 };
|
||||
const standardMap = { 'RB': 0, 'TM': 1, 'FH': 2, 'EC': 3, 'PC': 4, 'SS': 5, 'FS': 6 };
|
||||
const bhMap = { 'lightweight alloy': 0, 'reinforced alloy': 1, 'military grade composite': 2, 'mirrored surface composite': 3, 'reactive surface composite': 4 };
|
||||
|
||||
/**
|
||||
* Check is slot is empty
|
||||
* @param {Object} slot Slot model
|
||||
* @return {Boolean} True if empty
|
||||
*/
|
||||
function isEmptySlot(slot) {
|
||||
return slot.maxClass == this && slot.m === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a build is valid
|
||||
* @param {string} shipId Ship ID
|
||||
* @param {string} code Serialzied ship build 'code'
|
||||
* @param {string} name Build name
|
||||
* @throws {string} If build is not valid
|
||||
*/
|
||||
function validateBuild(shipId, code, name) {
|
||||
let shipData = Ships[shipId];
|
||||
|
||||
if (!shipData) {
|
||||
throw '"' + shipId + '" is not a valid Ship Id!';
|
||||
}
|
||||
if (typeof name != 'string' || name.length == 0) {
|
||||
throw shipData.properties.name + ' build "' + name + '" must be a string at least 1 character long!';
|
||||
}
|
||||
if (typeof code != 'string' || code.length < 10) {
|
||||
throw shipData.properties.name + ' build "' + name + '" is not valid!';
|
||||
}
|
||||
try {
|
||||
let ship = new Ship(shipId, shipData.properties, shipData.slots);
|
||||
ship.buildFrom(code);
|
||||
} catch (e) {
|
||||
throw shipData.properties.name + ' build "' + name + '" is not valid!';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a ship-loadout JSON object to a Coriolis build
|
||||
* @param {Object} detailedBuild ship-loadout
|
||||
* @return {Object} Coriolis build
|
||||
*/
|
||||
function detailedJsonToBuild(detailedBuild) {
|
||||
let ship;
|
||||
if (!detailedBuild.name) {
|
||||
throw 'Build Name missing!';
|
||||
}
|
||||
|
||||
if (!detailedBuild.name.trim()) {
|
||||
throw 'Build Name must be a string at least 1 character long!';
|
||||
}
|
||||
|
||||
try {
|
||||
ship = fromDetailedBuild(detailedBuild);
|
||||
} catch (e) {
|
||||
throw detailedBuild.ship + ' Build "' + detailedBuild.name + '": Invalid data';
|
||||
}
|
||||
|
||||
return { shipId: ship.id, name: detailedBuild.name, code: ship.toString() };
|
||||
}
|
||||
const STATE = {
|
||||
READY: 0,
|
||||
PARSED: 1,
|
||||
ERROR: 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* Import Modal
|
||||
*/
|
||||
export default class ModalImport extends TranslatedComponent {
|
||||
|
||||
|
||||
static propTypes = {
|
||||
importString: PropTypes.string, // Optional: Default data for import modal
|
||||
builds: PropTypes.object, // Optional: Import object
|
||||
};
|
||||
|
||||
@@ -95,207 +28,36 @@ export default class ModalImport extends TranslatedComponent {
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
autoBind(this);
|
||||
|
||||
this.state = {
|
||||
builds: props.builds,
|
||||
canEdit: !props.builds,
|
||||
comparisons: null,
|
||||
shipDiscount: null,
|
||||
moduleDiscount: null,
|
||||
errorMsg: null,
|
||||
importString: null,
|
||||
importValid: false,
|
||||
insurance: null
|
||||
status: STATE.READY,
|
||||
builds: props.builds || [],
|
||||
};
|
||||
|
||||
this._process = this._process.bind(this);
|
||||
this._import = this._import.bind(this);
|
||||
this._importBackup = this._importBackup.bind(this);
|
||||
this._importDetailedArray = this._importDetailedArray.bind(this);
|
||||
this._importTextBuild = this._importTextBuild.bind(this);
|
||||
this._importCompanionApiBuild = this._importCompanionApiBuild.bind(this);
|
||||
this._validateImport = this._validateImport.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a Coriolis backup
|
||||
* @param {Object} importData Backup Data
|
||||
* @throws {string} If import fails
|
||||
*/
|
||||
_importBackup(importData) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
this.setState({ builds: importData.builds });
|
||||
} else {
|
||||
throw 'builds must be an object!';
|
||||
}
|
||||
if (importData.comparisons) {
|
||||
for (let compName in importData.comparisons) {
|
||||
let comparison = importData.comparisons[compName];
|
||||
for (let i = 0, l = comparison.builds.length; i < l; i++) {
|
||||
let build = comparison.builds[i];
|
||||
if (!importData.builds[build.shipId] || !importData.builds[build.shipId][build.buildName]) {
|
||||
throw build.shipId + ' build "' + build.buildName + '" data is missing!';
|
||||
}
|
||||
}
|
||||
}
|
||||
this.setState({ comparisons: importData.comparisons });
|
||||
}
|
||||
// Check for old/deprecated discounts
|
||||
if (importData.discounts instanceof Array && importData.discounts.length == 2) {
|
||||
this.setState({ shipDiscount: importData.discounts[0], moduleDiscount: importData.discounts[1] });
|
||||
}
|
||||
// Check for ship discount
|
||||
if (!isNaN(importData.shipDiscount)) {
|
||||
this.setState({ shipDiscount: importData.shipDiscount * 1 });
|
||||
}
|
||||
// Check for module discount
|
||||
if (!isNaN(importData.moduleDiscount)) {
|
||||
this.setState({ shipDiscount: importData.moduleDiscount * 1 });
|
||||
}
|
||||
|
||||
if (typeof importData.insurance == 'string') {
|
||||
let insurance = importData.insurance.toLowerCase();
|
||||
|
||||
if (Insurance[insurance] !== undefined) {
|
||||
this.setState({ insurance });
|
||||
} else {
|
||||
throw 'Invalid insurance type: ' + insurance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Import an array of ship-loadout objects / builds
|
||||
* @param {Array} importArr Array of ship-loadout JSON Schema builds
|
||||
*/
|
||||
_importDetailedArray(importArr) {
|
||||
let builds = {};
|
||||
for (let i = 0, l = importArr.length; i < l; i++) {
|
||||
let build = detailedJsonToBuild(importArr[i]);
|
||||
if (!builds[build.shipId]) {
|
||||
builds[build.shipId] = {};
|
||||
}
|
||||
builds[build.shipId][build.name] = build.code;
|
||||
}
|
||||
this.setState({ builds });
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a build direct from the companion API
|
||||
* @param {string} build JSON from the companion API information
|
||||
* @throws {string} if parse/import fails
|
||||
*/
|
||||
_importCompanionApiBuild(build) {
|
||||
const shipModel = CompanionApiUtils.shipModelFromJson(build);
|
||||
const ship = CompanionApiUtils.shipFromJson(build);
|
||||
|
||||
let builds = {};
|
||||
builds[shipModel] = {};
|
||||
builds[shipModel]['Imported ' + Ships[shipModel].properties.name] = ship.toString();
|
||||
this.setState({ builds, singleBuild: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a text build from ED Shipyard
|
||||
* @param {string} buildStr Build string
|
||||
* Import SLEF formatted builds. Sets state to a map of the builds on success
|
||||
* and flags if there was only a single build.
|
||||
*
|
||||
* @param {string} importData - Array of the list of builds.
|
||||
* @throws {string} If parse / import fails
|
||||
*/
|
||||
_importTextBuild(buildStr) {
|
||||
let buildName = textBuildRegex.exec(buildStr)[1].trim();
|
||||
let shipName = buildName.toLowerCase();
|
||||
let shipId = null;
|
||||
_importSlefBuilds(importData) {
|
||||
const builds = importData.reduce((memo, { data }) => {
|
||||
const shipModel = shipModelFromJson(data);
|
||||
const ship = shipFromLoadoutJSON(data);
|
||||
const shipTemplate = Ships[shipModel];
|
||||
const shipName = data.ShipName || shipTemplate.properties.name;
|
||||
|
||||
for (let sId in Ships) {
|
||||
if (Ships[sId].properties.name.toLowerCase() == shipName) {
|
||||
shipId = sId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const key = `Imported ${shipName}`;
|
||||
memo[shipModel] = {};
|
||||
memo[shipModel][key] = ship.toString();
|
||||
|
||||
if (!shipId) {
|
||||
throw 'No such ship found: "' + buildName + '"';
|
||||
}
|
||||
return memo;
|
||||
}, {});
|
||||
|
||||
let lines = buildStr.split('\n');
|
||||
let ship = new Ship(shipId, Ships[shipId].properties, Ships[shipId].slots);
|
||||
ship.buildWith(null);
|
||||
|
||||
for (let i = 1; i < lines.length; i++) {
|
||||
let line = lines[i].trim();
|
||||
|
||||
if (!line) { continue; }
|
||||
if (line.substring(0, 3) == '---') { break; }
|
||||
|
||||
let parts = lineRegex.exec(line);
|
||||
|
||||
if (!parts) { throw 'Error parsing: "' + line + '"'; }
|
||||
|
||||
let typeSize = parts[1];
|
||||
let cl = parts[2];
|
||||
let rating = parts[3];
|
||||
let mount = parts[4];
|
||||
let missile = parts[5];
|
||||
let name = parts[6].trim();
|
||||
let slot, group;
|
||||
|
||||
if (isNaN(typeSize)) { // Standard or Hardpoint
|
||||
if (typeSize.length == 1) { // Hardpoint
|
||||
let slotClass = mountMap[typeSize];
|
||||
|
||||
if (cl > slotClass) { throw cl + rating + ' ' + name + ' exceeds slot size: "' + line + '"'; }
|
||||
|
||||
slot = ship.hardpoints.find(isEmptySlot, slotClass);
|
||||
|
||||
if (!slot) { throw 'No hardpoint slot available for: "' + line + '"'; }
|
||||
|
||||
group = ModuleNameToGroup[name.toLowerCase()];
|
||||
|
||||
let hp = ModuleUtils.findHardpoint(group, cl, rating, group ? null : name, mount, missile);
|
||||
|
||||
if (!hp) { throw 'Unknown component: "' + line + '"'; }
|
||||
|
||||
ship.use(slot, hp, true);
|
||||
} else if (typeSize == 'BH') {
|
||||
let bhId = bhMap[name.toLowerCase()];
|
||||
|
||||
if (bhId === undefined) { throw 'Unknown bulkhead: "' + line + '"'; }
|
||||
|
||||
ship.useBulkhead(bhId, true);
|
||||
} else if (standardMap[typeSize] != undefined) {
|
||||
let standardIndex = standardMap[typeSize];
|
||||
|
||||
if (ship.standard[standardIndex].maxClass < cl) { throw name + ' exceeds max class for the ' + ship.name; }
|
||||
|
||||
ship.use(ship.standard[standardIndex], ModuleUtils.standard(standardIndex, cl + rating), true);
|
||||
} else {
|
||||
throw 'Unknown component: "' + line + '"';
|
||||
}
|
||||
} else {
|
||||
if (cl > typeSize) { throw cl + rating + ' ' + name + ' exceeds slot size: "' + line + '"'; }
|
||||
|
||||
slot = ship.internal.find(isEmptySlot, typeSize);
|
||||
|
||||
if (!slot) { throw 'No internal slot available for: "' + line + '"'; }
|
||||
|
||||
group = ModuleNameToGroup[name.toLowerCase()];
|
||||
|
||||
let intComp = ModuleUtils.findInternal(group, cl, rating, group ? null : name);
|
||||
|
||||
if (!intComp) { throw 'Unknown component: "' + line + '"'; }
|
||||
|
||||
ship.use(slot, intComp);
|
||||
}
|
||||
}
|
||||
|
||||
let builds = {};
|
||||
builds[shipId] = {};
|
||||
builds[shipId]['Imported ' + buildName] = ship.toString();
|
||||
this.setState({ builds, singleBuild: true });
|
||||
this.setState({ builds, singleBuild: Object.keys(builds).length === 1 });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -303,158 +65,50 @@ export default class ModalImport extends TranslatedComponent {
|
||||
* @param {SyntheticEvent} event Event
|
||||
* @throws {string} If validation fails
|
||||
*/
|
||||
_validateImport(event) {
|
||||
let importData = null;
|
||||
let importString = event.target.value.trim();
|
||||
this.setState({
|
||||
builds: null,
|
||||
comparisons: null,
|
||||
shipDiscount: null,
|
||||
moduleDiscount: null,
|
||||
errorMsg: null,
|
||||
importValid: false,
|
||||
insurance: null,
|
||||
singleBuild: false,
|
||||
importString,
|
||||
});
|
||||
|
||||
if (!importString) {
|
||||
return;
|
||||
}
|
||||
|
||||
_parse(event) {
|
||||
const importString = event.target.value.trim();
|
||||
try {
|
||||
if (textBuildRegex.test(importString)) { // E:D Shipyard build text
|
||||
this._importTextBuild(importString);
|
||||
} else { // JSON Build data
|
||||
importData = JSON.parse(importString);
|
||||
|
||||
if (!importData || typeof importData != 'object') {
|
||||
throw 'Must be an object or array!';
|
||||
}
|
||||
|
||||
if (importData.modules != null && importData.modules.Armour != null) { // Only the companion API has this information
|
||||
this._importCompanionApiBuild(importData); // Single sihp definition
|
||||
} else if (importData.ship != null && importData.ship.modules != null && importData.ship.modules.Armour != null) { // Only the companion API has this information
|
||||
this._importCompanionApiBuild(importData.ship); // Complete API dump
|
||||
} else if (importData instanceof Array) { // Must be detailed export json
|
||||
this._importDetailedArray(importData);
|
||||
} else if (importData.ship && typeof importData.name !== undefined) { // Using JSON from a single ship build export
|
||||
this._importDetailedArray([importData]); // Convert to array with singleobject
|
||||
this.setState({ singleBuild: true });
|
||||
} else { // Using Backup JSON
|
||||
this._importBackup(importData);
|
||||
}
|
||||
let data = JSON.parse(importString);
|
||||
if (!isArray(data)) {
|
||||
data = [data];
|
||||
}
|
||||
} catch (e) {
|
||||
// console.log(e.stack);
|
||||
this.setState({ errorMsg: (typeof e == 'string') ? e : 'Cannot Parse the data!' });
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ importValid: true });
|
||||
};
|
||||
const ships = data.map((item) => {
|
||||
try {
|
||||
return new Ship(item.data ? item.data : item);
|
||||
} catch (err) {
|
||||
return err;
|
||||
}
|
||||
});
|
||||
this.setState({ ships, status: STATE.PARSED });
|
||||
} catch (err) {
|
||||
this.setState({ err, status: STATE.ERROR });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process imported data
|
||||
*/
|
||||
_process() {
|
||||
let builds = null, comparisons = null;
|
||||
|
||||
// If only importing a single build go straight to the outfitting page
|
||||
if (this.state.singleBuild) {
|
||||
builds = this.state.builds;
|
||||
let shipId = Object.keys(builds)[0];
|
||||
let name = Object.keys(builds[shipId])[0];
|
||||
Router.go(outfitURL(shipId, builds[shipId][name], name));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (this.state.builds) {
|
||||
builds = {}; // Create new builds object such that orginal name retained, but can be renamed
|
||||
for (let shipId in this.state.builds) {
|
||||
let shipbuilds = this.state.builds[shipId];
|
||||
builds[shipId] = {};
|
||||
for (let buildName in shipbuilds) {
|
||||
builds[shipId][buildName] = {
|
||||
code: shipbuilds[buildName],
|
||||
useName: buildName
|
||||
};
|
||||
}
|
||||
for (const build of this.state.builds) {
|
||||
if (!build instanceof Error) {
|
||||
Persist.saveBuild(build.Ship, build.CoriolisBuildName || build.ShipName, build.compress());
|
||||
}
|
||||
}
|
||||
|
||||
if (this.state.comparisons) {
|
||||
comparisons = {};
|
||||
for (let name in this.state.comparisons) {
|
||||
comparisons[name] = Object.assign({ useName: name }, this.state.comparisons[name]);
|
||||
}
|
||||
}
|
||||
|
||||
this.setState({ processed: true, builds, comparisons });
|
||||
};
|
||||
|
||||
/**
|
||||
* Import parsed, processed data and save
|
||||
*/
|
||||
_import() {
|
||||
let state = this.state;
|
||||
if (state.builds) {
|
||||
let builds = state.builds;
|
||||
for (let shipId in builds) {
|
||||
for (let buildName in builds[shipId]) {
|
||||
let build = builds[shipId][buildName];
|
||||
let name = build.useName.trim();
|
||||
if (name) {
|
||||
Persist.saveBuild(shipId, name, build.code);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state.comparisons) {
|
||||
let comparisons = state.comparisons;
|
||||
for (let comp in comparisons) {
|
||||
let comparison = comparisons[comp];
|
||||
let useName = comparison.useName.trim();
|
||||
if (useName) {
|
||||
Persist.saveComparison(useName, comparison.builds, comparison.facets);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (state.shipDiscount !== undefined) {
|
||||
Persist.setShipDiscount(state.shipDiscount);
|
||||
}
|
||||
if (state.moduleDiscount !== undefined) {
|
||||
Persist.setModuleDiscount(state.moduleDiscount);
|
||||
}
|
||||
|
||||
if (state.insurance) {
|
||||
Persist.setInsurance(state.insurance);
|
||||
}
|
||||
|
||||
this.context.hideModal();
|
||||
};
|
||||
this.setState({ builds: [], status: STATE.READY });
|
||||
}
|
||||
|
||||
/**
|
||||
* Capture build name changes
|
||||
* @param {Object} item Build/Comparison import object
|
||||
* @param {SyntheticEvent} e Event
|
||||
* @param {Object} index Build/Comparison import object
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_changeName(item, e) {
|
||||
item.useName = e.target.value;
|
||||
this.forceUpdate();
|
||||
_changeName(index, event) {
|
||||
const { builds } = this.state;
|
||||
builds[index].CoriolisBuildName = event.target.value.trim();
|
||||
this.setState({ builds });
|
||||
}
|
||||
|
||||
/**
|
||||
* If imported data is already provided process immediately on mount
|
||||
*/
|
||||
componentWillMount() {
|
||||
if (this.props.builds) {
|
||||
this._process();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* If textarea is shown focus on mount
|
||||
*/
|
||||
@@ -469,100 +123,54 @@ export default class ModalImport extends TranslatedComponent {
|
||||
* @return {React.Component} Modal contents
|
||||
*/
|
||||
render() {
|
||||
let translate = this.context.language.translate;
|
||||
let state = this.state;
|
||||
let importStage;
|
||||
const { translate } = this.context.language;
|
||||
const { status, builds, err } = this.state;
|
||||
|
||||
if (!state.processed) {
|
||||
importStage = (
|
||||
<div>
|
||||
<textarea className='cb json' ref={node => this.importField = node} onChange={this._validateImport} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} />
|
||||
<button id='proceed' className='l cap' onClick={this._process} disabled={!state.importValid} >{translate('proceed')}</button>
|
||||
<div className='l warning' style={{ marginLeft:'3em' }}>{state.errorMsg}</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
let comparisonTable, edit, buildRows = [];
|
||||
if (state.comparisons) {
|
||||
let comparisonRows = [];
|
||||
|
||||
for (let name in state.comparisons) {
|
||||
let comparison = state.comparisons[name];
|
||||
let hasComparison = Persist.hasComparison(comparison.useName);
|
||||
comparisonRows.push(
|
||||
<tr key={name} className='cb'>
|
||||
<td>
|
||||
<input type='text' onChange={this._changeName.bind(this, comparison)} value={comparison.useName}/>
|
||||
</td>
|
||||
<td style={{ textAlign:'center' }} className={ cn('cap', { warning: hasComparison, disabled: comparison.useName == '' }) }>
|
||||
{translate(comparison.useName == '' ? 'skip' : (hasComparison ? 'overwrite' : 'create'))}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
comparisonTable = (
|
||||
<table className='l' style={{ overflow:'hidden', margin: '1em 0', width: '100%' }} >
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ textAlign:'left' }}>{translate('comparison')}</th>
|
||||
<th>{translate('action')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{comparisonRows}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
const buildRows = builds.map((build, i) => {
|
||||
if (build instanceof Error) {
|
||||
return <tr key={i} className='cb'>
|
||||
<td colSpan={3} className='warning'>Error: {build.name}</td>
|
||||
</tr>;
|
||||
}
|
||||
|
||||
if(this.state.canEdit) {
|
||||
edit = <button className='l cap' style={{ marginLeft: '2em' }} onClick={() => this.setState({ processed: false })}>{translate('edit data')}</button>;
|
||||
}
|
||||
|
||||
let builds = this.state.builds;
|
||||
for (let shipId in builds) {
|
||||
let shipBuilds = builds[shipId];
|
||||
for (let buildName in shipBuilds) {
|
||||
let b = shipBuilds[buildName];
|
||||
let hasBuild = Persist.hasBuild(shipId, b.useName);
|
||||
buildRows.push(
|
||||
<tr key={shipId + buildName} className='cb'>
|
||||
<td>{Ships[shipId].properties.name}</td>
|
||||
<td><input type='text' onChange={this._changeName.bind(this, b)} value={b.useName}/></td>
|
||||
<td style={{ textAlign: 'center' }} className={cn('cap', { warning: hasBuild, disabled: b.useName == '' })}>
|
||||
{translate(b.useName == '' ? 'skip' : (hasBuild ? 'overwrite' : 'create'))}
|
||||
</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
importStage = (
|
||||
<div>
|
||||
<table className='l' style={{ overflow:'hidden', margin: '1em 0', width: '100%' }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ textAlign: 'left' }} >{translate('ship')}</th>
|
||||
<th style={{ textAlign: 'left' }} >{translate('build name')}</th>
|
||||
<th>{translate('action')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{buildRows}
|
||||
</tbody>
|
||||
</table>
|
||||
{comparisonTable}
|
||||
<button id='import' className='cl l' onClick={this._import}><Download/> {translate('import')}</button>
|
||||
{edit}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const exists = Persist.hasBuild(build.Ship, build.CoriolisBuildName);
|
||||
const saveName = build.CoriolisBuildName || build.ShipName;
|
||||
return <tr key={i} className='cb'>
|
||||
<td>{translate(build.Ship)}</td>
|
||||
<td><input type='text' onChange={this._changeName.bind(this, i)} value={saveName}/></td>
|
||||
<td style={{ textAlign: 'center' }} className={cn('cap', { warning: exists, disabled: saveName === '' })}>
|
||||
{translate(saveName === '' ? 'skip' : (exists ? 'overwrite' : 'create'))}
|
||||
</td>
|
||||
</tr>;
|
||||
});
|
||||
|
||||
return <div className='modal' onClick={ (e) => e.stopPropagation() }>
|
||||
<h2 >{translate('import')}</h2>
|
||||
{importStage}
|
||||
<button className={'r dismiss cap'} onClick={this.context.hideModal}>{translate('close')}</button>
|
||||
<div>
|
||||
<textarea spellCheck={false} className='cb json' ref={node => this.importField = node} onChange={this._parse} defaultValue={this.state.importString} placeholder={translate('PHRASE_IMPORT')} />
|
||||
{status === STATE.ERROR && <div className='l warning' style={{ marginLeft:'3em' }}>{err.toString()}</div>}
|
||||
</div>
|
||||
{builds.length && <div>
|
||||
<table className='l' style={{ overflow:'hidden', margin: '1em 0', width: '100%' }}>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ textAlign: 'left' }} >{translate('ship')}</th>
|
||||
<th style={{ textAlign: 'left' }} >{translate('build name')}</th>
|
||||
<th>{translate('action')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{buildRows}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>}
|
||||
<button id='proceed' className='l cap' onClick={this._process}
|
||||
disabled={status !== STATE.PARSED} >
|
||||
{translate('proceed')}
|
||||
</button>
|
||||
<button className={'r dismiss cap'} onClick={this.context.hideModal}>
|
||||
{translate('close')}
|
||||
</button>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import ShortenUrl from '../utils/ShortenUrl';
|
||||
* Permalink modal
|
||||
*/
|
||||
export default class ModalPermalink extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
url: PropTypes.string.isRequired
|
||||
};
|
||||
@@ -50,6 +49,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>;
|
||||
}
|
||||
|
||||
@@ -3,63 +3,52 @@ import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import cn from 'classnames';
|
||||
import NumberEditor from 'react-number-editor';
|
||||
import { Module } from 'ed-forge';
|
||||
|
||||
/**
|
||||
* Modification
|
||||
*/
|
||||
export default class Modification extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
ship: PropTypes.object.isRequired,
|
||||
m: PropTypes.object.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.number.isRequired,
|
||||
onChange: PropTypes.func.isRequired
|
||||
highlight: PropTypes.bool,
|
||||
m: PropTypes.instanceOf(Module).isRequired,
|
||||
property: PropTypes.string.isRequired,
|
||||
onSet: PropTypes.func.isRequired,
|
||||
showProp: PropTypes.object,
|
||||
onPropToggle: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
this.state.value = props.value;
|
||||
const { m, property, showProp } = props;
|
||||
const { beneficial, unit, value } = m.getFormatted(property, true);
|
||||
this.state = { beneficial, unit, value, showProp };
|
||||
}
|
||||
|
||||
/**
|
||||
* Update modification given a value.
|
||||
* @param {Number} value The value to set. This comes in as a string and must be stored in state as a string,
|
||||
* because it needs to allow illegal 'numbers' ('-', '1.', etc) when the user is typing
|
||||
* in a value by hand
|
||||
*/
|
||||
_updateValue(value) {
|
||||
const name = this.props.name;
|
||||
|
||||
let scaledValue = Math.round(Number(value) * 100);
|
||||
// Limit to +1000% / -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);
|
||||
|
||||
this.setState({ value });
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when an update to slider value is finished i.e. when losing focus
|
||||
* Notify listeners that a new value has been entered and commited.
|
||||
*/
|
||||
_updateFinished() {
|
||||
this.props.onChange();
|
||||
const { onSet, m, property } = this.props;
|
||||
const { inputValue } = this.state;
|
||||
const numValue = Number(inputValue);
|
||||
if (!isNaN(numValue) && this.state.value !== numValue) {
|
||||
onSet(property, numValue);
|
||||
const { beneficial, unit, value } = m.getFormatted(property, true);
|
||||
this.setState({ beneficial, unit, value });
|
||||
}
|
||||
}
|
||||
|
||||
_toggleProperty() {
|
||||
const { onPropToggle, property } = this.props;
|
||||
const showProp = !this.state.showProp;
|
||||
// TODO: defer until menu closed
|
||||
onPropToggle(property, showProp);
|
||||
this.setState({ showProp });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -67,29 +56,53 @@ export default class Modification extends TranslatedComponent {
|
||||
* @return {React.Component} modification
|
||||
*/
|
||||
render() {
|
||||
let translate = this.context.language.translate;
|
||||
let { m, name } = this.props;
|
||||
const { formats } = this.context.language;
|
||||
const { highlight, m, property } = this.props;
|
||||
const { beneficial, unit, value, inputValue, showProp } = this.state;
|
||||
|
||||
if (name === 'damagedist') {
|
||||
// We don't show damage distribution
|
||||
// Some features only apply to specific modules; these features will be
|
||||
// undefined on items that do not belong to the same class. Filter these
|
||||
// features here
|
||||
if (value === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let symbol;
|
||||
if (name === 'jitter') {
|
||||
symbol = '°';
|
||||
} else if (name !== 'burst' && name != 'burstrof') {
|
||||
symbol = '%';
|
||||
}
|
||||
if (symbol) {
|
||||
symbol = ' (' + symbol + ')';
|
||||
}
|
||||
|
||||
const { value: modifierValue, unit: modifierUnit } = m.getModifierFormatted(property);
|
||||
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>
|
||||
<tr>
|
||||
<td>
|
||||
<span>
|
||||
<input type="checkbox" checked={showProp} onClick={() => this._toggleProperty()}/>
|
||||
</span>
|
||||
</td>
|
||||
<td className="input-container">
|
||||
<span>
|
||||
<NumberEditor value={inputValue || value} stepModifier={1}
|
||||
decimals={2} step={0.01} style={{ textAlign: 'right', width: '100%' }}
|
||||
className={cn('cb', { 'greyed-out': !highlight })}
|
||||
onKeyDown={(event) => {
|
||||
if (event.key == 'Enter') {
|
||||
this._updateFinished();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}}
|
||||
onValueChange={(inputValue) => {
|
||||
if (inputValue.length <= 15) {
|
||||
this.setState({ inputValue });
|
||||
}
|
||||
}} />
|
||||
</span>
|
||||
</td>
|
||||
<td style={{ textAlign: 'left' }}>
|
||||
<span className="unit-container">{unit}</span>
|
||||
</td>
|
||||
<td style={{ textAlign: 'center' }}
|
||||
className={cn({
|
||||
secondary: beneficial,
|
||||
warning: beneficial === false,
|
||||
})}
|
||||
>{formats.f2(modifierValue)}{modifierUnit || ''}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import * as _ from 'lodash';
|
||||
import { chain, flatMap, keys } from 'lodash';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { isEmpty, stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
import { 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 {
|
||||
blueprintTooltip,
|
||||
specialToolTip
|
||||
} from '../utils/BlueprintFunctions';
|
||||
import { getBlueprintInfo, getExperimentalInfo } from 'ed-forge/lib/src/data/blueprints';
|
||||
import { getModuleInfo } from 'ed-forge/lib/src/data/items';
|
||||
import { SHOW } from '../shipyard/StatsMapping';
|
||||
|
||||
/**
|
||||
* Modifications menu
|
||||
*/
|
||||
export default class ModificationsMenu extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
ship: PropTypes.object.isRequired,
|
||||
className: PropTypes.string,
|
||||
m: PropTypes.object.isRequired,
|
||||
marker: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired
|
||||
propsToShow: PropTypes.object.isRequired,
|
||||
onPropToggle: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -30,50 +34,57 @@ 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._rollRandom = this._rollRandom.bind(this);
|
||||
this._rollBest = this._rollBest.bind(this);
|
||||
this._rollExtreme = this._rollExtreme.bind(this);
|
||||
this._reset = this._reset.bind(this);
|
||||
this.selectedModRef = null;
|
||||
this.selectedSpecialRef = null;
|
||||
|
||||
const { m } = props;
|
||||
this.state = {
|
||||
blueprintMenuOpened: false,
|
||||
blueprintProgress: m.getBlueprintProgress(),
|
||||
blueprintMenuOpened: !m.getBlueprint(),
|
||||
specialMenuOpened: false
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the blueprints
|
||||
* @param {Object} props React component properties
|
||||
* @param {Object} context React component context
|
||||
* @return {Object} list: Array of React Components
|
||||
*/
|
||||
_renderBlueprints(props, context) {
|
||||
const { m } = props;
|
||||
const { language, tooltip, termtip } = context;
|
||||
const translate = language.translate;
|
||||
_renderBlueprints() {
|
||||
const { m } = this.props;
|
||||
const { language, tooltip, termtip } = this.context;
|
||||
const { translate } = language;
|
||||
|
||||
const blueprints = [];
|
||||
for (const blueprintName in Modifications.modules[m.grp].blueprints) {
|
||||
const blueprint = getBlueprint(blueprintName, m);
|
||||
let blueprintGrades = [];
|
||||
for (let grade in Modifications.modules[m.grp].blueprints[blueprintName].grades) {
|
||||
const blueprints = m.getApplicableBlueprints().map(blueprint => {
|
||||
const info = getBlueprintInfo(blueprint);
|
||||
let blueprintGrades = keys(info.features).map(grade => {
|
||||
// Grade is a string in the JSON so make it a number
|
||||
grade = Number(grade);
|
||||
const classes = cn('c', {
|
||||
active: m.blueprint && blueprint.id === m.blueprint.id && grade === m.blueprint.grade
|
||||
});
|
||||
const close = this._blueprintSelected.bind(this, blueprintName, grade);
|
||||
const key = blueprintName + ':' + grade;
|
||||
const tooltipContent = blueprintTooltip(translate, blueprint.grades[grade], Modifications.modules[m.grp].blueprints[blueprintName].grades[grade].engineers, m.grp);
|
||||
blueprintGrades.unshift(<li key={key} className={classes} style={{ width: '2em' }} onMouseOver={termtip.bind(null, tooltipContent)} onMouseOut={tooltip.bind(null, null)} onClick={close}>{grade}</li>);
|
||||
}
|
||||
if (blueprintGrades) {
|
||||
blueprints.push(<div key={blueprint.name} className={'select-group cap'}>{translate(blueprint.name)}</div>);
|
||||
blueprints.push(<ul key={blueprintName}>{blueprintGrades}</ul>);
|
||||
}
|
||||
}
|
||||
return blueprints;
|
||||
const active = m.getBlueprint() === blueprint && m.getBlueprintGrade() === grade;
|
||||
const key = blueprint + ':' + grade;
|
||||
return <li key={key} data-id={key} className={cn('c', { active })}
|
||||
style={{ width: '2em' }}
|
||||
onMouseOver={termtip.bind(null, blueprintTooltip(language, m, blueprint, grade))}
|
||||
onMouseOut={tooltip.bind(null, null)}
|
||||
onClick={() => {
|
||||
m.setBlueprint(blueprint, grade, 1);
|
||||
this.setState({
|
||||
blueprintMenuOpened: false,
|
||||
specialMenuOpened: true,
|
||||
});
|
||||
}}
|
||||
ref={active ? (ref) => { this.selectedModRef = ref; } : undefined}
|
||||
>{grade}</li>;
|
||||
});
|
||||
|
||||
return [
|
||||
<div key={'div' + blueprint} className={'select-group cap'}>
|
||||
{translate(blueprint)}
|
||||
</div>,
|
||||
<ul key={'ul' + blueprint}>{blueprintGrades}</ul>
|
||||
];
|
||||
});
|
||||
|
||||
return flatMap(blueprints);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,136 +93,133 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
* @param {Object} context React component context
|
||||
* @return {Object} list: Array of React Components
|
||||
*/
|
||||
_renderSpecials(props, context) {
|
||||
const { m } = props;
|
||||
const { language, tooltip, termtip } = context;
|
||||
_renderSpecials() {
|
||||
const { m } = this.props;
|
||||
const { language, tooltip, termtip } = this.context;
|
||||
const translate = language.translate;
|
||||
|
||||
const specials = [];
|
||||
const specialsId = m.missile ? 'specials_' + m.missile : 'specials';
|
||||
if (Modifications.modules[m.grp][specialsId] && Modifications.modules[m.grp][specialsId].length > 0) {
|
||||
const close = this._specialSelected.bind(this, null);
|
||||
specials.push(<div style={{ cursor: 'pointer' }} key={ 'none' } onClick={ close }>{translate('PHRASE_NO_SPECIAL')}</div>);
|
||||
for (const specialName of Modifications.modules[m.grp][specialsId]) {
|
||||
const close = this._specialSelected.bind(this, specialName);
|
||||
specials.push(<div style={{ cursor: 'pointer' }} key={ specialName } onClick={ close }>{translate(Modifications.specials[specialName].name)}</div>);
|
||||
}
|
||||
const applied = m.getExperimental();
|
||||
const experimentals = [];
|
||||
for (const experimental of m.getApplicableExperimentals()) {
|
||||
const active = experimental === applied;
|
||||
let specialTt = specialToolTip(language, m, experimental);
|
||||
experimentals.push(
|
||||
<div key={experimental} data-id={experimental}
|
||||
style={{ cursor: 'pointer' }}
|
||||
className={cn('button-inline-menu', { active })}
|
||||
onClick={this._specialSelected(experimental)}
|
||||
ref={active ? (ref) => { this.selectedSpecialRef = ref; } : undefined}
|
||||
onMouseOver={termtip.bind(null, specialTt)}
|
||||
onMouseOut={tooltip.bind(null, null)}
|
||||
>{translate(experimental)}</div>
|
||||
);
|
||||
}
|
||||
return specials;
|
||||
|
||||
if (experimentals.length) {
|
||||
experimentals.unshift(
|
||||
<div style={{ cursor: 'pointer', fontWeight: 'bold' }}
|
||||
className="button-inline-menu warning" key="none" data-id="none"
|
||||
// Setting the special effect to undefined clears it
|
||||
onClick={this._specialSelected(undefined)}
|
||||
ref={!applied ? (ref) => { this.selectedSpecialRef = ref; } : undefined}
|
||||
>{translate('PHRASE_NO_SPECIAL')}</div>
|
||||
);
|
||||
}
|
||||
|
||||
return experimentals;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a modification component
|
||||
*/
|
||||
_mkModification(property, highlight) {
|
||||
const { translate } = this.context.language;
|
||||
const { m, propsToShow, onPropToggle } = this.props;
|
||||
|
||||
let onSet = m.set.bind(m);
|
||||
// Show resistance instead of effectiveness
|
||||
const mapped = SHOW[property];
|
||||
if (mapped) {
|
||||
property = mapped.as;
|
||||
onSet = mapped.setter.bind(undefined, m);
|
||||
}
|
||||
|
||||
return [
|
||||
<tr key={`th-${property}`}>
|
||||
<th colSpan="4">
|
||||
<span className="cb">{translate(property)}</span>
|
||||
</th>
|
||||
</tr>,
|
||||
<Modification key={property} m={m} property={property}
|
||||
onSet={onSet} highlight={highlight} showProp={propsToShow[property]}
|
||||
onPropToggle={onPropToggle} />
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the modifications
|
||||
* @param {Object} props React Component properties
|
||||
* @return {Object} list: Array of React Components
|
||||
* @return {Array} Array of React Components
|
||||
*/
|
||||
_renderModifications(props) {
|
||||
const { m, onChange, ship } = props;
|
||||
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 }/>);
|
||||
}
|
||||
}
|
||||
return modifications;
|
||||
_renderModifications() {
|
||||
const { m } = this.props;
|
||||
|
||||
const blueprintFeatures = getBlueprintInfo(m.getBlueprint()).features[
|
||||
m.getBlueprintGrade()
|
||||
];
|
||||
const blueprintModifications = chain(keys(blueprintFeatures))
|
||||
.map((feature) => this._mkModification(feature, true))
|
||||
.filter(([_, mod]) => Boolean(mod))
|
||||
.flatMap()
|
||||
.value();
|
||||
const moduleModifications = chain(keys(getModuleInfo(m.getItem()).props))
|
||||
.filter((prop) => !blueprintFeatures[prop])
|
||||
.map((prop) => this._mkModification(prop, false))
|
||||
.flatMap()
|
||||
.value();
|
||||
|
||||
return blueprintModifications.concat(moduleModifications);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the blueprints menu
|
||||
*/
|
||||
_toggleBlueprintsMenu() {
|
||||
const blueprintMenuOpened = !this.state.blueprintMenuOpened;
|
||||
this.setState({ blueprintMenuOpened });
|
||||
}
|
||||
|
||||
/**
|
||||
* Activated when a blueprint is selected
|
||||
* @param {int} fdname The Frontier name of the blueprint
|
||||
* @param {int} grade The grade of the selected blueprint
|
||||
*/
|
||||
_blueprintSelected(fdname, grade) {
|
||||
this.context.tooltip(null);
|
||||
const { m, ship } = this.props;
|
||||
const blueprint = getBlueprint(fdname, m);
|
||||
blueprint.grade = grade;
|
||||
ship.setModuleBlueprint(m, blueprint);
|
||||
|
||||
this.setState({ blueprintMenuOpened: false });
|
||||
this.props.onChange();
|
||||
this.setState({ blueprintMenuOpened: !this.state.blueprintMenuOpened });
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the specials menu
|
||||
*/
|
||||
_toggleSpecialsMenu() {
|
||||
const specialMenuOpened = !this.state.specialMenuOpened;
|
||||
this.setState({ specialMenuOpened });
|
||||
this.setState({ specialMenuOpened: !this.state.specialMenuOpened });
|
||||
}
|
||||
|
||||
/**
|
||||
* Activated when a special is selected
|
||||
* @param {int} special The name of the selected special
|
||||
* Creates a callback for when a special effect is being selected
|
||||
* @param {string} special The name of the selected special
|
||||
* @returns {function} Callback
|
||||
*/
|
||||
_specialSelected(special) {
|
||||
this.context.tooltip(null);
|
||||
const { m, ship } = this.props;
|
||||
return () => {
|
||||
const { m } = this.props;
|
||||
m.setExperimental(special);
|
||||
this.setState({ specialMenuOpened: false });
|
||||
};
|
||||
}
|
||||
|
||||
if (special === null) {
|
||||
ship.clearModuleSpecial(m);
|
||||
} else {
|
||||
ship.setModuleSpecial(m, Modifications.specials[special]);
|
||||
/**
|
||||
* Set focus on first element in modifications menu
|
||||
* if component updates, unless update is due to value change
|
||||
* in a modification
|
||||
*/
|
||||
componentDidUpdate() {
|
||||
if (this.selectedModRef) {
|
||||
this.selectedModRef.focus();
|
||||
return;
|
||||
} else if (this.selectedSpecialRef) {
|
||||
this.selectedSpecialRef.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
this.setState({ specialMenuOpened: false });
|
||||
this.props.onChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a 'worst' roll within the information we have
|
||||
*/
|
||||
_rollWorst() {
|
||||
const { m, ship } = this.props;
|
||||
setWorst(ship, m);
|
||||
this.props.onChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a random roll within the information we have
|
||||
*/
|
||||
_rollRandom() {
|
||||
const { m, ship } = this.props;
|
||||
setRandom(ship, m);
|
||||
this.props.onChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide a 'best' roll within the information we have
|
||||
*/
|
||||
_rollBest() {
|
||||
const { m, ship } = this.props;
|
||||
setBest(ship, m);
|
||||
this.props.onChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide an 'extreme' roll within the information we have
|
||||
*/
|
||||
_rollExtreme() {
|
||||
const { m, ship } = this.props;
|
||||
setExtreme(ship, m);
|
||||
this.props.onChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset modification information
|
||||
*/
|
||||
_reset() {
|
||||
const { m, ship } = this.props;
|
||||
ship.clearModifications(m);
|
||||
ship.clearModuleBlueprint(m);
|
||||
|
||||
this.props.onChange();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -222,74 +230,155 @@ export default class ModificationsMenu extends TranslatedComponent {
|
||||
const { language, tooltip, termtip } = this.context;
|
||||
const translate = language.translate;
|
||||
const { m } = this.props;
|
||||
const { blueprintMenuOpened, specialMenuOpened } = this.state;
|
||||
const {
|
||||
blueprintProgress, blueprintMenuOpened, specialMenuOpened,
|
||||
} = this.state;
|
||||
|
||||
const _toggleBlueprintsMenu = this._toggleBlueprintsMenu;
|
||||
const _toggleSpecialsMenu = this._toggleSpecialsMenu;
|
||||
const _rollBest = this._rollBest;
|
||||
const _rollExtreme = this._rollExtreme;
|
||||
const _rollWorst = this._rollWorst;
|
||||
const _rollRandom = this._rollRandom;
|
||||
const _reset = this._reset;
|
||||
const appliedBlueprint = m.getBlueprint();
|
||||
const appliedGrade = m.getBlueprintGrade();
|
||||
const appliedExperimental = m.getExperimental();
|
||||
|
||||
let blueprintLabel;
|
||||
let haveBlueprint = false;
|
||||
let blueprintTt;
|
||||
if (m.blueprint && m.blueprint.name) {
|
||||
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);
|
||||
let renderComponents = [];
|
||||
switch (true) {
|
||||
case !appliedBlueprint || blueprintMenuOpened:
|
||||
renderComponents = this._renderBlueprints();
|
||||
break;
|
||||
case specialMenuOpened:
|
||||
renderComponents = this._renderSpecials();
|
||||
break;
|
||||
default:
|
||||
// Since the first case didn't apply, there is a blueprint applied so
|
||||
// we render the modifications
|
||||
let blueprintTt = blueprintTooltip(language, m, appliedBlueprint, appliedGrade);
|
||||
|
||||
renderComponents.push(
|
||||
<div style={{ cursor: 'pointer' }} key="blueprintsMenu"
|
||||
className="section-menu button-inline-menu"
|
||||
onMouseOver={termtip.bind(null, blueprintTt)}
|
||||
onMouseOut={tooltip.bind(null, null)}
|
||||
onClick={this._toggleBlueprintsMenu}
|
||||
>
|
||||
{translate(appliedBlueprint)} {translate('grade')} {appliedGrade}
|
||||
</div>
|
||||
);
|
||||
|
||||
if (m.getApplicableExperimentals().length) {
|
||||
let specialLabel = translate('PHRASE_SELECT_SPECIAL');
|
||||
let specialTt;
|
||||
if (appliedExperimental) {
|
||||
specialLabel = appliedExperimental;
|
||||
specialTt = specialToolTip(language, m, appliedExperimental);
|
||||
}
|
||||
renderComponents.push(
|
||||
<div className="section-menu button-inline-menu"
|
||||
style={{ cursor: 'pointer' }}
|
||||
onMouseOver={specialTt ? termtip.bind(null, specialTt) : null}
|
||||
onMouseOut={specialTt ? tooltip.bind(null, null) : null}
|
||||
onClick={this._toggleSpecialsMenu}
|
||||
>{specialLabel}</div>
|
||||
);
|
||||
}
|
||||
|
||||
renderComponents.push(
|
||||
<div
|
||||
className="section-menu button-inline-menu warning"
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
m.resetEngineering();
|
||||
this.selectedModRef = null;
|
||||
this.selectedSpecialRef = null;
|
||||
tooltip(null);
|
||||
this.setState({
|
||||
blueprintMenuOpened: true,
|
||||
blueprintProgress: undefined,
|
||||
});
|
||||
}}
|
||||
onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')}
|
||||
onMouseOut={tooltip.bind(null, null)}
|
||||
>{translate('reset')}</div>,
|
||||
<table style={{ width: '100%', backgroundColor: 'transparent' }}>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td
|
||||
className={cn(
|
||||
'section-menu button-inline-menu',
|
||||
{ active: false },
|
||||
)}
|
||||
>{translate('mroll')}:</td>
|
||||
<td
|
||||
className={cn(
|
||||
'section-menu button-inline-menu',
|
||||
{ active: blueprintProgress === 0 },
|
||||
)} style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
m.setBlueprintProgress(0);
|
||||
this.setState({ blueprintProgress: 0 });
|
||||
}}
|
||||
onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')}
|
||||
onMouseOut={tooltip.bind(null, null)}
|
||||
>{translate('0%')}</td>
|
||||
<td
|
||||
className={cn(
|
||||
'section-menu button-inline-menu',
|
||||
{ active: blueprintProgress === 0.5 },
|
||||
)} style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
m.setBlueprintProgress(0.5);
|
||||
this.setState({ blueprintProgress: 0.5 });
|
||||
}}
|
||||
onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_FIFTY')}
|
||||
onMouseOut={tooltip.bind(null, null)}
|
||||
>{translate('50%')}</td>
|
||||
<td
|
||||
className={cn(
|
||||
'section-menu button-inline-menu',
|
||||
{ active: blueprintProgress === 1 },
|
||||
)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
m.setBlueprintProgress(1);
|
||||
this.setState({ blueprintProgress: 1 });
|
||||
}}
|
||||
onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')}
|
||||
onMouseOut={tooltip.bind(null, null)}
|
||||
>{translate('100%')}</td>
|
||||
<td
|
||||
className={cn(
|
||||
'section-menu button-inline-menu',
|
||||
{ active: blueprintProgress % 0.5 !== 0 },
|
||||
)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
onClick={() => {
|
||||
const blueprintProgress = Math.random();
|
||||
m.setBlueprintProgress(blueprintProgress);
|
||||
this.setState({ blueprintProgress });
|
||||
}}
|
||||
onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')}
|
||||
onMouseOut={tooltip.bind(null, null)}
|
||||
>{translate('random')}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>,
|
||||
<hr />,
|
||||
<span
|
||||
onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')}
|
||||
onMouseOut={tooltip.bind(null, null)}
|
||||
>
|
||||
<table style={{ width: '100%' }}>
|
||||
<tbody>
|
||||
{this._renderModifications()}
|
||||
</tbody>
|
||||
</table>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
let specialLabel;
|
||||
if (m.blueprint && m.blueprint.special) {
|
||||
specialLabel = m.blueprint.special.name;
|
||||
} else {
|
||||
specialLabel = translate('PHRASE_SELECT_SPECIAL');
|
||||
}
|
||||
|
||||
const specials = this._renderSpecials(this.props, this.context);
|
||||
|
||||
const showBlueprintsMenu = blueprintMenuOpened;
|
||||
const showSpecial = haveBlueprint && specials.length && !blueprintMenuOpened;
|
||||
const showSpecialsMenu = specialMenuOpened;
|
||||
const showRolls = haveBlueprint && !blueprintMenuOpened && !specialMenuOpened;
|
||||
const showReset = !blueprintMenuOpened && !specialMenuOpened;
|
||||
const showMods = !blueprintMenuOpened && !specialMenuOpened;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn('select', this.props.className)}
|
||||
onClick={(e) => e.stopPropagation() }
|
||||
onContextMenu={stopCtxPropagation}
|
||||
<div className={cn('select', this.props.className)}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
onContextMenu={stopCtxPropagation}
|
||||
>
|
||||
{ showBlueprintsMenu ? '' : haveBlueprint ?
|
||||
<div className={ cn('section-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onMouseOver={termtip.bind(null, blueprintTt)} onMouseOut={tooltip.bind(null, null)} onClick={_toggleBlueprintsMenu}>{blueprintLabel}</div> :
|
||||
<div className={ cn('section-menu', { selected: blueprintMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleBlueprintsMenu}>{translate('PHRASE_SELECT_BLUEPRINT')}</div> }
|
||||
{ showBlueprintsMenu ? this._renderBlueprints(this.props, this.context) : null }
|
||||
{ showSpecial ? <div className={ cn('section-menu', { selected: specialMenuOpened })} style={{ cursor: 'pointer' }} onClick={_toggleSpecialsMenu}>{specialLabel}</div> : null }
|
||||
{ showSpecialsMenu ? specials : null }
|
||||
{ showRolls || showReset ?
|
||||
<table style={{ width: '100%', backgroundColor: 'transparent' }}>
|
||||
<tbody>
|
||||
{ showRolls ?
|
||||
<tr>
|
||||
<td> { translate('roll') }: </td>
|
||||
<td style={{ cursor: 'pointer' }} onClick={_rollWorst} onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_WORST')} onMouseOut={tooltip.bind(null, null)}> { translate('worst') } </td>
|
||||
<td style={{ cursor: 'pointer' }} onClick={_rollBest}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_BEST')} onMouseOut={tooltip.bind(null, null)}> { translate('best') } </td>
|
||||
<td style={{ cursor: 'pointer' }} onClick={_rollExtreme}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_EXTREME')} onMouseOut={tooltip.bind(null, null)}> { translate('extreme') } </td>
|
||||
<td style={{ cursor: 'pointer' }} onClick={_rollRandom} onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RANDOM')} onMouseOut={tooltip.bind(null, null)}> { translate('random') } </td>
|
||||
</tr> : null }
|
||||
{ showReset ?
|
||||
<tr>
|
||||
<td colSpan={'5'} style={{ cursor: 'pointer' }} onClick={_reset}onMouseOver={termtip.bind(null, 'PHRASE_BLUEPRINT_RESET')} onMouseOut={tooltip.bind(null, null)}> { translate('reset') } </td>
|
||||
</tr> : null }
|
||||
</tbody>
|
||||
</table> : null }
|
||||
{ showMods ?
|
||||
<span onMouseOver={termtip.bind(null, 'HELP_MODIFICATIONS_MENU')} onMouseOut={tooltip.bind(null, null)} >
|
||||
{ this._renderModifications(this.props) }
|
||||
</span> : null }
|
||||
{renderComponents}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,47 +1,39 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { ShipProps } from 'ed-forge';
|
||||
const { SPEED, BOOST_SPEED, ROLL, BOOST_ROLL, YAW, BOOST_YAW, PITCH, BOOST_PITCH } = ShipProps;
|
||||
|
||||
/**
|
||||
* Movement
|
||||
*/
|
||||
export default class Movement extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
marker: PropTypes.string.isRequired,
|
||||
code: PropTypes.string.isRequired,
|
||||
ship: PropTypes.object.isRequired,
|
||||
boost: PropTypes.bool.isRequired,
|
||||
eng: PropTypes.number.isRequired,
|
||||
fuel: PropTypes.number.isRequired,
|
||||
cargo: PropTypes.number.isRequired
|
||||
pips: PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render movement
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { ship, boost, eng, cargo, fuel } = this.props;
|
||||
const { ship, boost } = this.props;
|
||||
const { language } = this.context;
|
||||
const { formats, translate, units } = language;
|
||||
const { formats } = language;
|
||||
|
||||
return (
|
||||
<span id='movement'>
|
||||
<svg viewBox='0 0 600 600' fillRule="evenodd" clipRule="evenodd">
|
||||
// Axes
|
||||
{/* Axes */}
|
||||
<path d="M150 250v300" strokeWidth='1'/>
|
||||
<path d="M150 250l236 236" strokeWidth='1'/>
|
||||
<path d="M150 250l350 -200" strokeWidth='1'/>
|
||||
// End Arrow
|
||||
{/* End Arrow */}
|
||||
<path d="M508 43.3L487 67l-10-17.3 31-6.4z"/>
|
||||
// Axes arcs and arrows
|
||||
{/* Axes arcs and arrows */}
|
||||
<path d="M71.7 251.7C64.2 259.2 60 269.4 60 280c0 22 18 40 40 40s40-18 40-40c0-10.6-4.2-20.8-11.7-28.3 7.5 7.5 11.7 17.7 11.7 28.3 0 22-18 40-40 40s-40-18-40-40c0-10.6 4.2-20.8 11.7-28.3z" strokeWidth='4' transform="matrix(.6 0 0 .3 87.5 376.3)"/>
|
||||
<path d="M142.8 453l-13.2 8.7-2.6-9.7 15.8 1z"/>
|
||||
<path d="M144.7 451.6l.5 1.6-16.2 10.6h-.4l-3.5-13 .7-.4 19.3 1.2zm-14.2 7.7l7.7-5-9.2-.7 1.5 5.7zm25.7-6.3l15.8-1-2.6 9.7-13.2-8.8z"/>
|
||||
@@ -57,14 +49,10 @@ export default class Movement extends TranslatedComponent {
|
||||
<path d="M359.5 422.4l-1.2 19.3-1.6.4-10.7-16 .2-.2 13-3.4.3.4zm-9 5l5.2 7.8.6-9.3-5.7 1.2zm-10.5 24l-13.2 8.6-2.6-9.7 15.8 1z"/>
|
||||
<path d="M342 450l.4 1.5-16.2 10.7-.4-.2-3.5-13 .3-.3L342 450zm-14.3 7.6l7.7-5-9.2-.6 1.5 5.6z"/>
|
||||
|
||||
// Speed
|
||||
<text x="470" y="30" strokeWidth='0'>{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>
|
||||
// Roll
|
||||
<text x="450" y="110" strokeWidth='0'>{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="470" y="30" strokeWidth='0'>{formats.int(ship.get(boost ? BOOST_SPEED : SPEED)) + 'm/s'}</text>
|
||||
<text x="355" y="410" strokeWidth='0'>{formats.int(ship.get(boost ? BOOST_PITCH : PITCH)) + '°/s'}</text>
|
||||
<text x="450" y="110" strokeWidth='0'>{formats.int(ship.get(boost ? BOOST_ROLL : ROLL)) + '°/s'}</text>
|
||||
<text x="160" y="430" strokeWidth='0'>{formats.int(ship.get(boost ? BOOST_YAW : YAW)) + '°/s'}</text>
|
||||
</svg>
|
||||
</span>);
|
||||
}
|
||||
|
||||
@@ -1,45 +1,35 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import * as Calc from '../shipyard/Calculations';
|
||||
import PieChart from './PieChart';
|
||||
import { nameComparator } from '../utils/SlotFunctions';
|
||||
import { MountFixed, MountGimballed, MountTurret } from './SvgIcons';
|
||||
import VerticalBarChart from './VerticalBarChart';
|
||||
import { Ship } from 'ed-forge';
|
||||
import autoBind from 'auto-bind';
|
||||
import { DAMAGE_METRICS } from 'ed-forge/lib/src/ship-stats';
|
||||
import { clone, mapValues, mergeWith, reverse, sortBy, sum, toPairs, values } from 'lodash';
|
||||
|
||||
/**
|
||||
* Generates an internationalization friendly weapon comparator that will
|
||||
* sort by specified property (if provided) then by name/group, class, rating
|
||||
* @param {function} translate Translation function
|
||||
* @param {function} propComparator Optional property comparator
|
||||
* @param {boolean} desc Use descending order
|
||||
* @return {function} Comparator function for names
|
||||
* Turns an object into a tooltip.
|
||||
* @param {function} translate Translate function
|
||||
* @param {object} o Map to make the tooltip from
|
||||
* @returns {React.Component} Tooltip
|
||||
*/
|
||||
export function weaponComparator(translate, propComparator, desc) {
|
||||
return (a, b) => {
|
||||
if (!desc) { // Flip A and B if ascending order
|
||||
let t = a;
|
||||
a = b;
|
||||
b = t;
|
||||
}
|
||||
function objToTooltip(translate, o) {
|
||||
return toPairs(o)
|
||||
.filter(([k, v]) => Boolean(v))
|
||||
.map(([k, v]) => <div key={k}>{`${translate(k)}: ${v}`}</div>);
|
||||
}
|
||||
|
||||
// If a property comparator is provided use it first
|
||||
let diff = propComparator ? propComparator(a, b) : nameComparator(translate, a, b);
|
||||
|
||||
if (diff) {
|
||||
return diff;
|
||||
}
|
||||
|
||||
// Property matches so sort by name / group, then class, rating
|
||||
if (a.name === b.name && a.grp === b.grp) {
|
||||
if(a.class == b.class) {
|
||||
return a.rating > b.rating ? 1 : -1;
|
||||
}
|
||||
return a.class - b.class;
|
||||
}
|
||||
|
||||
return nameComparator(translate, a, b);
|
||||
};
|
||||
/**
|
||||
* Returns a data object used by {@link PieChart} that shows damage by type.
|
||||
* @param {function} translate Translation function
|
||||
* @param {Calc.SDps} o Object that holds sdps split up by type
|
||||
* @returns {Object} Data object
|
||||
*/
|
||||
function objToPie(translate, o) {
|
||||
return toPairs(o).map(([k, value]) => {
|
||||
return { label: translate(k), value };
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,12 +42,10 @@ export function weaponComparator(translate, propComparator, desc) {
|
||||
*/
|
||||
export default class Offence extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
marker: PropTypes.string.isRequired,
|
||||
ship: PropTypes.object.isRequired,
|
||||
opponent: PropTypes.object.isRequired,
|
||||
engagementrange: PropTypes.number.isRequired,
|
||||
wep: PropTypes.number.isRequired,
|
||||
opponentSys: PropTypes.number.isRequired
|
||||
code: PropTypes.string.isRequired,
|
||||
ship: PropTypes.instanceOf(Ship).isRequired,
|
||||
opponent: PropTypes.instanceOf(Ship).isRequired,
|
||||
engagementRange: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -66,200 +54,364 @@ export default class Offence extends TranslatedComponent {
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
autoBind(this);
|
||||
|
||||
this._sort = this._sort.bind(this);
|
||||
|
||||
const damage = Calc.offenceMetrics(props.ship, props.opponent, props.wep, props.opponentSys, props.engagementrange);
|
||||
this.state = {
|
||||
predicate: 'n',
|
||||
this.state = {
|
||||
predicate: 'classRating',
|
||||
desc: true,
|
||||
damage
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state if our properties change
|
||||
* @param {Object} nextProps Incoming/Next properties
|
||||
* @return {boolean} Returns true if the component should be rerendered
|
||||
*/
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.marker != nextProps.marker || this.props.eng != nextProps.eng) {
|
||||
const damage = Calc.offenceMetrics(nextProps.ship, nextProps.opponent, nextProps.wep, nextProps.opponentSys, nextProps.engagementrange);
|
||||
this.setState({ damage });
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sort order and sort
|
||||
* @param {string} predicate Sort predicate
|
||||
*/
|
||||
_sortOrder(predicate) {
|
||||
let desc = this.state.desc;
|
||||
|
||||
if (predicate == this.state.predicate) {
|
||||
desc = !desc;
|
||||
} else {
|
||||
desc = true;
|
||||
}
|
||||
|
||||
this._sort(predicate, desc);
|
||||
let desc = predicate == this.state.predicate ? !this.state.desc : true;
|
||||
this.setState({ predicate, desc });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the weapon list
|
||||
* @param {string} predicate Sort predicate
|
||||
* @param {Boolean} desc Sort order descending
|
||||
*/
|
||||
_sort(predicate, desc) {
|
||||
let comp = weaponComparator.bind(null, this.context.language.translate);
|
||||
|
||||
switch (predicate) {
|
||||
case 'n': comp = comp(null, desc); break;
|
||||
case 'esdpss': comp = comp((a, b) => a.sdps.shields.total - b.sdps.shields.total, desc); break;
|
||||
case 'es': comp = comp((a, b) => a.effectiveness.shields.total - b.effectiveness.shields.total, desc); break;
|
||||
case 'esdpsh': comp = comp((a, b) => a.sdps.armour.total - b.sdps.armour.total, desc); break;
|
||||
case 'eh': comp = comp((a, b) => a.effectiveness.armour.total - b.effectiveness.armour.total, desc); break;
|
||||
}
|
||||
|
||||
this.state.damage.sort(comp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render offence
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { ship, opponent, wep, engagementrange } = this.props;
|
||||
const { ship } = this.props;
|
||||
const { language, tooltip, termtip } = this.context;
|
||||
const { formats, translate, units } = language;
|
||||
const { damage } = this.state;
|
||||
const sortOrder = this._sortOrder;
|
||||
|
||||
const pd = ship.standard[4].m;
|
||||
const {
|
||||
drained, sustained, rangeMultiplier, hardnessMultiplier, timeToDrain
|
||||
} = ship.getMetrics(DAMAGE_METRICS);
|
||||
const portions = {
|
||||
Absolute: sustained.types.abs,
|
||||
Explosive: sustained.types.expl,
|
||||
Kinetic: sustained.types.kin,
|
||||
Thermic: sustained.types.therm,
|
||||
};
|
||||
|
||||
const opponentShields = Calc.shieldMetrics(opponent, 4);
|
||||
const opponentArmour = Calc.armourMetrics(opponent);
|
||||
const oppShield = ship.getOpponent().getShield();
|
||||
const shieldMults = {
|
||||
Absolute: 1,
|
||||
Explosive: oppShield.explosive.damageMultiplier,
|
||||
Kinetic: oppShield.kinetic.damageMultiplier,
|
||||
Thermic: oppShield.thermal.damageMultiplier,
|
||||
};
|
||||
|
||||
const timeToDrain = Calc.timeToDrainWep(ship, wep);
|
||||
const oppArmour = ship.getOpponent().getArmour();
|
||||
const armourMults = {
|
||||
Absolute: 1,
|
||||
Explosive: oppArmour.explosive.damageMultiplier,
|
||||
Kinetic: oppArmour.kinetic.damageMultiplier,
|
||||
Thermic: oppArmour.thermal.damageMultiplier,
|
||||
};
|
||||
|
||||
let absoluteShieldsSDps = 0;
|
||||
let explosiveShieldsSDps = 0;
|
||||
let kineticShieldsSDps = 0;
|
||||
let thermalShieldsSDps = 0;
|
||||
let absoluteArmourSDps = 0;
|
||||
let explosiveArmourSDps = 0;
|
||||
let kineticArmourSDps = 0;
|
||||
let thermalArmourSDps = 0;
|
||||
const weapons = sortBy(ship.getHardpoints(), (m) => m.get('distributordraw'));
|
||||
let rows = weapons.map((weapon) => {
|
||||
const sdps = weapon.get('sustaineddamagepersecond');
|
||||
const byRange = weapon.getRangeEffectiveness();
|
||||
const weaponPortions = {
|
||||
Absolute: weapon.get('absolutedamageportion'),
|
||||
Explosive: weapon.get('explosivedamageportion'),
|
||||
Kinetic: weapon.get('kineticdamageportion'),
|
||||
Thermic: weapon.get('thermicdamageportion'),
|
||||
};
|
||||
const baseSdpsTooltip = objToTooltip(
|
||||
translate,
|
||||
mapValues(weaponPortions, (p) => formats.f1(sdps * p)),
|
||||
);
|
||||
|
||||
let totalSEps = 0;
|
||||
const bySys = oppShield.absolute.bySys;
|
||||
const shieldResEfts = mergeWith(
|
||||
clone(weaponPortions),
|
||||
shieldMults,
|
||||
(objV, srcV) => objV * srcV
|
||||
);
|
||||
const byShieldRes = sum(values(shieldResEfts));
|
||||
const shieldsSdpsTooltip = objToTooltip(
|
||||
translate,
|
||||
mapValues(
|
||||
shieldResEfts,
|
||||
(mult) => formats.f1(byRange * mult * bySys * sdps),
|
||||
),
|
||||
);
|
||||
const shieldsEftTooltip = objToTooltip(
|
||||
translate,
|
||||
{
|
||||
range: formats.pct1(byRange),
|
||||
resistance: formats.pct1(byShieldRes),
|
||||
'power distributor': formats.pct1(bySys),
|
||||
},
|
||||
);
|
||||
const shieldEft = byRange * byShieldRes * bySys;
|
||||
|
||||
const rows = [];
|
||||
for (let i = 0; i < damage.length; i++) {
|
||||
const weapon = damage[i];
|
||||
const byHardness = weapon.getArmourEffectiveness();
|
||||
const armourResEfts = mergeWith(
|
||||
clone(weaponPortions),
|
||||
armourMults,
|
||||
(objV, srcV) => objV * srcV,
|
||||
);
|
||||
const byArmourRes = sum(values(armourResEfts));
|
||||
const armourSdpsTooltip = objToTooltip(
|
||||
translate,
|
||||
mapValues(
|
||||
armourResEfts,
|
||||
(mult) => formats.f1(byRange * mult * byHardness * sdps)
|
||||
),
|
||||
);
|
||||
const armourEftTooltip = objToTooltip(
|
||||
translate,
|
||||
{
|
||||
range: formats.pct1(byRange),
|
||||
resistance: formats.pct1(byArmourRes),
|
||||
hardness: formats.pct1(byHardness),
|
||||
},
|
||||
);
|
||||
const armourEft = byRange * byArmourRes * byHardness;
|
||||
|
||||
totalSEps += weapon.seps;
|
||||
absoluteShieldsSDps += weapon.sdps.shields.absolute;
|
||||
explosiveShieldsSDps += weapon.sdps.shields.explosive;
|
||||
kineticShieldsSDps += weapon.sdps.shields.kinetic;
|
||||
thermalShieldsSDps += weapon.sdps.shields.thermal;
|
||||
absoluteArmourSDps += weapon.sdps.armour.absolute;
|
||||
explosiveArmourSDps += weapon.sdps.armour.explosive;
|
||||
kineticArmourSDps += weapon.sdps.armour.kinetic;
|
||||
thermalArmourSDps += weapon.sdps.armour.thermal;
|
||||
const bp = weapon.getBlueprint();
|
||||
const grade = weapon.getBlueprintGrade();
|
||||
const exp = weapon.getExperimental();
|
||||
let bpTitle = `${translate(bp)} ${translate('grade')} ${grade}`;
|
||||
if (exp) {
|
||||
bpTitle += `, ${translate(exp)}`;
|
||||
}
|
||||
return {
|
||||
slot: weapon.getSlot(),
|
||||
mount: weapon.mount,
|
||||
classRating: weapon.getClassRating(),
|
||||
type: weapon.readMeta('type'),
|
||||
bpTitle: bp ? ` (${bpTitle})` : null,
|
||||
sdps,
|
||||
baseSdpsTooltip,
|
||||
shieldSdps: sdps * shieldEft,
|
||||
shieldEft,
|
||||
shieldsSdpsTooltip,
|
||||
shieldsEftTooltip,
|
||||
armourSdps: sdps * armourEft,
|
||||
armourEft,
|
||||
armourSdpsTooltip,
|
||||
armourEftTooltip,
|
||||
};
|
||||
});
|
||||
|
||||
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 { predicate, desc } = this.state;
|
||||
rows = sortBy(rows, (row) => row[predicate]);
|
||||
if (desc) {
|
||||
reverse(rows);
|
||||
}
|
||||
|
||||
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 sdpsTooltip = objToTooltip(
|
||||
translate,
|
||||
mapValues(portions, (p) => formats.f1(sustained.dps * p)),
|
||||
);
|
||||
const sdpsPie = objToPie(
|
||||
translate,
|
||||
mapValues(portions, (p) => Math.round(sustained.dps * p)),
|
||||
);
|
||||
|
||||
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 shieldSdpsSrcs = mergeWith(
|
||||
clone(portions),
|
||||
shieldMults,
|
||||
(objV, srcV) => sustained.dps * oppShield.absolute.bySys *
|
||||
rangeMultiplier * objV * srcV,
|
||||
);
|
||||
const shieldsSdps = sum(values(shieldSdpsSrcs));
|
||||
const shieldsSdpsTooltip = objToTooltip(
|
||||
translate,
|
||||
mapValues(shieldSdpsSrcs, (v) => formats.f1(v)),
|
||||
);
|
||||
const shieldsSdpsPie = objToPie(
|
||||
translate,
|
||||
mapValues(shieldSdpsSrcs, (v) => Math.round(v)),
|
||||
);
|
||||
|
||||
rows.push(
|
||||
<tr key={weapon.id}>
|
||||
<td className='ri'>
|
||||
{weapon.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed className='icon'/></span> : null}
|
||||
{weapon.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : null}
|
||||
{weapon.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : null}
|
||||
{weapon.classRating} {translate(weapon.name)}
|
||||
{weapon.engineering ? ' (' + weapon.engineering + ')' : null }
|
||||
</td>
|
||||
<td className='ri'><span onMouseOver={termtip.bind(null, effectiveShieldsSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.shields.total)}</span></td>
|
||||
<td className='ri'><span onMouseOver={termtip.bind(null, effectivenessShieldsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(weapon.effectiveness.shields.total)}</span></td>
|
||||
<td className='ri'><span onMouseOver={termtip.bind(null, effectiveArmourSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.f1(weapon.sdps.armour.total)}</span></td>
|
||||
<td className='ri'><span onMouseOver={termtip.bind(null, effectivenessArmourTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>{formats.pct1(weapon.effectiveness.armour.total)}</span></td>
|
||||
</tr>);
|
||||
}
|
||||
const armourSdpsSrcs = mergeWith(
|
||||
clone(portions),
|
||||
armourMults,
|
||||
(objV, srcV) => sustained.dps * hardnessMultiplier * rangeMultiplier *
|
||||
objV * srcV,
|
||||
);
|
||||
const armourSdps = sum(values(armourSdpsSrcs));
|
||||
const totalArmourSDpsTooltipDetails = objToTooltip(
|
||||
translate,
|
||||
mapValues(armourSdpsSrcs, (v) => formats.f1(v)),
|
||||
);
|
||||
const armourSDpsData = objToPie(
|
||||
translate,
|
||||
mapValues(armourSdpsSrcs, (v) => Math.round(v)),
|
||||
);
|
||||
|
||||
const totalShieldsSDps = absoluteShieldsSDps + explosiveShieldsSDps + kineticShieldsSDps + thermalShieldsSDps;
|
||||
const totalArmourSDps = absoluteArmourSDps + explosiveArmourSDps + kineticArmourSDps + thermalArmourSDps;
|
||||
const drainedPortions = {
|
||||
Absolute: drained.types.abs,
|
||||
Explosive: drained.types.expl,
|
||||
Kinetic: drained.types.kin,
|
||||
Thermic: drained.types.therm,
|
||||
};
|
||||
|
||||
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') });
|
||||
// How much damage do we deal, before the capacitor is empty?
|
||||
const armourLeft = oppArmour.armour - (timeToDrain * armourSdps);
|
||||
// If we can't kill the enemy on one capacitor, factor in drained damage
|
||||
let timeToDepleteArmour;
|
||||
if (armourLeft > 0) {
|
||||
const effectiveDrainedDps = sum(values(mergeWith(
|
||||
clone(drainedPortions),
|
||||
armourMults,
|
||||
(objV, srcV) => objV * srcV,
|
||||
))) * drained.dps * rangeMultiplier *
|
||||
hardnessMultiplier;
|
||||
timeToDepleteArmour = effectiveDrainedDps === 0 ? Infinity :
|
||||
timeToDrain + (armourLeft / effectiveDrainedDps);
|
||||
} else {
|
||||
timeToDepleteArmour = oppArmour.armour / armourSdps;
|
||||
}
|
||||
|
||||
const armourSDpsData = [];
|
||||
armourSDpsData.push({ value: Math.round(absoluteArmourSDps), label: translate('absolute') });
|
||||
armourSDpsData.push({ value: Math.round(explosiveArmourSDps), label: translate('explosive') });
|
||||
armourSDpsData.push({ value: Math.round(kineticArmourSDps), label: translate('kinetic') });
|
||||
armourSDpsData.push({ value: Math.round(thermalArmourSDps), label: translate('thermal') });
|
||||
|
||||
const timeToDepleteShields = Calc.timeToDeplete(opponentShields.total, totalShieldsSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4));
|
||||
const timeToDepleteArmour = Calc.timeToDeplete(opponentArmour.total, totalArmourSDps, totalSEps, pd.getWeaponsCapacity(), pd.getWeaponsRechargeRate() * (wep / 4));
|
||||
// How much damage do we deal, before the capacitor is empty?
|
||||
const shieldsLeft = oppShield.withSCBs - (timeToDrain * shieldsSdps);
|
||||
// If we can't kill the enemy on one capacitor, factor in drained damage
|
||||
let timeToDepleteShields;
|
||||
if (shieldsLeft > 0) {
|
||||
const effectiveDrainedDps = sum(values(mergeWith(
|
||||
clone(drainedPortions),
|
||||
shieldMults,
|
||||
(objV, srcV) => objV * srcV,
|
||||
))) * drained.dps * rangeMultiplier;
|
||||
timeToDepleteShields = effectiveDrainedDps === 0 ? Infinity :
|
||||
timeToDrain + (shieldsLeft / effectiveDrainedDps);
|
||||
} else {
|
||||
timeToDepleteShields = oppShield.withSCBs / shieldsSdps;
|
||||
}
|
||||
|
||||
return (
|
||||
<span id='offence'>
|
||||
<div className='group full'>
|
||||
<table>
|
||||
<thead>
|
||||
<tr className='main'>
|
||||
<th rowSpan='2' className='sortable' onClick={sortOrder.bind(this, 'n')}>{translate('weapon')}</th>
|
||||
<th colSpan='2'>{translate('opponent\'s shields')}</th>
|
||||
<th colSpan='2'>{translate('opponent\'s armour')}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')} onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'esdpss')}>{'sdps'}</th>
|
||||
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_SHIELDS')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'es')}>{'eft'}</th>
|
||||
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'esdpsh')}>{'sdps'}</th>
|
||||
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_ARMOUR')} onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'eh')}>{'eft'}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows}
|
||||
</tbody>
|
||||
</table>
|
||||
<table>
|
||||
<thead>
|
||||
<tr className='main'>
|
||||
<th rowSpan='2' className='sortable' onClick={sortOrder.bind(this, 'classRating')}>{translate('weapon')}</th>
|
||||
<th colSpan='1'>{translate('overall')}</th>
|
||||
<th colSpan='3'>{translate('opponent\'s shields')}</th>
|
||||
<th colSpan='3'>{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, 'sdps')}>sdps</th>
|
||||
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_SHIELDS')}
|
||||
onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'shieldSdps')}>sdps</th>
|
||||
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_SHIELDS')}
|
||||
onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'shieldEft')}>eft</th>
|
||||
<th className='lft sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVE_SDPS_ARMOUR')}
|
||||
onMouseOut={tooltip.bind(null, null)}onClick={sortOrder.bind(this, 'armourSdps')}>sdps</th>
|
||||
<th className='sortable' onMouseOver={termtip.bind(null, 'TT_EFFECTIVENESS_ARMOUR')}
|
||||
onMouseOut={tooltip.bind(null, null)} onClick={sortOrder.bind(this, 'armourEft')}>eft</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{rows.map((row) => (
|
||||
<tr key={row.slot}>
|
||||
<td className='ri'>
|
||||
{row.mount == 'F' ? <span onMouseOver={termtip.bind(null, 'fixed')} onMouseOut={tooltip.bind(null, null)}><MountFixed className='icon'/></span> : null}
|
||||
{row.mount == 'G' ? <span onMouseOver={termtip.bind(null, 'gimballed')} onMouseOut={tooltip.bind(null, null)}><MountGimballed /></span> : null}
|
||||
{row.mount == 'T' ? <span onMouseOver={termtip.bind(null, 'turreted')} onMouseOut={tooltip.bind(null, null)}><MountTurret /></span> : null}
|
||||
{row.classRating} {translate(row.type)}
|
||||
{row.bpTitle}
|
||||
</td>
|
||||
<td className='ri'>
|
||||
<span onMouseOver={termtip.bind(null, row.baseSdpsTooltip)}
|
||||
onMouseOut={tooltip.bind(null, null)}
|
||||
>{formats.f1(row.sdps)}</span></td>
|
||||
<td className='ri'>
|
||||
<span onMouseOver={termtip.bind(null, row.shieldsSdpsTooltip)}
|
||||
onMouseOut={tooltip.bind(null, null)}
|
||||
>{formats.f1(row.shieldSdps)}</span></td>
|
||||
<td className='ri'>
|
||||
<span onMouseOver={termtip.bind(null, row.shieldsEftTooltip)}
|
||||
onMouseOut={tooltip.bind(null, null)}
|
||||
>{formats.pct1(row.shieldEft)}</span></td>
|
||||
<td className='ri'>
|
||||
<span onMouseOver={termtip.bind(null, row.armourSdpsTooltip)}
|
||||
onMouseOut={tooltip.bind(null, null)}
|
||||
>{formats.f1(row.armourSdps)}</span></td>
|
||||
<td className='ri'>
|
||||
<span onMouseOver={termtip.bind(null, row.armourEftTooltip)}
|
||||
onMouseOut={tooltip.bind(null, null)}
|
||||
>{formats.pct1(row.armourEft)}</span></td>
|
||||
</tr>
|
||||
))}
|
||||
{rows.length > 0 &&
|
||||
<tr>
|
||||
<td></td>
|
||||
<td className='ri'>
|
||||
<span onMouseOver={termtip.bind(null, sdpsTooltip)} onMouseOut={tooltip.bind(null, null)}>
|
||||
={formats.f1(sustained.dps)}
|
||||
</span>
|
||||
</td>
|
||||
<td className='ri'>
|
||||
<span onMouseOver={termtip.bind(null, shieldsSdpsTooltip)} onMouseOut={tooltip.bind(null, null)}>
|
||||
={formats.f1(shieldsSdps)}
|
||||
</span>
|
||||
</td>
|
||||
<td></td>
|
||||
<td className='ri'>
|
||||
<span onMouseOver={termtip.bind(null, totalArmourSDpsTooltipDetails)} onMouseOut={tooltip.bind(null, null)}>
|
||||
={formats.f1(armourSdps)}
|
||||
</span>
|
||||
</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2>{translate('offence metrics')}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_DRAIN_WEP'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_DRAIN_WEP')}<br/>{timeToDrain === Infinity ? translate('never') : formats.time(timeToDrain)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_EFFECTIVE_SDPS_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_EFFECTIVE_SDPS_SHIELDS')}<br/>{formats.f1(totalShieldsSDps)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_REMOVE_SHIELDS'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_REMOVE_SHIELDS')}<br/>{timeToDepleteShields === Infinity ? translate('never') : formats.time(timeToDepleteShields)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_EFFECTIVE_SDPS_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_EFFECTIVE_SDPS_ARMOUR')}<br/>{formats.f1(totalArmourSDps)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_REMOVE_ARMOUR'))} onMouseOut={tooltip.bind(null, null)}>{translate('PHRASE_TIME_TO_REMOVE_ARMOUR')}<br/>{timeToDepleteArmour === Infinity ? translate('never') : formats.time(timeToDepleteArmour)}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_DRAIN_WEP'))}
|
||||
onMouseOut={tooltip.bind(null, null)}>
|
||||
{translate('PHRASE_TIME_TO_DRAIN_WEP')}<br/>
|
||||
{timeToDrain === Infinity ? translate('never') : formats.time(timeToDrain)}
|
||||
</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_EFFECTIVE_SDPS_SHIELDS'))}
|
||||
onMouseOut={tooltip.bind(null, null)}>
|
||||
{translate('PHRASE_EFFECTIVE_SDPS_SHIELDS')}<br/>
|
||||
{formats.f1(shieldsSdps)}
|
||||
</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_REMOVE_SHIELDS'))}
|
||||
onMouseOut={tooltip.bind(null, null)}>
|
||||
{translate('PHRASE_TIME_TO_REMOVE_SHIELDS')}<br/>
|
||||
{timeToDepleteShields === Infinity ? translate('never') : formats.time(timeToDepleteShields)}
|
||||
</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_EFFECTIVE_SDPS_ARMOUR'))}
|
||||
onMouseOut={tooltip.bind(null, null)}>
|
||||
{translate('PHRASE_EFFECTIVE_SDPS_ARMOUR')}<br/>
|
||||
{formats.f1(armourSdps)}
|
||||
</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('TT_TIME_TO_REMOVE_ARMOUR'))}
|
||||
onMouseOut={tooltip.bind(null, null)}>
|
||||
{translate('PHRASE_TIME_TO_REMOVE_ARMOUR')}<br/>
|
||||
{timeToDepleteArmour === Infinity ? translate('never') : formats.time(timeToDepleteArmour)}
|
||||
</h2>
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('shield damage sources')}</h2>
|
||||
<PieChart data={shieldsSDpsData} />
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_OVERALL_DAMAGE'))}
|
||||
onMouseOut={tooltip.bind(null, null)}>
|
||||
{translate('overall damage')}
|
||||
</h2>
|
||||
<PieChart data={sdpsPie} />
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_ARMOUR_DAMAGE'))} onMouseOut={tooltip.bind(null, null)}>{translate('armour damage sources')}</h2>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_SHIELD_DAMAGE'))}
|
||||
onMouseOut={tooltip.bind(null, null)}>
|
||||
{translate('shield damage sources')}
|
||||
</h2>
|
||||
<PieChart data={shieldsSdpsPie} />
|
||||
</div>
|
||||
<div className='group quarter'>
|
||||
<h2 onMouseOver={termtip.bind(null, translate('PHRASE_ARMOUR_DAMAGE'))}
|
||||
onMouseOut={tooltip.bind(null, null)}>
|
||||
{translate('armour damage sources')}
|
||||
</h2>
|
||||
<PieChart data={armourSDpsData} />
|
||||
</div>
|
||||
</span>);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import Ship from '../shipyard/Ship';
|
||||
import Persist from '../stores/Persist';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import PowerManagement from './PowerManagement';
|
||||
@@ -13,29 +11,24 @@ import Movement from './Movement';
|
||||
import Offence from './Offence';
|
||||
import Defence from './Defence';
|
||||
import WeaponDamageChart from './WeaponDamageChart';
|
||||
import Pips from '../components/Pips';
|
||||
import Boost from '../components/Boost';
|
||||
import Fuel from '../components/Fuel';
|
||||
import Cargo from '../components/Cargo';
|
||||
import ShipPicker from '../components/ShipPicker';
|
||||
import EngagementRange from '../components/EngagementRange';
|
||||
import autoBind from 'auto-bind';
|
||||
import { ShipProps } from 'ed-forge';
|
||||
const { CARGO_CAPACITY, FUEL_CAPACITY } = ShipProps;
|
||||
|
||||
/**
|
||||
* Outfitting subpages
|
||||
*/
|
||||
export default class OutfittingSubpages extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
ship: PropTypes.object.isRequired,
|
||||
code: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
buildName: PropTypes.string,
|
||||
sys: PropTypes.number.isRequired,
|
||||
eng: PropTypes.number.isRequired,
|
||||
wep: PropTypes.number.isRequired,
|
||||
cargo: PropTypes.number.isRequired,
|
||||
fuel: PropTypes.number.isRequired,
|
||||
boost: PropTypes.bool.isRequired,
|
||||
engagementRange: PropTypes.number.isRequired,
|
||||
opponent: PropTypes.object.isRequired,
|
||||
opponentBuild: PropTypes.string,
|
||||
opponentSys: PropTypes.number.isRequired,
|
||||
opponentEng: PropTypes.number.isRequired,
|
||||
opponentWep: PropTypes.number.isRequired,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -44,13 +37,17 @@ export default class OutfittingSubpages extends TranslatedComponent {
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._powerTab = this._powerTab.bind(this);
|
||||
this._profilesTab = this._profilesTab.bind(this);
|
||||
this._offenceTab = this._offenceTab.bind(this);
|
||||
this._defenceTab = this._defenceTab.bind(this);
|
||||
autoBind(this);
|
||||
|
||||
this.props.ship.setOpponent(this.props.ship);
|
||||
this.state = {
|
||||
boost: false,
|
||||
cargo: props.ship.get(CARGO_CAPACITY),
|
||||
fuel: props.ship.get(FUEL_CAPACITY),
|
||||
pips: props.ship.getDistributorSettingsObject(),
|
||||
tab: Persist.getOutfittingTab() || 'power',
|
||||
engagementRange: 1000,
|
||||
opponent: this.props.ship,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -59,128 +56,113 @@ export default class OutfittingSubpages extends TranslatedComponent {
|
||||
* @param {string} tab Tab name
|
||||
*/
|
||||
_showTab(tab) {
|
||||
Persist.setOutfittingTab(tab);
|
||||
this.setState({ tab });
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the power tab
|
||||
* @return {React.Component} Tab contents
|
||||
*/
|
||||
_powerTab() {
|
||||
let { ship, buildName, code, onChange } = this.props;
|
||||
Persist.setOutfittingTab('power');
|
||||
|
||||
const powerMarker = `${ship.toString()}`;
|
||||
const costMarker = `${ship.toString().split('.')[0]}`;
|
||||
|
||||
return <div>
|
||||
<PowerManagement ship={ship} code={powerMarker} onChange={onChange} />
|
||||
<CostSection ship={ship} buildName={buildName} code={costMarker} />
|
||||
</div>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the profiles tab
|
||||
* @return {React.Component} Tab contents
|
||||
*/
|
||||
_profilesTab() {
|
||||
const { ship, opponent, cargo, fuel, eng, boost, engagementRange, opponentSys } = this.props;
|
||||
const { translate } = this.context.language;
|
||||
let realBoost = boost && ship.canBoost(cargo, fuel);
|
||||
Persist.setOutfittingTab('profiles');
|
||||
|
||||
const engineProfileMarker = `${ship.toString()}:${cargo}:${fuel}:${eng}:${realBoost}`;
|
||||
const fsdProfileMarker = `${ship.toString()}:${cargo}:${fuel}`;
|
||||
const movementMarker = `${ship.topSpeed}:${ship.pitch}:${ship.roll}:${ship.yaw}:${ship.canBoost(cargo, fuel)}`;
|
||||
const damageMarker = `${ship.toString()}:${opponent.toString()}:${engagementRange}:${opponentSys}`;
|
||||
|
||||
return <div>
|
||||
<div className='group third'>
|
||||
<h1>{translate('engine profile')}</h1>
|
||||
<EngineProfile ship={ship} marker={engineProfileMarker} fuel={fuel} cargo={cargo} eng={eng} boost={realBoost} />
|
||||
</div>
|
||||
|
||||
<div className='group third'>
|
||||
<h1>{translate('fsd profile')}</h1>
|
||||
<FSDProfile ship={ship} marker={fsdProfileMarker} fuel={fuel} cargo={cargo} />
|
||||
</div>
|
||||
|
||||
<div className='group third'>
|
||||
<h1>{translate('movement profile')}</h1>
|
||||
<Movement marker={movementMarker} ship={ship} boost={boost} eng={eng} cargo={cargo} fuel={fuel} />
|
||||
</div>
|
||||
|
||||
<div className='group half'>
|
||||
<h1>{translate('damage to opponent\'s shields')}</h1>
|
||||
<WeaponDamageChart marker={damageMarker} ship={ship} opponent={opponent} opponentSys={opponentSys} hull={false} engagementRange={engagementRange} />
|
||||
</div>
|
||||
|
||||
<div className='group half'>
|
||||
<h1>{translate('damage to opponent\'s hull')}</h1>
|
||||
<WeaponDamageChart marker={damageMarker} ship={ship} opponent={opponent} opponentSys={opponentSys} hull={true} engagementRange={engagementRange} />
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the offence tab
|
||||
* @return {React.Component} Tab contents
|
||||
*/
|
||||
_offenceTab() {
|
||||
const { ship, sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild, opponentSys } = this.props;
|
||||
Persist.setOutfittingTab('offence');
|
||||
|
||||
const marker = `${ship.toString()}${opponent.toString()}${opponentBuild}${engagementRange}${opponentSys}`;
|
||||
|
||||
return <div>
|
||||
<Offence marker={marker} ship={ship} opponent={opponent} wep={wep} opponentSys={opponentSys} engagementrange={engagementRange}/>
|
||||
</div>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the defence tab
|
||||
* @return {React.Component} Tab contents
|
||||
*/
|
||||
_defenceTab() {
|
||||
const { ship, sys, eng, wep, cargo, fuel, boost, engagementRange, opponent, opponentBuild, opponentWep } = this.props;
|
||||
Persist.setOutfittingTab('defence');
|
||||
|
||||
const marker = `${ship.toString()}${opponent.toString()}{opponentBuild}${engagementRange}${opponentWep}`;
|
||||
|
||||
return <div>
|
||||
<Defence marker={marker} ship={ship} opponent={opponent} sys={sys} opponentWep={opponentWep} engagementrange={engagementRange}/>
|
||||
</div>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the section
|
||||
* @return {React.Component} Contents
|
||||
*/
|
||||
render() {
|
||||
const tab = this.state.tab;
|
||||
const translate = this.context.language.translate;
|
||||
let tabSection;
|
||||
|
||||
switch (tab) {
|
||||
case 'power': tabSection = this._powerTab(); break;
|
||||
case 'profiles': tabSection = this._profilesTab(); break;
|
||||
case 'offence': tabSection = this._offenceTab(); break;
|
||||
case 'defence': tabSection = this._defenceTab(); break;
|
||||
}
|
||||
const { buildName, code, ship } = this.props;
|
||||
const { boost, cargo, fuel, pips, tab, engagementRange, opponent } = this.state;
|
||||
const { translate } = this.context.language;
|
||||
|
||||
const cargoCapacity = ship.get(CARGO_CAPACITY);
|
||||
const showCargoSlider = cargoCapacity > 0;
|
||||
return (
|
||||
<div className='group full' style={{ minHeight: '1000px' }}>
|
||||
<table className='tabs'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'power' })} onClick={this._showTab.bind(this, 'power')} >{translate('power and costs')}</th>
|
||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'profiles' })} onClick={this._showTab.bind(this, 'profiles')} >{translate('profiles')}</th>
|
||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'offence' })} onClick={this._showTab.bind(this, 'offence')} >{translate('offence')}</th>
|
||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'defence' })} onClick={this._showTab.bind(this, 'defence')} >{translate('defence')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
{tabSection}
|
||||
<div>
|
||||
{/* Control of ship and opponent */}
|
||||
<div className="group quarter">
|
||||
<h2 style={{ verticalAlign: 'middle', textAlign: 'center' }}>
|
||||
{translate('ship control')}
|
||||
</h2>
|
||||
<Boost boost={boost} onChange={(boost) => this.setState({ boost })} />
|
||||
</div>
|
||||
<div className="group quarter">
|
||||
<h2 style={{ verticalAlign: 'middle', textAlign: 'center' }}>
|
||||
{translate('opponent')}
|
||||
</h2>
|
||||
<ShipPicker ship={ship} onChange={(opponent) => this.setState({ opponent })} />
|
||||
</div>
|
||||
<div className={cn('group', { quarter: showCargoSlider, half: !showCargoSlider })}>
|
||||
<Fuel fuelCapacity={ship.get(FUEL_CAPACITY)} fuel={fuel}
|
||||
onChange={(fuel) => this.setState({ fuel })} />
|
||||
</div>
|
||||
{showCargoSlider ?
|
||||
<div className="group quarter">
|
||||
<Cargo cargoCapacity={cargoCapacity} cargo={cargo}
|
||||
onChange={(cargo) => this.setState({ cargo })} />
|
||||
</div> : null}
|
||||
<div className="group half">
|
||||
<Pips ship={ship} pips={pips} onChange={(pips) => this.setState({ pips })} />
|
||||
</div>
|
||||
<div className="group half">
|
||||
<EngagementRange ship={ship} engagementRange={engagementRange}
|
||||
onChange={(engagementRange) => this.setState({ engagementRange })} />
|
||||
</div>
|
||||
<div className='group full' style={{ minHeight: '1000px' }}>
|
||||
<table className='tabs'>
|
||||
{/* Select tab section */}
|
||||
<thead>
|
||||
<tr>
|
||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'power' })}
|
||||
onClick={this._showTab.bind(this, 'power')}>
|
||||
{translate('power and costs')}
|
||||
</th>
|
||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'profiles' })}
|
||||
onClick={this._showTab.bind(this, 'profiles')}>
|
||||
{translate('profiles')}</th>
|
||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'offence' })}
|
||||
onClick={this._showTab.bind(this, 'offence')}>
|
||||
{translate('offence')}
|
||||
</th>
|
||||
<th style={{ width:'25%' }} className={cn({ active: tab == 'defence' })}
|
||||
onClick={this._showTab.bind(this, 'defence')}>
|
||||
{translate('tab_defence')}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
</table>
|
||||
{/* Show selected tab */}
|
||||
{tab == 'power' ?
|
||||
<div>
|
||||
<PowerManagement ship={ship} code={code} />
|
||||
<CostSection ship={ship} buildName={buildName} code={code} />
|
||||
</div> : null}
|
||||
{tab == 'profiles' ?
|
||||
<div>
|
||||
<div className='group third'>
|
||||
<h1>{translate('engine profile')}</h1>
|
||||
<EngineProfile code={code} ship={ship} fuel={fuel} cargo={cargo} pips={pips} boost={boost} />
|
||||
</div>
|
||||
<div className='group third'>
|
||||
<h1>{translate('fsd profile')}</h1>
|
||||
<FSDProfile code={code} ship={ship} fuel={fuel} cargo={cargo} />
|
||||
</div>
|
||||
<div className='group third'>
|
||||
<h1>{translate('movement profile')}</h1>
|
||||
<Movement code={code} ship={ship} boost={boost} pips={pips} />
|
||||
</div>
|
||||
<div className='group half'>
|
||||
<h1>{translate('damage to opponent\'s shields')}</h1>
|
||||
<WeaponDamageChart code={code} ship={ship} opponentDefence={opponent.getShield()} engagementRange={engagementRange} />
|
||||
</div>
|
||||
<div className='group half'>
|
||||
<h1>{translate('damage to opponent\'s hull')}</h1>
|
||||
<WeaponDamageChart code={code} ship={ship} opponentDefence={opponent.getArmour()} engagementRange={engagementRange} />
|
||||
</div>
|
||||
</div> : null}
|
||||
{tab == 'offence' ?
|
||||
<div>
|
||||
<Offence code={code} ship={ship} opponent={opponent} engagementRange={engagementRange} />
|
||||
</div> : null}
|
||||
{tab == 'defence' ?
|
||||
<div>
|
||||
<Defence code={code} ship={ship} opponent={opponent} engagementRange={engagementRange} />
|
||||
</div> : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,97 +1,91 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import { nameComparator } from '../utils/SlotFunctions';
|
||||
import { Pip } from './SvgIcons';
|
||||
import LineChart from '../components/LineChart';
|
||||
import Slider from '../components/Slider';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import Module from '../shipyard/Module';
|
||||
import { autoBind } from 'react-extras';
|
||||
import { Ship } from 'ed-forge';
|
||||
|
||||
/**
|
||||
* Pips displays SYS/ENG/WEP pips and allows users to change them with key presses by clicking on the relevant area.
|
||||
@@ -15,10 +11,9 @@ import Module from '../shipyard/Module';
|
||||
*/
|
||||
export default class Pips extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
sys: PropTypes.number.isRequired,
|
||||
eng: PropTypes.number.isRequired,
|
||||
wep: PropTypes.number.isRequired,
|
||||
onChange: PropTypes.func.isRequired
|
||||
ship: PropTypes.instanceOf(Ship).isRequired,
|
||||
pips: PropTypes.object.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -28,9 +23,13 @@ export default class Pips extends TranslatedComponent {
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
const { sys, eng, wep } = props;
|
||||
autoBind(this);
|
||||
|
||||
this._keyDown = this._keyDown.bind(this);
|
||||
const { ship } = props;
|
||||
this._incSys = this._change(ship.incSys);
|
||||
this._incEng = this._change(ship.incEng);
|
||||
this._incWep = this._change(ship.incWep);
|
||||
this._reset = this._change(ship.pipsReset);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,181 +74,43 @@ export default class Pips extends TranslatedComponent {
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a click
|
||||
* @param {string} which Which item was clicked
|
||||
* Creates a function that handles pip assignment and call `onChance`.
|
||||
* @param {String} cb Callback that handles the actual pip assignment
|
||||
* @param {Boolean} isMc True when increase is by multi crew
|
||||
* @returns {Function} Function that handles pip assigment
|
||||
*/
|
||||
onClick(which) {
|
||||
if (which == 'SYS') {
|
||||
this._incSys();
|
||||
} else if (which == 'ENG') {
|
||||
this._incEng();
|
||||
} else if (which == 'WEP') {
|
||||
this._incWep();
|
||||
} else if (which == 'RST') {
|
||||
this._reset();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the capacitor
|
||||
*/
|
||||
_reset() {
|
||||
let { sys, eng, wep } = this.props;
|
||||
if (sys != 2 || eng != 2 || wep != 2) {
|
||||
sys = eng = wep = 2;
|
||||
this.props.onChange(sys, eng, wep);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the SYS capacitor
|
||||
*/
|
||||
_incSys() {
|
||||
let { sys, eng, wep } = this.props;
|
||||
|
||||
const required = Math.min(1, 4 - sys);
|
||||
if (required > 0) {
|
||||
if (required == 0.5) {
|
||||
// Take from whichever is larger
|
||||
if (eng > wep) {
|
||||
eng -= 0.5;
|
||||
sys += 0.5;
|
||||
} else {
|
||||
wep -= 0.5;
|
||||
sys += 0.5;
|
||||
}
|
||||
} else {
|
||||
// Required is 1 - take from both if possible
|
||||
if (eng == 0) {
|
||||
wep -= 1;
|
||||
sys += 1;
|
||||
} else if (wep == 0) {
|
||||
eng -= 1;
|
||||
sys += 1;
|
||||
} else {
|
||||
eng -= 0.5;
|
||||
wep -= 0.5;
|
||||
sys += 1;
|
||||
}
|
||||
}
|
||||
this.props.onChange(sys, eng, wep);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the ENG capacitor
|
||||
*/
|
||||
_incEng() {
|
||||
let { sys, eng, wep } = this.props;
|
||||
|
||||
const required = Math.min(1, 4 - eng);
|
||||
if (required > 0) {
|
||||
if (required == 0.5) {
|
||||
// Take from whichever is larger
|
||||
if (sys > wep) {
|
||||
sys -= 0.5;
|
||||
eng += 0.5;
|
||||
} else {
|
||||
wep -= 0.5;
|
||||
eng += 0.5;
|
||||
}
|
||||
} else {
|
||||
// Required is 1 - take from both if possible
|
||||
if (sys == 0) {
|
||||
wep -= 1;
|
||||
eng += 1;
|
||||
} else if (wep == 0) {
|
||||
sys -= 1;
|
||||
eng += 1;
|
||||
} else {
|
||||
sys -= 0.5;
|
||||
wep -= 0.5;
|
||||
eng += 1;
|
||||
}
|
||||
}
|
||||
this.props.onChange(sys, eng, wep);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the WEP capacitor
|
||||
*/
|
||||
_incWep() {
|
||||
let { sys, eng, wep } = this.props;
|
||||
|
||||
const required = Math.min(1, 4 - wep);
|
||||
if (required > 0) {
|
||||
if (required == 0.5) {
|
||||
// Take from whichever is larger
|
||||
if (sys > eng) {
|
||||
sys -= 0.5;
|
||||
wep += 0.5;
|
||||
} else {
|
||||
eng -= 0.5;
|
||||
wep += 0.5;
|
||||
}
|
||||
} else {
|
||||
// Required is 1 - take from both if possible
|
||||
if (sys == 0) {
|
||||
eng -= 1;
|
||||
wep += 1;
|
||||
} else if (eng == 0) {
|
||||
sys -= 1;
|
||||
wep += 1;
|
||||
} else {
|
||||
sys -= 0.5;
|
||||
eng -= 0.5;
|
||||
wep += 1;
|
||||
}
|
||||
}
|
||||
this.props.onChange(sys, eng, wep);
|
||||
}
|
||||
_change(cb, isMc) {
|
||||
return () => {
|
||||
cb(isMc);
|
||||
this.props.onChange(this.props.ship.getDistributorSettingsObject());
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the rendering for pips
|
||||
* @param {int} sys the SYS pips
|
||||
* @param {int} eng the ENG pips
|
||||
* @param {int} wep the WEP pips
|
||||
* @returns {Object} Object containing the rendering for the pips
|
||||
*/
|
||||
_renderPips(sys, eng, wep) {
|
||||
const pipsSvg = {};
|
||||
_renderPips() {
|
||||
const pipsSvg = {
|
||||
Sys: [],
|
||||
Eng: [],
|
||||
Wep: [],
|
||||
};
|
||||
|
||||
// SYS
|
||||
pipsSvg['SYS'] = [];
|
||||
for (let i = 0; i < Math.floor(sys); i++) {
|
||||
pipsSvg['SYS'].push(<Pip className='full' key={i} />);
|
||||
}
|
||||
if (sys > Math.floor(sys)) {
|
||||
pipsSvg['SYS'].push(<Pip className='half' key={'half'} />);
|
||||
}
|
||||
for (let i = Math.floor(sys + 0.5); i < 4; i++) {
|
||||
pipsSvg['SYS'].push(<Pip className='empty' key={i} />);
|
||||
}
|
||||
|
||||
// ENG
|
||||
pipsSvg['ENG'] = [];
|
||||
for (let i = 0; i < Math.floor(eng); i++) {
|
||||
pipsSvg['ENG'].push(<Pip className='full' key={i} />);
|
||||
}
|
||||
if (eng > Math.floor(eng)) {
|
||||
pipsSvg['ENG'].push(<Pip className='half' key={'half'} />);
|
||||
}
|
||||
for (let i = Math.floor(eng + 0.5); i < 4; i++) {
|
||||
pipsSvg['ENG'].push(<Pip className='empty' key={i} />);
|
||||
}
|
||||
|
||||
// WEP
|
||||
pipsSvg['WEP'] = [];
|
||||
for (let i = 0; i < Math.floor(wep); i++) {
|
||||
pipsSvg['WEP'].push(<Pip className='full' key={i} />);
|
||||
}
|
||||
if (wep > Math.floor(wep)) {
|
||||
pipsSvg['WEP'].push(<Pip className='half' key={'half'} />);
|
||||
}
|
||||
for (let i = Math.floor(wep + 0.5); i < 4; i++) {
|
||||
pipsSvg['WEP'].push(<Pip className='empty' key={i} />);
|
||||
for (let k in this.props.pips) {
|
||||
let { base, mc } = this.props.pips[k];
|
||||
for (let i = 0; i < Math.floor(base); i++) {
|
||||
pipsSvg[k].push(<Pip key={i} className='full' />);
|
||||
}
|
||||
if (base > Math.floor(base)) {
|
||||
pipsSvg[k].push(<Pip className='half' key={'half'} />);
|
||||
}
|
||||
for (let i = 0; i < mc; i++) {
|
||||
pipsSvg[k].push(<Pip key={base + i} className='mc' />);
|
||||
}
|
||||
for (let i = Math.ceil(base + mc); i < 4; i++) {
|
||||
pipsSvg[k].push(<Pip className='empty' key={i} />);
|
||||
}
|
||||
}
|
||||
|
||||
return pipsSvg;
|
||||
@@ -260,15 +121,10 @@ export default class Pips extends TranslatedComponent {
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { formats, translate, units } = this.context.language;
|
||||
const { sys, eng, wep } = this.props;
|
||||
const { ship } = this.props;
|
||||
const { translate } = this.context.language;
|
||||
|
||||
const onSysClicked = this.onClick.bind(this, 'SYS');
|
||||
const onEngClicked = this.onClick.bind(this, 'ENG');
|
||||
const onWepClicked = this.onClick.bind(this, 'WEP');
|
||||
const onRstClicked = this.onClick.bind(this, 'RST');
|
||||
|
||||
const pipsSvg = this._renderPips(sys, eng, wep);
|
||||
const pipsSvg = this._renderPips();
|
||||
return (
|
||||
<span id='pips'>
|
||||
<table>
|
||||
@@ -276,20 +132,40 @@ export default class Pips extends TranslatedComponent {
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td> </td>
|
||||
<td className='clickable' onClick={onEngClicked}>{pipsSvg['ENG']}</td>
|
||||
<td className='clickable' onClick={this._incEng}>{pipsSvg.Eng}</td>
|
||||
<td> </td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td className='clickable' onClick={onSysClicked}>{pipsSvg['SYS']}</td>
|
||||
<td className='clickable' onClick={onEngClicked}>{translate('ENG')}</td>
|
||||
<td className='clickable' onClick={onWepClicked}>{pipsSvg['WEP']}</td>
|
||||
<td className='clickable' onClick={this._incSys}>{pipsSvg.Sys}</td>
|
||||
<td className='clickable' onClick={this._incEng}>
|
||||
{translate('ENG')}
|
||||
</td>
|
||||
<td className='clickable' onClick={this._incWep}>{pipsSvg.Wep}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td className='clickable' onClick={onSysClicked}>{translate('SYS')}</td>
|
||||
<td className='clickable' onClick={onRstClicked}>{translate('RST')}</td>
|
||||
<td className='clickable' onClick={onWepClicked}>{translate('WEP')}</td>
|
||||
<td className='clickable' onClick={this._incSys}>
|
||||
{translate('SYS')}
|
||||
</td>
|
||||
<td className='clickable' onClick={this._reset}>
|
||||
{translate('RST')}
|
||||
</td>
|
||||
<td className='clickable' onClick={this._incWep}>
|
||||
{translate('WEP')}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td> </td>
|
||||
<td className='clickable' onClick={this._change(ship.incSys, true)}>
|
||||
<Pip className='mc' />
|
||||
</td>
|
||||
<td className='clickable' onClick={this._change(ship.incEng, true)}>
|
||||
<Pip className='mc' />
|
||||
</td>
|
||||
<td className='clickable' onClick={this._change(ship.incWep, true)}>
|
||||
<Pip className='mc' />
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -4,27 +4,25 @@ import * as d3 from 'd3';
|
||||
import cn from 'classnames';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { wrapCtxMenu } from '../utils/UtilityFunctions';
|
||||
import { Ship } from 'ed-forge';
|
||||
import { POWER_METRICS } from 'ed-forge/lib/src/ship-stats';
|
||||
import autoBind from 'auto-bind';
|
||||
|
||||
/**
|
||||
* Round to avoid floating point precision errors
|
||||
* Get the band-class.
|
||||
* @param {Boolean} selected Band selected
|
||||
* @param {number} sum Band power sum
|
||||
* @param {number} avail Total available power
|
||||
* @return {string} CSS Class name
|
||||
* @param {Number} relDraw Relative amount of power drawn by this band and
|
||||
* all prior
|
||||
* @return {string} CSS Class name
|
||||
*/
|
||||
function getClass(selected, sum, avail) {
|
||||
return selected ? 'secondary' : ((Math.round(sum * 100) / 100) >= avail) ? 'warning' : 'primary';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the # label for a Priority band
|
||||
* @param {number} val Priority Band Watt value
|
||||
* @param {number} index Priority Band index
|
||||
* @param {Function} wattScale Watt Scale function
|
||||
* @return {number} label / text
|
||||
*/
|
||||
function bandText(val, index, wattScale) {
|
||||
return (val > 0 && wattScale(val) > 13) ? index + 1 : null;
|
||||
function getClass(selected, relDraw) {
|
||||
if (selected) {
|
||||
return 'secondary';
|
||||
} else if (relDraw >= 1) {
|
||||
return 'warning';
|
||||
} else {
|
||||
return 'primary';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -32,12 +30,10 @@ function bandText(val, index, wattScale) {
|
||||
* Renders the SVG to simulate in-game power bands
|
||||
*/
|
||||
export default class PowerBands extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
bands: PropTypes.array.isRequired,
|
||||
available: PropTypes.number.isRequired,
|
||||
ship: PropTypes.instanceOf(Ship).isRequired,
|
||||
code: PropTypes.string.isRequired,
|
||||
width: PropTypes.number.isRequired,
|
||||
code: PropTypes.string,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -47,20 +43,16 @@ export default class PowerBands extends TranslatedComponent {
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
autoBind(this);
|
||||
this.wattScale = d3.scaleLinear();
|
||||
this.pctScale = d3.scaleLinear().domain([0, 1]);
|
||||
this.wattAxis = d3.axisTop(this.wattScale).tickSizeOuter(0).tickFormat(context.language.formats.r2);
|
||||
this.pctAxis = d3.axisBottom(this.pctScale).tickSizeOuter(0).tickFormat(context.language.formats.rPct);
|
||||
|
||||
this._updateDimensions = this._updateDimensions.bind(this);
|
||||
this._updateScales = this._updateScales.bind(this);
|
||||
this._selectNone = this._selectNone.bind(this);
|
||||
this._hidetip = () => this.context.tooltip();
|
||||
|
||||
let maxBand = props.bands[props.bands.length - 1];
|
||||
|
||||
this.profile = props.ship.getMetrics(POWER_METRICS);
|
||||
this.state = {
|
||||
maxPwr: Math.max(props.available, maxBand.retractedSum, maxBand.deployedSum),
|
||||
ret: {},
|
||||
dep: {}
|
||||
};
|
||||
@@ -84,8 +76,6 @@ export default class PowerBands extends TranslatedComponent {
|
||||
let mRight = Math.round(140 * scale);
|
||||
let innerWidth = props.width - mLeft - mRight;
|
||||
|
||||
this._updateScales(innerWidth, this.state.maxPwr, props.available);
|
||||
|
||||
this.setState({
|
||||
barHeight,
|
||||
innerHeight,
|
||||
@@ -141,41 +131,67 @@ export default class PowerBands extends TranslatedComponent {
|
||||
this.setState({ dep: Object.assign({}, dep) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Update scale
|
||||
* @param {number} innerWidth SVG innerwidth
|
||||
* @param {number} maxPwr Maximum power level MJ (deployed or available)
|
||||
* @param {number} available Available power MJ
|
||||
*/
|
||||
_updateScales(innerWidth, maxPwr, available) {
|
||||
this.wattScale.range([0, innerWidth]).domain([0, maxPwr]).clamp(true);
|
||||
this.pctScale.range([0, innerWidth]).domain([0, maxPwr / available]).clamp(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update state based on property and context changes
|
||||
* @param {Object} nextProps Incoming/Next properties
|
||||
* @param {Object} nextContext Incoming/Next context
|
||||
*/
|
||||
componentWillReceiveProps(nextProps, nextContext) {
|
||||
let { innerWidth, maxPwr } = this.state;
|
||||
let { language, sizeRatio } = this.context;
|
||||
let maxBand = nextProps.bands[nextProps.bands.length - 1];
|
||||
let nextMaxPwr = Math.max(nextProps.available, maxBand.retractedSum, maxBand.deployedSum);
|
||||
|
||||
if (language !== nextContext.language) {
|
||||
this.wattAxis.tickFormat(nextContext.language.formats.r2);
|
||||
this.pctAxis.tickFormat(nextContext.language.formats.rPct);
|
||||
}
|
||||
|
||||
if (maxPwr != nextMaxPwr) { // Update Axes if max power has changed
|
||||
this._updateScales(innerWidth, nextMaxPwr, nextProps.available);
|
||||
this.setState({ maxPwr: nextMaxPwr });
|
||||
} else if (nextProps.width != this.props.width || sizeRatio != nextContext.sizeRatio) {
|
||||
if (nextProps.width != this.props.width || sizeRatio != nextContext.sizeRatio) {
|
||||
this._updateDimensions(nextProps, nextContext.sizeRatio);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assemble bands for relative consumption array.
|
||||
* @param {Number[]} consumed Array of relative-consumption numbers
|
||||
* @param {object} selected Object mapping selected bands to 1
|
||||
* @param {Number} yOffset Offset in y-direction of the bar
|
||||
* @param {Function} onClick onClick callback
|
||||
* @returns {React.Component} Bands
|
||||
*/
|
||||
_consumedToBands(consumed, selected, yOffset, onClick) {
|
||||
const { state, wattScale } = this;
|
||||
const bands = [];
|
||||
let consumesPrev = 0;
|
||||
for (let i = 0; i < consumed.length; i++) {
|
||||
consumesPrev = consumed[i - 1] || consumesPrev;
|
||||
const consumes = consumed[i];
|
||||
|
||||
if (!consumes) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bands.push(<rect
|
||||
key={'b' + i}
|
||||
width={Math.ceil(Math.max(wattScale(consumes - consumesPrev), 0))}
|
||||
height={state.barHeight}
|
||||
x={wattScale(consumesPrev)}
|
||||
y={yOffset + 1}
|
||||
onClick={onClick.bind(this, i)}
|
||||
className={getClass(selected[i], consumes)}
|
||||
/>);
|
||||
|
||||
bands.push(<text
|
||||
key={'t' + i}
|
||||
dy='0.5em'
|
||||
textAnchor='middle'
|
||||
height={state.barHeight}
|
||||
x={wattScale(consumesPrev) + (wattScale(consumes - consumesPrev) / 2)}
|
||||
y={yOffset + (state.barHeight / 2)}
|
||||
onClick={onClick.bind(this, i)}
|
||||
className='primary-bg'>{i + 1}</text>
|
||||
);
|
||||
}
|
||||
return bands;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the power bands
|
||||
* @return {React.Component} Power bands
|
||||
@@ -185,78 +201,27 @@ export default class PowerBands extends TranslatedComponent {
|
||||
return null;
|
||||
}
|
||||
|
||||
let { wattScale, pctScale, context, props, state } = this;
|
||||
let { pctScale, context, props, state } = this;
|
||||
let { translate, formats } = context.language;
|
||||
let { f2, pct1 } = formats; // wattFmt, pctFmt
|
||||
let { available, bands } = props;
|
||||
let { innerWidth, ret, dep } = state;
|
||||
let pwrWarningClass = cn('threshold', { exceeded: bands[0].retractedSum > available * 0.4 });
|
||||
let deployed = [];
|
||||
let retracted = [];
|
||||
let { ship } = props;
|
||||
let { innerWidth, ret, dep, barHeight } = state;
|
||||
|
||||
let {
|
||||
consumed, generated, relativeConsumed, relativeConsumedRetracted
|
||||
} = ship.getMetrics(POWER_METRICS);
|
||||
let maxPwr = Math.max(consumed, generated);
|
||||
let retSum = relativeConsumedRetracted[relativeConsumedRetracted.length - 1];
|
||||
let depSum = relativeConsumed[relativeConsumed.length - 1];
|
||||
|
||||
this.wattScale.range([0, innerWidth]).domain([0, 1]).clamp(true);
|
||||
this.pctScale.range([0, innerWidth]).domain([0, maxPwr / generated]).clamp(true);
|
||||
|
||||
let pwrWarningClass = cn('threshold', { exceeded: retSum > generated * 0.4 });
|
||||
let retracted = this._consumedToBands(relativeConsumedRetracted, ret, 0, this._selectRet);
|
||||
let deployed = this._consumedToBands(relativeConsumed, dep, barHeight, this._selectDep);
|
||||
let retSelected = Object.keys(ret).length > 0;
|
||||
let depSelected = Object.keys(dep).length > 0;
|
||||
let retSum = 0;
|
||||
let depSum = 0;
|
||||
|
||||
for (let i = 0; i < bands.length; i++) {
|
||||
let b = bands[i];
|
||||
retSum += (!retSelected || ret[i]) ? b.retracted : 0;
|
||||
depSum += (!depSelected || dep[i]) ? b.deployed + b.retracted : 0;
|
||||
|
||||
if (b.retracted > 0) {
|
||||
let retLbl = bandText(b.retracted, i, wattScale);
|
||||
|
||||
retracted.push(<rect
|
||||
key={'rB' + i}
|
||||
width={Math.ceil(Math.max(wattScale(b.retracted), 0))}
|
||||
height={state.barHeight}
|
||||
x={Math.floor(Math.max(wattScale(b.retractedSum) - wattScale(b.retracted), 0))}
|
||||
y={1}
|
||||
onClick={this._selectRet.bind(this, i)}
|
||||
className={getClass(ret[i], b.retractedSum, available)}
|
||||
/>);
|
||||
|
||||
if (retLbl) {
|
||||
retracted.push(<text
|
||||
key={'rT' + i}
|
||||
dy='0.5em'
|
||||
textAnchor='middle'
|
||||
height={state.barHeight}
|
||||
x={wattScale(b.retractedSum) - (wattScale(b.retracted) / 2)}
|
||||
y={state.retY}
|
||||
onClick={this._selectRet.bind(this, i)}
|
||||
className='primary-bg'>{retLbl}</text>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (b.retracted > 0 || b.deployed > 0) {
|
||||
let depLbl = bandText(b.deployed + b.retracted, i, wattScale);
|
||||
|
||||
deployed.push(<rect
|
||||
key={'dB' + i}
|
||||
width={Math.ceil(Math.max(wattScale(b.deployed + b.retracted), 0))}
|
||||
height={state.barHeight}
|
||||
x={Math.floor(Math.max(wattScale(b.deployedSum) - wattScale(b.retracted) - wattScale(b.deployed), 0))}
|
||||
y={state.barHeight + 1}
|
||||
onClick={this._selectDep.bind(this, i)}
|
||||
className={getClass(dep[i], b.deployedSum, available)}
|
||||
/>);
|
||||
|
||||
if (depLbl) {
|
||||
deployed.push(<text
|
||||
key={'dT' + i}
|
||||
dy='0.5em'
|
||||
textAnchor='middle'
|
||||
height={state.barHeight}
|
||||
x={wattScale(b.deployedSum) - ((wattScale(b.retracted) + wattScale(b.deployed)) / 2)}
|
||||
y={state.depY}
|
||||
onClick={this._selectDep.bind(this, i)}
|
||||
className='primary-bg'>{depLbl}</text>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<svg style={{ marginTop: '1em', width: '100%', height: state.height }} onContextMenu={wrapCtxMenu(this._selectNone)}>
|
||||
@@ -272,8 +237,8 @@ export default class PowerBands extends TranslatedComponent {
|
||||
<line x1={pctScale(0.4)} x2={pctScale(0.4)} y1='0' y2={state.innerHeight} className={pwrWarningClass} />
|
||||
<text dy='0.5em' x='-3' y={state.retY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'retracted')} onMouseLeave={this._hidetip}>{translate('ret')}</text>
|
||||
<text dy='0.5em' x='-3' y={state.depY} className='primary upp' textAnchor='end' onMouseOver={this.context.termtip.bind(null, 'deployed', { orientation: 's', cap: 1 })} onMouseLeave={this._hidetip}>{translate('dep')}</text>
|
||||
<text dy='0.5em' x={innerWidth + 5} y={state.retY} className={getClass(retSelected, retSum, available)}>{f2(Math.max(0, retSum))} ({pct1(Math.max(0, retSum / available))})</text>
|
||||
<text dy='0.5em' x={innerWidth + 5} y={state.depY} className={getClass(depSelected, depSum, available)}>{f2(Math.max(0, depSum))} ({pct1(Math.max(0, depSum / available))})</text>
|
||||
<text dy='0.5em' x={innerWidth + 5} y={state.retY} className={getClass(retSelected, retSum, generated)}>{f2(Math.max(0, retSum * generated))} ({pct1(Math.max(0, retSum))})</text>
|
||||
<text dy='0.5em' x={innerWidth + 5} y={state.depY} className={getClass(depSelected, depSum, generated)}>{f2(Math.max(0, depSum * generated))} ({pct1(Math.max(0, depSum))})</text>
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
|
||||
@@ -3,24 +3,49 @@ import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import PowerBands from './PowerBands';
|
||||
import { slotName, slotComparator } from '../utils/SlotFunctions';
|
||||
import { Power, NoPower } from './SvgIcons';
|
||||
import autoBind from 'auto-bind';
|
||||
import { Ship, Module } from 'ed-forge';
|
||||
|
||||
const POWER = [
|
||||
null,
|
||||
null,
|
||||
<NoPower className='icon warning' />,
|
||||
<Power className='secondary-disabled' />
|
||||
];
|
||||
/**
|
||||
* Makes a comparison based on the order `false < undefined < true` (fut) and
|
||||
* maps it to `[-1, 0, 1]`.
|
||||
* @param {boolean} a Bool or undefined
|
||||
* @param {boolean} b Bool or undefined
|
||||
* @returns {number} Comparison
|
||||
*/
|
||||
function futComp(a, b) {
|
||||
switch (a) {
|
||||
case false: return (b === false ? 0 : 1);
|
||||
// The next else-expression maps false to -1 and true to 1
|
||||
case undefined: return (b === undefined ? 0 : 2 * Number(b) - 1);
|
||||
case true: return (b === true ? 0 : -1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the enabled-icon.
|
||||
* @param {boolean} enabled Is the module enabled?
|
||||
* @returns {React.Component} Enabled icon.
|
||||
*/
|
||||
function getPowerIcon(enabled) {
|
||||
if (enabled === undefined) {
|
||||
return null;
|
||||
}
|
||||
if (enabled) {
|
||||
return <Power className='secondary-disabled' />;
|
||||
} else {
|
||||
return <NoPower className='icon warning' />;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Power Management Section
|
||||
*/
|
||||
export default class PowerManagement extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
ship: PropTypes.object.isRequired,
|
||||
ship: PropTypes.instanceOf(Ship).isRequired,
|
||||
code: PropTypes.string.isRequired,
|
||||
onChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -29,19 +54,17 @@ export default class PowerManagement extends TranslatedComponent {
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._renderPowerRows = this._renderPowerRows.bind(this);
|
||||
this._updateWidth = this._updateWidth.bind(this);
|
||||
this._sort = this._sort.bind(this);
|
||||
autoBind(this);
|
||||
|
||||
this.state = {
|
||||
predicate: 'pwr',
|
||||
desc: false,
|
||||
desc: true,
|
||||
width: 0
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the sort order and sort
|
||||
* Set the sort order
|
||||
* @param {string} predicate Sort predicate
|
||||
*/
|
||||
_sortOrder(predicate) {
|
||||
@@ -53,50 +76,51 @@ export default class PowerManagement extends TranslatedComponent {
|
||||
desc = true;
|
||||
}
|
||||
|
||||
this._sort(this.props.ship, predicate, desc);
|
||||
this.setState({ predicate, desc });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the power list
|
||||
* @param {Ship} ship Ship instance
|
||||
* @param {string} predicate Sort predicate
|
||||
* @param {Boolean} desc Sort order descending
|
||||
* @param {Module[]} modules Modules to sort
|
||||
* @returns {Module[]} Sorted modules
|
||||
*/
|
||||
_sort(ship, predicate, desc) {
|
||||
let powerList = ship.powerList;
|
||||
let comp = slotComparator.bind(null, this.context.language.translate);
|
||||
|
||||
_sortAndFilter(modules) {
|
||||
modules = modules.filter((m) => m.get('powerdraw') >= 0);
|
||||
let { translate } = this.context.language;
|
||||
const { predicate, desc } = this.state;
|
||||
let comp;
|
||||
switch (predicate) {
|
||||
case 'n': comp = comp(null, desc); break;
|
||||
case 't': comp = comp((a, b) => a.type.localeCompare(b.type), desc); break;
|
||||
case 'pri': comp = comp((a, b) => a.priority - b.priority, desc); break;
|
||||
case 'pwr': comp = comp((a, b) => a.m.getPowerUsage() - b.m.getPowerUsage(), desc); break;
|
||||
case 'r': comp = comp((a, b) => ship.getSlotStatus(a) - ship.getSlotStatus(b), desc); break;
|
||||
case 'd': comp = comp((a, b) => ship.getSlotStatus(a, true) - ship.getSlotStatus(b, true), desc); break;
|
||||
case 'n': comp = (a, b) => translate(a.readMeta('type')).localeCompare(
|
||||
translate(b.readMeta('type'))
|
||||
); break;
|
||||
// case 't': comp = comp((a, b) => a.type.localeCompare(b.type), desc); break;
|
||||
case 'pri': comp = (a, b) => a.getPowerPriority() - b.getPowerPriority(); break;
|
||||
case 'pwr': comp = (a, b) => a.get('powerdraw') - b.get('powerdraw'); break;
|
||||
case 'r': comp = (a, b) => futComp(a.isPowered().retracted, b.isPowered().retracted); break;
|
||||
case 'd': comp = (a, b) => futComp(a.isPowered().deployed, b.isPowered().deployed); break;
|
||||
}
|
||||
|
||||
powerList.sort(comp);
|
||||
modules.sort(comp);
|
||||
if (desc) {
|
||||
modules.reverse();
|
||||
}
|
||||
return modules;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update slot priority
|
||||
* @param {Object} slot Slot model
|
||||
* @param {number} inc increment / decrement
|
||||
* Creates a callback that changes the power priority for the given module
|
||||
* based on the given delta.
|
||||
* @param {Module} m Module to set the priority for
|
||||
* @param {Number} delta Delta to set
|
||||
* @returns {Function} Callback
|
||||
*/
|
||||
_priority(slot, inc) {
|
||||
if (this.props.ship.setSlotPriority(slot, slot.priority + inc)) {
|
||||
this.props.onChange();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle slot active/inactive
|
||||
* @param {Object} slot Slot model
|
||||
*/
|
||||
_toggleEnabled(slot) {
|
||||
this.props.ship.setSlotEnabled(slot, !slot.enabled);
|
||||
this.props.onChange();
|
||||
_prioCb(m, delta) {
|
||||
return () => {
|
||||
const prio = m.getPowerPriority();
|
||||
const newPrio = Math.max(0, prio + delta);
|
||||
if (0 <= newPrio) {
|
||||
m.setPowerPriority(newPrio);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -110,36 +134,35 @@ export default class PowerManagement extends TranslatedComponent {
|
||||
_renderPowerRows(ship, translate, pwr, pct) {
|
||||
let powerRows = [];
|
||||
|
||||
for (let i = 0, l = ship.powerList.length; i < l; i++) {
|
||||
let slot = ship.powerList[i];
|
||||
|
||||
if (slot.m && slot.m.getPowerUsage() > 0) {
|
||||
let m = slot.m;
|
||||
let toggleEnabled = this._toggleEnabled.bind(this, slot);
|
||||
let retractedElem = null, deployedElem = null;
|
||||
|
||||
if (slot.enabled) {
|
||||
retractedElem = <td className='ptr upp' onClick={toggleEnabled}>{POWER[ship.getSlotStatus(slot, false)]}</td>;
|
||||
deployedElem = <td className='ptr upp' onClick={toggleEnabled}>{POWER[ship.getSlotStatus(slot, true)]}</td>;
|
||||
} else {
|
||||
retractedElem = <td className='ptr disabled upp' colSpan='2' onClick={toggleEnabled}>{translate('disabled')}</td>;
|
||||
}
|
||||
|
||||
powerRows.push(<tr key={i} className={cn('highlight', { disabled: !slot.enabled })}>
|
||||
<td className='ptr' style={{ width: '1em' }} onClick={toggleEnabled}>{m.class + m.rating}</td>
|
||||
<td className='ptr le shorten cap' onClick={toggleEnabled}>{slotName(translate, slot)}</td>
|
||||
<td className='ptr' onClick={toggleEnabled}><u>{translate(slot.type)}</u></td>
|
||||
<td>
|
||||
<span className='flip ptr btn' onClick={this._priority.bind(this, slot, -1)}>►</span>
|
||||
{' ' + (slot.priority + 1) + ' '}
|
||||
<span className='ptr btn' onClick={this._priority.bind(this, slot, 1)}>►</span>
|
||||
</td>
|
||||
<td className='ri ptr' style={{ width: '3.25em' }} onClick={toggleEnabled}>{pwr(m.getPowerUsage())}</td>
|
||||
<td className='ri ptr' style={{ width: '3em' }} onClick={toggleEnabled}><u>{pct(m.getPowerUsage() / ship.powerAvailable)}</u></td>
|
||||
{retractedElem}
|
||||
{deployedElem}
|
||||
</tr>);
|
||||
let modules = this._sortAndFilter(ship.getModules());
|
||||
for (let m of modules) {
|
||||
let retractedElem = null, deployedElem = null;
|
||||
const flipEnabled = () => m.setEnabled();
|
||||
if (m.isEnabled()) {
|
||||
let powered = m.isPowered();
|
||||
retractedElem = <td className='ptr upp' onClick={flipEnabled}>{getPowerIcon(powered.retracted)}</td>;
|
||||
deployedElem = <td className='ptr upp' onClick={flipEnabled}>{getPowerIcon(powered.deployed)}</td>;
|
||||
} else {
|
||||
retractedElem = <td className='ptr disabled upp' colSpan='2' onClick={flipEnabled}>{translate('disabled')}</td>;
|
||||
}
|
||||
|
||||
const slot = m.getSlot();
|
||||
powerRows.push(<tr key={slot} className={cn('highlight', { disabled: !m.isEnabled() })}>
|
||||
<td className='ptr' style={{ width: '1em' }} onClick={flipEnabled}>{String(m.getClass()) + m.getRating()}</td>
|
||||
<td className='ptr le shorten cap' onClick={flipEnabled}>{translate(m.readMeta('type'))}</td>
|
||||
{/* <td className='ptr' onClick={flipEnabled}><u>{translate(slot.type)}</u></td> */}
|
||||
<td>
|
||||
<span className='flip ptr btn' onClick={this._prioCb(m, -1)}>►</span>
|
||||
{' ' + (m.getPowerPriority() + 1) + ' '}
|
||||
<span className='ptr btn' onClick={this._prioCb(m, 1)}>►</span>
|
||||
</td>
|
||||
<td className='ri ptr' style={{ width: '3.25em' }} onClick={flipEnabled}>{pwr(m.get('powerdraw'))}</td>
|
||||
<td className='ri ptr' style={{ width: '3em' }} onClick={flipEnabled}>
|
||||
<u>{pct(m.get('powerdraw') / ship.getPowerPlant().get('powercapacity'))}</u>
|
||||
</td>
|
||||
{retractedElem}
|
||||
{deployedElem}
|
||||
</tr>);
|
||||
}
|
||||
return powerRows;
|
||||
}
|
||||
@@ -155,7 +178,6 @@ export default class PowerManagement extends TranslatedComponent {
|
||||
* Add listeners when about to mount and sort power list
|
||||
*/
|
||||
componentWillMount() {
|
||||
this._sort(this.props.ship, this.state.predicate, this.state.desc);
|
||||
this.resizeListener = this.context.onWindowResize(this._updateWidth);
|
||||
}
|
||||
|
||||
@@ -166,17 +188,6 @@ export default class PowerManagement extends TranslatedComponent {
|
||||
this._updateWidth();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort power list if the ship instance has changed
|
||||
* @param {Object} nextProps Incoming/Next properties
|
||||
* @param {Object} nextState Incoming/Next state
|
||||
*/
|
||||
componentWillUpdate(nextProps, nextState) {
|
||||
if (this.props.ship != nextProps.ship) {
|
||||
this._sort(nextProps.ship, nextState.predicate, nextState.desc);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove listeners on unmount
|
||||
*/
|
||||
@@ -191,39 +202,38 @@ export default class PowerManagement extends TranslatedComponent {
|
||||
render() {
|
||||
let { ship, code } = this.props;
|
||||
let { translate, formats } = this.context.language;
|
||||
let pwr = formats.f2;
|
||||
let pp = ship.standard[0].m;
|
||||
let sortOrder = this._sortOrder;
|
||||
let pp = ship.getPowerPlant();
|
||||
|
||||
return (
|
||||
<div ref={node => this.node = node} className='group half' id='componentPriority'>
|
||||
<table style={{ width: '100%' }}>
|
||||
<thead>
|
||||
<tr className='main'>
|
||||
<th colSpan='2' className='sortable le' onClick={sortOrder.bind(this, 'n')} >{translate('module')}</th>
|
||||
<th style={{ width: '3em' }} className='sortable' onClick={sortOrder.bind(this, 't')} >{translate('type')}</th>
|
||||
<th style={{ width: '4em' }} className='sortable' onClick={sortOrder.bind(this, 'pri')} >{translate('pri')}</th>
|
||||
<th colSpan='2' className='sortable' onClick={sortOrder.bind(this, 'pwr')} >{translate('PWR')}</th>
|
||||
<th style={{ width: '3em' }} className='sortable' onClick={sortOrder.bind(this, 'r')} >{translate('ret')}</th>
|
||||
<th style={{ width: '3em' }} className='sortable' onClick={sortOrder.bind(this, 'd')} >{translate('dep')}</th>
|
||||
<th colSpan='2' className='sortable le' onClick={() => this._sortOrder('n')} >{translate('module')}</th>
|
||||
{/* <th style={{ width: '3em' }} className='sortable' onClick={() => this._sortOrder('t')} >{translate('type')}</th> */}
|
||||
<th style={{ width: '4em' }} className='sortable' onClick={() => this._sortOrder('pri')} >{translate('pri')}</th>
|
||||
<th colSpan='2' className='sortable' onClick={() => this._sortOrder('pwr')} >{translate('PWR')}</th>
|
||||
<th style={{ width: '3em' }} className='sortable' onClick={() => this._sortOrder('r')} >{translate('ret')}</th>
|
||||
<th style={{ width: '3em' }} className='sortable' onClick={() => this._sortOrder('d')} >{translate('dep')}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{pp.class + pp.rating}</td>
|
||||
<td>{String(pp.getClass()) + pp.getRating()}</td>
|
||||
<td className='le shorten cap' >{translate('pp')}</td>
|
||||
<td><u >{translate('SYS')}</u></td>
|
||||
<td>1</td>
|
||||
<td className='ri'>{pwr(pp.getPowerGeneration())}</td>
|
||||
<td className='ri'>{formats.f2(pp.get('powercapacity'))}</td>
|
||||
<td className='ri'><u>100%</u></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr><td style={{ lineHeight:0 }} colSpan='8'><hr style={{ margin: '0 0 3px', background: '#ff8c0d', border: 0, height: 1 }} /></td></tr>
|
||||
{this._renderPowerRows(ship, translate, pwr, formats.pct1)}
|
||||
<tr><td style={{ lineHeight:0 }} colSpan='8'>
|
||||
<hr style={{ margin: '0 0 3px', background: '#ff8c0d', border: 0, height: 1 }} />
|
||||
</td></tr>
|
||||
{this._renderPowerRows(ship, translate, formats.f2, formats.pct1)}
|
||||
</tbody>
|
||||
</table>
|
||||
<PowerBands width={this.state.width} code={code} available={pp.getPowerGeneration()} bands={ship.priorityBands} />
|
||||
<PowerBands width={this.state.width} ship={ship} code={code} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import Ship from '../shipyard/Ship';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import { Rocket } from './SvgIcons';
|
||||
import Persist from '../stores/Persist';
|
||||
import cn from 'classnames';
|
||||
import { Factory, Ship } from 'ed-forge';
|
||||
import autoBind from 'auto-bind';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
/**
|
||||
* Ship picker
|
||||
@@ -14,40 +15,49 @@ import cn from 'classnames';
|
||||
export default class ShipPicker extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
onChange: PropTypes.func.isRequired,
|
||||
ship: PropTypes.string.isRequired,
|
||||
build: PropTypes.string
|
||||
ship: PropTypes.instanceOf(Ship).isRequired,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
ship: 'eagle'
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
super(props);
|
||||
|
||||
this.shipOrder = Object.keys(Ships).sort();
|
||||
this._toggleMenu = this._toggleMenu.bind(this);
|
||||
this._closeMenu = this._closeMenu.bind(this);
|
||||
|
||||
this.state = { menuOpen: false };
|
||||
autoBind(this);
|
||||
this.state = {
|
||||
menuOpen: false,
|
||||
opponent: {
|
||||
self: true,
|
||||
type: props.ship.getShipType(),
|
||||
stock: false,
|
||||
id: undefined,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Update ship
|
||||
* @param {object} ship the ship
|
||||
* @param {string} build the build, if present
|
||||
* @param {boolean} self True to compare with ship itself
|
||||
* @param {object} type The ship type
|
||||
* @param {boolean} stock True to compare with a stock version of given type
|
||||
* @param {string} id The build's stored ID
|
||||
*/
|
||||
_shipChange(ship, build) {
|
||||
this._closeMenu();
|
||||
|
||||
// Ensure that the ship has changed
|
||||
if (ship !== this.props.ship || build !== this.props.build) {
|
||||
this.props.onChange(ship, build);
|
||||
_shipChange(self, type, stock = false, id = null) {
|
||||
const opponent = { self, type, stock, id };
|
||||
if (isEqual(opponent, this.state.opponent)) {
|
||||
this.setState({ menuOpen: false });
|
||||
} else {
|
||||
const { onChange } = this.props;
|
||||
if (self) {
|
||||
onChange(this.props.ship);
|
||||
} else if (stock) {
|
||||
onChange(Factory.newShip(type));
|
||||
} else {
|
||||
onChange(new Ship(Persist.getBuild(type, id)));
|
||||
}
|
||||
this.setState({ menuOpen: false, opponent });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,26 +66,41 @@ export default class ShipPicker extends TranslatedComponent {
|
||||
* @returns {object} the picker menu
|
||||
*/
|
||||
_renderPickerMenu() {
|
||||
const { ship, build } = this.props;
|
||||
const _shipChange = this._shipChange;
|
||||
const builds = Persist.getBuilds();
|
||||
const buildList = [];
|
||||
for (let shipId of this.shipOrder) {
|
||||
const shipBuilds = [];
|
||||
// Add stock build
|
||||
const stockSelected = (ship == shipId && !build);
|
||||
shipBuilds.push(<li key={shipId} className={ cn({ 'selected': stockSelected })} onClick={_shipChange.bind(this, shipId, null)}>Stock</li>);
|
||||
if (builds[shipId]) {
|
||||
let buildNameOrder = Object.keys(builds[shipId]).sort();
|
||||
for (let buildName of buildNameOrder) {
|
||||
const buildSelected = ship === shipId && build === buildName;
|
||||
shipBuilds.push(<li key={shipId + '-' + buildName} className={ cn({ 'selected': buildSelected })} onClick={_shipChange.bind(this, shipId, buildName)}>{buildName}</li>);
|
||||
}
|
||||
}
|
||||
buildList.push(<ul key={shipId} className='block'>{Ships[shipId].properties.name}{shipBuilds}</ul>);
|
||||
const { menuOpen } = this.state;
|
||||
if (!menuOpen) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return buildList;
|
||||
const { translate } = this.context.language;
|
||||
const { self, type, stock, id } = this.state.opponent;
|
||||
return <div className='menu-list' onClick={(e) => e.stopPropagation()}>
|
||||
<div className='quad'>
|
||||
{Factory.getAllShipTypes().sort().map((shipType) =>
|
||||
<ul key={shipType} className='block'>
|
||||
{translate(shipType)}
|
||||
{/* Add stock build */}
|
||||
<li key={shipType}
|
||||
onClick={this._shipChange.bind(this, false, shipType, true)}
|
||||
className={cn({ selected: stock && type === shipType })}>
|
||||
{translate('stock')}
|
||||
</li>
|
||||
{Persist.getBuildsNamesFor(shipType).sort().map((storedId) =>
|
||||
<li key={`${shipType}-${storedId}`}
|
||||
onClick={this._shipChange.bind(this, false, shipType, false, storedId)}
|
||||
className={ cn({ selected: type === shipType && id === storedId })}>
|
||||
{storedId}
|
||||
</li>)}
|
||||
{/* Add ship itself */}
|
||||
{(this.props.ship.getShipType() === shipType ?
|
||||
<li key='self'
|
||||
onClick={this._shipChange.bind(this, true, shipType)}
|
||||
className={cn({ selected: self })}>
|
||||
{translate('THIS_SHIP')}
|
||||
</li> :
|
||||
null)}
|
||||
</ul>)}
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -86,40 +111,35 @@ export default class ShipPicker extends TranslatedComponent {
|
||||
this.setState({ menuOpen: !menuOpen });
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the menu
|
||||
*/
|
||||
_closeMenu() {
|
||||
const { menuOpen } = this.state;
|
||||
if (menuOpen) {
|
||||
this._toggleMenu();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render picker
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
||||
const { formats, translate, units } = language;
|
||||
const { ship, build } = this.props;
|
||||
const { translate } = this.context.language;
|
||||
const { ship } = this.props;
|
||||
const { menuOpen } = this.state;
|
||||
const { self, type, stock, id } = this.state.opponent;
|
||||
|
||||
let label;
|
||||
if (self) {
|
||||
label = translate('THIS_SHIP');
|
||||
} else if (stock) {
|
||||
label = translate('stock');
|
||||
} else {
|
||||
label = id;
|
||||
}
|
||||
|
||||
const shipString = ship + ': ' + (build ? build : translate('stock'));
|
||||
return (
|
||||
<div className='shippicker' onClick={ (e) => e.stopPropagation() }>
|
||||
<div className='menu'>
|
||||
<div className={cn('menu-header', { selected: menuOpen })} onClick={this._toggleMenu}>
|
||||
<span><Rocket className='warning' /></span>
|
||||
<span className='menu-item-label'>{shipString}</span>
|
||||
<span className='menu-item-label'>
|
||||
{`${translate(type)}: ${label}`}
|
||||
</span>
|
||||
</div>
|
||||
{ menuOpen ?
|
||||
<div className='menu-list' onClick={ (e) => e.stopPropagation() }>
|
||||
<div className='quad'>
|
||||
{this._renderPickerMenu()}
|
||||
</div>
|
||||
</div> : null }
|
||||
{this._renderPickerMenu()}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,101 +1,267 @@
|
||||
import autoBind from 'auto-bind';
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import cn from 'classnames';
|
||||
import { Warning } from './SvgIcons';
|
||||
import * as Calc from '../shipyard/Calculations';
|
||||
|
||||
import { ShipProps } from 'ed-forge';
|
||||
import { BOOST_INTERVAL, MINIMUM_MASS } from 'ed-forge/lib/src/ship-stats';
|
||||
const {
|
||||
SPEED, BOOST_SPEED, DAMAGE_METRICS, JUMP_METRICS, SHIELD_METRICS,
|
||||
ARMOUR_METRICS, CARGO_CAPACITY, FUEL_CAPACITY, UNLADEN_MASS, LADEN_MASS,
|
||||
MODULE_PROTECTION_METRICS, PASSENGER_CAPACITY
|
||||
} = ShipProps;
|
||||
|
||||
/**
|
||||
* Ship Summary Table / Stats
|
||||
*/
|
||||
export default class ShipSummaryTable extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
ship: PropTypes.object.isRequired,
|
||||
cargo: PropTypes.number.isRequired,
|
||||
fuel: PropTypes.number.isRequired,
|
||||
marker: PropTypes.string.isRequired,
|
||||
code: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
/**
|
||||
* The ShipSummaryTable constructor
|
||||
* @param {Object} props The props
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
autoBind(this);
|
||||
this.state = {
|
||||
shieldColour: 'blue'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the table
|
||||
* @return {React.Component} Summary table
|
||||
*/
|
||||
render() {
|
||||
const { ship, cargo, fuel } = this.props;
|
||||
const { ship } = 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 { time, int, f1, f2 } = formats;
|
||||
let hide = tooltip.bind(null, null);
|
||||
|
||||
const shieldGenerator = ship.findInternalByGroup('sg');
|
||||
const sgClassNames = cn({ warning: shieldGenerator && !ship.shield, muted: !shieldGenerator });
|
||||
const sgTooltip = shieldGenerator ? 'TT_SUMMARY_SHIELDS' : 'TT_SUMMARY_SHIELDS_NONFUNCTIONAL';
|
||||
const timeToDrain = Calc.timeToDrainWep(ship, 4);
|
||||
const canThrust = ship.canThrust(cargo, fuel);
|
||||
const speed = ship.get(SPEED);
|
||||
const shipBoost = ship.get(BOOST_SPEED);
|
||||
const boostInterval = ship.get(BOOST_INTERVAL);
|
||||
const canThrust = 0 < speed;
|
||||
const canBoost = canThrust && !isNaN(shipBoost);
|
||||
const speedTooltip = canThrust ? 'TT_SUMMARY_SPEED' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
|
||||
const canBoost = ship.canBoost(cargo, fuel);
|
||||
const boostTooltip = canBoost ? 'TT_SUMMARY_BOOST' : canThrust ? 'TT_SUMMARY_BOOST_NONFUNCTIONAL' : 'TT_SUMMARY_SPEED_NONFUNCTIONAL';
|
||||
|
||||
const sgMetrics = ship.get(SHIELD_METRICS);
|
||||
const armourMetrics = ship.get(ARMOUR_METRICS);
|
||||
const damageMetrics = ship.get(DAMAGE_METRICS);
|
||||
const moduleProtectionMetrics = ship.get(MODULE_PROTECTION_METRICS);
|
||||
const timeToDrain = damageMetrics.timeToDrain;
|
||||
|
||||
const shieldGenerator = ship.getShieldGenerator();
|
||||
const sgClassNames = cn({
|
||||
warning: shieldGenerator && !shieldGenerator.isEnabled(),
|
||||
muted: !shieldGenerator,
|
||||
});
|
||||
const sgTooltip = shieldGenerator ? 'TT_SUMMARY_SHIELDS' : 'TT_SUMMARY_SHIELDS_NONFUNCTIONAL';
|
||||
const sgType = shieldGenerator ? shieldGenerator.readMeta('type') : undefined;
|
||||
let shieldColour = 'blue';
|
||||
switch (sgType) {
|
||||
case 'biweaveshieldgen': shieldColour = 'purple'; break;
|
||||
case 'prismaticshieldgen': shieldColour = 'green'; break;
|
||||
}
|
||||
this.state = { shieldColour };
|
||||
|
||||
const jumpRangeMetrics = ship.getMetrics(JUMP_METRICS);
|
||||
|
||||
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': speed == 0 }) }>{translate('speed')}</th>
|
||||
<th rowSpan={2} className={ cn({ 'bg-warning-disabled': !canBoost }) }>{translate('boost')}</th>
|
||||
<th colSpan={5} className={ cn({ 'bg-warning-disabled': jumpRangeMetrics.jumpRangeCurrent == 0 }) }>{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} onMouseEnter={termtip.bind(null, 'passenger capacity', { cap: 0 })} onMouseLeave={hide}>{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_INTERVAL', { cap: 0 })} onMouseLeave={hide} rowSpan={2}>{translate('boost interval')}</th>
|
||||
{/* TODO: Resting heat */}
|
||||
{/* <th rowSpan={2}>{translate('resting heat (Beta)')}</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(speed)}{u['m/s']}</span> :
|
||||
<span className='warning'>0<Warning/></span>
|
||||
}</td>
|
||||
<td onMouseEnter={termtip.bind(null, boostTooltip, { cap: 0 })}
|
||||
onMouseLeave={hide}
|
||||
>{canBoost ?
|
||||
<span>{int(shipBoost)}{u['m/s']}</span> :
|
||||
<span className='warning'>0<Warning/></span>
|
||||
}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_MAX_SINGLE_JUMP', { cap: 0 })}
|
||||
onMouseLeave={hide}
|
||||
>{<span>{f2(jumpRangeMetrics.jumpRangeMax)}{u.LY}</span>}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_SINGLE_JUMP', { cap: 0 })}
|
||||
onMouseLeave={hide}
|
||||
>{<span>{f2(jumpRangeMetrics.jumpRangeUnladen)}{u.LY}</span>}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_SINGLE_JUMP', { cap: 0 })}
|
||||
onMouseLeave={hide}
|
||||
>{<span>{f2(jumpRangeMetrics.jumpRangeLaden)}{u.LY}</span>}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_TOTAL_JUMP', { cap: 0 })}
|
||||
onMouseLeave={hide}
|
||||
>{<span>{f2(jumpRangeMetrics.totalRangeUnladen)}{u.LY}</span>}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_TOTAL_JUMP', { cap: 0 })}
|
||||
onMouseLeave={hide}
|
||||
>{<span>{f2(jumpRangeMetrics.totalRangeLaden)}{u.LY}</span>}</td>
|
||||
<td className={sgClassNames}
|
||||
onMouseEnter={termtip.bind(null, sgTooltip, { cap: 0 })}
|
||||
onMouseLeave={hide}
|
||||
>{int(sgMetrics.shieldStrength)}{u.MJ}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_INTEGRITY', { cap: 0 })}
|
||||
onMouseLeave={hide}
|
||||
>{int(armourMetrics.armour)}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_DPS', { cap: 0 })}
|
||||
onMouseLeave={hide}
|
||||
>{f1(damageMetrics.dps)}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_EPS', { cap: 0 })}
|
||||
onMouseLeave={hide}
|
||||
>{f1(damageMetrics.eps)}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_TTD', { cap: 0 })}
|
||||
onMouseLeave={hide}
|
||||
>{timeToDrain === Infinity ? '∞' : time(timeToDrain)}</td>
|
||||
{/* <td>{f1(ship.totalHps)}</td> */}
|
||||
<td>{ship.get(CARGO_CAPACITY)}{u.T}</td>
|
||||
<td>{ship.get(PASSENGER_CAPACITY)}</td>
|
||||
<td>{ship.get(FUEL_CAPACITY)}{u.T}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_HULL_MASS', { cap: 0 })}
|
||||
onMouseLeave={hide}
|
||||
>{ship.readProp('hullmass')}{u.T}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_UNLADEN_MASS', { cap: 0 })}
|
||||
onMouseLeave={hide}
|
||||
>{int(ship.get(MINIMUM_MASS))}{u.T}</td>
|
||||
<td onMouseEnter={termtip.bind(null, 'TT_SUMMARY_LADEN_MASS', { cap: 0 })}
|
||||
onMouseLeave={hide}
|
||||
>{int(ship.get(LADEN_MASS))}{u.T}</td>
|
||||
<td>{int(ship.readProp('hardness'))}</td>
|
||||
<td>{ship.readMeta('crew')}</td>
|
||||
<td>{ship.readProp('masslock')}</td>
|
||||
<td>{time(boostInterval)}</td>
|
||||
{/* TODO: resting heat */}
|
||||
{/* <td>{NaN}</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(sgType || 'No Shield')}</td>
|
||||
<td>{formats.pct1(1 - sgMetrics.explosive.damageMultiplier)}</td>
|
||||
<td>{formats.pct1(1 - sgMetrics.kinetic.damageMultiplier)}</td>
|
||||
<td>{formats.pct1(1 - sgMetrics.thermal.damageMultiplier)}</td>
|
||||
<td></td>
|
||||
|
||||
<td>{int(sgMetrics.shieldStrength || 0)}{u.MJ}</td>
|
||||
<td>{int(sgMetrics.shieldStrength / sgMetrics.explosive.damageMultiplier || 0)}{u.MJ}</td>
|
||||
<td>{int(sgMetrics.shieldStrength / sgMetrics.kinetic.damageMultiplier || 0)}{u.MJ}</td>
|
||||
<td>{int(sgMetrics.shieldStrength / sgMetrics.thermal.damageMultiplier || 0)}{u.MJ}</td>
|
||||
<td></td>
|
||||
<td>{isNaN(sgMetrics.recover) ? translate('Never') : formats.time(sgMetrics.recover)}</td>
|
||||
<td>{isNaN(sgMetrics.recharge) ? 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.getAlloys().readMeta('type') || 'No Armour')}</td>
|
||||
<td>{formats.pct1(1 - armourMetrics.explosive.damageMultiplier)}</td>
|
||||
<td>{formats.pct1(1 - armourMetrics.kinetic.damageMultiplier)}</td>
|
||||
<td>{formats.pct1(1 - armourMetrics.thermal.damageMultiplier)}</td>
|
||||
<td>{formats.pct1(1 - armourMetrics.caustic.damageMultiplier)}</td>
|
||||
<td>{int(armourMetrics.armour)}</td>
|
||||
<td>{int(armourMetrics.armour / armourMetrics.explosive.damageMultiplier)}</td>
|
||||
<td>{int(armourMetrics.armour / armourMetrics.kinetic.damageMultiplier)}</td>
|
||||
<td>{int(armourMetrics.armour / armourMetrics.thermal.damageMultiplier)}</td>
|
||||
<td>{int(armourMetrics.armour / armourMetrics.caustic.damageMultiplier)}</td>
|
||||
<td>{int(moduleProtectionMetrics.moduleArmour)}</td>
|
||||
<td>{formats.pct1(1 - moduleProtectionMetrics.moduleProtection)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,166 +1,160 @@
|
||||
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';
|
||||
import autoBind from 'auto-bind';
|
||||
|
||||
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);
|
||||
autoBind(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 <div><svg
|
||||
onMouseUp={this._up} onMouseEnter={this._enter.bind(this)} onMouseMove={this._move} 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>
|
||||
</div>;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,30 +2,37 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import cn from 'classnames';
|
||||
import { ListModifications, Modified } from './SvgIcons';
|
||||
import AvailableModulesMenu from './AvailableModulesMenu';
|
||||
import ModificationsMenu from './ModificationsMenu';
|
||||
import { diffDetails } from '../utils/SlotFunctions';
|
||||
import { wrapCtxMenu } from '../utils/UtilityFunctions';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
import { stopCtxPropagation, wrapCtxMenu } from '../utils/UtilityFunctions';
|
||||
import { blueprintTooltip } from '../utils/BlueprintFunctions';
|
||||
import { Module } from 'ed-forge';
|
||||
import { TYPES } from 'ed-forge/lib/src/data/slots';
|
||||
import autoBind from 'auto-bind';
|
||||
import { toPairs } from 'lodash';
|
||||
|
||||
const HARDPOINT_SLOT_LABELS = {
|
||||
1: 'S',
|
||||
2: 'M',
|
||||
3: 'L',
|
||||
4: 'H',
|
||||
};
|
||||
|
||||
/**
|
||||
* Abstract Slot
|
||||
*/
|
||||
export default class Slot extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
availableModules: PropTypes.func.isRequired,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
onOpen: PropTypes.func.isRequired,
|
||||
maxClass: PropTypes.number.isRequired,
|
||||
selected: PropTypes.bool,
|
||||
m: PropTypes.object,
|
||||
ship: PropTypes.object.isRequired,
|
||||
eligible: PropTypes.object,
|
||||
currentMenu: PropTypes.any,
|
||||
hideSearch: PropTypes.bool,
|
||||
m: PropTypes.instanceOf(Module),
|
||||
warning: PropTypes.func,
|
||||
drag: PropTypes.func,
|
||||
drop: PropTypes.func,
|
||||
dropClass: PropTypes.string
|
||||
dropClass: PropTypes.string,
|
||||
propsToShow: PropTypes.object.isRequired,
|
||||
onPropToggle: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -34,23 +41,122 @@ export default class Slot extends TranslatedComponent {
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this._modificationsSelected = false;
|
||||
|
||||
this._contextMenu = wrapCtxMenu(this._contextMenu.bind(this));
|
||||
this._getMaxClassLabel = this._getMaxClassLabel.bind(this);
|
||||
autoBind(this);
|
||||
this.state = { menuIndex: 0 };
|
||||
}
|
||||
|
||||
// Must be implemented by subclasses:
|
||||
// _getSlotDetails()
|
||||
/**
|
||||
* Opens a menu while setting state.
|
||||
* @param {Object} newMenuIndex New menu index
|
||||
* @param {Event} event Event object
|
||||
*/
|
||||
_openMenu(newMenuIndex, event) {
|
||||
const slotName = this.props.m.getSlot();
|
||||
if (
|
||||
this.props.currentMenu === slotName &&
|
||||
newMenuIndex === this.state.menuIndex
|
||||
) {
|
||||
this.context.closeMenu();
|
||||
} {
|
||||
this.setState({ menuIndex: newMenuIndex });
|
||||
this.context.openMenu(slotName);
|
||||
}
|
||||
// If we don't stop event propagation, the underlying divs also might
|
||||
// get clicked which would open up other menus
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the CSS class name for the slot. Can/should be overriden
|
||||
* as necessary.
|
||||
* @return {string} CSS Class name
|
||||
* Generate the slot contents
|
||||
* @return {React.Component} Slot contents
|
||||
*/
|
||||
_getClassNames() {
|
||||
return null;
|
||||
_getSlotDetails() {
|
||||
const { m, propsToShow } = this.props;
|
||||
let { termtip, tooltip, language } = this.context;
|
||||
const { translate, units, formats } = language;
|
||||
|
||||
if (m.isEmpty()) {
|
||||
return <div className="empty">
|
||||
{translate(
|
||||
m.isOnSlot(TYPES.MILITARY) ? 'emptyrestricted' : 'empty'
|
||||
)}
|
||||
</div>;
|
||||
} else {
|
||||
let classRating = m.getClassRating();
|
||||
let { drag, drop } = this.props;
|
||||
|
||||
// Modifications tooltip shows blueprint and grade, if available
|
||||
let modTT = translate('modified');
|
||||
const blueprint = m.getBlueprint();
|
||||
const experimental = m.getExperimental();
|
||||
const grade = m.getBlueprintGrade();
|
||||
if (blueprint) {
|
||||
modTT = `${translate(blueprint)} ${translate('grade')}: ${grade}`;
|
||||
if (experimental) {
|
||||
modTT += `, ${translate(experimental)}`;
|
||||
}
|
||||
modTT = (
|
||||
<div>
|
||||
<div>{modTT}</div>
|
||||
{blueprintTooltip(language, m)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
let mass = m.get('mass') || m.get('cargo') || m.get('fuel') || 0;
|
||||
return (
|
||||
<div
|
||||
className={cn('details', { disabled: !m.isEnabled() })}
|
||||
draggable="true"
|
||||
onDragStart={drag}
|
||||
onDragEnd={drop}
|
||||
>
|
||||
<div className={'cb'}>
|
||||
<div className={'l'}>
|
||||
{classRating} {translate(m.readMeta('type'))}
|
||||
{blueprint && (
|
||||
<span
|
||||
onMouseOver={termtip.bind(null, modTT)}
|
||||
onMouseOut={tooltip.bind(null, null)}
|
||||
>
|
||||
<Modified />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
{propsToShow.mass ?
|
||||
<div className={'r'}>
|
||||
{formats.round(mass)}
|
||||
{units.T}
|
||||
</div> : null}
|
||||
</div>
|
||||
<div className={'cb'}>
|
||||
{toPairs(propsToShow).sort().map(([prop, show]) => {
|
||||
const { unit, value } = m.getFormatted(prop, true);
|
||||
// Don't show mass again; it's already shown on the top right
|
||||
// corner of a slot
|
||||
if (!show || isNaN(value) || prop == 'mass') {
|
||||
return null;
|
||||
} else {
|
||||
return (<div className='l'>
|
||||
{translate(prop)}: {formats.round(value)}{unit}
|
||||
</div>);
|
||||
}
|
||||
})}
|
||||
{(m.getApplicableBlueprints() || []).length > 0 ? (
|
||||
<div className="r">
|
||||
<button onClick={this._openMenu.bind(this, 1)}
|
||||
onContextMenu={stopCtxPropagation}
|
||||
onMouseOver={termtip.bind(null, translate('modifications'))}
|
||||
onMouseOut={tooltip.bind(null, null)}
|
||||
>
|
||||
<ListModifications />
|
||||
</button>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -59,7 +165,19 @@ export default class Slot extends TranslatedComponent {
|
||||
* @return {string} label
|
||||
*/
|
||||
_getMaxClassLabel() {
|
||||
return this.props.maxClass;
|
||||
const { m } = this.props;
|
||||
let size = m.getSizeNum();
|
||||
switch (true) {
|
||||
case m.getSlot() === 'armour':
|
||||
return '';
|
||||
case size === 0:
|
||||
// This can also happen for armour but that case was handled above
|
||||
return 'U';
|
||||
case m.isOnSlot(TYPES.HARDPOINT):
|
||||
return HARDPOINT_SLOT_LABELS[size];
|
||||
default:
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -69,7 +187,13 @@ export default class Slot extends TranslatedComponent {
|
||||
_contextMenu(event) {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
this.props.onSelect(null,null);
|
||||
const { m } = this.props;
|
||||
m.reset();
|
||||
if (this.props.currentMenu === m.getSlot()) {
|
||||
this.context.closeMenu();
|
||||
} else {
|
||||
this.forceUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -79,61 +203,41 @@ export default class Slot extends TranslatedComponent {
|
||||
render() {
|
||||
let language = this.context.language;
|
||||
let translate = language.translate;
|
||||
let { ship, m, dropClass, dragOver, onOpen, onChange, selected, eligible, onSelect, warning, availableModules } = this.props;
|
||||
let slotDetails, modificationsMarker, menu;
|
||||
|
||||
if (!selected) {
|
||||
// If not selected then sure that modifications flag is unset
|
||||
this._modificationsSelected = false;
|
||||
}
|
||||
|
||||
if (m) {
|
||||
slotDetails = this._getSlotDetails(m, translate, language.formats, language.units); // Must be implemented by sub classes
|
||||
modificationsMarker = JSON.stringify(m);
|
||||
} else {
|
||||
slotDetails = <div className={'empty'}>{translate(eligible ? 'emptyrestricted' : 'empty')}</div>;
|
||||
modificationsMarker = '';
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
if (this._modificationsSelected) {
|
||||
menu = <ModificationsMenu
|
||||
className={this._getClassNames()}
|
||||
onChange={onChange}
|
||||
ship={ship}
|
||||
m={m}
|
||||
marker={modificationsMarker}
|
||||
/>;
|
||||
} else {
|
||||
menu = <AvailableModulesMenu
|
||||
className={this._getClassNames()}
|
||||
modules={availableModules()}
|
||||
shipMass={ship.hullMass}
|
||||
m={m}
|
||||
onSelect={onSelect}
|
||||
warning={warning}
|
||||
diffDetails={diffDetails.bind(ship, this.context.language)}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
let {
|
||||
currentMenu, m, dropClass, dragOver, warning, hideSearch, propsToShow,
|
||||
onPropToggle,
|
||||
} = this.props;
|
||||
const { menuIndex } = this.state;
|
||||
|
||||
// TODO: implement touch dragging
|
||||
|
||||
const selected = currentMenu === m.getSlot();
|
||||
return (
|
||||
<div className={cn('slot', dropClass, { selected })} onClick={onOpen} onContextMenu={this._contextMenu} onDragOver={dragOver}>
|
||||
<div className='details-container'>
|
||||
<div className='sz'>{this._getMaxClassLabel(translate)}</div>
|
||||
{slotDetails}
|
||||
</div>
|
||||
{menu}
|
||||
<div
|
||||
className={cn('slot', dropClass, { selected })}
|
||||
onContextMenu={this._contextMenu}
|
||||
onDragOver={dragOver} tabIndex="0"
|
||||
onClick={this._openMenu.bind(this, 0)}
|
||||
>
|
||||
<div className={cn(
|
||||
'details-container',
|
||||
{ warning: warning && warning(m) },
|
||||
)}>
|
||||
<div className="sz">{this._getMaxClassLabel(translate)}</div>
|
||||
{this._getSlotDetails()}
|
||||
</div>
|
||||
{selected && menuIndex === 0 &&
|
||||
<AvailableModulesMenu
|
||||
m={m} hideSearch={hideSearch}
|
||||
onSelect={(item) => {
|
||||
m.setItem(item);
|
||||
this.context.closeMenu();
|
||||
}}
|
||||
warning={warning}
|
||||
/>}
|
||||
{selected && menuIndex === 1 &&
|
||||
<ModificationsMenu m={m} propsToShow={propsToShow}
|
||||
onPropToggle={onPropToggle} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the modifications flag when selecting the modifications icon
|
||||
*/
|
||||
_toggleModifications() {
|
||||
this._modificationsSelected = !this._modificationsSelected;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,42 +2,35 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { wrapCtxMenu } from '../utils/UtilityFunctions';
|
||||
import { canMount } from '../utils/SlotFunctions';
|
||||
import { Equalizer } from '../components/SvgIcons';
|
||||
import cn from 'classnames';
|
||||
import { Ship } from 'ed-forge';
|
||||
import autoBind from 'auto-bind';
|
||||
const browser = require('detect-browser');
|
||||
|
||||
/**
|
||||
* Abstract Slot Section
|
||||
*/
|
||||
export default class SlotSection extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
ship: PropTypes.object.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
ship: PropTypes.instanceOf(Ship),
|
||||
code: PropTypes.string.isRequired,
|
||||
togglePwr: PropTypes.func
|
||||
togglePwr: PropTypes.func,
|
||||
propsToShow: PropTypes.object.isRequired,
|
||||
onPropToggle: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
* @param {string} sectionId Section DOM Id
|
||||
* @param {string} sectionName Section name
|
||||
*/
|
||||
constructor(props, context, sectionId, sectionName) {
|
||||
constructor(props, sectionName) {
|
||||
super(props);
|
||||
this.sectionId = sectionId;
|
||||
autoBind(this);
|
||||
|
||||
this.sectionName = sectionName;
|
||||
|
||||
this._getSlots = this._getSlots.bind(this);
|
||||
this._selectModule = this._selectModule.bind(this);
|
||||
this._getSectionMenu = this._getSectionMenu.bind(this);
|
||||
this._contextMenu = this._contextMenu.bind(this);
|
||||
this._drop = this._drop.bind(this);
|
||||
this._dragOverNone = this._dragOverNone.bind(this);
|
||||
this._close = this._close.bind(this);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
@@ -45,31 +38,7 @@ export default class SlotSection extends TranslatedComponent {
|
||||
// _getSlots()
|
||||
// _getSectionMenu()
|
||||
// _contextMenu()
|
||||
|
||||
/**
|
||||
* Open a menu
|
||||
* @param {string} menu Menu name
|
||||
* @param {SyntheticEvent} event Event
|
||||
*/
|
||||
_openMenu(menu, event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
if (this.props.currentMenu === menu) {
|
||||
menu = null;
|
||||
}
|
||||
this.context.openMenu(menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount/Use the specified module in the slot
|
||||
* @param {Object} slot Slot
|
||||
* @param {Object} m Selected module
|
||||
*/
|
||||
_selectModule(slot, m) {
|
||||
this.props.ship.use(slot, m, false);
|
||||
this.props.onChange();
|
||||
this._close();
|
||||
}
|
||||
// componentDidUpdate(prevProps)
|
||||
|
||||
/**
|
||||
* Slot Drag Handler
|
||||
@@ -129,24 +98,65 @@ 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);
|
||||
let experimentalNum = this.props.ship.hardpoints
|
||||
.filter(s => s.m && s.m.experimental).length;
|
||||
// Remove the module on the last slot if we now exceed the number of
|
||||
// experimentals allowed
|
||||
if (m.experimental && 4 < experimentalNum) {
|
||||
this.props.ship.updateStats(originSlot, null, originSlot.m);
|
||||
originSlot.m = null; // Empty the slot
|
||||
originSlot.discountedCost = 0;
|
||||
}
|
||||
// Copy power info
|
||||
targetSlot.enabled = originSlot.enabled;
|
||||
targetSlot.priority = originSlot.priority;
|
||||
}
|
||||
} 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.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 });
|
||||
@@ -179,6 +189,17 @@ export default class SlotSection extends TranslatedComponent {
|
||||
return 'ineligible'; // Cannot be dropped / invalid drop slot
|
||||
}
|
||||
|
||||
_open(newMenu, event) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
const { currentMenu } = this.props;
|
||||
if (currentMenu === newMenu) {
|
||||
this.context.closeMenu();
|
||||
} else {
|
||||
this.context.openMenu(newMenu);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close current menu
|
||||
*/
|
||||
@@ -195,14 +216,13 @@ export default class SlotSection extends TranslatedComponent {
|
||||
render() {
|
||||
let translate = this.context.language.translate;
|
||||
let sectionMenuOpened = this.props.currentMenu === this.sectionName;
|
||||
let open = this._openMenu.bind(this, this.sectionName);
|
||||
let ctx = wrapCtxMenu(this._contextMenu);
|
||||
|
||||
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>
|
||||
{sectionMenuOpened ? this._getSectionMenu(translate, this.props.ship) : null }
|
||||
<div className="group" onDragLeave={this._dragOverNone}>
|
||||
<div className={cn('section-menu', { selected: sectionMenuOpened })}
|
||||
onContextMenu={wrapCtxMenu(this._contextMenu)} onClick={this._open.bind(this, this.sectionName)}>
|
||||
<h1 tabIndex="0">{translate(this.sectionName)}<Equalizer/></h1>
|
||||
{sectionMenuOpened && this._getSectionMenu()}
|
||||
</div>
|
||||
{this._getSlots()}
|
||||
</div>
|
||||
|
||||
@@ -1,136 +0,0 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import cn from 'classnames';
|
||||
import Persist from '../stores/Persist';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { diffDetails } from '../utils/SlotFunctions';
|
||||
import AvailableModulesMenu from './AvailableModulesMenu';
|
||||
import ModificationsMenu from './ModificationsMenu';
|
||||
import * as ModuleUtils from '../shipyard/ModuleUtils';
|
||||
import { ListModifications, Modified } from './SvgIcons';
|
||||
import { Modifications } from 'coriolis-data/dist';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
import { blueprintTooltip } from '../utils/BlueprintFunctions';
|
||||
|
||||
/**
|
||||
* Standard Slot
|
||||
*/
|
||||
export default class StandardSlot extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
slot: PropTypes.object,
|
||||
modules: PropTypes.array.isRequired,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
onOpen: PropTypes.func.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
ship: PropTypes.object.isRequired,
|
||||
selected: PropTypes.bool,
|
||||
warning: PropTypes.func,
|
||||
};
|
||||
|
||||
/**
|
||||
* Construct the slot
|
||||
* @param {object} props Object properties
|
||||
*/
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this._modificationsSelected = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the slot
|
||||
* @return {React.Component} Slot component
|
||||
*/
|
||||
render() {
|
||||
let { termtip, tooltip } = this.context;
|
||||
let { translate, formats, units } = this.context.language;
|
||||
let { modules, slot, selected, warning, onSelect, onChange, ship } = this.props;
|
||||
let m = slot.m;
|
||||
let classRating = m.class + m.rating;
|
||||
let menu;
|
||||
let validMods = m == null ? [] : (Modifications.modules[m.grp].modifications || []);
|
||||
let showModuleResistances = Persist.showModuleResistances();
|
||||
let mass = m.getMass() || m.cargo || m.fuel || 0;
|
||||
|
||||
// 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;
|
||||
modTT = (
|
||||
<div>
|
||||
<div>{modTT}</div>
|
||||
{blueprintTooltip(translate, m.blueprint.grades[m.blueprint.grade], null, m.grp, m)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!selected) {
|
||||
// If not selected then sure that modifications flag is unset
|
||||
this._modificationsSelected = false;
|
||||
}
|
||||
|
||||
const modificationsMarker = JSON.stringify(m);
|
||||
|
||||
if (selected) {
|
||||
if (this._modificationsSelected) {
|
||||
menu = <ModificationsMenu
|
||||
className='standard'
|
||||
onChange={onChange}
|
||||
ship={ship}
|
||||
m={m}
|
||||
marker={modificationsMarker}
|
||||
/>;
|
||||
} else {
|
||||
menu = <AvailableModulesMenu
|
||||
className='standard'
|
||||
modules={modules}
|
||||
shipMass={ModuleUtils.isShieldGenerator(m.grp) ? ship.hullMass : ship.unladenMass}
|
||||
m={m}
|
||||
onSelect={onSelect}
|
||||
warning={warning}
|
||||
diffDetails={diffDetails.bind(ship, this.context.language)}
|
||||
/>;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cn('slot', { selected: this.props.selected })} onClick={this.props.onOpen} onContextMenu={stopCtxPropagation}>
|
||||
<div className={cn('details-container', { warning: warning && warning(slot.m) })}>
|
||||
<div className={'sz'}>{slot.maxClass}</div>
|
||||
<div>
|
||||
<div className={'l'}>{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? <span className='r' onMouseOver={termtip.bind(null, modTT)} onMouseOut={tooltip.bind(null, null)}><Modified /></span> : null }</div>
|
||||
<div className={'r'}>{formats.round(mass)}{units.T}</div>
|
||||
<div/>
|
||||
<div className={'cb'}>
|
||||
{ m.getMinMass() ? <div className='l'>{translate('minimum mass')}: {formats.int(m.getMinMass())}{units.T}</div> : null }
|
||||
{ m.getOptMass() ? <div className='l'>{translate('optimal mass')}: {formats.int(m.getOptMass())}{units.T}</div> : null }
|
||||
{ m.getMaxMass() ? <div className='l'>{translate('max mass')}: {formats.int(m.getMaxMass())}{units.T}</div> : null }
|
||||
{ m.getOptMul() ? <div className='l'>{translate('optimal multiplier')}: {formats.rPct(m.getOptMul())}</div> : null }
|
||||
{ m.getRange() ? <div className='l'>{translate('range', m.grp)}: {formats.f2(m.getRange())}{units.km}</div> : null }
|
||||
{ m.time ? <div className='l'>{translate('time')}: {formats.time(m.time)}</div> : null }
|
||||
{ m.getThermalEfficiency() ? <div className='l'>{translate('efficiency')}: {formats.f2(m.getThermalEfficiency())}</div> : null }
|
||||
{ m.getPowerGeneration() > 0 ? <div className='l'>{translate('pgen')}: {formats.f1(m.getPowerGeneration())}{units.MW}</div> : null }
|
||||
{ m.getMaxFuelPerJump() ? <div className='l'>{translate('max')} {translate('fuel')}: {formats.f1(m.getMaxFuelPerJump())}{units.T}</div> : null }
|
||||
{ m.getWeaponsCapacity() ? <div className='l'>{translate('WEP')}: {formats.f1(m.getWeaponsCapacity())}{units.MJ} / {formats.f1(m.getWeaponsRechargeRate())}{units.MW}</div> : null }
|
||||
{ m.getSystemsCapacity() ? <div className='l'>{translate('SYS')}: {formats.f1(m.getSystemsCapacity())}{units.MJ} / {formats.f1(m.getSystemsRechargeRate())}{units.MW}</div> : null }
|
||||
{ m.getEnginesCapacity() ? <div className='l'>{translate('ENG')}: {formats.f1(m.getEnginesCapacity())}{units.MJ} / {formats.f1(m.getEnginesRechargeRate())}{units.MW}</div> : null }
|
||||
{ showModuleResistances && m.getExplosiveResistance() ? <div className='l'>{translate('explres')}: {formats.pct(m.getExplosiveResistance())}</div> : null }
|
||||
{ showModuleResistances && m.getKineticResistance() ? <div className='l'>{translate('kinres')}: {formats.pct(m.getKineticResistance())}</div> : null }
|
||||
{ showModuleResistances && m.getThermalResistance() ? <div className='l'>{translate('thermres')}: {formats.pct(m.getThermalResistance())}</div> : null }
|
||||
{ m.getIntegrity() ? <div className='l'>{translate('integrity')}: {formats.int(m.getIntegrity())}</div> : null }
|
||||
{ validMods.length > 0 ? <div className='r' ><button onClick={this._toggleModifications.bind(this)} onContextMenu={stopCtxPropagation} onMouseOver={termtip.bind(null, 'modifications')} onMouseOut={tooltip.bind(null, null)}><ListModifications /></button></div> : null }
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{menu}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle the modifications flag when selecting the modifications icon
|
||||
*/
|
||||
_toggleModifications() {
|
||||
this._modificationsSelected = !this._modificationsSelected;
|
||||
}
|
||||
}
|
||||
@@ -1,102 +1,67 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import SlotSection from './SlotSection';
|
||||
import StandardSlot from './StandardSlot';
|
||||
import Module from '../shipyard/Module';
|
||||
import * as ShipRoles from '../shipyard/ShipRoles';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
import Slot from './Slot';
|
||||
import autoBind from 'auto-bind';
|
||||
import { stopCtxPropagation, moduleGet } from '../utils/UtilityFunctions';
|
||||
import { ShipProps, Module } from 'ed-forge';
|
||||
import { getModuleInfo } from 'ed-forge/lib/src/data/items';
|
||||
const { CONSUMED_RETR, LADEN_MASS } = ShipProps;
|
||||
|
||||
/**
|
||||
* Standard Slot section
|
||||
*/
|
||||
export default class StandardSlotSection extends SlotSection {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props, context, 'standard', 'core internal');
|
||||
this._optimizeStandard = this._optimizeStandard.bind(this);
|
||||
this._selectBulkhead = this._selectBulkhead.bind(this);
|
||||
constructor(props) {
|
||||
super(props, 'core internal');
|
||||
autoBind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the lightest/optimal available standard modules
|
||||
* Resets all modules of the ship
|
||||
*/
|
||||
_optimizeStandard() {
|
||||
this.props.ship.useLightestStandard();
|
||||
this.props.onChange();
|
||||
_emptyAll() {
|
||||
this.props.ship.getModules().forEach((slot) => slot.reset());
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill all standard slots with the specificed rating (using max class)
|
||||
* @param {Boolean} shielded True if shield generator should be included
|
||||
* @param {integer} bulkheadIndex Bulkhead to use see Constants.BulkheadNames
|
||||
* Sets all modules to a specific rating
|
||||
* @param {string} rating Module rating to set
|
||||
* @param {string} fsdPPException Custom rating for FSD
|
||||
*/
|
||||
_multiPurpose(shielded, bulkheadIndex) {
|
||||
ShipRoles.multiPurpose(this.props.ship, shielded, bulkheadIndex);
|
||||
this.props.onChange();
|
||||
_nRated(rating, fsdPPException) {
|
||||
const { ship } = this.props;
|
||||
const pp = ship.getPowerPlant();
|
||||
pp.setItem('powerplant', pp.getSize(), fsdPPException || rating);
|
||||
const eng = ship.getThrusters();
|
||||
eng.setItem('thrusters', eng.getSize(), rating);
|
||||
const fsd = ship.getFSD();
|
||||
fsd.setItem('fsd', fsd.getSize(), fsdPPException || rating);
|
||||
const ls = ship.getLifeSupport();
|
||||
ls.setItem('lifesupport', ls.getSize(), rating);
|
||||
const pd = ship.getPowerDistributor();
|
||||
pd.setItem('powerdistributor', pd.getSize(), rating);
|
||||
const sen = ship.getSensors();
|
||||
sen.setItem('sensors', sen.getSize(), rating);
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trader Build
|
||||
* @param {Boolean} shielded True if shield generator should be included
|
||||
* Creates a new slot for a given module.
|
||||
* @param {Module} m Module to create the slot for
|
||||
* @param {function} warning Warning callback
|
||||
* @return {React.Component} Slot component
|
||||
*/
|
||||
_optimizeCargo(shielded) {
|
||||
ShipRoles.trader(this.props.ship, shielded);
|
||||
this.props.onChange();
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Miner Build
|
||||
* @param {Boolean} shielded True if shield generator should be included
|
||||
*/
|
||||
_optimizeMiner(shielded) {
|
||||
ShipRoles.miner(this.props.ship, shielded);
|
||||
this.props.onChange();
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Explorer role
|
||||
* @param {Boolean} planetary True if Planetary Vehicle Hangar (PVH) should be included
|
||||
*/
|
||||
_optimizeExplorer(planetary) {
|
||||
ShipRoles.explorer(this.props.ship, planetary);
|
||||
this.props.onChange();
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Racer role
|
||||
*/
|
||||
_optimizeRacer() {
|
||||
ShipRoles.racer(this.props.ship);
|
||||
this.props.onChange();
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the specified bulkhead
|
||||
* @param {Object} bulkhead Bulkhead module details
|
||||
*/
|
||||
_selectBulkhead(bulkhead) {
|
||||
this.props.ship.useBulkhead(bulkhead.index);
|
||||
this.context.tooltip();
|
||||
this.props.onChange();
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* On right click optimize the standard modules
|
||||
*/
|
||||
_contextMenu() {
|
||||
this._optimizeStandard();
|
||||
_mkSlot(m, warning) {
|
||||
const { currentMenu, propsToShow, onPropToggle } = this.props;
|
||||
return <Slot key={m.getSlot()} m={m} warning={warning} hideSearch={true}
|
||||
currentMenu={currentMenu} propsToShow={propsToShow} onPropToggle={onPropToggle}
|
||||
/>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,108 +69,30 @@ export default class StandardSlotSection extends SlotSection {
|
||||
* @return {Array} Array of Slots
|
||||
*/
|
||||
_getSlots() {
|
||||
let { ship, currentMenu, cargo, fuel } = this.props;
|
||||
let slots = new Array(8);
|
||||
let open = this._openMenu;
|
||||
let select = this._selectModule;
|
||||
let st = ship.standard;
|
||||
let avail = ship.getAvailableModules().standard;
|
||||
let bh = ship.bulkheads;
|
||||
|
||||
slots[0] = <StandardSlot
|
||||
key='bh'
|
||||
slot={bh}
|
||||
modules={ship.getAvailableModules().bulkheads}
|
||||
onOpen={open.bind(this, bh)}
|
||||
onSelect={this._selectBulkhead}
|
||||
selected={currentMenu == bh}
|
||||
onChange={this.props.onChange}
|
||||
ship={ship}
|
||||
/>;
|
||||
|
||||
slots[1] = <StandardSlot
|
||||
key='pp'
|
||||
slot={st[0]}
|
||||
modules={avail[0]}
|
||||
onOpen={open.bind(this, st[0])}
|
||||
onSelect={select.bind(this, st[0])}
|
||||
selected={currentMenu == st[0]}
|
||||
onChange={this.props.onChange}
|
||||
ship={ship}
|
||||
warning={m => m instanceof Module ? m.getPowerGeneration() < ship.powerRetracted : m.pgen < ship.powerRetracted}
|
||||
/>;
|
||||
|
||||
slots[2] = <StandardSlot
|
||||
key='th'
|
||||
slot={st[1]}
|
||||
modules={avail[1]}
|
||||
onOpen={open.bind(this, st[1])}
|
||||
onSelect={select.bind(this, st[1])}
|
||||
selected={currentMenu == st[1]}
|
||||
onChange={this.props.onChange}
|
||||
ship={ship}
|
||||
warning={m => m instanceof Module ? m.getMaxMass() < (ship.unladenMass + cargo + fuel - st[1].m.mass + m.mass) : m.maxmass < (ship.unladenMass + cargo + fuel - st[1].m.mass + m.mass)}
|
||||
/>;
|
||||
|
||||
|
||||
slots[3] = <StandardSlot
|
||||
key='fsd'
|
||||
slot={st[2]}
|
||||
modules={avail[2]}
|
||||
onOpen={open.bind(this, st[2])}
|
||||
onSelect={select.bind(this, st[2])}
|
||||
onChange={this.props.onChange}
|
||||
ship={ship}
|
||||
selected={currentMenu == st[2]}
|
||||
/>;
|
||||
|
||||
slots[4] = <StandardSlot
|
||||
key='ls'
|
||||
slot={st[3]}
|
||||
modules={avail[3]}
|
||||
onOpen={open.bind(this, st[3])}
|
||||
onSelect={select.bind(this, st[3])}
|
||||
onChange={this.props.onChange}
|
||||
ship={ship}
|
||||
selected={currentMenu == st[3]}
|
||||
/>;
|
||||
|
||||
slots[5] = <StandardSlot
|
||||
key='pd'
|
||||
slot={st[4]}
|
||||
modules={avail[4]}
|
||||
onOpen={open.bind(this, st[4])}
|
||||
onSelect={select.bind(this, st[4])}
|
||||
selected={currentMenu == st[4]}
|
||||
onChange={this.props.onChange}
|
||||
ship={ship}
|
||||
warning={m => m instanceof Module ? m.getEnginesCapacity() <= ship.boostEnergy : m.engcap <= ship.boostEnergy}
|
||||
/>;
|
||||
|
||||
slots[6] = <StandardSlot
|
||||
key='ss'
|
||||
slot={st[5]}
|
||||
modules={avail[5]}
|
||||
onOpen={open.bind(this, st[5])}
|
||||
onSelect={select.bind(this, st[5])}
|
||||
selected={currentMenu == st[5]}
|
||||
onChange={this.props.onChange}
|
||||
ship={ship}
|
||||
/>;
|
||||
|
||||
slots[7] = <StandardSlot
|
||||
key='ft'
|
||||
slot={st[6]}
|
||||
modules={avail[6]}
|
||||
onOpen={open.bind(this, st[6])}
|
||||
onSelect={select.bind(this, st[6])}
|
||||
selected={currentMenu == st[6]}
|
||||
onChange={this.props.onChange}
|
||||
ship={ship}
|
||||
warning= {m => m.fuel < st[2].m.maxfuel} // Show warning when fuel tank is smaller than FSD Max Fuel
|
||||
/>;
|
||||
|
||||
return slots;
|
||||
const { ship } = this.props;
|
||||
const fsd = ship.getFSD();
|
||||
return [
|
||||
this._mkSlot(ship.getAlloys()),
|
||||
this._mkSlot(
|
||||
ship.getPowerPlant(),
|
||||
(m) => moduleGet(m, 'powercapacity') < ship.get(CONSUMED_RETR),
|
||||
),
|
||||
this._mkSlot(
|
||||
ship.getThrusters(),
|
||||
(m) => moduleGet(m, 'enginemaximalmass') < ship.get(LADEN_MASS),
|
||||
),
|
||||
this._mkSlot(fsd),
|
||||
this._mkSlot(
|
||||
ship.getPowerDistributor(),
|
||||
(m) => moduleGet(m, 'enginescapacity') <= ship.readProp('boostenergy'),
|
||||
),
|
||||
this._mkSlot(ship.getLifeSupport()),
|
||||
this._mkSlot(ship.getSensors()),
|
||||
this._mkSlot(
|
||||
ship.getCoreFuelTank(),
|
||||
(m) => moduleGet(m, 'fuel') < fsd.get('maxfuel')
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,25 +100,18 @@ export default class StandardSlotSection extends SlotSection {
|
||||
* @param {Function} translate Translate function
|
||||
* @return {React.Component} Section menu
|
||||
*/
|
||||
_getSectionMenu(translate) {
|
||||
let planetaryDisabled = this.props.ship.internal.length < 4;
|
||||
_getSectionMenu() {
|
||||
const { translate } = this.context.language;
|
||||
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._emptyAll}>{translate('empty all slots')}</li>
|
||||
</ul>
|
||||
<div className='select-group cap'>{translate('roles')}</div>
|
||||
<div className='select-group cap'>{translate('core')}</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._nRated.bind(this, '5', undefined)}>{translate('A-rated')}</li>
|
||||
<li className='lc' tabIndex="0" onClick={this._nRated.bind(this, '2', undefined)}>{translate('D-rated')}</li>
|
||||
<li className='lc' tabIndex="0" onClick={this._nRated.bind(this, '2', '5')}>{translate('D-rated + A-rated FSD/PP')}</li>
|
||||
</ul>
|
||||
</div>;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 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)
|
||||
*/
|
||||
@@ -651,9 +741,9 @@ export class Modified extends SvgIcon {
|
||||
*/
|
||||
svg() {
|
||||
return <g>
|
||||
<path d="M100,5L18,52.5L18,147.5L100,195L182,147.5L182,52.5L100,5Z"/>
|
||||
<path d="M100,70L74,85L74,115L100,130L126,115L126,85L100,70Z"/>
|
||||
</g>;
|
||||
<path d="M100,5L18,52.5L18,147.5L100,195L182,147.5L182,52.5L100,5Z"/>
|
||||
<path d="M100,70L74,85L74,115L100,130L126,115L126,85L100,70Z"/>
|
||||
</g>;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import TranslatedComponent from './TranslatedComponent';
|
||||
* Document Root Tooltip
|
||||
*/
|
||||
export default class Tooltip extends TranslatedComponent {
|
||||
|
||||
static propTypes = {
|
||||
rect: PropTypes.object.isRequired,
|
||||
options: PropTypes.object
|
||||
@@ -127,5 +126,4 @@ export default class Tooltip extends TranslatedComponent {
|
||||
</div>
|
||||
</div>;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import { shallowEqual } from '../utils/UtilityFunctions';
|
||||
* Abstract Translated Component
|
||||
*/
|
||||
export default class TranslatedComponent extends React.Component {
|
||||
|
||||
static contextTypes = {
|
||||
language: PropTypes.object.isRequired,
|
||||
sizeRatio: PropTypes.number.isRequired,
|
||||
|
||||
@@ -1,84 +1,68 @@
|
||||
import React from 'react';
|
||||
import cn from 'classnames';
|
||||
import SlotSection from './SlotSection';
|
||||
import HardpointSlot from './HardpointSlot';
|
||||
import Slot from './Slot';
|
||||
import { stopCtxPropagation } from '../utils/UtilityFunctions';
|
||||
import autoBind from 'auto-bind';
|
||||
|
||||
/**
|
||||
* Utility Slot Section
|
||||
*/
|
||||
export default class UtilitySlotSection extends SlotSection {
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props, context, 'utility', 'utility mounts');
|
||||
this._empty = this._empty.bind(this);
|
||||
constructor(props) {
|
||||
super(props, 'utility mounts');
|
||||
autoBind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty all utility slots and close the menu
|
||||
*/
|
||||
_empty() {
|
||||
this.props.ship.emptyUtility();
|
||||
this.props.onChange();
|
||||
this.props.ship.getUtilities().forEach((slot) => slot.reset());
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mount module in utility slot, replace all if Alt is held
|
||||
* @param {string} group Module Group name
|
||||
* @param {string} type Module type
|
||||
* @param {string} rating Module Rating
|
||||
* @param {string} name Module name
|
||||
* @param {Synthetic} event Event
|
||||
*/
|
||||
_use(group, rating, name, event) {
|
||||
this.props.ship.useUtility(group, rating, name, event.getModifierState('Alt'));
|
||||
this.props.onChange();
|
||||
_use(type, rating, event) {
|
||||
const fillAll = event.getModifierState('Alt');
|
||||
for (const slot of this.props.ship.getUtilities(undefined, true)) {
|
||||
if (slot.isEmpty() || fillAll) {
|
||||
slot.setItem(type, '', rating);
|
||||
}
|
||||
}
|
||||
this._close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty all utility slots on right-click
|
||||
*/
|
||||
_contextMenu() {
|
||||
this._empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create all HardpointSlots (React component) for the slots
|
||||
* @return {Array} Array of HardpointSlots
|
||||
*/
|
||||
_getSlots() {
|
||||
let slots = [];
|
||||
let { ship, currentMenu } = this.props;
|
||||
let hardpoints = ship.hardpoints;
|
||||
let { ship, currentMenu, propsToShow, onPropToggle } = this.props;
|
||||
let { originSlot, targetSlot } = this.state;
|
||||
let availableModules = ship.getAvailableModules();
|
||||
|
||||
for (let i = 0, l = hardpoints.length; i < l; i++) {
|
||||
let h = hardpoints[i];
|
||||
if (h.maxClass === 0) {
|
||||
slots.push(<HardpointSlot
|
||||
key={i}
|
||||
maxClass={h.maxClass}
|
||||
availableModules={() => availableModules.getHps(h.maxClass)}
|
||||
onOpen={this._openMenu.bind(this,h)}
|
||||
onSelect={this._selectModule.bind(this, h)}
|
||||
onChange={this.props.onChange}
|
||||
selected={currentMenu == h}
|
||||
drag={this._drag.bind(this, h)}
|
||||
dragOver={this._dragOverSlot.bind(this, h)}
|
||||
drop={this._drop}
|
||||
dropClass={this._dropClass(h, originSlot, targetSlot)}
|
||||
enabled={h.enabled}
|
||||
ship={ship}
|
||||
m={h.m}
|
||||
/>);
|
||||
}
|
||||
for (let h of ship.getUtilities(undefined, true)) {
|
||||
slots.push(<Slot
|
||||
key={h.object.Slot}
|
||||
currentMenu={currentMenu}
|
||||
drag={this._drag.bind(this, h)}
|
||||
dragOver={this._dragOverSlot.bind(this, h)}
|
||||
drop={this._drop}
|
||||
dropClass={this._dropClass(h, originSlot, targetSlot)}
|
||||
m={h}
|
||||
enabled={h.enabled ? true : false}
|
||||
propsToShow={propsToShow}
|
||||
onPropToggle={onPropToggle}
|
||||
/>);
|
||||
}
|
||||
|
||||
return slots;
|
||||
@@ -89,35 +73,35 @@ export default class UtilitySlotSection extends SlotSection {
|
||||
* @param {Function} translate Translate function
|
||||
* @return {React.Component} Section menu
|
||||
*/
|
||||
_getSectionMenu(translate) {
|
||||
_getSectionMenu() {
|
||||
const { translate } = this.context.language;
|
||||
let _use = this._use;
|
||||
|
||||
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}>{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, 'shieldbooster', '5')}>A</li>
|
||||
<li className='c' tabIndex='0' onClick={_use.bind(this, 'shieldbooster', '4')}>B</li>
|
||||
<li className='c' tabIndex='0' onClick={_use.bind(this, 'shieldbooster', '3')}>C</li>
|
||||
<li className='c' tabIndex='0' onClick={_use.bind(this, 'shieldbooster', '2')}>D</li>
|
||||
<li className='c' tabIndex='0' onClick={_use.bind(this, 'shieldbooster', '1')}>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, 'heatsinklauncher', '')}>{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, 'chafflauncher', '')}>{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, 'pointdefence', '')}>{translate('Point Defence')}</li>
|
||||
</ul>
|
||||
</div>;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,111 +1,80 @@
|
||||
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, LabelList } 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' fill={CORIOLIS_COLOURS[0]} isAnimationActive={false} onMouseOver={this._termtip} onMouseOut={tooltip.bind(null, null)}>
|
||||
<LabelList dataKey='value' position='insideTop'/>
|
||||
</Bar>
|
||||
</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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,204 +1,104 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import TranslatedComponent from './TranslatedComponent';
|
||||
import { Ships } from 'coriolis-data/dist';
|
||||
import { nameComparator } from '../utils/SlotFunctions';
|
||||
import { CollapseSection, ExpandSection, MountFixed, MountGimballed, MountTurret } from './SvgIcons';
|
||||
import LineChart from '../components/LineChart';
|
||||
import Slider from '../components/Slider';
|
||||
import * as Calc from '../shipyard/Calculations';
|
||||
import Module from '../shipyard/Module';
|
||||
import { moduleReduce } from 'ed-forge/lib/src/helper';
|
||||
import { chain, keys, mapValues, values } from 'lodash';
|
||||
|
||||
const DAMAGE_DEALT_COLORS = ['#FFFFFF', '#FF0000', '#00FF00', '#7777FF', '#FFFF00', '#FF00FF', '#00FFFF', '#777777'];
|
||||
const PORTION_MAPPINGS = {
|
||||
'absolute': 'absolutedamageportion',
|
||||
'explosive': 'explosivedamageportion',
|
||||
'kinetic': 'kineticdamageportion',
|
||||
'thermal': 'thermicdamageportion',
|
||||
};
|
||||
const MULTS = keys(PORTION_MAPPINGS);
|
||||
|
||||
// TODO: help with this in ed-forge
|
||||
/**
|
||||
* .
|
||||
* @param {Object} opponentDefence .
|
||||
* @returns {Object} .
|
||||
*/
|
||||
function defenceToMults(opponentDefence) {
|
||||
return chain(opponentDefence)
|
||||
.pick(MULTS)
|
||||
.mapKeys((v, k) => PORTION_MAPPINGS[k])
|
||||
.mapValues((resistanceProfile) => resistanceProfile.damageMultiplier)
|
||||
.value();
|
||||
}
|
||||
|
||||
/**
|
||||
* Weapon damage chart
|
||||
*/
|
||||
export default class WeaponDamageChart extends TranslatedComponent {
|
||||
static propTypes = {
|
||||
code: PropTypes.string.isRequired,
|
||||
ship: PropTypes.object.isRequired,
|
||||
opponent: PropTypes.object.isRequired,
|
||||
hull: PropTypes.bool.isRequired,
|
||||
opponentDefence: PropTypes.object.isRequired,
|
||||
engagementRange: PropTypes.number.isRequired,
|
||||
opponentSys: PropTypes.number.isRequired,
|
||||
marker: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param {Object} props React Component properties
|
||||
* @param {Object} context React Component context
|
||||
*/
|
||||
constructor(props, context) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the initial weapons state
|
||||
*/
|
||||
componentWillMount() {
|
||||
const weaponNames = this._weaponNames(this.props.ship, this.context);
|
||||
const opponentShields = Calc.shieldMetrics(this.props.opponent, this.props.opponentSys);
|
||||
const opponentArmour = Calc.armourMetrics(this.props.opponent);
|
||||
const maxRange = this._calcMaxRange(this.props.ship);
|
||||
const maxDps = this._calcMaxSDps(this.props.ship, this.props.opponent, opponentShields, opponentArmour);
|
||||
|
||||
this.setState({ maxRange, maxDps, weaponNames, opponentShields, opponentArmour, calcSDpsFunc: this._calcSDps.bind(this, this.props.ship, weaponNames, this.props.opponent, opponentShields, opponentArmour, this.props.hull) });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the updated weapons state if our ship changes
|
||||
* @param {Object} nextProps Incoming/Next properties
|
||||
* @param {Object} nextContext Incoming/Next conext
|
||||
* @return {boolean} Returns true if the component should be rerendered
|
||||
*/
|
||||
componentWillReceiveProps(nextProps, nextContext) {
|
||||
if (nextProps.marker != this.props.marker) {
|
||||
const weaponNames = this._weaponNames(nextProps.ship, nextContext);
|
||||
const opponentShields = Calc.shieldMetrics(nextProps.opponent, nextProps.opponentSys);
|
||||
const opponentArmour = Calc.armourMetrics(nextProps.opponent);
|
||||
const maxRange = this._calcMaxRange(nextProps.ship);
|
||||
const maxDps = this._calcMaxSDps(nextProps.ship, nextProps.opponent, opponentShields, opponentArmour);
|
||||
this.setState({ weaponNames,
|
||||
opponentShields,
|
||||
opponentArmour,
|
||||
maxRange,
|
||||
maxDps,
|
||||
calcSDpsFunc: this._calcSDps.bind(this, nextProps.ship, weaponNames, nextProps.opponent, opponentShields, opponentArmour, nextProps.hull)
|
||||
});
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the maximum range of a ship's weapons
|
||||
* @param {Object} ship The ship
|
||||
* @returns {int} The maximum range, in metres
|
||||
*/
|
||||
_calcMaxRange(ship) {
|
||||
let maxRange = 1000; // Minimum
|
||||
for (let i = 0; i < ship.hardpoints.length; i++) {
|
||||
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
|
||||
const thisRange = ship.hardpoints[i].m.getRange();
|
||||
if (thisRange > maxRange) {
|
||||
maxRange = thisRange;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return maxRange;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the maximum sustained single-weapon DPS for this ship
|
||||
* @param {Object} ship The ship
|
||||
* @param {Object} opponent The opponent ship
|
||||
* @param {Object} opponentShields The opponent's shields
|
||||
* @param {Object} opponentArmour The opponent's armour
|
||||
* @return {number} The maximum sustained single-weapon DPS
|
||||
*/
|
||||
_calcMaxSDps(ship, opponent, opponentShields, opponentArmour) {
|
||||
// Additional information to allow effectiveness calculations
|
||||
let maxSDps = 0;
|
||||
for (let i = 0; i < ship.hardpoints.length; i++) {
|
||||
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
|
||||
const m = ship.hardpoints[i].m;
|
||||
|
||||
const sustainedDps = Calc._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, 0);
|
||||
const thisSDps = sustainedDps.damage.armour.total > sustainedDps.damage.shields.total ? sustainedDps.damage.armour.total : sustainedDps.damage.shields.total;
|
||||
if (thisSDps > maxSDps) {
|
||||
maxSDps = thisSDps;
|
||||
}
|
||||
}
|
||||
}
|
||||
return maxSDps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain the weapon names for this ship
|
||||
* @param {Object} ship The ship
|
||||
* @param {Object} context The context
|
||||
* @return {array} The weapon names
|
||||
*/
|
||||
_weaponNames(ship, context) {
|
||||
const translate = context.language.translate;
|
||||
let names = [];
|
||||
let num = 1;
|
||||
for (let i = 0; i < ship.hardpoints.length; i++) {
|
||||
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
|
||||
const m = ship.hardpoints[i].m;
|
||||
let name = '' + num++ + ': ' + m.class + m.rating + (m.missile ? '/' + m.missile : '') + ' ' + translate(m.name || m.grp);
|
||||
let engineering;
|
||||
if (m.blueprint && m.blueprint.name) {
|
||||
engineering = translate(m.blueprint.name) + ' ' + translate('grade') + ' ' + m.blueprint.grade;
|
||||
if (m.blueprint.special && m.blueprint.special.id) {
|
||||
engineering += ', ' + translate(m.blueprint.special.name);
|
||||
}
|
||||
}
|
||||
if (engineering) {
|
||||
name = name + ' (' + engineering + ')';
|
||||
}
|
||||
names.push(name);
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the per-weapon sustained DPS for this ship against another ship at a given range
|
||||
* @param {Object} ship The ship
|
||||
* @param {Object} weaponNames The names of the weapons for which to calculate DPS
|
||||
* @param {Object} opponent The target
|
||||
* @param {Object} opponentShields The opponent's shields
|
||||
* @param {Object} opponentArmour The opponent's armour
|
||||
* @param {bool} hull true if to calculate against hull, false if to calculate against shields
|
||||
* @param {Object} engagementRange The engagement range
|
||||
* @return {array} The array of weapon DPS
|
||||
*/
|
||||
_calcSDps(ship, weaponNames, opponent, opponentShields, opponentArmour, hull, engagementRange) {
|
||||
let results = {};
|
||||
let weaponNum = 0;
|
||||
for (let i = 0; i < ship.hardpoints.length; i++) {
|
||||
if (ship.hardpoints[i].maxClass > 0 && ship.hardpoints[i].m && ship.hardpoints[i].enabled) {
|
||||
const m = ship.hardpoints[i].m;
|
||||
const sustainedDps = Calc._weaponSustainedDps(m, opponent, opponentShields, opponentArmour, engagementRange);
|
||||
results[weaponNames[weaponNum++]] = hull ? sustainedDps.damage.armour.total : sustainedDps.damage.shields.total;
|
||||
}
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render damage dealt
|
||||
* @return {React.Component} contents
|
||||
*/
|
||||
render() {
|
||||
const { language, onWindowResize, sizeRatio, tooltip, termtip } = this.context;
|
||||
const { formats, translate, units } = language;
|
||||
const { maxRange } = this.state;
|
||||
const { ship, opponent, engagementRange } = this.props;
|
||||
const { language } = this.context;
|
||||
const { translate } = language;
|
||||
const { code, ship, opponentDefence, engagementRange } = this.props;
|
||||
|
||||
const sortOrder = this._sortOrder;
|
||||
const onCollapseExpand = this._onCollapseExpand;
|
||||
|
||||
const code = `${ship.toString()}:${opponent.toString()}`;
|
||||
const hardpoints = ship.getHardpoints();
|
||||
const hardpointsMap = chain(hardpoints)
|
||||
.map((m) => [m.getSlot(), m])
|
||||
.fromPairs()
|
||||
.value();
|
||||
const mults = defenceToMults(opponentDefence);
|
||||
const cb = (range) => {
|
||||
return mapValues(
|
||||
hardpointsMap,
|
||||
(m) => {
|
||||
const sdps = m.get('sustaineddamagepersecond', true);
|
||||
const resistanceMul = chain(mults)
|
||||
.toPairs()
|
||||
.map((pair) => {
|
||||
const [k, mul] = pair;
|
||||
return m.get(k, true) * mul;
|
||||
})
|
||||
.sum()
|
||||
.value();
|
||||
|
||||
const falloff = m.get('damagefalloffrange', true);
|
||||
const rangeMul = Math.min(1, Math.max(0,
|
||||
1 - (range - falloff) / (m.get('maximumrange', true) - falloff)
|
||||
));
|
||||
return sdps * resistanceMul * rangeMul;
|
||||
}
|
||||
);
|
||||
};
|
||||
return (
|
||||
<span>
|
||||
<div>
|
||||
<LineChart
|
||||
xMax={maxRange}
|
||||
yMax={this.state.maxDps}
|
||||
xMin={0}
|
||||
xMax={moduleReduce(
|
||||
hardpoints, 'maximumrange', true, (a, v) => Math.max(a, v), 1000,
|
||||
)}
|
||||
yMin={0}
|
||||
// Factor in highest damage multiplier to get a safe upper bound
|
||||
yMax={Math.max(1, ...values(mults)) * moduleReduce(
|
||||
hardpoints, 'sustaineddamagepersecond', true, (a, v) => Math.max(a, v), 0,
|
||||
)}
|
||||
xLabel={translate('range')}
|
||||
xUnit={translate('m')}
|
||||
yLabel={translate('sdps')}
|
||||
series={this.state.weaponNames}
|
||||
xMark={this.props.engagementRange}
|
||||
yLabel={translate('sustaineddamagepersecond')}
|
||||
series={hardpoints.map((m) => m.getSlot())}
|
||||
xMark={engagementRange}
|
||||
colors={DAMAGE_DEALT_COLORS}
|
||||
func={this.state.calcSDpsFunc}
|
||||
func={cb}
|
||||
points={200}
|
||||
code={code}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ 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 CN from './cn';
|
||||
import * as d3 from 'd3';
|
||||
|
||||
let fallbackTerms = EN.terms;
|
||||
@@ -25,6 +27,8 @@ export function getLanguage(langCode) {
|
||||
case 'it': lang = IT; break;
|
||||
case 'ru': lang = RU; break;
|
||||
case 'pl': lang = PL; break;
|
||||
case 'pt': lang = PT; break;
|
||||
case 'cn': lang = CN; break;
|
||||
default:
|
||||
lang = EN;
|
||||
}
|
||||
@@ -58,17 +62,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 +95,7 @@ export const Languages = {
|
||||
es: 'Español',
|
||||
fr: 'Français',
|
||||
ru: 'ру́сский',
|
||||
pl: 'polski'
|
||||
pl: 'polski',
|
||||
pt: 'português',
|
||||
cn: '中文'
|
||||
};
|
||||
|
||||
16
src/app/i18n/cn.js
Normal file
16
src/app/i18n/cn.js
Normal file
@@ -0,0 +1,16 @@
|
||||
export const formats = {
|
||||
decimal: '.',
|
||||
thousands: ',',
|
||||
grouping: [3],
|
||||
currency: ['¥', ''],
|
||||
dateTime: '%a %b %e %X %Y',
|
||||
date: '%Y年%m月%d日',
|
||||
time: '%H:%M:%S',
|
||||
periods: ['AM', 'PM'],
|
||||
days: ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'],
|
||||
shortDays: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
|
||||
months: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月'],
|
||||
shortMonths: ['一月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '十一月', '十二月']
|
||||
};
|
||||
|
||||
export { default as terms } from './cn.json';
|
||||
405
src/app/i18n/cn.json
Normal file
405
src/app/i18n/cn.json
Normal file
File diff suppressed because one or more lines are too long
@@ -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';
|
||||
450
src/app/i18n/de.json
Normal file
450
src/app/i18n/de.json
Normal file
File diff suppressed because one or more lines are too long
@@ -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' companion API is working. To do this check the 'Companion App' 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 'Shipyard' tab. At the right-hand side of each ship is an 'Export to Coriolis' button that will open your default web browser in Coriolis with the ship'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' companion API is working go to 'Settings ->Configuration' and set the 'Preferred Shipyard' to 'Coriolis'. Once this is set up clicking on your ship in the main window will open your default web browser in Coriolis with the ship'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 'Alt' 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'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 'Settings' 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's damage to the opponent'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's weapons with the opponent'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'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'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' 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' 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's hull and shield resistances, and the target'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'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'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';
|
||||
366
src/app/i18n/en.json
Normal file
366
src/app/i18n/en.json
Normal file
File diff suppressed because one or more lines are too long
@@ -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
193
src/app/i18n/es.json
Normal 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"
|
||||
}
|
||||
@@ -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
133
src/app/i18n/fr.json
Normal 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",
|
||||
"farthest 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"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user